diff --git a/api/src/main/java/com/cloud/offering/DiskOffering.java b/api/src/main/java/com/cloud/offering/DiskOffering.java index c2069c253594..1515f9178e2c 100644 --- a/api/src/main/java/com/cloud/offering/DiskOffering.java +++ b/api/src/main/java/com/cloud/offering/DiskOffering.java @@ -16,13 +16,12 @@ // under the License. package com.cloud.offering; -import java.util.Date; - +import com.cloud.storage.Storage.ProvisioningType; import org.apache.cloudstack.acl.InfrastructureEntity; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; -import com.cloud.storage.Storage.ProvisioningType; +import java.util.Date; /** * Represents a disk offering that specifies what the end user needs in @@ -111,6 +110,22 @@ public String toString() { Long getIopsWriteRate(); + Long getMinIopsPerGb(); + + void setMinIopsPerGb(Long minIopsPerGB); + + Long getMaxIopsPerGb(); + + void setMaxIopsPerGb(Long maxIopsPerGB); + + Long getHighestMinIops(); + + void setHighestMinIops(Long highestMinIops); + + Long getHighestMaxIops(); + + void setHighestMaxIops(Long highestMaxIops); + void setHypervisorSnapshotReserve(Integer hypervisorSnapshotReserve); Integer getHypervisorSnapshotReserve(); 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 d9090b892174..e394b2be19c1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -698,6 +698,10 @@ public class ApiConstants { public static final String VIRTUAL_SIZE = "virtualsize"; public static final String NETSCALER_CONTROLCENTER_ID = "netscalercontrolcenterid"; public static final String NETSCALER_SERVICEPACKAGE_ID = "netscalerservicepackageid"; + public static final String MIN_IOPS_PER_GB = "miniopspergb"; + public static final String MAX_IOPS_PER_GB = "maxiopspergb"; + public static final String HIGHEST_MIN_IOPS = "highestminiops"; + public static final String HIGHEST_MAX_IOPS = "highestmaxiops"; public static final String ZONE_ID_LIST = "zoneids"; public static final String DESTINATION_ZONE_ID_LIST = "destzoneids"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java index 747da053be64..03690b887571 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java @@ -98,6 +98,18 @@ public class CreateDiskOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.MAX_IOPS, type = CommandType.LONG, required = false, description = "max iops of the disk offering") private Long maxIops; + @Parameter(name = ApiConstants.MIN_IOPS_PER_GB, type = CommandType.LONG, required = false, description = "IOPS/GB rate for min IOPS. miniops = size * miniopspergb") + private Long minIopsPerGb; + + @Parameter(name = ApiConstants.MAX_IOPS_PER_GB, type = CommandType.LONG, required = false, description = "IOPS/GB rate for max IOPS. maxiops = size * maxiopspergb") + private Long maxIopsPerGb; + + @Parameter(name = ApiConstants.HIGHEST_MIN_IOPS, type = CommandType.LONG, required = false, description = "Highest Min IOPS value that is allowed for this offering") + private Long highestMinIops; + + @Parameter(name = ApiConstants.HIGHEST_MAX_IOPS, type = CommandType.LONG, required = false, description = "Highest Max IOPS value that is allowed for this offering") + private Long highestMaxIops; + @Parameter(name = ApiConstants.HYPERVISOR_SNAPSHOT_RESERVE, type = CommandType.INTEGER, required = false, @@ -176,6 +188,21 @@ public Integer getHypervisorSnapshotReserve() { return hypervisorSnapshotReserve; } + public Long getMinIopsPerGb() { + return minIopsPerGb; + } + + public Long getMaxIopsPerGb() { + return maxIopsPerGb; + } + + public Long getHighestMinIops() { + return highestMinIops; + } + + public Long getHighestMaxIops() { + return highestMaxIops; + } ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java index f579119483b8..f23e86d36174 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java @@ -16,16 +16,14 @@ // under the License. package org.apache.cloudstack.api.response; -import java.util.Date; - +import com.cloud.offering.DiskOffering; +import com.cloud.serializer.Param; import com.google.gson.annotations.SerializedName; - import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; -import com.cloud.offering.DiskOffering; -import com.cloud.serializer.Param; +import java.util.Date; @EntityReference(value = DiskOffering.class) public class DiskOfferingResponse extends BaseResponse { @@ -112,6 +110,21 @@ public class DiskOfferingResponse extends BaseResponse { @Param(description = "whether to display the offering to the end user or not.") private Boolean displayOffering; + @SerializedName(ApiConstants.MIN_IOPS_PER_GB) + @Param(description = "IOPS/GB rate for min IOPS. miniops = size * miniopspergb") + private Long minIopsPerGb; + @SerializedName(ApiConstants.MAX_IOPS_PER_GB) + @Param(description = "IOPS/GB rate for max IOPS. miniops = size * miniopspergb") + private Long maxIopsPerGb; + + @SerializedName(ApiConstants.HIGHEST_MIN_IOPS) + @Param(description = "Highest Min IOPS value that is allowed for this offering") + private Long highestMinIops; + + @SerializedName(ApiConstants.HIGHEST_MAX_IOPS) + @Param(description = "Highest Max IOPS value that is allowed for this offering") + private Long highestMaxIops; + public Boolean getDisplayOffering() { return displayOffering; } @@ -221,6 +234,38 @@ public Integer getHypervisorSnapshotReserve() { return hypervisorSnapshotReserve; } + public Long getMinIopsPerGb() { + return minIopsPerGb; + } + + public void setMinIopsPerGb(Long minIopsPerGb) { + this.minIopsPerGb = minIopsPerGb; + } + + public Long getMaxIopsPerGb() { + return maxIopsPerGb; + } + + public void setMaxIopsPerGb(Long maxIopsPerGb) { + this.maxIopsPerGb = maxIopsPerGb; + } + + public Long getHighestMinIops() { + return highestMinIops; + } + + public void setHighestMinIops(Long highestMinIops) { + this.highestMinIops = highestMinIops; + } + + public Long getHighestMaxIops() { + return highestMaxIops; + } + + public void setHighestMaxIops(Long highestMaxIops) { + this.highestMaxIops = highestMaxIops; + } + public void setHypervisorSnapshotReserve(Integer hypervisorSnapshotReserve) { this.hypervisorSnapshotReserve = hypervisorSnapshotReserve; } @@ -264,4 +309,5 @@ public void setIopsReadRate(Long iopsReadRate) { public void setIopsWriteRate(Long iopsWriteRate) { this.iopsWriteRate = iopsWriteRate; } + } diff --git a/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java b/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java index 5de7f987e12f..496fee9c6a56 100644 --- a/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java @@ -16,9 +16,8 @@ // under the License. package com.cloud.storage; -import java.util.Date; -import java.util.List; -import java.util.UUID; +import com.cloud.offering.DiskOffering; +import com.cloud.utils.db.GenericDao; import javax.persistence.Column; import javax.persistence.DiscriminatorColumn; @@ -35,9 +34,9 @@ import javax.persistence.Temporal; import javax.persistence.TemporalType; import javax.persistence.Transient; - -import com.cloud.offering.DiskOffering; -import com.cloud.utils.db.GenericDao; +import java.util.Date; +import java.util.List; +import java.util.UUID; @Entity @Table(name = "disk_offering") @@ -117,6 +116,18 @@ public class DiskOfferingVO implements DiskOffering { @Column(name = "iops_write_rate") Long iopsWriteRate; + @Column(name = "min_iops_per_gb") + Long minIopsPerGb; + + @Column(name = "max_iops_per_gb") + Long maxIopsPerGb; + + @Column(name = "highest_min_iops") + Long highestMinIops; + + @Column(name = "highest_max_iops") + Long highestMaxIops; + @Column(name = "cache_mode", updatable = true, nullable = false) @Enumerated(value = EnumType.STRING) private DiskCacheMode cacheMode; @@ -465,7 +476,7 @@ public void setDisplayOffering(boolean displayOffering) { this.displayOffering = displayOffering; } - @Override + @Override public void setBytesReadRate(Long bytesReadRate) { this.bytesReadRate = bytesReadRate; } @@ -505,6 +516,46 @@ public Long getIopsWriteRate() { return iopsWriteRate; } + @Override + public Long getMinIopsPerGb() { + return this.minIopsPerGb; + } + + @Override + public void setMinIopsPerGb(Long minIopsPerGb) { + this.minIopsPerGb = minIopsPerGb; + } + + @Override + public Long getMaxIopsPerGb() { + return maxIopsPerGb; + } + + @Override + public void setMaxIopsPerGb(Long maxIopsPerGb) { + this.maxIopsPerGb = maxIopsPerGb; + } + + @Override + public Long getHighestMinIops() { + return this.highestMinIops; + } + + @Override + public void setHighestMinIops(Long highestMinIops) { + this.highestMinIops = highestMinIops; + } + + @Override + public Long getHighestMaxIops() { + return this.highestMaxIops; + } + + @Override + public void setHighestMaxIops(Long highestMaxIops) { + this.highestMaxIops = highestMaxIops; + } + @Override public void setHypervisorSnapshotReserve(Integer hypervisorSnapshotReserve) { this.hypervisorSnapshotReserve = hypervisorSnapshotReserve; diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41100to41200.sql b/engine/schema/src/main/resources/META-INF/db/schema-41100to41200.sql new file mode 100644 index 000000000000..9dc9a5d659c6 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/schema-41100to41200.sql @@ -0,0 +1,70 @@ +-- IOPS per GB disk offering +DROP PROCEDURE IF EXISTS `cloud`.`ADD_COLUMN_TO_TABLE_IDEMPOTENT`; + +DELIMITER // +CREATE PROCEDURE `cloud`.`ADD_COLUMN_TO_TABLE_IDEMPOTENT`( + IN tableName VARCHAR(255), + IN colName VARCHAR(255), + IN colType VARCHAR(255), + IN comment VARCHAR(255)) +BEGIN + SET @t1=CONCAT('SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA="cloud" AND TABLE_NAME="', tableName, '" AND COLUMN_NAME="', colName, '" into @outvar'); + PREPARE stmt1 FROM @t1; + EXECUTE stmt1; + DEALLOCATE PREPARE stmt1; + IF (@outvar < 1) THEN + SET @t2=CONCAT('ALTER TABLE `cloud`.`', tableName, '` ADD COLUMN `', colName, '` ', colType, ' DEFAULT NULL COMMENT "', comment , '"'); + PREPARE stmt2 FROM @t2; + EXECUTE stmt2; + DEALLOCATE PREPARE stmt2; + END IF; END // +DELIMITER ; + +CALL `cloud`.`ADD_COLUMN_TO_TABLE_IDEMPOTENT`('disk_offering', 'min_iops_per_gb', 'int unsigned', 'Min IOPS per GB for rate based offering'); +CALL `cloud`.`ADD_COLUMN_TO_TABLE_IDEMPOTENT`('disk_offering', 'max_iops_per_gb', 'int unsigned', 'Max IOPS per GB for rate based offering'); +CALL `cloud`.`ADD_COLUMN_TO_TABLE_IDEMPOTENT`('disk_offering', 'highest_min_iops', 'int unsigned', 'Highest Min IOPS value for this offering'); +CALL `cloud`.`ADD_COLUMN_TO_TABLE_IDEMPOTENT`('disk_offering', 'highest_max_iops', 'int unsigned', 'Highest Max IOPS value for this offering'); + +DROP PROCEDURE `cloud`.`ADD_COLUMN_TO_TABLE_IDEMPOTENT`; + +DROP VIEW IF EXISTS `cloud`.`disk_offering_view`; +CREATE VIEW `cloud`.`disk_offering_view` AS + select + disk_offering.id, + disk_offering.uuid, + disk_offering.name, + disk_offering.display_text, + disk_offering.provisioning_type, + disk_offering.disk_size, + disk_offering.min_iops, + disk_offering.max_iops, + disk_offering.created, + disk_offering.tags, + disk_offering.customized, + disk_offering.customized_iops, + disk_offering.removed, + disk_offering.use_local_storage, + disk_offering.system_use, + disk_offering.hv_ss_reserve, + disk_offering.bytes_read_rate, + disk_offering.bytes_write_rate, + disk_offering.iops_read_rate, + disk_offering.iops_write_rate, + disk_offering.min_iops_per_gb, + disk_offering.max_iops_per_gb, + disk_offering.highest_min_iops, + disk_offering.highest_max_iops, + disk_offering.cache_mode, + disk_offering.sort_key, + disk_offering.type, + disk_offering.display_offering, + domain.id domain_id, + domain.uuid domain_uuid, + domain.name domain_name, + domain.path domain_path + from + `cloud`.`disk_offering` + left join + `cloud`.`domain` ON disk_offering.domain_id = domain.id + where + disk_offering.state='ACTIVE'; \ No newline at end of file diff --git a/framework/db/src/main/java/com/cloud/utils/db/ScriptRunner.java b/framework/db/src/main/java/com/cloud/utils/db/ScriptRunner.java index 45494b939158..3c23933dc781 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/ScriptRunner.java +++ b/framework/db/src/main/java/com/cloud/utils/db/ScriptRunner.java @@ -20,6 +20,8 @@ */ package com.cloud.utils.db; +import org.apache.log4j.Logger; + import java.io.IOException; import java.io.LineNumberReader; import java.io.Reader; @@ -29,8 +31,6 @@ import java.sql.SQLException; import java.sql.Statement; -import org.apache.log4j.Logger; - /** * Tool to run database scripts */ @@ -38,6 +38,7 @@ public class ScriptRunner { private static Logger s_logger = Logger.getLogger(ScriptRunner.class); private static final String DEFAULT_DELIMITER = ";"; + private static final String CHANGE_DELIMITER_STMT = "DELIMITER "; private Connection connection; @@ -128,6 +129,12 @@ private void runScript(Connection conn, Reader reader) throws IOException, SQLEx // Do nothing } else if (trimmedLine.length() < 1 || trimmedLine.startsWith("#")) { // Do nothing + } else if (trimmedLine.startsWith(CHANGE_DELIMITER_STMT)) { + String[] res = trimmedLine.split(CHANGE_DELIMITER_STMT, 2); + if (res.length == 2) { + String newDelimiter = res[1]; + setDelimiter(newDelimiter, fullLineDelimiter); + } } else if (!fullLineDelimiter && trimmedLine.endsWith(getDelimiter()) || fullLineDelimiter && trimmedLine.equals(getDelimiter())) { command.append(line.substring(0, line.lastIndexOf(getDelimiter()))); command.append(" "); diff --git a/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java index 9ca44872fec1..9e2c5ee817b2 100644 --- a/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java @@ -16,14 +16,6 @@ // under the License. package com.cloud.api.query.dao; -import java.util.List; - - -import org.apache.log4j.Logger; -import org.springframework.stereotype.Component; - -import org.apache.cloudstack.api.response.DiskOfferingResponse; - import com.cloud.api.query.vo.DiskOfferingJoinVO; import com.cloud.offering.DiskOffering; import com.cloud.offering.ServiceOffering; @@ -31,6 +23,11 @@ import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.api.response.DiskOfferingResponse; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import java.util.List; @Component public class DiskOfferingJoinDaoImpl extends GenericDaoBase implements DiskOfferingJoinDao { @@ -62,6 +59,10 @@ public DiskOfferingResponse newDiskOfferingResponse(DiskOfferingJoinVO offering) diskOfferingResponse.setDiskSize(offering.getDiskSize() / (1024 * 1024 * 1024)); diskOfferingResponse.setMinIops(offering.getMinIops()); diskOfferingResponse.setMaxIops(offering.getMaxIops()); + diskOfferingResponse.setMinIopsPerGb(offering.getMinIopsPerGb()); + diskOfferingResponse.setMaxIopsPerGb(offering.getMaxIopsPerGb()); + diskOfferingResponse.setHighestMinIops(offering.getHighestMinIops()); + diskOfferingResponse.setHighestMaxIops(offering.getHighestMaxIops()); diskOfferingResponse.setDomain(offering.getDomainName()); diskOfferingResponse.setDomainId(offering.getDomainUuid()); diff --git a/server/src/main/java/com/cloud/api/query/vo/DiskOfferingJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/DiskOfferingJoinVO.java index e846b0b646f9..4a19d01d7395 100644 --- a/server/src/main/java/com/cloud/api/query/vo/DiskOfferingJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/DiskOfferingJoinVO.java @@ -16,19 +16,17 @@ // under the License. package com.cloud.api.query.vo; -import java.util.Date; +import com.cloud.offering.DiskOffering.Type; +import com.cloud.storage.Storage; +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; - -import com.cloud.storage.Storage; -import org.apache.cloudstack.api.Identity; -import org.apache.cloudstack.api.InternalIdentity; - -import com.cloud.offering.DiskOffering.Type; -import com.cloud.utils.db.GenericDao; +import java.util.Date; @Entity @Table(name = "disk_offering_view") @@ -119,6 +117,18 @@ public class DiskOfferingJoinVO extends BaseViewVO implements InternalIdentity, @Column(name = "display_offering") boolean displayOffering; + @Column(name = "min_iops_per_gb") + Long minIopsPerGb; + + @Column(name = "max_iops_per_gb") + Long maxIopsPerGb; + + @Column(name = "highest_min_iops") + Long highestMinIops; + + @Column(name = "highest_max_iops") + Long highestMaxIops; + public DiskOfferingJoinVO() { } @@ -239,4 +249,20 @@ public Long getIopsReadRate() { public Long getIopsWriteRate() { return iopsWriteRate; } + + public Long getMinIopsPerGb() { + return minIopsPerGb; + } + + public Long getMaxIopsPerGb() { + return maxIopsPerGb; + } + + public Long getHighestMinIops() { + return highestMinIops; + } + + public Long getHighestMaxIops() { + return highestMaxIops; + } } diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 1632da95f954..b36905fb8543 100755 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -95,6 +95,7 @@ import org.apache.commons.collections.MapUtils; import org.apache.log4j.Logger; + import com.cloud.alert.AlertManager; import com.cloud.api.ApiDBUtils; import com.cloud.capacity.CapacityManager; @@ -2548,8 +2549,8 @@ public ServiceOffering updateServiceOffering(final UpdateServiceOfferingCmd cmd) protected DiskOfferingVO createDiskOffering(final Long userId, final Long domainId, final String name, final String description, final String provisioningType, final Long numGibibytes, String tags, boolean isCustomized, final boolean localStorageRequired, final boolean isDisplayOfferingEnabled, final Boolean isCustomizedIops, Long minIops, Long maxIops, - Long bytesReadRate, Long bytesWriteRate, Long iopsReadRate, Long iopsWriteRate, - final Integer hypervisorSnapshotReserve) { + Long bytesReadRate, Long bytesWriteRate, Long iopsReadRate, Long iopsWriteRate, Long minIopsPerGb, Long maxIopsPerGb, + Long highestMinIops, Long highestMaxIops, final Integer hypervisorSnapshotReserve) { long diskSize = 0;// special case for custom disk offerings if (numGibibytes != null && numGibibytes <= 0) { throw new InvalidParameterValueException("Please specify a disk size of at least 1 Gb."); @@ -2566,6 +2567,61 @@ protected DiskOfferingVO createDiskOffering(final Long userId, final Long domain isCustomized = true; } + if (minIopsPerGb != null || maxIopsPerGb != null) { + + if (!isCustomized) { + throw new InvalidParameterValueException("Cannot set Min/Max IOPS/GB for a fixed size disk offering"); + } + + if ((isCustomizedIops != null && isCustomizedIops) || minIops != null || maxIops != null) { + throw new InvalidParameterValueException("Cannot set Min/Max IOPS/GB with either " + + "custom IOPS or fixed IOPS"); + } + + if (minIopsPerGb != null && maxIopsPerGb != null) { + if (minIopsPerGb <= 0 || maxIopsPerGb <= 0) { + throw new InvalidParameterValueException("Min/Max IOPS/GB value must be greater than 0"); + } + + if (minIopsPerGb > maxIopsPerGb){ + throw new InvalidParameterValueException("Min IOPS/GB must be greater than max IOPS/GB"); + } + } + + //if either one of them is set but the other is not + if ((minIopsPerGb != null && maxIopsPerGb == null) || (minIopsPerGb == null && maxIopsPerGb != null)) { + throw new InvalidParameterValueException("Both min IOPS/GB and max IOPS/GB must be specified"); + } + } + + if (highestMinIops != null && highestMaxIops != null) { + if (highestMinIops > highestMaxIops){ + throw new InvalidParameterValueException("highestminiops must be less than highestmaxiops"); + } + if (highestMinIops <= 0 || highestMaxIops <= 0) { + throw new InvalidParameterValueException("highestminiops/highestmaxiops value must be greater than 0"); + } + + + if (minIopsPerGb == null && (isCustomizedIops == null || !isCustomizedIops)) { + throw new InvalidParameterValueException("highestminops specified but none of customizediops or miniopspergb specified"); + } + if (minIops != null) { + throw new InvalidParameterValueException("highestminiops cannot be specified with fixed miniops"); + } + + if (maxIopsPerGb == null && (isCustomizedIops == null || !isCustomizedIops)) { + throw new InvalidParameterValueException("highestmaxiops specified but none of customizediops or maxiopspergb specified"); + } + if (maxIops != null) { + throw new InvalidParameterValueException("highestmaxiops cannot be specified with fixed maxiops"); + } + }else { + if (highestMaxIops != null || highestMinIops != null) { + throw new InvalidParameterValueException("Both highestminiops and highestmaxiops should be specified"); + } + } + if (isCustomizedIops != null) { bytesReadRate = null; bytesWriteRate = null; @@ -2637,6 +2693,20 @@ protected DiskOfferingVO createDiskOffering(final Long userId, final Long domain newDiskOffering.setIopsWriteRate(iopsWriteRate); } + if (highestMinIops != null && highestMinIops > 0) { + newDiskOffering.setHighestMinIops(highestMinIops); + } + if (highestMaxIops != null && highestMaxIops > 0) { + newDiskOffering.setHighestMaxIops(highestMaxIops); + } + + if (minIopsPerGb != null && minIopsPerGb > 0) { + newDiskOffering.setMinIopsPerGb(minIopsPerGb); + } + if (maxIopsPerGb != null && maxIopsPerGb > 0) { + newDiskOffering.setMaxIopsPerGb(maxIopsPerGb); + } + if (hypervisorSnapshotReserve != null && hypervisorSnapshotReserve < 0) { throw new InvalidParameterValueException("If provided, Hypervisor Snapshot Reserve must be greater than or equal to 0."); } @@ -2697,11 +2767,16 @@ public DiskOffering createDiskOffering(final CreateDiskOfferingCmd cmd) { final Long iopsReadRate = cmd.getIopsReadRate(); final Long iopsWriteRate = cmd.getIopsWriteRate(); final Integer hypervisorSnapshotReserve = cmd.getHypervisorSnapshotReserve(); + final Long minIopsPerGb = cmd.getMinIopsPerGb(); + final Long maxIopsPerGb = cmd.getMaxIopsPerGb(); + final Long highestMinIops = cmd.getHighestMinIops(); + final Long highestMaxIops = cmd.getHighestMaxIops(); final Long userId = CallContext.current().getCallingUserId(); return createDiskOffering(userId, domainId, name, description, provisioningType, numGibibytes, tags, isCustomized, localStorageRequired, isDisplayOfferingEnabled, isCustomizedIops, minIops, - maxIops, bytesReadRate, bytesWriteRate, iopsReadRate, iopsWriteRate, hypervisorSnapshotReserve); + maxIops, bytesReadRate, bytesWriteRate, iopsReadRate, iopsWriteRate, minIopsPerGb, maxIopsPerGb, + highestMinIops, highestMaxIops, hypervisorSnapshotReserve); } @Override diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index bea9b4ad5bfb..074d442e3956 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -647,6 +647,24 @@ public VolumeVO allocVolume(CreateVolumeCmd cmd) throws ResourceAllocationExcept } } + //check if iops/gb is defined, if so, use it + if (diskOffering.getMinIopsPerGb() != null) { + minIops = sizeInGB * diskOffering.getMinIopsPerGb(); + } + + if (diskOffering.getMaxIopsPerGb() != null) { + maxIops = sizeInGB * diskOffering.getMaxIopsPerGb(); + } + + //check limits for IOPS and set them if required + if (diskOffering.getHighestMinIops() != null && minIops !=null && minIops > diskOffering.getHighestMinIops()) { + minIops = diskOffering.getHighestMinIops(); + } + + if (diskOffering.getHighestMaxIops() != null && maxIops != null && maxIops > diskOffering.getHighestMaxIops()) { + maxIops = diskOffering.getHighestMaxIops(); + } + provisioningType = diskOffering.getProvisioningType(); if (!validateVolumeSizeRange(size)) {// convert size from mb to gb @@ -671,12 +689,29 @@ public VolumeVO allocVolume(CreateVolumeCmd cmd) throws ResourceAllocationExcept // if zoneId is not provided, we default to create volume in the same zone as the snapshot zone. zoneId = snapshotCheck.getDataCenterId(); } - size = snapshotCheck.getSize(); // ; disk offering is used for tags - // purposes + size = snapshotCheck.getSize(); // ; disk offering is used for tags purposes + Long sizeInGB = size/(1024 * 1024 * 1024); minIops = snapshotCheck.getMinIops(); maxIops = snapshotCheck.getMaxIops(); + // IOPS/GB overrides the manually set IOPS + if (diskOffering.getMinIopsPerGb() != null) { + minIops = sizeInGB * diskOffering.getMinIopsPerGb(); + } + + if (diskOffering.getMaxIopsPerGb() != null) { + maxIops = sizeInGB * diskOffering.getMaxIopsPerGb(); + } + + if (diskOffering.getHighestMinIops() != null && minIops != null && minIops > diskOffering.getHighestMinIops()) { + minIops = diskOffering.getHighestMinIops(); + } + + if (diskOffering.getHighestMaxIops() != null && maxIops != null && maxIops > diskOffering.getHighestMaxIops()) { + maxIops = diskOffering.getHighestMaxIops(); + } + provisioningType = diskOffering.getProvisioningType(); // check snapshot permissions _accountMgr.checkAccess(caller, null, true, snapshotCheck); @@ -699,7 +734,6 @@ public VolumeVO allocVolume(CreateVolumeCmd cmd) throws ResourceAllocationExcept // permission check _accountMgr.checkAccess(caller, null, false, vm); } - } // Check that the resource limit for primary storage won't be exceeded @@ -857,9 +891,10 @@ protected VolumeVO createVolumeFromSnapshot(VolumeVO volume, long snapshotId, Lo @ActionEvent(eventType = EventTypes.EVENT_VOLUME_RESIZE, eventDescription = "resizing volume", async = true) public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationException { Long newSize; + Long newSizeInGb; Long newMinIops; Long newMaxIops; - Integer newHypervisorSnapshotReserve; + Integer newHypervisorSnapshotReserve = null; boolean shrinkOk = cmd.getShrinkOk(); VolumeVO volume = _volsDao.findById(cmd.getEntityId()); @@ -916,9 +951,8 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep // no parameter provided; just use the original size of the volume newSize = volume.getSize(); } - + newSizeInGb = newSize >> 30; newMinIops = cmd.getMinIops(); - if (newMinIops != null) { if (!volume.getVolumeType().equals(Volume.Type.ROOT) && (diskOffering.isCustomizedIops() == null || !diskOffering.isCustomizedIops())) { throw new InvalidParameterValueException("The current disk offering does not support customization of the 'Min IOPS' parameter."); @@ -926,19 +960,25 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep } else { // no parameter provided; just use the original min IOPS of the volume - newMinIops = volume.getMinIops(); + newMinIops = volume.getMinIops(); } - newMaxIops = cmd.getMaxIops(); + if (diskOffering.getMinIopsPerGb() != null) { + newMinIops = newSizeInGb * diskOffering.getMinIopsPerGb(); + } + newMaxIops = cmd.getMaxIops(); if (newMaxIops != null) { if (!volume.getVolumeType().equals(Volume.Type.ROOT) && (diskOffering.isCustomizedIops() == null || !diskOffering.isCustomizedIops())) { throw new InvalidParameterValueException("The current disk offering does not support customization of the 'Max IOPS' parameter."); } } else { - // no parameter provided; just use the original max IOPS of the volume - newMaxIops = volume.getMaxIops(); + // no parameter provided; just use the original max IOPS of the volume + newMaxIops = volume.getMaxIops(); + } + if (diskOffering.getMaxIopsPerGb()!=null) { + newMaxIops = newSizeInGb * diskOffering.getMaxIopsPerGb(); } validateIops(newMinIops, newMaxIops); @@ -981,6 +1021,7 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep newSize = newDiskOffering.getDiskSize(); } + newSizeInGb = newSize >> 30; if (!volume.getSize().equals(newSize) && !volume.getVolumeType().equals(Volume.Type.DATADISK)) { throw new InvalidParameterValueException("Only data volumes can be resized via a new disk offering."); } @@ -996,6 +1037,14 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep newMaxIops = newDiskOffering.getMaxIops(); } + if (newDiskOffering.getMinIopsPerGb() != null) { + newMinIops = newSizeInGb * newDiskOffering.getMinIopsPerGb(); + } + + if (newDiskOffering.getMaxIopsPerGb() != null) { + newMaxIops = newSizeInGb * newDiskOffering.getMaxIopsPerGb(); + } + // if the hypervisor snapshot reserve value is null, it must remain null (currently only KVM uses null and null is all KVM uses for a value here) newHypervisorSnapshotReserve = volume.getHypervisorSnapshotReserve() != null ? newDiskOffering.getHypervisorSnapshotReserve() : null; } @@ -1051,6 +1100,16 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep } } + Long highestMinIops = diskOffering.getHighestMinIops(); + if (newMinIops != null && highestMinIops!= null && newMinIops > highestMinIops) { + newMinIops = highestMinIops; + } + + Long highestMaxIops = diskOffering.getHighestMaxIops(); + if (newMaxIops != null && highestMaxIops!= null && newMaxIops > highestMaxIops) { + newMaxIops = highestMaxIops; + } + // Note: The storage plug-in in question should perform validation on the IOPS to check if a sufficient number of IOPS is available to perform // the requested change @@ -1062,7 +1121,6 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep volume.setMinIops(newMinIops); volume.setMaxIops(newMaxIops); volume.setHypervisorSnapshotReserve(newHypervisorSnapshotReserve); - if (newDiskOffering != null) { volume.setDiskOfferingId(cmd.getNewDiskOfferingId()); } diff --git a/server/src/main/java/com/cloud/test/DatabaseConfig.java b/server/src/main/java/com/cloud/test/DatabaseConfig.java index 657eb4e4ce54..e7e56675e37e 100644 --- a/server/src/main/java/com/cloud/test/DatabaseConfig.java +++ b/server/src/main/java/com/cloud/test/DatabaseConfig.java @@ -16,27 +16,6 @@ // under the License. package com.cloud.test; -import java.io.File; -import java.io.IOException; -import java.net.URISyntaxException; -import java.security.NoSuchAlgorithmException; -import java.sql.Date; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - import com.cloud.host.Status; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDaoImpl; @@ -52,7 +31,6 @@ import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.net.NfsUtils; -import org.apache.cloudstack.utils.security.DigestHelper; import org.apache.log4j.Logger; import org.apache.log4j.xml.DOMConfigurator; import org.w3c.dom.Document; @@ -62,6 +40,28 @@ import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.NoSuchAlgorithmException; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.apache.cloudstack.utils.security.DigestHelper; + + public class DatabaseConfig { private static final Logger s_logger = Logger.getLogger(DatabaseConfig.class.getName()); @@ -1021,6 +1021,18 @@ protected void saveDiskOffering() { Long iopsWriteRate = Long.parseLong(_currentObjectParams.get("iopsWriteRate")); if (iopsWriteRate != null && (iopsWriteRate > 0)) diskOffering.setIopsWriteRate(iopsWriteRate); + Long minIopsPerGb = Long.parseLong(_currentObjectParams.get("minIopsPerGb")); + if (minIopsPerGb > 0) + diskOffering.setMinIopsPerGb(minIopsPerGb); + Long maxIopsPerGb = Long.parseLong(_currentObjectParams.get("maxIopsPerGb")); + if (maxIopsPerGb > 0) + diskOffering.setMaxIopsPerGb(maxIopsPerGb); + Long highestMinIops = Long.parseLong(_currentObjectParams.get("highestMinIops")); + if (highestMinIops > 0) + diskOffering.setHighestMinIops(highestMinIops); + Long highestMaxIops = Long.parseLong(_currentObjectParams.get("highestMaxIops")); + if (highestMaxIops > 0) + diskOffering.setHighestMaxIops(highestMaxIops); DiskOfferingDaoImpl offering = ComponentContext.inject(DiskOfferingDaoImpl.class); try { diff --git a/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java b/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java index 8648033c9b0a..9bb7590b42d9 100644 --- a/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java +++ b/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java @@ -17,35 +17,6 @@ package com.cloud.configuration; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.UUID; - -import org.apache.log4j.Logger; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -import org.apache.cloudstack.api.command.admin.vlan.DedicatePublicIpRangeCmd; -import org.apache.cloudstack.api.command.admin.vlan.ReleasePublicIpRangeCmd; -import org.apache.cloudstack.api.command.user.network.ListNetworkOfferingsCmd; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; -import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; -import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; - import com.cloud.configuration.Resource.ResourceType; import com.cloud.dc.AccountVlanMapVO; import com.cloud.dc.ClusterVO; @@ -68,17 +39,16 @@ import com.cloud.network.Network; import com.cloud.network.Network.Capability; import com.cloud.network.NetworkModel; -import com.cloud.network.Networks; import com.cloud.network.dao.FirewallRulesDao; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.PhysicalNetworkDao; import com.cloud.network.dao.PhysicalNetworkVO; -import com.cloud.offering.NetworkOffering; -import com.cloud.offerings.NetworkOfferingVO; -import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.projects.ProjectManager; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.Storage; import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; @@ -87,16 +57,37 @@ import com.cloud.user.User; import com.cloud.user.UserVO; import com.cloud.user.dao.AccountDao; -import com.cloud.utils.db.Filter; -import com.cloud.utils.db.SearchCriteria; +import com.cloud.user.dao.UserDao; import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.Ip; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.dao.VMInstanceDao; +import org.apache.cloudstack.api.command.admin.vlan.DedicatePublicIpRangeCmd; +import org.apache.cloudstack.api.command.admin.vlan.ReleasePublicIpRangeCmd; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; +import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; +import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; +import org.apache.log4j.Logger; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.UUID; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.AdditionalAnswers.returnsFirstArg; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; @@ -111,7 +102,6 @@ public class ConfigurationManagerTest { private static final Logger s_logger = Logger.getLogger(ConfigurationManagerTest.class); - @InjectMocks ConfigurationManagerImpl configurationMgr = new ConfigurationManagerImpl(); DedicatePublicIpRangeCmd dedicatePublicIpRangesCmd = new DedicatePublicIpRangeCmdExtn(); @@ -129,8 +119,6 @@ public class ConfigurationManagerTest { @Mock NetworkOrchestrationService _networkMgr; @Mock - NetworkOfferingDao _networkOfferingDao; - @Mock AccountDao _accountDao; @Mock VlanDao _vlanDao; @@ -165,7 +153,9 @@ public class ConfigurationManagerTest { @Mock ImageStoreDao _imageStoreDao; @Mock - ConfigurationDao _configDao; + UserDao _userDao; + @Mock + DiskOfferingDao _diskOfferingDao; VlanVO vlan = new VlanVO(Vlan.VlanType.VirtualNetwork, "vlantag", "vlangateway", "vlannetmask", 1L, "iprange", 1L, 1L, null, null, null); @@ -177,6 +167,28 @@ public class ConfigurationManagerTest { @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); + configurationMgr._accountMgr = _accountMgr; + configurationMgr._projectMgr = _projectMgr; + configurationMgr._resourceLimitMgr = _resourceLimitMgr; + configurationMgr._networkMgr = _networkMgr; + configurationMgr._accountDao = _accountDao; + configurationMgr._vlanDao = _vlanDao; + configurationMgr._accountVlanMapDao = _accountVlanMapDao; + configurationMgr._domainVlanMapDao = _domainVlanMapDao; + configurationMgr._publicIpAddressDao = _publicIpAddressDao; + configurationMgr._zoneDao = _zoneDao; + configurationMgr._firewallDao = _firewallDao; + configurationMgr._ipAddrMgr = _ipAddrMgr; + configurationMgr._networkModel = _networkModel; + configurationMgr._privateIpAddressDao = _privateIpAddressDao; + configurationMgr._volumeDao = _volumeDao; + configurationMgr._hostDao = _hostDao; + configurationMgr._vmInstanceDao = _vmInstanceDao; + configurationMgr._clusterDao = _clusterDao; + configurationMgr._podDao = _podDao; + configurationMgr._physicalNetworkDao = _physicalNetworkDao; + configurationMgr._imageStoreDao = _imageStoreDao; + Account account = new AccountVO("testaccount", 1, "networkdomain", (short)0, UUID.randomUUID().toString()); when(configurationMgr._accountMgr.getAccount(anyLong())).thenReturn(account); @@ -247,7 +259,7 @@ public void testDedicatePublicIpRange() throws Exception { /* * TEST 5: given range is already allocated to a different account DedicatePublicIpRange should fail */ - runDedicatePublicIpRangeIPAddressAllocated(); + runDedicatePublicIpRangeIPAdressAllocated(); } @Test @@ -373,8 +385,8 @@ void runDedicatePublicIpRangeInvalidZone() throws Exception { } } - void runDedicatePublicIpRangeIPAddressAllocated() throws Exception { - TransactionLegacy txn = TransactionLegacy.open("runDedicatePublicIpRangeIPAddressAllocated"); + void runDedicatePublicIpRangeIPAdressAllocated() throws Exception { + TransactionLegacy txn = TransactionLegacy.open("runDedicatePublicIpRangeIPAdressAllocated"); when(configurationMgr._vlanDao.findById(anyLong())).thenReturn(vlan); @@ -397,7 +409,7 @@ void runDedicatePublicIpRangeIPAddressAllocated() throws Exception { } catch (Exception e) { Assert.assertTrue(e.getMessage().contains("Public IP address in range is allocated to another account")); } finally { - txn.close("runDedicatePublicIpRangeIPAddressAllocated"); + txn.close("runDedicatePublicIpRangeIPAdressAllocated"); } } @@ -488,26 +500,6 @@ void runReleaseNonDedicatedPublicIpRange() throws Exception { } } - @Test - public void searchForNetworkOfferingsTest() { - List offerings = Arrays.asList( - new NetworkOfferingVO("off1", "off1", Networks.TrafficType.Guest, false, false, null, null, false, NetworkOffering.Availability.Optional, null, Network.GuestType.Isolated, true, false, false, false, false, false), - new NetworkOfferingVO("off2", "off2", Networks.TrafficType.Guest, false, false, null, null, false, NetworkOffering.Availability.Optional, null, Network.GuestType.Isolated, true, false, false, false, false, false), - new NetworkOfferingVO("off3", "off3", Networks.TrafficType.Guest, false, false, null, null, false, NetworkOffering.Availability.Optional, null, Network.GuestType.Isolated, true, false, false, false, false, true) - ); - - Mockito.when(_networkOfferingDao.createSearchCriteria()).thenReturn(Mockito.mock(SearchCriteria.class)); - Mockito.when(_networkOfferingDao.search(Mockito.any(SearchCriteria.class), Mockito.any(Filter.class))).thenReturn(offerings); - - ListNetworkOfferingsCmd cmd = Mockito.spy(ListNetworkOfferingsCmd.class); - Mockito.when(cmd.getPageSize()).thenReturn(10); - - assertThat(configurationMgr.searchForNetworkOfferings(cmd).second(), is(3)); - - Mockito.when(cmd.getForVpc()).thenReturn(Boolean.FALSE); - assertThat(configurationMgr.searchForNetworkOfferings(cmd).second(), is(2)); - } - @Test public void validateEmptyStaticNatServiceCapablitiesTest() { Map staticNatServiceCapabilityMap = new HashMap(); @@ -892,23 +884,353 @@ public void hasSameSubnetTest() { Assert.assertTrue(result); } - @Test(expected = CloudRuntimeException.class) - public void testGetVlanNumberFromUriInvalidParameter() { - configurationMgr.getVlanNumberFromUri("vlan"); + @Test + public void testCreateDiskOfferingNoIopsFixedSize(){ + + configurationMgr._accountDao = _accountDao; + configurationMgr._userDao = _userDao; + configurationMgr._diskOfferingDao = _diskOfferingDao; + + UserVO userVO = Mockito.mock(UserVO.class); + AccountVO accountVO = Mockito.mock(AccountVO.class); + when(accountVO.getType()).thenReturn(Account.ACCOUNT_TYPE_ADMIN); + + when(configurationMgr._userDao.findById(anyLong())).thenReturn(userVO); + when(configurationMgr._accountDao.findById(anyLong())).thenReturn(accountVO); + when(configurationMgr._diskOfferingDao.persist(any(DiskOfferingVO.class))).then(returnsFirstArg()); + + DiskOfferingVO diskOfferingVO = configurationMgr.createDiskOffering(1L, 1L, "test-vol", "test-description", Storage.ProvisioningType.THIN.toString(), + 10L, null, false, false, false, null, null, null, null, null, null, null, null, null, null, null, null); + + Assert.assertEquals((10L * 1024 * 1024 * 1024), diskOfferingVO.getDiskSize()); + Assert.assertNull(diskOfferingVO.getMinIops()); + Assert.assertNull(diskOfferingVO.getMaxIops()); + Assert.assertFalse(diskOfferingVO.isCustomized()); + Assert.assertNull(diskOfferingVO.isCustomizedIops()); + Assert.assertNull(diskOfferingVO.getHighestMinIops()); + Assert.assertNull(diskOfferingVO.getHighestMaxIops()); + Assert.assertNull(diskOfferingVO.getMinIopsPerGb()); + Assert.assertNull(diskOfferingVO.getMaxIopsPerGb()); } - @Test(expected = CloudRuntimeException.class) - public void testGetVlanNumberFromUriInvalidSintax() { - configurationMgr.getVlanNumberFromUri("xxx://7"); + @Test + public void testCreateDiskOfferingFixedIopsFixedSizeNoHighest(){ + configurationMgr._accountDao = _accountDao; + configurationMgr._userDao = _userDao; + configurationMgr._diskOfferingDao = _diskOfferingDao; + + UserVO userVO = Mockito.mock(UserVO.class); + AccountVO accountVO = Mockito.mock(AccountVO.class); + when(accountVO.getType()).thenReturn(Account.ACCOUNT_TYPE_ADMIN); + + when(configurationMgr._userDao.findById(anyLong())).thenReturn(userVO); + when(configurationMgr._accountDao.findById(anyLong())).thenReturn(accountVO); + when(configurationMgr._diskOfferingDao.persist(any(DiskOfferingVO.class))).then(returnsFirstArg()); + + DiskOfferingVO diskOfferingVO = configurationMgr.createDiskOffering(1L, 1L, "test-vol", "test-description", Storage.ProvisioningType.THIN.toString(), + 10L, null, false, false, false, null, null, null, null, null, null, null, null, null, null, null, null); + + Assert.assertEquals((10L * 1024 * 1024 * 1024), diskOfferingVO.getDiskSize()); + Assert.assertNull(diskOfferingVO.getMinIops()); + Assert.assertNull(diskOfferingVO.getMaxIops()); + Assert.assertFalse(diskOfferingVO.isCustomized()); + Assert.assertNull(diskOfferingVO.isCustomizedIops()); + Assert.assertNull(diskOfferingVO.getHighestMinIops()); + Assert.assertNull(diskOfferingVO.getHighestMaxIops()); + Assert.assertNull(diskOfferingVO.getMinIopsPerGb()); + Assert.assertNull(diskOfferingVO.getMaxIopsPerGb()); + } + + @Test + public void testCreateDiskOfferingNoIopsFixedSizeWithHighest(){ + configurationMgr._accountDao = _accountDao; + configurationMgr._userDao = _userDao; + configurationMgr._diskOfferingDao = _diskOfferingDao; + boolean seenException = false; + + UserVO userVO = Mockito.mock(UserVO.class); + AccountVO accountVO = Mockito.mock(AccountVO.class); + when(accountVO.getType()).thenReturn(Account.ACCOUNT_TYPE_ADMIN); + + when(configurationMgr._userDao.findById(anyLong())).thenReturn(userVO); + when(configurationMgr._accountDao.findById(anyLong())).thenReturn(accountVO); + when(configurationMgr._diskOfferingDao.persist(any(DiskOfferingVO.class))).then(returnsFirstArg()); + + try { + DiskOfferingVO diskOfferingVO = configurationMgr.createDiskOffering(1L, 1L, "test-vol", "test-description", Storage.ProvisioningType.THIN.toString(), + 10L, null, false, false, false, null, null, null, null, null, null, null, null, null, 1000L, 5000L, null); + } catch (InvalidParameterValueException e) { + Assert.assertTrue(e.toString().contains("highestminops specified but none of customizediops or miniopspergb specified")); + seenException = true; + } + + Assert.assertTrue("InvalidParameterValueException expected but got no exception", seenException); + } + + @Test + public void testCreateDiskOfferingFixedIopsFixedSizeWithHighest(){ + configurationMgr._accountDao = _accountDao; + configurationMgr._userDao = _userDao; + configurationMgr._diskOfferingDao = _diskOfferingDao; + boolean seenException = false; + + UserVO userVO = Mockito.mock(UserVO.class); + AccountVO accountVO = Mockito.mock(AccountVO.class); + when(accountVO.getType()).thenReturn(Account.ACCOUNT_TYPE_ADMIN); + + when(configurationMgr._userDao.findById(anyLong())).thenReturn(userVO); + when(configurationMgr._accountDao.findById(anyLong())).thenReturn(accountVO); + when(configurationMgr._diskOfferingDao.persist(any(DiskOfferingVO.class))).then(returnsFirstArg()); + + try { + DiskOfferingVO diskOfferingVO = configurationMgr.createDiskOffering(1L, 1L, "test-vol", "test-description", Storage.ProvisioningType.THIN.toString(), + 10L, null, false, false, false, null, 1000L, 5000L, null, null, null, null, null, null, 1000L, 5000L, null); + } catch (InvalidParameterValueException e) { + Assert.assertTrue(e.toString().contains("highestminops specified but none of customizediops or miniopspergb specified")); + seenException = true; + } + + Assert.assertTrue("InvalidParameterValueException expected but got no exception", seenException); + + } + + @Test + public void testCreateDiskOfferingCustomIopsFixedSizeNoHighest(){ + configurationMgr._accountDao = _accountDao; + configurationMgr._userDao = _userDao; + configurationMgr._diskOfferingDao = _diskOfferingDao; + + UserVO userVO = Mockito.mock(UserVO.class); + AccountVO accountVO = Mockito.mock(AccountVO.class); + when(accountVO.getType()).thenReturn(Account.ACCOUNT_TYPE_ADMIN); + + when(configurationMgr._userDao.findById(anyLong())).thenReturn(userVO); + when(configurationMgr._accountDao.findById(anyLong())).thenReturn(accountVO); + when(configurationMgr._diskOfferingDao.persist(any(DiskOfferingVO.class))).then(returnsFirstArg()); + + DiskOfferingVO diskOfferingVO = configurationMgr.createDiskOffering(1L, 1L, "test-vol", "test-description", Storage.ProvisioningType.THIN.toString(), + 10L, null, false, false, false, true, null, null, null, null, null, null, null, null, null, null, null); + + Assert.assertEquals((10L * 1024 * 1024 * 1024), diskOfferingVO.getDiskSize()); + Assert.assertNull(diskOfferingVO.getMinIops()); + Assert.assertNull(diskOfferingVO.getMaxIops()); + Assert.assertFalse(diskOfferingVO.isCustomized()); + Assert.assertTrue(diskOfferingVO.isCustomizedIops()); + Assert.assertNull(diskOfferingVO.getHighestMinIops()); + Assert.assertNull(diskOfferingVO.getHighestMaxIops()); + Assert.assertNull(diskOfferingVO.getMinIopsPerGb()); + Assert.assertNull(diskOfferingVO.getMaxIopsPerGb()); + + } + + @Test + public void testCreateDiskOfferingCustomIopsFixedSizeWithHighest(){ + configurationMgr._accountDao = _accountDao; + configurationMgr._userDao = _userDao; + configurationMgr._diskOfferingDao = _diskOfferingDao; + DiskOfferingVO diskOfferingVO = null; + UserVO userVO = Mockito.mock(UserVO.class); + AccountVO accountVO = Mockito.mock(AccountVO.class); + when(accountVO.getType()).thenReturn(Account.ACCOUNT_TYPE_ADMIN); + + when(configurationMgr._userDao.findById(anyLong())).thenReturn(userVO); + when(configurationMgr._accountDao.findById(anyLong())).thenReturn(accountVO); + when(configurationMgr._diskOfferingDao.persist(any(DiskOfferingVO.class))).then(returnsFirstArg()); + + Long testHighestMinIops = 1000L; + Long testHighestMaxIops = 5000L; + boolean seenException = false; + + diskOfferingVO = configurationMgr.createDiskOffering(1L, 1L, "test-vol", "test-description", Storage.ProvisioningType.THIN.toString(), + 10L, null, false, false, false, true, null, null, null, null, null, null, null, null, testHighestMinIops, testHighestMaxIops, null); + + Assert.assertEquals((10L * 1024 * 1024 * 1024), diskOfferingVO.getDiskSize()); + Assert.assertNull(diskOfferingVO.getMinIops()); + Assert.assertNull(diskOfferingVO.getMaxIops()); + Assert.assertFalse(diskOfferingVO.isCustomized()); + Assert.assertTrue(diskOfferingVO.isCustomizedIops()); + Assert.assertEquals(testHighestMinIops, diskOfferingVO.getHighestMinIops()); + Assert.assertEquals(testHighestMaxIops, diskOfferingVO.getHighestMaxIops()); + Assert.assertNull(diskOfferingVO.getMinIopsPerGb()); + Assert.assertNull(diskOfferingVO.getMaxIopsPerGb()); + + // highestminiops specified but no highestmaxiops + try { + diskOfferingVO = configurationMgr.createDiskOffering(1L, 1L, "test-vol", "test-description", Storage.ProvisioningType.THIN.toString(), + 10L, null, false, false, false, true, null, null, null, null, null, null, null, null, testHighestMinIops, null, null); + }catch (InvalidParameterValueException e) { + Assert.assertTrue("Incorrect exception raised", e.toString().contains("Both highestminiops and highestmaxiops should be specified")); + seenException = true; + } + Assert.assertTrue("Expected to raise an exception, but no exception was raised", seenException); + + // highestmaxiops specified but no highestminiops + seenException = false; + try { + diskOfferingVO = configurationMgr.createDiskOffering(1L, 1L, "test-vol", "test-description", Storage.ProvisioningType.THIN.toString(), + 10L, null, false, false, false, true, null, null, null, null, null, null, null, null, null, testHighestMaxIops, null); + }catch (InvalidParameterValueException e) { + Assert.assertTrue("Incorrect exception raised", e.toString().contains("Both highestminiops and highestmaxiops should be specified")); + seenException = true; + } + Assert.assertTrue("Expected to raise an exception, but no exception was raised", seenException); + + // highest min > highest max + testHighestMinIops = 5000L; + testHighestMaxIops = 1000L; + seenException = false; + try { + diskOfferingVO = configurationMgr.createDiskOffering(1L, 1L, "test-vol", "test-description", Storage.ProvisioningType.THIN.toString(), + 10L, null, false, false, false, true, null, null, null, null, null, null, null, null, testHighestMinIops, testHighestMaxIops, null); + }catch (InvalidParameterValueException e) { + Assert.assertTrue("Incorrect exception raised", e.toString().contains("highestminiops must be less than highestmaxiops")); + seenException = true; + } + Assert.assertTrue("Expected to raise an exception, but no exception was raised", seenException); + + //non positive value for highestMinIops + testHighestMinIops = -1L; + testHighestMaxIops = 1000L; + seenException = false; + try { + diskOfferingVO = configurationMgr.createDiskOffering(1L, 1L, "test-vol", "test-description", Storage.ProvisioningType.THIN.toString(), + 10L, null, false, false, false, true, null, null, null, null, null, null, null, null, testHighestMinIops, testHighestMaxIops, null); + }catch (InvalidParameterValueException e) { + Assert.assertTrue("Incorrect exception raised", e.toString().contains("highestminiops/highestmaxiops value must be greater than 0")); + seenException = true; + } + Assert.assertTrue("Expected to raise an exception, but no exception was raised", seenException); + } + + @Test + public void testCreateDiskOfferingCustomIopsFixedSizeWithIopsGb(){ + configurationMgr._accountDao = _accountDao; + configurationMgr._userDao = _userDao; + configurationMgr._diskOfferingDao = _diskOfferingDao; + DiskOfferingVO diskOfferingVO = null; + UserVO userVO = Mockito.mock(UserVO.class); + AccountVO accountVO = Mockito.mock(AccountVO.class); + when(accountVO.getType()).thenReturn(Account.ACCOUNT_TYPE_ADMIN); + + when(configurationMgr._userDao.findById(anyLong())).thenReturn(userVO); + when(configurationMgr._accountDao.findById(anyLong())).thenReturn(accountVO); + when(configurationMgr._diskOfferingDao.persist(any(DiskOfferingVO.class))).then(returnsFirstArg()); + + Long testMinIopsPerGb = 10L; + Long testMaxIopsPerGb = 50L; + boolean seenException = false; + + try { + diskOfferingVO = configurationMgr.createDiskOffering(1L, 1L, "test-vol", "test-description", Storage.ProvisioningType.THIN.toString(), + 10L, null, false, false, false, true, null, null, null, null, null, null, testMinIopsPerGb, testMaxIopsPerGb, null, null, null); + } catch (InvalidParameterValueException e) { + Assert.assertTrue("Incorrect exception raised:" + e.toString(), e.toString().contains("Cannot set Min/Max IOPS/GB for a fixed size disk offering")); + seenException = true; + } + Assert.assertTrue("Expected to raise an exception, but no exception was raised", seenException); + } + + @Test + public void testCreateDiskOfferingFixedIopsOrCustomIopsWithIopsGb(){ + configurationMgr._accountDao = _accountDao; + configurationMgr._userDao = _userDao; + configurationMgr._diskOfferingDao = _diskOfferingDao; + DiskOfferingVO diskOfferingVO = null; + UserVO userVO = Mockito.mock(UserVO.class); + AccountVO accountVO = Mockito.mock(AccountVO.class); + when(accountVO.getType()).thenReturn(Account.ACCOUNT_TYPE_ADMIN); + + when(configurationMgr._userDao.findById(anyLong())).thenReturn(userVO); + when(configurationMgr._accountDao.findById(anyLong())).thenReturn(accountVO); + when(configurationMgr._diskOfferingDao.persist(any(DiskOfferingVO.class))).then(returnsFirstArg()); + + Long testMinIopsPerGb = 10L; + Long testMaxIopsPerGb = 50L; + boolean seenException = false; + + //fixed iops, custom size + try { + diskOfferingVO = configurationMgr.createDiskOffering(1L, 1L, "test-vol", "test-description", Storage.ProvisioningType.THIN.toString(), + null, null, true, false, false, null, 1000L, 5000L, null, null, null, null, testMinIopsPerGb, testMaxIopsPerGb, null, null, null); + } catch (InvalidParameterValueException e) { + Assert.assertTrue("Incorrect exception raised:" + e.toString(), e.toString().contains("Cannot set Min/Max IOPS/GB with either custom IOPS or fixed IOPS")); + seenException = true; + } + Assert.assertTrue("Expected to raise an exception, but no exception was raised", seenException); + + + //custom iops, custom size + seenException = false; + try { + diskOfferingVO = configurationMgr.createDiskOffering(1L, 1L, "test-vol", "test-description", Storage.ProvisioningType.THIN.toString(), + 10L, null, true, false, false, true, null, null, null, null, null, null, testMinIopsPerGb, testMaxIopsPerGb, null, null, null); + } catch (InvalidParameterValueException e) { + Assert.assertTrue("Incorrect exception raised:" + e.toString(), e.toString().contains("Cannot set Min/Max IOPS/GB with either custom IOPS or fixed IOPS")); + seenException = true; + } + Assert.assertTrue("Expected to raise an exception, but no exception was raised", seenException); } @Test - public void testGetVlanNumberFromUriVlan() { - Assert.assertEquals("7", configurationMgr.getVlanNumberFromUri("vlan://7")); + public void testCreateDiskOfferingCustomSizeWithIopsGb(){ + configurationMgr._accountDao = _accountDao; + configurationMgr._userDao = _userDao; + configurationMgr._diskOfferingDao = _diskOfferingDao; + DiskOfferingVO diskOfferingVO = null; + UserVO userVO = Mockito.mock(UserVO.class); + AccountVO accountVO = Mockito.mock(AccountVO.class); + when(accountVO.getType()).thenReturn(Account.ACCOUNT_TYPE_ADMIN); + + when(configurationMgr._userDao.findById(anyLong())).thenReturn(userVO); + when(configurationMgr._accountDao.findById(anyLong())).thenReturn(accountVO); + when(configurationMgr._diskOfferingDao.persist(any(DiskOfferingVO.class))).then(returnsFirstArg()); + + Long testMinIopsPerGb = 10L; + Long testMaxIopsPerGb = 50L; + boolean seenException = false; + + diskOfferingVO = configurationMgr.createDiskOffering(1L, 1L, "test-vol", "test-description", Storage.ProvisioningType.THIN.toString(), + null, null, true, false, false, null, null, null, null, null, null, null, testMinIopsPerGb, testMaxIopsPerGb, null, null, null); + + Assert.assertNull(diskOfferingVO.getMinIops()); + Assert.assertNull(diskOfferingVO.getMaxIops()); + Assert.assertTrue(diskOfferingVO.isCustomized()); + Assert.assertNull(diskOfferingVO.isCustomizedIops()); + Assert.assertEquals(testMinIopsPerGb, diskOfferingVO.getMinIopsPerGb()); + Assert.assertEquals(testMaxIopsPerGb, diskOfferingVO.getMaxIopsPerGb()); + Assert.assertNull(diskOfferingVO.getHighestMinIops()); + Assert.assertNull(diskOfferingVO.getHighestMaxIops()); } @Test - public void testGetVlanNumberFromUriUntagged() { - Assert.assertEquals("untagged", configurationMgr.getVlanNumberFromUri("vlan://untagged")); + public void testCreateDiskOfferingCustomSizeWithIopsGbWithHighest(){ + configurationMgr._accountDao = _accountDao; + configurationMgr._userDao = _userDao; + configurationMgr._diskOfferingDao = _diskOfferingDao; + DiskOfferingVO diskOfferingVO = null; + UserVO userVO = Mockito.mock(UserVO.class); + AccountVO accountVO = Mockito.mock(AccountVO.class); + when(accountVO.getType()).thenReturn(Account.ACCOUNT_TYPE_ADMIN); + + when(configurationMgr._userDao.findById(anyLong())).thenReturn(userVO); + when(configurationMgr._accountDao.findById(anyLong())).thenReturn(accountVO); + when(configurationMgr._diskOfferingDao.persist(any(DiskOfferingVO.class))).then(returnsFirstArg()); + + Long testMinIopsPerGb = 10L; + Long testMaxIopsPerGb = 50L; + Long testHighestMinIops = 500L; + Long testHighestMaxIops = 1000L; + + diskOfferingVO = configurationMgr.createDiskOffering(1L, 1L, "test-vol", "test-description", Storage.ProvisioningType.THIN.toString(), + null, null, true, false, false, null, null, null, null, null, null, null, testMinIopsPerGb, testMaxIopsPerGb, testHighestMinIops, testHighestMaxIops, null); + + Assert.assertNull(diskOfferingVO.getMinIops()); + Assert.assertNull(diskOfferingVO.getMaxIops()); + Assert.assertTrue(diskOfferingVO.isCustomized()); + Assert.assertNull(diskOfferingVO.isCustomizedIops()); + Assert.assertEquals(testMinIopsPerGb, diskOfferingVO.getMinIopsPerGb()); + Assert.assertEquals(testMaxIopsPerGb, diskOfferingVO.getMaxIopsPerGb()); + Assert.assertEquals(testHighestMinIops, diskOfferingVO.getHighestMinIops()); + Assert.assertEquals(testHighestMaxIops, diskOfferingVO.getHighestMaxIops()); } } diff --git a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java index 16f39bcddff0..aab37499f3e2 100644 --- a/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java +++ b/server/src/test/java/com/cloud/storage/VolumeApiServiceImplTest.java @@ -16,19 +16,26 @@ // under the License. package com.cloud.storage; +import com.cloud.configuration.ConfigurationManager; +import com.cloud.configuration.Resource; import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.DataCenterDao; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.ResourceAllocationException; +import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.offering.DiskOffering; import com.cloud.org.Grouping; import com.cloud.serializer.GsonHelper; +import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.AccountVO; +import com.cloud.user.ResourceLimitService; import com.cloud.user.User; import com.cloud.user.UserVO; +import com.cloud.user.dao.AccountDao; import com.cloud.utils.db.TransactionLegacy; import com.cloud.vm.UserVmManager; import com.cloud.vm.UserVmVO; @@ -38,14 +45,11 @@ import com.cloud.vm.dao.VMInstanceDao; import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; -import com.cloud.user.dao.AccountDao; -import com.cloud.user.ResourceLimitService; -import com.cloud.configuration.Resource; -import com.cloud.host.dao.HostDao; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd; import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd; +import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; @@ -58,11 +62,11 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.junit.Assert; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @@ -73,6 +77,7 @@ import java.util.List; import java.util.UUID; +import static junit.framework.TestCase.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; @@ -123,6 +128,10 @@ public class VolumeApiServiceImplTest { AccountDao _accountDao; @Mock HostDao _hostDao; + @Mock + DiskOfferingDao _diskOfferingDao; + @Mock + ConfigurationManager _configMgr; DetachVolumeCmd detachCmd = new DetachVolumeCmd(); Class _detachCmdClass = detachCmd.getClass(); @@ -145,6 +154,8 @@ public void setup() throws Exception { _svc._resourceLimitMgr = _resourceLimitMgr; _svc._accountDao = _accountDao; _svc._hostDao = _hostDao; + _svc._diskOfferingDao = _diskOfferingDao; + _svc._configMgr = _configMgr; _svc._gson = GsonHelper.getGsonLogger(); // mock caller context @@ -486,6 +497,256 @@ public void testResourceLimitCheckForUploadedVolume() throws NoSuchFieldExceptio } } + @Test + public void testResizeVolumeFromFixedSizeToCustomSize() throws NoSuchFieldException, IllegalAccessException { + ResizeVolumeCmd resizeVolumeCmd = Mockito.mock(ResizeVolumeCmd.class); + when(resizeVolumeCmd.getNewDiskOfferingId()).thenReturn(2L); + when(resizeVolumeCmd.getEntityId()).thenReturn(1L); + when(resizeVolumeCmd.getSize()).thenReturn(10L); + + VolumeVO volumeVO = new VolumeVO(Volume.Type.DATADISK, "test-vol", 1, 1, 1, 1, Storage.ProvisioningType.THIN, + 10L, null, null, null); + Field IdField = VolumeVO.class.getDeclaredField("id"); + IdField.setAccessible(true); + IdField.set(volumeVO, 1L); + + Field maxVolSizeField = _svc.getClass().getDeclaredField("_maxVolumeSizeInGb"); + maxVolSizeField.setAccessible(true); + maxVolSizeField.setLong(_svc, 2 * 1024); + + DiskOfferingVO diskOfferingVO = new DiskOfferingVO(1L,"fixed-size","fixed-size", Storage.ProvisioningType.THIN, + 10L, "", false, false, null, null, null); + DiskOfferingVO newDiskOfferingVO = new DiskOfferingVO(1L, "custom-size", "custom-size", Storage.ProvisioningType.THIN, + 0L, "", true, false, null, null, null); + + VolumeVO newVolume = null; + + when(_svc._volsDao.findById(1L)).thenReturn(volumeVO); + when(_svc._volsDao.getHypervisorType(1L)).thenReturn(HypervisorType.XenServer); + when(_svc._volsDao.update(anyLong(), any(VolumeVO.class))).thenReturn(true); + + when(_svc._vmSnapshotDao.findByVm(anyLong())).thenReturn(new ArrayList()); + + when(_svc._diskOfferingDao.findById(1L)).thenReturn(diskOfferingVO); + when(_svc._diskOfferingDao.findById(2L)).thenReturn(newDiskOfferingVO); + doNothing().when(_svc._configMgr).checkDiskOfferingAccess(any(Account.class), any(DiskOffering.class)); + + try { + newVolume = _svc.resizeVolume(resizeVolumeCmd); + Assert.assertEquals(Long.valueOf(2L), newVolume.getDiskOfferingId()); + } catch (ResourceAllocationException e) { + fail(e.getMessage()); + } + } + + @Test + public void testResizeVolumeFromCustomSizeFixedIopsToFixedSize() throws NoSuchFieldException, IllegalAccessException { + ResizeVolumeCmd resizeVolumeCmd = Mockito.mock(ResizeVolumeCmd.class); + when(resizeVolumeCmd.getNewDiskOfferingId()).thenReturn(2L); + when(resizeVolumeCmd.getEntityId()).thenReturn(1L); + when(resizeVolumeCmd.getSize()).thenReturn(10L); + + VolumeVO volumeVO = new VolumeVO(Volume.Type.DATADISK, "test-vol", 1, 1, 1, 1, Storage.ProvisioningType.THIN, + 10L, null, null, null); + Field IdField = VolumeVO.class.getDeclaredField("id"); + IdField.setAccessible(true); + IdField.set(volumeVO, 1L); + + Field maxVolSizeField = _svc.getClass().getDeclaredField("_maxVolumeSizeInGb"); + maxVolSizeField.setAccessible(true); + maxVolSizeField.setLong(_svc, 2 * 1024); + + DiskOfferingVO diskOfferingVO = new DiskOfferingVO(1L,"custom-size-fixed-iops","custom-size-fixed-iops", + Storage.ProvisioningType.THIN, 0L, "", true, false, 100L, 200L, null); + DiskOfferingVO newDiskOfferingVO = new DiskOfferingVO(1L, "fixed-size", "fixed-size", Storage.ProvisioningType.THIN, + 0L, "", true, false, null, null, null); + + VolumeVO newVolume = null; + + when(_svc._volsDao.findById(1L)).thenReturn(volumeVO); + when(_svc._volsDao.getHypervisorType(1L)).thenReturn(HypervisorType.XenServer); + when(_svc._volsDao.update(anyLong(), any(VolumeVO.class))).thenReturn(true); + + when(_svc._vmSnapshotDao.findByVm(anyLong())).thenReturn(new ArrayList()); + + when(_svc._diskOfferingDao.findById(1L)).thenReturn(diskOfferingVO); + when(_svc._diskOfferingDao.findById(2L)).thenReturn(newDiskOfferingVO); + doNothing().when(_svc._configMgr).checkDiskOfferingAccess(any(Account.class), any(DiskOffering.class)); + + try { + newVolume = _svc.resizeVolume(resizeVolumeCmd); + Assert.assertEquals(Long.valueOf(2L), newVolume.getDiskOfferingId()); + } catch (ResourceAllocationException e) { + fail(e.getMessage()); + } + } + + @Test + public void testResizeVolumeFromFixedSizeFixedIopsToCustomSizeIopsPerGb() throws NoSuchFieldException, IllegalAccessException { + Long newSize = 20L * 1024 * 1024 * 1024; + Long newSizeGb = 20L; + Long minIopsPerGb = 10L; + Long maxIopsPerGb = 20L; + Long newMinIops = newSizeGb * minIopsPerGb; + Long newMaxIops = newSizeGb * maxIopsPerGb; + + ResizeVolumeCmd resizeVolumeCmd = Mockito.mock(ResizeVolumeCmd.class); + when(resizeVolumeCmd.getNewDiskOfferingId()).thenReturn(2L); + when(resizeVolumeCmd.getEntityId()).thenReturn(1L); + when(resizeVolumeCmd.getSize()).thenReturn(newSizeGb); + + VolumeVO volumeVO = new VolumeVO(Volume.Type.DATADISK, "test-vol", 1, 1, 1, 1, Storage.ProvisioningType.THIN, + 10L, 100L, 200L, null); + Field IdField = VolumeVO.class.getDeclaredField("id"); + IdField.setAccessible(true); + IdField.set(volumeVO, 1L); + + Field maxVolSizeField = _svc.getClass().getDeclaredField("_maxVolumeSizeInGb"); + maxVolSizeField.setAccessible(true); + maxVolSizeField.setLong(_svc, 2 * 1024); + + DiskOfferingVO diskOfferingVO = new DiskOfferingVO(1L,"fixed-size-fixed-iops","fixed-size-fixed-iops", + Storage.ProvisioningType.THIN, 10L, "", false, false, 100L, 200L, null); + DiskOfferingVO newDiskOfferingVO = new DiskOfferingVO(1L, "custom-size-iopspergb", "custom-size-iopspergb", Storage.ProvisioningType.THIN, + 0L, "", true, false, null, null, null); + newDiskOfferingVO.setMinIopsPerGb(10L); + newDiskOfferingVO.setMaxIopsPerGb(20L); + + VolumeVO newVolume; + + when(_svc._volsDao.findById(1L)).thenReturn(volumeVO); + when(_svc._volsDao.getHypervisorType(1L)).thenReturn(HypervisorType.XenServer); + when(_svc._volsDao.update(anyLong(), any(VolumeVO.class))).thenReturn(true); + + when(_svc._vmSnapshotDao.findByVm(anyLong())).thenReturn(new ArrayList()); + + when(_svc._diskOfferingDao.findById(1L)).thenReturn(diskOfferingVO); + when(_svc._diskOfferingDao.findById(2L)).thenReturn(newDiskOfferingVO); + doNothing().when(_svc._configMgr).checkDiskOfferingAccess(any(Account.class), any(DiskOffering.class)); + + try { + newVolume = _svc.resizeVolume(resizeVolumeCmd); + Assert.assertEquals(newMinIops, newVolume.getMinIops()); + Assert.assertEquals(newMaxIops, newVolume.getMaxIops()); + Assert.assertEquals(newSize, newVolume.getSize()); + Assert.assertEquals(Volume.State.Allocated, newVolume.getState()); + } catch (ResourceAllocationException e) { + fail(e.getMessage()); + } + + } + + @Test + public void testResizeVolumeFromCustomSizeIopsPerGbToFixedSizeFixedIops() throws NoSuchFieldException, IllegalAccessException { + Long newSize = 20L * 1024 * 1024 * 1024; + Long newSizeGb = 20L; + Long newMinIops = 500L; + Long newMaxIops = 600L; + + ResizeVolumeCmd resizeVolumeCmd = Mockito.mock(ResizeVolumeCmd.class); + when(resizeVolumeCmd.getNewDiskOfferingId()).thenReturn(2L); + when(resizeVolumeCmd.getEntityId()).thenReturn(1L); + + VolumeVO volumeVO = new VolumeVO(Volume.Type.DATADISK, "test-vol", 1, 1, 1, 1, Storage.ProvisioningType.THIN, + newSize, 100L, 200L, null); + Field IdField = VolumeVO.class.getDeclaredField("id"); + IdField.setAccessible(true); + IdField.set(volumeVO, 1L); + + Field maxVolSizeField = _svc.getClass().getDeclaredField("_maxVolumeSizeInGb"); + maxVolSizeField.setAccessible(true); + maxVolSizeField.setLong(_svc, 2 * 1024); // 2 TB max vol size + + DiskOfferingVO diskOfferingVO = new DiskOfferingVO(1L, "custom-size-iopspergb", "custom-size-iopspergb", Storage.ProvisioningType.THIN, + 0L, "", true, false, null, null, null); + diskOfferingVO.setMinIopsPerGb(10L); + diskOfferingVO.setMaxIopsPerGb(20L); + + DiskOfferingVO newDiskOfferingVO = new DiskOfferingVO(1L,"fixed-size-fixed-iops","fixed-size-fixed-iops", + Storage.ProvisioningType.THIN, newSize, "", false, false, newMinIops, newMaxIops, null); + + VolumeVO newVolume; + + when(_svc._volsDao.findById(1L)).thenReturn(volumeVO); + when(_svc._volsDao.getHypervisorType(1L)).thenReturn(HypervisorType.XenServer); + when(_svc._volsDao.update(anyLong(), any(VolumeVO.class))).thenReturn(true); + + when(_svc._vmSnapshotDao.findByVm(anyLong())).thenReturn(new ArrayList()); + + when(_svc._diskOfferingDao.findById(1L)).thenReturn(diskOfferingVO); + when(_svc._diskOfferingDao.findById(2L)).thenReturn(newDiskOfferingVO); + doNothing().when(_svc._configMgr).checkDiskOfferingAccess(any(Account.class), any(DiskOffering.class)); + + try { + newVolume = _svc.resizeVolume(resizeVolumeCmd); + Assert.assertEquals(newMinIops, newVolume.getMinIops()); + Assert.assertEquals(newMaxIops, newVolume.getMaxIops()); + Assert.assertEquals(newSize, newVolume.getSize()); + Assert.assertEquals(Volume.State.Allocated, newVolume.getState()); + } catch (ResourceAllocationException e) { + fail(e.getMessage()); + } + } + + @Test + public void testResizeVolumeFromCustomSizeIopsPerGbToCustomSizeIopsPerGb() throws NoSuchFieldException, IllegalAccessException { + Long oldSize = 10L * 1024 * 1024 * 1024; + Long oldSizeGb = 10L; + Long oldMinIopsPerGb = 10L; + Long oldMaxIopsPerGb = 30L; + + Long newSize = 10L * 1024 * 1024 * 1024; + Long newSizeGb = 10L; + Long newMinIopsPerGb = 20L; + Long newMaxIopsPerGb = 50L; + + ResizeVolumeCmd resizeVolumeCmd = Mockito.mock(ResizeVolumeCmd.class); + when(resizeVolumeCmd.getNewDiskOfferingId()).thenReturn(2L); + when(resizeVolumeCmd.getEntityId()).thenReturn(1L); + + VolumeVO volumeVO = new VolumeVO(Volume.Type.DATADISK, "test-vol", 1, 1, 1, 1, Storage.ProvisioningType.THIN, + oldSize, oldSizeGb * oldMinIopsPerGb, oldSizeGb * oldMaxIopsPerGb, null); + + Field IdField = VolumeVO.class.getDeclaredField("id"); + IdField.setAccessible(true); + IdField.set(volumeVO, 1L); + + Field maxVolSizeField = _svc.getClass().getDeclaredField("_maxVolumeSizeInGb"); + maxVolSizeField.setAccessible(true); + maxVolSizeField.setLong(_svc, 2 * 1024); // 2 TB max vol size + + DiskOfferingVO diskOfferingVO = new DiskOfferingVO(1L, "custom-size-iopspergb-old", "custom-size-iopspergb-old", Storage.ProvisioningType.THIN, + 0L, "", true, false, null, null, null); + diskOfferingVO.setMinIopsPerGb(oldMinIopsPerGb); + diskOfferingVO.setMaxIopsPerGb(oldMaxIopsPerGb); + + DiskOfferingVO newDiskOfferingVO = new DiskOfferingVO(1L,"custom-size-iopspergb-new","custom-size-iopspergb-new", + Storage.ProvisioningType.THIN, newSize, "", false, false, null, null, null); + newDiskOfferingVO.setMinIopsPerGb(newMinIopsPerGb); + newDiskOfferingVO.setMaxIopsPerGb(newMaxIopsPerGb); + + VolumeVO newVolume; + + when(_svc._volsDao.findById(1L)).thenReturn(volumeVO); + when(_svc._volsDao.getHypervisorType(1L)).thenReturn(HypervisorType.XenServer); + when(_svc._volsDao.update(anyLong(), any(VolumeVO.class))).thenReturn(true); + + when(_svc._vmSnapshotDao.findByVm(anyLong())).thenReturn(new ArrayList()); + + when(_svc._diskOfferingDao.findById(1L)).thenReturn(diskOfferingVO); + when(_svc._diskOfferingDao.findById(2L)).thenReturn(newDiskOfferingVO); + doNothing().when(_svc._configMgr).checkDiskOfferingAccess(any(Account.class), any(DiskOffering.class)); + + try { + newVolume = _svc.resizeVolume(resizeVolumeCmd); + Assert.assertEquals(new Long(newMinIopsPerGb * newSizeGb), newVolume.getMinIops()); + Assert.assertEquals(new Long(newMaxIopsPerGb * newSizeGb), newVolume.getMaxIops()); + Assert.assertEquals(newSize, newVolume.getSize()); + Assert.assertEquals(Volume.State.Allocated, newVolume.getState()); + } catch (ResourceAllocationException e) { + fail(e.getMessage()); + } + } @After public void tearDown() { diff --git a/setup/db/db/schema-41000to41100.sql b/setup/db/db/schema-41000to41100.sql new file mode 100644 index 000000000000..0e8e3300e04f --- /dev/null +++ b/setup/db/db/schema-41000to41100.sql @@ -0,0 +1,324 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +--; +-- Schema upgrade from 4.10.0.0 to 4.11.0.0 +--; + +--Alter view template_view + +DROP VIEW IF EXISTS `cloud`.`template_view`; +CREATE VIEW `template_view` AS + SELECT + `vm_template`.`id` AS `id`, + `vm_template`.`uuid` AS `uuid`, + `vm_template`.`unique_name` AS `unique_name`, + `vm_template`.`name` AS `name`, + `vm_template`.`public` AS `public`, + `vm_template`.`featured` AS `featured`, + `vm_template`.`type` AS `type`, + `vm_template`.`hvm` AS `hvm`, + `vm_template`.`bits` AS `bits`, + `vm_template`.`url` AS `url`, + `vm_template`.`format` AS `format`, + `vm_template`.`created` AS `created`, + `vm_template`.`checksum` AS `checksum`, + `vm_template`.`display_text` AS `display_text`, + `vm_template`.`enable_password` AS `enable_password`, + `vm_template`.`dynamically_scalable` AS `dynamically_scalable`, + `vm_template`.`state` AS `template_state`, + `vm_template`.`guest_os_id` AS `guest_os_id`, + `guest_os`.`uuid` AS `guest_os_uuid`, + `guest_os`.`display_name` AS `guest_os_name`, + `vm_template`.`bootable` AS `bootable`, + `vm_template`.`prepopulate` AS `prepopulate`, + `vm_template`.`cross_zones` AS `cross_zones`, + `vm_template`.`hypervisor_type` AS `hypervisor_type`, + `vm_template`.`extractable` AS `extractable`, + `vm_template`.`template_tag` AS `template_tag`, + `vm_template`.`sort_key` AS `sort_key`, + `vm_template`.`removed` AS `removed`, + `vm_template`.`enable_sshkey` AS `enable_sshkey`, + `source_template`.`id` AS `source_template_id`, + `source_template`.`uuid` AS `source_template_uuid`, + `account`.`id` AS `account_id`, + `account`.`uuid` AS `account_uuid`, + `account`.`account_name` AS `account_name`, + `account`.`type` AS `account_type`, + `domain`.`id` AS `domain_id`, + `domain`.`uuid` AS `domain_uuid`, + `domain`.`name` AS `domain_name`, + `domain`.`path` AS `domain_path`, + `projects`.`id` AS `project_id`, + `projects`.`uuid` AS `project_uuid`, + `projects`.`name` AS `project_name`, + `data_center`.`id` AS `data_center_id`, + `data_center`.`uuid` AS `data_center_uuid`, + `data_center`.`name` AS `data_center_name`, + `launch_permission`.`account_id` AS `lp_account_id`, + `template_store_ref`.`store_id` AS `store_id`, + `image_store`.`scope` AS `store_scope`, + `template_store_ref`.`state` AS `state`, + `template_store_ref`.`download_state` AS `download_state`, + `template_store_ref`.`download_pct` AS `download_pct`, + `template_store_ref`.`error_str` AS `error_str`, + `template_store_ref`.`size` AS `size`, + `template_store_ref`.physical_size AS `physical_size`, + `template_store_ref`.`destroyed` AS `destroyed`, + `template_store_ref`.`created` AS `created_on_store`, + `vm_template_details`.`name` AS `detail_name`, + `vm_template_details`.`value` AS `detail_value`, + `resource_tags`.`id` AS `tag_id`, + `resource_tags`.`uuid` AS `tag_uuid`, + `resource_tags`.`key` AS `tag_key`, + `resource_tags`.`value` AS `tag_value`, + `resource_tags`.`domain_id` AS `tag_domain_id`, + `domain`.`uuid` AS `tag_domain_uuid`, + `domain`.`name` AS `tag_domain_name`, + `resource_tags`.`account_id` AS `tag_account_id`, + `account`.`account_name` AS `tag_account_name`, + `resource_tags`.`resource_id` AS `tag_resource_id`, + `resource_tags`.`resource_uuid` AS `tag_resource_uuid`, + `resource_tags`.`resource_type` AS `tag_resource_type`, + `resource_tags`.`customer` AS `tag_customer`, + CONCAT(`vm_template`.`id`, + '_', + IFNULL(`data_center`.`id`, 0)) AS `temp_zone_pair` + FROM + ((((((((((((`vm_template` + JOIN `guest_os` ON ((`guest_os`.`id` = `vm_template`.`guest_os_id`))) + JOIN `account` ON ((`account`.`id` = `vm_template`.`account_id`))) + JOIN `domain` ON ((`domain`.`id` = `account`.`domain_id`))) + LEFT JOIN `projects` ON ((`projects`.`project_account_id` = `account`.`id`))) + LEFT JOIN `vm_template_details` ON ((`vm_template_details`.`template_id` = `vm_template`.`id`))) + LEFT JOIN `vm_template` `source_template` ON ((`source_template`.`id` = `vm_template`.`source_template_id`))) + LEFT JOIN `template_store_ref` ON (((`template_store_ref`.`template_id` = `vm_template`.`id`) + AND (`template_store_ref`.`store_role` = 'Image') + AND (`template_store_ref`.`destroyed` = 0)))) + LEFT JOIN `image_store` ON ((ISNULL(`image_store`.`removed`) + AND (`template_store_ref`.`store_id` IS NOT NULL) + AND (`image_store`.`id` = `template_store_ref`.`store_id`)))) + LEFT JOIN `template_zone_ref` ON (((`template_zone_ref`.`template_id` = `vm_template`.`id`) + AND ISNULL(`template_store_ref`.`store_id`) + AND ISNULL(`template_zone_ref`.`removed`)))) + LEFT JOIN `data_center` ON (((`image_store`.`data_center_id` = `data_center`.`id`) + OR (`template_zone_ref`.`zone_id` = `data_center`.`id`)))) + LEFT JOIN `launch_permission` ON ((`launch_permission`.`template_id` = `vm_template`.`id`))) + LEFT JOIN `resource_tags` ON (((`resource_tags`.`resource_id` = `vm_template`.`id`) + AND ((`resource_tags`.`resource_type` = 'Template') + OR (`resource_tags`.`resource_type` = 'ISO'))))); + +UPDATE `cloud`.`configuration` SET value = '600', default_value = '600' WHERE category = 'Advanced' AND name = 'router.aggregation.command.each.timeout'; + +-- CA framework changes +DELETE from `cloud`.`configuration` where name='ssl.keystore'; + +-- Certificate Revocation List +CREATE TABLE IF NOT EXISTS `cloud`.`crl` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `serial` varchar(255) UNIQUE NOT NULL COMMENT 'certificate\'s serial number as hex string', + `cn` varchar(255) COMMENT 'certificate\'s common name', + `revoker_uuid` varchar(40) COMMENT 'revoker user account uuid', + `revoked` datetime COMMENT 'date of revocation', + PRIMARY KEY (`id`), + KEY (`serial`), + UNIQUE KEY (`serial`, `cn`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Host HA feature +CREATE TABLE IF NOT EXISTS `cloud`.`ha_config` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `resource_id` bigint(20) unsigned DEFAULT NULL COMMENT 'id of the resource', + `resource_type` varchar(255) NOT NULL COMMENT 'the type of the resource', + `enabled` int(1) unsigned DEFAULT '0' COMMENT 'is HA enabled for the resource', + `ha_state` varchar(255) DEFAULT 'Disabled' COMMENT 'HA state', + `provider` varchar(255) DEFAULT NULL COMMENT 'HA provider', + `update_count` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'state based incr-only counter for atomic ha_state updates', + `update_time` datetime COMMENT 'last ha_state update datetime', + `mgmt_server_id` bigint(20) unsigned DEFAULT NULL COMMENT 'management server id that is responsible for the HA for the resource', + PRIMARY KEY (`id`), + KEY `i_ha_config__enabled` (`enabled`), + KEY `i_ha_config__ha_state` (`ha_state`), + KEY `i_ha_config__mgmt_server_id` (`mgmt_server_id`), + UNIQUE KEY (`resource_id`, `resource_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +DELETE from `cloud`.`configuration` where name='outofbandmanagement.sync.interval'; + +-- Host HA changes: +DROP VIEW IF EXISTS `cloud`.`host_view`; +CREATE VIEW `cloud`.`host_view` AS + select + host.id, + host.uuid, + host.name, + host.status, + host.disconnected, + host.type, + host.private_ip_address, + host.version, + host.hypervisor_type, + host.hypervisor_version, + host.capabilities, + host.last_ping, + host.created, + host.removed, + host.resource_state, + host.mgmt_server_id, + host.cpu_sockets, + host.cpus, + host.speed, + host.ram, + cluster.id cluster_id, + cluster.uuid cluster_uuid, + cluster.name cluster_name, + cluster.cluster_type, + data_center.id data_center_id, + data_center.uuid data_center_uuid, + data_center.name data_center_name, + data_center.networktype data_center_type, + host_pod_ref.id pod_id, + host_pod_ref.uuid pod_uuid, + host_pod_ref.name pod_name, + host_tags.tag, + guest_os_category.id guest_os_category_id, + guest_os_category.uuid guest_os_category_uuid, + guest_os_category.name guest_os_category_name, + mem_caps.used_capacity memory_used_capacity, + mem_caps.reserved_capacity memory_reserved_capacity, + cpu_caps.used_capacity cpu_used_capacity, + cpu_caps.reserved_capacity cpu_reserved_capacity, + async_job.id job_id, + async_job.uuid job_uuid, + async_job.job_status job_status, + async_job.account_id job_account_id, + oobm.enabled AS `oobm_enabled`, + oobm.power_state AS `oobm_power_state`, + ha_config.enabled AS `ha_enabled`, + ha_config.ha_state AS `ha_state`, + ha_config.provider AS `ha_provider` + from + `cloud`.`host` + left join + `cloud`.`cluster` ON host.cluster_id = cluster.id + left join + `cloud`.`data_center` ON host.data_center_id = data_center.id + left join + `cloud`.`host_pod_ref` ON host.pod_id = host_pod_ref.id + left join + `cloud`.`host_details` ON host.id = host_details.host_id + and host_details.name = 'guest.os.category.id' + left join + `cloud`.`guest_os_category` ON guest_os_category.id = CONVERT( host_details.value , UNSIGNED) + left join + `cloud`.`host_tags` ON host_tags.host_id = host.id + left join + `cloud`.`op_host_capacity` mem_caps ON host.id = mem_caps.host_id + and mem_caps.capacity_type = 0 + left join + `cloud`.`op_host_capacity` cpu_caps ON host.id = cpu_caps.host_id + and cpu_caps.capacity_type = 1 + left join + `cloud`.`async_job` ON async_job.instance_id = host.id + and async_job.instance_type = 'Host' + and async_job.job_status = 0 + left join + `cloud`.`oobm` ON oobm.host_id = host.id + left join + `cloud`.`ha_config` ON ha_config.resource_id=host.id + and ha_config.resource_type='Host'; + +-- Out-of-band management driver for nested-cloudstack +ALTER TABLE `cloud`.`oobm` MODIFY COLUMN port VARCHAR(255); + + +-- CLOUDSTACK-9902: Console proxy SSL toggle +INSERT IGNORE INTO `cloud`.`configuration` (`category`, `instance`, `component`, `name`, `value`, `description`, `default_value`, `is_dynamic`) VALUES ('Console Proxy', 'DEFAULT', 'AgentManager', 'consoleproxy.sslEnabled', 'false', 'Enable SSL for console proxy', 'false', 0); + +-- CLOUDSTACK-9859: Retirement of midonet plugin (final removal) +delete from `cloud`.`configuration` where name in ('midonet.apiserver.address', 'midonet.providerrouter.id'); + +-- IOPS per GB disk offering +DROP PROCEDURE IF EXISTS `cloud`.`ADD_COLUMN_TO_TABLE_IDEMPOTENT`; + +DELIMITER // +CREATE PROCEDURE `cloud`.`ADD_COLUMN_TO_TABLE_IDEMPOTENT`( + IN tableName VARCHAR(255), + IN colName VARCHAR(255), + IN colType VARCHAR(255), + IN comment VARCHAR(255)) +BEGIN + SET @t1=CONCAT('SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA="cloud" AND TABLE_NAME="', tableName, '" AND COLUMN_NAME="', colName, '" into @outvar'); + PREPARE stmt1 FROM @t1; + EXECUTE stmt1; + DEALLOCATE PREPARE stmt1; + IF (@outvar < 1) THEN + SET @t2=CONCAT('ALTER TABLE `cloud`.`', tableName, '` ADD COLUMN `', colName, '` ', colType, ' DEFAULT NULL COMMENT "', comment , '"'); + PREPARE stmt2 FROM @t2; + EXECUTE stmt2; + DEALLOCATE PREPARE stmt2; + END IF; END // +DELIMITER ; + +CALL `cloud`.`ADD_COLUMN_TO_TABLE_IDEMPOTENT`('disk_offering', 'min_iops_per_gb', 'int unsigned', 'Min IOPS per GB for rate based offering'); +CALL `cloud`.`ADD_COLUMN_TO_TABLE_IDEMPOTENT`('disk_offering', 'max_iops_per_gb', 'int unsigned', 'Max IOPS per GB for rate based offering'); +CALL `cloud`.`ADD_COLUMN_TO_TABLE_IDEMPOTENT`('disk_offering', 'highest_min_iops', 'int unsigned', 'Highest Min IOPS value for this offering'); +CALL `cloud`.`ADD_COLUMN_TO_TABLE_IDEMPOTENT`('disk_offering', 'highest_max_iops', 'int unsigned', 'Highest Max IOPS value for this offering'); + +DROP PROCEDURE `cloud`.`ADD_COLUMN_TO_TABLE_IDEMPOTENT`; + +DROP VIEW IF EXISTS `cloud`.`disk_offering_view`; +CREATE VIEW `cloud`.`disk_offering_view` AS + select + disk_offering.id, + disk_offering.uuid, + disk_offering.name, + disk_offering.display_text, + disk_offering.provisioning_type, + disk_offering.disk_size, + disk_offering.min_iops, + disk_offering.max_iops, + disk_offering.created, + disk_offering.tags, + disk_offering.customized, + disk_offering.customized_iops, + disk_offering.removed, + disk_offering.use_local_storage, + disk_offering.system_use, + disk_offering.hv_ss_reserve, + disk_offering.bytes_read_rate, + disk_offering.bytes_write_rate, + disk_offering.iops_read_rate, + disk_offering.iops_write_rate, + disk_offering.min_iops_per_gb, + disk_offering.max_iops_per_gb, + disk_offering.highest_min_iops, + disk_offering.highest_max_iops, + disk_offering.cache_mode, + disk_offering.sort_key, + disk_offering.type, + disk_offering.display_offering, + domain.id domain_id, + domain.uuid domain_uuid, + domain.name domain_name, + domain.path domain_path + from + `cloud`.`disk_offering` + left join + `cloud`.`domain` ON disk_offering.domain_id = domain.id + where + disk_offering.state='ACTIVE'; diff --git a/ui/l10n/en.js b/ui/l10n/en.js index ef7c91e290a7..b1c571176754 100644 --- a/ui/l10n/en.js +++ b/ui/l10n/en.js @@ -673,6 +673,10 @@ var dictionary = { "label.disk.iops.total":"IOPS Total", "label.disk.iops.allocated":"IOPS Allocated", "label.disk.iops.write.rate":"Disk Write Rate (IOPS)", +"label.disk.iops.pergb.min":"Min IOPS Per GB", +"label.disk.iops.pergb.max":"Max IOPS Per GB", +"label.disk.iops.highest.min":"Highest Min IOPS", +"label.disk.iops.highest.max":"Highest Max IOPS", "label.disk.offering":"Disk Offering", "label.disk.offering.details":"Disk offering details", "label.disk.physicalsize":"Physical Size", diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index fdc9e492da75..80d8084c618f 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -1817,6 +1817,7 @@ args.$select.change(function() { var $form = $(this).closest('form'); var $isCustomizedIops = $form.find('.form-item[rel=isCustomizedIops]'); + var $isCustomized = $form.find('.form-item[rel=isCustomized]'); var $minIops = $form.find('.form-item[rel=minIops]'); var $maxIops = $form.find('.form-item[rel=maxIops]'); var $hypervisorSnapshotReserve = $form.find('.form-item[rel=hypervisorSnapshotReserve]'); @@ -1824,6 +1825,10 @@ var $diskBytesWriteRate = $form.find('.form-item[rel=diskBytesWriteRate]'); var $diskIopsReadRate = $form.find('.form-item[rel=diskIopsReadRate]'); var $diskIopsWriteRate = $form.find('.form-item[rel=diskIopsWriteRate]'); + var $highestMinIops = $form.find('.form-item[rel=highestMinIops]'); + var $highestMaxIops = $form.find('.form-item[rel=highestMaxIops]'); + var $minIopsPerGb = $form.find('.form-item[rel=minIopsPerGb]'); + var $maxIopsPerGb = $form.find('.form-item[rel=maxIopsPerGb]'); var qosId = $(this).val(); @@ -1835,6 +1840,11 @@ $isCustomizedIops.css('display', 'inline-block'); + $highestMinIops.css('display', 'inline-block'); + $highestMaxIops.css('display', 'inline-block'); + $minIopsPerGb.css('display', 'inline-block'); + $maxIopsPerGb.css('display', 'inline-block'); + if ($isCustomizedIops.find('input[type=checkbox]').is(':checked')) { $minIops.hide(); $maxIops.hide(); @@ -1848,6 +1858,11 @@ $isCustomizedIops.hide(); $minIops.hide(); $maxIops.hide(); + $highestMaxIops.hide(); + $highestMinIops.hide(); + $minIopsPerGb.hide(); + $maxIopsPerGb.hide(); + $hypervisorSnapshotReserve.hide(); $diskBytesReadRate.css('display', 'inline-block'); @@ -1862,6 +1877,10 @@ $isCustomizedIops.hide(); $minIops.hide(); $maxIops.hide(); + $highestMaxIops.hide(); + $highestMinIops.hide(); + $minIopsPerGb.hide(); + $maxIopsPerGb.hide(); $hypervisorSnapshotReserve.hide(); } }); @@ -1892,6 +1911,38 @@ number: true } }, + highestMinIops: { + label: 'label.disk.iops.highest.min', + docID: 'helpDiskOfferingHighestMinIops', + validation: { + required: false, + number: true + } + }, + highestMaxIops: { + label: 'label.disk.iops.highest.max', + docID: 'helpDiskOfferingHighestMaxIops', + validation: { + required: false, + number: true + } + }, + minIopsPerGb: { + label: 'label.disk.iops.pergb.min', + docID: 'helpDiskOfferingMinIopsPerGb', + validation: { + required: false, + number: true + } + }, + maxIopsPerGb: { + label: 'label.disk.iops.pergb.max', + docID: 'helpDiskOfferingMaxIopsPerGb', + validation: { + required: false, + number: true + } + }, hypervisorSnapshotReserve: { label: 'label.hypervisor.snapshot.reserve', docID: 'helpDiskOfferingHypervisorSnapshotReserve', @@ -2071,6 +2122,30 @@ } } + if (args.data.highestMinIops != null && args.data.highestMinIops > 0) { + $.extend(data, { + highestminiops: args.data.highestMinIops + }); + } + + if (args.data.highestMaxIops != null && args.data.highestMaxIops > 0) { + $.extend(data, { + highestmaxiops: args.data.highestMaxIops + }); + } + + if (args.data.minIopsPerGb != null && args.data.minIopsPerGb > 0) { + $.extend(data, { + miniopspergb: args.data.minIopsPerGb + }); + } + + if (args.data.maxIopsPerGb != null && args.data.maxIopsPerGb > 0) { + $.extend(data, { + maxiopspergb: args.data.maxIopsPerGb + }); + } + if (args.data.hypervisorSnapshotReserve != null && args.data.hypervisorSnapshotReserve.length > 0) { $.extend(data, { hypervisorsnapshotreserve: args.data.hypervisorSnapshotReserve @@ -2276,6 +2351,45 @@ diskIopsWriteRate: { label: 'label.disk.iops.write.rate' }, + miniopspergb: { + label: 'label.disk.iops.pergb.min', + converter: function(args) { + if (args > 0) + return args; + else + return "N/A"; + } + + }, + maxiopspergb: { + label: 'label.disk.iops.pergb.max', + converter: function(args) { + if (args > 0) + return args; + else + return "N/A"; + } + + }, + highestminiops: { + label: 'label.disk.iops.highest.min', + converter: function(args) { + if (args > 0) + return args; + else + return "N/A"; + } + + }, + highestmaxiops: { + label: 'label.disk.iops.highest.max', + converter: function(args) { + if (args > 0) + return args; + else + return "N/A"; + } + }, cacheMode: { label: 'label.cache.mode' }, diff --git a/ui/scripts/docs.js b/ui/scripts/docs.js index 583db58bb451..7f69bbd627d2 100755 --- a/ui/scripts/docs.js +++ b/ui/scripts/docs.js @@ -367,6 +367,22 @@ cloudStack.docs = { desc: 'Appears only if Custom IOPS is not selected. Define the maximum volume IOPS.', externalLink: '' }, + helpDiskOfferingHighestMinIops: { + desc: 'The highest minIOPS setting that can be set by the user for this offering. Applicable for custom IOPS and rate based IOPS', + externalLink: '' + }, + helpDiskOfferingHighestMaxIops: { + desc: 'The highest maxIOPS setting that can be set by the user for this offering. Applicable for custom IOPS and rate based IOPS', + externalLink: '' + }, + helpDiskOfferingMinIopsPerGb: { + desc: 'Rate at which min IOPS changes with size of the volume. minIops = minIopsPerGb * volumeSize. Applicable for custom size disk offerings', + externalLink: '' + }, + helpDiskOfferingMaxIopsPerGb: { + desc: 'Rate at which max IOPS changes with size of the volume. maxIops = maxIopsPerGb * volumeSize. Applicable for custom size disk offerings', + externalLink: '' + }, helpDiskOfferingHypervisorSnapshotReserve: { desc: 'Hypervisor snapshot reserve space as a percent of a volume (for managed storage using XenServer or VMware) (Ex. The value 25 means 25%.)).' }, diff --git a/ui/scripts/storage.js b/ui/scripts/storage.js index e9b6138d0b64..68c1623081ff 100644 --- a/ui/scripts/storage.js +++ b/ui/scripts/storage.js @@ -1696,7 +1696,7 @@ action: function(args) { var array1 = []; var newSize; - if (selectedDiskOfferingObj == null || selectedDiskOfferingObj.iscustomized == true) { + if (selectedDiskOfferingObj == null) { newSize = args.data.newsize; if (newSize != null && newSize.length > 0) { array1.push("&size=" + todb(newSize));