server: allows compute offering with or without constraints (#3245)

Problem: Custom compute offering does not allow setting min and max values for CPU and VRAM for custom VMs.

Root Cause: Custom compute offerings cannot be created with a given range of CPU number and memory instead it allows only fixed values.

Solution: createServiceOffering API has been modified to allow setting a defined range for CPU number and memory. Also, UI form for compute offering creation is provided with a new field named 'compute offering type’ with values - Fixed, Custom Constrained, Custom Constrained. It will allow the creation of compute offerings either with a fixed CPU speed and memory for fixed compute offering, or with a range of CPU number and memory for custom constrained compute offering or without predefined CPU number, CPU speed and memory for custom unconstrained compute offering.

To allow the user to set CPU number, CPU speed and memory during VM deployment, UI form for VM deployment has been modified to provide controls to change these values. These controls are depicted in screenshots below for custom constrained and custom unconstrained compute offering types.

Sample API calls using cmk to create a constrained service offering and deploying a VM using it,

create serviceoffering name=Constrained displaytext=Constrained customized=true mincpunumber=2 maxcpunumber=4 cpuspeed=400 minmemory=256 maxmemory=1024

deploy virtualmachine displayname=ConstrainedVM serviceofferingid=60f3e500-6559-40b2-9a61-2192891c2bd6 templateid=8e0f4a3e-601b-11e9-9df4-a0afbd4a2d60 zoneid=9612a0c6-ed28-4fae-9a48-6eb207af29e3 details[0].cpuNumber=3 details[0].memory=800

Signed-off-by: Abhishek Kumar <abhishek.kumar@shapeblue.com>
This commit is contained in:
Abhishek Kumar 2019-05-23 11:47:53 +05:30 committed by Rohit Yadav
parent 2ead7359d1
commit 2020bfb6a3
13 changed files with 444 additions and 76 deletions

View File

@ -202,6 +202,10 @@ public class ApiConstants {
public static final String MAX = "max"; public static final String MAX = "max";
public static final String MAC_ADDRESS = "macaddress"; public static final String MAC_ADDRESS = "macaddress";
public static final String MAX_SNAPS = "maxsnaps"; public static final String MAX_SNAPS = "maxsnaps";
public static final String MAX_CPU_NUMBER = "maxcpunumber";
public static final String MAX_MEMORY = "maxmemory";
public static final String MIN_CPU_NUMBER = "mincpunumber";
public static final String MIN_MEMORY = "minmemory";
public static final String MEMORY = "memory"; public static final String MEMORY = "memory";
public static final String MODE = "mode"; public static final String MODE = "mode";
public static final String KEEPALIVE_ENABLED = "keepaliveenabled"; public static final String KEEPALIVE_ENABLED = "keepaliveenabled";

View File

@ -18,10 +18,8 @@ package org.apache.cloudstack.api.command.admin.offering;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import com.cloud.storage.Storage;
import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.ApiErrorCode;
@ -30,10 +28,14 @@ import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse;
import org.apache.commons.collections.MapUtils;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.offering.ServiceOffering; import com.cloud.offering.ServiceOffering;
import com.cloud.storage.Storage;
import com.cloud.user.Account; import com.cloud.user.Account;
import com.google.common.base.Strings;
@APICommand(name = "createServiceOffering", description = "Creates a service offering.", responseObject = ServiceOfferingResponse.class, @APICommand(name = "createServiceOffering", description = "Creates a service offering.", responseObject = ServiceOfferingResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@ -162,6 +164,37 @@ public class CreateServiceOfferingCmd extends BaseCmd {
since = "4.4") since = "4.4")
private Integer hypervisorSnapshotReserve; private Integer hypervisorSnapshotReserve;
// Introduce 4 new optional paramaters to work custom compute offerings
@Parameter(name = ApiConstants.CUSTOMIZED,
type = CommandType.BOOLEAN,
since = "4.13",
description = "Whether service offering size is custom or not")
private Boolean customized;
@Parameter(name = ApiConstants.MAX_CPU_NUMBER,
type = CommandType.INTEGER,
description = "The maximum number of CPUs to be set with Custom Computer Offering",
since = "4.13")
private Integer maxCPU;
@Parameter(name = ApiConstants.MIN_CPU_NUMBER,
type = CommandType.INTEGER,
description = "The minimum number of CPUs to be set with Custom Computer Offering",
since = "4.13")
private Integer minCPU;
@Parameter(name = ApiConstants.MAX_MEMORY,
type = CommandType.INTEGER,
description = "The maximum memroy size of the custom service offering in MB",
since = "4.13")
private Integer maxMemory;
@Parameter(name = ApiConstants.MIN_MEMORY,
type = CommandType.INTEGER,
description = "The minimum memroy size of the custom service offering in MB",
since = "4.13")
private Integer minMemory;
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
/////////////////// Accessors /////////////////////// /////////////////// Accessors ///////////////////////
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
@ -175,6 +208,9 @@ public class CreateServiceOfferingCmd extends BaseCmd {
} }
public String getDisplayText() { public String getDisplayText() {
if (Strings.isNullOrEmpty(displayText)) {
throw new InvalidParameterValueException("Failed to create service offering because the offering display text has not been spified.");
}
return displayText; return displayText;
} }
@ -187,6 +223,9 @@ public class CreateServiceOfferingCmd extends BaseCmd {
} }
public String getServiceOfferingName() { public String getServiceOfferingName() {
if (Strings.isNullOrEmpty(serviceOfferingName)) {
throw new InvalidParameterValueException("Failed to create service offering because offering name has not been spified.");
}
return serviceOfferingName; return serviceOfferingName;
} }
@ -234,18 +273,12 @@ public class CreateServiceOfferingCmd extends BaseCmd {
return deploymentPlanner; return deploymentPlanner;
} }
public boolean isCustomized() {
return (cpuNumber == null || memory == null || cpuSpeed == null);
}
public Map<String, String> getDetails() { public Map<String, String> getDetails() {
Map<String, String> detailsMap = null; Map<String, String> detailsMap = new HashMap<>();
if (details != null && !details.isEmpty()) { if (MapUtils.isNotEmpty(details)) {
detailsMap = new HashMap<String, String>();
Collection<?> props = details.values(); Collection<?> props = details.values();
Iterator<?> iter = props.iterator(); for (Object prop : props) {
while (iter.hasNext()) { HashMap<String, String> detail = (HashMap<String, String>) prop;
HashMap<String, String> detail = (HashMap<String, String>) iter.next();
detailsMap.put(detail.get("key"), detail.get("value")); detailsMap.put(detail.get("key"), detail.get("value"));
} }
} }
@ -316,6 +349,36 @@ public class CreateServiceOfferingCmd extends BaseCmd {
return hypervisorSnapshotReserve; return hypervisorSnapshotReserve;
} }
/**
* If customized parameter is true, then cpuNumber, memory and cpuSpeed must be null
* Check if the optional params min/max CPU/Memory have been specified
* @return true if the following conditions are satisfied;
* - cpuNumber, memory and cpuSpeed are all null when customized parameter is set to true
* - min/max CPU/Memory params are all null or all set
*/
public boolean isCustomized() {
if (customized != null){
return customized;
}
return (cpuNumber == null || memory == null);
}
public Integer getMaxCPUs() {
return maxCPU;
}
public Integer getMinCPUs() {
return minCPU;
}
public Integer getMaxMemory() {
return maxMemory;
}
public Integer getMinMemory() {
return minMemory;
}
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
/////////////// API Implementation/////////////////// /////////////// API Implementation///////////////////
///////////////////////////////////////////////////// /////////////////////////////////////////////////////

View File

@ -331,4 +331,8 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering
public void setDynamicFlag(boolean isdynamic) { public void setDynamicFlag(boolean isdynamic) {
isDynamic = isdynamic; isDynamic = isdynamic;
} }
public boolean isCustomCpuSpeedSupported() {
return isCustomized() && getDetail("minCPU") != null;
}
} }

View File

@ -612,9 +612,15 @@ public class CapacityManagerImpl extends ManagerBase implements CapacityManager,
usedMemory += usedMemory +=
((Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.memory.name())) * 1024L * 1024L) / ramOvercommitRatio) * ((Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.memory.name())) * 1024L * 1024L) / ramOvercommitRatio) *
clusterRamOvercommitRatio; clusterRamOvercommitRatio;
usedCpu += if(vmDetails.containsKey(UsageEventVO.DynamicParameters.cpuSpeed.name())) {
((Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuNumber.name())) * Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuSpeed.name()))) / cpuOvercommitRatio) * usedCpu +=
clusterCpuOvercommitRatio; ((Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuNumber.name())) * Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuSpeed.name()))) / cpuOvercommitRatio) *
clusterCpuOvercommitRatio;
} else {
usedCpu +=
((Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuNumber.name())) * so.getSpeed()) / cpuOvercommitRatio) *
clusterCpuOvercommitRatio;
}
usedCpuCore += Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuNumber.name())); usedCpuCore += Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuNumber.name()));
} else { } else {
usedMemory += ((so.getRamSize() * 1024L * 1024L) / ramOvercommitRatio) * clusterRamOvercommitRatio; usedMemory += ((so.getRamSize() * 1024L * 1024L) / ramOvercommitRatio) * clusterRamOvercommitRatio;
@ -645,9 +651,15 @@ public class CapacityManagerImpl extends ManagerBase implements CapacityManager,
reservedMemory += reservedMemory +=
((Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.memory.name())) * 1024L * 1024L) / ramOvercommitRatio) * ((Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.memory.name())) * 1024L * 1024L) / ramOvercommitRatio) *
clusterRamOvercommitRatio; clusterRamOvercommitRatio;
reservedCpu += if(vmDetails.containsKey(UsageEventVO.DynamicParameters.cpuSpeed.name())) {
((Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuNumber.name())) * Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuSpeed.name()))) / cpuOvercommitRatio) * reservedCpu +=
clusterCpuOvercommitRatio; ((Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuNumber.name())) * Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuSpeed.name()))) / cpuOvercommitRatio) *
clusterCpuOvercommitRatio;
} else {
reservedCpu +=
((Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuNumber.name())) * so.getSpeed()) / cpuOvercommitRatio) *
clusterCpuOvercommitRatio;
}
reservedCpuCore += Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuNumber.name())); reservedCpuCore += Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuNumber.name()));
} else { } else {
reservedMemory += ((so.getRamSize() * 1024L * 1024L) / ramOvercommitRatio) * clusterRamOvercommitRatio; reservedMemory += ((so.getRamSize() * 1024L * 1024L) / ramOvercommitRatio) * clusterRamOvercommitRatio;

View File

@ -35,12 +35,11 @@ import java.util.UUID;
import javax.inject.Inject; import javax.inject.Inject;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import com.google.common.collect.Sets;
import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.AffinityGroupService; import org.apache.cloudstack.affinity.AffinityGroupService;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao; import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd; import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd;
import org.apache.cloudstack.api.command.admin.network.CreateManagementNetworkIpRangeCmd; import org.apache.cloudstack.api.command.admin.network.CreateManagementNetworkIpRangeCmd;
import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd; import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd;
@ -232,6 +231,7 @@ import com.cloud.vm.dao.VMInstanceDao;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.Sets;
public class ConfigurationManagerImpl extends ManagerBase implements ConfigurationManager, ConfigurationService, Configurable { public class ConfigurationManagerImpl extends ManagerBase implements ConfigurationManager, ConfigurationService, Configurable {
public static final Logger s_logger = Logger.getLogger(ConfigurationManagerImpl.class); public static final Logger s_logger = Logger.getLogger(ConfigurationManagerImpl.class);
@ -2218,6 +2218,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
@ActionEvent(eventType = EventTypes.EVENT_SERVICE_OFFERING_CREATE, eventDescription = "creating service offering") @ActionEvent(eventType = EventTypes.EVENT_SERVICE_OFFERING_CREATE, eventDescription = "creating service offering")
public ServiceOffering createServiceOffering(final CreateServiceOfferingCmd cmd) { public ServiceOffering createServiceOffering(final CreateServiceOfferingCmd cmd) {
final Long userId = CallContext.current().getCallingUserId(); final Long userId = CallContext.current().getCallingUserId();
final Map<String, String> details = cmd.getDetails();
final String offeringName = cmd.getServiceOfferingName();
final String name = cmd.getServiceOfferingName(); final String name = cmd.getServiceOfferingName();
if (name == null || name.length() == 0) { if (name == null || name.length() == 0) {
@ -2233,21 +2235,54 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
final Integer cpuSpeed = cmd.getCpuSpeed(); final Integer cpuSpeed = cmd.getCpuSpeed();
final Integer memory = cmd.getMemory(); final Integer memory = cmd.getMemory();
//restricting the createserviceoffering to allow setting all or none of the dynamic parameters to null // Optional Custom Parameters
if (cpuNumber == null || cpuSpeed == null || memory == null) { Integer maxCPU = cmd.getMaxCPUs();
if (cpuNumber != null || cpuSpeed != null || memory != null) { Integer minCPU = cmd.getMinCPUs();
throw new InvalidParameterValueException("For creating a custom compute offering cpu, cpu speed and memory all should be null"); Integer maxMemory = cmd.getMaxMemory();
} Integer minMemory = cmd.getMinMemory();
}
if (cpuNumber != null && (cpuNumber.intValue() <= 0 || cpuNumber.longValue() > Integer.MAX_VALUE)) { // Check if service offering is Custom,
throw new InvalidParameterValueException("Failed to create service offering " + name + ": specify the cpu number value between 1 and " + Integer.MAX_VALUE); // If Customized, the following conditions must hold
} // 1. cpuNumber, cpuSpeed and memory should be all null
if (cpuSpeed != null && (cpuSpeed.intValue() < 0 || cpuSpeed.longValue() > Integer.MAX_VALUE)) { // 2. minCPU, maxCPU, minMemory and maxMemory should all be null or all specified
throw new InvalidParameterValueException("Failed to create service offering " + name + ": specify the cpu speed value between 0 and " + Integer.MAX_VALUE); boolean isCustomized = cmd.isCustomized();
} if (isCustomized) {
if (memory != null && (memory.intValue() < 32 || memory.longValue() > Integer.MAX_VALUE)) { // validate specs
throw new InvalidParameterValueException("Failed to create service offering " + name + ": specify the memory value between 32 and " + Integer.MAX_VALUE + " MB"); //restricting the createserviceoffering to allow setting all or none of the dynamic parameters to null
if (cpuNumber != null || memory != null) {
throw new InvalidParameterValueException("For creating a custom compute offering cpu and memory all should be null");
}
// if any of them is null, then all of them shoull be null
if (maxCPU == null || minCPU == null || maxMemory == null || minMemory == null) {
if (maxCPU != null || minCPU != null || maxMemory != null || minMemory != null) {
throw new InvalidParameterValueException("For creating a custom compute offering min/max cpu and min/max memory should all be specified");
}
} else {
if (cpuSpeed != null && (cpuSpeed.intValue() < 0 || cpuSpeed.longValue() > Integer.MAX_VALUE)) {
throw new InvalidParameterValueException("Failed to create service offering " + offeringName + ": specify the cpu speed value between 1 and " + Integer.MAX_VALUE);
}
if ((maxCPU <= 0 || maxCPU.longValue() > Integer.MAX_VALUE) || (minCPU <= 0 || minCPU.longValue() > Integer.MAX_VALUE ) ) {
throw new InvalidParameterValueException("Failed to create service offering " + offeringName + ": specify the minimum or minimum cpu number value between 1 and " + Integer.MAX_VALUE);
}
if (minMemory < 32 || (minMemory.longValue() > Integer.MAX_VALUE) || (maxMemory.longValue() > Integer.MAX_VALUE)) {
throw new InvalidParameterValueException("Failed to create service offering " + offeringName + ": specify the memory value between 32 and " + Integer.MAX_VALUE + " MB");
}
// Persist min/max CPU and Memory parameters in the service_offering_details table
details.put(ApiConstants.MIN_MEMORY, minMemory.toString());
details.put(ApiConstants.MAX_MEMORY, maxMemory.toString());
details.put(ApiConstants.MIN_CPU_NUMBER, minCPU.toString());
details.put(ApiConstants.MAX_CPU_NUMBER, maxCPU.toString());
}
} else {
if (cpuNumber != null && (cpuNumber.intValue() <= 0 || cpuNumber.longValue() > Integer.MAX_VALUE)) {
throw new InvalidParameterValueException("Failed to create service offering " + offeringName + ": specify the cpu number value between 1 and " + Integer.MAX_VALUE);
}
if (cpuSpeed != null && (cpuSpeed.intValue() < 0 || cpuSpeed.longValue() > Integer.MAX_VALUE)) {
throw new InvalidParameterValueException("Failed to create service offering " + offeringName + ": specify the cpu speed value between 0 and " + Integer.MAX_VALUE);
}
if (memory != null && (memory.intValue() < 32 || memory.longValue() > Integer.MAX_VALUE)) {
throw new InvalidParameterValueException("Failed to create service offering " + offeringName + ": specify the memory value between 32 and " + Integer.MAX_VALUE + " MB");
}
} }
// check if valid domain // check if valid domain
@ -2330,7 +2365,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
return createServiceOffering(userId, cmd.isSystem(), vmType, cmd.getServiceOfferingName(), cpuNumber, memory, cpuSpeed, cmd.getDisplayText(), return createServiceOffering(userId, cmd.isSystem(), vmType, cmd.getServiceOfferingName(), cpuNumber, memory, cpuSpeed, cmd.getDisplayText(),
cmd.getProvisioningType(), localStorageRequired, offerHA, limitCpuUse, volatileVm, cmd.getTags(), cmd.getDomainId(), cmd.getHostTag(), cmd.getProvisioningType(), localStorageRequired, offerHA, limitCpuUse, volatileVm, cmd.getTags(), cmd.getDomainId(), cmd.getHostTag(),
cmd.getNetworkRate(), cmd.getDeploymentPlanner(), cmd.getDetails(), isCustomizedIops, cmd.getMinIops(), cmd.getMaxIops(), cmd.getNetworkRate(), cmd.getDeploymentPlanner(), details, isCustomizedIops, cmd.getMinIops(), cmd.getMaxIops(),
cmd.getBytesReadRate(), cmd.getBytesReadRateMax(), cmd.getBytesReadRateMaxLength(), cmd.getBytesReadRate(), cmd.getBytesReadRateMax(), cmd.getBytesReadRateMaxLength(),
cmd.getBytesWriteRate(), cmd.getBytesWriteRateMax(), cmd.getBytesWriteRateMaxLength(), cmd.getBytesWriteRate(), cmd.getBytesWriteRateMax(), cmd.getBytesWriteRateMaxLength(),
cmd.getIopsReadRate(), cmd.getIopsReadRateMax(), cmd.getIopsReadRateMaxLength(), cmd.getIopsReadRate(), cmd.getIopsReadRateMax(), cmd.getIopsReadRateMaxLength(),

View File

@ -1017,14 +1017,18 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
@Override @Override
public void validateCustomParameters(ServiceOfferingVO serviceOffering, Map<String, String> customParameters) { public void validateCustomParameters(ServiceOfferingVO serviceOffering, Map<String, String> customParameters) {
//TODO need to validate custom cpu, and memory against min/max CPU/Memory ranges from service_offering_details table
if (customParameters.size() != 0) { if (customParameters.size() != 0) {
Map<String, String> offeringDetails = serviceOfferingDetailsDao.listDetailsKeyPairs(serviceOffering.getId());
if (serviceOffering.getCpu() == null) { if (serviceOffering.getCpu() == null) {
String cpuNumber = customParameters.get(UsageEventVO.DynamicParameters.cpuNumber.name()); int minCPU = NumbersUtil.parseInt(offeringDetails.get(ApiConstants.MIN_CPU_NUMBER), 1);
if ((cpuNumber == null) || (NumbersUtil.parseInt(cpuNumber, -1) <= 0)) { int maxCPU = NumbersUtil.parseInt(offeringDetails.get(ApiConstants.MAX_CPU_NUMBER), Integer.MAX_VALUE);
throw new InvalidParameterValueException("Invalid cpu cores value, specify a value between 1 and " + Integer.MAX_VALUE); int cpuNumber = NumbersUtil.parseInt(customParameters.get(UsageEventVO.DynamicParameters.cpuNumber.name()), -1);
if (cpuNumber < minCPU || cpuNumber > maxCPU) {
throw new InvalidParameterValueException(String.format("Invalid cpu cores value, specify a value between %d and %d", minCPU, maxCPU));
} }
} else if (customParameters.containsKey(UsageEventVO.DynamicParameters.cpuNumber.name())) { } else if (customParameters.containsKey(UsageEventVO.DynamicParameters.cpuNumber.name())) {
throw new InvalidParameterValueException("The cpu cores of this offering id:" + serviceOffering.getId() throw new InvalidParameterValueException("The cpu cores of this offering id:" + serviceOffering.getUuid()
+ " is not customizable. This is predefined in the template."); + " is not customizable. This is predefined in the template.");
} }
@ -1033,18 +1037,20 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
if ((cpuSpeed == null) || (NumbersUtil.parseInt(cpuSpeed, -1) <= 0)) { if ((cpuSpeed == null) || (NumbersUtil.parseInt(cpuSpeed, -1) <= 0)) {
throw new InvalidParameterValueException("Invalid cpu speed value, specify a value between 1 and " + Integer.MAX_VALUE); throw new InvalidParameterValueException("Invalid cpu speed value, specify a value between 1 and " + Integer.MAX_VALUE);
} }
} else if (customParameters.containsKey(UsageEventVO.DynamicParameters.cpuSpeed.name())) { } else if (!serviceOffering.isCustomCpuSpeedSupported() && customParameters.containsKey(UsageEventVO.DynamicParameters.cpuSpeed.name())) {
throw new InvalidParameterValueException("The cpu speed of this offering id:" + serviceOffering.getId() throw new InvalidParameterValueException("The cpu speed of this offering id:" + serviceOffering.getUuid()
+ " is not customizable. This is predefined in the template."); + " is not customizable. This is predefined in the template.");
} }
if (serviceOffering.getRamSize() == null) { if (serviceOffering.getRamSize() == null) {
String memory = customParameters.get(UsageEventVO.DynamicParameters.memory.name()); int minMemory = NumbersUtil.parseInt(offeringDetails.get(ApiConstants.MIN_MEMORY), 32);
if (memory == null || (NumbersUtil.parseInt(memory, -1) < 32)) { int maxMemory = NumbersUtil.parseInt(offeringDetails.get(ApiConstants.MAX_MEMORY), Integer.MAX_VALUE);
throw new InvalidParameterValueException("Invalid memory value, specify a value between 32 and " + Integer.MAX_VALUE + " MB"); int memory = NumbersUtil.parseInt(customParameters.get(UsageEventVO.DynamicParameters.memory.name()), -1);
if (memory < minMemory || memory > maxMemory) {
throw new InvalidParameterValueException(String.format("Invalid memory value, specify a value between %d and %d", minMemory, maxMemory));
} }
} else if (customParameters.containsKey(UsageEventVO.DynamicParameters.memory.name())) { } else if (customParameters.containsKey(UsageEventVO.DynamicParameters.memory.name())) {
throw new InvalidParameterValueException("The memory of this offering id:" + serviceOffering.getId() + " is not customizable. This is predefined in the template."); throw new InvalidParameterValueException("The memory of this offering id:" + serviceOffering.getUuid() + " is not customizable. This is predefined in the template.");
} }
} else { } else {
throw new InvalidParameterValueException("Need to specify custom parameter values cpu, cpu speed and memory when using custom offering"); throw new InvalidParameterValueException("Need to specify custom parameter values cpu, cpu speed and memory when using custom offering");

View File

@ -6117,11 +6117,11 @@ label.error {
margin-top: 9px !important; margin-top: 9px !important;
} }
.multi-wizard.instance-wizard .custom-disk-size .select-container { .multi-wizard.instance-wizard .custom-slider-container .select-container {
height: 279px; height: 279px;
} }
.multi-wizard.instance-wizard .custom-disk-size .select-container { .multi-wizard.instance-wizard .custom-slider-container .select-container {
height: 213px; height: 213px;
margin: -7px 6px 0 8px; margin: -7px 6px 0 8px;
/*+border-radius:6px;*/ /*+border-radius:6px;*/
@ -6220,7 +6220,11 @@ label.error {
font-size: 10px; font-size: 10px;
} }
.instance-wizard .step.data-disk-offering.custom-disk-size .select-container { .instance-wizard .step.data-disk-offering.custom-slider-container .select-container {
height: 272px;
}
.instance-wizard .step.service-offering.custom-slider-container .select-container {
height: 272px; height: 272px;
} }
@ -6228,11 +6232,15 @@ label.error {
height: 240px; height: 240px;
} }
.instance-wizard .step.data-disk-offering.custom-disk-size.custom-iops-do .select-container { .instance-wizard .step.data-disk-offering.custom-slider-container.custom-iops-do .select-container {
height: 176px; height: 176px;
} }
.instance-wizard .step.data-disk-offering.required.custom-disk-size .select-container { .instance-wizard .step.service-offering.required.custom-slider-container .select-container {
height: 315px;
}
.instance-wizard .step.data-disk-offering.required.custom-slider-container .select-container {
height: 315px; height: 315px;
} }
@ -6240,7 +6248,7 @@ label.error {
height: 295px; height: 295px;
} }
.instance-wizard .step.data-disk-offering.required.custom-disk-size.custom-iops-do .select-container { .instance-wizard .step.data-disk-offering.required.custom-slider-container.custom-iops-do .select-container {
height: 223px; height: 223px;
} }

View File

@ -206,7 +206,7 @@
</div> </div>
<!-- Custom size slider --> <!-- Custom size slider -->
<div class="section custom-size"> <div class="section custom-size custom-no-limits">
<div class="field"> <div class="field">
<label><translate key="label.num.cpu.cores"/></label> <label><translate key="label.num.cpu.cores"/></label>
<input type="text" class="required disallowSpecialCharacters" name="compute-cpu-cores" /> <input type="text" class="required disallowSpecialCharacters" name="compute-cpu-cores" />
@ -221,6 +221,31 @@
</div> </div>
</div> </div>
<!-- Custom size slider -->
<div class="section custom-size custom-slider-container">
<div class="slider-cpu-cores">
<label><translate key="label.num.cpu.cores"/></label>
<!-- Slider -->
<label class="size min"><span></span></label>
<div class="slider custom-size"></div>
<label class="size max"><span></span></label>
<input type="text" class="required digits" name="slider-compute-cpu-cores" value="0" />
<label class="size">Cores</label>
</div>
<div class="slider-memory-mb">
<label><translate key="label.memory.mb"/></label>
<!-- Slider -->
<label class="size min"><span></span></label>
<div class="slider custom-size"></div>
<label class="size max"><span></span></label>
<input type="text" class="required disallowSpecialCharacters" name="slider-compute-memory" value="0"/>
<label class="size">MB</label>
</div>
<div class="slider-cpu-speed">
<input type="text" style="display:none;" name="slider-compute-cpu-speed" value="0" />
</div>
</div>
<!-- Custom iops --> <!-- Custom iops -->
<div class="section custom-iops"> <div class="section custom-iops">
<div class="field"> <div class="field">
@ -248,7 +273,7 @@
</div> </div>
<!-- Custom size slider --> <!-- Custom size slider -->
<div class="section custom-size custom-disk-size"> <div class="section custom-size custom-slider-container">
<label><translate key="label.disk.size"/></label> <label><translate key="label.disk.size"/></label>
<!-- Slider --> <!-- Slider -->

View File

@ -553,6 +553,10 @@ var dictionary = {
"label.compute":"Compute", "label.compute":"Compute",
"label.compute.and.storage":"Compute and Storage", "label.compute.and.storage":"Compute and Storage",
"label.compute.offering":"Compute offering", "label.compute.offering":"Compute offering",
"label.compute.offering.type":"Compute offering type",
"label.compute.offering.custom.constrained":"Custom Constrained",
"label.compute.offering.custom.unconstrained":"Custom Unconstrained",
"label.compute.offering.fixed":"Fixed Offering",
"label.compute.offerings":"Compute Offerings", "label.compute.offerings":"Compute Offerings",
"label.configuration":"Configuration", "label.configuration":"Configuration",
"label.configure":"Configure", "label.configure":"Configure",
@ -1037,6 +1041,8 @@ var dictionary = {
"label.memory.allocated":"Memory Allocated", "label.memory.allocated":"Memory Allocated",
"label.memory.limits":"Memory limits (MiB)", "label.memory.limits":"Memory limits (MiB)",
"label.memory.mb":"Memory (in MB)", "label.memory.mb":"Memory (in MB)",
"label.memory.minimum.mb":"Min Memory (in MB)",
"label.memory.maximum.mb":"Max Memory (in MB)",
"label.memory.total":"Memory Total", "label.memory.total":"Memory Total",
"label.memory.used":"Memory Used", "label.memory.used":"Memory Used",
"label.menu.accounts":"Accounts", "label.menu.accounts":"Accounts",
@ -1219,6 +1225,8 @@ var dictionary = {
"label.not.found":"Not Found", "label.not.found":"Not Found",
"label.notifications":"Notifications", "label.notifications":"Notifications",
"label.num.cpu.cores":"# of CPU Cores", "label.num.cpu.cores":"# of CPU Cores",
"label.min.cpu.cores":"Min CPU Cores",
"label.max.cpu.cores":"Max CPU Cores",
"label.number.of.clusters":"Number of Clusters", "label.number.of.clusters":"Number of Clusters",
"label.number.of.cpu.sockets":"The Number of CPU Sockets", "label.number.of.cpu.sockets":"The Number of CPU Sockets",
"label.number.of.hosts":"Number of Hosts", "label.number.of.hosts":"Number of Hosts",

View File

@ -136,11 +136,70 @@
}); });
} }
}, },
isCustomized: { offeringType: {
label: 'label.custom', label: 'label.compute.offering.type',
isBoolean: true, docID: 'helpComputeOfferingType',
isReverse: true, select: function(args) {
isChecked: false var items = [];
items.push({
id: 'fixed',
description: _l('label.compute.offering.fixed')
});
items.push({
id: 'customConstrained',
description: _l('label.compute.offering.custom.constrained')
});
items.push({
id: 'customUnconstrained',
description: _l('label.compute.offering.custom.unconstrained')
});
args.response.success({
data: items
});
args.$select.change(function() {
var $form = $(this).closest('form');
var $cpuNumber = $form.find('.form-item[rel=cpuNumber]');
var $cpuSpeed = $form.find('.form-item[rel=cpuSpeed]');
var $memory = $form.find('.form-item[rel=memory]');
var $minCPUNumber = $form.find('.form-item[rel=minCPUNumber]');
var $maxCPUNumber = $form.find('.form-item[rel=maxCPUNumber]');
var $minMemory = $form.find('.form-item[rel=minMemory]');
var $maxMemory = $form.find('.form-item[rel=maxMemory]');
var type = $(this).val();
if (type == 'fixed') {
$minCPUNumber.hide();
$maxCPUNumber.hide();
$minMemory.hide();
$maxMemory.hide();
$cpuNumber.css('display', 'inline-block');
$cpuSpeed.css('display', 'inline-block');
$memory.css('display', 'inline-block');
} else if (type == 'customConstrained') {
$cpuNumber.hide();
$memory.hide();
$cpuSpeed.css('display', 'inline-block');
$minCPUNumber.css('display', 'inline-block');
$maxCPUNumber.css('display', 'inline-block');
$minMemory.css('display', 'inline-block');
$maxMemory.css('display', 'inline-block');
} else {
$cpuNumber.hide();
$memory.hide();
$cpuSpeed.hide();
$minCPUNumber.hide();
$maxCPUNumber.hide();
$minMemory.hide();
$maxMemory.hide();
}
});
}
}, },
cpuNumber: { cpuNumber: {
label: 'label.num.cpu.cores', label: 'label.num.cpu.cores',
@ -169,6 +228,39 @@
number: true number: true
} }
}, },
// Custom Compute Offering
minCPUNumber: {
label: 'label.min.cpu.cores',
docID: 'helpComputeOfferingMinCPUCores',
validation: {
required: true,
number: true
}
},
maxCPUNumber: {
label: 'label.max.cpu.cores',
docID: 'helpComputeOfferingMaxCPUCores',
validation: {
required: true,
number: true
}
},
minMemory: {
label: 'label.memory.minimum.mb',
docID: 'helpComputeOfferingMinMemory',
validation: {
required: true,
number: true
}
},
maxMemory: {
label: 'label.memory.maximum.mb',
docID: 'helpComputeOfferingMaxMemory',
validation: {
required: true,
number: true
}
},
networkRate: { networkRate: {
label: 'label.network.rate', label: 'label.network.rate',
docID: 'helpComputeOfferingNetworkRate', docID: 'helpComputeOfferingNetworkRate',
@ -588,17 +680,18 @@
}, },
action: function(args) { action: function(args) {
var isFixedOfferingType = args.data.offeringType == 'fixed';
var data = { var data = {
issystem: false, issystem: false,
name: args.data.name, name: args.data.name,
displaytext: args.data.description, displaytext: args.data.description,
storageType: args.data.storageType, storageType: args.data.storageType,
provisioningType :args.data.provisioningType, provisioningType :args.data.provisioningType,
customized: (args.data.isCustomized == "on") customized: !isFixedOfferingType
}; };
//custom fields (begin) //custom fields (begin)
if (args.data.isCustomized != "on") { if (isFixedOfferingType) {
$.extend(data, { $.extend(data, {
cpuNumber: args.data.cpuNumber cpuNumber: args.data.cpuNumber
}); });
@ -608,6 +701,24 @@
$.extend(data, { $.extend(data, {
memory: args.data.memory memory: args.data.memory
}); });
} else {
if(args.data.cpuSpeed != null && args.data.minCPUNumber != null && args.data.maxCPUNumber != null && args.data.minMemory != null && args.data.maxMemory != null){
$.extend(data, {
cpuSpeed: args.data.cpuSpeed
});
$.extend(data, {
mincpunumber: args.data.minCPUNumber
});
$.extend(data, {
maxcpunumber: args.data.maxCPUNumber
});
$.extend(data, {
minmemory: args.data.minMemory
});
$.extend(data, {
maxmemory: args.data.maxMemory
});
}
} }
//custom fields (end) //custom fields (end)

View File

@ -1326,5 +1326,30 @@ cloudStack.docs = {
helpL2UserData: { helpL2UserData: {
desc: 'Pass user and meta data to VMs (via ConfigDrive)', desc: 'Pass user and meta data to VMs (via ConfigDrive)',
externalLink: '' externalLink: ''
},
helpComputeOfferingMinCPUCores: {
desc: 'This will be used for the setting the range (min-max) of the number of cpu cores that should be allowed for VMs using this custom offering.',
externalLink: ''
},
helpComputeOfferingMaxCPUCores: {
desc: 'This will be used for the setting the range (min-max) of the number of cpu cores that should be allowed for VMs using this custom offering.',
externalLink: ''
},
helpComputeOfferingMinMemory: {
desc: 'This will be used for the setting the range (min-max) amount of memory that should be allowed for VMs using this custom offering.',
externalLink: ''
},
helpComputeOfferingMaxMemory: {
desc: 'This will be used for the setting the range (min-max) amount of memory that should be allowed for VMs using this custom offering.',
externalLink: ''
},
helpComputeOfferingType: {
desc: 'This will be used for setting the type of compute offering - whether it is fixed, custom constrained or custom unconstrained.',
externalLink: ''
} }
}; };

View File

@ -811,6 +811,17 @@
'details[0].memory' : args.$wizard.find('input[name=compute-memory]').val() 'details[0].memory' : args.$wizard.find('input[name=compute-memory]').val()
}); });
} }
} else if (args.$wizard.find('input[name=slider-compute-cpu-cores]').parent().parent().css('display') != 'none') {
if (args.$wizard.find('input[name=slider-compute-cpu-cores]').val().length > 0) {
$.extend(deployVmData, {
'details[0].cpuNumber' : args.$wizard.find('input[name=slider-compute-cpu-cores]').val()
});
}
if (args.$wizard.find('input[name=slider-compute-memory]').val().length > 0) {
$.extend(deployVmData, {
'details[0].memory' : args.$wizard.find('input[name=slider-compute-memory]').val()
});
}
} }
if (args.$wizard.find('input[name=disk-min-iops]').parent().parent().css('display') != 'none') { if (args.$wizard.find('input[name=disk-min-iops]').parent().parent().css('display') != 'none') {

View File

@ -431,7 +431,7 @@
if (custom) { if (custom) {
$step.find('.section.custom-size').hide(); $step.find('.section.custom-size').hide();
$step.removeClass('custom-disk-size'); $step.removeClass('custom-slider-container');
} }
$step.find('input[type=radio]').bind('change', function() { $step.find('input[type=radio]').bind('change', function() {
@ -464,10 +464,10 @@
var hypervisor = item['hypervisor']; var hypervisor = item['hypervisor'];
if (hypervisor == 'KVM' || hypervisor == 'XenServer' || hypervisor == 'VMware') { if (hypervisor == 'KVM' || hypervisor == 'XenServer' || hypervisor == 'VMware') {
$step.find('.section.custom-size').show(); $step.find('.section.custom-size').show();
$step.addClass('custom-disk-size'); $step.addClass('custom-slider-container');
} else { } else {
$step.find('.section.custom-size').hide(); $step.find('.section.custom-size').hide();
$step.removeClass('custom-disk-size'); $step.removeClass('custom-slider-container');
} }
return true; return true;
@ -516,8 +516,65 @@
var custom = item[args.customFlag]; var custom = item[args.customFlag];
if (custom) { if (custom) {
// contains min/max CPU and Memory values
$step.addClass('custom-size'); $step.addClass('custom-size');
var offeringDetails = item['serviceofferingdetails'];
var offeringCpuSpeed = item['cpuspeed'];
$step.find('.custom-no-limits').hide();
$step.find('.custom-slider-container').hide();
var minCpuNumber = 0, maxCpuNumber = 0, minMemory = 0, maxMemory = 0;
if (offeringDetails){
minCpuNumber = offeringDetails['mincpunumber'];
maxCpuNumber = offeringDetails['maxcpunumber'];
minMemory = offeringDetails['minmemory'];
maxMemory = offeringDetails['maxmemory'];
}
if (minCpuNumber > 0 && maxCpuNumber > 0 && minMemory > 0 && maxMemory > 0) {
$step.find('.custom-slider-container.slider-cpu-speed input[type=text]').val(parseInt(offeringCpuSpeed));
$step.find('.custom-slider-container').show();
var setupSlider = function(sliderClassName, minVal, maxVal) {
$step.find('.custom-slider-container .' + sliderClassName + ' .size.min span').html(minVal);
$step.find('.custom-slider-container .' + sliderClassName + ' input[type=text]').val(minVal);
$step.find('.custom-slider-container .' + sliderClassName + ' .size.max span').html(maxVal);
$step.find('.custom-slider-container .' + sliderClassName + ' .slider').each(function() {
var $slider = $(this);
$slider.slider({
min: parseInt(minVal),
max: parseInt(maxVal),
slide: function(event, ui) {
$slider.closest('.section.custom-size .' + sliderClassName + '').find('input[type=text]').val(ui.value);
$step.find('span.custom-slider-container .' + sliderClassName + '').html(ui.value);
}
});
});
$step.find('.custom-slider-container .' + sliderClassName + ' input[type=text]').bind('change', function() {
var val = $step.find('.custom-slider-container .' + sliderClassName + ' input[type=text]').val();
if (val < minVal || val > maxVal) {
cloudStack.dialog.notice({ message: $.validator.format(_l('message.validate.range'), [minVal, maxVal]) });
}
if (val < minVal) {
val = minVal;
$step.find('.custom-slider-container .' + sliderClassName + ' input[type=text]').val(val);
}
if(val > maxVal) {
val = maxVal;
$step.find('.custom-slider-container .' + sliderClassName + ' input[type=text]').val(val);
}
$step.find('span.custom-slider-container .' + sliderClassName).html(_s(val));
});
}
setupSlider('slider-cpu-cores', minCpuNumber, maxCpuNumber);
setupSlider('slider-memory-mb', minMemory, maxMemory);
} else {
$step.find('.custom-slider-container.slider-cpu-speed.slider-compute-cpu-speed').val(0);
$step.find('.custom-no-limits').show();
}
} else { } else {
$step.find('.custom-no-limits').hide();
$step.find('.custom-slider-container').hide();
$step.removeClass('custom-size'); $step.removeClass('custom-size');
} }
@ -557,7 +614,7 @@
var multiDisk = args.multiDisk; var multiDisk = args.multiDisk;
$step.find('.multi-disk-select-container').remove(); $step.find('.multi-disk-select-container').remove();
$step.removeClass('custom-disk-size'); $step.removeClass('custom-slider-container');
$step.find('.main-desc, p.no-datadisk').remove(); $step.find('.main-desc, p.no-datadisk').remove();
if (!multiDisk){ if (!multiDisk){
@ -675,7 +732,7 @@
} else { } else {
// handle removal of custom size controls // handle removal of custom size controls
$step.find('.section.custom-size').hide(); $step.find('.section.custom-size').hide();
$step.removeClass('custom-disk-size'); $step.removeClass('custom-slider-container');
// handle removal of custom IOPS controls // handle removal of custom IOPS controls
$step.removeClass('custom-iops-do'); $step.removeClass('custom-iops-do');
@ -694,7 +751,7 @@
$('<span>').addClass('custom-size-label') $('<span>').addClass('custom-size-label')
.append(': ') .append(': ')
.append( .append(
$('<span>').addClass('custom-disk-size').html( $('<span>').addClass('custom-slider-container').html(
$step.find('.custom-size input[name=size]').val() $step.find('.custom-size input[name=size]').val()
) )
) )
@ -705,14 +762,14 @@
$('<span>').addClass('custom-size-label') $('<span>').addClass('custom-size-label')
.append(', ') .append(', ')
.append( .append(
$('<span>').addClass('custom-disk-size').html( $('<span>').addClass('custom-slider-container').html(
$step.find('.custom-size input[name=size]').val() $step.find('.custom-size input[name=size]').val()
) )
) )
.append(' GB') .append(' GB')
); );
$step.find('.section.custom-size').show(); $step.find('.section.custom-size').show();
$step.addClass('custom-disk-size'); $step.addClass('custom-slider-container');
$target.closest('.select-container').scrollTop( $target.closest('.select-container').scrollTop(
$target.position().top $target.position().top
); );
@ -723,7 +780,7 @@
$(this).closest('.disk-select-group').removeClass('custom-size'); $(this).closest('.disk-select-group').removeClass('custom-size');
} else { } else {
$step.find('.section.custom-size').hide(); $step.find('.section.custom-size').hide();
$step.removeClass('custom-disk-size'); $step.removeClass('custom-slider-container');
} }
} }
@ -1333,9 +1390,9 @@
args.maxDiskOfferingSize() : 100; args.maxDiskOfferingSize() : 100;
// Setup tabs and slider // Setup tabs and slider
$wizard.find('.section.custom-size.custom-disk-size .size.min span').html(minCustomDiskSize); $wizard.find('.section.custom-size.custom-slider-container .size.min span').html(minCustomDiskSize);
$wizard.find('.section.custom-size.custom-disk-size input[type=text]').val(minCustomDiskSize); $wizard.find('.section.custom-size.custom-slider-container input[type=text]').val(minCustomDiskSize);
$wizard.find('.section.custom-size.custom-disk-size .size.max span').html(maxCustomDiskSize); $wizard.find('.section.custom-size.custom-slider-container .size.max span').html(maxCustomDiskSize);
$wizard.find('.tab-view').tabs(); $wizard.find('.tab-view').tabs();
$wizard.find('.slider').each(function() { $wizard.find('.slider').each(function() {
var $slider = $(this); var $slider = $(this);
@ -1350,7 +1407,7 @@
$slider.closest('.section.custom-size').find('input[type=text]').val( $slider.closest('.section.custom-size').find('input[type=text]').val(
ui.value ui.value
); );
$slider.closest('.step').find('span.custom-disk-size').html( $slider.closest('.step').find('span.custom-slider-container').html(
ui.value ui.value
); );
} }
@ -1359,10 +1416,9 @@
$wizard.find('div.data-disk-offering div.custom-size input[type=text]').bind('change', function() { $wizard.find('div.data-disk-offering div.custom-size input[type=text]').bind('change', function() {
var old = $wizard.find('div.data-disk-offering div.custom-size input[type=text]').val(); var old = $wizard.find('div.data-disk-offering div.custom-size input[type=text]').val();
$wizard.find('div.data-disk-offering span.custom-disk-size').html(_s(old)); $wizard.find('div.data-disk-offering span.custom-slider-container').html(_s(old));
}); });
var wizardDialog = $wizard.dialog({ var wizardDialog = $wizard.dialog({
title: _l('label.vm.add'), title: _l('label.vm.add'),
width: 896, width: 896,