From ee95f45df4e8846215426b412bc62390e1e48270 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Wed, 13 Mar 2019 14:24:39 +0530 Subject: [PATCH] server: allows compute offering with or without constraints Changes allow admin to create compute offerings with unconstrained CPU, Memory or constrained range CPU, memory which can be later set by user while deploying VM. Signed-off-by: Abhishek Kumar --- .../apache/cloudstack/api/ApiConstants.java | 4 + .../offering/CreateServiceOfferingCmd.java | 87 ++++++++++-- .../com/cloud/service/ServiceOfferingVO.java | 4 + .../ConfigurationManagerImpl.java | 69 +++++++--- .../java/com/cloud/vm/UserVmManagerImpl.java | 27 +++- ui/css/cloudstack3.css | 20 ++- ui/index.html | 29 +++- ui/l10n/en.js | 8 ++ ui/scripts/configuration.js | 125 +++++++++++++++++- ui/scripts/docs.js | 25 ++++ ui/scripts/instanceWizard.js | 11 ++ ui/scripts/ui-custom/instanceWizard.js | 77 ++++++++--- 12 files changed, 424 insertions(+), 62 deletions(-) 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 0c1f2b11f327..2f36dad18cc4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -201,6 +201,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 ed87ad86c409..4332a4d21cb6 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 @@ 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.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.11") + 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 Integer getCpuSpeed() { } 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 Integer getMemory() { } 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 String getDeploymentPlanner() { 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 Integer getHypervisorSnapshotReserve() { 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 efaadcfae106..8cc834cecff4 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 boolean isDynamic() { public void setDynamicFlag(boolean isdynamic) { isDynamic = isdynamic; } + + public boolean isCustomCpuSpeedSupported() { + return isCustomized() && getDetail("minCPU") != null; + } } diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 30f2f7cd1e22..a104bfb41aac 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 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.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 DataCenter createZone(final CreateZoneCmd cmd) { @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 ServiceOffering createServiceOffering(final CreateServiceOfferingCmd cmd) 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(); + + // 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"); } - } - - 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 valid domain @@ -2330,7 +2365,7 @@ public ServiceOffering createServiceOffering(final CreateServiceOfferingCmd cmd) 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 68b45e1af7c3..2552792db2c1 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -1017,6 +1017,7 @@ public UserVm upgradeVirtualMachine(UpgradeVMCmd cmd) throws ResourceAllocationE @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) { if (serviceOffering.getCpu() == null) { String cpuNumber = customParameters.get(UsageEventVO.DynamicParameters.cpuNumber.name()); @@ -1033,7 +1034,7 @@ public void validateCustomParameters(ServiceOfferingVO serviceOffering, Map details = serviceOfferingDetailsDao.listDetailsKeyPairs(offering.getId()); + + if (details.containsKey(ApiConstants.MAX_CPU_NUMBER) && details.containsKey(ApiConstants.MIN_CPU_NUMBER) + && details.containsKey(ApiConstants.MIN_MEMORY) && details.containsKey(ApiConstants.MAX_MEMORY)){ + + int cpu = NumbersUtil.parseInt(customParameters.get(UsageEventVO.DynamicParameters.cpuNumber.name()), -1); + + if (cpu < NumbersUtil.parseInt(details.get(ApiConstants.MIN_CPU_NUMBER), -1) && cpu > NumbersUtil.parseInt(details.get(ApiConstants.MAX_CPU_NUMBER), -1)) { + throw new InvalidParameterValueException("The provided cpu value: " + cpu + "is out of the range supported by this custom offering"); + } + + int memory = NumbersUtil.parseInt(customParameters.get(UsageEventVO.DynamicParameters.memory.name()), -1); + + if (memory < NumbersUtil.parseInt(details.get(ApiConstants.MIN_MEMORY), -1) && memory > NumbersUtil.parseInt(details.get(ApiConstants.MAX_MEMORY), -1)) { + throw new InvalidParameterValueException("The provided memory value: " + memory + "is out of the range supported by this custom offering"); + } + + } else { + resourceLimitCheck(owner, isDisplayVm, new Long(offering.getCpu()), new Long(offering.getRamSize())); + } _resourceLimitMgr.checkResourceLimit(owner, ResourceType.volume, (isIso || diskOfferingId == null ? 1 : 2)); _resourceLimitMgr.checkResourceLimit(owner, ResourceType.primary_storage, size); diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index 5294f5658802..a95f48b77650 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -6304,11 +6304,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;*/ @@ -6393,7 +6393,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; } @@ -6401,11 +6405,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; } @@ -6413,7 +6421,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 6e33598ed9c4..547ea040583f 100644 --- a/ui/index.html +++ b/ui/index.html @@ -205,7 +205,7 @@

-
+
@@ -220,6 +220,31 @@

+ +
+
+ + + +
+ + + +
+
+ + + +
+ + + +
+
+ +
+
+
@@ -247,7 +272,7 @@

-
+
diff --git a/ui/l10n/en.js b/ui/l10n/en.js index eb7b82250891..685366091dc7 100644 --- a/ui/l10n/en.js +++ b/ui/l10n/en.js @@ -554,6 +554,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 Unsconstrained", +"label.compute.offering.fixed":"Fixed Offering", "label.compute.offerings":"Compute Offerings", "label.configuration":"Configuration", "label.configure":"Configure", @@ -1039,6 +1043,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", @@ -1223,6 +1229,8 @@ var dictionary = { "label.nuage.vpc.usedomaintemplate":"Use pre-configured Domain Template", "label.nuage.vpc.domaintemplatelist":"Domain Template", "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 de8f4726e66d..5a9555f3d796 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 bbe8f3e64b49..7daea6133c3d 100755 --- a/ui/scripts/docs.js +++ b/ui/scripts/docs.js @@ -1355,5 +1355,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 351ca7b30208..1eeae4288915 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 9b41e35e035c..63bb9e566444 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,54 @@ 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 old = $step.find('.custom-slider-container .' + sliderClassName + ' input[type=text]').val(); + $step.find('span.custom-slider-container .' + sliderClassName).html(_s(old)); + }); + } + 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 +603,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 +721,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 +740,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 +751,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 +769,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 +1379,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 +1396,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 +1405,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'),