mirror of https://github.com/apache/cloudstack.git
Add support to clone existing offerings and update them (#12357)
* Add support to clone existing offerings and update them * add support for vpc & backup offerings to be cloned * fix capability list and mapping of params * Add support to clone network and vpc offering with the right parameters * make fields non mandatory for clone offerings APIs * Add UI support for cloning Compute and System Service offerings * remove unnecessary changes * fix license and pre-ccommit issues * Add UI support to clone disk and network offering * vpc & backup offering clone api * add unit tests * fix pre-commit checks * increase test coverage * combine add/clone disk/compute offering forms * update license * fix unit tests * fix test failures * fix test failure - unnecessary stubbings * pre-commit check failure * add recently added domain id for bkp offering to be inherited in clone operation * extract common code wrt service capability in network & vpc offering in add/clone operations * add some checks to prevent networkmode change when provider is nsx/netris from the source networkmode * address copilot comments * address comments * combine check * use appropriate zoneId during clone bkp offering * add check * fix issue with test * remove unused imports * prevent creating a bkp offering of a bkp repo that already exists * extend clone disk and service offerings to domain admins
This commit is contained in:
parent
93239e09f1
commit
3bd5410f9a
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -375,11 +375,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";
|
||||
|
||||
|
|
@ -400,6 +402,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";
|
||||
|
|
@ -599,6 +602,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";
|
||||
|
||||
|
|
@ -631,6 +635,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";
|
||||
|
|
@ -1046,11 +1051,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);
|
||||
|
||||
|
|
@ -1071,6 +1078,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);
|
||||
|
|
|
|||
|
|
@ -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,6 +35,8 @@ 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,
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -185,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(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -635,6 +636,12 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
|
|||
final String externalProvider, final NetworkOffering.NetworkMode networkMode, List<Long> domainIds, List<Long> zoneIds, State state,
|
||||
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()));
|
||||
}
|
||||
|
|
@ -813,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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -256,6 +256,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",
|
||||
|
|
@ -575,6 +576,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",
|
||||
|
|
@ -950,6 +957,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.",
|
||||
|
|
@ -3267,6 +3275,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",
|
||||
|
|
@ -3387,6 +3399,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>",
|
||||
|
|
|
|||
|
|
@ -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 () {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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',
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = ''
|
||||
|
|
|
|||
|
|
@ -277,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',
|
||||
|
|
@ -742,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 = []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
|
@ -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>
|
||||
Loading…
Reference in New Issue