This is an automated email from the ASF dual-hosted git repository.
rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/master by this push:
new 2020bfb server: allows compute offering with or without constraints
(#3245)
2020bfb is described below
commit 2020bfb6a3bf597d762e162a355ea3dfd26857eb
Author: Abhishek Kumar <[email protected]>
AuthorDate: Thu May 23 11:47:53 2019 +0530
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 pr [...]
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 <[email protected]>
---
.../org/apache/cloudstack/api/ApiConstants.java | 4 +
.../admin/offering/CreateServiceOfferingCmd.java | 87 ++++++++++++--
.../java/com/cloud/service/ServiceOfferingVO.java | 4 +
.../com/cloud/capacity/CapacityManagerImpl.java | 24 +++-
.../configuration/ConfigurationManagerImpl.java | 69 +++++++++---
.../main/java/com/cloud/vm/UserVmManagerImpl.java | 26 +++--
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 | 88 ++++++++++++---
13 files changed, 444 insertions(+), 76 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 71a201f..0d8cb03 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 ed87ad8..60a55a5 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<String, String> getDetails() {
- Map<String, String> detailsMap = null;
- if (details != null && !details.isEmpty()) {
- detailsMap = new HashMap<String, String>();
+ Map<String, String> detailsMap = new HashMap<>();
+ if (MapUtils.isNotEmpty(details)) {
Collection<?> props = details.values();
- Iterator<?> iter = props.iterator();
- while (iter.hasNext()) {
- HashMap<String, String> detail = (HashMap<String, String>)
iter.next();
+ for (Object prop : props) {
+ HashMap<String, String> detail = (HashMap<String, String>)
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 efaadcf..8cc834c 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 cc2d7a5..6775bfc 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 30f2f7c..a104bfb 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<String, String> 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();
+
+ // 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 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 68b45e1..3727ea6 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<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) {
+ Map<String, String> 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 9e35834..d7b6159 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 a71d823..1140d14 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -206,7 +206,7 @@
</div>
<!-- Custom size slider -->
- <div class="section custom-size">
+ <div class="section custom-size
custom-no-limits">
<div class="field">
<label><translate
key="label.num.cpu.cores"/></label>
<input type="text" class="required
disallowSpecialCharacters" name="compute-cpu-cores" />
@@ -221,6 +221,31 @@
</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 -->
<div class="section custom-iops">
<div class="field">
@@ -248,7 +273,7 @@
</div>
<!-- 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>
<!-- Slider -->
diff --git a/ui/l10n/en.js b/ui/l10n/en.js
index 0c8db8d..c16a955 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 c81a632..4003154 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 016d806..9a73746 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 1234cfa..d175f1f 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 a219c69..706c0bf 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 @@
$('<span>').addClass('custom-size-label')
.append(': ')
.append(
-
$('<span>').addClass('custom-disk-size').html(
+
$('<span>').addClass('custom-slider-container').html(
$step.find('.custom-size input[name=size]').val()
)
)
@@ -705,14 +762,14 @@
$('<span>').addClass('custom-size-label')
.append(', ')
.append(
-
$('<span>').addClass('custom-disk-size').html(
+
$('<span>').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'),