add support for vpc & backup offerings to be cloned

This commit is contained in:
Pearl Dsilva 2026-01-06 11:14:27 -05:00
parent e13104bfea
commit afe6c86990
12 changed files with 604 additions and 11 deletions

View File

@ -631,6 +631,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_CLONE_OFFERING = "BACKUP.CLONE.OFFERING";
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";

View File

@ -20,6 +20,7 @@ package com.cloud.network.vpc;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.api.command.admin.vpc.CloneVPCOfferingCmd;
import org.apache.cloudstack.api.command.admin.vpc.CreateVPCOfferingCmd;
import org.apache.cloudstack.api.command.admin.vpc.UpdateVPCOfferingCmd;
import org.apache.cloudstack.api.command.user.vpc.ListVPCOfferingsCmd;
@ -34,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,

View File

@ -0,0 +1,99 @@
// 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 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.BackupOfferingResponse;
import org.apache.cloudstack.backup.BackupOffering;
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;
@APICommand(name = "cloneBackupOffering",
description = "Clones an existing backup offering with updated values. " +
"All parameters are copied from the source offering unless explicitly overridden.",
responseObject = BackupOfferingResponse.class,
since = "4.23.0",
authorized = {RoleType.Admin})
public class CloneBackupOfferingCmd extends ImportBackupOfferingCmd {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
entityType = BackupOfferingResponse.class,
required = true,
description = "The ID of the backup offering to clone")
private Long id;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException,
ServerApiException, ConcurrentOperationException, ResourceAllocationException,
NetworkRuleConflictException {
try {
BackupOffering clonedOffering = backupManager.cloneBackupOffering(this);
if (clonedOffering != null) {
BackupOfferingResponse response = _responseGenerator.createBackupOfferingResponse(clonedOffering);
response.setResponseName(getCommandName());
setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to clone backup offering");
}
} catch (InvalidParameterValueException e) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, e.getMessage());
} catch (CloudRuntimeException e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
}
}
@Override
public String getEventType() {
return EventTypes.EVENT_VM_BACKUP_CLONE_OFFERING;
}
@Override
public String getEventDescription() {
return "Cloning backup offering from ID: " + id + " to new offering: " + getName();
}
}

View File

@ -54,7 +54,7 @@ import java.util.Set;
public class ImportBackupOfferingCmd extends BaseAsyncCmd {
@Inject
private BackupManager backupManager;
protected BackupManager backupManager;
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////

View File

@ -62,6 +62,7 @@ public class CloneNetworkOfferingCmd extends CreateNetworkOfferingCmd {
"If specified along with 'supportedservices', this parameter is ignored.")
private List<String> dropServices;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -78,6 +79,24 @@ public class CloneNetworkOfferingCmd extends CreateNetworkOfferingCmd {
return dropServices;
}
/**
* Override to provide placeholder values that will be replaced with source offering values.
* This allows API validation to pass even though these are marked as required in the parent class.
*/
@Override
public String getGuestIpType() {
String value = super.getGuestIpType();
// Return placeholder if not provided - will be overwritten from source offering
return value != null ? value : "Isolated";
}
@Override
public String getTraffictype() {
String value = super.getTraffictype();
// Return placeholder if not provided - will be overwritten from source offering
return value != null ? value : "Guest";
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////

View File

@ -128,7 +128,7 @@ public class CreateNetworkOfferingCmd extends BaseCmd {
private Map serviceProviderList;
@Parameter(name = ApiConstants.SERVICE_CAPABILITY_LIST, type = CommandType.MAP, description = "Desired service capabilities as part of network offering")
private Map serviceCapabilitystList;
private Map serviceCapabilitiesList;
@Parameter(name = ApiConstants.SPECIFY_IP_RANGES,
type = CommandType.BOOLEAN,
@ -423,9 +423,9 @@ public class CreateNetworkOfferingCmd extends BaseCmd {
public Map<Capability, String> getServiceCapabilities(Service service) {
Map<Capability, String> capabilityMap = null;
if (serviceCapabilitystList != null && !serviceCapabilitystList.isEmpty()) {
if (serviceCapabilitiesList != null && !serviceCapabilitiesList.isEmpty()) {
capabilityMap = new HashMap<Capability, String>();
Collection serviceCapabilityCollection = serviceCapabilitystList.values();
Collection serviceCapabilityCollection = serviceCapabilitiesList.values();
Iterator iter = serviceCapabilityCollection.iterator();
while (iter.hasNext()) {
HashMap<String, String> svcCapabilityMap = (HashMap<String, String>) iter.next();

View File

@ -0,0 +1,95 @@
// 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 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 VPC offering to clone")
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 execute() {
VpcOffering result = _vpcProvSvc.cloneVPCOffering(this);
if (result != null) {
VpcOfferingResponse response = _responseGenerator.createVpcOfferingResponse(result);
response.setResponseName(getCommandName());
this.setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to clone VPC offering");
}
}
}

View File

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

View File

@ -8322,13 +8322,16 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
Map finalServiceProviderMap = resolveServiceProviderMap(cmd, sourceServiceProviderMap, finalServices);
// Reconstruct service capability list from source offering
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,
sourceDetailsMap, sourceDomainIds, sourceZoneIds);
sourceServiceCapabilityList, sourceDetailsMap, sourceDomainIds, sourceZoneIds);
}
private Map<String, String> getSourceOfferingDetails(Long sourceOfferingId) {
@ -8396,8 +8399,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
}
private void applyResolvedValuesToCommand(CloneNetworkOfferingCmd cmd, NetworkOfferingVO sourceOffering,
List<String> finalServices, Map finalServiceProviderMap, Map<String, String> sourceDetailsMap,
List<Long> sourceDomainIds, List<Long> sourceZoneIds) {
List<String> finalServices, Map finalServiceProviderMap, Map<String, String> sourceServiceCapabilityList,
Map<String, String> sourceDetailsMap, List<Long> sourceDomainIds, List<Long> sourceZoneIds) {
try {
Map<String, String> requestParams = cmd.getFullUrlParams();
@ -8409,6 +8412,14 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
setField(cmd, "serviceProviderList", finalServiceProviderMap);
}
// Apply service capability list if not provided via request parameters
// Check if any servicecapabilitylist parameters were passed (e.g., servicecapabilitylist[0].service)
boolean hasCapabilityParams = requestParams.keySet().stream()
.anyMatch(key -> key.startsWith(ApiConstants.SERVICE_CAPABILITY_LIST));
if (!hasCapabilityParams && sourceServiceCapabilityList != null && !sourceServiceCapabilityList.isEmpty()) {
setField(cmd, "serviceCapabilitystList", sourceServiceCapabilityList);
}
applyIfNotProvided(cmd, requestParams, "displayText", ApiConstants.DISPLAY_TEXT, cmd.getDisplayText(), sourceOffering.getDisplayText());
applyIfNotProvided(cmd, requestParams, "traffictype", ApiConstants.TRAFFIC_TYPE, cmd.getTraffictype(), sourceOffering.getTrafficType().toString());
@ -8466,7 +8477,90 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
}
}
private void applyIfNotProvided(Object cmd, Map<String, String> requestParams, String fieldName,
/**
* 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<String, String> with keys like "0.service", "0.capabilitytype", "0.capabilityvalue"
*/
private Map<String, String> reconstructNetworkServiceCapabilityList(NetworkOfferingVO sourceOffering) {
Map<String, String> capabilityList = new HashMap<>();
int index = 0;
// LB service capabilities
if (sourceOffering.isDedicatedLB()) {
capabilityList.put(index + ".service", Network.Service.Lb.getName());
capabilityList.put(index + ".capabilitytype", Network.Capability.SupportedLBIsolation.getName());
capabilityList.put(index + ".capabilityvalue", "dedicated");
index++;
}
if (sourceOffering.isElasticLb()) {
capabilityList.put(index + ".service", Network.Service.Lb.getName());
capabilityList.put(index + ".capabilitytype", Network.Capability.ElasticLb.getName());
capabilityList.put(index + ".capabilityvalue", "true");
index++;
}
if (sourceOffering.isInline()) {
capabilityList.put(index + ".service", Network.Service.Lb.getName());
capabilityList.put(index + ".capabilitytype", Network.Capability.InlineMode.getName());
capabilityList.put(index + ".capabilityvalue", "true");
index++;
}
if (sourceOffering.isPublicLb() || sourceOffering.isInternalLb()) {
List<String> schemes = new ArrayList<>();
if (sourceOffering.isPublicLb()) schemes.add("public");
if (sourceOffering.isInternalLb()) schemes.add("internal");
capabilityList.put(index + ".service", Network.Service.Lb.getName());
capabilityList.put(index + ".capabilitytype", Network.Capability.LbSchemes.getName());
capabilityList.put(index + ".capabilityvalue", String.join(",", schemes));
index++;
}
if (sourceOffering.isSupportsVmAutoScaling()) {
capabilityList.put(index + ".service", Network.Service.Lb.getName());
capabilityList.put(index + ".capabilitytype", Network.Capability.VmAutoScaling.getName());
capabilityList.put(index + ".capabilityvalue", "true");
index++;
}
// SourceNat service capabilities
if (sourceOffering.isSharedSourceNat() || sourceOffering.isRedundantRouter()) {
capabilityList.put(index + ".service", Network.Service.SourceNat.getName());
capabilityList.put(index + ".capabilitytype", Network.Capability.SupportedSourceNatTypes.getName());
capabilityList.put(index + ".capabilityvalue", sourceOffering.isSharedSourceNat() ? "perzone" : "peraccount");
index++;
}
if (sourceOffering.isRedundantRouter()) {
capabilityList.put(index + ".service", Network.Service.SourceNat.getName());
capabilityList.put(index + ".capabilitytype", Network.Capability.RedundantRouter.getName());
capabilityList.put(index + ".capabilityvalue", "true");
index++;
// Also add to Gateway service
capabilityList.put(index + ".service", Network.Service.Gateway.getName());
capabilityList.put(index + ".capabilitytype", Network.Capability.RedundantRouter.getName());
capabilityList.put(index + ".capabilityvalue", "true");
index++;
}
// StaticNat service capabilities
if (sourceOffering.isElasticIp()) {
capabilityList.put(index + ".service", Network.Service.StaticNat.getName());
capabilityList.put(index + ".capabilitytype", Network.Capability.ElasticIp.getName());
capabilityList.put(index + ".capabilityvalue", "true");
index++;
}
if (sourceOffering.isAssociatePublicIP()) {
capabilityList.put(index + ".service", Network.Service.StaticNat.getName());
capabilityList.put(index + ".capabilitytype", Network.Capability.AssociatePublicIP.getName());
capabilityList.put(index + ".capabilityvalue", "true");
index++;
}
return capabilityList;
}
public static void applyIfNotProvided(Object cmd, Map<String, String> requestParams, String fieldName,
String apiConstant, Object currentValue, Object sourceValue) throws Exception {
// If parameter was not provided in request and source has a value, use source value
if (!requestParams.containsKey(apiConstant) && sourceValue != null) {
@ -8475,19 +8569,34 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
// If parameter WAS provided in request, the framework already set it correctly
}
private void applyBooleanIfNotProvided(Object cmd, Map<String, String> requestParams,
public static void applyBooleanIfNotProvided(Object cmd, Map<String, String> requestParams,
String fieldName, String apiConstant, Boolean sourceValue) throws Exception {
if (!requestParams.containsKey(apiConstant) && sourceValue != null) {
setField(cmd, fieldName, sourceValue);
}
}
private void setField(Object obj, String fieldName, Object value) throws Exception {
java.lang.reflect.Field field = obj.getClass().getDeclaredField(fieldName);
public static void setField(Object obj, String fieldName, Object value) throws Exception {
java.lang.reflect.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 java.lang.reflect.Field findField(Class<?> clazz, String fieldName) {
Class<?> currentClass = clazz;
while (currentClass != null) {
try {
return currentClass.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
currentClass = currentClass.getSuperclass();
}
}
return null;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_NETWORK_OFFERING_EDIT, eventDescription = "updating network offering")
public NetworkOffering updateNetworkOffering(final UpdateNetworkOfferingCmd cmd) {

View File

@ -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;
@ -811,6 +812,213 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis
}
}
@Override
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);
applySourceOfferingValuesToCloneCmd(cmd, sourceVpcOffering);
return createVpcOffering(cmd);
}
private void applySourceOfferingValuesToCloneCmd(CloneVPCOfferingCmd cmd, VpcOffering sourceVpcOffering) {
Long sourceOfferingId = sourceVpcOffering.getId();
Map<Network.Service, Set<Network.Provider>> sourceServiceProviderMap = getVpcOffSvcProvidersMap(sourceOfferingId);
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()) {
finalServices.removeAll(dropServices);
logger.debug("Dropped services from clone: {}", dropServices);
}
if (addServices != null && !addServices.isEmpty()) {
for (String service : addServices) {
if (!finalServices.contains(service)) {
finalServices.add(service);
}
}
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;
}
private void applyResolvedValuesToCommand(CloneVPCOfferingCmd cmd, VpcOfferingVO sourceOffering,
List<String> finalServices, Map finalServiceProviderMap,
List<Long> sourceDomainIds, List<Long> sourceZoneIds,
Map<String, String> sourceServiceCapabilityList) {
try {
Map<String, String> requestParams = cmd.getFullUrlParams();
if (cmd.getSupportedServices() == null || cmd.getSupportedServices().isEmpty()) {
ConfigurationManagerImpl.setField(cmd, "supportedServices", finalServices);
}
if (cmd.getServiceProviders() == null || cmd.getServiceProviders().isEmpty()) {
ConfigurationManagerImpl.setField(cmd, "serviceProviderList", finalServiceProviderMap);
}
if ((cmd.getServiceCapabilityList() == null || cmd.getServiceCapabilityList().isEmpty())
&& sourceServiceCapabilityList != null && !sourceServiceCapabilityList.isEmpty()) {
ConfigurationManagerImpl.setField(cmd, "serviceCapabilityList", sourceServiceCapabilityList);
}
ConfigurationManagerImpl.applyIfNotProvided(cmd, requestParams, "displayText", ApiConstants.DISPLAY_TEXT, cmd.getDisplayText(), sourceOffering.getDisplayText());
ConfigurationManagerImpl.applyIfNotProvided(cmd, requestParams, "serviceOfferingId", ApiConstants.SERVICE_OFFERING_ID, cmd.getServiceOfferingId(), sourceOffering.getServiceOfferingId());
ConfigurationManagerImpl.applyBooleanIfNotProvided(cmd, requestParams, "enable", ApiConstants.ENABLE, sourceOffering.getState() == VpcOffering.State.Enabled);
ConfigurationManagerImpl.applyBooleanIfNotProvided(cmd, requestParams, "specifyAsNumber", ApiConstants.SPECIFY_AS_NUMBER, sourceOffering.isSpecifyAsNumber());
if (!requestParams.containsKey(ApiConstants.INTERNET_PROTOCOL)) {
String internetProtocol = vpcOfferingDetailsDao.getDetail(sourceOffering.getId(), ApiConstants.INTERNET_PROTOCOL);
if (internetProtocol != null) {
ConfigurationManagerImpl.setField(cmd, "internetProtocol", internetProtocol);
}
}
if (!requestParams.containsKey(ApiConstants.NETWORK_MODE) && sourceOffering.getNetworkMode() != null) {
ConfigurationManagerImpl.setField(cmd, "networkMode", sourceOffering.getNetworkMode().toString());
}
if (!requestParams.containsKey(ApiConstants.ROUTING_MODE) && 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.warn("Failed to apply some source offering parameters during clone: {}", e.getMessage());
}
}
private void validateConnectivtyServiceCapabilities(final Set<Provider> providers, final Map serviceCapabilitystList) {
if (serviceCapabilitystList != null && !serviceCapabilitystList.isEmpty()) {
final Collection serviceCapabilityCollection = serviceCapabilitystList.values();

View File

@ -327,6 +327,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;
@ -3967,6 +3968,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(RecoverVMCmd.class);
cmdList.add(CreatePrivateGatewayCmd.class);
cmdList.add(CreateVPCOfferingCmd.class);
cmdList.add(CloneVPCOfferingCmd.class);
cmdList.add(DeletePrivateGatewayCmd.class);
cmdList.add(DeleteVPCOfferingCmd.class);
cmdList.add(UpdateVPCOfferingCmd.class);

View File

@ -42,6 +42,7 @@ import com.cloud.utils.DomainHelper;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.api.command.admin.backup.CloneBackupOfferingCmd;
import org.apache.cloudstack.api.command.admin.backup.DeleteBackupOfferingCmd;
import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd;
import org.apache.cloudstack.api.command.admin.backup.ListBackupProviderOfferingsCmd;
@ -334,6 +335,55 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
return backupOfferingDetailsDao.findDomainIds(offeringId);
}
@ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_CLONE_OFFERING, eventDescription = "cloning backup offering", create = true)
public BackupOffering cloneBackupOffering(final CloneBackupOfferingCmd cmd) {
final BackupOfferingVO sourceOffering = backupOfferingDao.findById(cmd.getId());
if (sourceOffering == null) {
throw new InvalidParameterValueException("Unable to find backup offering with ID: " + cmd.getId());
}
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();
if (!externalId.equals(sourceOffering.getExternalId())) {
final BackupProvider provider = getBackupProvider(sourceOffering.getZoneId());
if (!provider.isValidProviderOffering(sourceOffering.getZoneId(), externalId)) {
throw new CloudRuntimeException("Backup offering '" + externalId + "' does not exist on provider " + provider.getName() + " on zone " + sourceOffering.getZoneId());
}
}
if (!externalId.equals(sourceOffering.getExternalId())) {
final BackupOffering existingOffering = backupOfferingDao.findByExternalId(externalId, sourceOffering.getZoneId());
if (existingOffering != null) {
throw new CloudRuntimeException("A backup offering with external ID '" + externalId + "' already exists in this zone");
}
}
final BackupOfferingVO clonedOffering = new BackupOfferingVO(
sourceOffering.getZoneId(),
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.getId());
}
logger.debug("Successfully cloned backup offering '" + sourceOffering.getName() + "' (ID: " + cmd.getId() + ") to '" + cmd.getName() + "' (ID: " + savedOffering.getId() + ")");
return savedOffering;
}
@Override
public Pair<List<BackupOffering>, Integer> listBackupOfferings(final ListBackupOfferingsCmd cmd) {
final Long offeringId = cmd.getOfferingId();