Add support to clone existing offerings and update them

This commit is contained in:
Pearl Dsilva 2025-12-31 11:18:01 -05:00
parent da85858e93
commit e13104bfea
9 changed files with 961 additions and 0 deletions

View File

@ -24,6 +24,7 @@ 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;
@ -33,6 +34,8 @@ 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.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
*

View File

@ -374,6 +374,7 @@ 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";

View File

@ -555,6 +555,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";

View File

@ -0,0 +1,97 @@
// 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 CreateNetworkOfferingCmd {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.SOURCE_OFFERING_ID,
type = BaseCmd.CommandType.UUID,
entityType = NetworkOfferingResponse.class,
required = true,
description = "The ID of the network 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() {
NetworkOffering result = _configService.cloneNetworkOffering(this);
if (result != null) {
NetworkOfferingResponse response = _responseGenerator.createNetworkOfferingResponse(result);
response.setResponseName(getCommandName());
this.setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to clone network offering");
}
}
}

View File

@ -0,0 +1,72 @@
// 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.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")
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 disk offering to clone")
private Long sourceOfferingId;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getSourceOfferingId() {
return sourceOfferingId;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() {
DiskOffering result = _configService.cloneDiskOffering(this);
if (result != null) {
DiskOfferingResponse response = _responseGenerator.createDiskOfferingResponse(result);
response.setResponseName(getCommandName());
this.setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to clone disk offering");
}
}
}

View File

@ -0,0 +1,72 @@
// 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.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")
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 service offering to clone")
private Long sourceOfferingId;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getSourceOfferingId() {
return sourceOfferingId;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public void execute() {
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");
}
}
}

View File

@ -67,6 +67,7 @@ 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;
@ -76,6 +77,8 @@ 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.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 +3857,458 @@ 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;
}
private <T> T getOrDefault(T cmdValue, T defaultValue) {
return cmdValue != null ? cmdValue : defaultValue;
}
private 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
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) {
@ -7827,6 +8282,212 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
}
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_NETWORK_OFFERING_CREATE, 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);
// Resolve parameters from source offering and apply add/drop logic
applySourceOfferingValuesToCloneCmd(cmd, sourceOffering);
return createNetworkOffering(cmd);
}
private void applySourceOfferingValuesToCloneCmd(CloneNetworkOfferingCmd cmd, NetworkOfferingVO sourceOffering) {
Long sourceOfferingId = sourceOffering.getId();
Map<Network.Service, Set<Network.Provider>> sourceServiceProviderMap =
_networkModel.getNetworkOfferingServiceProvidersMap(sourceOfferingId);
// Build final services list with add/drop support
List<String> finalServices = resolveFinalServicesList(cmd, sourceServiceProviderMap);
Map finalServiceProviderMap = resolveServiceProviderMap(cmd, sourceServiceProviderMap, finalServices);
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);
}
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()) {
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(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 finalServiceProviderMap, 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()) {
setField(cmd, "serviceProviderList", finalServiceProviderMap);
}
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(), sourceOffering.getAvailability().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());
}
}
private 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) {
setField(cmd, fieldName, sourceValue);
}
// If parameter WAS provided in request, the framework already set it correctly
}
private 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);
field.setAccessible(true);
field.set(obj, value);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_NETWORK_OFFERING_EDIT, eventDescription = "updating network offering")
public NetworkOffering updateNetworkOffering(final UpdateNetworkOfferingCmd cmd) {

View File

@ -131,6 +131,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;
@ -161,6 +162,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;
@ -3856,6 +3859,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);
@ -3876,7 +3880,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);

View File

@ -51,6 +51,7 @@ 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;
@ -60,6 +61,8 @@ 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.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)
*/