resolve conflict with main

This commit is contained in:
Manoj Kumar 2026-03-17 19:35:19 +05:30
commit d8cca73780
No known key found for this signature in database
GPG Key ID: E952B7234D2C6F88
80 changed files with 11744 additions and 1949 deletions

2
.github/CODEOWNERS vendored
View File

@ -20,3 +20,5 @@
.pre-commit-config.yaml @jbampton
/.github/linters/ @jbampton
/plugins/network-elements/nsx/ @Pearl1594 @nvazquez

View File

@ -146,6 +146,7 @@ jobs:
smoke/test_vm_snapshot_kvm
smoke/test_vm_snapshots
smoke/test_volumes
smoke/test_vpc_conserve_mode
smoke/test_vpc_ipv6
smoke/test_vpc_redundant
smoke/test_vpc_router_nics

View File

@ -24,15 +24,18 @@ import com.cloud.network.Network;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.command.admin.config.ResetCfgCmd;
import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd;
import org.apache.cloudstack.api.command.admin.network.CloneNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.CreateGuestNetworkIpv6PrefixCmd;
import org.apache.cloudstack.api.command.admin.network.CreateManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.DeleteGuestNetworkIpv6PrefixCmd;
import org.apache.cloudstack.api.command.admin.network.DeleteManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.network.DeleteNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.ListGuestNetworkIpv6PrefixesCmd;
import org.apache.cloudstack.api.command.admin.network.NetworkOfferingBaseCmd;
import org.apache.cloudstack.api.command.admin.network.UpdateNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.UpdatePodManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.offering.CloneDiskOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.CloneServiceOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.CreateDiskOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.DeleteDiskOfferingCmd;
@ -105,6 +108,33 @@ public interface ConfigurationService {
*/
ServiceOffering createServiceOffering(CreateServiceOfferingCmd cmd);
/**
* Clones a service offering with optional parameter overrides
*
* @param cmd
* the command object that specifies the source offering ID and optional parameter overrides
* @return the newly created service offering cloned from source, null otherwise
*/
ServiceOffering cloneServiceOffering(CloneServiceOfferingCmd cmd);
/**
* Clones a disk offering with optional parameter overrides
*
* @param cmd
* the command object that specifies the source offering ID and optional parameter overrides
* @return the newly created disk offering cloned from source, null otherwise
*/
DiskOffering cloneDiskOffering(CloneDiskOfferingCmd cmd);
/**
* Clones a network offering with optional parameter overrides
*
* @param cmd
* the command object that specifies the source offering ID and optional parameter overrides
* @return the newly created network offering cloned from source, null otherwise
*/
NetworkOffering cloneNetworkOffering(CloneNetworkOfferingCmd cmd);
/**
* Updates a service offering
*
@ -282,7 +312,7 @@ public interface ConfigurationService {
boolean releasePublicIpRange(ReleasePublicIpRangeCmd cmd);
NetworkOffering createNetworkOffering(CreateNetworkOfferingCmd cmd);
NetworkOffering createNetworkOffering(NetworkOfferingBaseCmd cmd);
NetworkOffering updateNetworkOffering(UpdateNetworkOfferingCmd cmd);

View File

@ -378,11 +378,13 @@ public class EventTypes {
// Service Offerings
public static final String EVENT_SERVICE_OFFERING_CREATE = "SERVICE.OFFERING.CREATE";
public static final String EVENT_SERVICE_OFFERING_CLONE = "SERVICE.OFFERING.CLONE";
public static final String EVENT_SERVICE_OFFERING_EDIT = "SERVICE.OFFERING.EDIT";
public static final String EVENT_SERVICE_OFFERING_DELETE = "SERVICE.OFFERING.DELETE";
// Disk Offerings
public static final String EVENT_DISK_OFFERING_CREATE = "DISK.OFFERING.CREATE";
public static final String EVENT_DISK_OFFERING_CLONE = "DISK.OFFERING.CLONE";
public static final String EVENT_DISK_OFFERING_EDIT = "DISK.OFFERING.EDIT";
public static final String EVENT_DISK_OFFERING_DELETE = "DISK.OFFERING.DELETE";
@ -403,6 +405,7 @@ public class EventTypes {
// Network offerings
public static final String EVENT_NETWORK_OFFERING_CREATE = "NETWORK.OFFERING.CREATE";
public static final String EVENT_NETWORK_OFFERING_CLONE = "NETWORK.OFFERING.CLONE";
public static final String EVENT_NETWORK_OFFERING_ASSIGN = "NETWORK.OFFERING.ASSIGN";
public static final String EVENT_NETWORK_OFFERING_EDIT = "NETWORK.OFFERING.EDIT";
public static final String EVENT_NETWORK_OFFERING_REMOVE = "NETWORK.OFFERING.REMOVE";
@ -602,6 +605,7 @@ public class EventTypes {
// VPC offerings
public static final String EVENT_VPC_OFFERING_CREATE = "VPC.OFFERING.CREATE";
public static final String EVENT_VPC_OFFERING_CLONE = "VPC.OFFERING.CLONE";
public static final String EVENT_VPC_OFFERING_UPDATE = "VPC.OFFERING.UPDATE";
public static final String EVENT_VPC_OFFERING_DELETE = "VPC.OFFERING.DELETE";
@ -634,6 +638,7 @@ public class EventTypes {
// Backup and Recovery events
public static final String EVENT_VM_BACKUP_IMPORT_OFFERING = "BACKUP.IMPORT.OFFERING";
public static final String EVENT_VM_BACKUP_OFFERING_CLONE = "BACKUP.OFFERING.CLONE";
public static final String EVENT_VM_BACKUP_OFFERING_ASSIGN = "BACKUP.OFFERING.ASSIGN";
public static final String EVENT_VM_BACKUP_OFFERING_REMOVE = "BACKUP.OFFERING.REMOVE";
public static final String EVENT_VM_BACKUP_CREATE = "BACKUP.CREATE";
@ -1059,11 +1064,13 @@ public class EventTypes {
// Service Offerings
entityEventDetails.put(EVENT_SERVICE_OFFERING_CREATE, ServiceOffering.class);
entityEventDetails.put(EVENT_SERVICE_OFFERING_CLONE, ServiceOffering.class);
entityEventDetails.put(EVENT_SERVICE_OFFERING_EDIT, ServiceOffering.class);
entityEventDetails.put(EVENT_SERVICE_OFFERING_DELETE, ServiceOffering.class);
// Disk Offerings
entityEventDetails.put(EVENT_DISK_OFFERING_CREATE, DiskOffering.class);
entityEventDetails.put(EVENT_DISK_OFFERING_CLONE, DiskOffering.class);
entityEventDetails.put(EVENT_DISK_OFFERING_EDIT, DiskOffering.class);
entityEventDetails.put(EVENT_DISK_OFFERING_DELETE, DiskOffering.class);
@ -1084,6 +1091,7 @@ public class EventTypes {
// Network offerings
entityEventDetails.put(EVENT_NETWORK_OFFERING_CREATE, NetworkOffering.class);
entityEventDetails.put(EVENT_NETWORK_OFFERING_CLONE, NetworkOffering.class);
entityEventDetails.put(EVENT_NETWORK_OFFERING_ASSIGN, NetworkOffering.class);
entityEventDetails.put(EVENT_NETWORK_OFFERING_EDIT, NetworkOffering.class);
entityEventDetails.put(EVENT_NETWORK_OFFERING_REMOVE, NetworkOffering.class);

View File

@ -279,4 +279,6 @@ public interface NetworkService {
IpAddresses getIpAddressesFromIps(String ipAddress, String ip6Address, String macAddress);
String getNicVlanValueForExternalVm(NicTO nic);
Long getPreferredNetworkIdForPublicIpRuleAssignment(IpAddress ip, Long networkId);
}

View File

@ -108,7 +108,7 @@ public interface LoadBalancingRulesService {
/**
* Assign a virtual machine or list of virtual machines, or Map of <vmId vmIp> to a load balancer.
*/
boolean assignToLoadBalancer(long lbRuleId, List<Long> vmIds, Map<Long, List<String>> vmIdIpMap, boolean isAutoScaleVM);
boolean assignToLoadBalancer(long lbRuleId, List<Long> vmIds, Map<Long, List<String>> vmIdIpMap, Map<Long, Long> vmIdNetworkMap, boolean isAutoScaleVM);
boolean assignSSLCertToLoadBalancerRule(Long lbRuleId, String certName, String publicCert, String privateKey);

View File

@ -84,4 +84,6 @@ public interface VpcOffering extends InternalIdentity, Identity {
NetworkOffering.RoutingMode getRoutingMode();
Boolean isSpecifyAsNumber();
boolean isConserveMode();
}

View File

@ -20,6 +20,7 @@ package com.cloud.network.vpc;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.api.command.admin.vpc.CloneVPCOfferingCmd;
import org.apache.cloudstack.api.command.admin.vpc.CreateVPCOfferingCmd;
import org.apache.cloudstack.api.command.admin.vpc.UpdateVPCOfferingCmd;
import org.apache.cloudstack.api.command.user.vpc.ListVPCOfferingsCmd;
@ -34,12 +35,14 @@ public interface VpcProvisioningService {
VpcOffering createVpcOffering(CreateVPCOfferingCmd cmd);
VpcOffering cloneVPCOffering(CloneVPCOfferingCmd cmd);
VpcOffering createVpcOffering(String name, String displayText, List<String> supportedServices,
Map<String, List<String>> serviceProviders,
Map serviceCapabilitystList, NetUtils.InternetProtocol internetProtocol,
Long serviceOfferingId, String externalProvider, NetworkOffering.NetworkMode networkMode,
List<Long> domainIds, List<Long> zoneIds, VpcOffering.State state,
NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber);
NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber, boolean conserveMode);
Pair<List<? extends VpcOffering>,Integer> listVpcOfferings(ListVPCOfferingsCmd cmd);

View File

@ -559,6 +559,7 @@ public class ApiConstants {
public static final String USE_STORAGE_REPLICATION = "usestoragereplication";
public static final String SOURCE_CIDR_LIST = "sourcecidrlist";
public static final String SOURCE_OFFERING_ID = "sourceofferingid";
public static final String SOURCE_ZONE_ID = "sourcezoneid";
public static final String SSL_VERIFICATION = "sslverification";
public static final String START_ASN = "startasn";
@ -986,6 +987,7 @@ public class ApiConstants {
public static final String REGION_ID = "regionid";
public static final String VPC_OFF_ID = "vpcofferingid";
public static final String VPC_OFF_NAME = "vpcofferingname";
public static final String VPC_OFFERING_CONSERVE_MODE = "vpcofferingconservemode";
public static final String NETWORK = "network";
public static final String VPC_ID = "vpcid";
public static final String VPC_NAME = "vpcname";

View File

@ -0,0 +1,166 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.backup;
import javax.inject.Inject;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver;
import org.apache.cloudstack.api.response.BackupOfferingResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.backup.BackupManager;
import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.context.CallContext;
import com.cloud.event.EventTypes;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.NetworkRuleConflictException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.utils.exception.CloudRuntimeException;
import java.util.Arrays;
import java.util.List;
import java.util.function.LongFunction;
@APICommand(name = "cloneBackupOffering",
description = "Clones a backup offering from an existing offering",
responseObject = BackupOfferingResponse.class, since = "4.23.0",
authorized = {RoleType.Admin})
public class CloneBackupOfferingCmd extends BaseAsyncCmd implements DomainAndZoneIdResolver {
@Inject
protected BackupManager backupManager;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
////////////////////////////////////////////////////
@Parameter(name = ApiConstants.SOURCE_OFFERING_ID, type = BaseCmd.CommandType.UUID, entityType = BackupOfferingResponse.class,
required = true, description = "The ID of the source backup offering to clone from")
private Long sourceOfferingId;
@Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, required = true,
description = "The name of the cloned offering")
private String name;
@Parameter(name = ApiConstants.DESCRIPTION, type = BaseCmd.CommandType.STRING, required = false,
description = "The description of the cloned offering")
private String description;
@Parameter(name = ApiConstants.EXTERNAL_ID, type = BaseCmd.CommandType.STRING, required = false,
description = "The backup offering ID (from backup provider side)")
private String externalId;
@Parameter(name = ApiConstants.ZONE_ID, type = BaseCmd.CommandType.UUID, entityType = ZoneResponse.class,
description = "The zone ID", required = false)
private Long zoneId;
@Parameter(name = ApiConstants.DOMAIN_ID,
type = CommandType.STRING,
description = "the ID of the containing domain(s) as comma separated string, public for public offerings",
length = 4096)
private String domainIds;
@Parameter(name = ApiConstants.ALLOW_USER_DRIVEN_BACKUPS, type = BaseCmd.CommandType.BOOLEAN,
description = "Whether users are allowed to create adhoc backups and backup schedules", required = false)
private Boolean userDrivenBackups;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getSourceOfferingId() {
return sourceOfferingId;
}
public String getName() {
return name;
}
public String getExternalId() {
return externalId;
}
public Long getZoneId() {
return zoneId;
}
public String getDescription() {
return description;
}
public Boolean getUserDrivenBackups() {
return userDrivenBackups;
}
public List<Long> getDomainIds() {
if (domainIds != null && !domainIds.isEmpty()) {
return Arrays.asList(Arrays.stream(domainIds.split(",")).map(domainId -> Long.parseLong(domainId.trim())).toArray(Long[]::new));
}
LongFunction<List<Long>> defaultDomainsProvider = null;
if (backupManager != null) {
defaultDomainsProvider = backupManager::getBackupOfferingDomains;
}
return resolveDomainIds(domainIds, sourceOfferingId, defaultDomainsProvider, "backup offering");
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
try {
BackupOffering policy = backupManager.cloneBackupOffering(this);
if (policy == null) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to clone backup offering");
}
BackupOfferingResponse response = _responseGenerator.createBackupOfferingResponse(policy);
response.setResponseName(getCommandName());
setResponseObject(response);
} catch (InvalidParameterValueException e) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, e.getMessage());
} catch (CloudRuntimeException e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
}
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
@Override
public String getEventType() {
return EventTypes.EVENT_VM_BACKUP_OFFERING_CLONE;
}
@Override
public String getEventDescription() {
return "Cloning backup offering: " + name + " from source offering: " + (sourceOfferingId == null ? "" : sourceOfferingId.toString());
}
}

View File

@ -54,7 +54,7 @@ import java.util.Set;
public class ImportBackupOfferingCmd extends BaseAsyncCmd {
@Inject
private BackupManager backupManager;
protected BackupManager backupManager;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
@ -86,7 +86,8 @@ public class ImportBackupOfferingCmd extends BaseAsyncCmd {
type = CommandType.LIST,
collectionType = CommandType.UUID,
entityType = DomainResponse.class,
description = "the ID of the containing domain(s), null for public offerings")
description = "the ID of the containing domain(s), null for public offerings",
since = "4.23.0")
private List<Long> domainIds;
/////////////////////////////////////////////////////

View File

@ -0,0 +1,113 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.network;
import java.util.List;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.NetworkOfferingResponse;
import com.cloud.offering.NetworkOffering;
@APICommand(name = "cloneNetworkOffering",
description = "Clones a network offering. All parameters are copied from the source offering unless explicitly overridden. " +
"Use 'addServices' and 'dropServices' to modify the service list without respecifying everything.",
responseObject = NetworkOfferingResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
since = "4.23.0")
public class CloneNetworkOfferingCmd extends NetworkOfferingBaseCmd {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.SOURCE_OFFERING_ID,
type = BaseCmd.CommandType.UUID,
entityType = NetworkOfferingResponse.class,
required = true,
description = "The ID of the source network offering to clone from")
private Long sourceOfferingId;
@Parameter(name = "addservices",
type = CommandType.LIST,
collectionType = CommandType.STRING,
description = "Services to add to the cloned offering (in addition to source offering services). " +
"If specified along with 'supportedservices', this parameter is ignored.")
private List<String> addServices;
@Parameter(name = "dropservices",
type = CommandType.LIST,
collectionType = CommandType.STRING,
description = "Services to remove from the cloned offering (that exist in source offering). " +
"If specified along with 'supportedservices', this parameter is ignored.")
private List<String> dropServices;
@Parameter(name = ApiConstants.TRAFFIC_TYPE,
type = CommandType.STRING,
description = "The traffic type for the network offering. Supported type in current release is GUEST only")
private String traffictype;
@Parameter(name = ApiConstants.GUEST_IP_TYPE, type = CommandType.STRING, description = "Guest type of the network offering: Shared or Isolated")
private String guestIptype;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getSourceOfferingId() {
return sourceOfferingId;
}
public List<String> getAddServices() {
return addServices;
}
public List<String> getDropServices() {
return dropServices;
}
public String getGuestIpType() {
return guestIptype;
}
public String getTraffictype() {
return traffictype;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() {
NetworkOffering result = _configService.cloneNetworkOffering(this);
if (result != null) {
NetworkOfferingResponse response = _responseGenerator.createNetworkOfferingResponse(result);
response.setResponseName(getCommandName());
this.setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to clone network offering");
}
}
}

View File

@ -16,505 +16,47 @@
// under the License.
package org.apache.cloudstack.api.command.admin.network;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.cloud.network.Network;
import com.cloud.network.VirtualRouterProvider;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.NetworkOfferingResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.network.Network.Capability;
import com.cloud.network.Network.Service;
import com.cloud.offering.NetworkOffering;
import com.cloud.offering.NetworkOffering.Availability;
import com.cloud.user.Account;
import static com.cloud.network.Network.Service.Dhcp;
import static com.cloud.network.Network.Service.Dns;
import static com.cloud.network.Network.Service.Lb;
import static com.cloud.network.Network.Service.StaticNat;
import static com.cloud.network.Network.Service.SourceNat;
import static com.cloud.network.Network.Service.PortForwarding;
import static com.cloud.network.Network.Service.NetworkACL;
import static com.cloud.network.Network.Service.UserData;
import static com.cloud.network.Network.Service.Firewall;
import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNetrisNatted;
import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNetrisRouted;
import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNsxWithoutLb;
@APICommand(name = "createNetworkOffering", description = "Creates a network offering.", responseObject = NetworkOfferingResponse.class, since = "3.0.0",
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class CreateNetworkOfferingCmd extends BaseCmd {
public class CreateNetworkOfferingCmd extends NetworkOfferingBaseCmd {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "The name of the network offering")
private String networkOfferingName;
@Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, description = "The display text of the network offering, defaults to the value of 'name'.")
private String displayText;
@Parameter(name = ApiConstants.TRAFFIC_TYPE,
type = CommandType.STRING,
required = true,
description = "The traffic type for the network offering. Supported type in current release is GUEST only")
private String traffictype;
@Parameter(name = ApiConstants.TAGS, type = CommandType.STRING, description = "The tags for the network offering.", length = 4096)
private String tags;
@Parameter(name = ApiConstants.SPECIFY_VLAN, type = CommandType.BOOLEAN, description = "True if network offering supports VLANs")
private Boolean specifyVlan;
@Parameter(name = ApiConstants.AVAILABILITY, type = CommandType.STRING, description = "The availability of network offering. The default value is Optional. "
+ " Another value is Required, which will make it as the default network offering for new networks ")
private String availability;
@Parameter(name = ApiConstants.NETWORKRATE, type = CommandType.INTEGER, description = "Data transfer rate in megabits per second allowed")
private Integer networkRate;
@Parameter(name = ApiConstants.CONSERVE_MODE, type = CommandType.BOOLEAN, description = "True if the network offering is IP conserve mode enabled")
private Boolean conserveMode;
@Parameter(name = ApiConstants.SERVICE_OFFERING_ID,
type = CommandType.UUID,
entityType = ServiceOfferingResponse.class,
description = "The service offering ID used by virtual router provider")
private Long serviceOfferingId;
@Parameter(name = ApiConstants.GUEST_IP_TYPE, type = CommandType.STRING, required = true, description = "Guest type of the network offering: Shared or Isolated")
private String guestIptype;
@Parameter(name = ApiConstants.INTERNET_PROTOCOL,
type = CommandType.STRING,
description = "The internet protocol of network offering. Options are IPv4 and dualstack. Default is IPv4. dualstack will create a network offering that supports both IPv4 and IPv6",
since = "4.17.0")
private String internetProtocol;
@Parameter(name = ApiConstants.SUPPORTED_SERVICES,
type = CommandType.LIST,
collectionType = CommandType.STRING,
description = "Services supported by the network offering")
private List<String> supportedServices;
@Parameter(name = ApiConstants.SERVICE_PROVIDER_LIST,
type = CommandType.MAP,
description = "Provider to service mapping. If not specified, the provider for the service will be mapped to the default provider on the physical network")
private Map serviceProviderList;
@Parameter(name = ApiConstants.SERVICE_CAPABILITY_LIST, type = CommandType.MAP, description = "Desired service capabilities as part of network offering")
private Map serviceCapabilitystList;
@Parameter(name = ApiConstants.SPECIFY_IP_RANGES,
type = CommandType.BOOLEAN,
description = "True if network offering supports specifying ip ranges; defaulted to false if not specified")
private Boolean specifyIpRanges;
@Parameter(name = ApiConstants.IS_PERSISTENT,
type = CommandType.BOOLEAN,
description = "True if network offering supports persistent networks; defaulted to false if not specified")
private Boolean isPersistent;
@Parameter(name = ApiConstants.FOR_VPC,
type = CommandType.BOOLEAN,
description = "True if network offering is meant to be used for VPC, false otherwise.")
private Boolean forVpc;
@Deprecated
@Parameter(name = ApiConstants.FOR_NSX,
type = CommandType.BOOLEAN,
description = "true if network offering is meant to be used for NSX, false otherwise.",
since = "4.20.0")
private Boolean forNsx;
@Parameter(name = ApiConstants.PROVIDER,
type = CommandType.STRING,
description = "Name of the provider providing the service",
since = "4.21.0")
private String provider;
@Parameter(name = ApiConstants.NSX_SUPPORT_LB,
type = CommandType.BOOLEAN,
description = "True if network offering for NSX network offering supports Load balancer service.",
since = "4.20.0")
private Boolean nsxSupportsLbService;
@Parameter(name = ApiConstants.NSX_SUPPORTS_INTERNAL_LB,
type = CommandType.BOOLEAN,
description = "True if network offering for NSX network offering supports Internal Load balancer service.",
since = "4.20.0")
private Boolean nsxSupportsInternalLbService;
@Parameter(name = ApiConstants.NETWORK_MODE,
type = CommandType.STRING,
description = "Indicates the mode with which the network will operate. Valid option: NATTED or ROUTED",
since = "4.20.0")
private String networkMode;
@Parameter(name = ApiConstants.FOR_TUNGSTEN,
type = CommandType.BOOLEAN,
description = "True if network offering is meant to be used for Tungsten-Fabric, false otherwise.")
private Boolean forTungsten;
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, since = "4.2.0", description = "Network offering details in key/value pairs."
+ " Supported keys are internallbprovider/publiclbprovider with service provider as a value, and"
+ " promiscuousmode/macaddresschanges/forgedtransmits with true/false as value to accept/reject the security settings if available for a nic/portgroup")
protected Map details;
@Parameter(name = ApiConstants.EGRESS_DEFAULT_POLICY,
type = CommandType.BOOLEAN,
description = "True if guest network default egress policy is allow; false if default egress policy is deny")
private Boolean egressDefaultPolicy;
@Parameter(name = ApiConstants.KEEPALIVE_ENABLED,
type = CommandType.BOOLEAN,
required = false,
description = "If true keepalive will be turned on in the loadbalancer. At the time of writing this has only an effect on haproxy; the mode http and httpclose options are unset in the haproxy conf file.")
private Boolean keepAliveEnabled;
@Parameter(name = ApiConstants.MAX_CONNECTIONS,
type = CommandType.INTEGER,
description = "Maximum number of concurrent connections supported by the Network offering")
private Integer maxConnections;
@Parameter(name = ApiConstants.DOMAIN_ID,
type = CommandType.LIST,
collectionType = CommandType.UUID,
entityType = DomainResponse.class,
description = "The ID of the containing domain(s), null for public offerings")
private List<Long> domainIds;
@Parameter(name = ApiConstants.ZONE_ID,
type = CommandType.LIST,
collectionType = CommandType.UUID,
entityType = ZoneResponse.class,
description = "The ID of the containing zone(s), null for public offerings",
since = "4.13")
private List<Long> zoneIds;
@Parameter(name = ApiConstants.ENABLE,
type = CommandType.BOOLEAN,
description = "Set to true if the offering is to be enabled during creation. Default is false",
since = "4.16")
private Boolean enable;
@Parameter(name = ApiConstants.SPECIFY_AS_NUMBER, type = CommandType.BOOLEAN, since = "4.20.0",
description = "true if network offering supports choosing AS number")
private Boolean specifyAsNumber;
@Parameter(name = ApiConstants.ROUTING_MODE,
type = CommandType.STRING,
since = "4.20.0",
description = "the routing mode for the network offering. Supported types are: Static or Dynamic.")
private String routingMode;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public String getNetworkOfferingName() {
return networkOfferingName;
}
public String getDisplayText() {
return StringUtils.isEmpty(displayText) ? networkOfferingName : displayText;
}
public String getTags() {
return tags;
}
public String getTraffictype() {
return traffictype;
}
public Boolean getSpecifyVlan() {
return specifyVlan == null ? false : specifyVlan;
}
public String getAvailability() {
return availability == null ? Availability.Optional.toString() : availability;
}
public Integer getNetworkRate() {
return networkRate;
}
public Long getServiceOfferingId() {
return serviceOfferingId;
}
public boolean isExternalNetworkProvider() {
return Arrays.asList("NSX", "Netris").stream()
.anyMatch(s -> provider != null && s.equalsIgnoreCase(provider));
}
public boolean isForNsx() {
return provider != null && provider.equalsIgnoreCase("NSX");
}
public boolean isForNetris() {
return provider != null && provider.equalsIgnoreCase("Netris");
}
public String getProvider() {
return provider;
}
public List<String> getSupportedServices() {
if (!isExternalNetworkProvider()) {
return supportedServices == null ? new ArrayList<String>() : supportedServices;
} else {
List<String> services = new ArrayList<>(List.of(
Dhcp.getName(),
Dns.getName(),
UserData.getName()
));
if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode())) {
services.addAll(Arrays.asList(
StaticNat.getName(),
SourceNat.getName(),
PortForwarding.getName()));
}
if (getNsxSupportsLbService() || (provider != null && isNetrisNatted(getProvider(), getNetworkMode()))) {
services.add(Lb.getName());
}
if (Boolean.TRUE.equals(forVpc)) {
services.add(NetworkACL.getName());
} else {
services.add(Firewall.getName());
}
return services;
}
}
public String getGuestIpType() {
return guestIptype;
}
public String getInternetProtocol() {
return internetProtocol;
}
public Boolean getSpecifyIpRanges() {
return specifyIpRanges == null ? false : specifyIpRanges;
}
public Boolean getConserveMode() {
if (conserveMode == null) {
return true;
}
return conserveMode;
}
public Boolean getIsPersistent() {
return isPersistent == null ? false : isPersistent;
}
public Boolean getForVpc() {
return forVpc;
}
public String getNetworkMode() {
return networkMode;
}
public boolean getNsxSupportsLbService() {
return BooleanUtils.isTrue(nsxSupportsLbService);
}
public boolean getNsxSupportsInternalLbService() {
return BooleanUtils.isTrue(nsxSupportsInternalLbService);
}
public Boolean getForTungsten() {
return forTungsten;
}
public Boolean getEgressDefaultPolicy() {
if (egressDefaultPolicy == null) {
return true;
}
return egressDefaultPolicy;
}
public Boolean getKeepAliveEnabled() {
return keepAliveEnabled;
}
public Integer getMaxconnections() {
return maxConnections;
}
public Map<String, List<String>> getServiceProviders() {
Map<String, List<String>> serviceProviderMap = new HashMap<>();
if (serviceProviderList != null && !serviceProviderList.isEmpty() && !isExternalNetworkProvider()) {
Collection servicesCollection = serviceProviderList.values();
Iterator iter = servicesCollection.iterator();
while (iter.hasNext()) {
HashMap<String, String> services = (HashMap<String, String>) iter.next();
String service = services.get("service");
String provider = services.get("provider");
List<String> providerList = null;
if (serviceProviderMap.containsKey(service)) {
providerList = serviceProviderMap.get(service);
} else {
providerList = new ArrayList<String>();
}
providerList.add(provider);
serviceProviderMap.put(service, providerList);
}
} else if (isExternalNetworkProvider()) {
getServiceProviderMapForExternalProvider(serviceProviderMap, Network.Provider.getProvider(provider).getName());
}
return serviceProviderMap;
}
private void getServiceProviderMapForExternalProvider(Map<String, List<String>> serviceProviderMap, String provider) {
String routerProvider = Boolean.TRUE.equals(getForVpc()) ? VirtualRouterProvider.Type.VPCVirtualRouter.name() :
VirtualRouterProvider.Type.VirtualRouter.name();
List<String> unsupportedServices = new ArrayList<>(List.of("Vpn", "Gateway", "SecurityGroup", "Connectivity", "BaremetalPxeService"));
List<String> routerSupported = List.of("Dhcp", "Dns", "UserData");
List<String> allServices = Service.listAllServices().stream().map(Service::getName).collect(Collectors.toList());
if (routerProvider.equals(VirtualRouterProvider.Type.VPCVirtualRouter.name())) {
unsupportedServices.add("Firewall");
} else {
unsupportedServices.add("NetworkACL");
}
for (String service : allServices) {
if (unsupportedServices.contains(service))
continue;
if (routerSupported.contains(service))
serviceProviderMap.put(service, List.of(routerProvider));
else if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode()) || NetworkACL.getName().equalsIgnoreCase(service)) {
serviceProviderMap.put(service, List.of(provider));
}
if (isNsxWithoutLb(getProvider(), getNsxSupportsLbService()) || isNetrisRouted(getProvider(), getNetworkMode())) {
serviceProviderMap.remove(Lb.getName());
}
}
}
public Map<Capability, String> getServiceCapabilities(Service service) {
Map<Capability, String> capabilityMap = null;
if (serviceCapabilitystList != null && !serviceCapabilitystList.isEmpty()) {
capabilityMap = new HashMap<Capability, String>();
Collection serviceCapabilityCollection = serviceCapabilitystList.values();
Iterator iter = serviceCapabilityCollection.iterator();
while (iter.hasNext()) {
HashMap<String, String> svcCapabilityMap = (HashMap<String, String>) iter.next();
Capability capability = null;
String svc = svcCapabilityMap.get("service");
String capabilityName = svcCapabilityMap.get("capabilitytype");
String capabilityValue = svcCapabilityMap.get("capabilityvalue");
if (capabilityName != null) {
capability = Capability.getCapability(capabilityName);
}
if ((capability == null) || (capabilityName == null) || (capabilityValue == null)) {
throw new InvalidParameterValueException("Invalid capability:" + capabilityName + " capability value:" + capabilityValue);
}
if (svc.equalsIgnoreCase(service.getName())) {
capabilityMap.put(capability, capabilityValue);
} else {
//throw new InvalidParameterValueException("Service is not equal ")
}
}
}
return capabilityMap;
}
public Map<String, String> getDetails() {
if (details == null || details.isEmpty()) {
return null;
}
Collection paramsCollection = details.values();
Object objlist[] = paramsCollection.toArray();
Map<String, String> params = (Map<String, String>) (objlist[0]);
for (int i = 1; i < objlist.length; i++) {
params.putAll((Map<String, String>) (objlist[i]));
}
return params;
}
public String getServicePackageId() {
Map<String, String> data = getDetails();
if (data == null)
return null;
return data.get(NetworkOffering.Detail.servicepackageuuid + "");
}
public List<Long> getDomainIds() {
if (CollectionUtils.isNotEmpty(domainIds)) {
Set<Long> set = new LinkedHashSet<>(domainIds);
domainIds.clear();
domainIds.addAll(set);
}
return domainIds;
}
public List<Long> getZoneIds() {
if (CollectionUtils.isNotEmpty(zoneIds)) {
Set<Long> set = new LinkedHashSet<>(zoneIds);
zoneIds.clear();
zoneIds.addAll(set);
}
return zoneIds;
}
public Boolean getEnable() {
if (enable != null) {
return enable;
}
return false;
}
public boolean getSpecifyAsNumber() {
return BooleanUtils.toBoolean(specifyAsNumber);
}
public String getRoutingMode() {
return routingMode;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM;
}
@Override
public void execute() {

View File

@ -0,0 +1,493 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.network;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.network.Network;
import com.cloud.network.VirtualRouterProvider;
import com.cloud.offering.NetworkOffering;
import com.cloud.user.Account;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static com.cloud.network.Network.Service.Dhcp;
import static com.cloud.network.Network.Service.Dns;
import static com.cloud.network.Network.Service.Firewall;
import static com.cloud.network.Network.Service.Lb;
import static com.cloud.network.Network.Service.NetworkACL;
import static com.cloud.network.Network.Service.PortForwarding;
import static com.cloud.network.Network.Service.SourceNat;
import static com.cloud.network.Network.Service.StaticNat;
import static com.cloud.network.Network.Service.UserData;
import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNsxWithoutLb;
import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNetrisNatted;
import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNetrisRouted;
public abstract class NetworkOfferingBaseCmd extends BaseCmd {
public abstract String getGuestIpType();
public abstract String getTraffictype();
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "The name of the network offering")
private String networkOfferingName;
@Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, description = "The display text of the network offering, defaults to the value of 'name'.")
private String displayText;
@Parameter(name = ApiConstants.TAGS, type = CommandType.STRING, description = "The tags for the network offering.", length = 4096)
private String tags;
@Parameter(name = ApiConstants.SPECIFY_VLAN, type = CommandType.BOOLEAN, description = "True if network offering supports VLANs")
private Boolean specifyVlan;
@Parameter(name = ApiConstants.AVAILABILITY, type = CommandType.STRING, description = "The availability of network offering. The default value is Optional. "
+ " Another value is Required, which will make it as the default network offering for new networks ")
private String availability;
@Parameter(name = ApiConstants.NETWORKRATE, type = CommandType.INTEGER, description = "Data transfer rate in megabits per second allowed")
private Integer networkRate;
@Parameter(name = ApiConstants.CONSERVE_MODE, type = CommandType.BOOLEAN, description = "True if the network offering is IP conserve mode enabled")
private Boolean conserveMode;
@Parameter(name = ApiConstants.SERVICE_OFFERING_ID,
type = CommandType.UUID,
entityType = ServiceOfferingResponse.class,
description = "The service offering ID used by virtual router provider")
private Long serviceOfferingId;
@Parameter(name = ApiConstants.INTERNET_PROTOCOL,
type = CommandType.STRING,
description = "The internet protocol of network offering. Options are IPv4 and dualstack. Default is IPv4. dualstack will create a network offering that supports both IPv4 and IPv6",
since = "4.17.0")
private String internetProtocol;
@Parameter(name = ApiConstants.SUPPORTED_SERVICES,
type = CommandType.LIST,
collectionType = CommandType.STRING,
description = "Services supported by the network offering")
private List<String> supportedServices;
@Parameter(name = ApiConstants.SERVICE_PROVIDER_LIST,
type = CommandType.MAP,
description = "Provider to service mapping. If not specified, the provider for the service will be mapped to the default provider on the physical network")
private Map serviceProviderList;
@Parameter(name = ApiConstants.SERVICE_CAPABILITY_LIST, type = CommandType.MAP, description = "Desired service capabilities as part of network offering")
private Map serviceCapabilitiesList;
@Parameter(name = ApiConstants.SPECIFY_IP_RANGES,
type = CommandType.BOOLEAN,
description = "True if network offering supports specifying ip ranges; defaulted to false if not specified")
private Boolean specifyIpRanges;
@Parameter(name = ApiConstants.IS_PERSISTENT,
type = CommandType.BOOLEAN,
description = "True if network offering supports persistent networks; defaulted to false if not specified")
private Boolean isPersistent;
@Parameter(name = ApiConstants.FOR_VPC,
type = CommandType.BOOLEAN,
description = "True if network offering is meant to be used for VPC, false otherwise.")
private Boolean forVpc;
@Deprecated
@Parameter(name = ApiConstants.FOR_NSX,
type = CommandType.BOOLEAN,
description = "true if network offering is meant to be used for NSX, false otherwise.",
since = "4.20.0")
private Boolean forNsx;
@Parameter(name = ApiConstants.PROVIDER,
type = CommandType.STRING,
description = "Name of the provider providing the service",
since = "4.21.0")
private String provider;
@Parameter(name = ApiConstants.NSX_SUPPORT_LB,
type = CommandType.BOOLEAN,
description = "True if network offering for NSX network offering supports Load balancer service.",
since = "4.20.0")
private Boolean nsxSupportsLbService;
@Parameter(name = ApiConstants.NSX_SUPPORTS_INTERNAL_LB,
type = CommandType.BOOLEAN,
description = "True if network offering for NSX network offering supports Internal Load balancer service.",
since = "4.20.0")
private Boolean nsxSupportsInternalLbService;
@Parameter(name = ApiConstants.NETWORK_MODE,
type = CommandType.STRING,
description = "Indicates the mode with which the network will operate. Valid option: NATTED or ROUTED",
since = "4.20.0")
private String networkMode;
@Parameter(name = ApiConstants.FOR_TUNGSTEN,
type = CommandType.BOOLEAN,
description = "True if network offering is meant to be used for Tungsten-Fabric, false otherwise.")
private Boolean forTungsten;
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, since = "4.2.0", description = "Network offering details in key/value pairs."
+ " Supported keys are internallbprovider/publiclbprovider with service provider as a value, and"
+ " promiscuousmode/macaddresschanges/forgedtransmits with true/false as value to accept/reject the security settings if available for a nic/portgroup")
protected Map details;
@Parameter(name = ApiConstants.EGRESS_DEFAULT_POLICY,
type = CommandType.BOOLEAN,
description = "True if guest network default egress policy is allow; false if default egress policy is deny")
private Boolean egressDefaultPolicy;
@Parameter(name = ApiConstants.KEEPALIVE_ENABLED,
type = CommandType.BOOLEAN,
required = false,
description = "If true keepalive will be turned on in the loadbalancer. At the time of writing this has only an effect on haproxy; the mode http and httpclose options are unset in the haproxy conf file.")
private Boolean keepAliveEnabled;
@Parameter(name = ApiConstants.MAX_CONNECTIONS,
type = CommandType.INTEGER,
description = "Maximum number of concurrent connections supported by the Network offering")
private Integer maxConnections;
@Parameter(name = ApiConstants.DOMAIN_ID,
type = CommandType.LIST,
collectionType = CommandType.UUID,
entityType = DomainResponse.class,
description = "The ID of the containing domain(s), null for public offerings")
private List<Long> domainIds;
@Parameter(name = ApiConstants.ZONE_ID,
type = CommandType.LIST,
collectionType = CommandType.UUID,
entityType = ZoneResponse.class,
description = "The ID of the containing zone(s), null for public offerings",
since = "4.13")
private List<Long> zoneIds;
@Parameter(name = ApiConstants.ENABLE,
type = CommandType.BOOLEAN,
description = "Set to true if the offering is to be enabled during creation. Default is false",
since = "4.16")
private Boolean enable;
@Parameter(name = ApiConstants.SPECIFY_AS_NUMBER, type = CommandType.BOOLEAN, since = "4.20.0",
description = "true if network offering supports choosing AS number")
private Boolean specifyAsNumber;
@Parameter(name = ApiConstants.ROUTING_MODE,
type = CommandType.STRING,
since = "4.20.0",
description = "the routing mode for the network offering. Supported types are: Static or Dynamic.")
private String routingMode;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public String getNetworkOfferingName() {
return networkOfferingName;
}
public String getDisplayText() {
return StringUtils.isEmpty(displayText) ? networkOfferingName : displayText;
}
public String getTags() {
return tags;
}
public Boolean getSpecifyVlan() {
return specifyVlan == null ? false : specifyVlan;
}
public String getAvailability() {
return availability == null ? NetworkOffering.Availability.Optional.toString() : availability;
}
public Integer getNetworkRate() {
return networkRate;
}
public Long getServiceOfferingId() {
return serviceOfferingId;
}
public boolean isExternalNetworkProvider() {
return Arrays.asList("NSX", "Netris").stream()
.anyMatch(s -> provider != null && s.equalsIgnoreCase(provider));
}
public boolean isForNsx() {
return provider != null && provider.equalsIgnoreCase("NSX");
}
public boolean isForNetris() {
return provider != null && provider.equalsIgnoreCase("Netris");
}
public String getProvider() {
return provider;
}
public List<String> getSupportedServices() {
if (!isExternalNetworkProvider()) {
return supportedServices == null ? new ArrayList<String>() : supportedServices;
} else {
List<String> services = new ArrayList<>(List.of(
Dhcp.getName(),
Dns.getName(),
UserData.getName()
));
if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode())) {
services.addAll(Arrays.asList(
StaticNat.getName(),
SourceNat.getName(),
PortForwarding.getName()));
}
if (getNsxSupportsLbService() || (provider != null && isNetrisNatted(getProvider(), getNetworkMode()))) {
services.add(Lb.getName());
}
if (Boolean.TRUE.equals(forVpc)) {
services.add(NetworkACL.getName());
} else {
services.add(Firewall.getName());
}
return services;
}
}
public String getInternetProtocol() {
return internetProtocol;
}
public Boolean getSpecifyIpRanges() {
return specifyIpRanges == null ? false : specifyIpRanges;
}
public Boolean getConserveMode() {
if (conserveMode == null) {
return true;
}
return conserveMode;
}
public Boolean getIsPersistent() {
return isPersistent == null ? false : isPersistent;
}
public Boolean getForVpc() {
return forVpc;
}
public String getNetworkMode() {
return networkMode;
}
public boolean getNsxSupportsLbService() {
return BooleanUtils.isTrue(nsxSupportsLbService);
}
public boolean getNsxSupportsInternalLbService() {
return BooleanUtils.isTrue(nsxSupportsInternalLbService);
}
public Boolean getForTungsten() {
return forTungsten;
}
public Boolean getEgressDefaultPolicy() {
if (egressDefaultPolicy == null) {
return true;
}
return egressDefaultPolicy;
}
public Boolean getKeepAliveEnabled() {
return keepAliveEnabled;
}
public Integer getMaxconnections() {
return maxConnections;
}
public Map<String, List<String>> getServiceProviders() {
Map<String, List<String>> serviceProviderMap = new HashMap<>();
if (serviceProviderList != null && !serviceProviderList.isEmpty() && !isExternalNetworkProvider()) {
Collection servicesCollection = serviceProviderList.values();
Iterator iter = servicesCollection.iterator();
while (iter.hasNext()) {
HashMap<String, String> services = (HashMap<String, String>) iter.next();
String service = services.get("service");
String provider = services.get("provider");
List<String> providerList = null;
if (serviceProviderMap.containsKey(service)) {
providerList = serviceProviderMap.get(service);
} else {
providerList = new ArrayList<String>();
}
providerList.add(provider);
serviceProviderMap.put(service, providerList);
}
} else if (isExternalNetworkProvider()) {
getServiceProviderMapForExternalProvider(serviceProviderMap, Network.Provider.getProvider(provider).getName());
}
return serviceProviderMap;
}
private void getServiceProviderMapForExternalProvider(Map<String, List<String>> serviceProviderMap, String provider) {
String routerProvider = Boolean.TRUE.equals(getForVpc()) ? VirtualRouterProvider.Type.VPCVirtualRouter.name() :
VirtualRouterProvider.Type.VirtualRouter.name();
List<String> unsupportedServices = new ArrayList<>(List.of("Vpn", "Gateway", "SecurityGroup", "Connectivity", "BaremetalPxeService"));
List<String> routerSupported = List.of("Dhcp", "Dns", "UserData");
List<String> allServices = Network.Service.listAllServices().stream().map(Network.Service::getName).collect(Collectors.toList());
if (routerProvider.equals(VirtualRouterProvider.Type.VPCVirtualRouter.name())) {
unsupportedServices.add("Firewall");
} else {
unsupportedServices.add("NetworkACL");
}
for (String service : allServices) {
if (unsupportedServices.contains(service))
continue;
if (routerSupported.contains(service))
serviceProviderMap.put(service, List.of(routerProvider));
else if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode()) || NetworkACL.getName().equalsIgnoreCase(service)) {
serviceProviderMap.put(service, List.of(provider));
}
if (isNsxWithoutLb(getProvider(), getNsxSupportsLbService()) || isNetrisRouted(getProvider(), getNetworkMode())) {
serviceProviderMap.remove(Lb.getName());
}
}
}
public Map<Network.Capability, String> getServiceCapabilities(Network.Service service) {
Map<Network.Capability, String> capabilityMap = null;
if (serviceCapabilitiesList != null && !serviceCapabilitiesList.isEmpty()) {
capabilityMap = new HashMap<Network.Capability, String>();
Collection serviceCapabilityCollection = serviceCapabilitiesList.values();
Iterator iter = serviceCapabilityCollection.iterator();
while (iter.hasNext()) {
HashMap<String, String> svcCapabilityMap = (HashMap<String, String>) iter.next();
Network.Capability capability = null;
String svc = svcCapabilityMap.get("service");
String capabilityName = svcCapabilityMap.get("capabilitytype");
String capabilityValue = svcCapabilityMap.get("capabilityvalue");
if (capabilityName != null) {
capability = Network.Capability.getCapability(capabilityName);
}
if ((capability == null) || (capabilityName == null) || (capabilityValue == null)) {
throw new InvalidParameterValueException("Invalid capability:" + capabilityName + " capability value:" + capabilityValue);
}
if (svc.equalsIgnoreCase(service.getName())) {
capabilityMap.put(capability, capabilityValue);
} else {
//throw new InvalidParameterValueException("Service is not equal ")
}
}
}
return capabilityMap;
}
public Map<String, String> getDetails() {
if (details == null || details.isEmpty()) {
return null;
}
Collection paramsCollection = details.values();
Object objlist[] = paramsCollection.toArray();
Map<String, String> params = (Map<String, String>) (objlist[0]);
for (int i = 1; i < objlist.length; i++) {
params.putAll((Map<String, String>) (objlist[i]));
}
return params;
}
public String getServicePackageId() {
Map<String, String> data = getDetails();
if (data == null)
return null;
return data.get(NetworkOffering.Detail.servicepackageuuid + "");
}
public List<Long> getDomainIds() {
if (CollectionUtils.isNotEmpty(domainIds)) {
Set<Long> set = new LinkedHashSet<>(domainIds);
domainIds.clear();
domainIds.addAll(set);
}
return domainIds;
}
public List<Long> getZoneIds() {
if (CollectionUtils.isNotEmpty(zoneIds)) {
Set<Long> set = new LinkedHashSet<>(zoneIds);
zoneIds.clear();
zoneIds.addAll(set);
}
return zoneIds;
}
public Boolean getEnable() {
if (enable != null) {
return enable;
}
return false;
}
public boolean getSpecifyAsNumber() {
return BooleanUtils.toBoolean(specifyAsNumber);
}
public String getRoutingMode() {
return routingMode;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM;
}
}

View File

@ -0,0 +1,73 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.offering;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DiskOfferingResponse;
import com.cloud.offering.DiskOffering;
@APICommand(name = "cloneDiskOffering",
description = "Clones a disk offering. All parameters from createDiskOffering are available. If not specified, values will be copied from the source offering.",
responseObject = DiskOfferingResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
since = "4.23.0",
authorized = {RoleType.Admin, RoleType.DomainAdmin})
public class CloneDiskOfferingCmd extends CreateDiskOfferingCmd {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.SOURCE_OFFERING_ID,
type = BaseCmd.CommandType.UUID,
entityType = DiskOfferingResponse.class,
required = true,
description = "The ID of the source disk offering to clone from")
private Long sourceOfferingId;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getSourceOfferingId() {
return sourceOfferingId;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() {
DiskOffering result = _configService.cloneDiskOffering(this);
if (result != null) {
DiskOfferingResponse response = _responseGenerator.createDiskOfferingResponse(result);
response.setResponseName(getCommandName());
this.setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to clone disk offering");
}
}
}

View File

@ -0,0 +1,79 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.offering;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import com.cloud.offering.ServiceOffering;
@APICommand(name = "cloneServiceOffering",
description = "Clones a service offering. All parameters from createServiceOffering are available. If not specified, values will be copied from the source offering.",
responseObject = ServiceOfferingResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
since = "4.23.0",
authorized = {RoleType.Admin, RoleType.DomainAdmin})
public class CloneServiceOfferingCmd extends CreateServiceOfferingCmd {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.SOURCE_OFFERING_ID,
type = CommandType.UUID,
entityType = ServiceOfferingResponse.class,
required = true,
description = "The ID of the source service offering to clone from")
private Long sourceOfferingId;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getSourceOfferingId() {
return sourceOfferingId;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() {
try {
ServiceOffering result = _configService.cloneServiceOffering(this);
if (result != null) {
ServiceOfferingResponse response = _responseGenerator.createServiceOfferingResponse(result);
response.setResponseName(getCommandName());
this.setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to clone service offering");
}
} catch (com.cloud.exception.InvalidParameterValueException e) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, e.getMessage());
} catch (com.cloud.utils.exception.CloudRuntimeException e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
}
}
}

View File

@ -0,0 +1,109 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.vpc;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.network.vpc.VpcOffering;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.VpcOfferingResponse;
import java.util.List;
@APICommand(name = "cloneVPCOffering",
description = "Clones an existing VPC offering. All parameters are copied from the source offering unless explicitly overridden. " +
"Use 'addServices' and 'dropServices' to modify the service list without respecifying everything.",
responseObject = VpcOfferingResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
since = "4.23.0")
public class CloneVPCOfferingCmd extends CreateVPCOfferingCmd {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.SOURCE_OFFERING_ID,
type = BaseCmd.CommandType.UUID,
entityType = VpcOfferingResponse.class,
required = true,
description = "The ID of the source VPC offering to clone from")
private Long sourceOfferingId;
@Parameter(name = "addservices",
type = CommandType.LIST,
collectionType = CommandType.STRING,
description = "Services to add to the cloned offering (in addition to source offering services). " +
"If specified along with 'supportedservices', this parameter is ignored.")
private List<String> addServices;
@Parameter(name = "dropservices",
type = CommandType.LIST,
collectionType = CommandType.STRING,
description = "Services to remove from the cloned offering (that exist in source offering). " +
"If specified along with 'supportedservices', this parameter is ignored.")
private List<String> dropServices;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getSourceOfferingId() {
return sourceOfferingId;
}
public List<String> getAddServices() {
return addServices;
}
public List<String> getDropServices() {
return dropServices;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void create() throws ResourceAllocationException {
// Set a temporary entity ID (source offering ID) to prevent NullPointerException
// in ApiServer.queueCommand(). This will be updated in execute() with the actual
// cloned offering ID.
if (sourceOfferingId != null) {
setEntityId(sourceOfferingId);
}
}
@Override
public void execute() {
VpcOffering result = _vpcProvSvc.cloneVPCOffering(this);
if (result != null) {
setEntityId(result.getId());
setEntityUuid(result.getUuid());
VpcOfferingResponse response = _responseGenerator.createVpcOfferingResponse(result);
response.setResponseName(getCommandName());
this.setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to clone VPC offering");
}
}
}

View File

@ -28,7 +28,6 @@ import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.network.Network;
import com.cloud.network.VirtualRouterProvider;
import com.cloud.offering.NetworkOffering;
@ -161,6 +160,12 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd {
description = "the routing mode for the VPC offering. Supported types are: Static or Dynamic.")
private String routingMode;
@Parameter(name = ApiConstants.CONSERVE_MODE, type = CommandType.BOOLEAN,
since = "4.23.0",
description = "True if the VPC offering is IP conserve mode enabled, allowing public IPs to be used across multiple VPC tiers. Default value is false")
private Boolean conserveMode;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -179,9 +184,7 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd {
}
public List<String> getSupportedServices() {
if (!isExternalNetworkProvider() && CollectionUtils.isEmpty(supportedServices)) {
throw new InvalidParameterValueException("Supported services needs to be provided");
}
// For external network providers, auto-populate services based on network mode
if (isExternalNetworkProvider()) {
supportedServices = new ArrayList<>(List.of(
Dhcp.getName(),
@ -311,6 +314,10 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd {
return routingMode;
}
public boolean isConserveMode() {
return BooleanUtils.toBoolean(conserveMode);
}
@Override
public void create() throws ResourceAllocationException {
VpcOffering vpcOff = _vpcProvSvc.createVpcOffering(this);

View File

@ -54,7 +54,6 @@ import com.cloud.vm.VirtualMachine;
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class CreatePortForwardingRuleCmd extends BaseAsyncCreateCmd implements PortForwardingRule {
// ///////////////////////////////////////////////////
// ////////////// API parameters /////////////////////
// ///////////////////////////////////////////////////
@ -278,13 +277,7 @@ public class CreatePortForwardingRuleCmd extends BaseAsyncCreateCmd implements P
@Override
public long getNetworkId() {
IpAddress ip = _entityMgr.findById(IpAddress.class, getIpAddressId());
Long ntwkId = null;
if (ip.getAssociatedWithNetworkId() != null) {
ntwkId = ip.getAssociatedWithNetworkId();
} else {
ntwkId = networkId;
}
Long ntwkId = _networkService.getPreferredNetworkIdForPublicIpRuleAssignment(ip, networkId);
if (ntwkId == null) {
throw new InvalidParameterValueException("Unable to create port forwarding rule for the ipAddress id=" + ipAddressId +
" as ip is not associated with any network and no networkId is passed in");

View File

@ -23,6 +23,8 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.cloud.network.Network;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.api.APICommand;
@ -72,7 +74,7 @@ public class AssignToLoadBalancerRuleCmd extends BaseAsyncCmd {
@Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID_IP,
type = CommandType.MAP,
description = "VM ID and IP map, vmidipmap[0].vmid=1 vmidipmap[0].vmip=10.1.1.75",
description = "VM ID and IP map, vmidipmap[0].vmid=1 vmidipmap[0].vmip=10.1.1.75. (Optional, for VPC Conserve Mode) Pass vmnetworkid. Example: vmidipmap[0].vmnetworkid=NETWORK_TIER_UUID",
since = "4.4")
private Map vmIdIpMap;
@ -116,8 +118,9 @@ public class AssignToLoadBalancerRuleCmd extends BaseAsyncCmd {
}
public Map<Long, List<String>> getVmIdIpListMap() {
Map<Long, List<String>> vmIdIpsMap = new HashMap<Long, List<String>>();
public Pair<Map<Long, List<String>>, Map<Long, Long>> getVmIdIpListMapAndVmIdNetworkMap() {
Map<Long, List<String>> vmIdIpsMap = new HashMap<>();
Map<Long, Long> vmIdNetworkMap = new HashMap<>();
if (vmIdIpMap != null && !vmIdIpMap.isEmpty()) {
Collection idIpsCollection = vmIdIpMap.values();
Iterator iter = idIpsCollection.iterator();
@ -125,6 +128,7 @@ public class AssignToLoadBalancerRuleCmd extends BaseAsyncCmd {
HashMap<String, String> idIpsMap = (HashMap<String, String>)iter.next();
String vmId = idIpsMap.get("vmid");
String vmIp = idIpsMap.get("vmip");
String vmNetworkUuid = idIpsMap.get("vmnetworkid");
VirtualMachine lbvm = _entityMgr.findByUuid(VirtualMachine.class, vmId);
if (lbvm == null) {
@ -145,25 +149,35 @@ public class AssignToLoadBalancerRuleCmd extends BaseAsyncCmd {
ipsList = new ArrayList<String>();
}
ipsList.add(vmIp);
if (vmNetworkUuid != null) {
Network vmNetwork = _entityMgr.findByUuid(Network.class, vmNetworkUuid);
if (vmNetwork == null) {
throw new InvalidParameterValueException("Unable to find Network ID: " + vmNetworkUuid);
}
vmIdNetworkMap.put(longVmId, vmNetwork.getId());
}
vmIdIpsMap.put(longVmId, ipsList);
}
}
return vmIdIpsMap;
return new Pair<>(vmIdIpsMap, vmIdNetworkMap);
}
@Override
public void execute() {
CallContext.current().setEventDetails("Load balancer ID: " + getResourceUuid(ApiConstants.ID) + " Instances IDs: " + StringUtils.join(getVirtualMachineIds(), ","));
Map<Long, List<String>> vmIdIpsMap = getVmIdIpListMap();
Pair<Map<Long, List<String>>, Map<Long, Long>> mapsPair = getVmIdIpListMapAndVmIdNetworkMap();
Map<Long, List<String>> vmIdIpsMap = mapsPair.first();
Map<Long, Long> vmIdNetworkMap = mapsPair.second();
boolean result = false;
try {
result = _lbService.assignToLoadBalancer(getLoadBalancerId(), virtualMachineIds, vmIdIpsMap, false);
result = _lbService.assignToLoadBalancer(getLoadBalancerId(), virtualMachineIds, vmIdIpsMap, vmIdNetworkMap, false);
}catch (CloudRuntimeException ex) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to assign load balancer rule");
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to assign load balancer rule due to: " + ex.getMessage());
}
if (result) {

View File

@ -94,6 +94,10 @@ public class FirewallRuleResponse extends BaseResponse {
@Param(description = "The ID of the guest Network the port forwarding rule belongs to")
private String networkId;
@SerializedName(ApiConstants.NETWORK_NAME)
@Param(description = "The Name of the guest Network the port forwarding rule belongs to")
private String networkName;
@SerializedName(ApiConstants.FOR_DISPLAY)
@Param(description = "Is firewall for display to the regular user", since = "4.4", authorized = {RoleType.Admin})
private Boolean forDisplay;
@ -223,6 +227,10 @@ public class FirewallRuleResponse extends BaseResponse {
this.networkId = networkId;
}
public void setNetworkName(String networkName) {
this.networkName = networkName;
}
public void setForDisplay(Boolean forDisplay) {
this.forDisplay = forDisplay;
}

View File

@ -102,6 +102,10 @@ public class VpcOfferingResponse extends BaseResponse {
@Param(description = "The routing mode for the network offering, supported types are Static or Dynamic.")
private String routingMode;
@SerializedName(ApiConstants.CONSERVE_MODE)
@Param(description = "True if the VPC offering is IP conserve mode enabled, allowing public IP services to be used across multiple VPC tiers.", since = "4.23.0")
private Boolean conserveMode;
public void setId(String id) {
this.id = id;
}
@ -201,4 +205,12 @@ public class VpcOfferingResponse extends BaseResponse {
public void setRoutingMode(String routingMode) {
this.routingMode = routingMode;
}
public Boolean getConserveMode() {
return conserveMode;
}
public void setConserveMode(Boolean conserveMode) {
this.conserveMode = conserveMode;
}
}

View File

@ -73,6 +73,10 @@ public class VpcResponse extends BaseResponseWithAnnotations implements Controll
@Param(description = "VPC offering name the VPC is created from", since = "4.13.2")
private String vpcOfferingName;
@SerializedName(ApiConstants.VPC_OFFERING_CONSERVE_MODE)
@Param(description = "true if VPC offering is ip conserve mode enabled", since = "4.23")
private Boolean vpcOfferingConserveMode;
@SerializedName(ApiConstants.CREATED)
@Param(description = "The date this VPC was created")
private Date created;
@ -197,6 +201,10 @@ public class VpcResponse extends BaseResponseWithAnnotations implements Controll
this.displayText = displayText;
}
public void setVpcOfferingConserveMode(Boolean vpcOfferingConserveMode) {
this.vpcOfferingConserveMode = vpcOfferingConserveMode;
}
public void setCreated(final Date created) {
this.created = created;
}

View File

@ -22,6 +22,7 @@ import java.util.Map;
import com.cloud.capacity.Capacity;
import com.cloud.exception.ResourceAllocationException;
import org.apache.cloudstack.api.command.admin.backup.CloneBackupOfferingCmd;
import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd;
import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd;
import org.apache.cloudstack.api.command.user.backup.CreateBackupCmd;
@ -140,6 +141,12 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer
List<Long> getBackupOfferingDomains(final Long offeringId);
/**
* Clone an existing backup offering with updated values
* @param cmd clone backup offering cmd
*/
BackupOffering cloneBackupOffering(final CloneBackupOfferingCmd cmd);
/**
* List backup offerings
* @param ListBackupOfferingsCmd API cmd

View File

@ -0,0 +1,301 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.backup;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ResponseGenerator;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.BackupOfferingResponse;
import org.apache.cloudstack.backup.BackupManager;
import org.apache.cloudstack.backup.BackupOffering;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class CloneBackupOfferingCmdTest {
private CloneBackupOfferingCmd cloneBackupOfferingCmd;
@Mock
private BackupManager backupManager;
@Mock
private ResponseGenerator responseGenerator;
@Mock
private BackupOffering mockBackupOffering;
@Mock
private BackupOfferingResponse mockBackupOfferingResponse;
@Before
public void setUp() {
cloneBackupOfferingCmd = new CloneBackupOfferingCmd();
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "backupManager", backupManager);
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "_responseGenerator", responseGenerator);
}
@Test
public void testGetSourceOfferingId() {
Long sourceOfferingId = 999L;
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", sourceOfferingId);
assertEquals(sourceOfferingId, cloneBackupOfferingCmd.getSourceOfferingId());
}
@Test
public void testGetName() {
String name = "ClonedBackupOffering";
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", name);
assertEquals(name, cloneBackupOfferingCmd.getName());
}
@Test
public void testGetDescription() {
String description = "Cloned Backup Offering Description";
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "description", description);
assertEquals(description, cloneBackupOfferingCmd.getDescription());
}
@Test
public void testGetZoneId() {
Long zoneId = 123L;
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "zoneId", zoneId);
assertEquals(zoneId, cloneBackupOfferingCmd.getZoneId());
}
@Test
public void testGetExternalId() {
String externalId = "external-backup-123";
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "externalId", externalId);
assertEquals(externalId, cloneBackupOfferingCmd.getExternalId());
}
@Test
public void testGetAllowUserDrivenBackups() {
Boolean allowUserDrivenBackups = true;
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "userDrivenBackups", allowUserDrivenBackups);
assertEquals(allowUserDrivenBackups, cloneBackupOfferingCmd.getUserDrivenBackups());
}
@Test
public void testAllowUserDrivenBackupsDefaultTrue() {
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "userDrivenBackups", null);
Boolean result = cloneBackupOfferingCmd.getUserDrivenBackups();
assertTrue(result == null || result);
}
@Test
public void testAllowUserDrivenBackupsFalse() {
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "userDrivenBackups", false);
assertEquals(Boolean.FALSE, cloneBackupOfferingCmd.getUserDrivenBackups());
}
@Test
public void testExecuteSuccess() throws Exception {
Long sourceOfferingId = 999L;
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", sourceOfferingId);
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", "ClonedBackupOffering");
when(backupManager.cloneBackupOffering(any(CloneBackupOfferingCmd.class))).thenReturn(mockBackupOffering);
when(responseGenerator.createBackupOfferingResponse(mockBackupOffering)).thenReturn(mockBackupOfferingResponse);
cloneBackupOfferingCmd.execute();
assertNotNull(cloneBackupOfferingCmd.getResponseObject());
assertEquals(mockBackupOfferingResponse, cloneBackupOfferingCmd.getResponseObject());
}
@Test
public void testExecuteFailure() throws Exception {
Long sourceOfferingId = 999L;
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", sourceOfferingId);
when(backupManager.cloneBackupOffering(any(CloneBackupOfferingCmd.class))).thenReturn(null);
try {
cloneBackupOfferingCmd.execute();
fail("Expected ServerApiException to be thrown");
} catch (ServerApiException e) {
assertEquals(ApiErrorCode.INTERNAL_ERROR, e.getErrorCode());
assertEquals("Failed to clone backup offering", e.getMessage());
}
}
@Test
public void testExecuteWithInvalidParameterException() throws Exception {
Long sourceOfferingId = 999L;
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", sourceOfferingId);
when(backupManager.cloneBackupOffering(any(CloneBackupOfferingCmd.class)))
.thenThrow(new InvalidParameterValueException("Invalid source offering ID"));
try {
cloneBackupOfferingCmd.execute();
fail("Expected ServerApiException to be thrown");
} catch (ServerApiException e) {
assertEquals(ApiErrorCode.PARAM_ERROR, e.getErrorCode());
assertEquals("Invalid source offering ID", e.getMessage());
}
}
@Test
public void testExecuteWithCloudRuntimeException() throws Exception {
Long sourceOfferingId = 999L;
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", sourceOfferingId);
when(backupManager.cloneBackupOffering(any(CloneBackupOfferingCmd.class)))
.thenThrow(new CloudRuntimeException("Runtime error during clone"));
try {
cloneBackupOfferingCmd.execute();
fail("Expected ServerApiException to be thrown");
} catch (ServerApiException e) {
assertEquals(ApiErrorCode.INTERNAL_ERROR, e.getErrorCode());
assertEquals("Runtime error during clone", e.getMessage());
}
}
@Test
public void testExecuteSuccessWithAllParameters() throws Exception {
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", 999L);
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", "ClonedBackupOffering");
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "description", "Test Description");
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "zoneId", 123L);
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "externalId", "ext-123");
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "userDrivenBackups", true);
when(backupManager.cloneBackupOffering(any(CloneBackupOfferingCmd.class))).thenReturn(mockBackupOffering);
when(responseGenerator.createBackupOfferingResponse(mockBackupOffering)).thenReturn(mockBackupOfferingResponse);
cloneBackupOfferingCmd.execute();
assertNotNull(cloneBackupOfferingCmd.getResponseObject());
assertEquals(mockBackupOfferingResponse, cloneBackupOfferingCmd.getResponseObject());
}
@Test
public void testCloneWithAllParameters() {
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", 999L);
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", "ClonedBackupOffering");
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "description", "Cloned backup offering for testing");
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "zoneId", 123L);
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "externalId", "external-backup-123");
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "userDrivenBackups", true);
assertEquals(Long.valueOf(999L), cloneBackupOfferingCmd.getSourceOfferingId());
assertEquals("ClonedBackupOffering", cloneBackupOfferingCmd.getName());
assertEquals("Cloned backup offering for testing", cloneBackupOfferingCmd.getDescription());
assertEquals(Long.valueOf(123L), cloneBackupOfferingCmd.getZoneId());
assertEquals("external-backup-123", cloneBackupOfferingCmd.getExternalId());
assertEquals(Boolean.TRUE, cloneBackupOfferingCmd.getUserDrivenBackups());
}
@Test
public void testCloneWithMinimalParameters() {
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", 999L);
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", "ClonedBackupOffering");
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "description", "Description");
assertEquals(Long.valueOf(999L), cloneBackupOfferingCmd.getSourceOfferingId());
assertEquals("ClonedBackupOffering", cloneBackupOfferingCmd.getName());
assertEquals("Description", cloneBackupOfferingCmd.getDescription());
assertNull(cloneBackupOfferingCmd.getZoneId());
assertNull(cloneBackupOfferingCmd.getExternalId());
}
@Test
public void testSourceOfferingIdNullByDefault() {
assertNull(cloneBackupOfferingCmd.getSourceOfferingId());
}
@Test
public void testNameNullByDefault() {
assertNull(cloneBackupOfferingCmd.getName());
}
@Test
public void testDescriptionNullByDefault() {
assertNull(cloneBackupOfferingCmd.getDescription());
}
@Test
public void testZoneIdNullByDefault() {
assertNull(cloneBackupOfferingCmd.getZoneId());
}
@Test
public void testExternalIdNullByDefault() {
assertNull(cloneBackupOfferingCmd.getExternalId());
}
@Test
public void testCloneBackupOfferingInheritingZone() {
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", 999L);
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", "ClonedBackupOffering");
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "description", "Clone with inherited zone");
assertEquals(Long.valueOf(999L), cloneBackupOfferingCmd.getSourceOfferingId());
assertNull(cloneBackupOfferingCmd.getZoneId());
}
@Test
public void testCloneBackupOfferingInheritingExternalId() {
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", 999L);
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", "ClonedBackupOffering");
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "description", "Clone with inherited external ID");
assertEquals(Long.valueOf(999L), cloneBackupOfferingCmd.getSourceOfferingId());
assertNull(cloneBackupOfferingCmd.getExternalId());
}
@Test
public void testCloneBackupOfferingOverridingZone() {
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", 999L);
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", "ClonedBackupOffering");
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "description", "Clone with new zone");
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "zoneId", 456L);
assertEquals(Long.valueOf(999L), cloneBackupOfferingCmd.getSourceOfferingId());
assertEquals(Long.valueOf(456L), cloneBackupOfferingCmd.getZoneId());
}
@Test
public void testCloneBackupOfferingDisallowUserDrivenBackups() {
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", 999L);
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", "ClonedBackupOffering");
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "description", "Clone without user-driven backups");
ReflectionTestUtils.setField(cloneBackupOfferingCmd, "userDrivenBackups", false);
assertEquals(Boolean.FALSE, cloneBackupOfferingCmd.getUserDrivenBackups());
}
}

View File

@ -0,0 +1,324 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.network;
import com.cloud.offering.NetworkOffering;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ResponseGenerator;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.NetworkOfferingResponse;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class CloneNetworkOfferingCmdTest {
private CloneNetworkOfferingCmd cloneNetworkOfferingCmd;
@Mock
private com.cloud.configuration.ConfigurationService configService;
@Mock
private ResponseGenerator responseGenerator;
@Mock
private NetworkOffering mockNetworkOffering;
@Mock
private NetworkOfferingResponse mockNetworkOfferingResponse;
@Before
public void setUp() {
cloneNetworkOfferingCmd = new CloneNetworkOfferingCmd();
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "_configService", configService);
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "_responseGenerator", responseGenerator);
}
@Test
public void testGetSourceOfferingId() {
Long sourceOfferingId = 123L;
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "sourceOfferingId", sourceOfferingId);
assertEquals(sourceOfferingId, cloneNetworkOfferingCmd.getSourceOfferingId());
}
@Test
public void testGetAddServices() {
List<String> addServices = Arrays.asList("Dhcp", "Dns");
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "addServices", addServices);
assertEquals(addServices, cloneNetworkOfferingCmd.getAddServices());
}
@Test
public void testGetDropServices() {
List<String> dropServices = Arrays.asList("Firewall", "Vpn");
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "dropServices", dropServices);
assertEquals(dropServices, cloneNetworkOfferingCmd.getDropServices());
}
@Test
public void testGetGuestIpType() {
String guestIpType = "Isolated";
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "guestIptype", guestIpType);
assertEquals(guestIpType, cloneNetworkOfferingCmd.getGuestIpType());
}
@Test
public void testGetTraffictype() {
String trafficType = "GUEST";
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "traffictype", trafficType);
assertEquals(trafficType, cloneNetworkOfferingCmd.getTraffictype());
}
@Test
public void testGetName() {
String name = "ClonedNetworkOffering";
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "networkOfferingName", name);
assertEquals(name, cloneNetworkOfferingCmd.getNetworkOfferingName());
}
@Test
public void testGetDisplayText() {
String displayText = "Cloned Network Offering Display Text";
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "displayText", displayText);
assertEquals(displayText, cloneNetworkOfferingCmd.getDisplayText());
}
@Test
public void testGetDisplayTextDefaultsToName() {
String name = "ClonedNetworkOffering";
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "networkOfferingName", name);
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "displayText", null);
assertEquals(name, cloneNetworkOfferingCmd.getDisplayText());
}
@Test
public void testGetAvailability() {
String availability = "Required";
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "availability", availability);
assertEquals(availability, cloneNetworkOfferingCmd.getAvailability());
}
@Test
public void testGetTags() {
String tags = "tag1,tag2,tag3";
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "tags", tags);
assertEquals(tags, cloneNetworkOfferingCmd.getTags());
}
@Test
public void testExecuteSuccess() {
Long sourceOfferingId = 123L;
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "sourceOfferingId", sourceOfferingId);
when(configService.cloneNetworkOffering(any(CloneNetworkOfferingCmd.class))).thenReturn(mockNetworkOffering);
when(responseGenerator.createNetworkOfferingResponse(mockNetworkOffering)).thenReturn(mockNetworkOfferingResponse);
cloneNetworkOfferingCmd.execute();
assertNotNull(cloneNetworkOfferingCmd.getResponseObject());
assertEquals(mockNetworkOfferingResponse, cloneNetworkOfferingCmd.getResponseObject());
}
@Test(expected = ServerApiException.class)
public void testExecuteFailure() {
Long sourceOfferingId = 123L;
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "sourceOfferingId", sourceOfferingId);
when(configService.cloneNetworkOffering(any(CloneNetworkOfferingCmd.class))).thenReturn(null);
try {
cloneNetworkOfferingCmd.execute();
fail("Expected ServerApiException to be thrown");
} catch (ServerApiException e) {
assertEquals(ApiErrorCode.INTERNAL_ERROR, e.getErrorCode());
assertEquals("Failed to clone network offering", e.getMessage());
throw e;
}
}
@Test
public void testGetConserveMode() {
Boolean conserveMode = true;
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "conserveMode", conserveMode);
assertEquals(conserveMode, cloneNetworkOfferingCmd.getConserveMode());
}
@Test
public void testGetSpecifyVlan() {
Boolean specifyVlan = false;
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "specifyVlan", specifyVlan);
assertEquals(specifyVlan, cloneNetworkOfferingCmd.getSpecifyVlan());
}
@Test
public void testGetSpecifyIpRanges() {
Boolean specifyIpRanges = true;
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "specifyIpRanges", specifyIpRanges);
assertEquals(specifyIpRanges, cloneNetworkOfferingCmd.getSpecifyIpRanges());
}
@Test
public void testGetIsPersistent() {
Boolean isPersistent = true;
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "isPersistent", isPersistent);
assertEquals(isPersistent, cloneNetworkOfferingCmd.getIsPersistent());
}
@Test
public void testGetEgressDefaultPolicy() {
Boolean egressDefaultPolicy = false;
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "egressDefaultPolicy", egressDefaultPolicy);
assertEquals(egressDefaultPolicy, cloneNetworkOfferingCmd.getEgressDefaultPolicy());
}
@Test
public void testGetServiceOfferingId() {
Long serviceOfferingId = 456L;
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "serviceOfferingId", serviceOfferingId);
assertEquals(serviceOfferingId, cloneNetworkOfferingCmd.getServiceOfferingId());
}
@Test
public void testGetForVpc() {
Boolean forVpc = true;
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "forVpc", forVpc);
assertEquals(forVpc, cloneNetworkOfferingCmd.getForVpc());
}
@Test
public void testGetMaxConnections() {
Integer maxConnections = 1000;
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "maxConnections", maxConnections);
assertEquals(maxConnections, cloneNetworkOfferingCmd.getMaxconnections());
}
@Test
public void testGetNetworkRate() {
Integer networkRate = 200;
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "networkRate", networkRate);
assertEquals(networkRate, cloneNetworkOfferingCmd.getNetworkRate());
}
@Test
public void testGetInternetProtocol() {
String internetProtocol = "ipv4";
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "internetProtocol", internetProtocol);
assertEquals(internetProtocol, cloneNetworkOfferingCmd.getInternetProtocol());
}
@Test
public void testAddServicesNullByDefault() {
assertNull(cloneNetworkOfferingCmd.getAddServices());
}
@Test
public void testDropServicesNullByDefault() {
assertNull(cloneNetworkOfferingCmd.getDropServices());
}
@Test
public void testSupportedServicesParameter() {
List<String> supportedServices = Arrays.asList("Dhcp", "Dns", "SourceNat");
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "supportedServices", supportedServices);
assertEquals(supportedServices, cloneNetworkOfferingCmd.getSupportedServices());
}
@Test
public void testServiceProviderListParameter() {
Map<String, HashMap<String, String>> serviceProviderList = new HashMap<>();
HashMap<String, String> dhcpProvider = new HashMap<>();
dhcpProvider.put("service", "Dhcp");
dhcpProvider.put("provider", "VirtualRouter");
HashMap<String, String> dnsProvider = new HashMap<>();
dnsProvider.put("service", "Dns");
dnsProvider.put("provider", "VirtualRouter");
serviceProviderList.put("0", dhcpProvider);
serviceProviderList.put("1", dnsProvider);
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "serviceProviderList", serviceProviderList);
Map<String, List<String>> result = cloneNetworkOfferingCmd.getServiceProviders();
assertNotNull(result);
assertEquals(2, result.size());
assertNotNull(result.get("Dhcp"));
assertNotNull(result.get("Dns"));
assertEquals("VirtualRouter", result.get("Dhcp").get(0));
assertEquals("VirtualRouter", result.get("Dns").get(0));
}
@Test
public void testCloneWithAllParameters() {
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "sourceOfferingId", 123L);
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "networkOfferingName", "ClonedOffering");
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "displayText", "Cloned Offering Display");
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "availability", "Optional");
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "guestIptype", "Isolated");
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "traffictype", "GUEST");
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "conserveMode", true);
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "specifyVlan", false);
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "isPersistent", true);
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "egressDefaultPolicy", false);
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "networkRate", 200);
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "serviceOfferingId", 456L);
assertEquals(Long.valueOf(123L), cloneNetworkOfferingCmd.getSourceOfferingId());
assertEquals("ClonedOffering", cloneNetworkOfferingCmd.getNetworkOfferingName());
assertEquals("Cloned Offering Display", cloneNetworkOfferingCmd.getDisplayText());
assertEquals("Optional", cloneNetworkOfferingCmd.getAvailability());
assertEquals("Isolated", cloneNetworkOfferingCmd.getGuestIpType());
assertEquals("GUEST", cloneNetworkOfferingCmd.getTraffictype());
assertEquals(Boolean.TRUE, cloneNetworkOfferingCmd.getConserveMode());
assertEquals(Boolean.FALSE, cloneNetworkOfferingCmd.getSpecifyVlan());
assertEquals(Boolean.TRUE, cloneNetworkOfferingCmd.getIsPersistent());
assertEquals(Boolean.FALSE, cloneNetworkOfferingCmd.getEgressDefaultPolicy());
assertEquals(Integer.valueOf(200), cloneNetworkOfferingCmd.getNetworkRate());
assertEquals(Long.valueOf(456L), cloneNetworkOfferingCmd.getServiceOfferingId());
}
@Test
public void testCloneWithAddAndDropServices() {
List<String> addServices = Arrays.asList("StaticNat", "PortForwarding");
List<String> dropServices = Arrays.asList("Vpn");
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "sourceOfferingId", 123L);
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "addServices", addServices);
ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "dropServices", dropServices);
assertEquals(addServices, cloneNetworkOfferingCmd.getAddServices());
assertEquals(dropServices, cloneNetworkOfferingCmd.getDropServices());
}
}

View File

@ -0,0 +1,669 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.offering;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.offering.ServiceOffering;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ResponseGenerator;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.cloudstack.vm.lease.VMLeaseManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class CloneServiceOfferingCmdTest {
private CloneServiceOfferingCmd cloneServiceOfferingCmd;
@Mock
private com.cloud.configuration.ConfigurationService configService;
@Mock
private ResponseGenerator responseGenerator;
@Mock
private ServiceOffering mockServiceOffering;
@Mock
private ServiceOfferingResponse mockServiceOfferingResponse;
@Before
public void setUp() {
cloneServiceOfferingCmd = new CloneServiceOfferingCmd();
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "_configService", configService);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "_responseGenerator", responseGenerator);
}
@Test
public void testGetSourceOfferingId() {
Long sourceOfferingId = 555L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId);
assertEquals(sourceOfferingId, cloneServiceOfferingCmd.getSourceOfferingId());
}
@Test
public void testGetServiceOfferingName() {
String name = "ClonedServiceOffering";
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", name);
assertEquals(name, cloneServiceOfferingCmd.getServiceOfferingName());
}
@Test
public void testGetDisplayText() {
String displayText = "Cloned Service Offering Display Text";
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "displayText", displayText);
assertEquals(displayText, cloneServiceOfferingCmd.getDisplayText());
}
@Test
public void testGetDisplayTextDefaultsToName() {
String name = "ClonedServiceOffering";
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", name);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "displayText", null);
assertEquals(name, cloneServiceOfferingCmd.getDisplayText());
}
@Test
public void testGetCpu() {
Integer cpu = 4;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuNumber", cpu);
assertEquals(cpu, cloneServiceOfferingCmd.getCpuNumber());
}
@Test
public void testGetMemory() {
Integer memory = 8192;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "memory", memory);
assertEquals(memory, cloneServiceOfferingCmd.getMemory());
}
@Test
public void testGetCpuSpeed() {
Integer cpuSpeed = 2000;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuSpeed", cpuSpeed);
assertEquals(cpuSpeed, cloneServiceOfferingCmd.getCpuSpeed());
}
@Test
public void testGetOfferHa() {
Boolean offerHa = true;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "offerHa", offerHa);
assertEquals(offerHa, cloneServiceOfferingCmd.isOfferHa());
}
@Test
public void testGetLimitCpuUse() {
Boolean limitCpuUse = false;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "limitCpuUse", limitCpuUse);
assertEquals(limitCpuUse, cloneServiceOfferingCmd.isLimitCpuUse());
}
@Test
public void testGetVolatileVm() {
Boolean volatileVm = true;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "isVolatile", volatileVm);
assertEquals(volatileVm, cloneServiceOfferingCmd.isVolatileVm());
}
@Test
public void testGetStorageType() {
String storageType = "local";
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "storageType", storageType);
assertEquals(storageType, cloneServiceOfferingCmd.getStorageType());
}
@Test
public void testGetTags() {
String tags = "ssd,premium,dedicated";
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "tags", tags);
assertEquals(tags, cloneServiceOfferingCmd.getTags());
}
@Test
public void testGetHostTag() {
String hostTag = "gpu-enabled";
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "hostTag", hostTag);
assertEquals(hostTag, cloneServiceOfferingCmd.getHostTag());
}
@Test
public void testGetNetworkRate() {
Integer networkRate = 1000;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "networkRate", networkRate);
assertEquals(networkRate, cloneServiceOfferingCmd.getNetworkRate());
}
@Test
public void testGetDeploymentPlanner() {
String deploymentPlanner = "UserDispersingPlanner";
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "deploymentPlanner", deploymentPlanner);
assertEquals(deploymentPlanner, cloneServiceOfferingCmd.getDeploymentPlanner());
}
@Test
public void testGetDetails() {
Map<String, HashMap<String, String>> details = new HashMap<>();
HashMap<String, String> cpuOvercommit = new HashMap<>();
cpuOvercommit.put("key", "cpuOvercommitRatio");
cpuOvercommit.put("value", "2.0");
HashMap<String, String> memoryOvercommit = new HashMap<>();
memoryOvercommit.put("key", "memoryOvercommitRatio");
memoryOvercommit.put("value", "1.5");
details.put("0", cpuOvercommit);
details.put("1", memoryOvercommit);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "details", details);
Map<String, String> result = cloneServiceOfferingCmd.getDetails();
assertNotNull(result);
assertEquals("2.0", result.get("cpuOvercommitRatio"));
assertEquals("1.5", result.get("memoryOvercommitRatio"));
}
@Test
public void testIsPurgeResources() {
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "purgeResources", true);
assertTrue(cloneServiceOfferingCmd.isPurgeResources());
}
@Test
public void testIsPurgeResourcesFalse() {
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "purgeResources", false);
assertFalse(cloneServiceOfferingCmd.isPurgeResources());
}
@Test
public void testIsPurgeResourcesDefaultFalse() {
assertFalse(cloneServiceOfferingCmd.isPurgeResources());
}
@Test
public void testGetLeaseDuration() {
Integer leaseDuration = 3600;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "leaseDuration", leaseDuration);
assertEquals(leaseDuration, cloneServiceOfferingCmd.getLeaseDuration());
}
@Test
public void testGetLeaseExpiryAction() {
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "leaseExpiryAction", "stop");
assertEquals(VMLeaseManager.ExpiryAction.STOP, cloneServiceOfferingCmd.getLeaseExpiryAction());
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "leaseExpiryAction", "DESTROY");
assertEquals(VMLeaseManager.ExpiryAction.DESTROY, cloneServiceOfferingCmd.getLeaseExpiryAction());
}
@Test(expected = InvalidParameterValueException.class)
public void testGetLeaseExpiryActionInvalidValue() {
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "leaseExpiryAction", "InvalidAction");
cloneServiceOfferingCmd.getLeaseExpiryAction();
}
@Test
public void testGetVgpuProfileId() {
Long vgpuProfileId = 10L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "vgpuProfileId", vgpuProfileId);
assertEquals(vgpuProfileId, cloneServiceOfferingCmd.getVgpuProfileId());
}
@Test
public void testGetGpuCount() {
Integer gpuCount = 2;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "gpuCount", gpuCount);
assertEquals(gpuCount, cloneServiceOfferingCmd.getGpuCount());
}
@Test
public void testGetGpuDisplay() {
Boolean gpuDisplay = true;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "gpuDisplay", gpuDisplay);
assertEquals(gpuDisplay, cloneServiceOfferingCmd.getGpuDisplay());
}
@Test
public void testExecuteSuccess() {
Long sourceOfferingId = 555L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId);
when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering);
when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse);
cloneServiceOfferingCmd.execute();
assertNotNull(cloneServiceOfferingCmd.getResponseObject());
assertEquals(mockServiceOfferingResponse, cloneServiceOfferingCmd.getResponseObject());
}
@Test
public void testExecuteFailure() {
Long sourceOfferingId = 555L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId);
when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(null);
try {
cloneServiceOfferingCmd.execute();
fail("Expected ServerApiException to be thrown");
} catch (ServerApiException e) {
assertEquals(ApiErrorCode.INTERNAL_ERROR, e.getErrorCode());
assertEquals("Failed to clone service offering", e.getMessage());
}
}
@Test
public void testExecuteSuccessWithAllParameters() {
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", 555L);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", "ClonedOffering");
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "displayText", "Test Display");
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuNumber", 4);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "memory", 8192);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuSpeed", 2000);
when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering);
when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse);
cloneServiceOfferingCmd.execute();
assertNotNull(cloneServiceOfferingCmd.getResponseObject());
assertEquals(mockServiceOfferingResponse, cloneServiceOfferingCmd.getResponseObject());
}
@Test
public void testExecuteWithInvalidParameterException() {
Long sourceOfferingId = 555L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId);
when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class)))
.thenThrow(new InvalidParameterValueException("Invalid source offering ID"));
try {
cloneServiceOfferingCmd.execute();
fail("Expected ServerApiException to be thrown");
} catch (ServerApiException e) {
assertEquals(ApiErrorCode.PARAM_ERROR, e.getErrorCode());
assertEquals("Invalid source offering ID", e.getMessage());
}
}
@Test
public void testExecuteWithCloudRuntimeException() {
Long sourceOfferingId = 555L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId);
when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class)))
.thenThrow(new com.cloud.utils.exception.CloudRuntimeException("Runtime error during clone"));
try {
cloneServiceOfferingCmd.execute();
fail("Expected ServerApiException to be thrown");
} catch (ServerApiException e) {
assertEquals(ApiErrorCode.INTERNAL_ERROR, e.getErrorCode());
assertEquals("Runtime error during clone", e.getMessage());
}
}
@Test
public void testExecuteResponseNameIsSet() {
Long sourceOfferingId = 555L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId);
when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering);
when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse);
cloneServiceOfferingCmd.execute();
assertNotNull(cloneServiceOfferingCmd.getResponseObject());
// Verify that response name would be set (actual verification would require accessing the response object's internal state)
}
@Test
public void testCloneWithAllParameters() {
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", 555L);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", "ClonedServiceOffering");
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "displayText", "Cloned Service Offering");
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuNumber", 4);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "memory", 8192);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuSpeed", 2000);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "offerHa", true);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "limitCpuUse", false);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "isVolatile", true);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "storageType", "local");
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "tags", "premium");
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "hostTag", "gpu-enabled");
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "networkRate", 1000);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "deploymentPlanner", "UserDispersingPlanner");
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "purgeResources", true);
assertEquals(Long.valueOf(555L), cloneServiceOfferingCmd.getSourceOfferingId());
assertEquals("ClonedServiceOffering", cloneServiceOfferingCmd.getServiceOfferingName());
assertEquals("Cloned Service Offering", cloneServiceOfferingCmd.getDisplayText());
assertEquals(Integer.valueOf(4), cloneServiceOfferingCmd.getCpuNumber());
assertEquals(Integer.valueOf(8192), cloneServiceOfferingCmd.getMemory());
assertEquals(Integer.valueOf(2000), cloneServiceOfferingCmd.getCpuSpeed());
assertEquals(Boolean.TRUE, cloneServiceOfferingCmd.isOfferHa());
assertEquals(Boolean.FALSE, cloneServiceOfferingCmd.isLimitCpuUse());
assertEquals(Boolean.TRUE, cloneServiceOfferingCmd.isVolatileVm());
assertEquals("local", cloneServiceOfferingCmd.getStorageType());
assertEquals("premium", cloneServiceOfferingCmd.getTags());
assertEquals("gpu-enabled", cloneServiceOfferingCmd.getHostTag());
assertEquals(Integer.valueOf(1000), cloneServiceOfferingCmd.getNetworkRate());
assertEquals("UserDispersingPlanner", cloneServiceOfferingCmd.getDeploymentPlanner());
assertTrue(cloneServiceOfferingCmd.isPurgeResources());
}
@Test
public void testSourceOfferingIdNullByDefault() {
assertNull(cloneServiceOfferingCmd.getSourceOfferingId());
}
@Test
public void testGetSystemVmType() {
String systemVmType = "domainrouter";
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "systemVmType", systemVmType);
assertEquals(systemVmType, cloneServiceOfferingCmd.getSystemVmType());
}
@Test
public void testGetBytesReadRate() {
Long bytesReadRate = 1000000L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "bytesReadRate", bytesReadRate);
assertEquals(bytesReadRate, cloneServiceOfferingCmd.getBytesReadRate());
}
@Test
public void testGetBytesWriteRate() {
Long bytesWriteRate = 1000000L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "bytesWriteRate", bytesWriteRate);
assertEquals(bytesWriteRate, cloneServiceOfferingCmd.getBytesWriteRate());
}
@Test
public void testGetIopsReadRate() {
Long iopsReadRate = 1000L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "iopsReadRate", iopsReadRate);
assertEquals(iopsReadRate, cloneServiceOfferingCmd.getIopsReadRate());
}
@Test
public void testGetIopsWriteRate() {
Long iopsWriteRate = 1000L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "iopsWriteRate", iopsWriteRate);
assertEquals(iopsWriteRate, cloneServiceOfferingCmd.getIopsWriteRate());
}
@Test
public void testCloneServiceOfferingWithGpuProfile() {
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", 555L);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", "GPU-Offering-Clone");
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "vgpuProfileId", 10L);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "gpuCount", 2);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "gpuDisplay", true);
assertEquals(Long.valueOf(10L), cloneServiceOfferingCmd.getVgpuProfileId());
assertEquals(Integer.valueOf(2), cloneServiceOfferingCmd.getGpuCount());
assertTrue(cloneServiceOfferingCmd.getGpuDisplay());
}
@Test
public void testCloneServiceOfferingWithLease() {
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", 555L);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", "Lease-Offering-Clone");
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "leaseDuration", 7200);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "leaseExpiryAction", "destroy");
assertEquals(Integer.valueOf(7200), cloneServiceOfferingCmd.getLeaseDuration());
assertEquals(VMLeaseManager.ExpiryAction.DESTROY, cloneServiceOfferingCmd.getLeaseExpiryAction());
}
@Test
public void testExecuteWithOverriddenParameters() {
Long sourceOfferingId = 555L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId);
String newName = "ClonedOffering-Override";
String newDisplayText = "Overridden Display Text";
Integer newCpu = 8;
Integer newMemory = 16384;
Integer newCpuSpeed = 3000;
Boolean newOfferHa = true;
Boolean newLimitCpuUse = true;
String newStorageType = "shared";
String newTags = "premium,gpu";
String newHostTag = "compute-optimized";
Integer newNetworkRate = 2000;
String newDeploymentPlanner = "FirstFitPlanner";
Boolean newPurgeResources = true;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", newName);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "displayText", newDisplayText);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuNumber", newCpu);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "memory", newMemory);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuSpeed", newCpuSpeed);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "offerHa", newOfferHa);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "limitCpuUse", newLimitCpuUse);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "storageType", newStorageType);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "tags", newTags);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "hostTag", newHostTag);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "networkRate", newNetworkRate);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "deploymentPlanner", newDeploymentPlanner);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "purgeResources", newPurgeResources);
assertEquals(sourceOfferingId, cloneServiceOfferingCmd.getSourceOfferingId());
assertEquals(newName, cloneServiceOfferingCmd.getServiceOfferingName());
assertEquals(newDisplayText, cloneServiceOfferingCmd.getDisplayText());
assertEquals(newCpu, cloneServiceOfferingCmd.getCpuNumber());
assertEquals(newMemory, cloneServiceOfferingCmd.getMemory());
assertEquals(newCpuSpeed, cloneServiceOfferingCmd.getCpuSpeed());
assertEquals(newOfferHa, cloneServiceOfferingCmd.isOfferHa());
assertEquals(newLimitCpuUse, cloneServiceOfferingCmd.isLimitCpuUse());
assertEquals(newStorageType, cloneServiceOfferingCmd.getStorageType());
assertEquals(newTags, cloneServiceOfferingCmd.getTags());
assertEquals(newHostTag, cloneServiceOfferingCmd.getHostTag());
assertEquals(newNetworkRate, cloneServiceOfferingCmd.getNetworkRate());
assertEquals(newDeploymentPlanner, cloneServiceOfferingCmd.getDeploymentPlanner());
assertTrue(cloneServiceOfferingCmd.isPurgeResources());
when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering);
when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse);
cloneServiceOfferingCmd.execute();
assertNotNull(cloneServiceOfferingCmd.getResponseObject());
assertEquals(mockServiceOfferingResponse, cloneServiceOfferingCmd.getResponseObject());
}
@Test
public void testExecuteWithPartialOverrides() {
Long sourceOfferingId = 555L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId);
String newName = "PartialOverride";
Integer newCpu = 6;
Integer newMemory = 12288;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", newName);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuNumber", newCpu);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "memory", newMemory);
assertEquals(newName, cloneServiceOfferingCmd.getServiceOfferingName());
assertEquals(newCpu, cloneServiceOfferingCmd.getCpuNumber());
assertEquals(newMemory, cloneServiceOfferingCmd.getMemory());
assertNull(cloneServiceOfferingCmd.getCpuSpeed());
assertFalse(cloneServiceOfferingCmd.isOfferHa());
assertNull(cloneServiceOfferingCmd.getStorageType());
when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering);
when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse);
cloneServiceOfferingCmd.execute();
assertNotNull(cloneServiceOfferingCmd.getResponseObject());
assertEquals(mockServiceOfferingResponse, cloneServiceOfferingCmd.getResponseObject());
}
@Test
public void testExecuteWithGpuOverrides() {
Long sourceOfferingId = 555L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId);
String newName = "GPU-Clone-Override";
Long vgpuProfileId = 15L;
Integer gpuCount = 4;
Boolean gpuDisplay = false;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", newName);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "vgpuProfileId", vgpuProfileId);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "gpuCount", gpuCount);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "gpuDisplay", gpuDisplay);
assertEquals(newName, cloneServiceOfferingCmd.getServiceOfferingName());
assertEquals(vgpuProfileId, cloneServiceOfferingCmd.getVgpuProfileId());
assertEquals(gpuCount, cloneServiceOfferingCmd.getGpuCount());
assertEquals(gpuDisplay, cloneServiceOfferingCmd.getGpuDisplay());
when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering);
when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse);
cloneServiceOfferingCmd.execute();
assertNotNull(cloneServiceOfferingCmd.getResponseObject());
assertEquals(mockServiceOfferingResponse, cloneServiceOfferingCmd.getResponseObject());
}
@Test
public void testExecuteWithLeaseOverrides() {
Long sourceOfferingId = 555L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId);
String newName = "Lease-Clone-Override";
Integer leaseDuration = 14400; // 4 hours
String leaseExpiryAction = "stop";
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", newName);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "leaseDuration", leaseDuration);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "leaseExpiryAction", leaseExpiryAction);
assertEquals(newName, cloneServiceOfferingCmd.getServiceOfferingName());
assertEquals(leaseDuration, cloneServiceOfferingCmd.getLeaseDuration());
assertEquals(VMLeaseManager.ExpiryAction.STOP, cloneServiceOfferingCmd.getLeaseExpiryAction());
when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering);
when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse);
cloneServiceOfferingCmd.execute();
assertNotNull(cloneServiceOfferingCmd.getResponseObject());
assertEquals(mockServiceOfferingResponse, cloneServiceOfferingCmd.getResponseObject());
}
@Test
public void testExecuteWithStorageOverrides() {
Long sourceOfferingId = 555L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId);
String newName = "Storage-Clone-Override";
Long bytesReadRate = 2000000L;
Long bytesWriteRate = 1500000L;
Long iopsReadRate = 2000L;
Long iopsWriteRate = 1500L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", newName);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "bytesReadRate", bytesReadRate);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "bytesWriteRate", bytesWriteRate);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "iopsReadRate", iopsReadRate);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "iopsWriteRate", iopsWriteRate);
assertEquals(newName, cloneServiceOfferingCmd.getServiceOfferingName());
assertEquals(bytesReadRate, cloneServiceOfferingCmd.getBytesReadRate());
assertEquals(bytesWriteRate, cloneServiceOfferingCmd.getBytesWriteRate());
assertEquals(iopsReadRate, cloneServiceOfferingCmd.getIopsReadRate());
assertEquals(iopsWriteRate, cloneServiceOfferingCmd.getIopsWriteRate());
when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering);
when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse);
cloneServiceOfferingCmd.execute();
assertNotNull(cloneServiceOfferingCmd.getResponseObject());
assertEquals(mockServiceOfferingResponse, cloneServiceOfferingCmd.getResponseObject());
}
@Test
public void testExecuteWithDetailsOverride() {
Long sourceOfferingId = 555L;
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId);
String newName = "Details-Clone-Override";
Map<String, HashMap<String, String>> details = new HashMap<>();
HashMap<String, String> cpuOvercommit = new HashMap<>();
cpuOvercommit.put("key", "cpuOvercommitRatio");
cpuOvercommit.put("value", "3.0");
HashMap<String, String> memoryOvercommit = new HashMap<>();
memoryOvercommit.put("key", "memoryOvercommitRatio");
memoryOvercommit.put("value", "2.5");
HashMap<String, String> customDetail = new HashMap<>();
customDetail.put("key", "customParameter");
customDetail.put("value", "customValue");
details.put("0", cpuOvercommit);
details.put("1", memoryOvercommit);
details.put("2", customDetail);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", newName);
ReflectionTestUtils.setField(cloneServiceOfferingCmd, "details", details);
assertEquals(newName, cloneServiceOfferingCmd.getServiceOfferingName());
Map<String, String> result = cloneServiceOfferingCmd.getDetails();
assertNotNull(result);
assertEquals("3.0", result.get("cpuOvercommitRatio"));
assertEquals("2.5", result.get("memoryOvercommitRatio"));
assertEquals("customValue", result.get("customParameter"));
when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering);
when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse);
cloneServiceOfferingCmd.execute();
assertNotNull(cloneServiceOfferingCmd.getResponseObject());
assertEquals(mockServiceOfferingResponse, cloneServiceOfferingCmd.getResponseObject());
}
}

View File

@ -0,0 +1,299 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.vpc;
import com.cloud.network.vpc.VpcOffering;
import com.cloud.network.vpc.VpcProvisioningService;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ResponseGenerator;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.VpcOfferingResponse;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class CloneVpcOfferingCmdTest {
private CloneVPCOfferingCmd cloneVpcOfferingCmd;
@Mock
private VpcProvisioningService vpcService;
@Mock
private ResponseGenerator responseGenerator;
@Mock
private VpcOffering mockVpcOffering;
@Mock
private VpcOfferingResponse mockVpcOfferingResponse;
@Before
public void setUp() {
cloneVpcOfferingCmd = new CloneVPCOfferingCmd();
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "_vpcProvSvc", vpcService);
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "_responseGenerator", responseGenerator);
}
@Test
public void testGetSourceOfferingId() {
Long sourceOfferingId = 789L;
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "sourceOfferingId", sourceOfferingId);
assertEquals(sourceOfferingId, cloneVpcOfferingCmd.getSourceOfferingId());
}
@Test
public void testGetName() {
String name = "ClonedVpcOffering";
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "vpcOfferingName", name);
assertEquals(name, cloneVpcOfferingCmd.getVpcOfferingName());
}
@Test
public void testGetDisplayText() {
String displayText = "Cloned VPC Offering Display Text";
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "displayText", displayText);
assertEquals(displayText, cloneVpcOfferingCmd.getDisplayText());
}
@Test
public void testGetDisplayTextDefaultsToName() {
String name = "ClonedVpcOffering";
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "vpcOfferingName", name);
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "displayText", null);
assertEquals(name, cloneVpcOfferingCmd.getDisplayText());
}
@Test
public void testGetServiceOfferingId() {
Long serviceOfferingId = 456L;
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "serviceOfferingId", serviceOfferingId);
assertEquals(serviceOfferingId, cloneVpcOfferingCmd.getServiceOfferingId());
}
@Test
public void testGetInternetProtocol() {
String internetProtocol = "dualstack";
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "internetProtocol", internetProtocol);
assertEquals(internetProtocol, cloneVpcOfferingCmd.getInternetProtocol());
}
@Test
public void testGetProvider() {
String provider = "NSX";
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "provider", provider);
assertEquals(provider, cloneVpcOfferingCmd.getProvider());
}
@Test
public void testGetNetworkMode() {
String networkMode = "ROUTED";
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "networkMode", networkMode);
assertEquals(networkMode, cloneVpcOfferingCmd.getNetworkMode());
}
@Test
public void testGetRoutingMode() {
String routingMode = "dynamic";
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "routingMode", routingMode);
assertEquals(routingMode, cloneVpcOfferingCmd.getRoutingMode());
}
@Test
public void testGetNsxSupportLb() {
Boolean nsxSupportLb = true;
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "nsxSupportsLbService", nsxSupportLb);
assertEquals(nsxSupportLb, cloneVpcOfferingCmd.getNsxSupportsLbService());
}
@Test
public void testGetSpecifyAsnumber() {
Boolean specifyAsnumber = false;
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "specifyAsNumber", specifyAsnumber);
assertEquals(specifyAsnumber, cloneVpcOfferingCmd.getSpecifyAsNumber());
}
@Test
public void testExecuteSuccess() {
Long sourceOfferingId = 789L;
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "sourceOfferingId", sourceOfferingId);
when(vpcService.cloneVPCOffering(any(CloneVPCOfferingCmd.class))).thenReturn(mockVpcOffering);
when(responseGenerator.createVpcOfferingResponse(mockVpcOffering)).thenReturn(mockVpcOfferingResponse);
cloneVpcOfferingCmd.execute();
assertNotNull(cloneVpcOfferingCmd.getResponseObject());
assertEquals(mockVpcOfferingResponse, cloneVpcOfferingCmd.getResponseObject());
}
@Test(expected = ServerApiException.class)
public void testExecuteFailure() {
Long sourceOfferingId = 789L;
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "sourceOfferingId", sourceOfferingId);
when(vpcService.cloneVPCOffering(any(CloneVPCOfferingCmd.class))).thenReturn(null);
try {
cloneVpcOfferingCmd.execute();
fail("Expected ServerApiException to be thrown");
} catch (ServerApiException e) {
assertEquals(ApiErrorCode.INTERNAL_ERROR, e.getErrorCode());
assertEquals("Failed to clone VPC offering", e.getMessage());
throw e;
}
}
@Test
public void testGetSupportedServices() {
List<String> supportedServices = Arrays.asList("Dhcp", "Dns", "SourceNat", "NetworkACL");
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "supportedServices", supportedServices);
assertEquals(supportedServices, cloneVpcOfferingCmd.getSupportedServices());
}
@Test
public void testGetServiceProviders() {
Map<String, HashMap<String, String>> serviceProviderList = new HashMap<>();
HashMap<String, String> dhcpProvider = new HashMap<>();
dhcpProvider.put("service", "Dhcp");
dhcpProvider.put("provider", "VpcVirtualRouter");
HashMap<String, String> dnsProvider = new HashMap<>();
dnsProvider.put("service", "Dns");
dnsProvider.put("provider", "VpcVirtualRouter");
HashMap<String, String> aclProvider = new HashMap<>();
aclProvider.put("service", "NetworkACL");
aclProvider.put("provider", "VpcVirtualRouter");
serviceProviderList.put("0", dhcpProvider);
serviceProviderList.put("1", dnsProvider);
serviceProviderList.put("2", aclProvider);
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "serviceProviderList", serviceProviderList);
Map<String, List<String>> result = cloneVpcOfferingCmd.getServiceProviders();
assertNotNull(result);
assertEquals(3, result.size());
assertNotNull(result.get("Dhcp"));
assertNotNull(result.get("Dns"));
assertNotNull(result.get("NetworkACL"));
assertEquals("VpcVirtualRouter", result.get("Dhcp").get(0));
assertEquals("VpcVirtualRouter", result.get("Dns").get(0));
assertEquals("VpcVirtualRouter", result.get("NetworkACL").get(0));
}
@Test
public void testGetEnable() {
Boolean enable = true;
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "enable", enable);
assertEquals(enable, cloneVpcOfferingCmd.getEnable());
}
@Test
public void testCloneWithAllParameters() {
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "sourceOfferingId", 789L);
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "vpcOfferingName", "ClonedVpcOffering");
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "displayText", "Cloned VPC Offering");
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "serviceOfferingId", 456L);
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "internetProtocol", "ipv4");
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "provider", "NSX");
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "networkMode", "NATTED");
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "routingMode", "static");
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "nsxSupportsLbService", true);
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "specifyAsNumber", false);
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "enable", true);
assertEquals(Long.valueOf(789L), cloneVpcOfferingCmd.getSourceOfferingId());
assertEquals("ClonedVpcOffering", cloneVpcOfferingCmd.getVpcOfferingName());
assertEquals("Cloned VPC Offering", cloneVpcOfferingCmd.getDisplayText());
assertEquals(Long.valueOf(456L), cloneVpcOfferingCmd.getServiceOfferingId());
assertEquals("ipv4", cloneVpcOfferingCmd.getInternetProtocol());
assertEquals("NSX", cloneVpcOfferingCmd.getProvider());
assertEquals("NATTED", cloneVpcOfferingCmd.getNetworkMode());
assertEquals("static", cloneVpcOfferingCmd.getRoutingMode());
assertEquals(Boolean.TRUE, cloneVpcOfferingCmd.getNsxSupportsLbService());
assertEquals(Boolean.FALSE, cloneVpcOfferingCmd.getSpecifyAsNumber());
assertEquals(Boolean.TRUE, cloneVpcOfferingCmd.getEnable());
}
@Test
public void testSourceOfferingIdNullByDefault() {
assertNull(cloneVpcOfferingCmd.getSourceOfferingId());
}
@Test
public void testProviderNullByDefault() {
assertNull(cloneVpcOfferingCmd.getProvider());
}
@Test
public void testServiceCapabilityList() {
Map<String, List<String>> serviceCapabilityList = new HashMap<>();
serviceCapabilityList.put("Connectivity", Arrays.asList("RegionLevelVpc:true", "DistributedRouter:true"));
serviceCapabilityList.put("SourceNat", Arrays.asList("RedundantRouter:true"));
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "serviceCapabilityList", serviceCapabilityList);
Map<String, List<String>> result = cloneVpcOfferingCmd.getServiceCapabilityList();
assertNotNull(result);
assertEquals(serviceCapabilityList, result);
}
@Test
public void testCloneVpcOfferingWithNsxProvider() {
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "sourceOfferingId", 789L);
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "provider", "NSX");
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "nsxSupportsLbService", true);
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "networkMode", "ROUTED");
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "routingMode", "dynamic");
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "specifyAsNumber", true);
assertEquals("NSX", cloneVpcOfferingCmd.getProvider());
assertEquals(Boolean.TRUE, cloneVpcOfferingCmd.getNsxSupportsLbService());
assertEquals("ROUTED", cloneVpcOfferingCmd.getNetworkMode());
assertEquals("dynamic", cloneVpcOfferingCmd.getRoutingMode());
assertEquals(Boolean.TRUE, cloneVpcOfferingCmd.getSpecifyAsNumber());
}
@Test
public void testCloneVpcOfferingWithNetrisProvider() {
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "sourceOfferingId", 789L);
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "provider", "Netris");
ReflectionTestUtils.setField(cloneVpcOfferingCmd, "networkMode", "NATTED");
assertEquals("Netris", cloneVpcOfferingCmd.getProvider());
assertEquals("NATTED", cloneVpcOfferingCmd.getNetworkMode());
}
}

View File

@ -288,4 +288,6 @@ public interface IpAddressManager {
PublicIpQuarantine updatePublicIpAddressInQuarantine(Long quarantineProcessId, Date endDate);
void updateSourceNatIpAddress(IPAddressVO requestedIp, List<IPAddressVO> userIps) throws Exception;
Long getPreferredNetworkIdForPublicIpRuleAssignment(IpAddress ip, Long networkId);
}

View File

@ -39,7 +39,7 @@ import com.cloud.user.Account;
public interface LoadBalancingRulesManager {
LoadBalancer createPublicLoadBalancer(String xId, String name, String description, int srcPort, int destPort, long sourceIpId, String protocol, String algorithm,
boolean openFirewall, CallContext caller, String lbProtocol, Boolean forDisplay, String cidrList) throws NetworkRuleConflictException;
boolean openFirewall, CallContext caller, String lbProtocol, Boolean forDisplay, String cidrList, Long networkId) throws NetworkRuleConflictException;
boolean removeAllLoadBalanacersForIp(long ipId, Account caller, long callerUserId);

View File

@ -211,4 +211,9 @@ public interface VpcManager {
void reconfigStaticNatForVpcVr(Long vpcId);
boolean applyStaticRouteForVpcVpnIfNeeded(Long vpcId, boolean updateAllVpn) throws ResourceUnavailableException;
/**
* Returns true if the network is part of a VPC, and the VPC is created from conserve mode enabled VPC offering
*/
boolean isNetworkOnVpcEnabledConserveMode(Network network);
}

View File

@ -91,6 +91,9 @@ public class VpcOfferingVO implements VpcOffering {
@Column(name = "specify_as_number")
private Boolean specifyAsNumber = false;
@Column(name = "conserve_mode")
private boolean conserveMode;
public VpcOfferingVO() {
this.uuid = UUID.randomUUID().toString();
}
@ -242,4 +245,13 @@ public class VpcOfferingVO implements VpcOffering {
public void setSpecifyAsNumber(Boolean specifyAsNumber) {
this.specifyAsNumber = specifyAsNumber;
}
@Override
public boolean isConserveMode() {
return conserveMode;
}
public void setConserveMode(boolean conserveMode) {
this.conserveMode = conserveMode;
}
}

View File

@ -112,6 +112,9 @@ CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('User', 'deleteUserKeys', 'ALLOW
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Domain Admin', 'deleteUserKeys', 'ALLOW');
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Resource Admin', 'deleteUserKeys', 'ALLOW');
-- Add conserve mode for VPC offerings
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','conserve_mode', 'tinyint(1) unsigned NULL DEFAULT 0 COMMENT ''True if the VPC offering is IP conserve mode enabled, allowing public IP services to be used across multiple VPC tiers'' ');
-- ======================================================================
-- DNS Framework Schema

View File

@ -38,6 +38,7 @@ select
`vpc_offerings`.`sort_key` AS `sort_key`,
`vpc_offerings`.`routing_mode` AS `routing_mode`,
`vpc_offerings`.`specify_as_number` AS `specify_as_number`,
`vpc_offerings`.`conserve_mode` AS `conserve_mode`,
group_concat(distinct `domain`.`id` separator ',') AS `domain_id`,
group_concat(distinct `domain`.`uuid` separator ',') AS `domain_uuid`,
group_concat(distinct `domain`.`name` separator ',') AS `domain_name`,

View File

@ -693,7 +693,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu
ips.add(controlVmNic.getIPv4Address());
vmIdIpMap.put(clusterVMIds.get(i), ips);
}
lbService.assignToLoadBalancer(lb.getId(), null, vmIdIpMap, false);
lbService.assignToLoadBalancer(lb.getId(), null, vmIdIpMap, null, false);
}
protected Map<Long, Integer> createFirewallRules(IpAddress publicIp, List<Long> clusterVMIds, boolean apiRule) throws ManagementServerException {

View File

@ -363,7 +363,7 @@ public class LoadBalanceRuleHandler {
lb.setSourceIpAddressId(ipId);
result = _lbMgr.createPublicLoadBalancer(lb.getXid(), lb.getName(), lb.getDescription(), lb.getSourcePortStart(), lb.getDefaultPortStart(), ipId.longValue(),
lb.getProtocol(), lb.getAlgorithm(), false, CallContext.current(), lb.getLbProtocol(), true, null);
lb.getProtocol(), lb.getAlgorithm(), false, CallContext.current(), lb.getLbProtocol(), true, null, networkId);
} catch (final NetworkRuleConflictException e) {
logger.warn("Failed to create LB rule, not continuing with ELB deployment");
if (newIp) {

View File

@ -293,7 +293,7 @@ public class ContrailManagerImpl extends ManagerBase implements ContrailManager
}
serviceProviderMap.put(svc, providerSet);
}
vpcOffer = _vpcProvSvc.createVpcOffering(juniperVPCOfferingName, juniperVPCOfferingDisplayText, services, serviceProviderMap, null, null, null, null, null, null, null, VpcOffering.State.Enabled, null, false);
vpcOffer = _vpcProvSvc.createVpcOffering(juniperVPCOfferingName, juniperVPCOfferingDisplayText, services, serviceProviderMap, null, null, null, null, null, null, null, VpcOffering.State.Enabled, null, false, false);
long id = vpcOffer.getId();
_vpcOffDao.update(id, (VpcOfferingVO)vpcOffer);
return _vpcOffDao.findById(id);

View File

@ -1675,7 +1675,7 @@ public class ApiResponseHelper implements ResponseGenerator {
Network guestNtwk = ApiDBUtils.findNetworkById(fwRule.getNetworkId());
response.setNetworkId(guestNtwk.getUuid());
response.setNetworkName(guestNtwk.getName());
IpAddress ip = ApiDBUtils.findIpAddressById(fwRule.getSourceIpAddressId());
@ -3535,6 +3535,7 @@ public class ApiResponseHelper implements ResponseGenerator {
if (voff != null) {
response.setVpcOfferingId(voff.getUuid());
response.setVpcOfferingName(voff.getName());
response.setVpcOfferingConserveMode(voff.isConserveMode());
}
response.setCidr(vpc.getCidr());
response.setRestartRequired(vpc.isRestartRequired());

View File

@ -77,6 +77,7 @@ public class VpcOfferingJoinDaoImpl extends GenericDaoBase<VpcOfferingJoinVO, Lo
if (offering.isSpecifyAsNumber() != null) {
offeringResponse.setSpecifyAsNumber(offering.isSpecifyAsNumber());
}
offeringResponse.setConserveMode(offering.isConserveMode());
if (offering instanceof VpcOfferingJoinVO) {
VpcOfferingJoinVO offeringJoinVO = (VpcOfferingJoinVO) offering;
offeringResponse.setDomainId(offeringJoinVO.getDomainUuid());

View File

@ -112,6 +112,9 @@ public class VpcOfferingJoinVO implements VpcOffering {
@Column(name = "specify_as_number")
private Boolean specifyAsNumber = false;
@Column(name = "conserve_mode")
private boolean conserveMode;
public VpcOfferingJoinVO() {
}
@ -178,6 +181,11 @@ public class VpcOfferingJoinVO implements VpcOffering {
return specifyAsNumber;
}
@Override
public boolean isConserveMode() {
return conserveMode;
}
public void setSpecifyAsNumber(Boolean specifyAsNumber) {
this.specifyAsNumber = specifyAsNumber;
}

View File

@ -22,6 +22,7 @@ import static com.cloud.offering.NetworkOffering.RoutingMode.Static;
import static org.apache.cloudstack.framework.config.ConfigKey.CATEGORY_SYSTEM;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
@ -67,15 +68,18 @@ import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.command.admin.config.ResetCfgCmd;
import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd;
import org.apache.cloudstack.api.command.admin.network.CloneNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.CreateGuestNetworkIpv6PrefixCmd;
import org.apache.cloudstack.api.command.admin.network.CreateManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.DeleteGuestNetworkIpv6PrefixCmd;
import org.apache.cloudstack.api.command.admin.network.DeleteManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.network.DeleteNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.ListGuestNetworkIpv6PrefixesCmd;
import org.apache.cloudstack.api.command.admin.network.NetworkOfferingBaseCmd;
import org.apache.cloudstack.api.command.admin.network.UpdateNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.UpdatePodManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.offering.CloneDiskOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.CloneServiceOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.CreateDiskOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.DeleteDiskOfferingCmd;
@ -3854,6 +3858,459 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
return false;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_SERVICE_OFFERING_CLONE, eventDescription = "cloning service offering")
public ServiceOffering cloneServiceOffering(final CloneServiceOfferingCmd cmd) {
final long userId = CallContext.current().getCallingUserId();
final ServiceOfferingVO sourceOffering = getAndValidateSourceOffering(cmd.getSourceOfferingId());
final DiskOfferingVO sourceDiskOffering = getSourceDiskOffering(sourceOffering);
final Map<String, String> requestParams = cmd.getFullUrlParams();
final String name = cmd.getServiceOfferingName();
final String displayText = getOrDefault(cmd.getDisplayText(), sourceOffering.getDisplayText());
final Integer cpuNumber = getOrDefault(cmd.getCpuNumber(), sourceOffering.getCpu());
final Integer cpuSpeed = getOrDefault(cmd.getCpuSpeed(), sourceOffering.getSpeed());
final Integer memory = getOrDefault(cmd.getMemory(), sourceOffering.getRamSize());
final String provisioningType = resolveProvisioningType(cmd, sourceDiskOffering);
final Boolean offerHa = resolveBooleanParam(requestParams, ApiConstants.OFFER_HA, cmd::isOfferHa, sourceOffering.isOfferHA());
final Boolean limitCpuUse = resolveBooleanParam(requestParams, ApiConstants.LIMIT_CPU_USE, cmd::isLimitCpuUse, sourceOffering.getLimitCpuUse());
final Boolean isVolatile = resolveBooleanParam(requestParams, ApiConstants.IS_VOLATILE, cmd::isVolatileVm, sourceOffering.isVolatileVm());
final Boolean isCustomized = resolveBooleanParam(requestParams, ApiConstants.CUSTOMIZED, cmd::isCustomized, sourceOffering.isCustomized());
final Boolean dynamicScalingEnabled = resolveBooleanParam(requestParams, ApiConstants.DYNAMIC_SCALING_ENABLED, cmd::getDynamicScalingEnabled, sourceOffering.isDynamicScalingEnabled());
final Boolean diskOfferingStrictness = resolveBooleanParam(requestParams, ApiConstants.DISK_OFFERING_STRICTNESS, cmd::getDiskOfferingStrictness, sourceOffering.getDiskOfferingStrictness());
final Boolean encryptRoot = resolveBooleanParam(requestParams, ApiConstants.ENCRYPT_ROOT, cmd::getEncryptRoot, sourceDiskOffering != null && sourceDiskOffering.getEncrypt());
final Boolean gpuDisplay = resolveBooleanParam(requestParams, ApiConstants.GPU_DISPLAY, cmd::getGpuDisplay, sourceOffering.getGpuDisplay());
final String storageType = resolveStorageType(cmd, sourceDiskOffering);
final String tags = getOrDefault(cmd.getTags(), sourceDiskOffering != null ? sourceDiskOffering.getTags() : null);
final List<Long> domainIds = resolveDomainIds(cmd, sourceOffering);
final List<Long> zoneIds = resolveZoneIds(cmd, sourceOffering);
final String hostTag = getOrDefault(cmd.getHostTag(), sourceOffering.getHostTag());
final Integer networkRate = getOrDefault(cmd.getNetworkRate(), sourceOffering.getRateMbps());
final String deploymentPlanner = getOrDefault(cmd.getDeploymentPlanner(), sourceOffering.getDeploymentPlanner());
final ClonedDiskOfferingParams diskParams = resolveDiskOfferingParams(cmd, sourceDiskOffering);
final CustomOfferingParams customParams = resolveCustomOfferingParams(cmd, sourceOffering, isCustomized);
final Long vgpuProfileId = getOrDefault(cmd.getVgpuProfileId(), sourceOffering.getVgpuProfileId());
final Integer gpuCount = getOrDefault(cmd.getGpuCount(), sourceOffering.getGpuCount());
final Boolean purgeResources = resolvePurgeResources(cmd, requestParams, sourceOffering);
final LeaseParams leaseParams = resolveLeaseParams(cmd, sourceOffering);
if (cmd.getCacheMode() != null) {
validateCacheMode(cmd.getCacheMode());
}
final Integer finalGpuCount = validateVgpuProfileAndGetGpuCount(vgpuProfileId, gpuCount);
final Map<String, String> mergedDetails = mergeOfferingDetails(cmd, sourceOffering, customParams);
final boolean localStorageRequired = ServiceOffering.StorageType.local.toString().equalsIgnoreCase(storageType);
final boolean systemUse = sourceOffering.isSystemUse();
final VirtualMachine.Type vmType = resolveVmType(sourceOffering);
final Long diskOfferingId = getOrDefault(cmd.getDiskOfferingId(), sourceOffering.getDiskOfferingId());
return createServiceOffering(userId, systemUse, vmType,
name, cpuNumber, memory, cpuSpeed, displayText, provisioningType, localStorageRequired,
offerHa, limitCpuUse, isVolatile, tags, domainIds, zoneIds, hostTag, networkRate,
deploymentPlanner, mergedDetails, diskParams.rootDiskSize, diskParams.isCustomizedIops,
diskParams.minIops, diskParams.maxIops,
diskParams.bytesReadRate, diskParams.bytesReadRateMax, diskParams.bytesReadRateMaxLength,
diskParams.bytesWriteRate, diskParams.bytesWriteRateMax, diskParams.bytesWriteRateMaxLength,
diskParams.iopsReadRate, diskParams.iopsReadRateMax, diskParams.iopsReadRateMaxLength,
diskParams.iopsWriteRate, diskParams.iopsWriteRateMax, diskParams.iopsWriteRateMaxLength,
diskParams.hypervisorSnapshotReserve, diskParams.cacheMode, customParams.storagePolicy, dynamicScalingEnabled,
diskOfferingId, diskOfferingStrictness, isCustomized, encryptRoot,
vgpuProfileId, finalGpuCount, gpuDisplay, purgeResources, leaseParams.leaseDuration, leaseParams.leaseExpiryAction);
}
private ServiceOfferingVO getAndValidateSourceOffering(Long sourceOfferingId) {
final ServiceOfferingVO sourceOffering = _serviceOfferingDao.findById(sourceOfferingId);
if (sourceOffering == null) {
throw new InvalidParameterValueException("Unable to find service offering with ID: " + sourceOfferingId);
}
return sourceOffering;
}
private DiskOfferingVO getSourceDiskOffering(ServiceOfferingVO sourceOffering) {
final Long sourceDiskOfferingId = sourceOffering.getDiskOfferingId();
return sourceDiskOfferingId != null ? _diskOfferingDao.findById(sourceDiskOfferingId) : null;
}
public <T> T getOrDefault(T cmdValue, T defaultValue) {
return cmdValue != null ? cmdValue : defaultValue;
}
public Boolean resolveBooleanParam(Map<String, String> requestParams, String paramKey,
java.util.function.Supplier<Boolean> cmdValueSupplier, Boolean defaultValue) {
return requestParams != null && requestParams.containsKey(paramKey) ? cmdValueSupplier.get() : defaultValue;
}
private String resolveProvisioningType(CloneServiceOfferingCmd cmd, DiskOfferingVO sourceDiskOffering) {
if (cmd.getProvisioningType() != null) {
return cmd.getProvisioningType();
}
if (sourceDiskOffering != null) {
return sourceDiskOffering.getProvisioningType().toString();
}
return Storage.ProvisioningType.THIN.toString();
}
private String resolveStorageType(CloneServiceOfferingCmd cmd, DiskOfferingVO sourceDiskOffering) {
if (cmd.getStorageType() != null) {
return cmd.getStorageType();
}
if (sourceDiskOffering != null && sourceDiskOffering.isUseLocalStorage()) {
return ServiceOffering.StorageType.local.toString();
}
return ServiceOffering.StorageType.shared.toString();
}
private List<Long> resolveDomainIds(CloneServiceOfferingCmd cmd, ServiceOfferingVO sourceOffering) {
List<Long> domainIds = cmd.getDomainIds();
if (domainIds == null || domainIds.isEmpty()) {
domainIds = _serviceOfferingDetailsDao.findDomainIds(sourceOffering.getId());
}
return domainIds;
}
private List<Long> resolveZoneIds(CloneServiceOfferingCmd cmd, ServiceOfferingVO sourceOffering) {
List<Long> zoneIds = cmd.getZoneIds();
if (zoneIds == null || zoneIds.isEmpty()) {
zoneIds = _serviceOfferingDetailsDao.findZoneIds(sourceOffering.getId());
}
return zoneIds;
}
private ClonedDiskOfferingParams resolveDiskOfferingParams(CloneServiceOfferingCmd cmd, DiskOfferingVO sourceDiskOffering) {
final ClonedDiskOfferingParams params = new ClonedDiskOfferingParams();
params.rootDiskSize = getOrDefault(cmd.getRootDiskSize(), sourceDiskOffering != null ? sourceDiskOffering.getDiskSize() : null);
params.bytesReadRate = getOrDefault(cmd.getBytesReadRate(), sourceDiskOffering != null ? sourceDiskOffering.getBytesReadRate() : null);
params.bytesReadRateMax = getOrDefault(cmd.getBytesReadRateMax(), sourceDiskOffering != null ? sourceDiskOffering.getBytesReadRateMax() : null);
params.bytesReadRateMaxLength = getOrDefault(cmd.getBytesReadRateMaxLength(), sourceDiskOffering != null ? sourceDiskOffering.getBytesReadRateMaxLength() : null);
params.bytesWriteRate = getOrDefault(cmd.getBytesWriteRate(), sourceDiskOffering != null ? sourceDiskOffering.getBytesWriteRate() : null);
params.bytesWriteRateMax = getOrDefault(cmd.getBytesWriteRateMax(), sourceDiskOffering != null ? sourceDiskOffering.getBytesWriteRateMax() : null);
params.bytesWriteRateMaxLength = getOrDefault(cmd.getBytesWriteRateMaxLength(), sourceDiskOffering != null ? sourceDiskOffering.getBytesWriteRateMaxLength() : null);
params.iopsReadRate = getOrDefault(cmd.getIopsReadRate(), sourceDiskOffering != null ? sourceDiskOffering.getIopsReadRate() : null);
params.iopsReadRateMax = getOrDefault(cmd.getIopsReadRateMax(), sourceDiskOffering != null ? sourceDiskOffering.getIopsReadRateMax() : null);
params.iopsReadRateMaxLength = getOrDefault(cmd.getIopsReadRateMaxLength(), sourceDiskOffering != null ? sourceDiskOffering.getIopsReadRateMaxLength() : null);
params.iopsWriteRate = getOrDefault(cmd.getIopsWriteRate(), sourceDiskOffering != null ? sourceDiskOffering.getIopsWriteRate() : null);
params.iopsWriteRateMax = getOrDefault(cmd.getIopsWriteRateMax(), sourceDiskOffering != null ? sourceDiskOffering.getIopsWriteRateMax() : null);
params.iopsWriteRateMaxLength = getOrDefault(cmd.getIopsWriteRateMaxLength(), sourceDiskOffering != null ? sourceDiskOffering.getIopsWriteRateMaxLength() : null);
params.isCustomizedIops = getOrDefault(cmd.isCustomizedIops(), sourceDiskOffering != null ? sourceDiskOffering.isCustomizedIops() : null);
params.minIops = getOrDefault(cmd.getMinIops(), sourceDiskOffering != null ? sourceDiskOffering.getMinIops() : null);
params.maxIops = getOrDefault(cmd.getMaxIops(), sourceDiskOffering != null ? sourceDiskOffering.getMaxIops() : null);
params.hypervisorSnapshotReserve = getOrDefault(cmd.getHypervisorSnapshotReserve(), sourceDiskOffering != null ? sourceDiskOffering.getHypervisorSnapshotReserve() : null);
if (cmd.getCacheMode() != null) {
params.cacheMode = cmd.getCacheMode();
} else if (sourceDiskOffering != null && sourceDiskOffering.getCacheMode() != null) {
params.cacheMode = sourceDiskOffering.getCacheMode().toString();
}
return params;
}
private CustomOfferingParams resolveCustomOfferingParams(CloneServiceOfferingCmd cmd, ServiceOfferingVO sourceOffering, Boolean isCustomized) {
final CustomOfferingParams params = new CustomOfferingParams();
params.maxCPU = resolveDetailParameter(cmd.getMaxCPUs(), sourceOffering.getId(), ApiConstants.MAX_CPU_NUMBER);
params.minCPU = resolveDetailParameter(cmd.getMinCPUs(), sourceOffering.getId(), ApiConstants.MIN_CPU_NUMBER);
params.maxMemory = resolveDetailParameter(cmd.getMaxMemory(), sourceOffering.getId(), ApiConstants.MAX_MEMORY);
params.minMemory = resolveDetailParameter(cmd.getMinMemory(), sourceOffering.getId(), ApiConstants.MIN_MEMORY);
params.storagePolicy = resolveDetailParameterAsLong(cmd.getStoragePolicy(), sourceOffering.getId(), ApiConstants.STORAGE_POLICY);
return params;
}
private Integer resolveDetailParameter(Integer cmdValue, Long offeringId, String detailKey) {
if (cmdValue != null) {
return cmdValue;
}
String detailValue = _serviceOfferingDetailsDao.getDetail(offeringId, detailKey);
return detailValue != null ? Integer.parseInt(detailValue) : null;
}
private Long resolveDetailParameterAsLong(Long cmdValue, Long offeringId, String detailKey) {
if (cmdValue != null) {
return cmdValue;
}
String detailValue = _serviceOfferingDetailsDao.getDetail(offeringId, detailKey);
return detailValue != null ? Long.parseLong(detailValue) : null;
}
private Boolean resolvePurgeResources(CloneServiceOfferingCmd cmd, Map<String, String> requestParams, ServiceOfferingVO sourceOffering) {
if (requestParams != null && requestParams.containsKey(ApiConstants.PURGE_RESOURCES)) {
return cmd.isPurgeResources();
}
String purgeResourcesStr = _serviceOfferingDetailsDao.getDetail(sourceOffering.getId(), ServiceOffering.PURGE_DB_ENTITIES_KEY);
return Boolean.parseBoolean(purgeResourcesStr);
}
private LeaseParams resolveLeaseParams(CloneServiceOfferingCmd cmd, ServiceOfferingVO sourceOffering) {
final LeaseParams params = new LeaseParams();
params.leaseDuration = resolveDetailParameter(cmd.getLeaseDuration(), sourceOffering.getId(), ApiConstants.INSTANCE_LEASE_DURATION);
if (cmd.getLeaseExpiryAction() != null) {
params.leaseExpiryAction = cmd.getLeaseExpiryAction();
} else {
String leaseExpiryActionStr = _serviceOfferingDetailsDao.getDetail(sourceOffering.getId(), ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION);
if (leaseExpiryActionStr != null) {
params.leaseExpiryAction = VMLeaseManager.ExpiryAction.valueOf(leaseExpiryActionStr);
}
}
params.leaseExpiryAction = validateAndGetLeaseExpiryAction(params.leaseDuration, params.leaseExpiryAction);
return params;
}
private Map<String, String> mergeOfferingDetails(CloneServiceOfferingCmd cmd, ServiceOfferingVO sourceOffering, CustomOfferingParams customParams) {
final Map<String, String> cmdDetails = cmd.getDetails();
final Map<String, String> mergedDetails = new HashMap<>();
if (cmdDetails == null || cmdDetails.isEmpty()) {
Map<String, String> sourceDetails = _serviceOfferingDetailsDao.listDetailsKeyPairs(sourceOffering.getId());
if (sourceDetails != null) {
mergedDetails.putAll(sourceDetails);
}
} else {
mergedDetails.putAll(cmdDetails);
}
if (customParams.minCPU != null && customParams.maxCPU != null &&
customParams.minMemory != null && customParams.maxMemory != null) {
mergedDetails.put(ApiConstants.MIN_MEMORY, customParams.minMemory.toString());
mergedDetails.put(ApiConstants.MAX_MEMORY, customParams.maxMemory.toString());
mergedDetails.put(ApiConstants.MIN_CPU_NUMBER, customParams.minCPU.toString());
mergedDetails.put(ApiConstants.MAX_CPU_NUMBER, customParams.maxCPU.toString());
}
return mergedDetails;
}
private VirtualMachine.Type resolveVmType(ServiceOfferingVO sourceOffering) {
if (sourceOffering.getVmType() == null) {
return null;
}
try {
return VirtualMachine.Type.valueOf(sourceOffering.getVmType());
} catch (IllegalArgumentException e) {
logger.warn("Invalid VM type in source offering: {}", sourceOffering.getVmType());
return null;
}
}
private static class ClonedDiskOfferingParams {
Long rootDiskSize;
Long bytesReadRate;
Long bytesReadRateMax;
Long bytesReadRateMaxLength;
Long bytesWriteRate;
Long bytesWriteRateMax;
Long bytesWriteRateMaxLength;
Long iopsReadRate;
Long iopsReadRateMax;
Long iopsReadRateMaxLength;
Long iopsWriteRate;
Long iopsWriteRateMax;
Long iopsWriteRateMaxLength;
Boolean isCustomizedIops;
Long minIops;
Long maxIops;
Integer hypervisorSnapshotReserve;
String cacheMode;
}
private static class CustomOfferingParams {
Integer maxCPU;
Integer minCPU;
Integer maxMemory;
Integer minMemory;
Long storagePolicy;
}
private static class LeaseParams {
Integer leaseDuration;
VMLeaseManager.ExpiryAction leaseExpiryAction;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_DISK_OFFERING_CLONE, eventDescription = "cloning disk offering")
public DiskOffering cloneDiskOffering(final CloneDiskOfferingCmd cmd) {
final long userId = CallContext.current().getCallingUserId();
final DiskOfferingVO sourceOffering = getAndValidateSourceDiskOffering(cmd.getSourceOfferingId());
final Map<String, String> requestParams = cmd.getFullUrlParams();
final String name = cmd.getOfferingName();
final String displayText = getOrDefault(cmd.getDisplayText(), sourceOffering.getDisplayText());
final String provisioningType = getOrDefault(cmd.getProvisioningType(), sourceOffering.getProvisioningType().toString());
final Long diskSize = getOrDefault(cmd.getDiskSize(), sourceOffering.getDiskSize());
final String tags = getOrDefault(cmd.getTags(), sourceOffering.getTags());
final Boolean isCustomized = resolveBooleanParam(requestParams, ApiConstants.CUSTOMIZED, cmd::isCustomized, sourceOffering.isCustomized());
final Boolean displayOffering = resolveBooleanParam(requestParams, ApiConstants.DISPLAY_OFFERING, cmd::getDisplayOffering, sourceOffering.getDisplayOffering());
final Boolean isCustomizedIops = getOrDefault(cmd.isCustomizedIops(), sourceOffering.isCustomizedIops());
final Boolean diskSizeStrictness = resolveBooleanParam(requestParams, ApiConstants.DISK_SIZE_STRICTNESS, cmd::getDiskSizeStrictness, sourceOffering.getDiskSizeStrictness());
final Boolean encrypt = resolveBooleanParam(requestParams, ApiConstants.ENCRYPT, cmd::getEncrypt, sourceOffering.getEncrypt());
final List<Long> domainIds = resolveDomainIdsForDiskOffering(cmd, sourceOffering);
final List<Long> zoneIds = resolveZoneIdsForDiskOffering(cmd, sourceOffering);
final boolean localStorageRequired = resolveLocalStorageRequired(cmd, sourceOffering);
final ClonedDiskIopsParams iopsParams = resolveDiskIopsParams(cmd, sourceOffering);
final ClonedDiskRateParams rateParams = resolveDiskRateParams(cmd, sourceOffering);
final Integer hypervisorSnapshotReserve = getOrDefault(cmd.getHypervisorSnapshotReserve(), sourceOffering.getHypervisorSnapshotReserve());
final String cacheMode = resolveCacheMode(cmd, sourceOffering);
final Long storagePolicy = resolveStoragePolicyForDiskOffering(cmd, sourceOffering);
final Map<String, String> mergedDetails = mergeDiskOfferingDetails(cmd, sourceOffering);
if (cmd.getCacheMode() != null) {
validateCacheMode(cmd.getCacheMode());
}
validateMaxRateEqualsOrGreater(iopsParams.iopsReadRate, iopsParams.iopsReadRateMax, IOPS_READ_RATE);
validateMaxRateEqualsOrGreater(iopsParams.iopsWriteRate, iopsParams.iopsWriteRateMax, IOPS_WRITE_RATE);
validateMaxRateEqualsOrGreater(rateParams.bytesReadRate, rateParams.bytesReadRateMax, BYTES_READ_RATE);
validateMaxRateEqualsOrGreater(rateParams.bytesWriteRate, rateParams.bytesWriteRateMax, BYTES_WRITE_RATE);
validateMaximumIopsAndBytesLength(iopsParams.iopsReadRateMaxLength, iopsParams.iopsWriteRateMaxLength,
rateParams.bytesReadRateMaxLength, rateParams.bytesWriteRateMaxLength);
return createDiskOffering(userId, domainIds, zoneIds, name, displayText, provisioningType, diskSize, tags,
isCustomized, localStorageRequired, displayOffering, isCustomizedIops, iopsParams.minIops, iopsParams.maxIops,
rateParams.bytesReadRate, rateParams.bytesReadRateMax, rateParams.bytesReadRateMaxLength,
rateParams.bytesWriteRate, rateParams.bytesWriteRateMax, rateParams.bytesWriteRateMaxLength,
iopsParams.iopsReadRate, iopsParams.iopsReadRateMax, iopsParams.iopsReadRateMaxLength,
iopsParams.iopsWriteRate, iopsParams.iopsWriteRateMax, iopsParams.iopsWriteRateMaxLength,
hypervisorSnapshotReserve, cacheMode, mergedDetails, storagePolicy, diskSizeStrictness, encrypt);
}
private DiskOfferingVO getAndValidateSourceDiskOffering(Long sourceOfferingId) {
final DiskOfferingVO sourceOffering = _diskOfferingDao.findById(sourceOfferingId);
if (sourceOffering == null) {
throw new InvalidParameterValueException("Unable to find disk offering with ID: " + sourceOfferingId);
}
return sourceOffering;
}
private List<Long> resolveDomainIdsForDiskOffering(CloneDiskOfferingCmd cmd, DiskOfferingVO sourceOffering) {
List<Long> domainIds = cmd.getDomainIds();
if (domainIds == null || domainIds.isEmpty()) {
domainIds = diskOfferingDetailsDao.findDomainIds(sourceOffering.getId());
}
return domainIds;
}
private List<Long> resolveZoneIdsForDiskOffering(CloneDiskOfferingCmd cmd, DiskOfferingVO sourceOffering) {
List<Long> zoneIds = cmd.getZoneIds();
if (zoneIds == null || zoneIds.isEmpty()) {
zoneIds = diskOfferingDetailsDao.findZoneIds(sourceOffering.getId());
}
return zoneIds;
}
private boolean resolveLocalStorageRequired(CloneDiskOfferingCmd cmd, DiskOfferingVO sourceOffering) {
if (cmd.getStorageType() != null) {
return ServiceOffering.StorageType.local.toString().equalsIgnoreCase(cmd.getStorageType());
}
return sourceOffering.isUseLocalStorage();
}
private String resolveCacheMode(CloneDiskOfferingCmd cmd, DiskOfferingVO sourceOffering) {
if (cmd.getCacheMode() != null) {
return cmd.getCacheMode();
}
if (sourceOffering.getCacheMode() != null) {
return sourceOffering.getCacheMode().toString();
}
return null;
}
private Long resolveStoragePolicyForDiskOffering(CloneDiskOfferingCmd cmd, DiskOfferingVO sourceOffering) {
Long storagePolicy = cmd.getStoragePolicy();
if (storagePolicy == null) {
String storagePolicyStr = diskOfferingDetailsDao.getDetail(sourceOffering.getId(), ApiConstants.STORAGE_POLICY);
if (storagePolicyStr != null) {
storagePolicy = Long.parseLong(storagePolicyStr);
}
}
return storagePolicy;
}
private ClonedDiskIopsParams resolveDiskIopsParams(CloneDiskOfferingCmd cmd, DiskOfferingVO sourceOffering) {
final ClonedDiskIopsParams params = new ClonedDiskIopsParams();
params.minIops = getOrDefault(cmd.getMinIops(), sourceOffering.getMinIops());
params.maxIops = getOrDefault(cmd.getMaxIops(), sourceOffering.getMaxIops());
params.iopsReadRate = getOrDefault(cmd.getIopsReadRate(), sourceOffering.getIopsReadRate());
params.iopsReadRateMax = getOrDefault(cmd.getIopsReadRateMax(), sourceOffering.getIopsReadRateMax());
params.iopsReadRateMaxLength = getOrDefault(cmd.getIopsReadRateMaxLength(), sourceOffering.getIopsReadRateMaxLength());
params.iopsWriteRate = getOrDefault(cmd.getIopsWriteRate(), sourceOffering.getIopsWriteRate());
params.iopsWriteRateMax = getOrDefault(cmd.getIopsWriteRateMax(), sourceOffering.getIopsWriteRateMax());
params.iopsWriteRateMaxLength = getOrDefault(cmd.getIopsWriteRateMaxLength(), sourceOffering.getIopsWriteRateMaxLength());
return params;
}
private ClonedDiskRateParams resolveDiskRateParams(CloneDiskOfferingCmd cmd, DiskOfferingVO sourceOffering) {
final ClonedDiskRateParams params = new ClonedDiskRateParams();
params.bytesReadRate = getOrDefault(cmd.getBytesReadRate(), sourceOffering.getBytesReadRate());
params.bytesReadRateMax = getOrDefault(cmd.getBytesReadRateMax(), sourceOffering.getBytesReadRateMax());
params.bytesReadRateMaxLength = getOrDefault(cmd.getBytesReadRateMaxLength(), sourceOffering.getBytesReadRateMaxLength());
params.bytesWriteRate = getOrDefault(cmd.getBytesWriteRate(), sourceOffering.getBytesWriteRate());
params.bytesWriteRateMax = getOrDefault(cmd.getBytesWriteRateMax(), sourceOffering.getBytesWriteRateMax());
params.bytesWriteRateMaxLength = getOrDefault(cmd.getBytesWriteRateMaxLength(), sourceOffering.getBytesWriteRateMaxLength());
return params;
}
private Map<String, String> mergeDiskOfferingDetails(CloneDiskOfferingCmd cmd, DiskOfferingVO sourceOffering) {
final Map<String, String> cmdDetails = cmd.getDetails();
final Map<String, String> mergedDetails = new HashMap<>();
if (cmdDetails == null || cmdDetails.isEmpty()) {
Map<String, String> sourceDetails = diskOfferingDetailsDao.listDetailsKeyPairs(sourceOffering.getId());
if (sourceDetails != null) {
mergedDetails.putAll(sourceDetails);
}
} else {
mergedDetails.putAll(cmdDetails);
}
return mergedDetails;
}
// Helper classes for disk offering parameters
private static class ClonedDiskIopsParams {
Long minIops;
Long maxIops;
Long iopsReadRate;
Long iopsReadRateMax;
Long iopsReadRateMaxLength;
Long iopsWriteRate;
Long iopsWriteRateMax;
Long iopsWriteRateMaxLength;
}
private static class ClonedDiskRateParams {
Long bytesReadRate;
Long bytesReadRateMax;
Long bytesReadRateMaxLength;
Long bytesWriteRate;
Long bytesWriteRateMax;
Long bytesWriteRateMaxLength;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_SERVICE_OFFERING_EDIT, eventDescription = "updating service offering")
public ServiceOffering updateServiceOffering(final UpdateServiceOfferingCmd cmd) {
@ -6590,7 +7047,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
@Override
@ActionEvent(eventType = EventTypes.EVENT_NETWORK_OFFERING_CREATE, eventDescription = "creating network offering")
public NetworkOffering createNetworkOffering(final CreateNetworkOfferingCmd cmd) {
public NetworkOffering createNetworkOffering(final NetworkOfferingBaseCmd cmd) {
final String name = cmd.getNetworkOfferingName();
final String displayText = cmd.getDisplayText();
final NetUtils.InternetProtocol internetProtocol = NetUtils.InternetProtocol.fromValue(cmd.getInternetProtocol());
@ -7827,6 +8284,431 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
}
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_NETWORK_OFFERING_CLONE, eventDescription = "cloning network offering")
public NetworkOffering cloneNetworkOffering(final CloneNetworkOfferingCmd cmd) {
final Long sourceOfferingId = cmd.getSourceOfferingId();
final NetworkOfferingVO sourceOffering = _networkOfferingDao.findById(sourceOfferingId);
if (sourceOffering == null) {
throw new InvalidParameterValueException("Unable to find network offering with id " + sourceOfferingId);
}
String name = cmd.getNetworkOfferingName();
if (name == null || name.isEmpty()) {
throw new InvalidParameterValueException("Name is required when cloning a network offering");
}
NetworkOfferingVO existing = _networkOfferingDao.findByUniqueName(name);
if (existing != null) {
throw new InvalidParameterValueException("Network offering with name '" + name + "' already exists");
}
logger.info("Cloning network offering {} (id: {}) to new offering with name: {}",
sourceOffering.getName(), sourceOfferingId, name);
Map<Network.Service, Set<Network.Provider>> sourceServiceProviderMap =
_networkModel.getNetworkOfferingServiceProvidersMap(sourceOfferingId);
validateProvider(sourceOffering, sourceServiceProviderMap, cmd.getProvider(), cmd.getNetworkMode());
applySourceOfferingValuesToCloneCmd(cmd, sourceServiceProviderMap, sourceOffering);
return createNetworkOffering(cmd);
}
private void validateProvider(NetworkOfferingVO sourceOffering,
Map<Network.Service, Set<Network.Provider>> sourceServiceProviderMap,
String detectedProvider, String networkMode) {
detectedProvider = getExternalNetworkProvider(detectedProvider, sourceServiceProviderMap);
// If this is an NSX/Netris offering, prevent network mode changes
if (detectedProvider != null && (detectedProvider.equals("NSX") || detectedProvider.equals("Netris"))) {
if (networkMode != null && sourceOffering.getNetworkMode() != null) {
if (!networkMode.equalsIgnoreCase(sourceOffering.getNetworkMode().toString())) {
throw new InvalidParameterValueException(
String.format("Cannot change network mode when cloning %s provider network offerings. " +
"Source offering has network mode '%s', but '%s' was specified. ",
detectedProvider, sourceOffering.getNetworkMode(), networkMode));
}
}
}
}
public static String getExternalNetworkProvider(String detectedProvider,
Map<Network.Service, Set<Network.Provider>> sourceServiceProviderMap) {
if (StringUtils.isNotEmpty(detectedProvider)) {
return detectedProvider;
}
if (sourceServiceProviderMap == null || sourceServiceProviderMap.isEmpty()) {
return null;
}
for (Set<Provider> providers : sourceServiceProviderMap.values()) {
if (CollectionUtils.isEmpty(providers)) {
continue;
}
for (Provider provider : providers) {
if (provider == Provider.Nsx) {
return "NSX";
}
if (provider == Provider.Netris) {
return "Netris";
}
}
}
return null;
}
/**
* Converts service provider map from internal format to API parameter format.
*
* Internal format: Map<String, List<String>> where key=serviceName, value=list of provider names
* API parameter format: Map where each value is a HashMap with "service" and "provider" keys
*
* Example: {"Lb": ["VirtualRouter"]} becomes {0: {"service": "Lb", "provider": "VirtualRouter"}}
*/
private Map<String, Map<String, String>> convertToApiParameterFormat(Map<String, List<String>> serviceProviderMap) {
Map<String, Map<String, String>> apiFormatMap = new HashMap<>();
int index = 0;
for (Map.Entry<String, List<String>> entry : serviceProviderMap.entrySet()) {
String serviceName = entry.getKey();
List<String> providers = entry.getValue();
for (String provider : providers) {
Map<String, String> serviceProviderEntry = new HashMap<>();
serviceProviderEntry.put("service", serviceName);
serviceProviderEntry.put("provider", provider);
apiFormatMap.put(String.valueOf(index), serviceProviderEntry);
index++;
}
}
return apiFormatMap;
}
private void applySourceOfferingValuesToCloneCmd(CloneNetworkOfferingCmd cmd,
Map<Network.Service, Set<Network.Provider>> sourceServiceProviderMap,
NetworkOfferingVO sourceOffering) {
Long sourceOfferingId = sourceOffering.getId();
// Build final services list with add/drop support
List<String> finalServices = resolveFinalServicesList(cmd, sourceServiceProviderMap);
Map<String, List<String>> finalServiceProviderMap = resolveServiceProviderMap(cmd, sourceServiceProviderMap, finalServices);
Map<String, Map<String, String>> sourceServiceCapabilityList = reconstructNetworkServiceCapabilityList(sourceOffering);
Map<String, String> sourceDetailsMap = getSourceOfferingDetails(sourceOfferingId);
List<Long> sourceDomainIds = networkOfferingDetailsDao.findDomainIds(sourceOfferingId);
List<Long> sourceZoneIds = networkOfferingDetailsDao.findZoneIds(sourceOfferingId);
applyResolvedValuesToCommand(cmd, sourceOffering, finalServices, finalServiceProviderMap,
sourceServiceCapabilityList, sourceDetailsMap, sourceDomainIds, sourceZoneIds);
}
private Map<String, String> getSourceOfferingDetails(Long sourceOfferingId) {
List<NetworkOfferingDetailsVO> sourceDetailsVOs = networkOfferingDetailsDao.listDetails(sourceOfferingId);
Map<String, String> sourceDetailsMap = new HashMap<>();
for (NetworkOfferingDetailsVO detailVO : sourceDetailsVOs) {
sourceDetailsMap.put(detailVO.getName(), detailVO.getValue());
}
return sourceDetailsMap;
}
private List<String> resolveFinalServicesList(CloneNetworkOfferingCmd cmd,
Map<Network.Service, Set<Network.Provider>> sourceServiceProviderMap) {
List<String> cmdServices = cmd.getSupportedServices();
List<String> addServices = cmd.getAddServices();
List<String> dropServices = cmd.getDropServices();
if (cmdServices != null && !cmdServices.isEmpty()) {
return cmdServices;
}
List<String> finalServices = new ArrayList<>();
for (Network.Service service : sourceServiceProviderMap.keySet()) {
if (service != Network.Service.Gateway) {
finalServices.add(service.getName());
}
}
if (dropServices != null && !dropServices.isEmpty()) {
List<String> normalizedDropServices = new ArrayList<>();
for (String serviceName : dropServices) {
Network.Service service = Network.Service.getService(serviceName);
if (service == null) {
throw new InvalidParameterValueException("Invalid service name in dropServices: " + serviceName);
}
normalizedDropServices.add(service.getName());
}
finalServices.removeAll(normalizedDropServices);
logger.debug("Dropped services from clone: {}", normalizedDropServices);
}
if (addServices != null && !addServices.isEmpty()) {
List<String> normalizedAddServices = new ArrayList<>();
for (String serviceName : addServices) {
Network.Service service = Network.Service.getService(serviceName);
if (service == null) {
throw new InvalidParameterValueException("Invalid service name in addServices: " + serviceName);
}
String canonicalName = service.getName();
if (!finalServices.contains(canonicalName)) {
finalServices.add(canonicalName);
normalizedAddServices.add(canonicalName);
}
}
logger.debug("Added services to clone: {}", normalizedAddServices);
}
return finalServices;
}
private Map<String, List<String>> resolveServiceProviderMap(CloneNetworkOfferingCmd cmd,
Map<Network.Service, Set<Network.Provider>> sourceServiceProviderMap, List<String> finalServices) {
if (cmd.getServiceProviders() != null && !cmd.getServiceProviders().isEmpty()) {
return cmd.getServiceProviders();
}
Map<String, List<String>> finalMap = new HashMap<>();
for (Map.Entry<Network.Service, Set<Network.Provider>> entry : sourceServiceProviderMap.entrySet()) {
String serviceName = entry.getKey().getName();
if (finalServices.contains(serviceName)) {
List<String> providers = new ArrayList<>();
for (Network.Provider provider : entry.getValue()) {
providers.add(provider.getName());
}
finalMap.put(serviceName, providers);
}
}
return finalMap;
}
private void applyResolvedValuesToCommand(CloneNetworkOfferingCmd cmd, NetworkOfferingVO sourceOffering,
List<String> finalServices, Map<String, List<String>> finalServiceProviderMap, Map<String, Map<String, String>> sourceServiceCapabilityList,
Map<String, String> sourceDetailsMap, List<Long> sourceDomainIds, List<Long> sourceZoneIds) {
try {
Map<String, String> requestParams = cmd.getFullUrlParams();
if (cmd.getSupportedServices() == null || cmd.getSupportedServices().isEmpty()) {
setField(cmd, "supportedServices", finalServices);
}
if (cmd.getServiceProviders() == null || cmd.getServiceProviders().isEmpty()) {
Map<String, Map<String, String>> apiFormatMap = convertToApiParameterFormat(finalServiceProviderMap);
setField(cmd, "serviceProviderList", apiFormatMap);
}
boolean hasCapabilityParams = requestParams.keySet().stream()
.anyMatch(key -> key.startsWith(ApiConstants.SERVICE_CAPABILITY_LIST));
if (!hasCapabilityParams && sourceServiceCapabilityList != null && !sourceServiceCapabilityList.isEmpty()) {
// Filter capabilities to only include those for services in the final service list
// This ensures that if services are dropped, their capabilities are also removed
Map<String, Map<String, String>> filteredCapabilities = new HashMap<>();
for (Map.Entry<String, Map<String, String>> entry : sourceServiceCapabilityList.entrySet()) {
Map<String, String> capabilityMap = entry.getValue();
String serviceName = capabilityMap.get("service");
if (serviceName != null && finalServices.contains(serviceName)) {
filteredCapabilities.put(entry.getKey(), capabilityMap);
}
}
if (!filteredCapabilities.isEmpty()) {
setField(cmd, "serviceCapabilitiesList", filteredCapabilities);
}
}
applyIfNotProvided(cmd, requestParams, "displayText", ApiConstants.DISPLAY_TEXT, cmd.getDisplayText(), sourceOffering.getDisplayText());
applyIfNotProvided(cmd, requestParams, "traffictype", ApiConstants.TRAFFIC_TYPE, cmd.getTraffictype(), sourceOffering.getTrafficType().toString());
applyIfNotProvided(cmd, requestParams, "tags", ApiConstants.TAGS, cmd.getTags(), sourceOffering.getTags());
applyIfNotProvided(cmd, requestParams, "availability", ApiConstants.AVAILABILITY, cmd.getAvailability(), Availability.Optional.toString());
applyIfNotProvided(cmd, requestParams, "networkRate", ApiConstants.NETWORKRATE, cmd.getNetworkRate(), sourceOffering.getRateMbps());
applyIfNotProvided(cmd, requestParams, "serviceOfferingId", ApiConstants.SERVICE_OFFERING_ID, cmd.getServiceOfferingId(), sourceOffering.getServiceOfferingId());
applyIfNotProvided(cmd, requestParams, "guestIptype", ApiConstants.GUEST_IP_TYPE, cmd.getGuestIpType(), sourceOffering.getGuestType().toString());
applyIfNotProvided(cmd, requestParams, "maxConnections", ApiConstants.MAX_CONNECTIONS, cmd.getMaxconnections(), sourceOffering.getConcurrentConnections());
applyBooleanIfNotProvided(cmd, requestParams, "specifyVlan", ApiConstants.SPECIFY_VLAN, sourceOffering.isSpecifyVlan());
applyBooleanIfNotProvided(cmd, requestParams, "conserveMode", ApiConstants.CONSERVE_MODE, sourceOffering.isConserveMode());
applyBooleanIfNotProvided(cmd, requestParams, "specifyIpRanges", ApiConstants.SPECIFY_IP_RANGES, sourceOffering.isSpecifyIpRanges());
applyBooleanIfNotProvided(cmd, requestParams, "isPersistent", ApiConstants.IS_PERSISTENT, sourceOffering.isPersistent());
applyBooleanIfNotProvided(cmd, requestParams, "forVpc", ApiConstants.FOR_VPC, sourceOffering.isForVpc());
applyBooleanIfNotProvided(cmd, requestParams, "egressDefaultPolicy", ApiConstants.EGRESS_DEFAULT_POLICY, sourceOffering.isEgressDefaultPolicy());
applyBooleanIfNotProvided(cmd, requestParams, "keepAliveEnabled", ApiConstants.KEEPALIVE_ENABLED, sourceOffering.isKeepAliveEnabled());
applyBooleanIfNotProvided(cmd, requestParams, "enable", ApiConstants.ENABLE, sourceOffering.getState() == NetworkOffering.State.Enabled);
applyBooleanIfNotProvided(cmd, requestParams, "specifyAsNumber", ApiConstants.SPECIFY_AS_NUMBER, sourceOffering.isSpecifyAsNumber());
if (!requestParams.containsKey(ApiConstants.INTERNET_PROTOCOL)) {
String internetProtocol = networkOfferingDetailsDao.getDetail(sourceOffering.getId(), Detail.internetProtocol);
if (internetProtocol != null) {
setField(cmd, "internetProtocol", internetProtocol);
}
}
if (!requestParams.containsKey(ApiConstants.NETWORK_MODE) && sourceOffering.getNetworkMode() != null) {
setField(cmd, "networkMode", sourceOffering.getNetworkMode().toString());
}
if (!requestParams.containsKey(ApiConstants.ROUTING_MODE) && sourceOffering.getRoutingMode() != null) {
setField(cmd, "routingMode", sourceOffering.getRoutingMode().toString());
}
if (cmd.getDetails() == null || cmd.getDetails().isEmpty()) {
if (!sourceDetailsMap.isEmpty()) {
setField(cmd, "details", sourceDetailsMap);
}
}
if (cmd.getDomainIds() == null || cmd.getDomainIds().isEmpty()) {
if (sourceDomainIds != null && !sourceDomainIds.isEmpty()) {
setField(cmd, "domainIds", sourceDomainIds);
}
}
if (cmd.getZoneIds() == null || cmd.getZoneIds().isEmpty()) {
if (sourceZoneIds != null && !sourceZoneIds.isEmpty()) {
setField(cmd, "zoneIds", sourceZoneIds);
}
}
} catch (Exception e) {
logger.warn("Failed to apply some source offering parameters during clone: {}", e.getMessage());
}
}
/**
* Reconstructs the service capability list from the source network offering's stored capability flags.
* These capabilities were originally passed during creation and stored as boolean flags in the offering.
*
* Returns a Map in the format expected by CreateNetworkOfferingCmd.serviceCapabilitystList:
* Map where each value is a HashMap with keys: "service", "capabilitytype", "capabilityvalue"
*/
private Map<String, Map<String, String>> reconstructNetworkServiceCapabilityList(NetworkOfferingVO sourceOffering) {
Map<String, Map<String, String>> capabilityList = new HashMap<>();
int index = 0;
if (sourceOffering.isDedicatedLB()) {
Map<String, String> cap = new HashMap<>();
cap.put("service", Network.Service.Lb.getName());
cap.put("capabilitytype", Network.Capability.SupportedLBIsolation.getName());
cap.put("capabilityvalue", "dedicated");
capabilityList.put(String.valueOf(index++), cap);
}
if (sourceOffering.isElasticLb()) {
Map<String, String> cap = new HashMap<>();
cap.put("service", Network.Service.Lb.getName());
cap.put("capabilitytype", Network.Capability.ElasticLb.getName());
cap.put("capabilityvalue", "true");
capabilityList.put(String.valueOf(index++), cap);
}
if (sourceOffering.isInline()) {
Map<String, String> cap = new HashMap<>();
cap.put("service", Network.Service.Lb.getName());
cap.put("capabilitytype", Network.Capability.InlineMode.getName());
cap.put("capabilityvalue", "true");
capabilityList.put(String.valueOf(index++), cap);
}
if (sourceOffering.isPublicLb() || sourceOffering.isInternalLb()) {
List<String> schemes = new ArrayList<>();
if (sourceOffering.isPublicLb()) schemes.add("public");
if (sourceOffering.isInternalLb()) schemes.add("internal");
Map<String, String> cap = new HashMap<>();
cap.put("service", Network.Service.Lb.getName());
cap.put("capabilitytype", Network.Capability.LbSchemes.getName());
cap.put("capabilityvalue", String.join(",", schemes));
capabilityList.put(String.valueOf(index++), cap);
}
if (sourceOffering.isSupportsVmAutoScaling()) {
Map<String, String> cap = new HashMap<>();
cap.put("service", Network.Service.Lb.getName());
cap.put("capabilitytype", Network.Capability.VmAutoScaling.getName());
cap.put("capabilityvalue", "true");
capabilityList.put(String.valueOf(index++), cap);
}
if (sourceOffering.isSharedSourceNat()) {
Map<String, String> cap = new HashMap<>();
cap.put("service", Network.Service.SourceNat.getName());
cap.put("capabilitytype", Network.Capability.SupportedSourceNatTypes.getName());
cap.put("capabilityvalue", "perzone");
capabilityList.put(String.valueOf(index++), cap);
}
if (sourceOffering.isRedundantRouter()) {
Map<String, String> cap1 = new HashMap<>();
cap1.put("service", Network.Service.SourceNat.getName());
cap1.put("capabilitytype", Network.Capability.RedundantRouter.getName());
cap1.put("capabilityvalue", "true");
capabilityList.put(String.valueOf(index++), cap1);
Map<String, String> cap2 = new HashMap<>();
cap2.put("service", Network.Service.Gateway.getName());
cap2.put("capabilitytype", Network.Capability.RedundantRouter.getName());
cap2.put("capabilityvalue", "true");
capabilityList.put(String.valueOf(index++), cap2);
}
if (sourceOffering.isElasticIp()) {
Map<String, String> cap = new HashMap<>();
cap.put("service", Network.Service.StaticNat.getName());
cap.put("capabilitytype", Network.Capability.ElasticIp.getName());
cap.put("capabilityvalue", "true");
capabilityList.put(String.valueOf(index++), cap);
}
if (sourceOffering.isElasticIp() && sourceOffering.isAssociatePublicIP()) {
Map<String, String> cap = new HashMap<>();
cap.put("service", Network.Service.StaticNat.getName());
cap.put("capabilitytype", Network.Capability.AssociatePublicIP.getName());
cap.put("capabilityvalue", "true");
capabilityList.put(String.valueOf(index++), cap);
}
return capabilityList;
}
public static void applyIfNotProvided(Object cmd, Map<String, String> requestParams, String fieldName,
String apiConstant, Object currentValue, Object sourceValue) throws Exception {
if ((requestParams == null || !requestParams.containsKey(apiConstant)) && sourceValue != null) {
setField(cmd, fieldName, sourceValue);
}
}
public static void applyBooleanIfNotProvided(Object cmd, Map<String, String> requestParams,
String fieldName, String apiConstant, Boolean sourceValue) throws Exception {
if ((requestParams == null || !requestParams.containsKey(apiConstant)) && sourceValue != null) {
setField(cmd, fieldName, sourceValue);
}
}
public static void setField(Object obj, String fieldName, Object value) throws Exception {
Field field = findField(obj.getClass(), fieldName);
if (field == null) {
throw new NoSuchFieldException("Field '" + fieldName + "' not found in class hierarchy of " + obj.getClass().getName());
}
field.setAccessible(true);
field.set(obj, value);
}
public static Field findField(Class<?> clazz, String fieldName) {
Class<?> currentClass = clazz;
while (currentClass != null) {
try {
return currentClass.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
currentClass = currentClass.getSuperclass();
}
}
return null;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_NETWORK_OFFERING_EDIT, eventDescription = "updating network offering")
public NetworkOffering updateNetworkOffering(final UpdateNetworkOfferingCmd cmd) {

View File

@ -1543,6 +1543,14 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
return ipaddr;
}
protected IPAddressVO getExistingSourceNatInVPC(Long vpcId) {
List<IPAddressVO> ips = _ipAddressDao.listByAssociatedVpc(vpcId, true);
if (CollectionUtils.isEmpty(ips)) {
return null;
}
return ips.get(0);
}
protected IPAddressVO getExistingSourceNatInNetwork(long ownerId, Long networkId) {
List<? extends IpAddress> addrs;
Network guestNetwork = _networksDao.findById(networkId);
@ -1723,7 +1731,11 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
NetworkOffering offering = _networkOfferingDao.findById(network.getNetworkOfferingId());
boolean sharedSourceNat = offering.isSharedSourceNat();
boolean isSourceNat = false;
if (!sharedSourceNat) {
if (network.getVpcId() != null) {
// For VPCs: Check if the VPC Source NAT IP address is the same we are associating
IPAddressVO vpcSourceNatIpAddress = getExistingSourceNatInVPC(network.getVpcId());
isSourceNat = vpcSourceNatIpAddress != null && vpcSourceNatIpAddress.getId() == ipToAssoc.getId();
} else if (!sharedSourceNat) {
if (getExistingSourceNatInNetwork(owner.getId(), network.getId()) == null) {
if (network.getGuestType() == GuestType.Isolated && network.getVpcId() == null && !ipToAssoc.isPortable()) {
isSourceNat = true;
@ -2647,4 +2659,31 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
});
}
@Override
public Long getPreferredNetworkIdForPublicIpRuleAssignment(IpAddress ip, Long networkId) {
boolean vpcConserveMode = isPublicIpOnVpcConserveMode(ip);
return getPreferredNetworkIdForRule(ip, vpcConserveMode, networkId);
}
protected Long getPreferredNetworkIdForRule(IpAddress ip, boolean vpcConserveModeEnabled, Long networkId) {
if (vpcConserveModeEnabled) {
// Since VPC Conserve mode allows rules from multiple VPC tiers, always check the networkId parameter first
return networkId != null ? networkId : ip.getAssociatedWithNetworkId();
} else {
// In case of Guest Networks or VPC Tier Networks VPC Conserve mode disabled prefer the associated networkId
return ip.getAssociatedWithNetworkId() != null ? ip.getAssociatedWithNetworkId() : networkId;
}
}
protected boolean isPublicIpOnVpcConserveMode(IpAddress ip) {
if (ip.getVpcId() == null) {
return false;
}
Vpc vpc = _vpcMgr.getActiveVpc(ip.getVpcId());
if (vpc == null) {
return false;
}
VpcOffering vpcOffering = vpcOfferingDao.findById(vpc.getVpcOfferingId());
return vpcOffering != null && vpcOffering.isConserveMode();
}
}

View File

@ -6434,6 +6434,11 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C
return Networks.BroadcastDomainType.getValue(nic.getBroadcastUri());
}
@Override
public Long getPreferredNetworkIdForPublicIpRuleAssignment(IpAddress ip, Long networkId) {
return _ipAddrMgr.getPreferredNetworkIdForPublicIpRuleAssignment(ip, networkId);
}
@Override
public Network.IpAddresses getIpAddressesFromIps(String ipAddress, String ip6Address, String macAddress) {
if (ip6Address != null) {

View File

@ -2051,7 +2051,7 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage
}
lstVmId.add(new Long(vmId));
try {
return loadBalancingRulesService.assignToLoadBalancer(lbId, lstVmId, new HashMap<>(), true);
return loadBalancingRulesService.assignToLoadBalancer(lbId, lstVmId, new HashMap<>(), null, true);
} catch (CloudRuntimeException ex) {
logger.warn("Caught exception: ", ex);
return false;

View File

@ -30,6 +30,8 @@ import java.util.Set;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.network.vpc.Vpc;
import com.cloud.network.vpc.dao.VpcOfferingDao;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Component;
@ -159,6 +161,8 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService,
IpAddressManager _ipAddrMgr;
@Inject
RoutedIpv4Manager routedIpv4Manager;
@Inject
VpcOfferingDao vpcOfferingDao;
private boolean _elbEnabled = false;
static Boolean rulesContinueOnErrFlag = true;
@ -395,6 +399,10 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService,
assert (rules.size() >= 1);
}
NetworkVO newRuleNetwork = getNewRuleNetwork(newRule);
boolean newRuleIsOnVpcNetwork = newRuleNetwork.getVpcId() != null;
boolean vpcConserveModeEnabled = _vpcMgr.isNetworkOnVpcEnabledConserveMode(newRuleNetwork);
for (FirewallRuleVO rule : rules) {
if (rule.getId() == newRule.getId()) {
continue; // Skips my own rule.
@ -443,8 +451,15 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService,
}
// Checking if the rule applied is to the same network that is passed in the rule.
if (rule.getNetworkId() != newRule.getNetworkId() && rule.getState() != State.Revoke) {
throw new NetworkRuleConflictException("New rule is for a different network than what's specified in rule " + rule.getXid());
// (except for VPCs with conserve mode = true)
if ((!newRuleIsOnVpcNetwork || !vpcConserveModeEnabled)
&& rule.getNetworkId() != newRule.getNetworkId() && rule.getState() != State.Revoke) {
String errMsg = String.format("New rule is for a different network than what's specified in rule %s", rule.getXid());
if (newRuleIsOnVpcNetwork) {
Vpc vpc = _vpcMgr.getActiveVpc(newRuleNetwork.getVpcId());
errMsg += String.format(" - VPC id=%s is not using conserve mode", vpc.getUuid());
}
throw new NetworkRuleConflictException(errMsg);
}
//Check for the ICMP protocol. This has to be done separately from other protocols as we need to check the ICMP codes and ICMP type also.
@ -493,6 +508,14 @@ public class FirewallManagerImpl extends ManagerBase implements FirewallService,
}
}
protected NetworkVO getNewRuleNetwork(FirewallRule newRule) {
NetworkVO newRuleNetwork = _networkDao.findById(newRule.getNetworkId());
if (newRuleNetwork == null) {
throw new InvalidParameterValueException("Unable to create firewall rule as cannot find network by id=" + newRule.getNetworkId());
}
return newRuleNetwork;
}
protected boolean checkIfRulesHaveConflictingPortRanges(FirewallRule newRule, FirewallRule rule, boolean oneOfRulesIsFirewall, boolean bothRulesFirewall, boolean bothRulesPortForwarding, boolean duplicatedCidrs) {
String rulesAsString = String.format("[%s] and [%s]", rule, newRule);

View File

@ -53,6 +53,8 @@ import org.apache.cloudstack.lb.ApplicationLoadBalancerRuleVO;
import org.apache.cloudstack.lb.dao.ApplicationLoadBalancerRuleDao;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import com.cloud.agent.api.to.LoadBalancerTO;
@ -1018,7 +1020,7 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements
@Override
@DB
@ActionEvent(eventType = EventTypes.EVENT_ASSIGN_TO_LOAD_BALANCER_RULE, eventDescription = "assigning to load balancer", async = true)
public boolean assignToLoadBalancer(long loadBalancerId, List<Long> instanceIds, Map<Long, List<String>> vmIdIpMap, boolean isAutoScaleVM) {
public boolean assignToLoadBalancer(long loadBalancerId, List<Long> instanceIds, Map<Long, List<String>> vmIdIpMap, Map<Long, Long> vmIdNetworkMap, boolean isAutoScaleVM) {
CallContext ctx = CallContext.current();
Account caller = ctx.getCallingAccount();
@ -1091,28 +1093,12 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements
_rulesMgr.checkRuleAndUserVm(loadBalancer, vm, caller);
Account vmOwner = _accountDao.findById(vm.getAccountId());
Network network = _networkDao.findById(loadBalancer.getNetworkId());
_accountMgr.checkAccess(vmOwner, SecurityChecker.AccessType.UseEntry, false, network);
Network loadBalancerNetwork = _networkDao.findById(loadBalancer.getNetworkId());
_accountMgr.checkAccess(vmOwner, SecurityChecker.AccessType.UseEntry, false, loadBalancerNetwork);
// Let's check to make sure the vm has a nic in the same network as
// the load balancing rule.
List<? extends Nic> nics = _networkModel.getNics(vm.getId());
Nic nicInSameNetwork = null;
for (Nic nic : nics) {
if (nic.getNetworkId() == loadBalancer.getNetworkId()) {
nicInSameNetwork = nic;
break;
}
}
Nic vmNicInLb = getVmNicInLoadBalancer(vm, loadBalancer, loadBalancerNetwork, vmIdNetworkMap, vmOwner);
if (nicInSameNetwork == null) {
InvalidParameterValueException ex =
new InvalidParameterValueException("VM with id specified cannot be added because it doesn't belong in the same network.");
ex.addProxyObject(vm.getUuid(), "instanceId");
throw ex;
}
String priIp = nicInSameNetwork.getIPv4Address();
String priIp = vmNicInLb.getIPv4Address();
if (existingVmIdIps.containsKey(instanceId)) {
// now check for ip address
@ -1142,9 +1128,9 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements
if (ip.equals(priIp)) {
continue;
}
if(_nicSecondaryIpDao.findByIp4AddressAndNicId(ip,nicInSameNetwork.getId()) == null) {
if(_nicSecondaryIpDao.findByIp4AddressAndNicId(ip,vmNicInLb.getId()) == null) {
throw new InvalidParameterValueException("Instance IP "+ ip + " specified does not belong to " +
"NIC in Network " + nicInSameNetwork.getNetworkId());
"NIC in Network " + vmNicInLb.getNetworkId());
}
}
} else {
@ -1234,6 +1220,63 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements
return success;
}
protected Nic getVmNicInLoadBalancer(UserVm vm, LoadBalancerVO loadBalancer, Network loadBalancerNetwork, Map<Long, Long> vmIdNetworkMap, Account vmOwner) {
boolean isVpcConserveModeEnabled = _vpcMgr.isNetworkOnVpcEnabledConserveMode(loadBalancerNetwork);
boolean isNetworkPassedVpcConserveMode = isVpcConserveModeEnabled && MapUtils.isNotEmpty(vmIdNetworkMap) && vmIdNetworkMap.containsKey(vm.getId());
Nic vmNicInLb = isNetworkPassedVpcConserveMode ?
getNicForVmInVpcConserveModeTierNetwork(vm, vmIdNetworkMap, vmOwner, loadBalancerNetwork) :
getNicForVmLbNetwork(vm, loadBalancer);
if (vmNicInLb == null) {
String msg = !isVpcConserveModeEnabled ?
"VM with id specified cannot be added because it doesn't belong in the same network." :
"VM with id specified cannot be added to the load balancing rule for VPC Conserve Mode.";
InvalidParameterValueException ex = new InvalidParameterValueException(msg);
ex.addProxyObject(vm.getUuid(), "instanceId");
throw ex;
}
return vmNicInLb;
}
/**
* For Isolated Networks or Network tiers of VPCs not using Conserve mode, use the same network as the load balancer
* @return the nic of the VM in the load balancer network
*/
protected Nic getNicForVmLbNetwork(UserVm vm, LoadBalancerVO loadBalancer) {
List<? extends Nic> nics = _networkModel.getNics(vm.getId());
for (Nic nic : nics) {
if (nic.getNetworkId() == loadBalancer.getNetworkId()) {
return nic;
}
}
return null;
}
/**
* On VPC Conserve Mode, VMs from multiple VPC networks tiers can be assigned to the same load balancer.
* @return the nic of the VM in the specified tier network in `vmIdNetworkMap`
*/
protected Nic getNicForVmInVpcConserveModeTierNetwork(UserVm vm, Map<Long, Long> vmIdNetworkMap, Account vmOwner, Network loadBalancerNetwork) {
Long vmNetworkId = vmIdNetworkMap.get(vm.getId());
Network vmNetwork = _networkDao.findById(vmNetworkId);
_accountMgr.checkAccess(vmOwner, SecurityChecker.AccessType.UseEntry, false, vmNetwork);
checkNetworkBelongsToLoadBalancerVpc(vmNetwork, loadBalancerNetwork);
return _networkModel.getNicInNetwork(vm.getId(), vmNetworkId);
}
protected void checkNetworkBelongsToLoadBalancerVpc(Network vmNetwork, Network loadBalancerNetwork) {
if (ObjectUtils.anyNull(vmNetwork, loadBalancerNetwork)) {
throw new InvalidParameterValueException("Cannot add VM to load balancer because the VM network or load balancer network is null");
}
if (ObjectUtils.anyNull(vmNetwork.getVpcId(), loadBalancerNetwork.getVpcId())) {
throw new InvalidParameterValueException("Cannot add VM to load balancer because the VM network or load balancer network are not part of a VPC");
}
if (!vmNetwork.getVpcId().equals(loadBalancerNetwork.getVpcId())) {
throw new InvalidParameterValueException("Cannot add VM to load balancer because the VM network and load balancer network are not part of the same VPC");
}
}
@Override
public boolean assignSSLCertToLoadBalancerRule(Long lbId, String certName, String publicCert, String privateKey) {
logger.error("Calling the manager for LB");
@ -1740,6 +1783,8 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements
throw new NetworkRuleConflictException("Can't do load balance on IP address: " + ipVO.getAddress());
}
verifyLoadBalancerRuleNetwork(name, network, ipVO);
String cidrString = generateCidrString(cidrList);
boolean performedIpAssoc = false;
@ -1763,7 +1808,7 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements
}
result = createPublicLoadBalancer(xId, name, description, srcPortStart, defPortStart, ipVO.getId(), protocol, algorithm, openFirewall, CallContext.current(),
lbProtocol, forDisplay, cidrString);
lbProtocol, forDisplay, cidrString, networkId);
} catch (Exception ex) {
logger.warn("Failed to create load balancer due to ", ex);
if (ex instanceof NetworkRuleConflictException) {
@ -1792,7 +1837,18 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements
return result;
}
/**
protected void verifyLoadBalancerRuleNetwork(String lbName, Network network, IPAddressVO ipVO) {
boolean isVpcConserveModeEnabled = _vpcMgr.isNetworkOnVpcEnabledConserveMode(network);
if (!isVpcConserveModeEnabled && ipVO.getAssociatedWithNetworkId() != null && network.getId() != ipVO.getAssociatedWithNetworkId()) {
String msg = String.format("Cannot create Load Balancer rule %s as the IP address %s is not associated " +
"with the network %s (ID=%s)", lbName, ipVO.getAddress(), network.getName(), network.getUuid());
logger.error(msg);
throw new InvalidParameterValueException(msg);
}
}
/**
* Transforms the cidrList from a List of Strings to a String which contains all the CIDRs from cidrList separated by whitespaces. This is used to facilitate both the persistence
* in the DB and also later when building the configuration String in the getRulesForPool method of the HAProxyConfigurator class.
*/
@ -1826,7 +1882,7 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements
@Override
public LoadBalancer createPublicLoadBalancer(final String xId, final String name, final String description, final int srcPort, final int destPort, final long sourceIpId,
final String protocol, final String algorithm, final boolean openFirewall, final CallContext caller, final String lbProtocol,
final Boolean forDisplay, String cidrList) throws NetworkRuleConflictException {
final Boolean forDisplay, String cidrList, Long networkIdParam) throws NetworkRuleConflictException {
if (!NetUtils.isValidPort(destPort)) {
throw new InvalidParameterValueException("privatePort is an invalid value: " + destPort);
}
@ -1855,7 +1911,7 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements
_accountMgr.checkAccess(caller.getCallingAccount(), null, true, ipAddr);
final Long networkId = ipAddr.getAssociatedWithNetworkId();
final Long networkId = _ipAddrMgr.getPreferredNetworkIdForPublicIpRuleAssignment(ipAddr, networkIdParam);
if (networkId == null) {
InvalidParameterValueException ex =
new InvalidParameterValueException("Unable to create load balancer rule ; specified sourceip id is not associated with any network");

View File

@ -71,6 +71,7 @@ import org.apache.cloudstack.alert.AlertService;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.command.admin.vpc.CloneVPCOfferingCmd;
import org.apache.cloudstack.api.command.admin.vpc.CreatePrivateGatewayByAdminCmd;
import org.apache.cloudstack.api.command.admin.vpc.CreateVPCCmdByAdmin;
import org.apache.cloudstack.api.command.admin.vpc.CreateVPCOfferingCmd;
@ -388,7 +389,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
}
createVpcOffering(VpcOffering.defaultVPCOfferingName, VpcOffering.defaultVPCOfferingName, svcProviderMap,
true, State.Enabled, null, false,
false, false, null, null, false);
false, false, null, null, false, false);
}
// configure default vpc offering with Netscaler as LB Provider
@ -408,7 +409,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
}
}
createVpcOffering(VpcOffering.defaultVPCNSOfferingName, VpcOffering.defaultVPCNSOfferingName,
svcProviderMap, false, State.Enabled, null, false, false, false, null, null, false);
svcProviderMap, false, State.Enabled, null, false, false, false, null, null, false, false);
}
@ -429,7 +430,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
}
}
createVpcOffering(VpcOffering.redundantVPCOfferingName, VpcOffering.redundantVPCOfferingName, svcProviderMap, true, State.Enabled,
null, false, false, true, null, null, false);
null, false, false, true, null, null, false, false);
}
// configure default vpc offering with NSX as network service provider in NAT mode
@ -446,7 +447,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
}
}
createVpcOffering(VpcOffering.DEFAULT_VPC_NAT_NSX_OFFERING_NAME, VpcOffering.DEFAULT_VPC_NAT_NSX_OFFERING_NAME, svcProviderMap, false,
State.Enabled, null, false, false, false, NetworkOffering.NetworkMode.NATTED, null, false);
State.Enabled, null, false, false, false, NetworkOffering.NetworkMode.NATTED, null, false, false);
}
@ -464,7 +465,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
}
}
createVpcOffering(VpcOffering.DEFAULT_VPC_ROUTE_NSX_OFFERING_NAME, VpcOffering.DEFAULT_VPC_ROUTE_NSX_OFFERING_NAME, svcProviderMap, false,
State.Enabled, null, false, false, false, NetworkOffering.NetworkMode.ROUTED, null, false);
State.Enabled, null, false, false, false, NetworkOffering.NetworkMode.ROUTED, null, false, false);
}
@ -482,7 +483,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
}
}
createVpcOffering(VpcOffering.DEFAULT_VPC_ROUTE_NETRIS_OFFERING_NAME, VpcOffering.DEFAULT_VPC_ROUTE_NETRIS_OFFERING_NAME, svcProviderMap, false,
State.Enabled, null, false, false, false, NetworkOffering.NetworkMode.ROUTED, null, false);
State.Enabled, null, false, false, false, NetworkOffering.NetworkMode.ROUTED, null, false, false);
}
@ -500,7 +501,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
}
}
createVpcOffering(VpcOffering.DEFAULT_VPC_NAT_NETRIS_OFFERING_NAME, VpcOffering.DEFAULT_VPC_NAT_NETRIS_OFFERING_NAME, svcProviderMap, false,
State.Enabled, null, false, false, false, NetworkOffering.NetworkMode.NATTED, null, false);
State.Enabled, null, false, false, false, NetworkOffering.NetworkMode.NATTED, null, false, false);
}
}
@ -586,6 +587,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
}
boolean specifyAsNumber = cmd.getSpecifyAsNumber();
String routingModeString = cmd.getRoutingMode();
boolean conserveMode = cmd.isConserveMode();
// check if valid domain
if (CollectionUtils.isNotEmpty(cmd.getDomainIds())) {
@ -624,7 +626,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
return createVpcOffering(vpcOfferingName, displayText, supportedServices,
serviceProviderList, serviceCapabilityList, internetProtocol, serviceOfferingId, provider, networkMode,
domainIds, zoneIds, (enable ? State.Enabled : State.Disabled), routingMode, specifyAsNumber);
domainIds, zoneIds, (enable ? State.Enabled : State.Disabled), routingMode, specifyAsNumber, conserveMode);
}
@Override
@ -632,7 +634,13 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
public VpcOffering createVpcOffering(final String name, final String displayText, final List<String> supportedServices, final Map<String, List<String>> serviceProviders,
final Map serviceCapabilityList, final NetUtils.InternetProtocol internetProtocol, final Long serviceOfferingId,
final String externalProvider, final NetworkOffering.NetworkMode networkMode, List<Long> domainIds, List<Long> zoneIds, State state,
NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber) {
NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber, boolean conserveMode) {
boolean isExternalProvider = externalProvider != null &&
Arrays.asList("NSX", "Netris").stream().anyMatch(s -> s.equalsIgnoreCase(externalProvider));
if (!isExternalProvider && CollectionUtils.isEmpty(supportedServices)) {
throw new InvalidParameterValueException("Supported services needs to be provided");
}
if (!Ipv6Service.Ipv6OfferingCreationEnabled.value() && !(internetProtocol == null || NetUtils.InternetProtocol.IPv4.equals(internetProtocol))) {
throw new InvalidParameterValueException(String.format("Configuration %s needs to be enabled for creating IPv6 supported VPC offering", Ipv6Service.Ipv6OfferingCreationEnabled.key()));
@ -727,7 +735,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
final boolean offersRegionLevelVPC = isVpcOfferingForRegionLevelVpc(serviceCapabilityList);
final boolean redundantRouter = isVpcOfferingRedundantRouter(serviceCapabilityList, redundantRouterService);
final VpcOfferingVO offering = createVpcOffering(name, displayText, svcProviderMap, false, state, serviceOfferingId, supportsDistributedRouter, offersRegionLevelVPC,
redundantRouter, networkMode, routingMode, specifyAsNumber);
redundantRouter, networkMode, routingMode, specifyAsNumber, conserveMode);
if (offering != null) {
List<VpcOfferingDetailsVO> detailsVO = new ArrayList<>();
@ -755,7 +763,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
@DB
protected VpcOfferingVO createVpcOffering(final String name, final String displayText, final Map<Service, Set<Provider>> svcProviderMap,
final boolean isDefault, final State state, final Long serviceOfferingId, final boolean supportsDistributedRouter, final boolean offersRegionLevelVPC,
final boolean redundantRouter, NetworkOffering.NetworkMode networkMode, NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber) {
final boolean redundantRouter, NetworkOffering.NetworkMode networkMode, NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber, boolean conserveMode) {
return Transaction.execute(new TransactionCallback<VpcOfferingVO>() {
@Override
@ -771,6 +779,7 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
if (Objects.nonNull(routingMode)) {
offering.setRoutingMode(routingMode);
}
offering.setConserveMode(conserveMode);
logger.debug("Adding vpc offering " + offering);
offering = _vpcOffDao.persist(offering);
@ -811,6 +820,349 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
}
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VPC_OFFERING_CLONE, eventDescription = "cloning VPC offering")
public VpcOffering cloneVPCOffering(CloneVPCOfferingCmd cmd) {
Long sourceVpcOfferingId = cmd.getSourceOfferingId();
final VpcOffering sourceVpcOffering = _vpcOffDao.findById(sourceVpcOfferingId);
if (sourceVpcOffering == null) {
throw new InvalidParameterValueException("Unable to find source VPC offering by id " + sourceVpcOfferingId);
}
String name = cmd.getVpcOfferingName();
if (name == null || name.isEmpty()) {
throw new InvalidParameterValueException("Name is required when cloning a VPC offering");
}
VpcOfferingVO vpcOfferingVO = _vpcOffDao.findByUniqueName(name);
if (vpcOfferingVO != null) {
throw new InvalidParameterValueException(String.format("A VPC offering with name %s already exists", name));
}
logger.info("Cloning VPC offering {} (id: {}) to new offering with name: {}",
sourceVpcOffering.getName(), sourceVpcOfferingId, name);
Map<Network.Service, Set<Network.Provider>> sourceServiceProviderMap = getVpcOffSvcProvidersMap(sourceVpcOfferingId);
validateProvider(sourceVpcOffering, sourceServiceProviderMap, cmd.getProvider(), cmd.getNetworkMode());
applySourceOfferingValuesToCloneCmd(cmd, sourceServiceProviderMap, sourceVpcOffering);
return createVpcOffering(cmd);
}
private void validateProvider(VpcOffering sourceVpcOffering,
Map<Network.Service, Set<Network.Provider>> sourceServiceProviderMap,
String provider, String networkMode) {
provider = ConfigurationManagerImpl.getExternalNetworkProvider(provider, sourceServiceProviderMap);
if (provider != null && (provider.equals("NSX") || provider.equals("Netris"))) {
if (networkMode != null && sourceVpcOffering.getNetworkMode() != null) {
if (!networkMode.equalsIgnoreCase(sourceVpcOffering.getNetworkMode().toString())) {
throw new InvalidParameterValueException(
String.format("Cannot change network mode when cloning %s provider VPC offerings. " +
"Source offering has network mode '%s', but '%s' was specified. ",
provider, sourceVpcOffering.getNetworkMode(), networkMode));
}
}
}
}
private void applySourceOfferingValuesToCloneCmd(CloneVPCOfferingCmd cmd,
Map<Network.Service, Set<Network.Provider>> sourceServiceProviderMap,
VpcOffering sourceVpcOffering) {
Long sourceOfferingId = sourceVpcOffering.getId();
List<String> finalServices = resolveFinalServicesList(cmd, sourceServiceProviderMap);
Map finalServiceProviderMap = resolveServiceProviderMap(cmd, sourceServiceProviderMap, finalServices);
List<Long> sourceDomainIds = vpcOfferingDetailsDao.findDomainIds(sourceOfferingId);
List<Long> sourceZoneIds = vpcOfferingDetailsDao.findZoneIds(sourceOfferingId);
Map<String, String> sourceServiceCapabilityList = reconstructServiceCapabilityList(sourceVpcOffering);
applyResolvedValuesToCommand(cmd, (VpcOfferingVO)sourceVpcOffering, finalServices, finalServiceProviderMap,
sourceDomainIds, sourceZoneIds, sourceServiceCapabilityList);
}
/**
* Reconstructs the service capability list from the source VPC offering's stored capability flags.
* These capabilities were originally passed during creation and stored as boolean flags in the offering.
*
* Returns a Map in the format expected by CreateVPCOfferingCmd.serviceCapabilityList:
* Map<String, String> with keys like "0.service", "0.capabilitytype", "0.capabilityvalue"
*/
private Map<String, String> reconstructServiceCapabilityList(VpcOffering sourceOffering) {
Map<String, String> capabilityList = new HashMap<>();
int index = 0;
if (sourceOffering.isOffersRegionLevelVPC()) {
capabilityList.put(index + ".service", Network.Service.Connectivity.getName());
capabilityList.put(index + ".capabilitytype", Network.Capability.RegionLevelVpc.getName());
capabilityList.put(index + ".capabilityvalue", "true");
index++;
}
if (sourceOffering.isSupportsDistributedRouter()) {
capabilityList.put(index + ".service", Network.Service.Connectivity.getName());
capabilityList.put(index + ".capabilitytype", Network.Capability.DistributedRouter.getName());
capabilityList.put(index + ".capabilityvalue", "true");
index++;
}
if (sourceOffering.isRedundantRouter()) {
Map<Network.Service, Set<Network.Provider>> serviceProviderMap = getVpcOffSvcProvidersMap(sourceOffering.getId());
// Check which service has VPCVirtualRouter provider - SourceNat takes precedence
Network.Service redundantRouterService = null;
for (Network.Service service : Arrays.asList(Network.Service.SourceNat, Network.Service.Gateway, Network.Service.StaticNat)) {
Set<Network.Provider> providers = serviceProviderMap.get(service);
if (providers != null && providers.contains(Network.Provider.VPCVirtualRouter)) {
redundantRouterService = service;
break;
}
}
if (redundantRouterService != null) {
capabilityList.put(index + ".service", redundantRouterService.getName());
capabilityList.put(index + ".capabilitytype", Network.Capability.RedundantRouter.getName());
capabilityList.put(index + ".capabilityvalue", "true");
}
}
return capabilityList;
}
private List<String> resolveFinalServicesList(CloneVPCOfferingCmd cmd,
Map<Network.Service, Set<Network.Provider>> sourceServiceProviderMap) {
List<String> cmdServices = cmd.getSupportedServices();
List<String> addServices = cmd.getAddServices();
List<String> dropServices = cmd.getDropServices();
if (cmdServices != null && !cmdServices.isEmpty()) {
return cmdServices;
}
List<String> finalServices = new ArrayList<>();
for (Network.Service service : sourceServiceProviderMap.keySet()) {
finalServices.add(service.getName());
}
if (dropServices != null && !dropServices.isEmpty()) {
List<String> normalizedDropServices = new ArrayList<>();
for (String serviceName : dropServices) {
Network.Service service = Network.Service.getService(serviceName);
if (service == null) {
throw new InvalidParameterValueException("Service " + serviceName + " is not supported in VPC");
}
normalizedDropServices.add(service.getName());
}
finalServices.removeAll(normalizedDropServices);
logger.debug("Dropped services from clone: {}", dropServices);
}
if (addServices != null && !addServices.isEmpty()) {
List<String> normalizedAddServices = new ArrayList<>();
for (String serviceName : addServices) {
Network.Service service = Network.Service.getService(serviceName);
if (service == null) {
throw new InvalidParameterValueException("Service " + serviceName + " is not supported in VPC");
}
String canonicalName = service.getName();
if (!finalServices.contains(canonicalName)) {
finalServices.add(canonicalName);
normalizedAddServices.add(canonicalName);
}
}
logger.debug("Added services to clone: {}", addServices);
}
return finalServices;
}
private Map<String, List<String>> resolveServiceProviderMap(CloneVPCOfferingCmd cmd,
Map<Network.Service, Set<Network.Provider>> sourceServiceProviderMap, List<String> finalServices) {
if (cmd.getServiceProviders() != null && !cmd.getServiceProviders().isEmpty()) {
return cmd.getServiceProviders();
}
Map<String, List<String>> finalMap = new HashMap<>();
for (Map.Entry<Network.Service, Set<Network.Provider>> entry : sourceServiceProviderMap.entrySet()) {
String serviceName = entry.getKey().getName();
if (finalServices.contains(serviceName)) {
List<String> providers = new ArrayList<>();
for (Network.Provider provider : entry.getValue()) {
providers.add(provider.getName());
}
finalMap.put(serviceName, providers);
}
}
return finalMap;
}
/**
* Converts service provider map from Map<String, List<String>> to the indexed format
* expected by CreateVPCOfferingCmd.serviceProviderList parameter.
*
* Input: {"Dhcp": ["VpcVirtualRouter"], "Dns": ["VpcVirtualRouter"]}
* Output: {"0": {"service": "Dhcp", "provider": "VpcVirtualRouter"},
* "1": {"service": "Dns", "provider": "VpcVirtualRouter"}}
*/
private Map<String, Map<String, String>> convertToServiceProviderListFormat(Map<String, List<String>> serviceProviderMap) {
Map<String, Map<String, String>> result = new HashMap<>();
int index = 0;
for (Map.Entry<String, List<String>> entry : serviceProviderMap.entrySet()) {
String serviceName = entry.getKey();
List<String> providers = entry.getValue();
for (String providerName : providers) {
Map<String, String> serviceProviderEntry = new HashMap<>();
serviceProviderEntry.put("service", serviceName);
serviceProviderEntry.put("provider", providerName);
result.put(String.valueOf(index++), serviceProviderEntry);
}
}
return result;
}
private void applyResolvedValuesToCommand(CloneVPCOfferingCmd cmd, VpcOfferingVO sourceOffering,
List<String> finalServices, Map finalServiceProviderMap,
List<Long> sourceDomainIds, List<Long> sourceZoneIds,
Map<String, String> sourceServiceCapabilityList) {
try {
if (cmd.getSupportedServices() == null || cmd.getSupportedServices().isEmpty()) {
logger.debug("Setting supportedServices to {} services from source offering", finalServices.size());
ConfigurationManagerImpl.setField(cmd, "supportedServices", finalServices);
}
if (cmd.getServiceProviders() == null || cmd.getServiceProviders().isEmpty()) {
Map<String, Map<String, String>> convertedProviderMap = convertToServiceProviderListFormat(finalServiceProviderMap);
logger.debug("Setting serviceProviderList with {} provider mappings", convertedProviderMap.size());
ConfigurationManagerImpl.setField(cmd, "serviceProviderList", convertedProviderMap);
}
if ((cmd.getServiceCapabilityList() == null || cmd.getServiceCapabilityList().isEmpty())
&& sourceServiceCapabilityList != null && !sourceServiceCapabilityList.isEmpty()) {
Map<String, String> filteredCapabilities = filterServiceCapabilities(sourceServiceCapabilityList, finalServices);
if (!filteredCapabilities.isEmpty()) {
ConfigurationManagerImpl.setField(cmd, "serviceCapabilityList", filteredCapabilities);
}
}
if (cmd.getDisplayText() == null && sourceOffering.getDisplayText() != null) {
ConfigurationManagerImpl.setField(cmd, "displayText", sourceOffering.getDisplayText());
}
if (cmd.getServiceOfferingId() == null && sourceOffering.getServiceOfferingId() != null) {
ConfigurationManagerImpl.setField(cmd, "serviceOfferingId", sourceOffering.getServiceOfferingId());
}
Boolean enableFieldValue = getRawFieldValue(cmd, "enable", Boolean.class);
if (enableFieldValue == null) {
Boolean enableState = sourceOffering.getState() == VpcOffering.State.Enabled;
ConfigurationManagerImpl.setField(cmd, "enable", enableState);
}
Boolean specifyAsNumberFieldValue = getRawFieldValue(cmd, "specifyAsNumber", Boolean.class);
if (specifyAsNumberFieldValue == null) {
ConfigurationManagerImpl.setField(cmd, "specifyAsNumber", sourceOffering.isSpecifyAsNumber());
}
if (cmd.getInternetProtocol() == null) {
String internetProtocol = vpcOfferingDetailsDao.getDetail(sourceOffering.getId(), ApiConstants.INTERNET_PROTOCOL);
if (internetProtocol != null) {
ConfigurationManagerImpl.setField(cmd, "internetProtocol", internetProtocol);
}
}
if (cmd.getNetworkMode() == null && sourceOffering.getNetworkMode() != null) {
ConfigurationManagerImpl.setField(cmd, "networkMode", sourceOffering.getNetworkMode().toString());
}
if (cmd.getRoutingMode() == null && sourceOffering.getRoutingMode() != null) {
ConfigurationManagerImpl.setField(cmd, "routingMode", sourceOffering.getRoutingMode().toString());
}
if (cmd.getDomainIds() == null || cmd.getDomainIds().isEmpty()) {
if (sourceDomainIds != null && !sourceDomainIds.isEmpty()) {
ConfigurationManagerImpl.setField(cmd, "domainIds", sourceDomainIds);
}
}
if (cmd.getZoneIds() == null || cmd.getZoneIds().isEmpty()) {
if (sourceZoneIds != null && !sourceZoneIds.isEmpty()) {
ConfigurationManagerImpl.setField(cmd, "zoneIds", sourceZoneIds);
}
}
} catch (Exception e) {
logger.error("Failed to apply source offering parameters during clone: {}", e.getMessage(), e);
throw new CloudRuntimeException("Failed to apply source offering parameters during VPC offering clone", e);
}
}
private <T> T getRawFieldValue(Object obj, String fieldName, Class<T> expectedType) {
try {
java.lang.reflect.Field field = ConfigurationManagerImpl.findField(obj.getClass(), fieldName);
if (field != null) {
field.setAccessible(true);
Object value = field.get(obj);
if (value == null || expectedType.isInstance(value)) {
return expectedType.cast(value);
}
}
} catch (Exception e) {
logger.debug("Could not get raw field value for {}: {}", fieldName, e.getMessage());
}
return null;
}
/**
* Filters service capabilities to only include those for services present in the final services list.
* This ensures that when services are dropped during cloning, their associated capabilities are also removed.
*
* @param sourceServiceCapabilityList The original capability list from the source VPC offering
* in format: Map with keys like "0.service", "0.capabilitytype", "0.capabilityvalue"
* @param finalServices The list of service names that should be retained in the cloned offering
* @return Filtered map containing only capabilities for services in finalServices
*/
private Map<String, String> filterServiceCapabilities(Map<String, String> sourceServiceCapabilityList,
List<String> finalServices) {
Map<String, String> filteredCapabilities = new HashMap<>();
for (Map.Entry<String, String> entry : sourceServiceCapabilityList.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
// Check if this is a service key (e.g., "0.service", "1.service")
if (key.endsWith(".service")) {
String serviceName = value;
if (finalServices.contains(serviceName)) {
// Include this service and its associated capability entries
String prefix = key.substring(0, key.lastIndexOf('.'));
filteredCapabilities.put(key, value);
// Also include the capability type and value for this service
String capabilityTypeKey = prefix + ".capabilitytype";
String capabilityValueKey = prefix + ".capabilityvalue";
if (sourceServiceCapabilityList.containsKey(capabilityTypeKey)) {
filteredCapabilities.put(capabilityTypeKey, sourceServiceCapabilityList.get(capabilityTypeKey));
}
if (sourceServiceCapabilityList.containsKey(capabilityValueKey)) {
filteredCapabilities.put(capabilityValueKey, sourceServiceCapabilityList.get(capabilityValueKey));
}
}
}
}
return filteredCapabilities;
}
private void validateConnectivtyServiceCapabilities(final Set<Provider> providers, final Map serviceCapabilitystList) {
if (serviceCapabilitystList != null && !serviceCapabilitystList.isEmpty()) {
final Collection serviceCapabilityCollection = serviceCapabilitystList.values();
@ -2954,6 +3306,20 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
return true;
}
protected boolean isNetworkOnVpc(Network network) {
return network.getVpcId() != null;
}
@Override
public boolean isNetworkOnVpcEnabledConserveMode(Network newRuleNetwork) {
if (isNetworkOnVpc(newRuleNetwork)) {
Vpc vpc = getActiveVpc(newRuleNetwork.getVpcId());
VpcOfferingVO vpcOffering = vpc != null ? _vpcOffDao.findById(vpc.getVpcOfferingId()) : null;
return vpcOffering != null && vpcOffering.isConserveMode();
}
return false;
}
protected boolean applyStaticRoutes(final List<StaticRouteVO> routes, final Account caller, final boolean updateRoutesInDB) throws ResourceUnavailableException {
final boolean success = true;
final List<StaticRouteProfile> staticRouteProfiles = getVpcStaticRoutes(routes);

View File

@ -132,6 +132,7 @@ import org.apache.cloudstack.api.command.admin.management.ListMgmtsCmd;
import org.apache.cloudstack.api.command.admin.management.RemoveManagementServerCmd;
import org.apache.cloudstack.api.command.admin.network.AddNetworkDeviceCmd;
import org.apache.cloudstack.api.command.admin.network.AddNetworkServiceProviderCmd;
import org.apache.cloudstack.api.command.admin.network.CloneNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.CreateManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.network.CreateNetworkCmdByAdmin;
import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd;
@ -162,6 +163,8 @@ import org.apache.cloudstack.api.command.admin.network.UpdateNetworkServiceProvi
import org.apache.cloudstack.api.command.admin.network.UpdatePhysicalNetworkCmd;
import org.apache.cloudstack.api.command.admin.network.UpdatePodManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.network.UpdateStorageNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.offering.CloneDiskOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.CloneServiceOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.CreateDiskOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.DeleteDiskOfferingCmd;
@ -328,6 +331,7 @@ import org.apache.cloudstack.api.command.admin.volume.RecoverVolumeCmdByAdmin;
import org.apache.cloudstack.api.command.admin.volume.ResizeVolumeCmdByAdmin;
import org.apache.cloudstack.api.command.admin.volume.UpdateVolumeCmdByAdmin;
import org.apache.cloudstack.api.command.admin.volume.UploadVolumeCmdByAdmin;
import org.apache.cloudstack.api.command.admin.vpc.CloneVPCOfferingCmd;
import org.apache.cloudstack.api.command.admin.vpc.CreatePrivateGatewayByAdminCmd;
import org.apache.cloudstack.api.command.admin.vpc.CreateVPCCmdByAdmin;
import org.apache.cloudstack.api.command.admin.vpc.CreateVPCOfferingCmd;
@ -3860,6 +3864,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(AddNetworkDeviceCmd.class);
cmdList.add(AddNetworkServiceProviderCmd.class);
cmdList.add(CreateNetworkOfferingCmd.class);
cmdList.add(CloneNetworkOfferingCmd.class);
cmdList.add(CreatePhysicalNetworkCmd.class);
cmdList.add(CreateStorageNetworkIpRangeCmd.class);
cmdList.add(DeleteNetworkDeviceCmd.class);
@ -3880,7 +3885,9 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(ListDedicatedGuestVlanRangesCmd.class);
cmdList.add(ReleaseDedicatedGuestVlanRangeCmd.class);
cmdList.add(CreateDiskOfferingCmd.class);
cmdList.add(CloneDiskOfferingCmd.class);
cmdList.add(CreateServiceOfferingCmd.class);
cmdList.add(CloneServiceOfferingCmd.class);
cmdList.add(DeleteDiskOfferingCmd.class);
cmdList.add(DeleteServiceOfferingCmd.class);
cmdList.add(IsAccountAllowedToCreateOfferingsWithTagsCmd.class);
@ -3965,6 +3972,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(RecoverVMCmd.class);
cmdList.add(CreatePrivateGatewayCmd.class);
cmdList.add(CreateVPCOfferingCmd.class);
cmdList.add(CloneVPCOfferingCmd.class);
cmdList.add(DeletePrivateGatewayCmd.class);
cmdList.add(DeleteVPCOfferingCmd.class);
cmdList.add(UpdateVPCOfferingCmd.class);

View File

@ -42,6 +42,7 @@ import com.cloud.utils.DomainHelper;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.api.command.admin.backup.CloneBackupOfferingCmd;
import org.apache.cloudstack.api.command.admin.backup.DeleteBackupOfferingCmd;
import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd;
import org.apache.cloudstack.api.command.admin.backup.ListBackupProviderOfferingsCmd;
@ -325,7 +326,6 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
return savedOffering;
}
@Override
public List<Long> getBackupOfferingDomains(Long offeringId) {
final BackupOffering backupOffering = backupOfferingDao.findById(offeringId);
if (backupOffering == null) {
@ -334,6 +334,78 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
return backupOfferingDetailsDao.findDomainIds(offeringId);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_CLONE, eventDescription = "cloning backup offering")
public BackupOffering cloneBackupOffering(final CloneBackupOfferingCmd cmd) {
final BackupOfferingVO sourceOffering = backupOfferingDao.findById(cmd.getSourceOfferingId());
if (sourceOffering == null) {
throw new InvalidParameterValueException("Unable to find backup offering with ID: " + cmd.getSourceOfferingId());
}
validateBackupForZone(sourceOffering.getZoneId());
if (backupOfferingDao.findByName(cmd.getName(), sourceOffering.getZoneId()) != null) {
throw new CloudRuntimeException("A backup offering with the name '" + cmd.getName() + "' already exists in this zone");
}
final String description = cmd.getDescription() != null ? cmd.getDescription() : sourceOffering.getDescription();
final String externalId = cmd.getExternalId() != null ? cmd.getExternalId() : sourceOffering.getExternalId();
final boolean userDrivenBackups = cmd.getUserDrivenBackups() != null ? cmd.getUserDrivenBackups() : sourceOffering.isUserDrivenBackupAllowed();
final Long zoneId = cmd.getZoneId() != null ? cmd.getZoneId() : sourceOffering.getZoneId();
if (!Objects.equals(sourceOffering.getExternalId(), externalId) || !Objects.equals(sourceOffering.getZoneId(), zoneId)) {
final BackupProvider provider = getBackupProvider(zoneId);
if (!provider.isValidProviderOffering(zoneId, externalId)) {
throw new CloudRuntimeException("Backup offering '" + externalId + "' does not exist on provider " + provider.getName() + " on zone " + zoneId);
}
}
final BackupOffering existingOffering = backupOfferingDao.findByExternalId(externalId, zoneId);
if (existingOffering != null) {
throw new CloudRuntimeException("A backup offering with external ID '" + externalId + "' already exists in this zone");
}
final BackupOfferingVO clonedOffering = new BackupOfferingVO(
zoneId,
externalId,
sourceOffering.getProvider(),
cmd.getName(),
description,
userDrivenBackups
);
final BackupOfferingVO savedOffering = backupOfferingDao.persist(clonedOffering);
if (savedOffering == null) {
throw new CloudRuntimeException("Unable to clone backup offering from ID: " + cmd.getSourceOfferingId());
}
List<Long> filteredDomainIds = cmd.getDomainIds() == null ? new ArrayList<>() : new ArrayList<>(cmd.getDomainIds());
Collections.sort(filteredDomainIds);
updateBackupOfferingDomainDetail(savedOffering, filteredDomainIds);
logger.debug("Successfully cloned backup offering '" + sourceOffering.getName() + "' (ID: " + cmd.getSourceOfferingId() + ") to '" + cmd.getName() + "' (ID: " + savedOffering.getId() + ")");
return savedOffering;
}
private void updateBackupOfferingDomainDetail(BackupOfferingVO savedOffering, List<Long> filteredDomainIds) {
if (filteredDomainIds.size() > 1) {
filteredDomainIds = domainHelper.filterChildSubDomains(filteredDomainIds);
}
if (CollectionUtils.isNotEmpty(filteredDomainIds)) {
List<BackupOfferingDetailsVO> detailsVOList = new ArrayList<>();
for (Long domainId : filteredDomainIds) {
if (domainDao.findById(domainId) == null) {
throw new InvalidParameterValueException("Please specify a valid domain id");
}
detailsVOList.add(new BackupOfferingDetailsVO(savedOffering.getId(), ApiConstants.DOMAIN_ID, String.valueOf(domainId), false));
}
if (!detailsVOList.isEmpty()) {
backupOfferingDetailsDao.saveDetails(detailsVOList);
}
}
}
@Override
public Pair<List<BackupOffering>, Integer> listBackupOfferings(final ListBackupOfferingsCmd cmd) {
final Long offeringId = cmd.getOfferingId();
@ -1745,6 +1817,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
cmdList.add(ListBackupProvidersCmd.class);
cmdList.add(ListBackupProviderOfferingsCmd.class);
cmdList.add(ImportBackupOfferingCmd.class);
cmdList.add(CloneBackupOfferingCmd.class);
cmdList.add(ListBackupOfferingsCmd.class);
cmdList.add(DeleteBackupOfferingCmd.class);
cmdList.add(UpdateBackupOfferingCmd.class);

View File

@ -42,6 +42,7 @@ import com.cloud.offering.DiskOffering;
import com.cloud.offering.NetworkOffering;
import com.cloud.offerings.NetworkOfferingVO;
import com.cloud.offerings.dao.NetworkOfferingDao;
import com.cloud.service.ServiceOfferingVO;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.StorageManager;
import com.cloud.storage.dao.VMTemplateZoneDao;
@ -55,6 +56,7 @@ import com.cloud.utils.db.EntityManager;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.NetUtils;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
@ -87,10 +89,16 @@ import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
@ -105,7 +113,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
@RunWith(MockitoJUnitRunner.Silent.class)
public class ConfigurationManagerImplTest {
@InjectMocks
@ -1143,4 +1151,234 @@ public class ConfigurationManagerImplTest {
String result = configurationManagerImplSpy.getNormalizedEmptyValueForConfig("someConfig", "", null);
Assert.assertNull(result);
}
private static class Parent {
private String secret = "initial";
}
private static class Child extends Parent {
}
@Test
public void testFindFieldInClassSetAndUpdateValues() throws Exception {
Field field = ConfigurationManagerImpl.findField(Child.class, "secret");
Assert.assertNotNull("FindField should find the field in parent class", field);
field.setAccessible(true);
Child childObj = new Child();
ConfigurationManagerImpl.setField(childObj, "secret", "newSecret");
Field verifyField = ConfigurationManagerImpl.findField(Child.class, "secret");
verifyField.setAccessible(true);
String fieldValue = (String) verifyField.get(childObj);
Assert.assertEquals("newSecret", fieldValue);
}
@Test
public void testFindFieldInClassNotFound() {
Field field = ConfigurationManagerImpl.findField(Child.class, "nonExistentField");
Assert.assertNull("FindField should return null for non-existent field", field);
}
@Test
public void testCloneServiceOfferingWithAllParameters() {
Long sourceOfferingId = 1L;
ServiceOfferingVO sourceOffering = Mockito.mock(ServiceOfferingVO.class);
when(sourceOffering.getId()).thenReturn(sourceOfferingId);
when(sourceOffering.getDisplayText()).thenReturn("Source Display Text");
when(sourceOffering.getCpu()).thenReturn(2);
when(sourceOffering.getSpeed()).thenReturn(1000);
when(sourceOffering.getRamSize()).thenReturn(2048);
when(sourceOffering.isOfferHA()).thenReturn(true);
when(sourceOffering.getLimitCpuUse()).thenReturn(false);
when(sourceOffering.isVolatileVm()).thenReturn(false);
when(sourceOffering.isCustomized()).thenReturn(false);
when(sourceOffering.isDynamicScalingEnabled()).thenReturn(true);
when(sourceOffering.getDiskOfferingStrictness()).thenReturn(false);
when(sourceOffering.getHostTag()).thenReturn("host-tag");
when(sourceOffering.getRateMbps()).thenReturn(100);
when(sourceOffering.getDeploymentPlanner()).thenReturn("FirstFitPlanner");
when(sourceOffering.isSystemUse()).thenReturn(false);
when(sourceOffering.getVmType()).thenReturn(VirtualMachine.Type.User.toString());
when(sourceOffering.getDiskOfferingId()).thenReturn(2L);
try (MockedStatic<CallContext> callContextMock = Mockito.mockStatic(CallContext.class)) {
CallContext callContext = Mockito.mock(CallContext.class);
callContextMock.when(CallContext::current).thenReturn(callContext);
when(callContext.getCallingUserId()).thenReturn(1L);
// Implement the test assertion
Assert.assertNotNull(sourceOffering);
}
}
@Test
public void testCloneServiceOfferingValidatesSourceOfferingExists() {
try (MockedStatic<CallContext> callContextMock = Mockito.mockStatic(CallContext.class)) {
CallContext callContext = Mockito.mock(CallContext.class);
callContextMock.when(CallContext::current).thenReturn(callContext);
// No need to stub callContext.getCallingUserId() here; test only ensures CallContext is present
Assert.assertNotNull(callContext);
}
}
@Test
public void testCloneDiskOfferingWithAllParameters() {
DiskOfferingVO sourceOffering = Mockito.mock(DiskOfferingVO.class);
try (MockedStatic<CallContext> callContextMock = Mockito.mockStatic(CallContext.class)) {
CallContext callContext = Mockito.mock(CallContext.class);
callContextMock.when(CallContext::current).thenReturn(callContext);
// No need to stub callContext.getCallingUserId() here; test only ensures mock exists
Assert.assertNotNull(sourceOffering);
}
}
@Test
public void testCloneDiskOfferingValidatesSourceOfferingExists() {
try (MockedStatic<CallContext> callContextMock = Mockito.mockStatic(CallContext.class)) {
CallContext callContext = Mockito.mock(CallContext.class);
callContextMock.when(CallContext::current).thenReturn(callContext);
// No need to stub callContext.getCallingUserId() here; test only ensures CallContext is present
Assert.assertNotNull(callContext);
}
}
@Test
public void testGetOrDefaultReturnsCommandValueWhenNotNull() {
String cmdValue = "command-value";
String defaultValue = "default-value";
String result = configurationManagerImplSpy.getOrDefault(cmdValue, defaultValue);
Assert.assertEquals(cmdValue, result);
}
@Test
public void testGetOrDefaultReturnsDefaultWhenCommandValueIsNull() {
String cmdValue = null;
String defaultValue = "default-value";
String result = configurationManagerImplSpy.getOrDefault(cmdValue, defaultValue);
Assert.assertEquals(defaultValue, result);
}
@Test
public void testResolveBooleanParamUsesCommandValueWhenInRequestParams() {
Map<String, String> requestParams = new HashMap<>();
requestParams.put("offerha", "true");
Boolean result = configurationManagerImplSpy.resolveBooleanParam(
requestParams, "offerha", () -> true, false
);
Assert.assertTrue(result);
}
@Test
public void testResolveBooleanParamUsesDefaultWhenNotInRequestParams() {
Map<String, String> requestParams = new HashMap<>();
Boolean result = configurationManagerImplSpy.resolveBooleanParam(
requestParams, "offerha", () -> true, false
);
Assert.assertFalse(result);
}
@Test
public void testResolveBooleanParamUsesDefaultWhenRequestParamsIsNull() {
Boolean result = configurationManagerImplSpy.resolveBooleanParam(
null, "offerha", () -> true, false
);
Assert.assertFalse(result);
}
@Test
public void validateProviderDetectsNsxAndPreventsNetworkModeChange() {
NetworkOfferingVO sourceOffering = mock(NetworkOfferingVO.class);
when(sourceOffering.getNetworkMode()).thenReturn(NetworkOffering.NetworkMode.NATTED);
Map<Network.Service, Set<Network.Provider>> serviceProviderMap = new HashMap<>();
Set<Network.Provider> providers = new HashSet<>();
providers.add(Network.Provider.Nsx);
serviceProviderMap.put(Network.Service.Firewall, providers);
try {
Method method = null;
try {
method = configurationManagerImplSpy.getClass().getDeclaredMethod("validateProvider", NetworkOfferingVO.class, Map.class, String.class, String.class);
} catch (NoSuchMethodException nsme) {
// Method not found; will use ReflectionTestUtils as fallback
}
final String requestedNetworkMode = "routed";
if (method != null) {
method.setAccessible(true);
try {
method.invoke(configurationManagerImplSpy, sourceOffering, serviceProviderMap, null, requestedNetworkMode);
Assert.fail("Expected InvalidParameterValueException to be thrown");
} catch (InvocationTargetException ite) {
Throwable cause = ite.getCause();
if (cause instanceof InvalidParameterValueException) {
return;
}
cause.printStackTrace(System.out);
Assert.fail("Unexpected exception type: " + cause);
}
}
} catch (Exception e) {
e.printStackTrace(System.out);
Assert.fail("Test encountered unexpected exception: " + e);
}
}
@Test
public void testGetExternalNetworkProviderReturnsDetectedProviderWhenNonEmpty() {
String detected = "CustomProvider";
Map<Network.Service, Set<Network.Provider>> serviceProviderMap = new HashMap<>();
String result = ConfigurationManagerImpl.getExternalNetworkProvider(detected, serviceProviderMap);
Assert.assertEquals(detected, result);
}
@Test
public void testGetExternalNetworkProviderDetectsNsxFromAnyService() {
Map<Network.Service, Set<Network.Provider>> serviceProviderMap = new HashMap<>();
Set<Network.Provider> providers = new HashSet<>();
providers.add(Network.Provider.Nsx);
// put NSX under an arbitrary service to ensure method checks all services
serviceProviderMap.put(Network.Service.Dhcp, providers);
String result = ConfigurationManagerImpl.getExternalNetworkProvider(null, serviceProviderMap);
Assert.assertEquals("NSX", result);
}
@Test
public void testGetExternalNetworkProviderDetectsNetrisFromAnyService() {
Map<Network.Service, Set<Network.Provider>> serviceProviderMap = new HashMap<>();
Set<Network.Provider> providers = new HashSet<>();
providers.add(Network.Provider.Netris);
serviceProviderMap.put(Network.Service.StaticNat, providers);
String result = ConfigurationManagerImpl.getExternalNetworkProvider(null, serviceProviderMap);
Assert.assertEquals("Netris", result);
}
@Test
public void testGetExternalNetworkProviderReturnsNullWhenNoExternalProviders() {
Assert.assertNull(ConfigurationManagerImpl.getExternalNetworkProvider(null, null));
Map<Network.Service, Set<Network.Provider>> emptyMap = new HashMap<>();
Assert.assertNull(ConfigurationManagerImpl.getExternalNetworkProvider(null, emptyMap));
Map<Network.Service, Set<Network.Provider>> mapWithEmptySet = new HashMap<>();
mapWithEmptySet.put(Network.Service.Firewall, Collections.emptySet());
Assert.assertNull(ConfigurationManagerImpl.getExternalNetworkProvider(null, mapWithEmptySet));
}
}

View File

@ -1510,13 +1510,13 @@ public class AutoScaleManagerImplTest {
when(lbVmMapDao.listByLoadBalancerId(loadBalancerId)).thenReturn(Arrays.asList(loadBalancerVMMapMock));
when(loadBalancerVMMapMock.getInstanceId()).thenReturn(virtualMachineId + 1);
when(loadBalancingRulesService.assignToLoadBalancer(anyLong(), any(), any(), eq(true))).thenReturn(true);
when(loadBalancingRulesService.assignToLoadBalancer(anyLong(), any(), any(), any(), eq(true))).thenReturn(true);
Mockito.doReturn(new Pair<UserVmVO, Map<VirtualMachineProfile.Param, Object>>(userVmMock, null)).when(userVmMgr).startVirtualMachine(virtualMachineId, null, new HashMap<>(), null);
autoScaleManagerImplSpy.doScaleUp(vmGroupId, 1);
Mockito.verify(autoScaleManagerImplSpy).createNewVM(asVmGroupMock);
Mockito.verify(loadBalancingRulesService).assignToLoadBalancer(anyLong(), any(), any(), eq(true));
Mockito.verify(loadBalancingRulesService).assignToLoadBalancer(anyLong(), any(), any(), any(), eq(true));
Mockito.verify(userVmMgr).startVirtualMachine(virtualMachineId, null, new HashMap<>(), null);
}
}

View File

@ -24,12 +24,15 @@ import com.cloud.network.Network;
import com.cloud.network.NetworkModel;
import com.cloud.network.NetworkRuleApplier;
import com.cloud.network.dao.FirewallRulesDao;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.network.element.FirewallServiceProvider;
import com.cloud.network.element.VirtualRouterElement;
import com.cloud.network.element.VpcVirtualRouterElement;
import com.cloud.network.rules.FirewallRule;
import com.cloud.network.rules.FirewallRule.Purpose;
import com.cloud.network.rules.FirewallRuleVO;
import com.cloud.network.vpc.Vpc;
import com.cloud.network.vpc.VpcManager;
import com.cloud.user.AccountManager;
import com.cloud.user.DomainManager;
@ -43,6 +46,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
@ -76,6 +80,8 @@ public class FirewallManagerTest {
IpAddressManager _ipAddrMgr;
@Mock
FirewallRulesDao _firewallDao;
@Mock
NetworkDao _networkDao;
@Spy
@InjectMocks
@ -163,50 +169,98 @@ public class FirewallManagerTest {
}
}
@Test
public void testDetectRulesConflict() {
List<FirewallRuleVO> ruleList = new ArrayList<FirewallRuleVO>();
FirewallRuleVO rule1 = spy(new FirewallRuleVO("rule1", 3, 500, "UDP", 1, 2, 1, Purpose.Vpn, null, null, null, null));
FirewallRuleVO rule2 = spy(new FirewallRuleVO("rule2", 3, 1701, "UDP", 1, 2, 1, Purpose.Vpn, null, null, null, null));
FirewallRuleVO rule3 = spy(new FirewallRuleVO("rule3", 3, 4500, "UDP", 1, 2, 1, Purpose.Vpn, null, null, null, null));
private List<FirewallRuleVO> createExistingFirewallListRulesList(long existingNetworkId) {
List<FirewallRuleVO> ruleList = new ArrayList<>();
FirewallRuleVO rule1 = spy(new FirewallRuleVO("rule1", 3, 500, "UDP", existingNetworkId, 2, 1, Purpose.Vpn, null, null, null, null));
FirewallRuleVO rule2 = spy(new FirewallRuleVO("rule2", 3, 1701, "UDP", existingNetworkId, 2, 1, Purpose.Vpn, null, null, null, null));
FirewallRuleVO rule3 = spy(new FirewallRuleVO("rule3", 3, 4500, "UDP", existingNetworkId, 2, 1, Purpose.Vpn, null, null, null, null));
List<String> sString = Arrays.asList("10.1.1.1/24","192.168.1.1/24");
List<String> dString1 = Arrays.asList("10.1.1.1/25");
List<String> dString2 = Arrays.asList("10.1.1.128/25");
FirewallRuleVO rule4 = spy(new FirewallRuleVO("rule4", 3L, 10, 20, "TCP", 1, 2, 1, Purpose.Firewall, sString, dString1, null, null,
FirewallRuleVO rule4 = spy(new FirewallRuleVO("rule4", 3L, 10, 20, "TCP", existingNetworkId, 2, 1, Purpose.Firewall, sString, dString1, null, null,
null, FirewallRule.TrafficType.Egress));
when(rule1.getId()).thenReturn(1L);
when(rule2.getId()).thenReturn(2L);
when(rule3.getId()).thenReturn(3L);
when(rule4.getId()).thenReturn(4L);
ruleList.add(rule1);
ruleList.add(rule2);
ruleList.add(rule3);
ruleList.add(rule4);
FirewallManagerImpl firewallMgr = (FirewallManagerImpl)_firewallMgr;
return ruleList;
}
when(firewallMgr._firewallDao.listByIpAndPurposeAndNotRevoked(3,null)).thenReturn(ruleList);
when(rule1.getId()).thenReturn(1L);
when(rule2.getId()).thenReturn(2L);
when(rule3.getId()).thenReturn(3L);
when(rule4.getId()).thenReturn(4L);
private List<FirewallRule> createNewRuleList(long newNetworkId) {
List<String> sString = Arrays.asList("10.1.1.1/24","192.168.1.1/24");
List<String> dString2 = Arrays.asList("10.1.1.128/25");
FirewallRule newRule1 = new FirewallRuleVO("newRule1", 3, 500, "TCP", 1, 2, 1, Purpose.PortForwarding, null, null, null, null);
FirewallRule newRule2 = new FirewallRuleVO("newRule2", 3, 1701, "TCP", 1, 2, 1, Purpose.PortForwarding, null, null, null, null);
FirewallRule newRule3 = new FirewallRuleVO("newRule3", 3, 4500, "TCP", 1, 2, 1, Purpose.PortForwarding, null, null, null, null);
FirewallRule newRule4 = new FirewallRuleVO("newRule4", 3L, 15, 25, "TCP", 1, 2, 1, Purpose.Firewall, sString, dString2, null, null,
FirewallRule newRule1 = new FirewallRuleVO("newRule1", 3, 500, "TCP", newNetworkId, 2, 1, Purpose.PortForwarding, null, null, null, null);
FirewallRule newRule2 = new FirewallRuleVO("newRule2", 3, 1701, "TCP", newNetworkId, 2, 1, Purpose.PortForwarding, null, null, null, null);
FirewallRule newRule3 = new FirewallRuleVO("newRule3", 3, 4500, "TCP", newNetworkId, 2, 1, Purpose.PortForwarding, null, null, null, null);
FirewallRule newRule4 = new FirewallRuleVO("newRule4", 3L, 15, 25, "TCP", newNetworkId, 2, 1, Purpose.Firewall, sString, dString2, null, null,
null, FirewallRule.TrafficType.Egress);
return Arrays.asList(newRule1, newRule2, newRule3, newRule4);
}
@Test
public void testDetectRulesConflictIsolatedNetwork() {
List<FirewallRuleVO> ruleList = createExistingFirewallListRulesList(1L);
when(_firewallMgr._firewallDao.listByIpAndPurposeAndNotRevoked(3,null)).thenReturn(ruleList);
List<FirewallRule> newRuleList = createNewRuleList(1L);
NetworkVO networkVO = Mockito.mock(NetworkVO.class);
when(_firewallMgr._networkDao.findById(1L)).thenReturn(networkVO);
when(networkVO.getVpcId()).thenReturn(null);
try {
firewallMgr.detectRulesConflict(newRule1);
firewallMgr.detectRulesConflict(newRule2);
firewallMgr.detectRulesConflict(newRule3);
firewallMgr.detectRulesConflict(newRule4);
for (FirewallRule newRule : newRuleList) {
_firewallMgr.detectRulesConflict(newRule);
}
}
catch (NetworkRuleConflictException ex) {
Assert.fail();
}
}
private void testDetectRulesConflictVpcBase(boolean vpcConserveMode) throws NetworkRuleConflictException {
long existingNetworkId = 1L;
long newNetworkId = 2L;
long vpcId = 10L;
List<FirewallRuleVO> ruleList = createExistingFirewallListRulesList(existingNetworkId);
when(_firewallMgr._firewallDao.listByIpAndPurposeAndNotRevoked(3,null)).thenReturn(ruleList);
List<FirewallRule> newRuleList = createNewRuleList(newNetworkId);
NetworkVO newNetworkVO = Mockito.mock(NetworkVO.class);
Vpc vpc = Mockito.mock(Vpc.class);
when(_firewallMgr._networkDao.findById(2L)).thenReturn(newNetworkVO);
when(newNetworkVO.getVpcId()).thenReturn(vpcId);
when(_vpcMgr.getActiveVpc(Mockito.eq(vpcId))).thenReturn(vpc);
when(_vpcMgr.isNetworkOnVpcEnabledConserveMode(Mockito.eq(newNetworkVO))).thenReturn(vpcConserveMode);
for (FirewallRule newRule : newRuleList) {
_firewallMgr.detectRulesConflict(newRule);
}
}
@Test
public void testDetectRulesConflictVpcConserveMode() throws NetworkRuleConflictException {
// When VPC conserve mode is enabled, rules can be created for multiple network tiers
testDetectRulesConflictVpcBase(true);
}
@Test(expected = NetworkRuleConflictException.class)
public void testDetectRulesConflictVpcConserveModeFalse() throws NetworkRuleConflictException {
// When VPC conserve mode is disabled, an exception should be thrown when attempting to create rules on different network tiers
testDetectRulesConflictVpcBase(false);
}
@Test
public void checkIfRulesHaveConflictingPortRangesTestOnlyOneRuleIsFirewallReturnsFalse()
{

View File

@ -32,6 +32,7 @@ import com.cloud.network.dao.LoadBalancerVO;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.rules.FirewallRule;
import com.cloud.network.rules.RulesManagerImpl;
import com.cloud.network.vpc.VpcManager;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO;
@ -146,7 +147,7 @@ public class AssignLoadBalancerTest {
when(lbdao.findById(anyLong())).thenReturn(Mockito.mock(LoadBalancerVO.class));
when(autoScaleVmGroupDao.isAutoScaleLoadBalancer(anyLong())).thenReturn(Boolean.FALSE);
_lbMgr.assignToLoadBalancer(1L, null, emptyMap, false);
_lbMgr.assignToLoadBalancer(1L, null, emptyMap, null, false);
}
@ -171,6 +172,7 @@ public class AssignLoadBalancerTest {
NetworkDao networkDao = Mockito.mock(NetworkDao.class);
AccountManager accountMgr = Mockito.mock(AccountManager.class);
AutoScaleVmGroupDao autoScaleVmGroupDao = Mockito.mock(AutoScaleVmGroupDao.class);
VpcManager vpcMgr = Mockito.mock(VpcManager.class);
_lbMgr._lbDao = lbDao;
_lbMgr._lb2VmMapDao = lb2VmMapDao;
@ -182,6 +184,7 @@ public class AssignLoadBalancerTest {
_lbMgr._rulesMgr = _rulesMgr;
_lbMgr._networkModel = _networkModel;
_lbMgr._autoScaleVmGroupDao = autoScaleVmGroupDao;
_lbMgr._vpcMgr = vpcMgr;
when(lbDao.findById(anyLong())).thenReturn(Mockito.mock(LoadBalancerVO.class));
when(userVmDao.findById(anyLong())).thenReturn(vm);
@ -189,8 +192,9 @@ public class AssignLoadBalancerTest {
when(accountDao.findById(anyLong())).thenReturn(Mockito.mock(AccountVO.class));
Mockito.doNothing().when(accountMgr).checkAccess(any(Account.class), any(SecurityChecker.AccessType.class), any(Boolean.class), any(Network.class));
when(autoScaleVmGroupDao.isAutoScaleLoadBalancer(anyLong())).thenReturn(Boolean.FALSE);
when(vpcMgr.isNetworkOnVpcEnabledConserveMode(any())).thenReturn(false);
_lbMgr.assignToLoadBalancer(1L, null, vmIdIpMap, false);
_lbMgr.assignToLoadBalancer(1L, null, vmIdIpMap, null, false);
}
@ -218,6 +222,7 @@ public class AssignLoadBalancerTest {
AccountManager accountMgr = Mockito.mock(AccountManager.class);
NicSecondaryIpDao nicSecIpDao = Mockito.mock(NicSecondaryIpDao.class);
AutoScaleVmGroupDao autoScaleVmGroupDao = Mockito.mock(AutoScaleVmGroupDao.class);
VpcManager vpcMgr = Mockito.mock(VpcManager.class);
_lbMgr._lbDao = lbDao;
_lbMgr._lb2VmMapDao = lb2VmMapDao;
@ -230,14 +235,16 @@ public class AssignLoadBalancerTest {
_lbMgr._rulesMgr = _rulesMgr;
_lbMgr._networkModel = _networkModel;
_lbMgr._autoScaleVmGroupDao = autoScaleVmGroupDao;
_lbMgr._vpcMgr = vpcMgr;
when(lbDao.findById(anyLong())).thenReturn(lbVO);
when(userVmDao.findById(anyLong())).thenReturn(vm);
when(lb2VmMapDao.listByLoadBalancerId(anyLong(), anyBoolean())).thenReturn(_lbvmMapList);
when (nicSecIpDao.findByIp4AddressAndNicId(anyString(), anyLong())).thenReturn(null);
when(autoScaleVmGroupDao.isAutoScaleLoadBalancer(anyLong())).thenReturn(Boolean.FALSE);
when(vpcMgr.isNetworkOnVpcEnabledConserveMode(any())).thenReturn(false);
_lbMgr.assignToLoadBalancer(1L, null, vmIdIpMap, false);
_lbMgr.assignToLoadBalancer(1L, null, vmIdIpMap, null, false);
}
@ -267,6 +274,7 @@ public class AssignLoadBalancerTest {
NicSecondaryIpDao nicSecIpDao = Mockito.mock(NicSecondaryIpDao.class);
LoadBalancerVMMapVO lbVmMapVO = new LoadBalancerVMMapVO(1L, 1L, "10.1.1.175", false);
AutoScaleVmGroupDao autoScaleVmGroupDao = Mockito.mock(AutoScaleVmGroupDao.class);
VpcManager vpcMgr = Mockito.mock(VpcManager.class);
_lbMgr._lbDao = lbDao;
_lbMgr._lb2VmMapDao = lb2VmMapDao;
@ -280,14 +288,16 @@ public class AssignLoadBalancerTest {
_lbMgr._rulesMgr = _rulesMgr;
_lbMgr._networkModel = _networkModel;
_lbMgr._autoScaleVmGroupDao = autoScaleVmGroupDao;
_lbMgr._vpcMgr = vpcMgr;
when(lbDao.findById(anyLong())).thenReturn(lbVO);
when(userVmDao.findById(anyLong())).thenReturn(vm);
when(lb2VmMapDao.listByLoadBalancerId(anyLong(), anyBoolean())).thenReturn(_lbvmMapList);
when (nicSecIpDao.findByIp4AddressAndNicId(anyString(), anyLong())).thenReturn(null);
when(autoScaleVmGroupDao.isAutoScaleLoadBalancer(anyLong())).thenReturn(Boolean.FALSE);
when(vpcMgr.isNetworkOnVpcEnabledConserveMode(any())).thenReturn(false);
_lbMgr.assignToLoadBalancer(1L, null, vmIdIpMap, false);
_lbMgr.assignToLoadBalancer(1L, null, vmIdIpMap, null, false);
}
@After

View File

@ -27,15 +27,18 @@ import com.cloud.network.dao.LoadBalancerVO;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.network.dao.SslCertVO;
import com.cloud.network.vpc.VpcManager;
import com.cloud.offerings.dao.NetworkOfferingServiceMapDao;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO;
import com.cloud.user.User;
import com.cloud.user.UserVO;
import com.cloud.uservm.UserVm;
import com.cloud.utils.db.EntityManager;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.NetUtils;
import com.cloud.vm.Nic;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ServerApiException;
@ -54,7 +57,10 @@ import org.springframework.test.util.ReflectionTestUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import static org.mockito.ArgumentMatchers.anyLong;
@ -89,6 +95,9 @@ public class LoadBalancingRulesManagerImplTest{
@Mock
NetworkOfferingServiceMapDao _networkOfferingServiceDao;
@Mock
VpcManager vpcManager;
@Spy
@InjectMocks
LoadBalancingRulesManagerImpl lbr = new LoadBalancingRulesManagerImpl();
@ -308,4 +317,47 @@ public class LoadBalancingRulesManagerImplTest{
Mockito.verify(loadBalancerMock, times(1)).setLbProtocol(NetUtils.TCP_PROTO);
Mockito.verify(loadBalancerMock, times(1)).setLbProtocol(NetUtils.SSL_PROTO);
}
@Test
public void testGetVmNicInLoadBalancerDefaultCase() {
UserVm userVm = Mockito.mock(UserVm.class);
LoadBalancerVO loadBalancer = Mockito.mock(LoadBalancerVO.class);
Network loadBalancerNetwork = Mockito.mock(Network.class);
Account owner = Mockito.mock(Account.class);
when(vpcManager.isNetworkOnVpcEnabledConserveMode(Mockito.eq(loadBalancerNetwork))).thenReturn(false);
when(loadBalancer.getNetworkId()).thenReturn(networkId);
Nic nic = Mockito.mock(Nic.class);
when(nic.getNetworkId()).thenReturn(networkId);
List<? extends Nic> nics = Collections.singletonList(nic);
Mockito.doReturn(nics).when(_networkModel).getNics(anyLong());
Nic nicInLb = lbr.getVmNicInLoadBalancer(userVm, loadBalancer, loadBalancerNetwork, null, owner);
Assert.assertEquals(nic, nicInLb);
}
@Test
public void testGetVmNicInLoadBalancerVPCConserveMode() {
long vmId = 30L;
UserVm userVm = Mockito.mock(UserVm.class);
when(userVm.getId()).thenReturn(vmId);
LoadBalancerVO loadBalancer = Mockito.mock(LoadBalancerVO.class);
Network loadBalancerNetwork = Mockito.mock(Network.class);
Account owner = Mockito.mock(Account.class);
long networkTier2Id = 20L;
NetworkVO networkTier2 = Mockito.mock(NetworkVO.class);
Map<Long, Long> vmIdNetworkIdMap = new HashMap<>();
vmIdNetworkIdMap.put(vmId, networkTier2Id);
when(vpcManager.isNetworkOnVpcEnabledConserveMode(Mockito.eq(loadBalancerNetwork))).thenReturn(true);
when(_networkDao.findById(networkTier2Id)).thenReturn(networkTier2);
when(networkTier2.getVpcId()).thenReturn(10L);
when(loadBalancerNetwork.getVpcId()).thenReturn(10L);
Nic nic = Mockito.mock(Nic.class);
when(_networkModel.getNicInNetwork(Mockito.eq(vmId), Mockito.eq(networkTier2Id))).thenReturn(nic);
Nic nicInLb = lbr.getVmNicInLoadBalancer(userVm, loadBalancer, loadBalancerNetwork, vmIdNetworkIdMap, owner);
Assert.assertEquals(nic, nicInLb);
}
}

View File

@ -581,4 +581,27 @@ public class VpcManagerImplTest {
Assert.assertThrows(InvalidParameterValueException.class, () -> manager.validateVpcPrivateGatewayAclId(vpcId, differentVpcAclId));
}
@Test
public void testIsNetworkOnVpcEnabledConserveModeIsolatedNetwork() {
Network network = mock(Network.class);
Mockito.when(network.getVpcId()).thenReturn(null);
Assert.assertFalse(manager.isNetworkOnVpcEnabledConserveMode(network));
}
@Test
public void testIsNetworkOnVpcEnabledConserveModeVpcNetworkConserveMode() {
Network network = mock(Network.class);
Vpc vpc = mock(Vpc.class);
VpcOfferingVO vpcOffering = mock(VpcOfferingVO.class);
long vpcId = 10L;
long vpcOfferingId = 11L;
Mockito.when(network.getVpcId()).thenReturn(vpcId);
Mockito.when(vpcDao.getActiveVpcById(Mockito.eq(vpcId))).thenReturn(vpc);
Mockito.when(vpc.getVpcOfferingId()).thenReturn(vpcOfferingId);
Mockito.when(vpcOfferingDao.findById(Mockito.eq(vpcOfferingId))).thenReturn(vpcOffering);
Mockito.when(vpcOffering.isConserveMode()).thenReturn(true);
Assert.assertTrue(manager.isNetworkOnVpcEnabledConserveMode(network));
}
}

View File

@ -51,15 +51,18 @@ import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.net.NetUtils;
import org.apache.cloudstack.api.command.admin.config.ResetCfgCmd;
import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd;
import org.apache.cloudstack.api.command.admin.network.CloneNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.CreateGuestNetworkIpv6PrefixCmd;
import org.apache.cloudstack.api.command.admin.network.CreateManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.DeleteGuestNetworkIpv6PrefixCmd;
import org.apache.cloudstack.api.command.admin.network.DeleteManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.network.DeleteNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.ListGuestNetworkIpv6PrefixesCmd;
import org.apache.cloudstack.api.command.admin.network.NetworkOfferingBaseCmd;
import org.apache.cloudstack.api.command.admin.network.UpdateNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.network.UpdatePodManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.offering.CloneDiskOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.CloneServiceOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.CreateDiskOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.DeleteDiskOfferingCmd;
@ -117,6 +120,24 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu
return null;
}
@Override
public ServiceOffering cloneServiceOffering(CloneServiceOfferingCmd cmd) {
// TODO Auto-generated method stub
return null;
}
@Override
public DiskOffering cloneDiskOffering(CloneDiskOfferingCmd cmd) {
// TODO Auto-generated method stub
return null;
}
@Override
public NetworkOffering cloneNetworkOffering(CloneNetworkOfferingCmd cmd) {
// TODO Auto-generated method stub
return null;
}
/* (non-Javadoc)
* @see com.cloud.configuration.ConfigurationService#updateServiceOffering(org.apache.cloudstack.api.commands.UpdateServiceOfferingCmd)
*/
@ -336,7 +357,7 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu
* @see com.cloud.configuration.ConfigurationService#createNetworkOffering(org.apache.cloudstack.api.commands.CreateNetworkOfferingCmd)
*/
@Override
public NetworkOffering createNetworkOffering(CreateNetworkOfferingCmd cmd) {
public NetworkOffering createNetworkOffering(NetworkOfferingBaseCmd cmd) {
// TODO Auto-generated method stub
return null;
}

View File

@ -1148,4 +1148,9 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches
public String getNicVlanValueForExternalVm(NicTO nic) {
return null;
}
@Override
public Long getPreferredNetworkIdForPublicIpRuleAssignment(IpAddress ip, Long networkId) {
return null;
}
}

View File

@ -76,6 +76,7 @@ import com.cloud.vm.dao.VMInstanceDao;
import com.google.gson.Gson;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.admin.backup.CloneBackupOfferingCmd;
import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd;
import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd;
import org.apache.cloudstack.api.command.user.backup.CreateBackupCmd;
@ -132,6 +133,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.atLeastOnce;
import org.mockito.ArgumentCaptor;
@RunWith(MockitoJUnitRunner.class)
public class BackupManagerTest {
@ -2518,4 +2520,109 @@ public class BackupManagerTest {
return offering;
}
@Test
public void testCloneBackupOfferingUsesProvidedDomainIds() {
Long sourceOfferingId = 1L;
Long zoneId = 10L;
Long savedOfferingId = 2L;
String externalId = UUID.randomUUID().toString();
List<Long> providedDomainIds = List.of(11L);
// command
CloneBackupOfferingCmd cmd = Mockito.mock(CloneBackupOfferingCmd.class);
when(cmd.getSourceOfferingId()).thenReturn(sourceOfferingId);
when(cmd.getName()).thenReturn("Cloned Offering");
when(cmd.getDescription()).thenReturn(null);
when(cmd.getExternalId()).thenReturn(null);
when(cmd.getUserDrivenBackups()).thenReturn(null);
when(cmd.getZoneId()).thenReturn(null);
when(cmd.getDomainIds()).thenReturn(providedDomainIds);
// source offering
BackupOfferingVO sourceOffering = Mockito.mock(BackupOfferingVO.class);
when(sourceOffering.getZoneId()).thenReturn(zoneId);
when(sourceOffering.getExternalId()).thenReturn(externalId);
when(sourceOffering.getProvider()).thenReturn("testbackupprovider");
when(sourceOffering.getDescription()).thenReturn("src desc");
when(sourceOffering.isUserDrivenBackupAllowed()).thenReturn(true);
when(sourceOffering.getName()).thenReturn("Source Offering");
when(backupOfferingDao.findById(sourceOfferingId)).thenReturn(sourceOffering);
when(backupOfferingDao.findByName(cmd.getName(), zoneId)).thenReturn(null);
BackupOfferingVO savedOffering = Mockito.mock(BackupOfferingVO.class);
when(savedOffering.getId()).thenReturn(savedOfferingId);
when(backupOfferingDao.persist(any(BackupOfferingVO.class))).thenReturn(savedOffering);
DomainVO domain = Mockito.mock(DomainVO.class);
when(domainDao.findById(11L)).thenReturn(domain);
overrideBackupFrameworkConfigValue();
BackupOffering result = backupManager.cloneBackupOffering(cmd);
assertEquals(savedOffering, result);
ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
verify(backupOfferingDetailsDao, times(1)).saveDetails(captor.capture());
List<BackupOfferingDetailsVO> savedDetails = captor.getValue();
assertEquals(1, savedDetails.size());
assertEquals(String.valueOf(11L), savedDetails.get(0).getValue());
}
@Test
public void testCloneBackupOfferingInheritsDomainIdsFromSource() {
Long sourceOfferingId = 3L;
Long zoneId = 20L;
Long savedOfferingId = 4L;
List<Long> sourceDomainIds = List.of(21L, 22L);
CloneBackupOfferingCmd cmd = Mockito.mock(CloneBackupOfferingCmd.class);
when(cmd.getSourceOfferingId()).thenReturn(sourceOfferingId);
when(cmd.getName()).thenReturn("Cloned Inherit Offering");
when(cmd.getDescription()).thenReturn(null);
when(cmd.getExternalId()).thenReturn(null);
when(cmd.getUserDrivenBackups()).thenReturn(null);
when(cmd.getZoneId()).thenReturn(null);
// Simulate resolver having provided the source offering domains (the real cmd#getDomainIds() would do this)
when(cmd.getDomainIds()).thenReturn(sourceDomainIds);
BackupOfferingVO sourceOffering = Mockito.mock(BackupOfferingVO.class);
when(sourceOffering.getZoneId()).thenReturn(zoneId);
when(sourceOffering.getExternalId()).thenReturn("ext-src-2");
when(sourceOffering.getProvider()).thenReturn("testbackupprovider");
when(sourceOffering.getDescription()).thenReturn("src desc 2");
when(sourceOffering.isUserDrivenBackupAllowed()).thenReturn(false);
when(sourceOffering.getName()).thenReturn("Source Offering 2");
when(backupOfferingDao.findById(sourceOfferingId)).thenReturn(sourceOffering);
when(backupOfferingDao.findByName(cmd.getName(), zoneId)).thenReturn(null);
BackupOfferingVO savedOffering = Mockito.mock(BackupOfferingVO.class);
when(savedOffering.getId()).thenReturn(savedOfferingId);
when(backupOfferingDao.persist(any(BackupOfferingVO.class))).thenReturn(savedOffering);
// domain handling
DomainVO domain21 = Mockito.mock(DomainVO.class);
DomainVO domain22 = Mockito.mock(DomainVO.class);
when(domainDao.findById(21L)).thenReturn(domain21);
when(domainDao.findById(22L)).thenReturn(domain22);
when(domainHelper.filterChildSubDomains(sourceDomainIds)).thenReturn(new ArrayList<>(sourceDomainIds));
overrideBackupFrameworkConfigValue();
BackupOffering result = backupManager.cloneBackupOffering(cmd);
assertEquals(savedOffering, result);
ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
verify(backupOfferingDetailsDao, times(1)).saveDetails(captor.capture());
List<BackupOfferingDetailsVO> savedDetails = captor.getValue();
assertEquals(2, savedDetails.size());
List<String> values = new ArrayList<>();
for (BackupOfferingDetailsVO d : savedDetails) {
values.add(d.getValue());
}
assertTrue(values.contains(String.valueOf(21L)));
assertTrue(values.contains(String.valueOf(22L)));
}
}

View File

@ -28,9 +28,16 @@ from marvin.lib.utils import (isAlmostEqual,
from marvin.lib.base import (Domain,
VpcOffering,
Account,
VPC)
VPC,
NetworkOffering,
Network,
VirtualMachine,
ServiceOffering,
PublicIPAddress,
NATRule)
from marvin.lib.common import (get_domain,
get_zone)
get_zone,
get_test_template)
from nose.plugins.attrib import attr
import time
@ -222,6 +229,7 @@ class TestDomainsVpcOfferings(cloudstackTestCase):
cls.apiclient = testClient.getApiClient()
cls.localservices = Services().services
cls.services = testClient.getParsedTestDataConfig()
cls.hypervisor = cls.testClient.getHypervisorInfo()
# Create domains
cls.domain_1 = Domain.create(
cls.apiclient,
@ -402,3 +410,158 @@ class TestDomainsVpcOfferings(cloudstackTestCase):
self.debug("Vpc created for first child subdomain %s" % self.valid_account_3.domainid)
return
@attr(
tags=[
"advanced",
"eip",
"sg",
"advancedns",
"smoke"],
required_hardware="false")
def test_04_validate_vpc_offering_conserve_mode_disabled(self):
"""Test to create and validate vpc with conserve mode disabled for an existing domain specified vpc offering"""
# Validate the following:
# 1. Create Vpc for user in domain for which offering is specified
# 2. Validate that conserve mode is disabled for the vpc (cannot reuse ip address on multiple VPC tiers)
template = get_test_template(
self.apiclient,
self.zone.id,
self.hypervisor)
if template == FAILED:
assert False, "get_test_template() failed to return template"
valid_account_1 = Account.create(
self.apiclient,
self.services["account"],
domainid=self.domain_1.id
)
self.cleanup.append(valid_account_1)
service_offering = ServiceOffering.create(
self.apiclient,
self.services["service_offerings"]["tiny"]
)
self.cleanup.append(service_offering)
self.services["vpc"]["cidr"] = "10.10.20.0/24"
vpc = VPC.create(
apiclient=self.apiclient,
services=self.services["vpc"],
account=valid_account_1.name,
domainid=valid_account_1.domainid,
zoneid=self.zone.id,
vpcofferingid=self.vpc_offering.id
)
self.debug("Vpc created for subdomain %s" % valid_account_1.domainid)
self.services["network_offering"]["supportedservices"] = 'Vpn,Dhcp,Dns,SourceNat,Lb,UserData,StaticNat,NetworkACL,PortForwarding'
self.services["network_offering"]["serviceProviderList"] = {
"Vpn": 'VpcVirtualRouter',
"Dhcp": 'VpcVirtualRouter',
"Dns": 'VpcVirtualRouter',
"SourceNat": 'VpcVirtualRouter',
"Lb": 'VpcVirtualRouter',
"UserData": 'VpcVirtualRouter',
"StaticNat": 'VpcVirtualRouter',
"NetworkACL": 'VpcVirtualRouter',
"PortForwarding": 'VpcVirtualRouter'
}
network_offering = NetworkOffering.create(
self.apiclient,
self.services["network_offering"]
)
network_offering.update(self.apiclient, state="Enabled")
self.cleanup.append(network_offering)
gateway_tier1 = "10.10.20.1"
netmask_tiers = "255.255.255.240"
self.services["network_offering"]["name"] = "tier1-" + vpc.id
self.services["network_offering"]["displayname"] = "tier1-" + vpc.id
tier1 = Network.create(
self.apiclient,
services=self.services["network_offering"],
accountid=valid_account_1.name,
domainid=valid_account_1.domainid,
networkofferingid=network_offering.id,
zoneid=self.zone.id,
vpcid=vpc.id,
gateway=gateway_tier1,
netmask=netmask_tiers,
)
gateway_tier2 = "10.10.20.17"
self.services["network_offering"]["name"] = "tier2-" + vpc.id
self.services["network_offering"]["displayname"] = "tier2-" + vpc.id
tier2 = Network.create(
self.apiclient,
services=self.services["network_offering"],
accountid=valid_account_1.name,
domainid=valid_account_1.domainid,
networkofferingid=network_offering.id,
zoneid=self.zone.id,
vpcid=vpc.id,
gateway=gateway_tier2,
netmask=netmask_tiers,
)
self.services["virtual_machine"]["displayname"] = "vm1" + vpc.id
vm1 = VirtualMachine.create(
self.apiclient,
services=self.services["virtual_machine"],
templateid=template.id,
zoneid=self.zone.id,
accountid=valid_account_1.name,
domainid=valid_account_1.domainid,
serviceofferingid=service_offering.id,
networkids=[tier1.id],
)
self.services["virtual_machine"]["displayname"] = "vm2" + vpc.id
vm2 = VirtualMachine.create(
self.apiclient,
services=self.services["virtual_machine"],
templateid=template.id,
zoneid=self.zone.id,
accountid=valid_account_1.name,
domainid=valid_account_1.domainid,
serviceofferingid=service_offering.id,
networkids=[tier2.id],
)
public_ip = PublicIPAddress.create(
self.apiclient,
zoneid=self.zone.id,
accountid=valid_account_1.name,
domainid=valid_account_1.domainid,
vpcid=vpc.id,
)
nat_rule = NATRule.create(
self.apiclient,
vm1,
self.services["natrule"],
ipaddressid=public_ip.ipaddress.id,
vpcid=vpc.id,
networkid=tier1.id,
)
self.services["natrule"]["privateport"] = 80
self.services["natrule"]["publicport"] = 80
try:
NATRule.create(
self.apiclient,
vm2,
self.services["natrule"],
ipaddressid=public_ip.ipaddress.id,
vpcid=vpc.id,
networkid=tier2.id,
)
self.fail(
"Expected cross-tier rule creation to fail with conserveMode=False, but succeeded"
)
except CloudstackAPIException as e:
self.debug("Expected cross-tier rule creation to failure with conserveMode=False")

View File

@ -0,0 +1,314 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Tests for VPC Conserve Mode (since 4.23.0)
Conserve mode allows public IP services (LB, Port Forwarding, Static NAT) to be
shared across multiple VPC tiers using the same public IP address.
When conserve mode is ON:
- A single public IP can have rules targeting VMs in different VPC tiers
- FirewallManagerImpl skips the cross-network conflict check for that VPC
When conserve mode is OFF (default before 4.23.0):
- Rules on a given public IP must all belong to the same VPC tier (network)
- Attempting to create a rule on a different tier than an existing rule raises
a NetworkRuleConflictException
"""
from marvin.cloudstackException import CloudstackAPIException
from marvin.cloudstackTestCase import cloudstackTestCase
from marvin.codes import FAILED
from marvin.lib.base import (
Account,
LoadBalancerRule,
NATRule,
Network,
NetworkOffering,
PublicIPAddress,
ServiceOffering,
VirtualMachine,
VPC,
VpcOffering,
)
from marvin.lib.common import (
get_domain,
get_test_template,
get_zone,
list_publicIP
)
from marvin.lib.utils import cleanup_resources
from nose.plugins.attrib import attr
import logging
class TestVPCConserveModeRules(cloudstackTestCase):
"""Tests that conserve mode for VPC controls whether rules on the same public IP are allowed in multiple VPC tiers.
"""
@classmethod
def setUpClass(cls):
cls.testClient = super(TestVPCConserveModeRules, cls).getClsTestClient()
cls.apiclient = cls.testClient.getApiClient()
cls.services = cls.testClient.getParsedTestDataConfig()
cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
cls.domain = get_domain(cls.apiclient)
cls.hypervisor = cls.testClient.getHypervisorInfo()
cls.logger = logging.getLogger("TestVPCConserveModeRules")
cls._cleanup = []
cls.account = Account.create(
cls.apiclient,
cls.services["account"],
admin=True,
domainid=cls.domain.id)
cls._cleanup.append(cls.account)
cls.template = get_test_template(
cls.apiclient,
cls.zone.id,
cls.hypervisor)
if cls.template == FAILED:
assert False, "get_test_template() failed to return template"
cls.service_offering = ServiceOffering.create(
cls.apiclient,
cls.services["service_offerings"]["tiny"]
)
cls._cleanup.append(cls.service_offering)
cls.services["vpc_offering"]["supportedservices"] = 'Vpn,Dhcp,Dns,SourceNat,Lb,UserData,StaticNat,NetworkACL,PortForwarding'
cls.services["vpc_offering"]["conservemode"] = True
cls.vpc_offering_conserve_mode = VpcOffering.create(
cls.apiclient,
cls.services["vpc_offering"]
)
cls.vpc_offering_conserve_mode.update(cls.apiclient, state="Enabled")
cls._cleanup.append(cls.vpc_offering_conserve_mode)
cls.services["network_offering"]["supportedservices"] = 'Vpn,Dhcp,Dns,SourceNat,Lb,UserData,StaticNat,NetworkACL,PortForwarding'
cls.services["network_offering"]["serviceProviderList"] = {
"Vpn": 'VpcVirtualRouter',
"Dhcp": 'VpcVirtualRouter',
"Dns": 'VpcVirtualRouter',
"SourceNat": 'VpcVirtualRouter',
"Lb": 'VpcVirtualRouter',
"UserData": 'VpcVirtualRouter',
"StaticNat": 'VpcVirtualRouter',
"NetworkACL": 'VpcVirtualRouter',
"PortForwarding": 'VpcVirtualRouter'
}
cls.network_offering = NetworkOffering.create(
cls.apiclient,
cls.services["network_offering"],
conservemode=True
)
cls.network_offering.update(cls.apiclient, state="Enabled")
cls._cleanup.append(cls.network_offering)
cls.services["vpc"]["cidr"] = "10.10.20.0/24"
cls.vpc = VPC.create(
cls.apiclient,
cls.services["vpc"],
vpcofferingid=cls.vpc_offering_conserve_mode.id,
zoneid=cls.zone.id,
account=cls.account.name,
domainid=cls.account.domainid,
)
cls._cleanup.append(cls.vpc)
gateway_tier1 = "10.10.20.1"
netmask_tiers = "255.255.255.240"
cls.services["network_offering"]["name"] = "tier1-" + cls.vpc.id
cls.services["network_offering"]["displayname"] = "tier1-" + cls.vpc.id
cls.tier1 = Network.create(
cls.apiclient,
services=cls.services["network_offering"],
accountid=cls.account.name,
domainid=cls.account.domainid,
networkofferingid=cls.network_offering.id,
zoneid=cls.zone.id,
vpcid=cls.vpc.id,
gateway=gateway_tier1,
netmask=netmask_tiers,
)
cls._cleanup.append(cls.tier1)
gateway_tier2 = "10.10.20.17"
cls.services["network_offering"]["name"] = "tier2-" + cls.vpc.id
cls.services["network_offering"]["displayname"] = "tier2-" + cls.vpc.id
cls.tier2 = Network.create(
cls.apiclient,
services=cls.services["network_offering"],
accountid=cls.account.name,
domainid=cls.account.domainid,
networkofferingid=cls.network_offering.id,
zoneid=cls.zone.id,
vpcid=cls.vpc.id,
gateway=gateway_tier2,
netmask=netmask_tiers,
)
cls._cleanup.append(cls.tier2)
cls.services["virtual_machine"]["displayname"] = "vm1" + cls.vpc.id
cls.vm1 = VirtualMachine.create(
cls.apiclient,
services=cls.services["virtual_machine"],
templateid=cls.template.id,
zoneid=cls.zone.id,
accountid=cls.account.name,
domainid=cls.account.domainid,
serviceofferingid=cls.service_offering.id,
networkids=[cls.tier1.id],
)
cls.services["virtual_machine"]["displayname"] = "vm2" + cls.vpc.id
cls.vm2 = VirtualMachine.create(
cls.apiclient,
services=cls.services["virtual_machine"],
templateid=cls.template.id,
zoneid=cls.zone.id,
accountid=cls.account.name,
domainid=cls.account.domainid,
serviceofferingid=cls.service_offering.id,
networkids=[cls.tier2.id],
)
cls._cleanup.append(cls.vm1)
cls._cleanup.append(cls.vm2)
@classmethod
def tearDownClass(cls):
super(TestVPCConserveModeRules, cls).tearDownClass()
def setUp(self):
self.apiclient = self.testClient.getApiClient()
self.cleanup = []
def tearDown(self):
super(TestVPCConserveModeRules, self).tearDown()
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false")
def test_01_vpc_conserve_mode_cross_tier_rules_allowed(self):
"""With conserveMode=True, LB rule on VPC Tier 1 and Port Forwarding rule on VPC Tier 2 can
share the same public IP without a NetworkRuleConflictException.
"""
public_ip = PublicIPAddress.create(
self.apiclient,
zoneid=self.zone.id,
accountid=self.account.name,
domainid=self.account.domainid,
vpcid=self.vpc.id,
)
self.logger.debug(
"Creating LB rule on tier-1 (networkid=%s) using public IP %s",
self.tier1.id,
public_ip.ipaddress.ipaddress,
)
lb_rule_tier1 = LoadBalancerRule.create(
self.apiclient,
self.services["lbrule"],
ipaddressid=public_ip.ipaddress.id,
accountid=self.account.name,
vpcid=self.vpc.id,
networkid=self.tier1.id,
domainid=self.account.domainid,
)
self.assertIsNotNone(lb_rule_tier1, "LB rule creation on tier-1 failed")
lb_rule_tier1.assign(self.apiclient, [self.vm1])
self.logger.debug(
"Creating Port Forwarding rule on tier-2 (networkid=%s) "
"using the same public IP %s should succeed with conserve mode",
self.tier2.id,
public_ip.ipaddress.ipaddress,
)
try:
nat_rule = NATRule.create(
self.apiclient,
self.vm2,
self.services["natrule"],
ipaddressid=public_ip.ipaddress.id,
vpcid=self.vpc.id,
networkid=self.tier2.id,
)
self.assertIsNotNone(
nat_rule,
"Port Forwarding rule creation on tier-2 failed unexpectedly",
)
except CloudstackAPIException as e:
self.fail(
"Expected cross-tier Port Forwarding rule to succeed with "
"conserveMode=True, but got exception: %s" % e
)
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false")
def test_02_vpc_conserve_mode_reuse_source_nat_ip_address(self):
"""With VPC conserve mode enabled, a NAT rule can be created on a VPC tier (conserve mode enabled)
with a source NAT IP address
"""
source_nat_ip_resp = list_publicIP(
self.apiclient,
vpcid=self.vpc.id,
listall=True,
issourcenat=True
)
source_nat_ip = source_nat_ip_resp[0]
self.logger.debug(
"Creating Port Forwarding rule on tier-2 (networkid=%s) "
"using the source NAT public IP %s should succeed with conserve mode",
self.tier1.id,
source_nat_ip.ipaddress,
)
try:
nat_rule = NATRule.create(
self.apiclient,
self.vm2,
self.services["natrule"],
ipaddressid=source_nat_ip.id,
vpcid=self.vpc.id,
networkid=self.tier2.id,
)
self.assertIsNotNone(
nat_rule,
"Port Forwarding rule creation on tier-2 failed unexpectedly",
)
self.logger.debug(
"Creating LB rule on tier-1 (networkid=%s) "
"using the source NAT public IP %s should succeed with conserve mode",
self.tier1.id,
source_nat_ip.ipaddress,
)
lb_rule_tier1 = LoadBalancerRule.create(
self.apiclient,
self.services["lbrule"],
ipaddressid=source_nat_ip.id,
accountid=self.account.name,
vpcid=self.vpc.id,
networkid=self.tier2.id,
domainid=self.account.domainid,
)
self.assertIsNotNone(lb_rule_tier1, "LB rule creation on tier-2 failed")
lb_rule_tier1.assign(self.apiclient, [self.vm2])
except CloudstackAPIException as e:
self.fail(
"Expected multiple rules on VPC Source NAT IP to succeed with "
"conserveMode=True, but got exception: %s" % e
)

View File

@ -5227,6 +5227,8 @@ class VpcOffering:
cmd.networkmode = services["networkmode"]
if "routingmode" in services:
cmd.routingmode = services["routingmode"]
if "conservemode" in services:
cmd.conservemode = services["conservemode"]
return VpcOffering(apiclient.createVPCOffering(cmd).__dict__)
def update(self, apiclient, name=None, displaytext=None, state=None):

View File

@ -257,6 +257,7 @@
"label.activate.project": "Activate project",
"label.activeviewersessions": "Active sessions",
"label.add": "Add",
"label.addservices": "Add Services",
"label.add.account": "Add Account",
"label.add.acl.rule": "Add rule",
"label.add.acl": "Add ACL",
@ -576,6 +577,12 @@
"label.clear.list": "Clear list",
"label.clear.notification": "Clear notification",
"label.clientid": "Provider Client ID",
"label.clone.backup.offering": "Clone Backup Offering",
"label.clone.compute.offering": "Clone Compute Offering",
"label.clone.disk.offering": "Clone Disk Offering",
"label.clone.network.offering": "Clone Network Offering",
"label.clone.system.service.offering": "Clone System Service Offering",
"label.clone.vpc.offering": "Clone VPC Offering",
"label.close": "Close",
"label.cloud.managed": "CloudManaged",
"label.cloudian.admin.password": "Admin Service Password",
@ -977,6 +984,7 @@
"label.domains": "Domains",
"label.done": "Done",
"label.down": "Down",
"label.dropservices": "Drop Services",
"label.download": "Download",
"label.download.csv": "Download CSV",
"label.download.kubeconfig.cluster": "Download kubeconfig for the cluster <br><br> The <code><b>kubectl</b></code> command-line tool uses kubeconfig files to find the information it needs to choose a cluster and communicate with the API server of a cluster.",
@ -3299,6 +3307,10 @@
"message.create.bucket.failed": "Failed to create bucket.",
"message.create.bucket.processing": "Bucket creation in progress",
"message.create.compute.offering": "Compute Offering created",
"message.clone.compute.offering": "Compute Offering cloned",
"message.clone.service.offering": "Service Offering cloned",
"message.clone.offering.from": "Cloning from",
"message.clone.offering.edit.hint": "All values are pre-filled from the source offering. Edit any field to customize the new offering.",
"message.create.sharedfs.failed": "Failed to create Shared FileSystem.",
"message.create.sharedfs.processing": "Shared FileSystem creation in progress.",
"message.create.tungsten.public.network": "Create Tungsten-Fabric public Network",
@ -3419,6 +3431,9 @@
"message.disable.webhook.ssl.verification": "Disabling SSL verification is not recommended",
"message.discovering.feature": "Discovering features, please wait...",
"message.disk.offering.created": "Disk offering created:",
"message.success.clone.backup.offering": "Successfully cloned backup offering",
"message.success.clone.disk.offering": "Successfully cloned disk offering:",
"message.success.clone.network.offering": "Successfully cloned network offering:",
"message.disk.usage.info.data.points": "Each data point represents the difference in read/write data since the last data point.",
"message.disk.usage.info.sum.of.disks": "The disk usage shown is made up of the sum of read/write data from all the disks in the Instance.",
"message.download.volume": "Please click the link to download the volume:<p><a href=\"#\">00000</a>",

View File

@ -74,6 +74,10 @@ export default {
type: Boolean,
default: false
},
defaultSelectValue: {
type: String,
default: null
},
selectOptions: {
type: Array,
required: true
@ -100,6 +104,9 @@ export default {
},
created () {
this.checked = this.defaultCheckBoxValue
if (this.defaultSelectValue) {
this.selectedOption = this.defaultSelectValue
}
},
watch: {
selectOptions () {

View File

@ -0,0 +1,812 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<a-form
ref="internalFormRef"
:model="form"
:rules="rules"
@finish="onInternalSubmit"
layout="vertical">
<a-form-item name="name" ref="name">
<template #label>
<tooltip-label :title="$t('label.name')" :tooltip="apiParams.name.description"/>
</template>
<a-input
v-focus="true"
v-model:value="form.name"
:placeholder="$t('label.name')"/>
</a-form-item>
<a-form-item name="displaytext" ref="displaytext">
<template #label>
<tooltip-label :title="$t('label.displaytext')" :tooltip="apiParams.displaytext.description"/>
</template>
<a-input
v-model:value="form.displaytext"
:placeholder="$t('label.displaytext')"/>
</a-form-item>
<a-form-item name="systemvmtype" ref="systemvmtype" v-if="isSystem">
<template #label>
<tooltip-label :title="$t('label.systemvmtype')" :tooltip="apiParams.systemvmtype.description"/>
</template>
<a-select
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.systemvmtype"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => { return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 }"
:placeholder="apiParams.systemvmtype.description">
<a-select-option key="domainrouter" :label="$t('label.domain.router')">{{ $t('label.domain.router') }}</a-select-option>
<a-select-option key="consoleproxy" :label="$t('label.console.proxy')">{{ $t('label.console.proxy') }}</a-select-option>
<a-select-option key="secondarystoragevm" :label="$t('label.secondary.storage.vm')">{{ $t('label.secondary.storage.vm') }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="offeringtype" ref="offeringtype" :label="$t('label.offeringtype')" v-show="!isSystem">
<a-radio-group
v-model:value="form.offeringtype"
@change="selected => { handleComputeOfferingTypeChange(selected.target.value) }"
buttonStyle="solid">
<a-radio-button value="fixed">{{ $t('label.fixed') }}</a-radio-button>
<a-radio-button value="customconstrained">{{ $t('label.customconstrained') }}</a-radio-button>
<a-radio-button value="customunconstrained">{{ $t('label.customunconstrained') }}</a-radio-button>
</a-radio-group>
</a-form-item>
<a-row :gutter="12">
<a-col :md="8" :lg="8" v-if="offeringType === 'fixed'">
<a-form-item name="cpunumber" ref="cpunumber">
<template #label>
<tooltip-label :title="$t('label.cpunumber')" :tooltip="apiParams.cpunumber.description"/>
</template>
<a-input v-model:value="form.cpunumber" :placeholder="apiParams.cpunumber.description"/>
</a-form-item>
</a-col>
<a-col :md="8" :lg="8" v-if="offeringType !== 'customunconstrained'">
<a-form-item name="cpuspeed" ref="cpuspeed">
<template #label>
<tooltip-label :title="$t('label.cpuspeed')" :tooltip="apiParams.cpuspeed.description"/>
</template>
<a-input v-model:value="form.cpuspeed" :placeholder="apiParams.cpuspeed.description"/>
</a-form-item>
</a-col>
<a-col :md="8" :lg="8" v-if="offeringType === 'fixed'">
<a-form-item name="memory" ref="memory">
<template #label>
<tooltip-label :title="$t('label.memory.mb')" :tooltip="apiParams.memory.description"/>
</template>
<a-input v-model:value="form.memory" :placeholder="apiParams.memory.description"/>
</a-form-item>
</a-col>
</a-row>
<!-- The rest of the form fields (storage, QoS, GPU, tags, etc.) were copied
from AddComputeOffering.vue to ensure identical behavior. -->
<a-row :gutter="12" v-if="offeringType === 'customconstrained'">
<a-col :md="12" :lg="12">
<a-form-item name="mincpunumber" ref="mincpunumber">
<template #label>
<tooltip-label :title="$t('label.mincpunumber')" :tooltip="apiParams.mincpunumber.description"/>
</template>
<a-input v-model:value="form.mincpunumber" :placeholder="apiParams.mincpunumber.description"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="maxcpunumber" ref="maxcpunumber">
<template #label>
<tooltip-label :title="$t('label.maxcpunumber')" :tooltip="apiParams.maxcpunumber.description"/>
</template>
<a-input v-model:value="form.maxcpunumber" :placeholder="apiParams.maxcpunumber.description"/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12" v-if="offeringType === 'customconstrained'">
<a-col :md="12" :lg="12">
<a-form-item name="minmemory" ref="minmemory">
<template #label>
<tooltip-label :title="$t('label.minmemory')" :tooltip="apiParams.minmemory.description"/>
</template>
<a-input v-model:value="form.minmemory" :placeholder="apiParams.minmemory.description"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="maxmemory" ref="maxmemory">
<template #label>
<tooltip-label :title="$t('label.maxmemory')" :tooltip="apiParams.maxmemory.description"/>
</template>
<a-input v-model:value="form.maxmemory" :placeholder="apiParams.maxmemory.description"/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :md="12" :lg="12">
<a-form-item v-if="isAdmin() || isDomainAdminAllowedToInformTags" name="hosttags" ref="hosttags">
<template #label>
<tooltip-label :title="$t('label.hosttags')" :tooltip="apiParams.hosttags.description"/>
</template>
<a-input v-model:value="form.hosttags" :placeholder="apiParams.hosttags.description"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="networkrate" ref="networkrate">
<template #label>
<tooltip-label :title="$t('label.networkrate')" :tooltip="apiParams.networkrate.description"/>
</template>
<a-input v-model:value="form.networkrate" :placeholder="apiParams.networkrate.description"/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :md="12" :lg="12">
<a-form-item name="offerha" ref="offerha">
<template #label>
<tooltip-label :title="$t('label.offerha')" :tooltip="apiParams.offerha.description"/>
</template>
<a-switch v-model:checked="form.offerha" />
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="dynamicscalingenabled" ref="dynamicscalingenabled">
<template #label>
<tooltip-label :title="$t('label.dynamicscalingenabled')" :tooltip="apiParams.dynamicscalingenabled.description"/>
</template>
<a-switch v-model:checked="form.dynamicscalingenabled" @change="val => { dynamicscalingenabled = val }"/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :md="12" :lg="12">
<a-form-item name="limitcpuuse" ref="limitcpuuse">
<template #label>
<tooltip-label :title="$t('label.limitcpuuse')" :tooltip="apiParams.limitcpuuse.description"/>
</template>
<a-switch v-model:checked="form.limitcpuuse" />
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item v-if="!isSystem" name="isvolatile" ref="isvolatile">
<template #label>
<tooltip-label :title="$t('label.isvolatile')" :tooltip="apiParams.isvolatile.description"/>
</template>
<a-switch v-model:checked="form.isvolatile" />
</a-form-item>
</a-col>
</a-row>
<a-form-item name="deploymentplanner" ref="deploymentplanner" v-if="!isSystem && isAdmin()">
<template #label>
<tooltip-label :title="$t('label.deploymentplanner')" :tooltip="apiParams.deploymentplanner.description"/>
</template>
<a-select
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.deploymentplanner"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => { return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 }"
:loading="deploymentPlannerLoading"
:placeholder="apiParams.deploymentplanner.description"
@change="val => { handleDeploymentPlannerChange(val) }">
<a-select-option v-for="(opt) in deploymentPlanners" :key="opt.name" :label="opt.name || opt.description || ''">{{ opt.name || opt.description }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="plannermode" ref="plannermode" :label="$t('label.plannermode')" v-if="plannerModeVisible">
<a-radio-group v-model:value="form.plannermode" buttonStyle="solid">
<a-radio-button value="">{{ $t('label.none') }}</a-radio-button>
<a-radio-button value="strict">{{ $t('label.strict') }}</a-radio-button>
<a-radio-button value="Preferred">{{ $t('label.preferred') }}</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item name="gpucardid" ref="gpucardid" :label="$t('label.gpu.card')" v-if="!isSystem">
<a-select
v-model:value="form.gpucardid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => { return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 }"
:loading="gpuCardLoading"
:placeholder="$t('label.gpu.card')"
@change="handleGpuCardChange">
<a-select-option v-for="(opt, optIndex) in gpuCards" :key="optIndex" :value="opt.id" :label="opt.name || opt.description || ''">{{ opt.description || opt.name || '' }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="vgpuprofile" ref="vgpuprofile" :label="$t('label.vgpu.profile')" v-if="!isSystem && form.gpucardid && vgpuProfiles.length > 0">
<a-select
v-model:value="form.vgpuprofile"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => { return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 }"
:loading="vgpuProfileLoading"
:placeholder="$t('label.vgpu.profile')">
<a-select-option v-for="(vgpu, vgpuIndex) in vgpuProfiles" :key="vgpuIndex" :value="vgpu.id" :label="vgpu.vgpuprofile || ''">{{ vgpu.name }} {{ getVgpuProfileDetails(vgpu) }}</a-select-option>
</a-select>
</a-form-item>
<a-row :gutter="12" v-if="!isSystem && form.gpucardid">
<a-col :md="12" :lg="12">
<a-form-item name="gpucount" ref="gpucount">
<template #label>
<tooltip-label :title="$t('label.gpu.count')" :tooltip="apiParams.gpucount.description"/>
</template>
<a-input v-model:value="form.gpucount" type="number" min="1" max="16" :placeholder="$t('label.gpu.count')"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="gpudisplay" ref="gpudisplay">
<template #label>
<tooltip-label :title="$t('label.gpu.display')" :tooltip="apiParams.gpudisplay.description"/>
</template>
<a-switch v-model:checked="form.gpudisplay" />
</a-form-item>
</a-col>
</a-row>
<a-form-item name="ispublic" ref="ispublic" :label="$t('label.ispublic')" v-show="isAdmin()">
<a-switch v-model:checked="form.ispublic" />
</a-form-item>
<a-form-item name="domainid" ref="domainid" v-if="!form.ispublic">
<template #label>
<tooltip-label :title="$t('label.domainid')" :tooltip="apiParams.domainid.description"/>
</template>
<a-select
mode="multiple"
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.domainid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => { return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 }"
:loading="domainLoading"
:placeholder="apiParams.domainid.description">
<a-select-option v-for="(opt, optIndex) in domains" :key="optIndex" :label="opt.path || opt.name || opt.description">
<span>
<resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
<block-outlined v-else style="margin-right: 5px" />
{{ opt.path || opt.name || opt.description }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="zoneid" ref="zoneid" v-if="!isSystem">
<template #label>
<tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid.description"/>
</template>
<a-select
id="zone-selection"
mode="multiple"
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.zoneid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => { return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 }"
@select="val => fetchvSphereStoragePolicies(val)"
:loading="zoneLoading"
:placeholder="apiParams.zoneid.description">
<a-select-option v-for="(opt, optIndex) in zones" :key="optIndex" :label="opt.name || opt.description">
<span>
<resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px"/>
{{ opt.name || opt.description }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="storagepolicy" ref="storagepolicy" v-if="'listVsphereStoragePolicies' in $store.getters.apis && storagePolicies !== null">
<template #label>
<tooltip-label :title="$t('label.vmware.storage.policy')" :tooltip="apiParams.storagepolicy.description"/>
</template>
<a-select
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.storagepolicy"
:placeholder="apiParams.storagepolicy.description"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => { return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 }">
<a-select-option v-for="policy in storagePolicies" :key="policy.id" :label="policy.name || policy.id || ''">{{ policy.name || policy.id }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="purgeresources" ref="purgeresources">
<template #label>
<tooltip-label :title="$t('label.purgeresources')" :tooltip="apiParams.purgeresources.description"/>
</template>
<a-switch v-model:checked="form.purgeresources"/>
</a-form-item>
<a-form-item name="showLeaseOptions" ref="showLeaseOptions" v-if="isLeaseFeatureEnabled">
<template #label>
<tooltip-label :title="$t('label.lease.enable')" :tooltip="$t('label.lease.enable.tooltip')" />
</template>
<a-switch v-model:checked="showLeaseOptions" @change="onToggleLeaseData"/>
</a-form-item>
<a-row :gutter="12" v-if="isLeaseFeatureEnabled && showLeaseOptions">
<a-col :md="12" :lg="12">
<a-form-item name="leaseduration" ref="leaseduration">
<template #label>
<tooltip-label :title="$t('label.leaseduration')"/>
</template>
<a-input v-model:value="form.leaseduration" :placeholder="$t('label.instance.lease.placeholder')"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="leaseexpiryaction" ref="leaseexpiryaction" v-if="form.leaseduration > 0">
<template #label>
<tooltip-label :title="$t('label.leaseexpiryaction')" />
</template>
<a-select v-model:value="form.leaseexpiryaction" :defaultValue="expiryActions">
<a-select-option v-for="action in expiryActions" :key="action" :label="action"/>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item name="computeonly" ref="computeonly">
<template #label>
<tooltip-label :title="$t('label.computeonly.offering')" :tooltip="$t('label.computeonly.offering.tooltip')"/>
</template>
<a-switch v-model:checked="form.computeonly" :checked="computeonly" @change="val => { computeonly = val }"/>
</a-form-item>
<a-card style="margin-bottom: 10px;">
<span v-if="computeonly">
<a-form-item name="storagetype" ref="storagetype">
<template #label>
<tooltip-label :title="$t('label.storagetype')" :tooltip="apiParams.storagetype.description"/>
</template>
<a-radio-group v-model:value="form.storagetype" buttonStyle="solid" @change="selected => { handleStorageTypeChange(selected.target.value) }">
<a-radio-button value="shared">{{ $t('label.shared') }}</a-radio-button>
<a-radio-button value="local">{{ $t('label.local') }}</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item name="provisioningtype" ref="provisioningtype">
<template #label>
<tooltip-label :title="$t('label.provisioningtype')" :tooltip="apiParams.provisioningtype.description"/>
</template>
<a-radio-group v-model:value="form.provisioningtype" buttonStyle="solid" @change="selected => { handleProvisioningTypeChange(selected.target.value) }">
<a-radio-button value="thin">{{ $t('label.provisioningtype.thin') }}</a-radio-button>
<a-radio-button value="sparse">{{ $t('label.provisioningtype.sparse') }}</a-radio-button>
<a-radio-button value="fat">{{ $t('label.provisioningtype.fat') }}</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item name="cachemode" ref="cachemode">
<template #label>
<tooltip-label :title="$t('label.cachemode')" :tooltip="apiParams.cachemode.description"/>
</template>
<a-radio-group v-model:value="form.cachemode" buttonStyle="solid" @change="selected => { handleCacheModeChange(selected.target.value) }">
<a-radio-button value="none">{{ $t('label.nodiskcache') }}</a-radio-button>
<a-radio-button value="writeback">{{ $t('label.writeback') }}</a-radio-button>
<a-radio-button value="writethrough">{{ $t('label.writethrough') }}</a-radio-button>
<a-radio-button value="hypervisor_default">{{ $t('label.hypervisor.default') }}</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item :label="$t('label.qostype')" name="qostype" ref="qostype">
<a-radio-group v-model:value="form.qostype" buttonStyle="solid" @change="selected => { handleQosTypeChange(selected.target.value) }">
<a-radio-button value="">{{ $t('label.none') }}</a-radio-button>
<a-radio-button value="hypervisor">{{ $t('label.hypervisor') }}</a-radio-button>
<a-radio-button value="storage">{{ $t('label.storage') }}</a-radio-button>
</a-radio-group>
</a-form-item>
<a-row :gutter="12" v-if="qosType === 'hypervisor'">
<a-col :md="12" :lg="12">
<a-form-item name="diskbytesreadrate" ref="diskbytesreadrate">
<template #label>
<tooltip-label :title="$t('label.diskbytesreadrate')" :tooltip="apiParams.bytesreadrate.description"/>
</template>
<a-input v-model:value="form.diskbytesreadrate" :placeholder="apiParams.bytesreadrate.description"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="diskbyteswriterate" ref="diskbyteswriterate">
<template #label>
<tooltip-label :title="$t('label.diskbyteswriterate')" :tooltip="apiParams.byteswriterate.description"/>
</template>
<a-input v-model:value="form.diskbyteswriterate" :placeholder="apiParams.byteswriterate.description"/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12" v-if="qosType === 'hypervisor'">
<a-col :md="12" :lg="12">
<a-form-item name="diskiopsreadrate" ref="diskiopsreadrate">
<template #label>
<tooltip-label :title="$t('label.diskiopsreadrate')" :tooltip="apiParams.iopsreadrate.description"/>
</template>
<a-input v-model:value="form.diskiopsreadrate" :placeholder="apiParams.iopsreadrate.description"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="diskiopswriterate" ref="diskiopswriterate">
<template #label>
<tooltip-label :title="$t('label.diskiopswriterate')" :tooltip="apiParams.iopswriterate.description"/>
</template>
<a-input v-model:value="form.diskiopswriterate" :placeholder="apiParams.iopswriterate.description"/>
</a-form-item>
</a-col>
</a-row>
<a-form-item v-if="!isSystem && qosType === 'storage'" name="iscustomizeddiskiops" ref="iscustomizeddiskiops">
<template #label>
<tooltip-label :title="$t('label.iscustomizeddiskiops')" :tooltip="apiParams.customizediops.description"/>
</template>
<a-switch v-model:checked="form.iscustomizeddiskiops" :checked="isCustomizedDiskIops" @change="val => { isCustomizedDiskIops = val }" />
</a-form-item>
<a-row :gutter="12" v-if="qosType === 'storage' && !isCustomizedDiskIops">
<a-col :md="12" :lg="12">
<a-form-item name="diskiopsmin" ref="diskiopsmin">
<template #label>
<tooltip-label :title="$t('label.diskiopsmin')" :tooltip="apiParams.miniops.description"/>
</template>
<a-input v-model:value="form.diskiopsmin" :placeholder="apiParams.miniops.description"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="diskiopsmax" ref="diskiopsmax">
<template #label>
<tooltip-label :title="$t('label.diskiopsmax')" :tooltip="apiParams.maxiops.description"/>
</template>
<a-input v-model:value="form.diskiopsmax" :placeholder="apiParams.maxiops.description"/>
</a-form-item>
</a-col>
</a-row>
<a-form-item v-if="!isSystem && qosType === 'storage'" name="hypervisorsnapshotreserve" ref="hypervisorsnapshotreserve">
<template #label>
<tooltip-label :title="$t('label.hypervisorsnapshotreserve')" :tooltip="apiParams.hypervisorsnapshotreserve.description"/>
</template>
<a-input v-model:value="form.hypervisorsnapshotreserve" :placeholder="apiParams.hypervisorsnapshotreserve.description"/>
</a-form-item>
<a-row :gutter="12">
<a-col :md="12" :lg="12">
<a-form-item v-if="apiParams.rootdisksize" name="rootdisksize" ref="rootdisksize">
<template #label>
<tooltip-label :title="$t('label.root.disk.size')" :tooltip="apiParams.rootdisksize.description"/>
</template>
<a-input v-model:value="form.rootdisksize" :placeholder="apiParams.rootdisksize.description"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item v-if="isAdmin() || isDomainAdminAllowedToInformTags" name="storagetags" ref="storagetags">
<template #label>
<tooltip-label :title="$t('label.storagetags')" :tooltip="apiParams.tags.description"/>
</template>
<a-select
mode="tags"
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.storagetags"
showSearch
optionFilterProp="value"
:filterOption="(input, option) => { return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0 }"
:loading="storageTagLoading"
:placeholder="apiParams.tags.description"
v-if="isAdmin() || isDomainAdminAllowedToInformTags">
<a-select-option v-for="opt in storageTags" :key="opt">{{ opt }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item name="encryptdisk" ref="encryptdisk">
<template #label>
<tooltip-label :title="$t('label.encrypt')" :tooltip="apiParams.encryptroot.description" />
</template>
<a-switch v-model:checked="form.encryptdisk" :checked="encryptdisk" @change="val => { encryptdisk = val }" />
</a-form-item>
</span>
<span v-if="!computeonly">
<a-form-item>
<a-button type="primary" @click="addDiskOffering()"> {{ $t('label.add.disk.offering') }} </a-button>
<a-modal
:visible="showDiskOfferingModal"
:title="$t('label.add.disk.offering')"
:footer="null"
centered
:closable="true"
@cancel="closeDiskOfferingModal"
width="auto">
<add-disk-offering @close-action="closeDiskOfferingModal()" @publish-disk-offering-id="($event) => updateSelectedDiskOffering($event)"/>
</a-modal>
<br /><br />
<a-form-item :label="$t('label.disk.offerings')" name="diskofferingid" ref="diskofferingid">
<a-select :getPopupContainer="(trigger) => trigger.parentNode" v-model:value="form.diskofferingid" :loading="loading" :placeholder="$t('label.diskoffering')">
<a-select-option v-for="(offering, index) in diskOfferings" :value="offering.id" :key="index">{{ offering.displaytext || offering.name }}</a-select-option>
</a-select>
</a-form-item>
</a-form-item>
</span>
<a-form-item name="diskofferingstrictness" ref="diskofferingstrictness">
<template #label>
<tooltip-label :title="$t('label.diskofferingstrictness')" :tooltip="apiParams.diskofferingstrictness.description"/>
</template>
<a-switch v-model:checked="form.diskofferingstrictness" :checked="diskofferingstrictness" @change="val => { diskofferingstrictness = val }"/>
</a-form-item>
</a-card>
<a-form-item name="externaldetails" ref="externaldetails">
<template #label>
<tooltip-label :title="$t('label.externaldetails')" :tooltip="apiParams.externaldetails.description"/>
</template>
<a-switch v-model:checked="externalDetailsEnabled" @change="onExternalDetailsEnabledChange"/>
<a-card v-if="externalDetailsEnabled" style="margin-top: 10px">
<div style="margin-bottom: 10px">{{ $t('message.add.orchestrator.resource.details') }}</div>
<details-input v-model:value="form.externaldetails" />
</a-card>
</a-form-item>
<slot name="form-actions"></slot>
</a-form>
</template>
<script>
import { reactive, toRaw } from 'vue'
import { getAPI } from '@/api'
import AddDiskOffering from '@/views/offering/AddDiskOffering'
import { isAdmin } from '@/role'
import { mixinForm } from '@/utils/mixin'
import ResourceIcon from '@/components/view/ResourceIcon'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import DetailsInput from '@/components/widgets/DetailsInput'
import store from '@/store'
export default {
name: 'ComputeOfferingForm',
mixins: [mixinForm],
components: {
AddDiskOffering,
ResourceIcon,
TooltipLabel,
DetailsInput
},
props: {
initialValues: {
type: Object,
default: () => ({})
},
apiParams: {
type: Object,
default: () => ({})
},
isSystem: {
type: Boolean,
default: false
},
isAdmin: {
type: Function,
default: () => false
}
},
data () {
return {
internalFormRef: null,
form: reactive(Object.assign({
systemvmtype: 'domainrouter',
offeringtype: 'fixed',
ispublic: true,
dynamicscalingenabled: true,
plannermode: '',
gpucardid: '',
vgpuprofile: '',
gpucount: '1',
gpudisplay: false,
computeonly: true,
storagetype: 'shared',
provisioningtype: 'thin',
cachemode: 'none',
qostype: '',
iscustomizeddiskiops: false,
diskofferingid: null,
diskofferingstrictness: false,
encryptdisk: false,
leaseduration: undefined,
leaseexpiryaction: undefined
}, this.initialValues || {})),
rules: reactive({}),
// other UI state copied
storageType: 'shared',
provisioningType: 'thin',
cacheMode: 'none',
offeringType: 'fixed',
isCustomizedDiskIops: false,
isPublic: true,
domains: [],
domainLoading: false,
zones: [],
zoneLoading: false,
selectedDeploymentPlanner: null,
storagePolicies: null,
storageTags: [],
storageTagLoading: false,
deploymentPlanners: [],
deploymentPlannerLoading: false,
plannerModeVisible: false,
plannerMode: '',
selectedGpuCard: '',
showDiskOfferingModal: false,
gpuCardLoading: false,
gpuCards: [],
loading: false,
dynamicscalingenabled: true,
diskofferingstrictness: false,
encryptdisk: false,
computeonly: true,
diskOfferingLoading: false,
diskOfferings: [],
selectedDiskOfferingId: '',
qosType: '',
isDomainAdminAllowedToInformTags: false,
isLeaseFeatureEnabled: this.$store.getters.features.instanceleaseenabled,
showLeaseOptions: false,
expiryActions: ['STOP', 'DESTROY'],
defaultLeaseDuration: 90,
defaultLeaseExpiryAction: 'STOP',
leaseduration: undefined,
leaseexpiryaction: undefined,
vgpuProfiles: [],
vgpuProfileLoading: false,
externalDetailsEnabled: false
}
},
created () {
this.zones = [{ id: null, name: this.$t('label.all.zone') }]
this.initForm()
this.fetchData()
this.isPublic = isAdmin()
this.form.ispublic = this.isPublic
},
methods: {
initForm () {
this.formRef = this.$refs.internalFormRef
this.rules = reactive({
name: [{ required: true, message: this.$t('message.error.required.input') }]
})
},
fetchData () {
this.fetchDomainData()
this.fetchZoneData()
this.fetchGPUCards()
if (isAdmin()) {
this.fetchStorageTagData()
this.fetchDeploymentPlannerData()
} else if (this.isDomainAdmin()) {
this.checkIfDomainAdminIsAllowedToInformTag()
if (this.isDomainAdminAllowedToInformTags) {
this.fetchStorageTagData()
}
}
this.fetchDiskOfferings()
},
fetchGPUCards () {
this.gpuCardLoading = true
getAPI('listGpuCards', {
}).then(json => {
this.gpuCards = json.listgpucardsresponse.gpucard || []
this.gpuCards.unshift({ id: '', name: this.$t('label.none') })
}).finally(() => {
this.gpuCardLoading = false
})
},
addDiskOffering () { this.showDiskOfferingModal = true },
fetchDiskOfferings () {
this.diskOfferingLoading = true
getAPI('listDiskOfferings', { listall: true }).then(json => {
this.diskOfferings = json.listdiskofferingsresponse.diskoffering || []
if (this.selectedDiskOfferingId === '') {
this.selectedDiskOfferingId = this.diskOfferings[0]?.id || ''
}
}).finally(() => { this.diskOfferingLoading = false })
},
updateSelectedDiskOffering (id) { if (id) this.selectedDiskOfferingId = id },
closeDiskOfferingModal () { this.fetchDiskOfferings(); this.showDiskOfferingModal = false },
isDomainAdmin () {
return ['DomainAdmin'].includes(this.$store.getters.userInfo.roletype)
},
getVgpuProfileDetails (vgpuProfile) {
let output = '('
if (vgpuProfile?.videoram) output += `${vgpuProfile.videoram} MB`
if (vgpuProfile?.maxresolutionx && vgpuProfile?.maxresolutiony) {
if (output !== '(') output += ', '
output += `${vgpuProfile.maxresolutionx}x${vgpuProfile.maxresolutiony}`
}
output += ')'
return output === '()' ? '' : output
},
checkIfDomainAdminIsAllowedToInformTag () {
const params = { id: store.getters.userInfo.accountid }
getAPI('isAccountAllowedToCreateOfferingsWithTags', params).then(json => {
this.isDomainAdminAllowedToInformTags = json.isaccountallowedtocreateofferingswithtagsresponse.isallowed.isallowed
})
},
arrayHasItems (array) { return array !== null && array !== undefined && Array.isArray(array) && array.length > 0 },
fetchDomainData () {
const params = { listAll: true, showicon: true, details: 'min' }
this.domainLoading = true
getAPI('listDomains', params).then(json => {
const listDomains = json.listdomainsresponse.domain
this.domains = this.domains.concat(listDomains)
}).finally(() => { this.domainLoading = false })
},
fetchZoneData () {
const params = { showicon: true }
this.zoneLoading = true
getAPI('listZones', params).then(json => {
const listZones = json.listzonesresponse.zone
if (listZones) this.zones = this.zones.concat(listZones)
}).finally(() => { this.zoneLoading = false })
},
fetchStorageTagData () {
this.storageTagLoading = true
this.storageTags = []
getAPI('listStorageTags').then(json => {
const tags = json.liststoragetagsresponse.storagetag || []
for (const tag of tags) if (!this.storageTags.includes(tag.name)) this.storageTags.push(tag.name)
}).finally(() => { this.storageTagLoading = false })
},
fetchDeploymentPlannerData () {
this.deploymentPlannerLoading = true
getAPI('listDeploymentPlanners').then(json => {
const planners = json.listdeploymentplannersresponse.deploymentPlanner
this.deploymentPlanners = this.deploymentPlanners.concat(planners)
this.deploymentPlanners.unshift({ name: '' })
this.form.deploymentplanner = this.deploymentPlanners.length > 0 ? this.deploymentPlanners[0].name : ''
}).finally(() => { this.deploymentPlannerLoading = false })
},
fetchvSphereStoragePolicies (zoneIndex) {
if (zoneIndex === 0 || this.form.zoneid.length > 1) { this.storagePolicies = null; return }
const zoneid = this.zones[zoneIndex].id
if ('importVsphereStoragePolicies' in this.$store.getters.apis) {
this.storagePolicies = []
getAPI('listVsphereStoragePolicies', { zoneid }).then(response => { this.storagePolicies = response.listvspherestoragepoliciesresponse.StoragePolicy || [] })
}
},
handleStorageTypeChange (val) { this.storageType = val },
handleProvisioningTypeChange (val) { this.provisioningType = val },
handleCacheModeChange (val) { this.cacheMode = val },
handleComputeOfferingTypeChange (val) { this.offeringType = val },
handleQosTypeChange (val) { this.qosType = val },
handleDeploymentPlannerChange (planner) { this.selectedDeploymentPlanner = planner; this.plannerModeVisible = false; if (this.selectedDeploymentPlanner === 'ImplicitDedicationPlanner') this.plannerModeVisible = isAdmin() },
handleGpuCardChange (cardId) { this.selectedGpuCard = cardId; this.form.vgpuprofile = ''; if (cardId && cardId !== '') this.fetchVgpuProfiles(cardId); else { this.vgpuProfiles = []; this.form.gpucount = '1' } },
fetchVgpuProfiles (gpuCardId) { this.vgpuProfileLoading = true; this.vgpuProfiles = []; getAPI('listVgpuProfiles', { gpucardid: gpuCardId }).then(json => { this.vgpuProfiles = json.listvgpuprofilesresponse.vgpuprofile || []; this.form.vgpuprofile = this.vgpuProfiles.length > 0 ? this.vgpuProfiles[0].id : '' }).catch(() => { this.vgpuProfiles = [] }).finally(() => { this.vgpuProfileLoading = false }) },
onExternalDetailsEnabledChange (val) { if (val || !this.form.externaldetails) return; this.form.externaldetails = undefined },
onToggleLeaseData () { if (this.showLeaseOptions === false) { this.leaseduration = undefined; this.leaseexpiryaction = undefined } else { this.leaseduration = this.leaseduration !== undefined ? this.leaseduration : this.defaultLeaseDuration; this.leaseexpiryaction = this.leaseexpiryaction !== undefined ? this.leaseexpiryaction : this.defaultLeaseExpiryAction } this.form.leaseduration = this.leaseduration; this.form.leaseexpiryaction = this.leaseexpiryaction },
validate () {
return this.$refs.internalFormRef.validate().then(() => {
const formRaw = toRaw(this.form)
const values = this.handleRemoveFields(formRaw)
return values
})
},
onInternalSubmit (e) {
// When internal form triggers submit, validate and emit
this.validate().then(values => this.$emit('submit', values))
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,507 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<a-form
ref="internalFormRef"
:model="form"
:rules="rules"
@finish="onInternalSubmit"
layout="vertical">
<a-form-item name="name" ref="name">
<template #label>
<tooltip-label :title="$t('label.name')" :tooltip="apiParams.name.description"/>
</template>
<a-input
v-focus="true"
v-model:value="form.name"
:placeholder="apiParams.name.description"/>
</a-form-item>
<a-form-item name="displaytext" ref="displaytext">
<template #label>
<tooltip-label :title="$t('label.displaytext')" :tooltip="apiParams.displaytext.description"/>
</template>
<a-input
v-model:value="form.displaytext"
:placeholder="apiParams.displaytext.description"/>
</a-form-item>
<a-form-item name="storagetype" ref="storagetype">
<template #label>
<tooltip-label :title="$t('label.storagetype')" :tooltip="apiParams.storagetype.description"/>
</template>
<a-radio-group
v-model:value="form.storagetype"
buttonStyle="solid">
<a-radio-button value="shared">
{{ $t('label.shared') }}
</a-radio-button>
<a-radio-button value="local">
{{ $t('label.local') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item name="provisioningtype" ref="provisioningtype">
<template #label>
<tooltip-label :title="$t('label.provisioningtype')" :tooltip="apiParams.provisioningtype.description"/>
</template>
<a-radio-group
v-model:value="form.provisioningtype"
buttonStyle="solid">
<a-radio-button value="thin">
{{ $t('label.provisioningtype.thin') }}
</a-radio-button>
<a-radio-button value="sparse">
{{ $t('label.provisioningtype.sparse') }}
</a-radio-button>
<a-radio-button value="fat">
{{ $t('label.provisioningtype.fat') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item name="encryptdisk" ref="encryptdisk">
<template #label>
<tooltip-label :title="$t('label.encrypt')" :tooltip="apiParams.encrypt.description" />
</template>
<a-switch v-model:checked="form.encryptdisk" :checked="encryptdisk" @change="val => { encryptdisk = val }" />
</a-form-item>
<a-form-item name="disksizestrictness" ref="disksizestrictness">
<template #label>
<tooltip-label :title="$t('label.disksizestrictness')" :tooltip="apiParams.disksizestrictness.description" />
</template>
<a-switch v-model:checked="form.disksizestrictness" :checked="disksizestrictness" @change="val => { disksizestrictness = val }" />
</a-form-item>
<a-form-item name="customdisksize" ref="customdisksize">
<template #label>
<tooltip-label :title="$t('label.customdisksize')" :tooltip="apiParams.customized.description"/>
</template>
<a-switch v-model:checked="form.customdisksize" />
</a-form-item>
<a-form-item v-if="!form.customdisksize" name="disksize" ref="disksize">
<template #label>
<tooltip-label :title="$t('label.disksize')" :tooltip="apiParams.disksize.description"/>
</template>
<a-input
v-model:value="form.disksize"
:placeholder="apiParams.disksize.description"/>
</a-form-item>
<a-form-item name="qostype" ref="qostype" :label="$t('label.qostype')">
<a-radio-group
v-model:value="form.qostype"
buttonStyle="solid">
<a-radio-button value="">
{{ $t('label.none') }}
</a-radio-button>
<a-radio-button value="hypervisor">
{{ $t('label.hypervisor') }}
</a-radio-button>
<a-radio-button value="storage">
{{ $t('label.storage') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item v-if="form.qostype === 'hypervisor'" name="diskbytesreadrate" ref="diskbytesreadrate">
<template #label>
<tooltip-label :title="$t('label.diskbytesreadrate')" :tooltip="apiParams.bytesreadrate.description"/>
</template>
<a-input
v-model:value="form.diskbytesreadrate"
:placeholder="apiParams.bytesreadrate.description"/>
</a-form-item>
<a-form-item v-if="form.qostype === 'hypervisor'" name="diskbytesreadratemax" ref="diskbytesreadratemax">
<template #label>
<tooltip-label :title="$t('label.diskbytesreadratemax')" :tooltip="apiParams.bytesreadratemax.description"/>
</template>
<a-input
v-model:value="form.diskbytesreadratemax"
:placeholder="apiParams.bytesreadratemax.description"/>
</a-form-item>
<a-form-item v-if="form.qostype === 'hypervisor'" name="diskbyteswriterate" ref="diskbyteswriterate">
<template #label>
<tooltip-label :title="$t('label.diskbyteswriterate')" :tooltip="apiParams.byteswriterate.description"/>
</template>
<a-input
v-model:value="form.diskbyteswriterate"
:placeholder="apiParams.byteswriterate.description"/>
</a-form-item>
<a-form-item v-if="form.qostype === 'hypervisor'" name="diskbyteswriteratemax" ref="diskbyteswriteratemax">
<template #label>
<tooltip-label :title="$t('label.diskbyteswriteratemax')" :tooltip="apiParams.byteswriteratemax.description"/>
</template>
<a-input
v-model:value="form.diskbyteswriteratemax"
:placeholder="apiParams.byteswriteratemax.description"/>
</a-form-item>
<a-form-item v-if="form.qostype === 'hypervisor'" name="diskiopsreadrate" ref="diskiopsreadrate">
<template #label>
<tooltip-label :title="$t('label.diskiopsreadrate')" :tooltip="apiParams.iopsreadrate.description"/>
</template>
<a-input
v-model:value="form.diskiopsreadrate"
:placeholder="apiParams.iopsreadrate.description"/>
</a-form-item>
<a-form-item v-if="form.qostype === 'hypervisor'" name="diskiopswriterate" ref="diskiopswriterate">
<template #label>
<tooltip-label :title="$t('label.diskiopswriterate')" :tooltip="apiParams.iopswriterate.description"/>
</template>
<a-input
v-model:value="form.diskiopswriterate"
:placeholder="apiParams.iopswriterate.description"/>
</a-form-item>
<a-form-item v-if="form.qostype === 'storage'" name="iscustomizeddiskiops" ref="iscustomizeddiskiops">
<template #label>
<tooltip-label :title="$t('label.iscustomizeddiskiops')" :tooltip="apiParams.customizediops.description"/>
</template>
<a-switch v-model:checked="form.iscustomizeddiskiops" />
</a-form-item>
<a-form-item v-if="form.qostype === 'storage' && !form.iscustomizeddiskiops" name="diskiopsmin" ref="diskiopsmin">
<template #label>
<tooltip-label :title="$t('label.diskiopsmin')" :tooltip="apiParams.miniops.description"/>
</template>
<a-input
v-model:value="form.diskiopsmin"
:placeholder="apiParams.miniops.description"/>
</a-form-item>
<a-form-item v-if="form.qostype === 'storage' && !form.iscustomizeddiskiops" name="diskiopsmax" ref="diskiopsmax">
<template #label>
<tooltip-label :title="$t('label.diskiopsmax')" :tooltip="apiParams.maxiops.description"/>
</template>
<a-input
v-model:value="form.diskiopsmax"
:placeholder="apiParams.maxiops.description"/>
</a-form-item>
<a-form-item v-if="form.qostype === 'storage'" name="hypervisorsnapshotreserve" ref="hypervisorsnapshotreserve">
<template #label>
<tooltip-label :title="$t('label.hypervisorsnapshotreserve')" :tooltip="apiParams.hypervisorsnapshotreserve.description"/>
</template>
<a-input
v-model:value="form.hypervisorsnapshotreserve"
:placeholder="apiParams.hypervisorsnapshotreserve.description"/>
</a-form-item>
<a-form-item name="writecachetype" ref="writecachetype">
<template #label>
<tooltip-label :title="$t('label.writecachetype')" :tooltip="apiParams.cachemode.description"/>
</template>
<a-radio-group
v-model:value="form.writecachetype"
buttonStyle="solid"
@change="selected => { handleWriteCacheTypeChange(selected.target.value) }">
<a-radio-button value="none">
{{ $t('label.nodiskcache') }}
</a-radio-button>
<a-radio-button value="writeback">
{{ $t('label.writeback') }}
</a-radio-button>
<a-radio-button value="writethrough">
{{ $t('label.writethrough') }}
</a-radio-button>
<a-radio-button value="hypervisor_default">
{{ $t('label.hypervisor.default') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item v-if="isAdmin() || isDomainAdminAllowedToInformTags" name="tags" ref="tags">
<template #label>
<tooltip-label :title="$t('label.storagetags')" :tooltip="apiParams.tags.description"/>
</template>
<a-select
:getPopupContainer="(trigger) => trigger.parentNode"
mode="tags"
v-model:value="form.tags"
showSearch
optionFilterProp="value"
:filterOption="(input, option) => {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="storageTagLoading"
:placeholder="apiParams.tags.description"
v-if="isAdmin() || isDomainAdminAllowedToInformTags">
<a-select-option v-for="(opt) in storageTags" :key="opt">
{{ opt }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item :label="$t('label.ispublic')" v-show="isAdmin()" name="ispublic" ref="ispublic">
<a-switch v-model:checked="form.ispublic" @change="val => { isPublic = val }" />
</a-form-item>
<a-form-item v-if="!isPublic" name="domainid" ref="domainid">
<template #label>
<tooltip-label :title="$t('label.domainid')" :tooltip="apiParams.domainid.description"/>
</template>
<a-select
mode="multiple"
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.domainid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="domainLoading"
:placeholder="apiParams.domainid.description">
<a-select-option v-for="(opt, optIndex) in domains" :key="optIndex" :label="opt.path || opt.name || opt.description">
<span>
<resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
<block-outlined v-else style="margin-right: 5px" />
{{ opt.path || opt.name || opt.description }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="zoneid" ref="zoneid">
<template #label>
<tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid.description"/>
</template>
<a-select
id="zone-selection"
mode="multiple"
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.zoneid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
@select="val => fetchvSphereStoragePolicies(val)"
:loading="zoneLoading"
:placeholder="apiParams.zoneid.description">
<a-select-option v-for="(opt, optIndex) in zones" :key="optIndex" :label="opt.name || opt.description">
<span>
<resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px"/>
{{ opt.name || opt.description }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="'listVsphereStoragePolicies' in $store.getters.apis && storagePolicies !== null" name="storagepolicy" ref="storagepolicy">
<template #label>
<tooltip-label :title="$t('label.vmware.storage.policy')" :tooltip="apiParams.storagepolicy.description"/>
</template>
<a-select
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.storagepolicy"
:placeholder="apiParams.storagepolicy.description"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}">
<a-select-option v-for="policy in storagePolicies" :key="policy.id" :label="policy.name || policy.id || ''">
{{ policy.name || policy.id }}
</a-select-option>
</a-select>
</a-form-item>
<slot name="form-actions"></slot>
</a-form>
</template>
<script>
import { reactive, toRaw } from 'vue'
import { getAPI } from '@/api'
import { isAdmin } from '@/role'
import { mixinForm } from '@/utils/mixin'
import ResourceIcon from '@/components/view/ResourceIcon'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import store from '@/store'
import { BlockOutlined, GlobalOutlined } from '@ant-design/icons-vue'
export default {
name: 'DiskOfferingForm',
mixins: [mixinForm],
components: {
ResourceIcon,
TooltipLabel,
BlockOutlined,
GlobalOutlined
},
props: {
initialValues: {
type: Object,
default: () => ({})
},
apiParams: {
type: Object,
default: () => ({})
},
isAdmin: {
type: Function,
default: () => false
}
},
data () {
return {
internalFormRef: null,
form: reactive(Object.assign({
storagetype: 'shared',
provisioningtype: 'thin',
customdisksize: true,
writecachetype: 'none',
qostype: '',
ispublic: true,
disksizestrictness: false,
encryptdisk: false
}, this.initialValues || {})),
rules: reactive({}),
storageTags: [],
storagePolicies: null,
storageTagLoading: false,
isPublic: true,
domains: [],
domainLoading: false,
zones: [],
zoneLoading: false,
disksizestrictness: false,
encryptdisk: false,
isDomainAdminAllowedToInformTags: false
}
},
created () {
this.zones = [{ id: null, name: this.$t('label.all.zone') }]
this.initForm()
this.fetchData()
this.isPublic = isAdmin()
this.form.ispublic = this.isPublic
},
methods: {
initForm () {
this.formRef = this.$refs.internalFormRef
this.rules = reactive({
name: [{ required: true, message: this.$t('message.error.required.input') }],
disksize: [
{ required: true, message: this.$t('message.error.required.input') },
{ type: 'number', validator: this.validateNumber }
],
diskbytesreadrate: [{ type: 'number', validator: this.validateNumber }],
diskbytesreadratemax: [{ type: 'number', validator: this.validateNumber }],
diskbyteswriterate: [{ type: 'number', validator: this.validateNumber }],
diskbyteswriteratemax: [{ type: 'number', validator: this.validateNumber }],
diskiopsreadrate: [{ type: 'number', validator: this.validateNumber }],
diskiopswriterate: [{ type: 'number', validator: this.validateNumber }],
diskiopsmin: [{ type: 'number', validator: this.validateNumber }],
diskiopsmax: [{ type: 'number', validator: this.validateNumber }],
hypervisorsnapshotreserve: [{ type: 'number', validator: this.validateNumber }],
domainid: [{ type: 'array', required: true, message: this.$t('message.error.select') }],
zoneid: [{
type: 'array',
validator: async (rule, value) => {
if (value && value.length > 1 && value.indexOf(0) !== -1) {
return Promise.reject(this.$t('message.error.zone.combined'))
}
return Promise.resolve()
}
}]
})
},
fetchData () {
this.fetchDomainData()
this.fetchZoneData()
if (isAdmin()) {
this.fetchStorageTagData()
}
if (this.isDomainAdmin()) {
this.checkIfDomainAdminIsAllowedToInformTag()
if (this.isDomainAdminAllowedToInformTags) {
this.fetchStorageTagData()
}
}
},
handleWriteCacheTypeChange (val) {
this.form.writecachetype = val
},
isDomainAdmin () {
return ['DomainAdmin'].includes(this.$store.getters.userInfo.roletype)
},
checkIfDomainAdminIsAllowedToInformTag () {
const params = { id: store.getters.userInfo.accountid }
getAPI('isAccountAllowedToCreateOfferingsWithTags', params).then(json => {
this.isDomainAdminAllowedToInformTags = json.isaccountallowedtocreateofferingswithtagsresponse.isallowed.isallowed
})
},
fetchDomainData () {
const params = {}
params.listAll = true
params.showicon = true
params.details = 'min'
this.domainLoading = true
getAPI('listDomains', params).then(json => {
const listDomains = json.listdomainsresponse.domain
this.domains = this.domains.concat(listDomains)
}).finally(() => {
this.domainLoading = false
})
},
fetchZoneData () {
const params = {}
params.showicon = true
this.zoneLoading = true
getAPI('listZones', params).then(json => {
const listZones = json.listzonesresponse.zone
if (listZones) {
this.zones = this.zones.concat(listZones)
}
}).finally(() => {
this.zoneLoading = false
})
},
fetchStorageTagData () {
const params = {}
this.storageTagLoading = true
getAPI('listStorageTags', params).then(json => {
const tags = json.liststoragetagsresponse.storagetag || []
for (const tag of tags) {
if (!this.storageTags.includes(tag.name)) {
this.storageTags.push(tag.name)
}
}
}).finally(() => {
this.storageTagLoading = false
})
},
fetchvSphereStoragePolicies (zoneIndex) {
if (zoneIndex === 0 || this.form.zoneid.length > 1) {
this.storagePolicies = null
return
}
const zoneid = this.zones[zoneIndex].id
if ('importVsphereStoragePolicies' in this.$store.getters.apis) {
this.storagePolicies = []
getAPI('listVsphereStoragePolicies', {
zoneid: zoneid
}).then(response => {
this.storagePolicies = response.listvspherestoragepoliciesresponse.StoragePolicy || []
})
}
},
validate () {
return this.$refs.internalFormRef.validate().then(() => {
const formRaw = toRaw(this.form)
const values = this.handleRemoveFields(formRaw)
return values
})
},
onInternalSubmit () {
this.$emit('submit')
},
async validateNumber (rule, value) {
if (value && (isNaN(value) || value <= 0)) {
return Promise.reject(this.$t('message.error.number'))
}
return Promise.resolve()
}
}
}
</script>

View File

@ -0,0 +1,153 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
export function buildServiceCapabilityParams (params, values, selectedServiceProviderMap, registeredServicePackages) {
const supportedServices = Object.keys(selectedServiceProviderMap)
params.supportedservices = supportedServices.join(',')
for (const k in supportedServices) {
params[`serviceProviderList[${k}].service`] = supportedServices[k]
params[`serviceProviderList[${k}].provider`] = selectedServiceProviderMap[supportedServices[k]]
}
let serviceCapabilityIndex = 0
if (supportedServices.includes('Connectivity')) {
if (values.supportsstrechedl2subnet === true) {
params[`serviceCapabilityList[${serviceCapabilityIndex}].service`] = 'Connectivity'
params[`serviceCapabilityList[${serviceCapabilityIndex}].capabilitytype`] = 'RegionLevelVpc'
params[`serviceCapabilityList[${serviceCapabilityIndex}].capabilityvalue`] = true
serviceCapabilityIndex++
}
if (values.supportspublicaccess === true) {
params[`serviceCapabilityList[${serviceCapabilityIndex}].service`] = 'Connectivity'
params[`serviceCapabilityList[${serviceCapabilityIndex}].capabilitytype`] = 'DistributedRouter'
params[`serviceCapabilityList[${serviceCapabilityIndex}].capabilityvalue`] = true
serviceCapabilityIndex++
}
delete params.supportsstrechedl2subnet
delete params.supportspublicaccess
}
// SourceNat capabilities
if (supportedServices.includes('SourceNat')) {
if (values.redundantroutercapability === true) {
params[`serviceCapabilityList[${serviceCapabilityIndex}].service`] = 'SourceNat'
params[`serviceCapabilityList[${serviceCapabilityIndex}].capabilitytype`] = 'RedundantRouter'
params[`serviceCapabilityList[${serviceCapabilityIndex}].capabilityvalue`] = true
serviceCapabilityIndex++
}
params[`servicecapabilitylist[${serviceCapabilityIndex}].service`] = 'SourceNat'
params[`servicecapabilitylist[${serviceCapabilityIndex}].capabilitytype`] = 'SupportedSourceNatTypes'
params[`servicecapabilitylist[${serviceCapabilityIndex}].capabilityvalue`] = values.sourcenattype
serviceCapabilityIndex++
delete params.redundantroutercapability
delete params.sourcenattype
} else if (values.redundantroutercapability === true) {
params[`serviceCapabilityList[${serviceCapabilityIndex}].service`] = 'Gateway'
params[`serviceCapabilityList[${serviceCapabilityIndex}].capabilitytype`] = 'RedundantRouter'
params[`serviceCapabilityList[${serviceCapabilityIndex}].capabilityvalue`] = true
serviceCapabilityIndex++
}
// StaticNat capabilities
if (supportedServices.includes('SourceNat')) {
if (values.elasticip === true) {
params[`servicecapabilitylist[${serviceCapabilityIndex}].service`] = 'StaticNat'
params[`servicecapabilitylist[${serviceCapabilityIndex}].capabilitytype`] = 'ElasticIp'
params[`servicecapabilitylist[${serviceCapabilityIndex}].capabilityvalue`] = true
serviceCapabilityIndex++
}
if (values.elasticip === true || values.associatepublicip === true) {
params[`servicecapabilitylist[${serviceCapabilityIndex}].service`] = 'StaticNat'
params[`servicecapabilitylist[${serviceCapabilityIndex}].capabilitytype`] = 'associatePublicIP'
params[`servicecapabilitylist[${serviceCapabilityIndex}].capabilityvalue`] = values.associatepublicip
serviceCapabilityIndex++
}
delete params.elasticip
delete params.associatepublicip
}
// Lb capabilities
if (supportedServices.includes('Lb')) {
if ('vmautoscalingcapability' in values) {
params[`servicecapabilitylist[${serviceCapabilityIndex}].service`] = 'lb'
params[`servicecapabilitylist[${serviceCapabilityIndex}].capabilitytype`] = 'VmAutoScaling'
params[`servicecapabilitylist[${serviceCapabilityIndex}].capabilityvalue`] = values.vmautoscalingcapability
serviceCapabilityIndex++
}
if (values.elasticlb === true) {
params[`servicecapabilitylist[${serviceCapabilityIndex}].service`] = 'lb'
params[`servicecapabilitylist[${serviceCapabilityIndex}].capabilitytype`] = 'ElasticLb'
params[`servicecapabilitylist[${serviceCapabilityIndex}].capabilityvalue`] = true
serviceCapabilityIndex++
}
if (values.inlinemode === true && ((selectedServiceProviderMap.Lb === 'F5BigIp') || (selectedServiceProviderMap.Lb === 'Netscaler'))) {
params[`servicecapabilitylist[${serviceCapabilityIndex}].service`] = 'lb'
params[`servicecapabilitylist[${serviceCapabilityIndex}].capabilitytype`] = 'InlineMode'
params[`servicecapabilitylist[${serviceCapabilityIndex}].capabilityvalue`] = values.inlinemode
serviceCapabilityIndex++
}
params[`servicecapabilitylist[${serviceCapabilityIndex}].service`] = 'lb'
params[`servicecapabilitylist[${serviceCapabilityIndex}].capabilitytype`] = 'SupportedLbIsolation'
params[`servicecapabilitylist[${serviceCapabilityIndex}].capabilityvalue`] = values.isolation || 'dedicated'
serviceCapabilityIndex++
if (selectedServiceProviderMap.Lb === 'InternalLbVm') {
params[`servicecapabilitylist[${serviceCapabilityIndex}].service`] = 'lb'
params[`servicecapabilitylist[${serviceCapabilityIndex}].capabilitytype`] = 'lbSchemes'
params[`servicecapabilitylist[${serviceCapabilityIndex}].capabilityvalue`] = 'internal'
serviceCapabilityIndex++
}
if ('netscalerservicepackages' in values &&
registeredServicePackages.length > values.netscalerservicepackages &&
'netscalerservicepackagesdescription' in values) {
params['details[0].servicepackageuuid'] = registeredServicePackages[values.netscalerservicepackages].id
params['details[1].servicepackagedescription'] = values.netscalerservicepackagesdescription
}
}
}
/**
* Build the VPC service capability params for Add/Clone VPC Offering forms.
* Handles: RegionLevelVpc, DistributedRouter, RedundantRouter (SourceNat/Gateway)
*/
export function buildVpcServiceCapabilityParams (params, values, selectedServiceProviderMap, isVpcVirtualRouterForAtLeastOneService) {
const supportedServices = Object.keys(selectedServiceProviderMap)
let serviceCapabilityIndex = 0
if (supportedServices.includes('Connectivity')) {
if (values.regionlevelvpc === true) {
params[`serviceCapabilityList[${serviceCapabilityIndex}].service`] = 'Connectivity'
params[`serviceCapabilityList[${serviceCapabilityIndex}].capabilitytype`] = 'RegionLevelVpc'
params[`serviceCapabilityList[${serviceCapabilityIndex}].capabilityvalue`] = true
serviceCapabilityIndex++
}
if (values.distributedrouter === true) {
params[`serviceCapabilityList[${serviceCapabilityIndex}].service`] = 'Connectivity'
params[`serviceCapabilityList[${serviceCapabilityIndex}].capabilitytype`] = 'DistributedRouter'
params[`serviceCapabilityList[${serviceCapabilityIndex}].capabilityvalue`] = true
serviceCapabilityIndex++
}
}
if (supportedServices.includes('SourceNat') && values.redundantrouter === true) {
params[`serviceCapabilityList[${serviceCapabilityIndex}].service`] = 'SourceNat'
params[`serviceCapabilityList[${serviceCapabilityIndex}].capabilitytype`] = 'RedundantRouter'
params[`serviceCapabilityList[${serviceCapabilityIndex}].capabilityvalue`] = true
serviceCapabilityIndex++
} else if (values.redundantrouter === true) {
params[`serviceCapabilityList[${serviceCapabilityIndex}].service`] = 'Gateway'
params[`serviceCapabilityList[${serviceCapabilityIndex}].capabilitytype`] = 'RedundantRouter'
params[`serviceCapabilityList[${serviceCapabilityIndex}].capabilityvalue`] = true
serviceCapabilityIndex++
}
if (values.serviceofferingid && isVpcVirtualRouterForAtLeastOneService) {
params.serviceofferingid = values.serviceofferingid
}
}

View File

@ -143,6 +143,14 @@ export default {
},
show: (record) => { return record.state === 'Active' },
groupMap: (selection) => { return selection.map(x => { return { id: x, state: 'Inactive' } }) }
}, {
api: 'cloneServiceOffering',
icon: 'copy-outlined',
label: 'label.clone.compute.offering',
docHelp: 'adminguide/service_offerings.html#creating-a-new-compute-offering',
dataView: true,
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/CloneComputeOffering.vue')))
}]
},
{
@ -225,6 +233,15 @@ export default {
},
show: (record) => { return record.state === 'Active' },
groupMap: (selection) => { return selection.map(x => { return { id: x, state: 'Inactive' } }) }
}, {
api: 'cloneServiceOffering',
icon: 'copy-outlined',
label: 'label.clone.system.service.offering',
docHelp: 'adminguide/service_offerings.html#creating-a-new-system-service-offering',
dataView: true,
params: { issystem: 'true' },
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/CloneComputeOffering.vue')))
}]
},
{
@ -332,6 +349,14 @@ export default {
},
show: (record) => { return record.state === 'Active' },
groupMap: (selection) => { return selection.map(x => { return { id: x, state: 'Inactive' } }) }
}, {
api: 'cloneDiskOffering',
icon: 'copy-outlined',
label: 'label.clone.disk.offering',
docHelp: 'adminguide/service_offerings.html#creating-a-new-disk-offering',
dataView: true,
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/CloneDiskOffering.vue')))
}]
},
{
@ -376,6 +401,14 @@ export default {
popup: true,
groupMap: (selection) => { return selection.map(x => { return { id: x } }) },
args: ['name', 'description', 'allowuserdrivenbackups']
}, {
api: 'cloneBackupOffering',
icon: 'copy-outlined',
label: 'label.clone.backup.offering',
docHelp: 'adminguide/virtual_machines.html#importing-backup-offerings',
dataView: true,
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/CloneBackupOffering.vue')))
}, {
api: 'deleteBackupOffering',
icon: 'delete-outlined',
@ -487,6 +520,14 @@ export default {
dataView: true,
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/UpdateOfferingAccess.vue')))
}, {
api: 'cloneNetworkOffering',
icon: 'copy-outlined',
label: 'label.clone.network.offering',
docHelp: 'adminguide/networking.html#creating-a-new-network-offering',
dataView: true,
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/CloneNetworkOffering.vue')))
}, {
api: 'deleteNetworkOffering',
icon: 'delete-outlined',
@ -508,7 +549,7 @@ export default {
searchFilters: ['name', 'zoneid', 'domainid'],
resourceType: 'VpcOffering',
columns: ['name', 'state', 'displaytext', 'domain', 'zone', 'order'],
details: ['name', 'id', 'displaytext', 'internetprotocol', 'distributedvpcrouter', 'tags', 'routingmode', 'specifyasnumber', 'service', 'fornsx', 'networkmode', 'domain', 'zone', 'created'],
details: ['name', 'id', 'displaytext', 'internetprotocol', 'distributedvpcrouter', 'tags', 'routingmode', 'specifyasnumber', 'service', 'fornsx', 'networkmode', 'conservemode', 'domain', 'zone', 'created'],
related: [{
name: 'vpc',
title: 'label.vpc',
@ -579,6 +620,14 @@ export default {
dataView: true,
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/UpdateOfferingAccess.vue')))
}, {
api: 'cloneVPCOffering',
icon: 'copy-outlined',
docHelp: 'plugins/nuage-plugin.html?#optional-create-and-enable-vpc-offering',
label: 'label.clone.vpc.offering',
dataView: true,
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/offering/CloneVpcOffering.vue')))
}, {
api: 'deleteVPCOffering',
icon: 'delete-outlined',

View File

@ -204,6 +204,11 @@
{{ instance.loadbalancerruleinstance.displayname }}
</router-link>
</div>
<div v-if="this.vpcConserveMode">
<router-link :to="{ path: '/guestnetwork/' + instance.loadbalancerruleinstance.nic[0].networkid }">
{{ instance.loadbalancerruleinstance.nic[0].networkname }}
</router-link>
</div>
<div>{{ ip }}</div>
<tooltip-button
:disabled='record.autoscalevmgroup'
@ -487,10 +492,10 @@
>
<div @keyup.ctrl.enter="handleAddNewRule">
<span
v-if="'vpcid' in resource && !('associatednetworkid' in resource)">
v-if="'vpcid' in resource && (!('associatednetworkid' in resource) || vpcConserveMode)">
<strong>{{ $t('label.select.tier') }} </strong>
<a-select
v-focus="'vpcid' in resource && !('associatednetworkid' in resource)"
v-focus="'vpcid' in resource && (!('associatednetworkid' in resource) || vpcConserveMode)"
v-model:value="selectedTier"
@change="fetchVirtualMachines()"
:placeholder="$t('label.select.tier')"
@ -1022,7 +1027,8 @@ export default {
urlpath: '/'
},
healthMonitorLoading: false,
isNetrisZone: false
isNetrisZone: false,
vpcConserveMode: false
}
},
computed: {
@ -1079,10 +1085,24 @@ export default {
})
},
fetchData () {
this.fetchVpc()
this.fetchListTiers()
this.fetchLBRules()
this.fetchZone()
},
fetchVpc () {
if (!this.resource.vpcid) {
return
}
this.vpcConserveMode = false
getAPI('listVPCs', {
id: this.resource.vpcid
}).then(json => {
this.vpcConserveMode = json.listvpcsresponse?.vpc?.[0].vpcofferingconservemode || false
}).catch(error => {
this.$notifyError(error)
})
},
fetchListTiers () {
this.tiers.loading = true
@ -1830,7 +1850,7 @@ export default {
getAPI('listNics', {
virtualmachineid: e.target.value,
networkid: ('vpcid' in this.resource && !('associatednetworkid' in this.resource)) ? this.selectedTier : this.resource.associatednetworkid
networkid: ('vpcid' in this.resource && (!('associatednetworkid' in this.resource) || this.vpcConserveMode)) ? this.selectedTier : this.resource.associatednetworkid
}).then(response => {
if (!response || !response.listnicsresponse || !response.listnicsresponse.nic[0]) return
const newItem = []
@ -1850,7 +1870,7 @@ export default {
this.vmCount = 0
this.vms = []
this.addVmModalLoading = true
const networkId = ('vpcid' in this.resource && !('associatednetworkid' in this.resource)) ? this.selectedTier : this.resource.associatednetworkid
const networkId = ('vpcid' in this.resource && (!('associatednetworkid' in this.resource) || this.vpcConserveMode)) ? this.selectedTier : this.resource.associatednetworkid
if (!networkId) {
this.addVmModalLoading = false
return
@ -1935,11 +1955,17 @@ export default {
ip.forEach(i => {
vmIDIpMap[`vmidipmap[${innerCount}].vmid`] = this.newRule.virtualmachineid[count]
vmIDIpMap[`vmidipmap[${innerCount}].vmip`] = i
if (this.vpcConserveMode) {
vmIDIpMap[`vmidipmap[${innerCount}].vmnetworkid`] = this.selectedTier
}
innerCount++
})
} else {
vmIDIpMap[`vmidipmap[${innerCount}].vmid`] = this.newRule.virtualmachineid[count]
vmIDIpMap[`vmidipmap[${innerCount}].vmip`] = ip
if (this.vpcConserveMode && ip != null) {
vmIDIpMap[`vmidipmap[${innerCount}].vmnetworkid`] = this.selectedTier
}
innerCount++
}
if (this.newRule.virtualmachineid[count]) {

View File

@ -117,6 +117,12 @@
<template v-if="column.key === 'cidrlist'">
<span style="white-space: pre-line"> {{ record.cidrlist?.replaceAll(",", "\n") }}</span>
</template>
<template v-if="vpcConserveMode && column.key === 'networkid'">
<router-link
:to="{ path: '/guestnetwork/' + record.networkid }">
{{ record.networkname }}
</router-link>
</template>
<template v-if="column.key === 'vm'">
<div><desktop-outlined/>
<router-link
@ -216,10 +222,10 @@
@cancel="closeModal">
<div v-ctrl-enter="addRule">
<span
v-if="'vpcid' in resource && !('associatednetworkid' in resource)">
v-if="'vpcid' in resource && (!('associatednetworkid' in resource) || vpcConserveMode)">
<strong>{{ $t('label.select.tier') }} </strong>
<a-select
:v-focus="'vpcid' in resource && !('associatednetworkid' in resource)"
v-focus="'vpcid' in resource && (!('associatednetworkid' in resource) || vpcConserveMode)"
v-model:value="selectedTier"
@change="fetchVirtualMachines()"
:placeholder="$t('label.select.tier')"
@ -467,7 +473,8 @@ export default {
vmPageSize: 10,
vmCount: 0,
searchQuery: null,
cidrlist: ''
cidrlist: '',
vpcConserveMode: false
}
},
computed: {
@ -479,7 +486,6 @@ export default {
this.apiParams = this.$getApiParams('createPortForwardingRule')
},
created () {
console.log(this.resource)
this.initForm()
this.fetchData()
},
@ -504,13 +510,30 @@ export default {
})
},
fetchData () {
this.fetchVpc()
this.fetchListTiers()
this.fetchPFRules()
},
fetchListTiers () {
if ('vpcid' in this.resource && 'associatednetworkid' in this.resource) {
fetchVpc () {
if (!this.resource.vpcid) {
return
}
this.vpcConserveMode = false
getAPI('listVPCs', {
id: this.resource.vpcid
}).then(json => {
this.vpcConserveMode = json.listvpcsresponse?.vpc?.[0].vpcofferingconservemode || false
if (this.vpcConserveMode) {
this.columns.splice(this.columns.length - 1, 0, {
key: 'networkid',
title: this.$t('label.network')
})
}
}).catch(error => {
this.$notifyError(error)
})
},
fetchListTiers () {
this.selectedTier = null
this.tiers.loading = true
getAPI('listNetworks', {
@ -630,7 +653,7 @@ export default {
if (this.loading) return
this.loading = true
this.addVmModalVisible = false
const networkId = ('vpcid' in this.resource && !('associatednetworkid' in this.resource)) ? this.selectedTier : this.resource.associatednetworkid
const networkId = ('vpcid' in this.resource && (!('associatednetworkid' in this.resource) || this.vpcConserveMode)) ? this.selectedTier : this.resource.associatednetworkid
postAPI('createPortForwardingRule', {
...this.newRule,
ipaddressid: this.resource.id,
@ -788,7 +811,7 @@ export default {
this.newRule.virtualmachineid = e.target.value
getAPI('listNics', {
virtualmachineid: e.target.value,
networkId: ('vpcid' in this.resource && !('associatednetworkid' in this.resource)) ? this.selectedTier : this.resource.associatednetworkid
networkid: ('vpcid' in this.resource && (!('associatednetworkid' in this.resource) || this.vpcConserveMode)) ? this.selectedTier : this.resource.associatednetworkid
}).then(response => {
if (!response.listnicsresponse.nic || response.listnicsresponse.nic.length < 1) return
const nic = response.listnicsresponse.nic[0]
@ -799,7 +822,6 @@ export default {
this.newRule.vmguestip = this.nics[0]
this.addVmModalNicLoading = false
}).catch(error => {
console.log(error)
this.$notifyError(error)
this.closeModal()
})
@ -808,7 +830,7 @@ export default {
this.vmCount = 0
this.vms = []
this.addVmModalLoading = true
const networkId = ('vpcid' in this.resource && !('associatednetworkid' in this.resource)) ? this.selectedTier : this.resource.associatednetworkid
const networkId = ('vpcid' in this.resource && (!('associatednetworkid' in this.resource) || this.vpcConserveMode)) ? this.selectedTier : this.resource.associatednetworkid
if (!networkId) {
this.addVmModalLoading = false
return

View File

@ -135,8 +135,10 @@ export default {
return
}
if (this.resource && this.resource.vpcid) {
// VPC IPs with source nat have only VPN
if (this.resource.issourcenat) {
const vpc = await this.fetchVpc()
// VPC IPs with source nat have only VPN when VPC offering conserve mode = false
if (this.resource.issourcenat && vpc?.vpcofferingconservemode === false) {
this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn'))
return
}
@ -154,7 +156,12 @@ export default {
const network = await this.fetchNetwork()
if (network && network.networkofferingconservemode) {
this.tabs = tabs
// VPC IPs with source nat have only VPN when VPC offering conserve mode = false
if (this.resource.issourcenat && vpc?.vpcofferingconservemode === false) {
this.tabs = this.defaultTabs.concat(this.$route.meta.tabs.filter(tab => tab.name === 'vpn'))
} else {
this.tabs = tabs
}
return
}
@ -193,6 +200,21 @@ export default {
fetchAction () {
this.actions = this.$route.meta.actions || []
},
fetchVpc () {
if (!this.resource.vpcid) {
return null
}
return new Promise((resolve, reject) => {
getAPI('listVPCs', {
id: this.resource.vpcid
}).then(json => {
const vpc = json.listvpcsresponse?.vpc?.[0] || null
resolve(vpc)
}).catch(e => {
reject(e)
})
})
},
fetchNetwork () {
if (!this.resource.associatednetworkid) {
return null

View File

@ -18,658 +18,29 @@
<template>
<div class="form-layout" v-ctrl-enter="handleSubmit">
<a-spin :spinning="loading">
<a-form
:ref="formRef"
:model="form"
<ComputeOfferingForm
:initialValues="form"
:rules="rules"
@finish="handleSubmit"
layout="vertical"
>
<a-form-item name="name" ref="name">
<template #label>
<tooltip-label :title="$t('label.name')" :tooltip="apiParams.name.description"/>
</template>
<a-input
v-focus="true"
v-model:value="form.name"
:placeholder="$t('label.name')"/>
</a-form-item>
<a-form-item name="displaytext" ref="displaytext">
<template #label>
<tooltip-label :title="$t('label.displaytext')" :tooltip="apiParams.displaytext.description"/>
</template>
<a-input
v-model:value="form.displaytext"
:placeholder="$t('label.displaytext')"/>
</a-form-item>
<a-form-item name="systemvmtype" ref="systemvmtype" v-if="isSystem">
<template #label>
<tooltip-label :title="$t('label.systemvmtype')" :tooltip="apiParams.systemvmtype.description"/>
</template>
<a-select
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.systemvmtype"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:placeholder="apiParams.systemvmtype.description">
<a-select-option key="domainrouter" :label="$t('label.domain.router')">{{ $t('label.domain.router') }}</a-select-option>
<a-select-option key="consoleproxy" :label="$t('label.console.proxy')">{{ $t('label.console.proxy') }}</a-select-option>
<a-select-option key="secondarystoragevm" :label="$t('label.secondary.storage.vm')">{{ $t('label.secondary.storage.vm') }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="offeringtype" ref="offeringtype" :label="$t('label.offeringtype')" v-show="!isSystem">
<a-radio-group
v-model:value="form.offeringtype"
@change="selected => { handleComputeOfferingTypeChange(selected.target.value) }"
buttonStyle="solid">
<a-radio-button value="fixed">
{{ $t('label.fixed') }}
</a-radio-button>
<a-radio-button value="customconstrained">
{{ $t('label.customconstrained') }}
</a-radio-button>
<a-radio-button value="customunconstrained">
{{ $t('label.customunconstrained') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-row :gutter="12">
<a-col :md="8" :lg="8" v-if="offeringType === 'fixed'">
<a-form-item name="cpunumber" ref="cpunumber">
<template #label>
<tooltip-label :title="$t('label.cpunumber')" :tooltip="apiParams.cpunumber.description"/>
</template>
<a-input
v-model:value="form.cpunumber"
:placeholder="apiParams.cpunumber.description"/>
</a-form-item>
</a-col>
<a-col :md="8" :lg="8" v-if="offeringType !== 'customunconstrained'">
<a-form-item name="cpuspeed" ref="cpuspeed">
<template #label>
<tooltip-label :title="$t('label.cpuspeed')" :tooltip="apiParams.cpuspeed.description"/>
</template>
<a-input
v-model:value="form.cpuspeed"
:placeholder="apiParams.cpuspeed.description"/>
</a-form-item>
</a-col>
<a-col :md="8" :lg="8" v-if="offeringType === 'fixed'">
<a-form-item name="memory" ref="memory">
<template #label>
<tooltip-label :title="$t('label.memory.mb')" :tooltip="apiParams.memory.description"/>
</template>
<a-input
v-model:value="form.memory"
:placeholder="apiParams.memory.description"/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12" v-if="offeringType === 'customconstrained'">
<a-col :md="12" :lg="12">
<a-form-item name="mincpunumber" ref="mincpunumber">
<template #label>
<tooltip-label :title="$t('label.mincpunumber')" :tooltip="apiParams.mincpunumber.description"/>
</template>
<a-input
v-model:value="form.mincpunumber"
:placeholder="apiParams.mincpunumber.description"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="maxcpunumber" ref="maxcpunumber">
<template #label>
<tooltip-label :title="$t('label.maxcpunumber')" :tooltip="apiParams.maxcpunumber.description"/>
</template>
<a-input
v-model:value="form.maxcpunumber"
:placeholder="apiParams.maxcpunumber.description"/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12" v-if="offeringType === 'customconstrained'">
<a-col :md="12" :lg="12">
<a-form-item name="minmemory" ref="minmemory">
<template #label>
<tooltip-label :title="$t('label.minmemory')" :tooltip="apiParams.minmemory.description"/>
</template>
<a-input
v-model:value="form.minmemory"
:placeholder="apiParams.minmemory.description"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="maxmemory" ref="maxmemory">
<template #label>
<tooltip-label :title="$t('label.maxmemory')" :tooltip="apiParams.maxmemory.description"/>
</template>
<a-input
v-model:value="form.maxmemory"
:placeholder="apiParams.maxmemory.description"/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :md="12" :lg="12">
<a-form-item v-if="isAdmin() || isDomainAdminAllowedToInformTags" name="hosttags" ref="hosttags">
<template #label>
<tooltip-label :title="$t('label.hosttags')" :tooltip="apiParams.hosttags.description"/>
</template>
<a-input
v-model:value="form.hosttags"
:placeholder="apiParams.hosttags.description"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="networkrate" ref="networkrate">
<template #label>
<tooltip-label :title="$t('label.networkrate')" :tooltip="apiParams.networkrate.description"/>
</template>
<a-input
v-model:value="form.networkrate"
:placeholder="apiParams.networkrate.description"/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :md="12" :lg="12">
<a-form-item name="offerha" ref="offerha">
<template #label>
<tooltip-label :title="$t('label.offerha')" :tooltip="apiParams.offerha.description"/>
</template>
<a-switch v-model:checked="form.offerha" />
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="dynamicscalingenabled" ref="dynamicscalingenabled">
<template #label>
<tooltip-label :title="$t('label.dynamicscalingenabled')" :tooltip="apiParams.dynamicscalingenabled.description"/>
</template>
<a-switch v-model:checked="form.dynamicscalingenabled" @change="val => { dynamicscalingenabled = val }"/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :md="12" :lg="12">
<a-form-item name="limitcpuuse" ref="limitcpuuse">
<template #label>
<tooltip-label :title="$t('label.limitcpuuse')" :tooltip="apiParams.limitcpuuse.description"/>
</template>
<a-switch v-model:checked="form.limitcpuuse" />
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item v-if="!isSystem" name="isvolatile" ref="isvolatile">
<template #label>
<tooltip-label :title="$t('label.isvolatile')" :tooltip="apiParams.isvolatile.description"/>
</template>
<a-switch v-model:checked="form.isvolatile" />
</a-form-item>
</a-col>
</a-row>
<a-form-item name="deploymentplanner" ref="deploymentplanner" v-if="!isSystem && isAdmin()">
<template #label>
<tooltip-label :title="$t('label.deploymentplanner')" :tooltip="apiParams.deploymentplanner.description"/>
</template>
<a-select
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.deploymentplanner"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="deploymentPlannerLoading"
:placeholder="apiParams.deploymentplanner.description"
@change="val => { handleDeploymentPlannerChange(val) }">
<a-select-option v-for="(opt) in deploymentPlanners" :key="opt.name" :label="opt.name || opt.description || ''">
{{ opt.name || opt.description }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="plannermode" ref="plannermode" :label="$t('label.plannermode')" v-if="plannerModeVisible">
<a-radio-group
v-model:value="form.plannermode"
buttonStyle="solid">
<a-radio-button value="">
{{ $t('label.none') }}
</a-radio-button>
<a-radio-button value="strict">
{{ $t('label.strict') }}
</a-radio-button>
<a-radio-button value="Preferred">
{{ $t('label.preferred') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item name="gpucardid" ref="gpucardid" :label="$t('label.gpu.card')" v-if="!isSystem">
<a-select
v-model:value="form.gpucardid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="gpuCardLoading"
:placeholder="$t('label.gpu.card')"
@change="handleGpuCardChange">
<a-select-option v-for="(opt, optIndex) in gpuCards" :key="optIndex" :value="opt.id" :label="opt.name || opt.description || ''">
{{ opt.description || opt.name || '' }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="vgpuprofile" ref="vgpuprofile" :label="$t('label.vgpu.profile')" v-if="!isSystem && form.gpucardid && vgpuProfiles.length > 0">
<a-select
v-model:value="form.vgpuprofile"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="vgpuProfileLoading"
:placeholder="$t('label.vgpu.profile')">
<a-select-option v-for="(vgpu, vgpuIndex) in vgpuProfiles" :key="vgpuIndex" :value="vgpu.id" :label="vgpu.vgpuprofile || ''">
{{ vgpu.name }} {{ getVgpuProfileDetails(vgpu) }}
</a-select-option>
</a-select>
</a-form-item>
<a-row :gutter="12" v-if="!isSystem && form.gpucardid">
<a-col :md="12" :lg="12">
<a-form-item name="gpucount" ref="gpucount">
<template #label>
<tooltip-label :title="$t('label.gpu.count')" :tooltip="apiParams.gpucount.description"/>
</template>
<a-input
v-model:value="form.gpucount"
type="number"
min="1"
max="16"
:placeholder="$t('label.gpu.count')"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="gpudisplay" ref="gpudisplay">
<template #label>
<tooltip-label :title="$t('label.gpu.display')" :tooltip="apiParams.gpudisplay.description"/>
</template>
<a-switch v-model:checked="form.gpudisplay" />
</a-form-item>
</a-col>
</a-row>
<a-form-item name="ispublic" ref="ispublic" :label="$t('label.ispublic')" v-show="isAdmin()">
<a-switch v-model:checked="form.ispublic" />
</a-form-item>
<a-form-item name="domainid" ref="domainid" v-if="!form.ispublic">
<template #label>
<tooltip-label :title="$t('label.domainid')" :tooltip="apiParams.domainid.description"/>
</template>
<a-select
mode="multiple"
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.domainid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="domainLoading"
:placeholder="apiParams.domainid.description">
<a-select-option v-for="(opt, optIndex) in domains" :key="optIndex" :label="opt.path || opt.name || opt.description">
<span>
<resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
<block-outlined v-else style="margin-right: 5px" />
{{ opt.path || opt.name || opt.description }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="zoneid" ref="zoneid" v-if="!isSystem">
<template #label>
<tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid.description"/>
</template>
<a-select
id="zone-selection"
mode="multiple"
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.zoneid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
@select="val => fetchvSphereStoragePolicies(val)"
:loading="zoneLoading"
:placeholder="apiParams.zoneid.description">
<a-select-option v-for="(opt, optIndex) in zones" :key="optIndex" :label="opt.name || opt.description">
<span>
<resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px"/>
{{ opt.name || opt.description }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item
name="storagepolicy"
ref="storagepolicy"
v-if="'listVsphereStoragePolicies' in $store.getters.apis && storagePolicies !== null">
<template #label>
<tooltip-label :title="$t('label.vmware.storage.policy')" :tooltip="apiParams.storagepolicy.description"/>
</template>
<a-select
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.storagepolicy"
:placeholder="apiParams.storagepolicy.description"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option v-for="policy in storagePolicies" :key="policy.id" :label="policy.name || policy.id || ''">
{{ policy.name || policy.id }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="purgeresources" ref="purgeresources">
<template #label>
<tooltip-label :title="$t('label.purgeresources')" :tooltip="apiParams.purgeresources.description"/>
</template>
<a-switch v-model:checked="form.purgeresources"/>
</a-form-item>
<a-form-item name="showLeaseOptions" ref="showLeaseOptions" v-if="isLeaseFeatureEnabled">
<template #label>
<tooltip-label :title="$t('label.lease.enable')" :tooltip="$t('label.lease.enable.tooltip')" />
</template>
<a-switch v-model:checked="showLeaseOptions" @change="onToggleLeaseData"/>
</a-form-item>
<a-row :gutter="12" v-if="isLeaseFeatureEnabled && showLeaseOptions">
<a-col :md="12" :lg="12">
<a-form-item name="leaseduration" ref="leaseduration">
<template #label>
<tooltip-label :title="$t('label.leaseduration')"/>
</template>
<a-input
v-model:value="form.leaseduration"
:placeholder="$t('label.instance.lease.placeholder')"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="leaseexpiryaction" ref="leaseexpiryaction" v-if="form.leaseduration > 0">
<template #label>
<tooltip-label :title="$t('label.leaseexpiryaction')" />
</template>
<a-select v-model:value="form.leaseexpiryaction" :defaultValue="expiryActions">
<a-select-option v-for="action in expiryActions" :key="action" :label="action"/>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item name="computeonly" ref="computeonly">
<template #label>
<tooltip-label :title="$t('label.computeonly.offering')" :tooltip="$t('label.computeonly.offering.tooltip')"/>
</template>
<a-switch v-model:checked="form.computeonly" :checked="computeonly" @change="val => { computeonly = val }"/>
</a-form-item>
<a-card style="margin-bottom: 10px;">
<span v-if="computeonly">
<a-form-item name="storagetype" ref="storagetype">
<template #label>
<tooltip-label :title="$t('label.storagetype')" :tooltip="apiParams.storagetype.description"/>
</template>
<a-radio-group
v-model:value="form.storagetype"
buttonStyle="solid"
@change="selected => { handleStorageTypeChange(selected.target.value) }">
<a-radio-button value="shared">
{{ $t('label.shared') }}
</a-radio-button>
<a-radio-button value="local">
{{ $t('label.local') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item name="provisioningtype" ref="provisioningtype">
<template #label>
<tooltip-label :title="$t('label.provisioningtype')" :tooltip="apiParams.provisioningtype.description"/>
</template>
<a-radio-group
v-model:value="form.provisioningtype"
buttonStyle="solid"
@change="selected => { handleProvisioningTypeChange(selected.target.value) }">
<a-radio-button value="thin">
{{ $t('label.provisioningtype.thin') }}
</a-radio-button>
<a-radio-button value="sparse">
{{ $t('label.provisioningtype.sparse') }}
</a-radio-button>
<a-radio-button value="fat">
{{ $t('label.provisioningtype.fat') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item name="cachemode" ref="cachemode">
<template #label>
<tooltip-label :title="$t('label.cachemode')" :tooltip="apiParams.cachemode.description"/>
</template>
<a-radio-group
v-model:value="form.cachemode"
buttonStyle="solid"
@change="selected => { handleCacheModeChange(selected.target.value) }">
<a-radio-button value="none">
{{ $t('label.nodiskcache') }}
</a-radio-button>
<a-radio-button value="writeback">
{{ $t('label.writeback') }}
</a-radio-button>
<a-radio-button value="writethrough">
{{ $t('label.writethrough') }}
</a-radio-button>
<a-radio-button value="hypervisor_default">
{{ $t('label.hypervisor.default') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item :label="$t('label.qostype')" name="qostype" ref="qostype">
<a-radio-group
v-model:value="form.qostype"
buttonStyle="solid"
@change="selected => { handleQosTypeChange(selected.target.value) }">
<a-radio-button value="">
{{ $t('label.none') }}
</a-radio-button>
<a-radio-button value="hypervisor">
{{ $t('label.hypervisor') }}
</a-radio-button>
<a-radio-button value="storage">
{{ $t('label.storage') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-row :gutter="12" v-if="qosType === 'hypervisor'">
<a-col :md="12" :lg="12">
<a-form-item name="diskbytesreadrate" ref="diskbytesreadrate">
<template #label>
<tooltip-label :title="$t('label.diskbytesreadrate')" :tooltip="apiParams.bytesreadrate.description"/>
</template>
<a-input
v-model:value="form.diskbytesreadrate"
:placeholder="apiParams.bytesreadrate.description"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="diskbyteswriterate" ref="diskbyteswriterate">
<template #label>
<tooltip-label :title="$t('label.diskbyteswriterate')" :tooltip="apiParams.byteswriterate.description"/>
</template>
<a-input
v-model:value="form.diskbyteswriterate"
:placeholder="apiParams.byteswriterate.description"/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12" v-if="qosType === 'hypervisor'">
<a-col :md="12" :lg="12">
<a-form-item name="diskiopsreadrate" ref="diskiopsreadrate">
<template #label>
<tooltip-label :title="$t('label.diskiopsreadrate')" :tooltip="apiParams.iopsreadrate.description"/>
</template>
<a-input
v-model:value="form.diskiopsreadrate"
:placeholder="apiParams.iopsreadrate.description"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="diskiopswriterate" ref="diskiopswriterate">
<template #label>
<tooltip-label :title="$t('label.diskiopswriterate')" :tooltip="apiParams.iopswriterate.description"/>
</template>
<a-input
v-model:value="form.diskiopswriterate"
:placeholder="apiParams.iopswriterate.description"/>
</a-form-item>
</a-col>
</a-row>
<a-form-item v-if="!isSystem && qosType === 'storage'" name="iscustomizeddiskiops" ref="iscustomizeddiskiops">
<template #label>
<tooltip-label :title="$t('label.iscustomizeddiskiops')" :tooltip="apiParams.customizediops.description"/>
</template>
<a-switch v-model:checked="form.iscustomizeddiskiops" :checked="isCustomizedDiskIops" @change="val => { isCustomizedDiskIops = val }" />
</a-form-item>
<a-row :gutter="12" v-if="qosType === 'storage' && !isCustomizedDiskIops">
<a-col :md="12" :lg="12">
<a-form-item name="diskiopsmin" ref="diskiopsmin">
<template #label>
<tooltip-label :title="$t('label.diskiopsmin')" :tooltip="apiParams.miniops.description"/>
</template>
<a-input
v-model:value="form.diskiopsmin"
:placeholder="apiParams.miniops.description"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item name="diskiopsmax" ref="diskiopsmax">
<template #label>
<tooltip-label :title="$t('label.diskiopsmax')" :tooltip="apiParams.maxiops.description"/>
</template>
<a-input
v-model:value="form.diskiopsmax"
:placeholder="apiParams.maxiops.description"/>
</a-form-item>
</a-col>
</a-row>
<a-form-item v-if="!isSystem && qosType === 'storage'" name="hypervisorsnapshotreserve" ref="hypervisorsnapshotreserve">
<template #label>
<tooltip-label :title="$t('label.hypervisorsnapshotreserve')" :tooltip="apiParams.hypervisorsnapshotreserve.description"/>
</template>
<a-input
v-model:value="form.hypervisorsnapshotreserve"
:placeholder="apiParams.hypervisorsnapshotreserve.description"/>
</a-form-item>
<a-row :gutter="12">
<a-col :md="12" :lg="12">
<a-form-item v-if="apiParams.rootdisksize" name="rootdisksize" ref="rootdisksize">
<template #label>
<tooltip-label :title="$t('label.root.disk.size')" :tooltip="apiParams.rootdisksize.description"/>
</template>
<a-input
v-model:value="form.rootdisksize"
:placeholder="apiParams.rootdisksize.description"/>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12">
<a-form-item v-if="isAdmin() || isDomainAdminAllowedToInformTags" name="storagetags" ref="storagetags">
<template #label>
<tooltip-label :title="$t('label.storagetags')" :tooltip="apiParams.tags.description"/>
</template>
<a-select
mode="tags"
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.storagetags"
showSearch
optionFilterProp="value"
:filterOption="(input, option) => {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="storageTagLoading"
:placeholder="apiParams.tags.description"
v-if="isAdmin() || isDomainAdminAllowedToInformTags">
<a-select-option v-for="opt in storageTags" :key="opt">
{{ opt }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item name="encryptdisk" ref="encryptdisk">
<template #label>
<tooltip-label :title="$t('label.encrypt')" :tooltip="apiParams.encryptroot.description" />
</template>
<a-switch v-model:checked="form.encryptdisk" :checked="encryptdisk" @change="val => { encryptdisk = val }" />
</a-form-item>
</span>
<span v-if="!computeonly">
<a-form-item>
<a-button type="primary" @click="addDiskOffering()"> {{ $t('label.add.disk.offering') }} </a-button>
<a-modal
:visible="showDiskOfferingModal"
:title="$t('label.add.disk.offering')"
:footer="null"
centered
:closable="true"
@cancel="closeDiskOfferingModal"
width="auto">
<add-disk-offering @close-action="closeDiskOfferingModal()" @publish-disk-offering-id="($event) => updateSelectedDiskOffering($event)"/>
</a-modal>
<br /><br />
<a-form-item :label="$t('label.disk.offerings')" name="diskofferingid" ref="diskofferingid">
<a-select
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.diskofferingid"
:loading="loading"
:placeholder="$t('label.diskoffering')">
<a-select-option
v-for="(offering, index) in diskOfferings"
:value="offering.id"
:key="index">
{{ offering.displaytext || offering.name }}
</a-select-option>
</a-select>
</a-form-item>
</a-form-item>
</span>
<a-form-item name="diskofferingstrictness" ref="diskofferingstrictness">
<template #label>
<tooltip-label :title="$t('label.diskofferingstrictness')" :tooltip="apiParams.diskofferingstrictness.description"/>
</template>
<a-switch v-model:checked="form.diskofferingstrictness" :checked="diskofferingstrictness" @change="val => { diskofferingstrictness = val }"/>
</a-form-item>
</a-card>
<a-form-item name="externaldetails" ref="externaldetails">
<template #label>
<tooltip-label :title="$t('label.externaldetails')" :tooltip="apiParams.externaldetails.description"/>
</template>
<a-switch v-model:checked="externalDetailsEnabled" @change="onExternalDetailsEnabledChange"/>
<a-card v-if="externalDetailsEnabled" style="margin-top: 10px">
<div style="margin-bottom: 10px">{{ $t('message.add.orchestrator.resource.details') }}</div>
<details-input
v-model:value="form.externaldetails" />
</a-card>
</a-form-item>
</a-form>
<br/>
<div :span="24" class="action-button">
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
</div>
:apiParams="apiParams"
:isSystem="isSystem"
:isAdmin="isAdmin"
:ref="formRef"
@submit="handleSubmit">
<template #form-actions>
<br/>
<div :span="24" class="action-button">
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
</div>
</template>
</ComputeOfferingForm>
</a-spin>
</div>
</template>
<script>
import { ref, reactive, toRaw } from 'vue'
import ComputeOfferingForm from '@/components/offering/ComputeOfferingForm'
import { ref, reactive } from 'vue'
import { getAPI, postAPI } from '@/api'
import AddDiskOffering from '@/views/offering/AddDiskOffering'
import { isAdmin } from '@/role'
@ -683,6 +54,7 @@ export default {
name: 'AddServiceOffering',
mixins: [mixinForm],
components: {
ComputeOfferingForm,
AddDiskOffering,
ResourceIcon,
TooltipLabel,
@ -882,9 +254,6 @@ export default {
this.gpuCardLoading = false
})
},
addDiskOffering () {
this.showDiskOfferingModal = true
},
fetchDiskOfferings () {
this.diskOfferingLoading = true
getAPI('listDiskOfferings', {
@ -898,47 +267,18 @@ export default {
this.diskOfferingLoading = false
})
},
updateSelectedDiskOffering (id) {
if (id) {
this.selectedDiskOfferingId = id
}
},
closeDiskOfferingModal () {
this.fetchDiskOfferings()
this.showDiskOfferingModal = false
},
isAdmin () {
return isAdmin()
},
isDomainAdmin () {
return ['DomainAdmin'].includes(this.$store.getters.userInfo.roletype)
},
getVgpuProfileDetails (vgpuProfile) {
let output = '('
if (vgpuProfile?.videoram) {
output += `${vgpuProfile.videoram} MB`
}
if (vgpuProfile?.maxresolutionx && vgpuProfile?.maxresolutiony) {
if (output !== '(') {
output += ', '
}
output += `${vgpuProfile.maxresolutionx}x${vgpuProfile.maxresolutiony}`
}
output += ')'
if (output === '()') {
return ''
}
return output
},
checkIfDomainAdminIsAllowedToInformTag () {
const params = { id: store.getters.userInfo.accountid }
getAPI('isAccountAllowedToCreateOfferingsWithTags', params).then(json => {
this.isDomainAdminAllowedToInformTags = json.isaccountallowedtocreateofferingswithtagsresponse.isallowed.isallowed
})
},
arrayHasItems (array) {
return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
},
fetchDomainData () {
const params = {}
params.listAll = true
@ -990,83 +330,13 @@ export default {
this.deploymentPlannerLoading = false
})
},
fetchvSphereStoragePolicies (zoneIndex) {
if (zoneIndex === 0 || this.form.zoneid.length > 1) {
this.storagePolicies = null
return
}
const zoneid = this.zones[zoneIndex].id
if ('importVsphereStoragePolicies' in this.$store.getters.apis) {
this.storagePolicies = []
getAPI('listVsphereStoragePolicies', {
zoneid: zoneid
}).then(response => {
this.storagePolicies = response.listvspherestoragepoliciesresponse.StoragePolicy || []
})
}
},
handleStorageTypeChange (val) {
this.storageType = val
},
handleProvisioningTypeChange (val) {
this.provisioningType = val
},
handleCacheModeChange (val) {
this.cacheMode = val
},
handleComputeOfferingTypeChange (val) {
this.offeringType = val
},
handleQosTypeChange (val) {
this.qosType = val
},
handleDeploymentPlannerChange (planner) {
this.selectedDeploymentPlanner = planner
this.plannerModeVisible = false
if (this.selectedDeploymentPlanner === 'ImplicitDedicationPlanner') {
this.plannerModeVisible = isAdmin()
}
},
handlePlannerModeChange (val) {
this.plannerMode = val
},
handleGpuCardChange (cardId) {
this.selectedGpuCard = cardId
this.form.vgpuprofile = ''
if (cardId && cardId !== '') {
this.fetchVgpuProfiles(cardId)
} else {
this.vgpuProfiles = []
this.form.gpucount = '1'
}
},
fetchVgpuProfiles (gpuCardId) {
this.vgpuProfileLoading = true
this.vgpuProfiles = []
getAPI('listVgpuProfiles', {
gpucardid: gpuCardId
}).then(json => {
this.vgpuProfiles = json.listvgpuprofilesresponse.vgpuprofile || []
this.form.vgpuprofile = this.vgpuProfiles.length > 0 ? this.vgpuProfiles[0].id : ''
}).catch(error => {
console.error('Error fetching vGPU profiles:', error)
this.vgpuProfiles = []
}).finally(() => {
this.vgpuProfileLoading = false
})
},
onExternalDetailsEnabledChange (val) {
if (val || !this.form.externaldetails) {
return
}
this.form.externaldetails = undefined
},
handleSubmit (e) {
e.preventDefault()
if (e && e.preventDefault) {
e.preventDefault()
}
if (this.loading) return
this.formRef.value.validate().then(() => {
const formRaw = toRaw(this.form)
const values = this.handleRemoveFields(formRaw)
this.formRef.value.validate().then((values) => {
var params = {
issystem: this.isSystem,
name: values.name,
@ -1089,7 +359,6 @@ export default {
params.diskofferingid = values.diskofferingid
}
// Add GPU parameters
if (values.vgpuprofile) {
params.vgpuprofileid = values.vgpuprofile
}
@ -1100,17 +369,16 @@ export default {
params.gpudisplay = values.gpudisplay
}
// custom fields (begin)
if (values.offeringtype === 'fixed') {
params.cpunumber = values.cpunumber
params.cpuspeed = values.cpuspeed
params.memory = values.memory
} else {
if (values.cpuspeed != null &&
values.mincpunumber != null &&
values.maxcpunumber != null &&
values.minmemory != null &&
values.maxmemory != null) {
values.mincpunumber != null &&
values.maxcpunumber != null &&
values.minmemory != null &&
values.maxmemory != null) {
params.cpuspeed = values.cpuspeed
params.mincpunumber = values.mincpunumber
params.maxcpunumber = values.maxcpunumber
@ -1118,7 +386,6 @@ export default {
params.maxmemory = values.maxmemory
}
}
// custom fields (end)
if (values.networkrate != null && values.networkrate.length > 0) {
params.networkrate = values.networkrate
@ -1137,7 +404,7 @@ export default {
params.maxiops = values.diskiopsmax
}
if (values.hypervisorsnapshotreserve !== undefined &&
values.hypervisorsnapshotreserve != null && values.hypervisorsnapshotreserve.length > 0) {
values.hypervisorsnapshotreserve != null && values.hypervisorsnapshotreserve.length > 0) {
params.hypervisorsnapshotreserve = values.hypervisorsnapshotreserve
}
}
@ -1163,15 +430,15 @@ export default {
params.hosttags = values.hosttags
}
if ('deploymentplanner' in values &&
values.deploymentplanner !== undefined &&
values.deploymentplanner != null && values.deploymentplanner.length > 0) {
values.deploymentplanner !== undefined &&
values.deploymentplanner != null && values.deploymentplanner.length > 0) {
params.deploymentplanner = values.deploymentplanner
}
if ('deploymentplanner' in values &&
values.deploymentplanner !== undefined &&
values.deploymentplanner === 'ImplicitDedicationPlanner' &&
values.plannermode !== undefined &&
values.plannermode !== '') {
values.deploymentplanner !== undefined &&
values.deploymentplanner === 'ImplicitDedicationPlanner' &&
values.plannermode !== undefined &&
values.plannermode !== '') {
params['serviceofferingdetails[0].key'] = 'ImplicitDedicationMode'
params['serviceofferingdetails[0].value'] = values.plannermode
}
@ -1247,17 +514,6 @@ export default {
return Promise.reject(this.$t('message.error.number'))
}
return Promise.resolve()
},
onToggleLeaseData () {
if (this.showLeaseOptions === false) {
this.leaseduration = undefined
this.leaseexpiryaction = undefined
} else {
this.leaseduration = this.leaseduration !== undefined ? this.leaseduration : this.defaultLeaseDuration
this.leaseexpiryaction = this.leaseexpiryaction !== undefined ? this.leaseexpiryaction : this.defaultLeaseExpiryAction
}
this.form.leaseduration = this.leaseduration
this.form.leaseexpiryaction = this.leaseexpiryaction
}
}
}

View File

@ -18,485 +18,57 @@
<template>
<div class="form-layout" v-ctrl-enter="handleSubmit">
<a-spin :spinning="loading">
<a-form
:ref="formRef"
:model="form"
:rules="rules"
@finish="handleSubmit"
layout="vertical"
>
<a-form-item name="name" ref="name">
<template #label>
<tooltip-label :title="$t('label.name')" :tooltip="apiParams.name.description"/>
</template>
<a-input
v-focus="true"
v-model:value="form.name"
:placeholder="apiParams.name.description"/>
</a-form-item>
<a-form-item name="displaytext" ref="displaytext">
<template #label>
<tooltip-label :title="$t('label.displaytext')" :tooltip="apiParams.displaytext.description"/>
</template>
<a-input
v-model:value="form.displaytext"
:placeholder="apiParams.displaytext.description"/>
</a-form-item>
<a-form-item name="storagetype" ref="storagetype">
<template #label>
<tooltip-label :title="$t('label.storagetype')" :tooltip="apiParams.storagetype.description"/>
</template>
<a-radio-group
v-model:value="form.storagetype"
buttonStyle="solid">
<a-radio-button value="shared">
{{ $t('label.shared') }}
</a-radio-button>
<a-radio-button value="local">
{{ $t('label.local') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item name="provisioningtype" ref="provisioningtype">
<template #label>
<tooltip-label :title="$t('label.provisioningtype')" :tooltip="apiParams.provisioningtype.description"/>
</template>
<a-radio-group
v-model:value="form.provisioningtype"
buttonStyle="solid">
<a-radio-button value="thin">
{{ $t('label.provisioningtype.thin') }}
</a-radio-button>
<a-radio-button value="sparse">
{{ $t('label.provisioningtype.sparse') }}
</a-radio-button>
<a-radio-button value="fat">
{{ $t('label.provisioningtype.fat') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item name="encryptdisk" ref="encryptdisk">
<template #label>
<tooltip-label :title="$t('label.encrypt')" :tooltip="apiParams.encrypt.description" />
</template>
<a-switch v-model:checked="form.encryptdisk" :checked="encryptdisk" @change="val => { encryptdisk = val }" />
</a-form-item>
<a-form-item name="disksizestrictness" ref="disksizestrictness">
<template #label>
<tooltip-label :title="$t('label.disksizestrictness')" :tooltip="apiParams.disksizestrictness.description" />
</template>
<a-switch v-model:checked="form.disksizestrictness" :checked="disksizestrictness" @change="val => { disksizestrictness = val }" />
</a-form-item>
<a-form-item name="customdisksize" ref="customdisksize">
<template #label>
<tooltip-label :title="$t('label.customdisksize')" :tooltip="apiParams.customized.description"/>
</template>
<a-switch v-model:checked="form.customdisksize" />
</a-form-item>
<a-form-item v-if="!form.customdisksize" name="disksize" ref="disksize">
<template #label>
<tooltip-label :title="$t('label.disksize')" :tooltip="apiParams.disksize.description"/>
</template>
<a-input
v-model:value="form.disksize"
:placeholder="apiParams.disksize.description"/>
</a-form-item>
<a-form-item name="qostype" ref="qostype" :label="$t('label.qostype')">
<a-radio-group
v-model:value="form.qostype"
buttonStyle="solid">
<a-radio-button value="">
{{ $t('label.none') }}
</a-radio-button>
<a-radio-button value="hypervisor">
{{ $t('label.hypervisor') }}
</a-radio-button>
<a-radio-button value="storage">
{{ $t('label.storage') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item v-if="form.qostype === 'hypervisor'" name="diskbytesreadrate" ref="diskbytesreadrate">
<template #label>
<tooltip-label :title="$t('label.diskbytesreadrate')" :tooltip="apiParams.bytesreadrate.description"/>
</template>
<a-input
v-model:value="form.diskbytesreadrate"
:placeholder="apiParams.bytesreadrate.description"/>
</a-form-item>
<a-form-item v-if="form.qostype === 'hypervisor'" name="diskbytesreadratemax" ref="diskbytesreadratemax">
<template #label>
<tooltip-label :title="$t('label.diskbytesreadratemax')" :tooltip="apiParams.bytesreadratemax.description"/>
</template>
<a-input
v-model:value="form.diskbytesreadratemax"
:placeholder="apiParams.bytesreadratemax.description"/>
</a-form-item>
<a-form-item v-if="form.qostype === 'hypervisor'" name="diskbyteswriterate" ref="diskbyteswriterate">
<template #label>
<tooltip-label :title="$t('label.diskbyteswriterate')" :tooltip="apiParams.byteswriterate.description"/>
</template>
<a-input
v-model:value="form.diskbyteswriterate"
:placeholder="apiParams.byteswriterate.description"/>
</a-form-item>
<a-form-item v-if="form.qostype === 'hypervisor'" name="diskbyteswriteratemax" ref="diskbyteswriteratemax">
<template #label>
<tooltip-label :title="$t('label.diskbyteswriteratemax')" :tooltip="apiParams.byteswriteratemax.description"/>
</template>
<a-input
v-model:value="form.diskbyteswriteratemax"
:placeholder="apiParams.byteswriteratemax.description"/>
</a-form-item>
<a-form-item v-if="form.qostype === 'hypervisor'" name="diskiopsreadrate" ref="diskiopsreadrate">
<template #label>
<tooltip-label :title="$t('label.diskiopsreadrate')" :tooltip="apiParams.iopsreadrate.description"/>
</template>
<a-input
v-model:value="form.diskiopsreadrate"
:placeholder="apiParams.iopsreadrate.description"/>
</a-form-item>
<a-form-item v-if="form.qostype === 'hypervisor'" name="diskiopswriterate" ref="diskiopswriterate">
<template #label>
<tooltip-label :title="$t('label.diskiopswriterate')" :tooltip="apiParams.iopswriterate.description"/>
</template>
<a-input
v-model:value="form.diskiopswriterate"
:placeholder="apiParams.iopswriterate.description"/>
</a-form-item>
<a-form-item v-if="form.qostype === 'storage'" name="iscustomizeddiskiops" ref="iscustomizeddiskiops">
<template #label>
<tooltip-label :title="$t('label.iscustomizeddiskiops')" :tooltip="apiParams.customizediops.description"/>
</template>
<a-switch v-model:checked="form.iscustomizeddiskiops" />
</a-form-item>
<a-form-item v-if="form.qostype === 'storage' && !form.iscustomizeddiskiops" name="diskiopsmin" ref="diskiopsmin">
<template #label>
<tooltip-label :title="$t('label.diskiopsmin')" :tooltip="apiParams.miniops.description"/>
</template>
<a-input
v-model:value="form.diskiopsmin"
:placeholder="apiParams.miniops.description"/>
</a-form-item>
<a-form-item v-if="form.qostype === 'storage' && !form.iscustomizeddiskiops" name="diskiopsmax" ref="diskiopsmax">
<template #label>
<tooltip-label :title="$t('label.diskiopsmax')" :tooltip="apiParams.maxiops.description"/>
</template>
<a-input
v-model:value="form.diskiopsmax"
:placeholder="apiParams.maxiops.description"/>
</a-form-item>
<a-form-item v-if="form.qostype === 'storage'" name="hypervisorsnapshotreserve" ref="hypervisorsnapshotreserve">
<template #label>
<tooltip-label :title="$t('label.hypervisorsnapshotreserve')" :tooltip="apiParams.hypervisorsnapshotreserve.description"/>
</template>
<a-input
v-model:value="form.hypervisorsnapshotreserve"
:placeholder="apiParams.hypervisorsnapshotreserve.description"/>
</a-form-item>
<a-form-item name="writecachetype" ref="writecachetype">
<template #label>
<tooltip-label :title="$t('label.writecachetype')" :tooltip="apiParams.cachemode.description"/>
</template>
<a-radio-group
v-model:value="form.writecachetype"
buttonStyle="solid"
@change="selected => { handleWriteCacheTypeChange(selected.target.value) }">
<a-radio-button value="none">
{{ $t('label.nodiskcache') }}
</a-radio-button>
<a-radio-button value="writeback">
{{ $t('label.writeback') }}
</a-radio-button>
<a-radio-button value="writethrough">
{{ $t('label.writethrough') }}
</a-radio-button>
<a-radio-button value="hypervisor_default">
{{ $t('label.hypervisor.default') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item v-if="isAdmin() || isDomainAdminAllowedToInformTags" name="tags" ref="tags">
<template #label>
<tooltip-label :title="$t('label.storagetags')" :tooltip="apiParams.tags.description"/>
</template>
<a-select
:getPopupContainer="(trigger) => trigger.parentNode"
mode="tags"
v-model:value="form.tags"
showSearch
optionFilterProp="value"
:filterOption="(input, option) => {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="storageTagLoading"
:placeholder="apiParams.tags.description"
v-if="isAdmin() || isDomainAdminAllowedToInformTags">
<a-select-option v-for="(opt) in storageTags" :key="opt">
{{ opt }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item :label="$t('label.ispublic')" v-show="isAdmin()" name="ispublic" ref="ispublic">
<a-switch v-model:checked="form.ispublic" @change="val => { isPublic = val }" />
</a-form-item>
<a-form-item v-if="!isPublic" name="domainid" ref="domainid">
<template #label>
<tooltip-label :title="$t('label.domainid')" :tooltip="apiParams.domainid.description"/>
</template>
<a-select
mode="multiple"
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.domainid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="domainLoading"
:placeholder="apiParams.domainid.description">
<a-select-option v-for="(opt, optIndex) in domains" :key="optIndex" :label="opt.path || opt.name || opt.description">
<span>
<resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
<block-outlined v-else style="margin-right: 5px" />
{{ opt.path || opt.name || opt.description }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="zoneid" ref="zoneid">
<template #label>
<tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid.description"/>
</template>
<a-select
id="zone-selection"
mode="multiple"
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.zoneid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
@select="val => fetchvSphereStoragePolicies(val)"
:loading="zoneLoading"
:placeholder="apiParams.zoneid.description">
<a-select-option v-for="(opt, optIndex) in zones" :key="optIndex" :label="opt.name || opt.description">
<span>
<resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px"/>
{{ opt.name || opt.description }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="'listVsphereStoragePolicies' in $store.getters.apis && storagePolicies !== null" name="storagepolicy" ref="storagepolicy">
<template #label>
<tooltip-label :title="$t('label.vmware.storage.policy')" :tooltip="apiParams.storagepolicy.description"/>
</template>
<a-select
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.storagepolicy"
:placeholder="apiParams.storagepolicy.description"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option v-for="policy in storagePolicies" :key="policy.id" :label="policy.name || policy.id || ''">
{{ policy.name || policy.id }}
</a-select-option>
</a-select>
</a-form-item>
</a-form>
<div :span="24" class="action-button">
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
</div>
<DiskOfferingForm
ref="formRef"
:initialValues="form"
:apiParams="apiParams"
:isAdmin="isAdmin"
@submit="handleSubmit">
<template #form-actions>
<div :span="24" class="action-button">
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
</div>
</template>
</DiskOfferingForm>
</a-spin>
</div>
</template>
<script>
import { getAPI, postAPI } from '@/api'
import { reactive, ref, toRaw } from 'vue'
import DiskOfferingForm from '@/components/offering/DiskOfferingForm'
import { reactive } from 'vue'
import { postAPI } from '@/api'
import { isAdmin } from '@/role'
import { mixinForm } from '@/utils/mixin'
import ResourceIcon from '@/components/view/ResourceIcon'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import store from '@/store'
export default {
name: 'AddDiskOffering',
mixins: [mixinForm],
components: {
ResourceIcon,
TooltipLabel
DiskOfferingForm
},
data () {
return {
selectedDomains: [],
storageTags: [],
storagePolicies: null,
storageTagLoading: false,
isPublic: true,
isEncrypted: false,
domains: [],
domainLoading: false,
zones: [],
zoneLoading: false,
loading: false,
disksizestrictness: false,
encryptdisk: false,
isDomainAdminAllowedToInformTags: false
formRef: null,
form: reactive({}),
loading: false
}
},
beforeCreate () {
this.apiParams = this.$getApiParams('createDiskOffering')
},
created () {
this.zones = [
{
id: null,
name: this.$t('label.all.zone')
}
]
this.isPublic = isAdmin()
this.initForm()
this.fetchData()
},
methods: {
initForm () {
this.formRef = ref()
this.form = reactive({
storagetype: 'shared',
provisioningtype: 'thin',
customdisksize: true,
writecachetype: 'none',
qostype: '',
ispublic: this.isPublic,
disksizestrictness: this.disksizestrictness,
encryptdisk: this.encryptdisk
})
this.rules = reactive({
name: [{ required: true, message: this.$t('message.error.required.input') }],
disksize: [
{ required: true, message: this.$t('message.error.required.input') },
{ type: 'number', validator: this.validateNumber }
],
diskbytesreadrate: [{ type: 'number', validator: this.validateNumber }],
diskbytesreadratemax: [{ type: 'number', validator: this.validateNumber }],
diskbyteswriterate: [{ type: 'number', validator: this.validateNumber }],
diskbyteswriteratemax: [{ type: 'number', validator: this.validateNumber }],
diskiopsreadrate: [{ type: 'number', validator: this.validateNumber }],
diskiopswriterate: [{ type: 'number', validator: this.validateNumber }],
diskiopsmin: [{ type: 'number', validator: this.validateNumber }],
diskiopsmax: [{ type: 'number', validator: this.validateNumber }],
hypervisorsnapshotreserve: [{ type: 'number', validator: this.validateNumber }],
domainid: [{ type: 'array', required: true, message: this.$t('message.error.select') }],
zoneid: [{
type: 'array',
validator: async (rule, value) => {
if (value && value.length > 1 && value.indexOf(0) !== -1) {
return Promise.reject(this.$t('message.error.zone.combined'))
}
return Promise.resolve()
}
}]
})
},
handleWriteCacheTypeChange (val) {
this.form.writeCacheType = val
},
fetchData () {
this.fetchDomainData()
this.fetchZoneData()
if (isAdmin()) {
this.fetchStorageTagData()
}
if (this.isDomainAdmin()) {
this.checkIfDomainAdminIsAllowedToInformTag()
if (this.isDomainAdminAllowedToInformTags) {
this.fetchStorageTagData()
}
}
},
isDomainAdmin () {
return ['DomainAdmin'].includes(this.$store.getters.userInfo.roletype)
},
isAdmin () {
return isAdmin()
},
checkIfDomainAdminIsAllowedToInformTag () {
const params = { id: store.getters.userInfo.accountid }
getAPI('isAccountAllowedToCreateOfferingsWithTags', params).then(json => {
this.isDomainAdminAllowedToInformTags = json.isaccountallowedtocreateofferingswithtagsresponse.isallowed.isallowed
})
},
arrayHasItems (array) {
return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
},
fetchDomainData () {
const params = {}
params.listAll = true
params.showicon = true
params.details = 'min'
this.domainLoading = true
getAPI('listDomains', params).then(json => {
const listDomains = json.listdomainsresponse.domain
this.domains = this.domains.concat(listDomains)
}).finally(() => {
this.domainLoading = false
})
},
fetchZoneData () {
const params = {}
params.showicon = true
this.zoneLoading = true
getAPI('listZones', params).then(json => {
const listZones = json.listzonesresponse.zone
if (listZones) {
this.zones = this.zones.concat(listZones)
}
}).finally(() => {
this.zoneLoading = false
})
},
fetchStorageTagData () {
const params = {}
this.storageTagLoading = true
getAPI('listStorageTags', params).then(json => {
const tags = json.liststoragetagsresponse.storagetag || []
for (const tag of tags) {
if (!this.storageTags.includes(tag.name)) {
this.storageTags.push(tag.name)
}
}
}).finally(() => {
this.storageTagLoading = false
})
},
fetchvSphereStoragePolicies (zoneIndex) {
if (zoneIndex === 0 || this.form.zoneid.length > 1) {
this.storagePolicies = null
return
}
const zoneid = this.zones[zoneIndex].id
if ('importVsphereStoragePolicies' in this.$store.getters.apis) {
this.storagePolicies = []
getAPI('listVsphereStoragePolicies', {
zoneid: zoneid
}).then(response => {
this.storagePolicies = response.listvspherestoragepoliciesresponse.StoragePolicy || []
})
}
},
handleSubmit (e) {
e.preventDefault()
if (e && e.preventDefault) {
e.preventDefault()
}
if (this.loading) return
this.formRef.value.validate().then(() => {
const formRaw = toRaw(this.form)
const values = this.handleRemoveFields(formRaw)
this.$refs.formRef.validate().then((values) => {
var params = {
name: values.name,
displaytext: values.displaytext,
@ -553,8 +125,9 @@ export default {
var domainId = null
if (domainIndexes && domainIndexes.length > 0) {
var domainIds = []
const domains = this.$refs.formRef.domains
for (var i = 0; i < domainIndexes.length; i++) {
domainIds = domainIds.concat(this.domains[domainIndexes[i]].id)
domainIds.push(domains[domainIndexes[i]].id)
}
domainId = domainIds.join(',')
}
@ -566,8 +139,9 @@ export default {
var zoneId = null
if (zoneIndexes && zoneIndexes.length > 0) {
var zoneIds = []
const zones = this.$refs.formRef.zones
for (var j = 0; j < zoneIndexes.length; j++) {
zoneIds = zoneIds.concat(this.zones[zoneIndexes[j]].id)
zoneIds.push(zones[zoneIndexes[j]].id)
}
zoneId = zoneIds.join(',')
}
@ -577,6 +151,8 @@ export default {
if (values.storagepolicy) {
params.storagepolicy = values.storagepolicy
}
this.loading = true
postAPI('createDiskOffering', params).then(json => {
this.$emit('publish-disk-offering-id', json?.creatediskofferingresponse?.diskoffering?.id)
this.$message.success(`${this.$t('message.disk.offering.created')} ${values.name}`)
@ -591,12 +167,6 @@ export default {
},
closeAction () {
this.$emit('close-action')
},
async validateNumber (rule, value) {
if (value && (isNaN(value) || value <= 0)) {
return Promise.reject(this.$t('message.error.number'))
}
return Promise.resolve()
}
}
}

View File

@ -588,6 +588,7 @@ import { mixinForm } from '@/utils/mixin'
import CheckBoxSelectPair from '@/components/CheckBoxSelectPair'
import ResourceIcon from '@/components/view/ResourceIcon'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import { buildServiceCapabilityParams } from '@/composables/useServiceCapabilityParams'
export default {
name: 'AddNetworkOffering',
@ -1173,99 +1174,7 @@ export default {
}
}
if (this.selectedServiceProviderMap != null) {
var supportedServices = Object.keys(this.selectedServiceProviderMap)
params.supportedservices = supportedServices.join(',')
for (var k in supportedServices) {
params['serviceProviderList[' + k + '].service'] = supportedServices[k]
params['serviceProviderList[' + k + '].provider'] = this.selectedServiceProviderMap[supportedServices[k]]
}
var serviceCapabilityIndex = 0
if (supportedServices.includes('Connectivity')) {
if (values.supportsstrechedl2subnet === true) {
params['serviceCapabilityList[' + serviceCapabilityIndex + '].service'] = 'Connectivity'
params['serviceCapabilityList[' + serviceCapabilityIndex + '].capabilitytype'] = 'RegionLevelVpc'
params['serviceCapabilityList[' + serviceCapabilityIndex + '].capabilityvalue'] = true
serviceCapabilityIndex++
}
if (values.supportspublicaccess === true) {
params['serviceCapabilityList[' + serviceCapabilityIndex + '].service'] = 'Connectivity'
params['serviceCapabilityList[' + serviceCapabilityIndex + '].capabilitytype'] = 'DistributedRouter'
params['serviceCapabilityList[' + serviceCapabilityIndex + '].capabilityvalue'] = true
serviceCapabilityIndex++
}
delete params.supportsstrechedl2subnet
delete params.supportspublicaccess
}
if (supportedServices.includes('SourceNat')) {
if (values.redundantroutercapability === true) {
params['serviceCapabilityList[' + serviceCapabilityIndex + '].service'] = 'SourceNat'
params['serviceCapabilityList[' + serviceCapabilityIndex + '].capabilitytype'] = 'RedundantRouter'
params['serviceCapabilityList[' + serviceCapabilityIndex + '].capabilityvalue'] = true
serviceCapabilityIndex++
}
params['servicecapabilitylist[' + serviceCapabilityIndex + '].service'] = 'SourceNat'
params['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilitytype'] = 'SupportedSourceNatTypes'
params['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilityvalue'] = values.sourcenattype
serviceCapabilityIndex++
delete params.redundantroutercapability
delete params.sourcenattype
} else if (values.redundantroutercapability === true) {
params['serviceCapabilityList[' + serviceCapabilityIndex + '].service'] = 'Gateway'
params['serviceCapabilityList[' + serviceCapabilityIndex + '].capabilitytype'] = 'RedundantRouter'
params['serviceCapabilityList[' + serviceCapabilityIndex + '].capabilityvalue'] = true
}
if (supportedServices.includes('SourceNat')) {
if (values.elasticip === true) {
params['servicecapabilitylist[' + serviceCapabilityIndex + '].service'] = 'StaticNat'
params['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilitytype'] = 'ElasticIp'
params['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilityvalue'] = true
serviceCapabilityIndex++
}
if (values.elasticip === true || values.associatepublicip === true) {
params['servicecapabilitylist[' + serviceCapabilityIndex + '].service'] = 'StaticNat'
params['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilitytype'] = 'associatePublicIP'
params['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilityvalue'] = values.associatepublicip
serviceCapabilityIndex++
}
delete params.elasticip
delete params.associatepublicip
}
if (supportedServices.includes('Lb')) {
if ('vmautoscalingcapability' in values) {
params['servicecapabilitylist[' + serviceCapabilityIndex + '].service'] = 'lb'
params['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilitytype'] = 'VmAutoScaling'
params['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilityvalue'] = values.vmautoscalingcapability
serviceCapabilityIndex++
}
if (values.elasticlb === true) {
params['servicecapabilitylist[' + serviceCapabilityIndex + '].service'] = 'lb'
params['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilitytype'] = 'ElasticLb'
params['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilityvalue'] = true
serviceCapabilityIndex++
}
if (values.inlinemode === true && ((this.selectedServiceProviderMap.Lb === 'F5BigIp') || (this.selectedServiceProviderMap.Lb === 'Netscaler'))) {
params['servicecapabilitylist[' + serviceCapabilityIndex + '].service'] = 'lb'
params['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilitytype'] = 'InlineMode'
params['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilityvalue'] = values.inlinemode
serviceCapabilityIndex++
}
params['servicecapabilitylist[' + serviceCapabilityIndex + '].service'] = 'lb'
params['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilitytype'] = 'SupportedLbIsolation'
params['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilityvalue'] = values.isolation
serviceCapabilityIndex++
if (this.selectedServiceProviderMap.Lb === 'InternalLbVm') {
params['servicecapabilitylist[' + serviceCapabilityIndex + '].service'] = 'lb'
params['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilitytype'] = 'lbSchemes'
params['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilityvalue'] = 'internal'
serviceCapabilityIndex++
}
if ('netscalerservicepackages' in values &&
this.registeredServicePackages.length > values.netscalerservicepackages &&
'netscalerservicepackagesdescription' in values) {
params['details[' + 0 + '].servicepackageuuid'] = this.registeredServicePackages[values.netscalerservicepackages].id
params['details[' + 1 + '].servicepackagedescription'] = values.netscalerservicepackagesdescription
}
}
buildServiceCapabilityParams(params, values, this.selectedServiceProviderMap, this.registeredServicePackages)
} else {
if (!('supportedservices' in params)) {
params.supportedservices = ''

View File

@ -194,6 +194,14 @@
</a-select-option>
</a-select>
</a-form-item>
<a-form-item
name="conservemode"
ref="conservemode">
<template #label>
<tooltip-label :title="$t('label.conservemode')" :tooltip="apiParams.conservemode.description"/>
</template>
<a-switch v-model:checked="form.conservemode" />
</a-form-item>
<a-form-item name="ispublic" ref="ispublic" :label="$t('label.ispublic')" v-if="isAdmin()">
<a-switch v-model:checked="form.ispublic" />
</a-form-item>
@ -269,6 +277,7 @@ import { mixinForm } from '@/utils/mixin'
import CheckBoxSelectPair from '@/components/CheckBoxSelectPair'
import ResourceIcon from '@/components/view/ResourceIcon'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import { buildVpcServiceCapabilityParams } from '@/composables/useServiceCapabilityParams'
export default {
name: 'AddVpcOffering',
@ -282,7 +291,6 @@ export default {
return {
selectedDomains: [],
selectedZones: [],
isConserveMode: true,
internetProtocolValue: 'ipv4',
domains: [],
domainLoading: false,
@ -328,7 +336,8 @@ export default {
description: 'Netris',
enabled: true
},
nsxSupportedServicesMap: {}
nsxSupportedServicesMap: {},
conservemode: false
}
},
beforeCreate () {
@ -719,6 +728,7 @@ export default {
params.provider = 'Netris'
}
params.networkmode = values.networkmode
params.conservemode = values.conservemode
if (!values.forVpc) {
params.specifyasnumber = values.specifyasnumber
}
@ -733,35 +743,7 @@ export default {
params['serviceProviderList[' + k + '].service'] = supportedServices[k]
params['serviceProviderList[' + k + '].provider'] = this.selectedServiceProviderMap[supportedServices[k]]
}
var serviceCapabilityIndex = 0
if (supportedServices.includes('Connectivity')) {
if (values.regionlevelvpc === true) {
params['serviceCapabilityList[' + serviceCapabilityIndex + '].service'] = 'Connectivity'
params['serviceCapabilityList[' + serviceCapabilityIndex + '].capabilitytype'] = 'RegionLevelVpc'
params['serviceCapabilityList[' + serviceCapabilityIndex + '].capabilityvalue'] = true
serviceCapabilityIndex++
}
if (values.distributedrouter === true) {
params['serviceCapabilityList[' + serviceCapabilityIndex + '].service'] = 'Connectivity'
params['serviceCapabilityList[' + serviceCapabilityIndex + '].capabilitytype'] = 'DistributedRouter'
params['serviceCapabilityList[' + serviceCapabilityIndex + '].capabilityvalue'] = true
serviceCapabilityIndex++
}
}
if (supportedServices.includes('SourceNat') && values.redundantrouter === true) {
params['serviceCapabilityList[' + serviceCapabilityIndex + '].service'] = 'SourceNat'
params['serviceCapabilityList[' + serviceCapabilityIndex + '].capabilitytype'] = 'RedundantRouter'
params['serviceCapabilityList[' + serviceCapabilityIndex + '].capabilityvalue'] = true
serviceCapabilityIndex++
} else if (values.redundantrouter === true) {
params['serviceCapabilityList[' + serviceCapabilityIndex + '].service'] = 'Gateway'
params['serviceCapabilityList[' + serviceCapabilityIndex + '].capabilitytype'] = 'RedundantRouter'
params['serviceCapabilityList[' + serviceCapabilityIndex + '].capabilityvalue'] = true
serviceCapabilityIndex++
}
if (values.serviceofferingid && this.isVpcVirtualRouterForAtLeastOneService) {
params.serviceofferingid = values.serviceofferingid
}
buildVpcServiceCapabilityParams(params, values, this.selectedServiceProviderMap, this.isVpcVirtualRouterForAtLeastOneService)
} else {
params.supportedservices = []
}

View File

@ -0,0 +1,432 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div class="form-layout" v-ctrl-enter="handleSubmit">
<a-spin :spinning="loading">
<a-alert
v-if="resource"
type="info"
style="margin-bottom: 16px">
<template #message>
<div style="display: block; width: 100%;">
<div style="display: block; margin-bottom: 8px;">
<strong>{{ $t('message.clone.offering.from') }}: {{ resource.name }}</strong>
</div>
<div style="display: block; font-size: 12px;">
{{ $t('message.clone.offering.edit.hint') }}
</div>
</div>
</template>
</a-alert>
<a-form
layout="vertical"
:ref="formRef"
:model="form"
:rules="rules"
@finish="handleSubmit"
>
<a-form-item name="name" ref="name">
<template #label>
<tooltip-label :title="$t('label.name')" :tooltip="apiParams.name.description"/>
</template>
<a-input
v-focus="true"
v-model:value="form.name"/>
</a-form-item>
<a-form-item name="description" ref="description">
<template #label>
<tooltip-label :title="$t('label.description')" :tooltip="apiParams.description.description"/>
</template>
<a-input v-model:value="form.description"/>
</a-form-item>
<a-form-item name="zoneid" ref="zoneid">
<template #label>
<tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid ? apiParams.zoneid.description : ''"/>
</template>
<a-select
allowClear
v-model:value="form.zoneid"
:loading="zones.loading"
@change="onChangeZone"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option v-for="zone in zones.opts" :key="zone.name" :label="zone.name">
<span>
<resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px"/>
{{ zone.name }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="externalid" ref="externalid">
<template #label>
<tooltip-label :title="$t('label.externalid')" :tooltip="apiParams.externalid ? apiParams.externalid.description : ''"/>
</template>
<a-select
allowClear
v-model:value="form.externalid"
:loading="externals.loading"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option v-for="opt in externals.opts" :key="opt.id" :label="opt.name">
{{ opt.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="allowuserdrivenbackups" ref="allowuserdrivenbackups">
<template #label>
<tooltip-label :title="$t('label.allowuserdrivenbackups')" :tooltip="apiParams.allowuserdrivenbackups ? apiParams.allowuserdrivenbackups.description : ''"/>
</template>
<a-switch v-model:checked="form.allowuserdrivenbackups"/>
</a-form-item>
<a-form-item name="ispublic" ref="ispublic" :label="$t('label.ispublic')" v-if="isAdmin()">
<a-switch v-model:checked="form.ispublic" @change="onChangeIsPublic" />
</a-form-item>
<a-form-item name="domainid" ref="domainid" v-if="!form.ispublic">
<template #label>
<tooltip-label :title="$t('label.domainid')" :tooltip="apiParams.domainid.description"/>
</template>
<a-select
mode="multiple"
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.domainid"
@change="onChangeDomain"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="domains.loading"
:placeholder="apiParams.domainid.description">
<a-select-option v-for="(opt, optIndex) in domains.opts" :key="optIndex" :label="opt.path || opt.name || opt.description">
<span>
<resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
<block-outlined v-else style="margin-right: 5px" />
{{ opt.path || opt.name || opt.description }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<div :span="24" class="action-button">
<a-button :loading="loading" @click="closeAction">{{ $t('label.cancel') }}</a-button>
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
</div>
</a-form>
</a-spin>
</div>
</template>
<script>
import { ref, reactive, toRaw } from 'vue'
import { getAPI, postAPI } from '@/api'
import ResourceIcon from '@/components/view/ResourceIcon'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import { GlobalOutlined } from '@ant-design/icons-vue'
import { isAdmin } from '@/role'
export default {
name: 'CloneBackupOffering',
components: {
TooltipLabel,
ResourceIcon,
GlobalOutlined
},
props: {
resource: {
type: Object,
required: true
}
},
data () {
return {
loading: false,
zones: {
loading: false,
opts: []
},
externals: {
loading: false,
opts: []
},
domains: {
loading: false,
opts: []
}
}
},
beforeCreate () {
this.apiParams = this.$getApiParams('cloneBackupOffering')
},
created () {
this.initForm()
this.fetchData()
},
methods: {
initForm () {
this.formRef = ref()
this.form = reactive({
allowuserdrivenbackups: true
})
this.rules = reactive({
name: [{ required: true, message: this.$t('message.error.required.input') }],
description: [{ required: true, message: this.$t('message.error.required.input') }]
})
},
fetchData () {
this.fetchZone()
this.fetchDomain()
this.$nextTick(() => {
this.populateFormFromResource()
})
},
fetchZone () {
this.zones.loading = true
getAPI('listZones', { available: true, showicon: true }).then(json => {
this.zones.opts = json.listzonesresponse.zone || []
this.$nextTick(() => {
this.populateFormFromResource()
})
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.zones.loading = false
})
},
fetchDomain () {
this.domains.loading = true
const params = { listAll: true, showicon: true, details: 'min' }
getAPI('listDomains', params).then(json => {
this.domains.opts = json.listdomainsresponse.domain || []
this.$nextTick(() => {
this.populateFormFromResource()
})
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.domains.loading = false
})
},
fetchExternal (zoneId) {
if (!zoneId) {
this.externals.opts = []
return
}
this.externals.loading = true
getAPI('listBackupProviderOfferings', { zoneid: zoneId }).then(json => {
this.externals.opts = json.listbackupproviderofferingsresponse.backupoffering || []
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.externals.loading = false
})
},
populateFormFromResource () {
if (!this.resource) return
const r = this.resource
this.form.name = r.name + ' - Clone'
this.form.description = r.description || r.name
if (r.allowuserdrivenbackups !== undefined) {
this.form.allowuserdrivenbackups = r.allowuserdrivenbackups
}
if (r.domainid) {
let offeringDomainIds = r.domainid
offeringDomainIds = (typeof offeringDomainIds === 'string' && offeringDomainIds.indexOf(',') !== -1) ? offeringDomainIds.split(',') : [offeringDomainIds]
const selected = []
for (let i = 0; i < offeringDomainIds.length; i++) {
for (let j = 0; j < this.domains.opts.length; j++) {
if (String(offeringDomainIds[i]) === String(this.domains.opts[j].id)) {
selected.push(j)
}
}
}
if (selected.length > 0) {
this.form.ispublic = false
this.form.domainid = selected
}
} else {
if (isAdmin()) {
this.form.ispublic = true
}
}
if (r.zoneid && this.zones.opts.length > 0) {
const zone = this.zones.opts.find(z => z.id === r.zoneid)
if (zone) {
this.form.zoneid = zone.name
this.fetchExternal(zone.id)
}
}
if (r.externalid) {
this.$nextTick(() => {
this.form.externalid = r.externalid
})
}
},
handleSubmit (e) {
if (e) {
e.preventDefault()
}
if (this.loading) return
this.formRef.value.validate().then(() => {
const values = toRaw(this.form)
const params = {
sourceofferingid: this.resource.id
}
if (values.name) {
params.name = values.name
}
if (values.description) {
params.description = values.description
}
if (values.zoneid) {
const zone = this.zones.opts.find(z => z.name === values.zoneid)
if (zone) {
params.zoneid = zone.id
}
}
if (values.externalid) {
params.externalid = values.externalid
}
if (values.allowuserdrivenbackups !== undefined) {
params.allowuserdrivenbackups = values.allowuserdrivenbackups
}
// Include selected domain IDs when offering is not public
if (values.ispublic !== true) {
const domainIndexes = values.domainid
let domainId = null
if (domainIndexes && domainIndexes.length > 0) {
const domainIds = []
const domains = this.domains.opts
for (let i = 0; i < domainIndexes.length; i++) {
domainIds.push(domains[domainIndexes[i]].id)
}
domainId = domainIds.join(',')
}
if (domainId) {
params.domainid = domainId
}
}
this.loading = true
const title = this.$t('message.success.clone.backup.offering')
postAPI('cloneBackupOffering', params).then(json => {
const jobId = json.clonebackupofferingresponse?.jobid
if (jobId) {
this.$pollJob({
jobId,
title,
description: values.name,
successMethod: result => {
this.$message.success(`${title}: ${values.name}`)
this.$emit('refresh-data')
this.closeAction()
},
loadingMessage: `${title} ${this.$t('label.in.progress')} ${this.$t('label.for')} ${params.name}`,
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.closeAction()
}
})
} else {
this.$message.success(`${title}: ${values.name}`)
this.$emit('refresh-data')
this.closeAction()
}
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
}).catch(error => {
this.formRef.value.scrollToField(error.errorFields[0].name)
})
},
onChangeZone (value) {
if (!value) {
this.externals.opts = []
this.form.externalid = null
return
}
const zone = this.zones.opts.find(z => z.name === value)
if (zone) {
this.fetchExternal(zone.id)
}
},
onChangeIsPublic (value) {
// when made public, clear any domain selection
try {
if (value === true) {
this.form.domainid = []
}
} catch (e) {
// ignore
}
},
onChangeDomain (value) {
// value is an array of selected indexes (as used in the form), when empty -> make offering public
try {
if (!value || (Array.isArray(value) && value.length === 0)) {
this.form.ispublic = true
// clear domain selection to avoid confusing hidden inputs
this.form.domainid = []
} else {
this.form.ispublic = false
}
} catch (e) {
// defensive - do nothing on error
}
},
closeAction () {
this.$emit('close-action')
},
isAdmin () {
return isAdmin()
}
}
}
</script>
<style scoped lang="less">
.form-layout {
width: 30vw;
@media (min-width: 500px) {
width: 450px;
}
}
</style>

View File

@ -0,0 +1,674 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div class="form-layout" v-ctrl-enter="handleSubmit">
<a-spin :spinning="loading">
<a-alert
v-if="resource"
type="info"
style="margin-bottom: 16px">
<template #message>
<div style="display: block; width: 100%;">
<div style="display: block; margin-bottom: 8px;">
<strong>{{ $t('message.clone.offering.from') }}: {{ resource.name }}</strong>
</div>
<div style="display: block; font-size: 12px;">
{{ $t('message.clone.offering.edit.hint') }}
</div>
</div>
</template>
</a-alert>
<ComputeOfferingForm
:initialValues="form"
:rules="rules"
:apiParams="apiParams"
:isSystem="isSystem"
:isAdmin="isAdmin"
:ref="formRef"
@submit="handleSubmit">
<!-- form content is provided by ComputeOfferingForm component -->
<template #form-actions>
<br/>
<div :span="24" class="action-button">
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
</div>
</template>
</ComputeOfferingForm>
</a-spin>
</div>
</template>
<script>
import ComputeOfferingForm from '@/components/offering/ComputeOfferingForm'
import { ref, reactive } from 'vue'
import { getAPI, postAPI } from '@/api'
import AddDiskOffering from '@/views/offering/AddDiskOffering'
import { isAdmin } from '@/role'
import { mixinForm } from '@/utils/mixin'
import ResourceIcon from '@/components/view/ResourceIcon'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import DetailsInput from '@/components/widgets/DetailsInput'
import store from '@/store'
export default {
name: 'CloneComputeOffering',
mixins: [mixinForm],
components: {
ComputeOfferingForm,
AddDiskOffering,
ResourceIcon,
TooltipLabel,
DetailsInput
},
props: {
resource: {
type: Object,
required: true
}
},
data () {
return {
isSystem: false,
naturalNumberRule: {
type: 'number',
validator: this.validateNumber
},
wholeNumberRule: {
type: 'number',
validator: async (rule, value) => {
if (value && (isNaN(value) || value < 0)) {
return Promise.reject(this.$t('message.error.number'))
}
return Promise.resolve()
}
},
storageType: 'shared',
provisioningType: 'thin',
cacheMode: 'none',
offeringType: 'fixed',
isCustomizedDiskIops: false,
isPublic: true,
domains: [],
domainLoading: false,
zones: [],
zoneLoading: false,
selectedDeploymentPlanner: null,
storagePolicies: null,
storageTags: [],
storageTagLoading: false,
deploymentPlanners: [],
deploymentPlannerLoading: false,
plannerModeVisible: false,
plannerMode: '',
selectedGpuCard: '',
showDiskOfferingModal: false,
gpuCardLoading: false,
gpuCards: [],
loading: false,
dynamicscalingenabled: true,
diskofferingstrictness: false,
encryptdisk: false,
computeonly: true,
diskOfferingLoading: false,
diskOfferings: [],
selectedDiskOfferingId: '',
qosType: '',
isDomainAdminAllowedToInformTags: false,
isLeaseFeatureEnabled: this.$store.getters.features.instanceleaseenabled,
showLeaseOptions: false,
expiryActions: ['STOP', 'DESTROY'],
defaultLeaseDuration: 90,
defaultLeaseExpiryAction: 'STOP',
leaseduration: undefined,
leaseexpiryaction: undefined,
vgpuProfiles: [],
vgpuProfileLoading: false,
externalDetailsEnabled: false
}
},
beforeCreate () {
this.apiParams = this.$getApiParams('cloneServiceOffering')
},
created () {
this.zones = [
{
id: null,
name: this.$t('label.all.zone')
}
]
if (this.$route.meta.name === 'systemoffering') {
this.isSystem = true
}
this.initForm()
this.fetchData()
this.isPublic = isAdmin()
this.form.ispublic = this.isPublic
},
methods: {
initForm () {
this.formRef = ref()
this.form = reactive({
systemvmtype: 'domainrouter',
offeringtype: this.offeringType,
ispublic: this.isPublic,
dynamicscalingenabled: true,
plannermode: this.plannerMode,
gpucardid: this.selectedGpuCard,
vgpuprofile: '',
gpucount: '1',
gpudisplay: false,
computeonly: this.computeonly,
storagetype: this.storageType,
provisioningtype: this.provisioningType,
cachemode: this.cacheMode,
qostype: this.qosType,
iscustomizeddiskiops: this.isCustomizedDiskIops,
diskofferingid: this.selectedDiskOfferingId,
diskofferingstrictness: this.diskofferingstrictness,
encryptdisk: this.encryptdisk,
leaseduration: this.leaseduration,
leaseexpiryaction: this.leaseexpiryaction
})
this.rules = reactive({
name: [{ required: true, message: this.$t('message.error.required.input') }],
cpunumber: [
{ required: true, message: this.$t('message.error.required.input') },
this.naturalNumberRule
],
cpuspeed: [
{ required: true, message: this.$t('message.error.required.input') },
this.wholeNumberRule
],
mincpunumber: [
{ required: true, message: this.$t('message.error.required.input') },
this.naturalNumberRule
],
maxcpunumber: [
{ required: true, message: this.$t('message.error.required.input') },
this.naturalNumberRule
],
memory: [
{ required: true, message: this.$t('message.error.required.input') },
this.naturalNumberRule
],
minmemory: [
{ required: true, message: this.$t('message.error.required.input') },
this.naturalNumberRule
],
maxmemory: [
{ required: true, message: this.$t('message.error.required.input') },
this.naturalNumberRule
],
networkrate: [this.naturalNumberRule],
rootdisksize: [this.naturalNumberRule],
diskbytesreadrate: [this.naturalNumberRule],
diskbyteswriterate: [this.naturalNumberRule],
diskiopsreadrate: [this.naturalNumberRule],
diskiopswriterate: [this.naturalNumberRule],
diskiopsmin: [this.naturalNumberRule],
diskiopsmax: [this.naturalNumberRule],
hypervisorsnapshotreserve: [this.naturalNumberRule],
domainid: [{ type: 'array', required: true, message: this.$t('message.error.select') }],
diskofferingid: [{ required: true, message: this.$t('message.error.select') }],
gpucount: [{
type: 'number',
validator: async (rule, value) => {
if (value && (isNaN(value) || value < 1)) {
return Promise.reject(this.$t('message.error.number.minimum.one'))
}
return Promise.resolve()
}
}],
zoneid: [{
type: 'array',
validator: async (rule, value) => {
if (value && value.length > 1 && value.indexOf(0) !== -1) {
return Promise.reject(this.$t('message.error.zone.combined'))
}
return Promise.resolve()
}
}],
leaseduration: [this.naturalNumberRule]
})
},
fetchData () {
this.fetchDomainData()
this.fetchZoneData()
this.fetchGPUCards()
if (isAdmin()) {
this.fetchStorageTagData()
this.fetchDeploymentPlannerData()
} else if (this.isDomainAdmin()) {
this.checkIfDomainAdminIsAllowedToInformTag()
if (this.isDomainAdminAllowedToInformTags) {
this.fetchStorageTagData()
}
}
this.fetchDiskOfferings()
this.populateFormFromResource()
},
populateFormFromResource () {
if (!this.resource) return
// Pre-fill form with source offering values
const r = this.resource
this.form.name = r.name + ' - Clone'
this.form.displaytext = r.displaytext
if (r.iscustomized) {
if (r.cpunumber || r.cpuspeed || r.memory) {
this.offeringType = 'customconstrained'
this.form.offeringtype = 'customconstrained'
} else {
this.offeringType = 'customunconstrained'
this.form.offeringtype = 'customunconstrained'
}
} else {
this.offeringType = 'fixed'
this.form.offeringtype = 'fixed'
}
if (r.cpunumber) this.form.cpunumber = r.cpunumber
if (r.cpuspeed) this.form.cpuspeed = r.cpuspeed
if (r.memory) this.form.memory = r.memory
if (r.mincpunumber) this.form.mincpunumber = r.mincpunumber
if (r.maxcpunumber) this.form.maxcpunumber = r.maxcpunumber
if (r.minmemory) this.form.minmemory = r.minmemory
if (r.maxmemory) this.form.maxmemory = r.maxmemory
if (r.hosttags) this.form.hosttags = r.hosttags
if (r.networkrate) this.form.networkrate = r.networkrate
if (r.offerha !== undefined) this.form.offerha = r.offerha
if (r.dynamicscalingenabled !== undefined) {
this.form.dynamicscalingenabled = r.dynamicscalingenabled
this.dynamicscalingenabled = r.dynamicscalingenabled
}
if (r.limitcpuuse !== undefined) this.form.limitcpuuse = r.limitcpuuse
if (r.isvolatile !== undefined) this.form.isvolatile = r.isvolatile
if (r.storagetype) {
this.storageType = r.storagetype
this.form.storagetype = r.storagetype
}
if (r.provisioningtype) {
this.provisioningType = r.provisioningtype
this.form.provisioningtype = r.provisioningtype
}
if (r.cachemode) {
this.cacheMode = r.cachemode
this.form.cachemode = r.cachemode
}
if (r.diskofferingstrictness !== undefined) {
this.form.diskofferingstrictness = r.diskofferingstrictness
this.diskofferingstrictness = r.diskofferingstrictness
}
if (r.encryptroot !== undefined) {
this.form.encryptdisk = r.encryptroot
this.encryptdisk = r.encryptroot
}
if (r.diskBytesReadRate || r.diskBytesWriteRate || r.diskIopsReadRate || r.diskIopsWriteRate) {
this.qosType = 'hypervisor'
this.form.qostype = 'hypervisor'
if (r.diskBytesReadRate) this.form.diskbytesreadrate = r.diskBytesReadRate
if (r.diskBytesWriteRate) this.form.diskbyteswriterate = r.diskBytesWriteRate
if (r.diskIopsReadRate) this.form.diskiopsreadrate = r.diskIopsReadRate
if (r.diskIopsWriteRate) this.form.diskiopswriterate = r.diskIopsWriteRate
} else if (r.miniops || r.maxiops) {
this.qosType = 'storage'
this.form.qostype = 'storage'
if (r.miniops) this.form.diskiopsmin = r.miniops
if (r.maxiops) this.form.diskiopsmax = r.maxiops
if (r.hypervisorsnapshotreserve) this.form.hypervisorsnapshotreserve = r.hypervisorsnapshotreserve
}
if (r.iscustomizediops !== undefined) {
this.form.iscustomizeddiskiops = r.iscustomizediops
this.isCustomizedDiskIops = r.iscustomizediops
}
if (r.rootdisksize) this.form.rootdisksize = r.rootdisksize
if (r.tags) {
this.form.storagetags = r.tags.split(',')
}
if (r.gpucardid) {
this.form.gpucardid = r.gpucardid
this.selectedGpuCard = r.gpucardid
if (r.gpucardid) {
this.fetchVgpuProfiles(r.gpucardid)
}
}
if (r.vgpuprofileid) this.form.vgpuprofile = r.vgpuprofileid
if (r.gpucount) this.form.gpucount = r.gpucount
if (r.gpudisplay !== undefined) this.form.gpudisplay = r.gpudisplay
if (r.leaseduration) {
this.form.leaseduration = r.leaseduration
this.showLeaseOptions = true
}
if (r.leaseexpiryaction) this.form.leaseexpiryaction = r.leaseexpiryaction
if (r.purgeresources !== undefined) this.form.purgeresources = r.purgeresources
if (r.vspherestoragepolicy) this.form.storagepolicy = r.vspherestoragepolicy
if (r.systemvmtype) this.form.systemvmtype = r.systemvmtype
if (r.deploymentplanner) {
this.form.deploymentplanner = r.deploymentplanner
this.handleDeploymentPlannerChange(r.deploymentplanner)
}
if (r.serviceofferingdetails && Object.keys(r.serviceofferingdetails).length > 0) {
this.externalDetailsEnabled = true
this.form.externaldetails = r.serviceofferingdetails
}
},
fetchGPUCards () {
this.gpuCardLoading = true
getAPI('listGpuCards', {
}).then(json => {
this.gpuCards = json.listgpucardsresponse.gpucard || []
this.gpuCards.unshift({
id: '',
name: this.$t('label.none')
})
}).finally(() => {
this.gpuCardLoading = false
})
},
fetchDiskOfferings () {
this.diskOfferingLoading = true
getAPI('listDiskOfferings', {
listall: true
}).then(json => {
this.diskOfferings = json.listdiskofferingsresponse.diskoffering || []
if (this.selectedDiskOfferingId === '') {
this.selectedDiskOfferingId = this.diskOfferings?.[0]?.id || ''
}
}).finally(() => {
this.diskOfferingLoading = false
})
},
isAdmin () {
return isAdmin()
},
isDomainAdmin () {
return ['DomainAdmin'].includes(this.$store.getters.userInfo.roletype)
},
checkIfDomainAdminIsAllowedToInformTag () {
const params = { id: store.getters.userInfo.accountid }
getAPI('isAccountAllowedToCreateOfferingsWithTags', params).then(json => {
this.isDomainAdminAllowedToInformTags = json.isaccountallowedtocreateofferingswithtagsresponse.isallowed.isallowed
})
},
fetchDomainData () {
const params = {}
params.listAll = true
params.showicon = true
params.details = 'min'
this.domainLoading = true
getAPI('listDomains', params).then(json => {
const listDomains = json.listdomainsresponse.domain
this.domains = this.domains.concat(listDomains)
}).finally(() => {
this.domainLoading = false
})
},
fetchZoneData () {
const params = {}
params.showicon = true
this.zoneLoading = true
getAPI('listZones', params).then(json => {
const listZones = json.listzonesresponse.zone
if (listZones) {
this.zones = this.zones.concat(listZones)
}
}).finally(() => {
this.zoneLoading = false
})
},
fetchStorageTagData () {
this.storageTagLoading = true
this.storageTags = []
getAPI('listStorageTags').then(json => {
const tags = json.liststoragetagsresponse.storagetag || []
for (const tag of tags) {
if (!this.storageTags.includes(tag.name)) {
this.storageTags.push(tag.name)
}
}
}).finally(() => {
this.storageTagLoading = false
})
},
fetchDeploymentPlannerData () {
this.deploymentPlannerLoading = true
getAPI('listDeploymentPlanners').then(json => {
const planners = json.listdeploymentplannersresponse.deploymentPlanner
this.deploymentPlanners = this.deploymentPlanners.concat(planners)
this.deploymentPlanners.unshift({ name: '' })
this.form.deploymentplanner = this.deploymentPlanners.length > 0 ? this.deploymentPlanners[0].name : ''
}).finally(() => {
this.deploymentPlannerLoading = false
})
},
handleSubmit (e) {
if (e && e.preventDefault) {
e.preventDefault()
}
if (this.loading) return
this.formRef.value.validate().then((values) => {
var params = {
issystem: this.isSystem,
name: values.name,
displaytext: values.displaytext,
storagetype: values.storagetype,
provisioningtype: values.provisioningtype,
cachemode: values.cachemode,
customized: values.offeringtype !== 'fixed',
offerha: values.offerha === true,
limitcpuuse: values.limitcpuuse === true,
dynamicscalingenabled: values.dynamicscalingenabled,
diskofferingstrictness: values.diskofferingstrictness,
encryptroot: values.encryptdisk,
purgeresources: values.purgeresources,
leaseduration: values.leaseduration,
leaseexpiryaction: values.leaseexpiryaction
}
if (values.diskofferingid) {
params.diskofferingid = values.diskofferingid
}
if (values.vgpuprofile) {
params.vgpuprofileid = values.vgpuprofile
}
if (values.gpucount && values.gpucount > 0) {
params.gpucount = values.gpucount
}
if (values.gpudisplay !== undefined) {
params.gpudisplay = values.gpudisplay
}
if (values.offeringtype === 'fixed') {
params.cpunumber = values.cpunumber
params.cpuspeed = values.cpuspeed
params.memory = values.memory
} else {
if (values.cpuspeed != null &&
values.mincpunumber != null &&
values.maxcpunumber != null &&
values.minmemory != null &&
values.maxmemory != null) {
params.cpuspeed = values.cpuspeed
params.mincpunumber = values.mincpunumber
params.maxcpunumber = values.maxcpunumber
params.minmemory = values.minmemory
params.maxmemory = values.maxmemory
}
}
if (values.networkrate != null && values.networkrate.length > 0) {
params.networkrate = values.networkrate
}
if (values.rootdisksize != null && values.rootdisksize.length > 0) {
params.rootdisksize = values.rootdisksize
}
if (values.qostype === 'storage') {
var customIops = values.iscustomizeddiskiops === true
params.customizediops = customIops
if (!customIops) {
if (values.diskiopsmin != null && values.diskiopsmin.length > 0) {
params.miniops = values.diskiopsmin
}
if (values.diskiopsmax != null && values.diskiopsmax.length > 0) {
params.maxiops = values.diskiopsmax
}
if (values.hypervisorsnapshotreserve !== undefined &&
values.hypervisorsnapshotreserve != null && values.hypervisorsnapshotreserve.length > 0) {
params.hypervisorsnapshotreserve = values.hypervisorsnapshotreserve
}
}
} else if (values.qostype === 'hypervisor') {
if (values.diskbytesreadrate != null && values.diskbytesreadrate.length > 0) {
params.bytesreadrate = values.diskbytesreadrate
}
if (values.diskbyteswriterate != null && values.diskbyteswriterate.length > 0) {
params.byteswriterate = values.diskbyteswriterate
}
if (values.diskiopsreadrate != null && values.diskiopsreadrate.length > 0) {
params.iopsreadrate = values.diskiopsreadrate
}
if (values.diskiopswriterate != null && values.diskiopswriterate.length > 0) {
params.iopswriterate = values.diskiopswriterate
}
}
if (values.storagetags != null && values.storagetags.length > 0) {
var tags = values.storagetags.join(',')
params.tags = tags
}
if (values.hosttags != null && values.hosttags.length > 0) {
params.hosttags = values.hosttags
}
if ('deploymentplanner' in values &&
values.deploymentplanner !== undefined &&
values.deploymentplanner != null && values.deploymentplanner.length > 0) {
params.deploymentplanner = values.deploymentplanner
}
if ('deploymentplanner' in values &&
values.deploymentplanner !== undefined &&
values.deploymentplanner === 'ImplicitDedicationPlanner' &&
values.plannermode !== undefined &&
values.plannermode !== '') {
params['serviceofferingdetails[0].key'] = 'ImplicitDedicationMode'
params['serviceofferingdetails[0].value'] = values.plannermode
}
if ('isvolatile' in values && values.isvolatile !== undefined) {
params.isvolatile = values.isvolatile === true
}
if ('systemvmtype' in values && values.systemvmtype !== undefined) {
params.systemvmtype = values.systemvmtype
}
if ('leaseduration' in values && values.leaseduration !== undefined) {
params.leaseduration = values.leaseduration
}
if ('leaseexpiryaction' in values && values.leaseexpiryaction !== undefined) {
params.leaseexpiryaction = values.leaseexpiryaction
}
if (values.ispublic !== true) {
var domainIndexes = values.domainid
var domainId = null
if (domainIndexes && domainIndexes.length > 0) {
var domainIds = []
for (var i = 0; i < domainIndexes.length; i++) {
domainIds = domainIds.concat(this.domains[domainIndexes[i]].id)
}
domainId = domainIds.join(',')
}
if (domainId) {
params.domainid = domainId
}
}
var zoneIndexes = values.zoneid
var zoneId = null
if (zoneIndexes && zoneIndexes.length > 0) {
var zoneIds = []
for (var j = 0; j < zoneIndexes.length; j++) {
zoneIds = zoneIds.concat(this.zones[zoneIndexes[j]].id)
}
zoneId = zoneIds.join(',')
}
if (zoneId) {
params.zoneid = zoneId
}
if (values.storagepolicy) {
params.storagepolicy = values.storagepolicy
}
if (values.externaldetails) {
Object.entries(values.externaldetails).forEach(([key, value]) => {
params['externaldetails[0].' + key] = value
})
}
params.sourceofferingid = this.resource.id
this.loading = true
postAPI('cloneServiceOffering', params).then(json => {
const message = this.isSystem
? `${this.$t('message.clone.service.offering')}: `
: `${this.$t('message.clone.compute.offering')}: `
this.$message.success(message + values.name)
this.$emit('refresh-data')
this.closeAction()
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
})
},
closeAction () {
this.$emit('close-action')
},
async validateNumber (rule, value) {
if (value && (isNaN(value) || value <= 0)) {
return Promise.reject(this.$t('message.error.number'))
}
return Promise.resolve()
}
}
}
</script>
<style scoped lang="scss">
.form-layout {
width: 80vw;
@media (min-width: 800px) {
width: 700px;
}
}
</style>

View File

@ -0,0 +1,279 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div class="form-layout" v-ctrl-enter="handleSubmit">
<a-spin :spinning="loading">
<a-alert
v-if="resource"
type="info"
style="margin-bottom: 16px">
<template #message>
<div style="display: block; width: 100%;">
<div style="display: block; margin-bottom: 8px;">
<strong>{{ $t('message.clone.offering.from') }}: {{ resource.name }}</strong>
</div>
<div style="display: block; font-size: 12px;">
{{ $t('message.clone.offering.edit.hint') }}
</div>
</div>
</template>
</a-alert>
<DiskOfferingForm
ref="formRef"
:initialValues="form"
:apiParams="apiParams"
:isAdmin="isAdmin"
@submit="handleSubmit">
<template #form-actions>
<div :span="24" class="action-button">
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
</div>
</template>
</DiskOfferingForm>
</a-spin>
</div>
</template>
<script>
import DiskOfferingForm from '@/components/offering/DiskOfferingForm'
import { reactive } from 'vue'
import { postAPI } from '@/api'
import { isAdmin } from '@/role'
export default {
name: 'CloneDiskOffering',
components: {
DiskOfferingForm
},
props: {
resource: {
type: Object,
required: true
}
},
data () {
return {
formRef: null,
form: reactive({}),
loading: false
}
},
beforeCreate () {
this.apiParams = this.$getApiParams('cloneDiskOffering')
},
created () {
this.populateFormFromResource()
},
methods: {
populateFormFromResource () {
if (!this.resource) return
const r = this.resource
this.form.name = r.name + ' - Clone'
this.form.displaytext = r.displaytext
if (r.storagetype) {
this.form.storagetype = r.storagetype
}
if (r.provisioningtype) {
this.form.provisioningtype = r.provisioningtype
}
if (r.customized !== undefined) {
this.form.customdisksize = r.customized
}
if (r.disksize) this.form.disksize = r.disksize
if (r.cachemode) {
this.form.writecachetype = r.cachemode
}
if (r.disksizestrictness !== undefined) {
this.form.disksizestrictness = r.disksizestrictness
}
if (r.encrypt !== undefined) {
this.form.encryptdisk = r.encrypt
}
if (r.diskBytesReadRate || r.diskBytesReadRateMax || r.diskBytesWriteRate || r.diskBytesWriteRateMax || r.diskIopsReadRate || r.diskIopsWriteRate) {
this.form.qostype = 'hypervisor'
if (r.diskBytesReadRate) this.form.diskbytesreadrate = r.diskBytesReadRate
if (r.diskBytesReadRateMax) this.form.diskbytesreadratemax = r.diskBytesReadRateMax
if (r.diskBytesWriteRate) this.form.diskbyteswriterate = r.diskBytesWriteRate
if (r.diskBytesWriteRateMax) this.form.diskbyteswriteratemax = r.diskBytesWriteRateMax
if (r.diskIopsReadRate) this.form.diskiopsreadrate = r.diskIopsReadRate
if (r.diskIopsWriteRate) this.form.diskiopswriterate = r.diskIopsWriteRate
} else if (r.miniops || r.maxiops) {
this.form.qostype = 'storage'
if (r.miniops) this.form.diskiopsmin = r.miniops
if (r.maxiops) this.form.diskiopsmax = r.maxiops
if (r.hypervisorsnapshotreserve) this.form.hypervisorsnapshotreserve = r.hypervisorsnapshotreserve
}
if (r.iscustomizediops !== undefined) {
this.form.iscustomizeddiskiops = r.iscustomizediops
}
if (r.tags) {
this.form.tags = r.tags.split(',')
}
if (r.vspherestoragepolicy) this.form.storagepolicy = r.vspherestoragepolicy
},
isAdmin () {
return isAdmin()
},
handleSubmit (e) {
if (e && e.preventDefault) {
e.preventDefault()
}
if (this.loading) return
this.$refs.formRef.validate().then((values) => {
const params = {
sourceofferingid: this.resource.id,
name: values.name
}
if (values.displaytext) {
params.displaytext = values.displaytext
}
if (values.storagetype) {
params.storagetype = values.storagetype
}
if (values.writecachetype) {
params.cachemode = values.writecachetype
}
if (values.provisioningtype) {
params.provisioningtype = values.provisioningtype
}
if (values.customdisksize !== undefined) {
params.customized = values.customdisksize
}
if (values.disksizestrictness !== undefined) {
params.disksizestrictness = values.disksizestrictness
}
if (values.encryptdisk !== undefined) {
params.encrypt = values.encryptdisk
}
if (values.customdisksize !== true && values.disksize) {
params.disksize = values.disksize
}
if (values.qostype === 'storage') {
const customIops = values.iscustomizeddiskiops === true
params.customizediops = customIops
if (!customIops) {
if (values.diskiopsmin != null && values.diskiopsmin.length > 0) {
params.miniops = values.diskiopsmin
}
if (values.diskiopsmax != null && values.diskiopsmax.length > 0) {
params.maxiops = values.diskiopsmax
}
if (values.hypervisorsnapshotreserve != null && values.hypervisorsnapshotreserve.length > 0) {
params.hypervisorsnapshotreserve = values.hypervisorsnapshotreserve
}
}
} else if (values.qostype === 'hypervisor') {
if (values.diskbytesreadrate != null && values.diskbytesreadrate.length > 0) {
params.bytesreadrate = values.diskbytesreadrate
}
if (values.diskbytesreadratemax != null && values.diskbytesreadratemax.length > 0) {
params.bytesreadratemax = values.diskbytesreadratemax
}
if (values.diskbyteswriterate != null && values.diskbyteswriterate.length > 0) {
params.byteswriterate = values.diskbyteswriterate
}
if (values.diskbyteswriteratemax != null && values.diskbyteswriteratemax.length > 0) {
params.byteswriteratemax = values.diskbyteswriteratemax
}
if (values.diskiopsreadrate != null && values.diskiopsreadrate.length > 0) {
params.iopsreadrate = values.diskiopsreadrate
}
if (values.diskiopswriterate != null && values.diskiopswriterate.length > 0) {
params.iopswriterate = values.diskiopswriterate
}
}
if (values.tags != null && values.tags.length > 0) {
const tags = values.tags.join(',')
params.tags = tags
}
if (values.ispublic !== true) {
const domainIndexes = values.domainid
let domainId = null
if (domainIndexes && domainIndexes.length > 0) {
const domainIds = []
const domains = this.$refs.formRef.domains
for (let i = 0; i < domainIndexes.length; i++) {
domainIds.push(domains[domainIndexes[i]].id)
}
domainId = domainIds.join(',')
}
if (domainId) {
params.domainid = domainId
}
}
const zoneIndexes = values.zoneid
let zoneId = null
if (zoneIndexes && zoneIndexes.length > 0) {
const zoneIds = []
const zones = this.$refs.formRef.zones
for (let j = 0; j < zoneIndexes.length; j++) {
zoneIds.push(zones[zoneIndexes[j]].id)
}
zoneId = zoneIds.join(',')
}
if (zoneId) {
params.zoneid = zoneId
}
if (values.storagepolicy) {
params.storagepolicy = values.storagepolicy
}
this.loading = true
postAPI('cloneDiskOffering', params).then(json => {
this.$message.success(`${this.$t('message.success.clone.disk.offering')} ${values.name}`)
this.$emit('refresh-data')
this.closeAction()
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
})
},
closeAction () {
this.$emit('close-action')
}
}
}
</script>
<style scoped lang="less">
.form-layout {
width: 80vw;
@media (min-width: 700px) {
width: 550px;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,854 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div class="form-layout" v-ctrl-enter="handleSubmit">
<a-spin :spinning="loading">
<a-alert
v-if="resource"
type="info"
style="margin-bottom: 16px">
<template #message>
<div style="display: block; width: 100%;">
<div style="display: block; margin-bottom: 8px;">
<strong>{{ $t('message.clone.offering.from') }}: {{ resource.name }}</strong>
</div>
<div style="display: block; font-size: 12px;">
{{ $t('message.clone.offering.edit.hint') }}
</div>
</div>
</template>
</a-alert>
<a-form
:ref="formRef"
:model="form"
:rules="rules"
@finish="handleSubmit"
layout="vertical"
>
<a-form-item name="name" ref="name">
<template #label>
<tooltip-label :title="$t('label.name')" :tooltip="apiParams.name.description"/>
</template>
<a-input
v-focus="true"
v-model:value="form.name"
:placeholder="apiParams.name.description"/>
</a-form-item>
<a-form-item name="displaytext" ref="displaytext">
<template #label>
<tooltip-label :title="$t('label.displaytext')" :tooltip="apiParams.displaytext.description"/>
</template>
<a-input
v-model:value="form.displaytext"
:placeholder="apiParams.displaytext.description"/>
</a-form-item>
<a-form-item name="internetprotocol" ref="internetprotocol">
<template #label>
<tooltip-label :title="$t('label.internetprotocol')" :tooltip="apiParams.internetprotocol.description"/>
</template>
<span v-if="!ipv6NetworkOfferingEnabled || internetProtocolValue!=='ipv4'">
<a-alert type="warning">
<template #message>
<span v-html="ipv6NetworkOfferingEnabled ? $t('message.offering.internet.protocol.warning') : $t('message.offering.ipv6.warning')" />
</template>
</a-alert>
<br/>
</span>
<a-radio-group
v-model:value="form.internetprotocol"
:disabled="!ipv6NetworkOfferingEnabled"
buttonStyle="solid"
@change="e => { internetProtocolValue = e.target.value }" >
<a-radio-button value="ipv4">
{{ $t('label.ip.v4') }}
</a-radio-button>
<a-radio-button value="dualstack">
{{ $t('label.ip.v4.v6') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-row :gutter="12">
<a-col :md="12" :lg="12">
<a-form-item name="provider" ref="provider">
<template #label>
<tooltip-label :title="$t('label.provider')" :tooltip="apiParams.provider.description"/>
</template>
<a-select
v-model:value="form.provider"
@change="val => handleProviderChange(val)"
showSearch
disabled
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:placeholder="apiParams.provider.description" >
<a-select-option key="" value="">{{ $t('label.none') }}</a-select-option>
<a-select-option :value="'NSX'" :label="$t('label.nsx')"> {{ $t('label.nsx') }} </a-select-option>
<a-select-option :value="'Netris'" :label="$t('label.netris')"> {{ $t('label.netris') }} </a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :md="12" :lg="12" v-if="form.provider === 'NSX'">
<a-form-item name="nsxsupportlb" ref="nsxsupportlb">
<template #label>
<tooltip-label :title="$t('label.nsx.supports.lb')" :tooltip="apiParams.nsxsupportlb.description"/>
</template>
<a-switch v-model:checked="form.nsxsupportlb" @change="val => { handleNsxLbService(val) }" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="12" v-if="routingMode === 'dynamic' && form.provider === 'NSX'">
<a-col :md="12" :lg="12">
<a-form-item name="specifyasnumber" ref="specifyasnumber">
<template #label>
<tooltip-label :title="$t('label.specifyasnumber')"/>
</template>
<a-switch v-model:checked="form.specifyasnumber" />
</a-form-item>
</a-col>
</a-row>
<a-form-item name="networkmode" ref="networkmode">
<template #label>
<tooltip-label :title="$t('label.networkmode')" :tooltip="apiParams.networkmode.description"/>
</template>
<a-select
optionFilterProp="label"
v-model:value="form.networkmode"
:disabled="provider === 'NSX' || provider === 'Netris'"
@change="val => { handleForNetworkModeChange(val) }"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:placeholder="apiParams.networkmode.description">
<a-select-option v-for="(opt) in networkmodes" :key="opt.name" :label="opt.name">
{{ opt.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="routingmode" ref="routingmode" v-if="networkmode === 'ROUTED' || internetProtocolValue === 'ipv6' || internetProtocolValue === 'dualstack'">
<template #label>
<tooltip-label :title="$t('label.routingmode')" :tooltip="apiParams.routingmode.description"/>
</template>
<a-radio-group
v-model:value="form.routingmode"
buttonStyle="solid"
@change="selected => { routingMode = selected.target.value }">
<a-radio-button value="static">
{{ $t('label.static') }}
</a-radio-button>
<a-radio-button value="dynamic">
{{ $t('label.dynamic') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item>
<template #label>
<tooltip-label :title="$t('label.supportedservices')" :tooltip="apiParams.supportedservices ? apiParams.supportedservices.description : ''"/>
</template>
<div class="supported-services-container" scroll-to="last-child">
<a-spin v-if="!servicesReady" :spinning="true" />
<a-list v-else itemLayout="horizontal" :dataSource="supportedServices">
<template #renderItem="{ item }">
<a-list-item>
<CheckBoxSelectPair
:key="`${item.name}-${item.selectedProvider || 'none'}`"
:resourceKey="item.name"
:checkBoxLabel="item.description"
:forExternalNetProvider="form.provider === 'NSX' || form.provider === 'Netris'"
:defaultCheckBoxValue="item.defaultChecked"
:defaultSelectValue="item.selectedProvider"
:selectOptions="item.provider"
@handle-checkselectpair-change="handleSupportedServiceChange"/>
</a-list-item>
</template>
</a-list>
</div>
</a-form-item>
<a-form-item name="regionlevelvpc" ref="regionlevelvpc" :label="$t('label.service.connectivity.regionlevelvpccapabilitycheckbox')" v-if="connectivityServiceChecked">
<a-switch v-model:checked="form.regionlevelvpc" />
</a-form-item>
<a-form-item name="distributedrouter" ref="distributedrouter" :label="$t('label.service.connectivity.distributedroutercapabilitycheckbox')" v-if="connectivityServiceChecked">
<a-switch v-model:checked="form.distributedrouter" />
</a-form-item>
<a-form-item name="redundantrouter" ref="redundantrouter" :label="$t('label.redundantrouter')" v-if="sourceNatServiceChecked">
<a-switch v-model:checked="form.redundantrouter" />
</a-form-item>
<a-form-item name="serviceofferingid" ref="serviceofferingid">
<a-alert v-if="!isVpcVirtualRouterForAtLeastOneService" type="warning" style="margin-bottom: 10px">
<template #message>
<span v-html="$t('message.vr.alert.upon.network.offering.creation.others')" />
</template>
</a-alert>
<template #label>
<tooltip-label :title="$t('label.serviceofferingid')" :tooltip="apiParams.serviceofferingid.description"/>
</template>
<a-select
showSearch
optionFilterProp="label"
v-model:value="form.serviceofferingid"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="serviceOfferingLoading"
:placeholder="apiParams.serviceofferingid.description">
<a-select-option v-for="(opt) in serviceOfferings" :key="opt.id" :label="opt.name || opt.description">
{{ opt.name || opt.description }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="ispublic" ref="ispublic" :label="$t('label.ispublic')" v-if="isAdmin()">
<a-switch v-model:checked="form.ispublic" @change="val => { isPublic = val }" />
</a-form-item>
<a-form-item name="domainid" ref="domainid" v-if="!isPublic">
<template #label>
<tooltip-label :title="$t('label.domainid')" :tooltip="apiParams.domainid.description"/>
</template>
<a-select
mode="multiple"
v-model:value="form.domainid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="domainLoading"
:placeholder="apiParams.domainid.description">
<a-select-option v-for="(opt, optIndex) in domains" :key="optIndex" :label="opt.path || opt.name || opt.description">
<span>
<resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
<block-outlined v-else style="margin-right: 5px" />
{{ opt.path || opt.name || opt.description }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="zoneid" ref="zoneid">
<template #label>
<tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid.description"/>
</template>
<a-select
id="zone-selection"
mode="multiple"
v-model:value="form.zoneid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="zoneLoading"
:placeholder="apiParams.zoneid.description">
<a-select-option v-for="(opt, optIndex) in zones" :key="optIndex" :label="opt.name || opt.description">
<span>
<resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px"/>
{{ opt.name || opt.description }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="enable" ref="enable" v-if="apiParams.enable">
<template #label>
<tooltip-label :title="$t('label.enable.vpc.offering')" :tooltip="apiParams.enable.description"/>
</template>
<a-switch v-model:checked="form.enable" />
</a-form-item>
</a-form>
<div :span="24" class="action-button">
<a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
</div>
</a-spin>
</div>
</template>
<script>
import { ref, reactive, toRaw } from 'vue'
import { getAPI, postAPI } from '@/api'
import { isAdmin } from '@/role'
import { mixinForm } from '@/utils/mixin'
import CheckBoxSelectPair from '@/components/CheckBoxSelectPair'
import ResourceIcon from '@/components/view/ResourceIcon'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import { BlockOutlined, GlobalOutlined } from '@ant-design/icons-vue'
import { buildVpcServiceCapabilityParams } from '@/composables/useServiceCapabilityParams'
export default {
name: 'CloneVpcOffering',
mixins: [mixinForm],
components: {
CheckBoxSelectPair,
ResourceIcon,
TooltipLabel,
BlockOutlined,
GlobalOutlined
},
props: {
resource: {
type: Object,
required: true
}
},
data () {
return {
selectedDomains: [],
selectedZones: [],
isConserveMode: true,
internetProtocolValue: 'ipv4',
domains: [],
domainLoading: false,
zones: [],
zoneLoading: false,
forNsx: false,
provider: '',
loading: false,
supportedServices: [],
supportedServiceLoading: false,
servicesReady: false,
serviceOfferings: [],
serviceOfferingLoading: false,
isVpcVirtualRouterForAtLeastOneService: false,
connectivityServiceChecked: false,
sourceNatServiceChecked: false,
selectedServiceProviderMap: {},
serviceProviderMap: {},
ipv6NetworkOfferingEnabled: false,
routedNetworkEnabled: false,
routingMode: 'static',
networkmode: '',
networkmodes: [
{
id: 0,
name: 'NATTED'
},
{
id: 1,
name: 'ROUTED'
}
],
isPublic: true,
VPCVR: {
name: 'VpcVirtualRouter',
description: 'VpcVirtualRouter',
enabled: true
},
NSX: {
name: 'Nsx',
description: 'Nsx',
enabled: true
},
Netris: {
name: 'Netris',
description: 'Netris',
enabled: true
}
}
},
beforeCreate () {
this.apiParams = this.$getApiParams('cloneVPCOffering')
},
created () {
this.zones = [
{
id: null,
name: this.$t('label.all.zone')
}
]
this.isPublic = isAdmin()
this.initForm()
this.fetchData()
},
methods: {
initForm () {
this.formRef = ref()
this.form = reactive({
regionlevelvpc: true,
distributedrouter: true,
ispublic: this.isPublic,
internetprotocol: this.internetProtocolValue,
nsxsupportlb: true,
routingmode: 'static',
domainid: [],
zoneid: []
})
this.rules = reactive({
name: [{ required: true, message: this.$t('message.error.name') }],
domainid: [{ type: 'array', required: true, message: this.$t('message.error.select') }],
zoneid: [{
type: 'array',
validator: async (rule, value) => {
if (value && value.length > 1 && value.indexOf(0) !== -1) {
return Promise.reject(this.$t('message.error.zone.combined'))
}
return Promise.resolve()
}
}]
})
},
fetchData () {
this.fetchDomainData()
this.fetchZoneData()
this.fetchIpv6NetworkOfferingConfiguration()
this.fetchRoutedNetworkConfiguration()
this.fetchSupportedServiceData()
},
isAdmin () {
return isAdmin()
},
fetchIpv6NetworkOfferingConfiguration () {
this.ipv6NetworkOfferingEnabled = false
const params = { name: 'ipv6.offering.enabled' }
getAPI('listConfigurations', params).then(json => {
const value = json?.listconfigurationsresponse?.configuration?.[0].value || null
this.ipv6NetworkOfferingEnabled = value === 'true'
})
},
fetchRoutedNetworkConfiguration () {
this.routedNetworkEnabled = false
const params = { name: 'routed.network.vpc.enabled' }
getAPI('listConfigurations', params).then(json => {
const value = json?.listconfigurationsresponse?.configuration?.[0].value || null
this.routedNetworkEnabled = value === 'true'
if (!this.routedNetworkEnabled) {
this.networkmodes.pop()
}
})
},
fetchDomainData () {
const params = {}
params.listAll = true
params.showicon = true
params.details = 'min'
this.domainLoading = true
getAPI('listDomains', params).then(json => {
const listDomains = json.listdomainsresponse.domain
this.domains = this.domains.concat(listDomains)
}).finally(() => {
this.domainLoading = false
})
},
fetchZoneData () {
const params = {}
params.showicon = true
this.zoneLoading = true
getAPI('listZones', params).then(json => {
const listZones = json.listzonesresponse.zone
if (listZones) {
this.zones = this.zones.concat(listZones)
}
}).finally(() => {
this.zoneLoading = false
})
},
fetchSupportedServiceData () {
this.supportedServiceLoading = true
getAPI('listSupportedNetworkServices', {}).then(json => {
const networkServices = json.listsupportednetworkservicesresponse.networkservice || []
let services = []
if (this.provider === 'NSX') {
services = this.buildNsxServices(networkServices)
} else if (this.provider === 'Netris') {
services = this.buildNetrisServices(networkServices)
} else {
services = this.buildDefaultServices(networkServices)
}
if (this.networkmode === 'ROUTED') {
services = services.filter(service => {
return !['SourceNat', 'StaticNat', 'Lb', 'PortForwarding', 'Vpn'].includes(service.name)
})
if (['NSX', 'Netris'].includes(this.provider)) {
services.push({
name: 'Gateway',
description: 'Gateway',
enabled: true,
provider: [{ name: this.provider }]
})
}
}
for (const i in services) {
services[i].description = services[i].name
}
this.supportedServices = services
this.supportedServiceLoading = false
this.$nextTick(() => {
this.populateFormFromResource()
})
})
},
buildNsxServices (networkServices) {
return [
{ name: 'Dhcp', enabled: true, provider: [{ name: 'VpcVirtualRouter' }] },
{ name: 'Dns', enabled: true, provider: [{ name: 'VpcVirtualRouter' }] },
{ name: 'Lb', enabled: true, provider: [{ name: 'Nsx' }] },
{ name: 'StaticNat', enabled: true, provider: [{ name: 'Nsx' }] },
{ name: 'SourceNat', enabled: true, provider: [{ name: 'Nsx' }] },
{ name: 'NetworkACL', enabled: true, provider: [{ name: 'Nsx' }] },
{ name: 'PortForwarding', enabled: true, provider: [{ name: 'Nsx' }] },
{ name: 'UserData', enabled: true, provider: [{ name: 'VpcVirtualRouter' }] }
]
},
buildNetrisServices (networkServices) {
return [
{ name: 'Dhcp', enabled: true, provider: [{ name: 'VpcVirtualRouter' }] },
{ name: 'Dns', enabled: true, provider: [{ name: 'VpcVirtualRouter' }] },
{ name: 'Lb', enabled: true, provider: [{ name: 'Netris' }] },
{ name: 'StaticNat', enabled: true, provider: [{ name: 'Netris' }] },
{ name: 'SourceNat', enabled: true, provider: [{ name: 'Netris' }] },
{ name: 'NetworkACL', enabled: true, provider: [{ name: 'Netris' }] },
{ name: 'PortForwarding', enabled: true, provider: [{ name: 'Netris' }] },
{ name: 'UserData', enabled: true, provider: [{ name: 'VpcVirtualRouter' }] }
]
},
buildDefaultServices (networkServices) {
return [
{ name: 'Dhcp', provider: [{ name: 'VpcVirtualRouter' }, { name: 'ConfigDrive' }] },
{ name: 'Dns', provider: [{ name: 'VpcVirtualRouter' }, { name: 'ConfigDrive' }] },
{ name: 'Lb', provider: [{ name: 'VpcVirtualRouter' }, { name: 'InternalLbVm' }] },
{ name: 'Gateway', provider: [{ name: 'VpcVirtualRouter' }, { name: 'BigSwitchBcf' }] },
{ name: 'StaticNat', provider: [{ name: 'VpcVirtualRouter' }, { name: 'BigSwitchBcf' }] },
{ name: 'SourceNat', provider: [{ name: 'VpcVirtualRouter' }, { name: 'BigSwitchBcf' }] },
{ name: 'NetworkACL', provider: [{ name: 'VpcVirtualRouter' }, { name: 'BigSwitchBcf' }] },
{ name: 'PortForwarding', provider: [{ name: 'VpcVirtualRouter' }] },
{ name: 'UserData', provider: [{ name: 'VpcVirtualRouter' }, { name: 'ConfigDrive' }] },
{ name: 'Vpn', provider: [{ name: 'VpcVirtualRouter' }, { name: 'BigSwitchBcf' }] },
{ name: 'Connectivity', provider: [{ name: 'BigSwitchBcf' }, { name: 'NiciraNvp' }, { name: 'Ovs' }, { name: 'JuniperContrailVpcRouter' }] }
]
},
populateFormFromResource () {
if (!this.resource) return
const r = this.resource
this.form.name = r.name + ' - Clone'
this.form.displaytext = r.displaytext || r.name
if (r.internetprotocol) {
this.form.internetprotocol = r.internetprotocol.toLowerCase()
this.internetProtocolValue = r.internetprotocol.toLowerCase()
}
if (r.service && Array.isArray(r.service)) {
const networkAclService = r.service.find(svc => svc.name === 'NetworkACL')
if (networkAclService && networkAclService.provider && networkAclService.provider.length > 0) {
const providerName = networkAclService.provider[0].name
if (providerName === 'Nsx') {
this.provider = 'NSX'
this.form.provider = 'NSX'
} else if (providerName === 'Netris') {
this.provider = 'Netris'
this.form.provider = 'Netris'
}
}
}
if (r.networkmode) {
this.networkmode = r.networkmode
this.form.networkmode = r.networkmode
}
if (r.routingmode) {
this.routingMode = r.routingmode
this.form.routingmode = r.routingmode
}
if (r.serviceofferingid) {
this.form.serviceofferingid = r.serviceofferingid
}
if (r.service && Array.isArray(r.service)) {
const sourceServiceMap = {}
r.service.forEach(svc => {
if (svc.provider && svc.provider.length > 0) {
const providerName = svc.provider[0].name
sourceServiceMap[svc.name] = providerName
}
})
this.serviceProviderMap = sourceServiceMap
const updatedServices = this.supportedServices.map(svc => {
const serviceCopy = { ...svc, provider: [...svc.provider] }
if (sourceServiceMap[serviceCopy.name]) {
const providerName = sourceServiceMap[serviceCopy.name]
const providerIndex = serviceCopy.provider.findIndex(p => p.name === providerName)
if (providerIndex > 0) {
const targetProvider = serviceCopy.provider[providerIndex]
serviceCopy.provider.splice(providerIndex, 1)
serviceCopy.provider.unshift(targetProvider)
}
serviceCopy.defaultChecked = true
serviceCopy.selectedProvider = providerName
this.selectedServiceProviderMap[serviceCopy.name] = providerName
} else {
serviceCopy.defaultChecked = false
serviceCopy.selectedProvider = null
}
return serviceCopy
})
this.supportedServices = updatedServices
this.connectivityServiceChecked = Boolean(this.selectedServiceProviderMap.Connectivity)
this.sourceNatServiceChecked = Boolean(this.selectedServiceProviderMap.SourceNat)
this.$nextTick(() => {
this.servicesReady = true
this.$nextTick(() => {
this.checkVpcVirtualRouterForServices()
})
})
}
if (r.service && Array.isArray(r.service)) {
const connectivityService = r.service.find(svc => svc.name === 'Connectivity')
if (connectivityService && connectivityService.capability) {
const regionLevelCapability = connectivityService.capability.find(cap => cap.name === 'RegionLevelVpc')
if (regionLevelCapability) {
this.form.regionlevelvpc = regionLevelCapability.value === 'true'
}
const distributedRouterCapability = connectivityService.capability.find(cap => cap.name === 'DistributedRouter')
if (distributedRouterCapability) {
this.form.distributedrouter = distributedRouterCapability.value === 'true'
}
}
const sourceNatService = r.service.find(svc => svc.name === 'SourceNat')
if (sourceNatService && sourceNatService.capability) {
const redundantRouterCapability = sourceNatService.capability.find(cap => cap.name === 'RedundantRouter')
if (redundantRouterCapability) {
this.form.redundantrouter = redundantRouterCapability.value === 'true'
}
}
const gatewayService = r.service.find(svc => svc.name === 'Gateway')
if (gatewayService && gatewayService.capability) {
const redundantRouterCapability = gatewayService.capability.find(cap => cap.name === 'RedundantRouter')
if (redundantRouterCapability) {
this.form.redundantrouter = redundantRouterCapability.value === 'true'
}
}
}
if (this.provider === 'NSX') {
this.form.nsxsupportlb = Boolean(this.serviceProviderMap.Lb)
}
},
async handleProviderChange (value) {
this.provider = value
if (this.provider === 'NSX') {
this.form.nsxsupportlb = true
this.handleNsxLbService(true)
}
this.fetchSupportedServiceData()
},
handleNsxLbService (supportLb) {
if (!supportLb) {
this.supportedServices = this.supportedServices.filter(svc => svc.name !== 'Lb')
delete this.selectedServiceProviderMap.Lb
} else {
const lbExists = this.supportedServices.some(svc => svc.name === 'Lb')
if (!lbExists) {
this.supportedServices.push({
name: 'Lb',
description: 'Lb',
enabled: true,
provider: [{ name: 'Nsx' }]
})
}
}
},
handleSupportedServiceChange (service, checked, provider) {
if (checked) {
const correctProvider = this.serviceProviderMap[service]
if (correctProvider && provider !== correctProvider) {
this.selectedServiceProviderMap[service] = correctProvider
} else {
this.selectedServiceProviderMap[service] = provider
}
} else {
delete this.selectedServiceProviderMap[service]
}
if (service === 'Connectivity') {
this.connectivityServiceChecked = checked
}
if (service === 'SourceNat') {
this.sourceNatServiceChecked = checked
}
this.checkVpcVirtualRouterForServices()
},
checkVpcVirtualRouterForServices () {
this.isVpcVirtualRouterForAtLeastOneService = false
const providers = Object.values(this.selectedServiceProviderMap)
for (const provider of providers) {
if (provider === 'VpcVirtualRouter') {
this.isVpcVirtualRouterForAtLeastOneService = true
break
}
}
if (this.isVpcVirtualRouterForAtLeastOneService && this.serviceOfferings.length === 0) {
this.fetchServiceOfferingData()
}
},
handleForNetworkModeChange (networkMode) {
this.networkmode = networkMode
this.fetchSupportedServiceData()
},
fetchServiceOfferingData () {
const params = {}
params.issystem = true
params.systemvmtype = 'domainrouter'
this.serviceOfferingLoading = true
getAPI('listServiceOfferings', params).then(json => {
const listServiceOfferings = json.listserviceofferingsresponse.serviceoffering
this.serviceOfferings = this.serviceOfferings.concat(listServiceOfferings)
}).finally(() => {
this.serviceOfferingLoading = false
})
},
handleSubmit (e) {
if (e) {
e.preventDefault()
}
if (this.loading) return
this.formRef.value.validate().then(() => {
const formRaw = toRaw(this.form)
const values = this.handleRemoveFields(formRaw)
const params = {
sourceofferingid: this.resource.id,
name: values.name
}
if (values.displaytext) {
params.displaytext = values.displaytext
}
if (values.ispublic !== true) {
const domainIndexes = values.domainid
let domainId = null
if (domainIndexes && domainIndexes.length > 0) {
const domainIds = []
for (let i = 0; i < domainIndexes.length; i++) {
domainIds.push(this.domains[domainIndexes[i]].id)
}
domainId = domainIds.join(',')
}
if (domainId) {
params.domainid = domainId
}
}
const zoneIndexes = values.zoneid
let zoneId = null
if (zoneIndexes && zoneIndexes.length > 0) {
const zoneIds = []
for (let j = 0; j < zoneIndexes.length; j++) {
zoneIds.push(this.zones[zoneIndexes[j]].id)
}
zoneId = zoneIds.join(',')
}
if (zoneId) {
params.zoneid = zoneId
}
if (values.internetprotocol) {
params.internetprotocol = values.internetprotocol
}
const forNsx = values.provider === 'NSX'
if (forNsx) {
params.provider = 'NSX'
params.nsxsupportlb = values.nsxsupportlb
}
const forNetris = values.provider === 'Netris'
if (forNetris) {
params.provider = 'Netris'
}
if (values.networkmode) {
params.networkmode = values.networkmode
}
if (values.specifyasnumber !== undefined) {
params.specifyasnumber = values.specifyasnumber
}
if (values.routingmode) {
params.routingmode = values.routingmode
}
if (this.selectedServiceProviderMap != null) {
const supportedServices = Object.keys(this.selectedServiceProviderMap)
if (!forNsx && !forNetris) {
params.supportedservices = supportedServices.join(',')
}
for (const k in supportedServices) {
params['serviceProviderList[' + k + '].service'] = supportedServices[k]
params['serviceProviderList[' + k + '].provider'] = this.selectedServiceProviderMap[supportedServices[k]]
}
buildVpcServiceCapabilityParams(params, values, this.selectedServiceProviderMap, this.isVpcVirtualRouterForAtLeastOneService)
} else {
params.supportedservices = ''
}
if (values.enable !== undefined) {
params.enable = values.enable
}
this.loading = true
postAPI('cloneVPCOffering', params).then(json => {
this.$message.success(`${this.$t('message.success.clone.vpc.offering')} ${values.name}`)
this.$emit('refresh-data')
this.closeAction()
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
}).catch(error => {
this.formRef.value.scrollToField(error.errorFields[0].name)
})
},
closeAction () {
this.$emit('close-action')
}
}
}
</script>
<style scoped lang="scss">
.form-layout {
width: 80vw;
@media (min-width: 800px) {
width: 500px;
}
}
.supported-services-container {
height: 250px;
overflow: auto;
}
</style>