diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 71a201f5bd8..0d8cb03cd3e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -202,6 +202,10 @@ public class ApiConstants { public static final String MAX = "max"; public static final String MAC_ADDRESS = "macaddress"; 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 MODE = "mode"; public static final String KEEPALIVE_ENABLED = "keepaliveenabled"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java index ed87ad86c40..60a55a5ffd7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java @@ -18,10 +18,8 @@ package org.apache.cloudstack.api.command.admin.offering; import java.util.Collection; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; -import com.cloud.storage.Storage; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; 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.response.DomainResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.commons.collections.MapUtils; import org.apache.log4j.Logger; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.offering.ServiceOffering; +import com.cloud.storage.Storage; import com.cloud.user.Account; +import com.google.common.base.Strings; @APICommand(name = "createServiceOffering", description = "Creates a service offering.", responseObject = ServiceOfferingResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -162,6 +164,37 @@ public class CreateServiceOfferingCmd extends BaseCmd { since = "4.4") 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 /////////////////////// ///////////////////////////////////////////////////// @@ -175,6 +208,9 @@ public class CreateServiceOfferingCmd extends BaseCmd { } 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; } @@ -187,6 +223,9 @@ public class CreateServiceOfferingCmd extends BaseCmd { } public String getServiceOfferingName() { + if (Strings.isNullOrEmpty(serviceOfferingName)) { + throw new InvalidParameterValueException("Failed to create service offering because offering name has not been spified."); + } return serviceOfferingName; } @@ -234,18 +273,12 @@ public class CreateServiceOfferingCmd extends BaseCmd { return deploymentPlanner; } - public boolean isCustomized() { - return (cpuNumber == null || memory == null || cpuSpeed == null); - } - public Map getDetails() { - Map detailsMap = null; - if (details != null && !details.isEmpty()) { - detailsMap = new HashMap(); + Map detailsMap = new HashMap<>(); + if (MapUtils.isNotEmpty(details)) { Collection props = details.values(); - Iterator iter = props.iterator(); - while (iter.hasNext()) { - HashMap detail = (HashMap) iter.next(); + for (Object prop : props) { + HashMap detail = (HashMap) prop; detailsMap.put(detail.get("key"), detail.get("value")); } } @@ -316,6 +349,36 @@ public class CreateServiceOfferingCmd extends BaseCmd { 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/////////////////// ///////////////////////////////////////////////////// diff --git a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java index efaadcfae10..8cc834cecff 100644 --- a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java @@ -331,4 +331,8 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering public void setDynamicFlag(boolean isdynamic) { isDynamic = isdynamic; } + + public boolean isCustomCpuSpeedSupported() { + return isCustomized() && getDetail("minCPU") != null; + } } diff --git a/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java b/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java index cc2d7a56b60..6775bfc6cff 100644 --- a/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java +++ b/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java @@ -612,9 +612,15 @@ public class CapacityManagerImpl extends ManagerBase implements CapacityManager, usedMemory += ((Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.memory.name())) * 1024L * 1024L) / ramOvercommitRatio) * clusterRamOvercommitRatio; - usedCpu += - ((Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuNumber.name())) * Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuSpeed.name()))) / cpuOvercommitRatio) * - clusterCpuOvercommitRatio; + if(vmDetails.containsKey(UsageEventVO.DynamicParameters.cpuSpeed.name())) { + usedCpu += + ((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())); } else { usedMemory += ((so.getRamSize() * 1024L * 1024L) / ramOvercommitRatio) * clusterRamOvercommitRatio; @@ -645,9 +651,15 @@ public class CapacityManagerImpl extends ManagerBase implements CapacityManager, reservedMemory += ((Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.memory.name())) * 1024L * 1024L) / ramOvercommitRatio) * clusterRamOvercommitRatio; - reservedCpu += - ((Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuNumber.name())) * Integer.parseInt(vmDetails.get(UsageEventVO.DynamicParameters.cpuSpeed.name()))) / cpuOvercommitRatio) * - clusterCpuOvercommitRatio; + if(vmDetails.containsKey(UsageEventVO.DynamicParameters.cpuSpeed.name())) { + reservedCpu += + ((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())); } else { reservedMemory += ((so.getRamSize() * 1024L * 1024L) / ramOvercommitRatio) * clusterRamOvercommitRatio; diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 30f2f7cd1e2..a104bfb41aa 100755 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -35,12 +35,11 @@ import java.util.UUID; import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.google.common.collect.Sets; - import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroupService; 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.network.CreateManagementNetworkIpRangeCmd; 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.Preconditions; import com.google.common.base.Strings; +import com.google.common.collect.Sets; public class ConfigurationManagerImpl extends ManagerBase implements ConfigurationManager, ConfigurationService, Configurable { 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") public ServiceOffering createServiceOffering(final CreateServiceOfferingCmd cmd) { final Long userId = CallContext.current().getCallingUserId(); + final Map details = cmd.getDetails(); + final String offeringName = cmd.getServiceOfferingName(); final String name = cmd.getServiceOfferingName(); if (name == null || name.length() == 0) { @@ -2233,21 +2235,54 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati final Integer cpuSpeed = cmd.getCpuSpeed(); final Integer memory = cmd.getMemory(); - //restricting the createserviceoffering to allow setting all or none of the dynamic parameters to null - if (cpuNumber == null || cpuSpeed == null || memory == null) { - if (cpuNumber != null || cpuSpeed != null || memory != null) { - throw new InvalidParameterValueException("For creating a custom compute offering cpu, cpu speed and memory all should be null"); - } - } + // Optional Custom Parameters + Integer maxCPU = cmd.getMaxCPUs(); + Integer minCPU = cmd.getMinCPUs(); + Integer maxMemory = cmd.getMaxMemory(); + Integer minMemory = cmd.getMinMemory(); - if (cpuNumber != null && (cpuNumber.intValue() <= 0 || cpuNumber.longValue() > Integer.MAX_VALUE)) { - throw new InvalidParameterValueException("Failed to create service offering " + name + ": 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 " + name + ": 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 " + name + ": specify the memory value between 32 and " + Integer.MAX_VALUE + " MB"); + // Check if service offering is Custom, + // If Customized, the following conditions must hold + // 1. cpuNumber, cpuSpeed and memory should be all null + // 2. minCPU, maxCPU, minMemory and maxMemory should all be null or all specified + boolean isCustomized = cmd.isCustomized(); + if (isCustomized) { + // validate specs + //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 @@ -2330,7 +2365,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati 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.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.getBytesWriteRate(), cmd.getBytesWriteRateMax(), cmd.getBytesWriteRateMaxLength(), cmd.getIopsReadRate(), cmd.getIopsReadRateMax(), cmd.getIopsReadRateMaxLength(), diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 68b45e1af7c..3727ea69960 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -1017,14 +1017,18 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir @Override public void validateCustomParameters(ServiceOfferingVO serviceOffering, Map customParameters) { + //TODO need to validate custom cpu, and memory against min/max CPU/Memory ranges from service_offering_details table if (customParameters.size() != 0) { + Map offeringDetails = serviceOfferingDetailsDao.listDetailsKeyPairs(serviceOffering.getId()); if (serviceOffering.getCpu() == null) { - String cpuNumber = customParameters.get(UsageEventVO.DynamicParameters.cpuNumber.name()); - if ((cpuNumber == null) || (NumbersUtil.parseInt(cpuNumber, -1) <= 0)) { - throw new InvalidParameterValueException("Invalid cpu cores value, specify a value between 1 and " + Integer.MAX_VALUE); + int minCPU = NumbersUtil.parseInt(offeringDetails.get(ApiConstants.MIN_CPU_NUMBER), 1); + int maxCPU = NumbersUtil.parseInt(offeringDetails.get(ApiConstants.MAX_CPU_NUMBER), 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())) { - 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."); } @@ -1033,18 +1037,20 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir if ((cpuSpeed == null) || (NumbersUtil.parseInt(cpuSpeed, -1) <= 0)) { throw new InvalidParameterValueException("Invalid cpu speed value, specify a value between 1 and " + Integer.MAX_VALUE); } - } else if (customParameters.containsKey(UsageEventVO.DynamicParameters.cpuSpeed.name())) { - throw new InvalidParameterValueException("The cpu speed of this offering id:" + serviceOffering.getId() + } else if (!serviceOffering.isCustomCpuSpeedSupported() && customParameters.containsKey(UsageEventVO.DynamicParameters.cpuSpeed.name())) { + throw new InvalidParameterValueException("The cpu speed of this offering id:" + serviceOffering.getUuid() + " is not customizable. This is predefined in the template."); } if (serviceOffering.getRamSize() == null) { - String memory = customParameters.get(UsageEventVO.DynamicParameters.memory.name()); - if (memory == null || (NumbersUtil.parseInt(memory, -1) < 32)) { - throw new InvalidParameterValueException("Invalid memory value, specify a value between 32 and " + Integer.MAX_VALUE + " MB"); + int minMemory = NumbersUtil.parseInt(offeringDetails.get(ApiConstants.MIN_MEMORY), 32); + int maxMemory = NumbersUtil.parseInt(offeringDetails.get(ApiConstants.MAX_MEMORY), Integer.MAX_VALUE); + 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())) { - 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 { throw new InvalidParameterValueException("Need to specify custom parameter values cpu, cpu speed and memory when using custom offering"); diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 9e35834de6d..d7b6159d2bf 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -6117,11 +6117,11 @@ label.error { margin-top: 9px !important; } -.multi-wizard.instance-wizard .custom-disk-size .select-container { +.multi-wizard.instance-wizard .custom-slider-container .select-container { height: 279px; } -.multi-wizard.instance-wizard .custom-disk-size .select-container { +.multi-wizard.instance-wizard .custom-slider-container .select-container { height: 213px; margin: -7px 6px 0 8px; /*+border-radius:6px;*/ @@ -6220,7 +6220,11 @@ label.error { 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; } @@ -6228,11 +6232,15 @@ label.error { 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; } -.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; } @@ -6240,7 +6248,7 @@ label.error { 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; } diff --git a/ui/index.html b/ui/index.html index a71d82395f0..1140d146577 100644 --- a/ui/index.html +++ b/ui/index.html @@ -206,7 +206,7 @@ -
+
@@ -221,6 +221,31 @@
+ +
+
+ + + +
+ + + +
+
+ + + +
+ + + +
+
+ +
+
+
@@ -248,7 +273,7 @@
-
+
diff --git a/ui/l10n/en.js b/ui/l10n/en.js index 0c8db8d68c0..c16a955695c 100644 --- a/ui/l10n/en.js +++ b/ui/l10n/en.js @@ -553,6 +553,10 @@ var dictionary = { "label.compute":"Compute", "label.compute.and.storage":"Compute and Storage", "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.configuration":"Configuration", "label.configure":"Configure", @@ -1037,6 +1041,8 @@ var dictionary = { "label.memory.allocated":"Memory Allocated", "label.memory.limits":"Memory limits (MiB)", "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.used":"Memory Used", "label.menu.accounts":"Accounts", @@ -1219,6 +1225,8 @@ var dictionary = { "label.not.found":"Not Found", "label.notifications":"Notifications", "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.cpu.sockets":"The Number of CPU Sockets", "label.number.of.hosts":"Number of Hosts", diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index c81a632bd69..4003154710a 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -136,11 +136,70 @@ }); } }, - isCustomized: { - label: 'label.custom', - isBoolean: true, - isReverse: true, - isChecked: false + offeringType: { + label: 'label.compute.offering.type', + docID: 'helpComputeOfferingType', + select: function(args) { + 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: { label: 'label.num.cpu.cores', @@ -169,6 +228,39 @@ 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: { label: 'label.network.rate', docID: 'helpComputeOfferingNetworkRate', @@ -588,17 +680,18 @@ }, action: function(args) { + var isFixedOfferingType = args.data.offeringType == 'fixed'; var data = { issystem: false, name: args.data.name, displaytext: args.data.description, storageType: args.data.storageType, provisioningType :args.data.provisioningType, - customized: (args.data.isCustomized == "on") + customized: !isFixedOfferingType }; //custom fields (begin) - if (args.data.isCustomized != "on") { + if (isFixedOfferingType) { $.extend(data, { cpuNumber: args.data.cpuNumber }); @@ -608,6 +701,24 @@ $.extend(data, { 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) diff --git a/ui/scripts/docs.js b/ui/scripts/docs.js index 016d806c3bf..9a737469ced 100755 --- a/ui/scripts/docs.js +++ b/ui/scripts/docs.js @@ -1326,5 +1326,30 @@ cloudStack.docs = { helpL2UserData: { desc: 'Pass user and meta data to VMs (via ConfigDrive)', 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: '' } }; diff --git a/ui/scripts/instanceWizard.js b/ui/scripts/instanceWizard.js index 1234cfa7de3..d175f1f8dfd 100644 --- a/ui/scripts/instanceWizard.js +++ b/ui/scripts/instanceWizard.js @@ -811,6 +811,17 @@ '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') { diff --git a/ui/scripts/ui-custom/instanceWizard.js b/ui/scripts/ui-custom/instanceWizard.js index a219c69d444..706c0bfa4b5 100644 --- a/ui/scripts/ui-custom/instanceWizard.js +++ b/ui/scripts/ui-custom/instanceWizard.js @@ -431,7 +431,7 @@ if (custom) { $step.find('.section.custom-size').hide(); - $step.removeClass('custom-disk-size'); + $step.removeClass('custom-slider-container'); } $step.find('input[type=radio]').bind('change', function() { @@ -464,10 +464,10 @@ var hypervisor = item['hypervisor']; if (hypervisor == 'KVM' || hypervisor == 'XenServer' || hypervisor == 'VMware') { $step.find('.section.custom-size').show(); - $step.addClass('custom-disk-size'); + $step.addClass('custom-slider-container'); } else { $step.find('.section.custom-size').hide(); - $step.removeClass('custom-disk-size'); + $step.removeClass('custom-slider-container'); } return true; @@ -516,8 +516,65 @@ var custom = item[args.customFlag]; if (custom) { + // contains min/max CPU and Memory values $step.addClass('custom-size'); - } else { + 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 { + $step.find('.custom-no-limits').hide(); + $step.find('.custom-slider-container').hide(); $step.removeClass('custom-size'); } @@ -557,7 +614,7 @@ var multiDisk = args.multiDisk; $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(); if (!multiDisk){ @@ -675,7 +732,7 @@ } else { // handle removal of custom size controls $step.find('.section.custom-size').hide(); - $step.removeClass('custom-disk-size'); + $step.removeClass('custom-slider-container'); // handle removal of custom IOPS controls $step.removeClass('custom-iops-do'); @@ -694,7 +751,7 @@ $('').addClass('custom-size-label') .append(': ') .append( - $('').addClass('custom-disk-size').html( + $('').addClass('custom-slider-container').html( $step.find('.custom-size input[name=size]').val() ) ) @@ -705,14 +762,14 @@ $('').addClass('custom-size-label') .append(', ') .append( - $('').addClass('custom-disk-size').html( + $('').addClass('custom-slider-container').html( $step.find('.custom-size input[name=size]').val() ) ) .append(' GB') ); $step.find('.section.custom-size').show(); - $step.addClass('custom-disk-size'); + $step.addClass('custom-slider-container'); $target.closest('.select-container').scrollTop( $target.position().top ); @@ -723,7 +780,7 @@ $(this).closest('.disk-select-group').removeClass('custom-size'); } else { $step.find('.section.custom-size').hide(); - $step.removeClass('custom-disk-size'); + $step.removeClass('custom-slider-container'); } } @@ -1333,9 +1390,9 @@ args.maxDiskOfferingSize() : 100; // Setup tabs and slider - $wizard.find('.section.custom-size.custom-disk-size .size.min span').html(minCustomDiskSize); - $wizard.find('.section.custom-size.custom-disk-size 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.min span').html(minCustomDiskSize); + $wizard.find('.section.custom-size.custom-slider-container input[type=text]').val(minCustomDiskSize); + $wizard.find('.section.custom-size.custom-slider-container .size.max span').html(maxCustomDiskSize); $wizard.find('.tab-view').tabs(); $wizard.find('.slider').each(function() { var $slider = $(this); @@ -1350,7 +1407,7 @@ $slider.closest('.section.custom-size').find('input[type=text]').val( ui.value ); - $slider.closest('.step').find('span.custom-disk-size').html( + $slider.closest('.step').find('span.custom-slider-container').html( ui.value ); } @@ -1359,9 +1416,8 @@ $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(); - $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({ title: _l('label.vm.add'),