mirror of https://github.com/apache/cloudstack.git
add support for vpc & backup offerings to be cloned
This commit is contained in:
parent
e13104bfea
commit
afe6c86990
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ import java.util.Set;
|
|||
public class ImportBackupOfferingCmd extends BaseAsyncCmd {
|
||||
|
||||
@Inject
|
||||
private BackupManager backupManager;
|
||||
protected BackupManager backupManager;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
|
|
|
|||
|
|
@ -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///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue