diff --git a/api/src/com/cloud/agent/api/to/DiskTO.java b/api/src/com/cloud/agent/api/to/DiskTO.java index 7b32f006606..556ccd4db46 100644 --- a/api/src/com/cloud/agent/api/to/DiskTO.java +++ b/api/src/com/cloud/agent/api/to/DiskTO.java @@ -23,14 +23,16 @@ import com.cloud.storage.Volume; public class DiskTO { private DataTO data; private Long diskSeq; + private String vdiUuid; private Volume.Type type; public DiskTO() { } - public DiskTO(DataTO data, Long diskSeq, Volume.Type type) { + public DiskTO(DataTO data, Long diskSeq, String vdiUuid, Volume.Type type) { this.data = data; this.diskSeq = diskSeq; + this.vdiUuid = vdiUuid; this.type = type; } @@ -50,6 +52,14 @@ public class DiskTO { this.diskSeq = diskSeq; } + public String getVdiUuid() { + return vdiUuid; + } + + public void setVdiUuid(String vdiUuid) { + this.vdiUuid = vdiUuid; + } + public Volume.Type getType() { return type; } diff --git a/api/src/com/cloud/offering/DiskOffering.java b/api/src/com/cloud/offering/DiskOffering.java index ae4528cc81a..9c196e08b69 100644 --- a/api/src/com/cloud/offering/DiskOffering.java +++ b/api/src/com/cloud/offering/DiskOffering.java @@ -47,12 +47,24 @@ public interface DiskOffering extends InfrastructureEntity, Identity, InternalId Date getCreated(); - long getDiskSize(); - boolean isCustomized(); void setDiskSize(long diskSize); + long getDiskSize(); + + void setCustomizedIops(Boolean customizedIops); + + Boolean isCustomizedIops(); + + void setMinIops(Long minIops); + + Long getMinIops(); + + void setMaxIops(Long maxIops); + + Long getMaxIops(); + void setBytesReadRate(Long bytesReadRate); Long getBytesReadRate(); diff --git a/api/src/com/cloud/storage/StoragePool.java b/api/src/com/cloud/storage/StoragePool.java index 8f8b8644ddc..6e9af12d24e 100644 --- a/api/src/com/cloud/storage/StoragePool.java +++ b/api/src/com/cloud/storage/StoragePool.java @@ -60,6 +60,8 @@ public interface StoragePool extends Identity, InternalIdentity { */ long getUsedBytes(); + Long getCapacityIops(); + Long getClusterId(); /** diff --git a/api/src/com/cloud/storage/Volume.java b/api/src/com/cloud/storage/Volume.java index f5ed4e267b6..342dfd3ab2e 100755 --- a/api/src/com/cloud/storage/Volume.java +++ b/api/src/com/cloud/storage/Volume.java @@ -122,6 +122,12 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba */ Long getSize(); + Long getMinIops(); + + Long getMaxIops(); + + String get_iScsiName(); + /** * @return the vm instance id */ diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java index 809e0236968..dd876f75ed3 100755 --- a/api/src/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/org/apache/cloudstack/api/ApiConstants.java @@ -51,6 +51,9 @@ public class ApiConstants { public static final String CPU_OVERCOMMIT_RATIO="cpuovercommitratio"; public static final String CREATED = "created"; public static final String CUSTOMIZED = "customized"; + public static final String CUSTOMIZED_IOPS = "customizediops"; + public static final String MIN_IOPS = "miniops"; + public static final String MAX_IOPS = "maxiops"; public static final String DESCRIPTION = "description"; public static final String DESTINATION_ZONE_ID = "destzoneid"; public static final String DETAILS = "details"; @@ -326,6 +329,9 @@ public class ApiConstants { public static final String SERVICE_CAPABILITY_LIST = "servicecapabilitylist"; public static final String CAN_CHOOSE_SERVICE_CAPABILITY = "canchooseservicecapability"; public static final String PROVIDER = "provider"; + public static final String MANAGED = "managed"; + public static final String CAPACITY_BYTES = "capacitybytes"; + public static final String CAPACITY_IOPS = "capacityiops"; public static final String NETWORK_SPEED = "networkspeed"; public static final String BROADCAST_DOMAIN_RANGE = "broadcastdomainrange"; public static final String ISOLATION_METHODS = "isolationmethods"; diff --git a/api/src/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java b/api/src/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java index a2c5f77f583..47415911e9b 100644 --- a/api/src/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java +++ b/api/src/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java @@ -52,7 +52,7 @@ public class CreateDiskOfferingCmd extends BaseCmd { @Parameter(name=ApiConstants.TAGS, type=CommandType.STRING, description="tags for the disk offering", length=4096) private String tags; - @Parameter(name=ApiConstants.CUSTOMIZED, type=CommandType.BOOLEAN, description="whether disk offering is custom or not") + @Parameter(name=ApiConstants.CUSTOMIZED, type=CommandType.BOOLEAN, description="whether disk offering size is custom or not") private Boolean customized; @Parameter(name=ApiConstants.DOMAIN_ID, type=CommandType.UUID, entityType=DomainResponse.class, @@ -62,6 +62,9 @@ public class CreateDiskOfferingCmd extends BaseCmd { @Parameter(name=ApiConstants.STORAGE_TYPE, type=CommandType.STRING, description="the storage type of the disk offering. Values are local and shared.") private String storageType = ServiceOffering.StorageType.shared.toString(); + @Parameter(name=ApiConstants.DISPLAY_OFFERING, type=CommandType.BOOLEAN, description="an optional field, whether to display the offering to the end user or not.") + private Boolean displayOffering; + @Parameter(name=ApiConstants.BYTES_READ_RATE, type=CommandType.LONG, required=false, description="bytes read rate of the disk offering") private Long bytesReadRate; @@ -74,8 +77,14 @@ public class CreateDiskOfferingCmd extends BaseCmd { @Parameter(name=ApiConstants.IOPS_WRITE_RATE, type=CommandType.LONG, required=false, description="io requests write rate of the disk offering") private Long iopsWriteRate; - @Parameter(name=ApiConstants.DISPLAY_OFFERING, type=CommandType.BOOLEAN, description="an optional field, whether to display the offering to the end user or not.") - private Boolean displayOffering; + @Parameter(name=ApiConstants.CUSTOMIZED_IOPS, type=CommandType.BOOLEAN, required=false, description="whether disk offering iops is custom or not") + private Boolean customizedIops; + + @Parameter(name=ApiConstants.MIN_IOPS, type=CommandType.LONG, required=false, description="min iops of the disk offering") + private Long minIops; + + @Parameter(name=ApiConstants.MAX_IOPS, type=CommandType.LONG, required=false, description="max iops of the disk offering") + private Long maxIops; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// @@ -101,6 +110,18 @@ public class CreateDiskOfferingCmd extends BaseCmd { return customized; } + public Boolean isCustomizedIops() { + return customizedIops; + } + + public Long getMinIops() { + return minIops; + } + + public Long getMaxIops() { + return maxIops; + } + public Long getDomainId(){ return domainId; } diff --git a/api/src/org/apache/cloudstack/api/command/admin/storage/CreateStoragePoolCmd.java b/api/src/org/apache/cloudstack/api/command/admin/storage/CreateStoragePoolCmd.java index 74eb2b9bf8f..f5750b99284 100644 --- a/api/src/org/apache/cloudstack/api/command/admin/storage/CreateStoragePoolCmd.java +++ b/api/src/org/apache/cloudstack/api/command/admin/storage/CreateStoragePoolCmd.java @@ -80,6 +80,18 @@ public class CreateStoragePoolCmd extends BaseCmd { required=false, description="the scope of the storage: cluster or zone") private String scope; + @Parameter(name=ApiConstants.MANAGED, type=CommandType.BOOLEAN, + required=false, description="whether the storage should be managed by CloudStack") + private Boolean managed; + + @Parameter(name=ApiConstants.CAPACITY_IOPS, type=CommandType.LONG, + required=false, description="IOPS CloudStack can provision from this storage pool") + private Long capacityIops; + + @Parameter(name=ApiConstants.CAPACITY_BYTES, type=CommandType.LONG, + required=false, description="bytes CloudStack can provision from this storage pool") + private Long capacityBytes; + @Parameter(name=ApiConstants.HYPERVISOR, type=CommandType.STRING, required=false, description="hypervisor type of the hosts in zone that will be attached to this storage pool. KVM, VMware supported as of now.") private String hypervisor; @@ -124,6 +136,18 @@ public class CreateStoragePoolCmd extends BaseCmd { return this.scope; } + public Boolean isManaged() { + return managed; + } + + public Long getCapacityIops() { + return capacityIops; + } + + public Long getCapacityBytes() { + return capacityBytes; + } + public String getHypervisor() { return hypervisor; } diff --git a/api/src/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java b/api/src/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java index 6f0bf3a6ca7..f293a03da91 100644 --- a/api/src/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java @@ -68,6 +68,12 @@ public class CreateVolumeCmd extends BaseAsyncCreateCmd { @Parameter(name=ApiConstants.SIZE, type=CommandType.LONG, description="Arbitrary volume size") private Long size; + @Parameter(name=ApiConstants.MIN_IOPS, type=CommandType.LONG, description="min iops") + private Long minIops; + + @Parameter(name=ApiConstants.MAX_IOPS, type=CommandType.LONG, description="max iops") + private Long maxIops; + @Parameter(name=ApiConstants.SNAPSHOT_ID, type=CommandType.UUID, entityType=SnapshotResponse.class, description="the snapshot ID for the disk volume. Either diskOfferingId or snapshotId must be passed in.") private Long snapshotId; @@ -104,6 +110,14 @@ public class CreateVolumeCmd extends BaseAsyncCreateCmd { return size; } + public Long getMinIops() { + return minIops; + } + + public Long getMaxIops() { + return maxIops; + } + public Long getSnapshotId() { return snapshotId; } diff --git a/api/src/org/apache/cloudstack/api/response/DiskOfferingResponse.java b/api/src/org/apache/cloudstack/api/response/DiskOfferingResponse.java index 35cf21a3b17..4291d854142 100644 --- a/api/src/org/apache/cloudstack/api/response/DiskOfferingResponse.java +++ b/api/src/org/apache/cloudstack/api/response/DiskOfferingResponse.java @@ -52,6 +52,15 @@ public class DiskOfferingResponse extends BaseResponse { @SerializedName("iscustomized") @Param(description="true if disk offering uses custom size, false otherwise") private Boolean customized; + @SerializedName("iscustomizediops") @Param(description="true if disk offering uses custom iops, false otherwise") + private Boolean customizedIops; + + @SerializedName(ApiConstants.MIN_IOPS) @Param(description="the min iops of the disk offering") + private Long minIops; + + @SerializedName(ApiConstants.MAX_IOPS) @Param(description="the max iops of the disk offering") + private Long maxIops; + @SerializedName(ApiConstants.TAGS) @Param(description="the tags for the disk offering") private String tags; @@ -154,6 +163,30 @@ public class DiskOfferingResponse extends BaseResponse { this.customized = customized; } + public Boolean isCustomizedIops() { + return customizedIops; + } + + public void setCustomizedIops(Boolean customizedIops) { + this.customizedIops = customizedIops; + } + + public Long getMinIops() { + return minIops; + } + + public void setMinIops(Long minIops) { + this.minIops = minIops; + } + + public Long getMaxIops() { + return maxIops; + } + + public void setMaxIops(Long maxIops) { + this.maxIops = maxIops; + } + public String getStorageType() { return storageType; } diff --git a/api/src/org/apache/cloudstack/api/response/StoragePoolResponse.java b/api/src/org/apache/cloudstack/api/response/StoragePoolResponse.java index 00500007c18..7321d98b476 100644 --- a/api/src/org/apache/cloudstack/api/response/StoragePoolResponse.java +++ b/api/src/org/apache/cloudstack/api/response/StoragePoolResponse.java @@ -74,6 +74,9 @@ public class StoragePoolResponse extends BaseResponse { @SerializedName("disksizeused") @Param(description="the host's currently used disk size") private Long diskSizeUsed; + @SerializedName("capacityiops") @Param(description="IOPS CloudStack can provision from this storage pool") + private Long capacityIops; + @SerializedName("tags") @Param(description="the tags for the storage pool") private String tags; @@ -237,6 +240,14 @@ public class StoragePoolResponse extends BaseResponse { this.diskSizeUsed = diskSizeUsed; } + public Long getCapacityIops() { + return capacityIops; + } + + public void setCapacityIops(Long capacityIops) { + this.capacityIops = capacityIops; + } + public String getTags() { return tags; } diff --git a/api/src/org/apache/cloudstack/api/response/VolumeResponse.java b/api/src/org/apache/cloudstack/api/response/VolumeResponse.java index b643de12585..338fcaae5a4 100644 --- a/api/src/org/apache/cloudstack/api/response/VolumeResponse.java +++ b/api/src/org/apache/cloudstack/api/response/VolumeResponse.java @@ -76,6 +76,14 @@ public class VolumeResponse extends BaseResponse implements ControlledViewEntity @Param(description = "size of the disk volume") private Long size; + @SerializedName(ApiConstants.MIN_IOPS) + @Param(description = "min iops of the disk volume") + private Long minIops; + + @SerializedName(ApiConstants.MAX_IOPS) + @Param(description = "max iops of the disk volume") + private Long maxIops; + @SerializedName(ApiConstants.CREATED) @Param(description = "the date the disk volume was created") private Date created; @@ -241,6 +249,14 @@ public class VolumeResponse extends BaseResponse implements ControlledViewEntity this.size = size; } + public void setMinIops(Long minIops) { + this.minIops = minIops; + } + + public void setMaxIops(Long maxIops) { + this.maxIops = maxIops; + } + public void setCreated(Date created) { this.created = created; } diff --git a/client/WEB-INF/classes/resources/messages.properties b/client/WEB-INF/classes/resources/messages.properties index ad8d29d8eac..b1a09b103f9 100644 --- a/client/WEB-INF/classes/resources/messages.properties +++ b/client/WEB-INF/classes/resources/messages.properties @@ -14,6 +14,10 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +label.custom.disk.iops=Custom IOPS +label.disk.iops.min=Min IOPS +label.disk.iops.max=Max IOPS +label.disk.iops.total=IOPS Total label.view.secondary.ips=View secondary IPs message.acquire.ip.nic=Please confirm that you would like to acquire a new secondary IP for this NIC.
NOTE: You need to manually configure the newly-acquired secondary IP inside the virtual machine. message.select.affinity.groups=Please select any affinity groups you want this VM to belong to: @@ -395,7 +399,7 @@ label.code=Code label.community=Community label.compute.and.storage=Compute and Storage label.compute.offering=Compute offering -label.compute.offerings=Compute offerings +label.compute.offerings=Compute Offerings label.compute=Compute label.configuration=Configuration label.configure.network.ACLs=Configure Network ACLs @@ -1046,6 +1050,7 @@ label.stopped.vms=Stopped VMs label.storage.tags=Storage Tags label.storage.traffic=Storage Traffic label.storage.type=Storage Type +label.qos.type=QoS Type label.storage=Storage label.subdomain.access=Subdomain Access label.submit=Submit diff --git a/client/pom.xml b/client/pom.xml index b8182c26bb4..d1eeb3b0e52 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -20,6 +20,11 @@ 4.2.0-SNAPSHOT + + org.apache.cloudstack + cloud-plugin-storage-volume-solidfire + ${project.version} + org.apache.cloudstack cloud-server diff --git a/client/tomcatconf/applicationContext.xml.in b/client/tomcatconf/applicationContext.xml.in index 82ce9e944fb..7052fd7f30a 100644 --- a/client/tomcatconf/applicationContext.xml.in +++ b/client/tomcatconf/applicationContext.xml.in @@ -806,6 +806,7 @@ + diff --git a/core/src/com/cloud/agent/api/AttachVolumeAnswer.java b/core/src/com/cloud/agent/api/AttachVolumeAnswer.java index b377b7c1386..6b965b05043 100644 --- a/core/src/com/cloud/agent/api/AttachVolumeAnswer.java +++ b/core/src/com/cloud/agent/api/AttachVolumeAnswer.java @@ -19,35 +19,33 @@ package com.cloud.agent.api; public class AttachVolumeAnswer extends Answer { private Long deviceId; + private String vdiUuid; private String chainInfo; - protected AttachVolumeAnswer() { - - } - public AttachVolumeAnswer(AttachVolumeCommand cmd, String result) { super(cmd, false, result); this.deviceId = null; } - public AttachVolumeAnswer(AttachVolumeCommand cmd, Long deviceId) { + public AttachVolumeAnswer(AttachVolumeCommand cmd, Long deviceId, String vdiUuid) { super(cmd); this.deviceId = deviceId; + this.vdiUuid = vdiUuid; } - public AttachVolumeAnswer(AttachVolumeCommand cmd) { super(cmd); this.deviceId = null; } - /** - * @return the deviceId - */ public Long getDeviceId() { return deviceId; } + public String getVdiUuid() { + return vdiUuid; + } + public void setChainInfo(String chainInfo) { this.chainInfo = chainInfo; } diff --git a/core/src/com/cloud/agent/api/AttachVolumeCommand.java b/core/src/com/cloud/agent/api/AttachVolumeCommand.java index 2658262a6cd..2eb503ab099 100644 --- a/core/src/com/cloud/agent/api/AttachVolumeCommand.java +++ b/core/src/com/cloud/agent/api/AttachVolumeCommand.java @@ -19,29 +19,37 @@ package com.cloud.agent.api; import com.cloud.storage.Storage.StoragePoolType; public class AttachVolumeCommand extends Command { - - boolean attach; - String vmName; - StoragePoolType pooltype; - String poolUuid; - String volumeFolder; - String volumePath; - String volumeName; - Long deviceId; - String chainInfo; - Long bytesReadRate; - Long bytesWriteRate; - Long iopsReadRate; - Long iopsWriteRate; + private boolean attach; + private boolean _managed; + private String vmName; + private StoragePoolType pooltype; + private String volumePath; + private String volumeName; + private Long deviceId; + private String chainInfo; + private String poolUuid; + private String _storageHost; + private int _storagePort; + private String _iScsiName; + private String _chapInitiatorUsername; + private String _chapInitiatorPassword; + private String _chapTargetUsername; + private String _chapTargetPassword; + private Long bytesReadRate; + private Long bytesWriteRate; + private Long iopsReadRate; + private Long iopsWriteRate; protected AttachVolumeCommand() { } - public AttachVolumeCommand(boolean attach, String vmName, StoragePoolType pooltype, String volumeFolder, String volumePath, String volumeName, Long deviceId, String chainInfo) { + public AttachVolumeCommand(boolean attach, boolean managed, String vmName, + StoragePoolType pooltype, String volumePath, String volumeName, + Long deviceId, String chainInfo) { this.attach = attach; + this._managed = managed; this.vmName = vmName; this.pooltype = pooltype; - this.volumeFolder = volumeFolder; this.volumePath = volumePath; this.volumeName = volumeName; this.deviceId = deviceId; @@ -54,7 +62,7 @@ public class AttachVolumeCommand extends Command { } public boolean getAttach() { - return attach; + return attach; } public String getVmName() { @@ -69,16 +77,12 @@ public class AttachVolumeCommand extends Command { this.pooltype = pooltype; } - public String getVolumeFolder() { - return volumeFolder; - } - public String getVolumePath() { return volumePath; } public String getVolumeName() { - return volumeName; + return volumeName; } public Long getDeviceId() { @@ -90,17 +94,77 @@ public class AttachVolumeCommand extends Command { } public String getPoolUuid() { - return poolUuid; + return poolUuid; } public void setPoolUuid(String poolUuid) { - this.poolUuid = poolUuid; + this.poolUuid = poolUuid; } public String getChainInfo() { - return chainInfo; + return chainInfo; } + public void setStorageHost(String storageHost) { + _storageHost = storageHost; + } + + public String getStorageHost() { + return _storageHost; + } + + public void setStoragePort(int storagePort) { + _storagePort = storagePort; + } + + public int getStoragePort() { + return _storagePort; + } + + public boolean isManaged() { + return _managed; + } + + public void set_iScsiName(String iScsiName) { + this._iScsiName = iScsiName; + } + + public String get_iScsiName() { + return _iScsiName; + } + + public void setChapInitiatorUsername(String chapInitiatorUsername) { + _chapInitiatorUsername = chapInitiatorUsername; + } + + public String getChapInitiatorUsername() { + return _chapInitiatorUsername; + } + + public void setChapInitiatorPassword(String chapInitiatorPassword) { + _chapInitiatorPassword = chapInitiatorPassword; + } + + public String getChapInitiatorPassword() { + return _chapInitiatorPassword; + } + + public void setChapTargetUsername(String chapTargetUsername) { + _chapTargetUsername = chapTargetUsername; + } + + public String getChapTargetUsername() { + return _chapTargetUsername; + } + + public void setChapTargetPassword(String chapTargetPassword) { + _chapTargetPassword = chapTargetPassword; + } + + public String getChapTargetPassword() { + return _chapTargetPassword; + } + public void setBytesReadRate(Long bytesReadRate) { this.bytesReadRate = bytesReadRate; } diff --git a/core/test/org/apache/cloudstack/api/agent/test/AttachVolumeAnswerTest.java b/core/test/org/apache/cloudstack/api/agent/test/AttachVolumeAnswerTest.java index 251a6cb917e..9e43d9f1793 100644 --- a/core/test/org/apache/cloudstack/api/agent/test/AttachVolumeAnswerTest.java +++ b/core/test/org/apache/cloudstack/api/agent/test/AttachVolumeAnswerTest.java @@ -26,14 +26,14 @@ import com.cloud.agent.api.AttachVolumeCommand; import com.cloud.storage.Storage.StoragePoolType; public class AttachVolumeAnswerTest { - AttachVolumeCommand avc = new AttachVolumeCommand(true, "vmname", - StoragePoolType.Filesystem, "vFolder", "vPath", "vName", + AttachVolumeCommand avc = new AttachVolumeCommand(true, false, "vmname", + StoragePoolType.Filesystem, "vPath", "vName", 123456789L, "chainInfo"); AttachVolumeAnswer ava1 = new AttachVolumeAnswer(avc); String results = ""; AttachVolumeAnswer ava2 = new AttachVolumeAnswer(avc, results); Long deviceId = 10L; - AttachVolumeAnswer ava3 = new AttachVolumeAnswer(avc, deviceId); + AttachVolumeAnswer ava3 = new AttachVolumeAnswer(avc, deviceId, ""); @Test public void testGetDeviceId() { diff --git a/core/test/org/apache/cloudstack/api/agent/test/AttachVolumeCommandTest.java b/core/test/org/apache/cloudstack/api/agent/test/AttachVolumeCommandTest.java index 1ec416a4d02..6f413c0268d 100644 --- a/core/test/org/apache/cloudstack/api/agent/test/AttachVolumeCommandTest.java +++ b/core/test/org/apache/cloudstack/api/agent/test/AttachVolumeCommandTest.java @@ -25,8 +25,8 @@ import com.cloud.agent.api.AttachVolumeCommand; import com.cloud.storage.Storage.StoragePoolType; public class AttachVolumeCommandTest { - AttachVolumeCommand avc = new AttachVolumeCommand(true, "vmname", - StoragePoolType.Filesystem, "vFolder", "vPath", "vName", + AttachVolumeCommand avc = new AttachVolumeCommand(true, false, "vmname", + StoragePoolType.Filesystem, "vPath", "vName", 123456789L, "chainInfo"); @Test @@ -65,12 +65,6 @@ public class AttachVolumeCommandTest { assertTrue(pt.equals(StoragePoolType.Iscsi)); } - @Test - public void testGetVolumeFolder() { - String vFolder = avc.getVolumeFolder(); - assertTrue(vFolder.equals("vFolder")); - } - @Test public void testGetVolumePath() { String vPath = avc.getVolumePath(); diff --git a/core/test/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java b/core/test/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java index 989059322a9..0fee8c64d87 100644 --- a/core/test/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java +++ b/core/test/org/apache/cloudstack/api/agent/test/BackupSnapshotCommandTest.java @@ -87,6 +87,11 @@ public class BackupSnapshotCommandTest { return 0L; }; + @Override + public Long getCapacityIops() { + return 0L; + } + @Override public Long getClusterId() { return 0L; diff --git a/core/test/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java b/core/test/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java index 4db65570f1b..b834a26b6cc 100644 --- a/core/test/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java +++ b/core/test/org/apache/cloudstack/api/agent/test/CheckNetworkAnswerTest.java @@ -125,6 +125,11 @@ public class CheckNetworkAnswerTest { return 0L; }; + @Override + public Long getCapacityIops() { + return 0L; + }; + @Override public Long getClusterId() { return 0L; diff --git a/core/test/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java b/core/test/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java index 3076d453434..35bdfc8c883 100644 --- a/core/test/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java +++ b/core/test/org/apache/cloudstack/api/agent/test/SnapshotCommandTest.java @@ -78,6 +78,10 @@ public class SnapshotCommandTest { return 0L; }; + public Long getCapacityIops() { + return 0L; + }; + public Long getClusterId() { return 0L; }; diff --git a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/ChapInfo.java b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/ChapInfo.java new file mode 100644 index 00000000000..97c9ecbaab0 --- /dev/null +++ b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/ChapInfo.java @@ -0,0 +1,26 @@ +/* + * 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. + */ +package org.apache.cloudstack.engine.subsystem.api.storage; + +public interface ChapInfo { + String getInitiatorUsername(); + String getInitiatorSecret(); + String getTargetUsername(); + String getTargetSecret(); +} diff --git a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreDriver.java b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreDriver.java index 1cb6e158489..127b8589987 100644 --- a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreDriver.java +++ b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreDriver.java @@ -24,17 +24,11 @@ import org.apache.cloudstack.framework.async.AsyncCompletionCallback; import org.apache.cloudstack.storage.command.CommandResult; public interface DataStoreDriver { - void createAsync(DataObject data, AsyncCompletionCallback callback); - - void deleteAsync(DataObject data, AsyncCompletionCallback callback); - - void copyAsync(DataObject srcdata, DataObject destData, AsyncCompletionCallback callback); - - boolean canCopy(DataObject srcData, DataObject destData); - - void resize(DataObject data, AsyncCompletionCallback callback); - DataTO getTO(DataObject data); - DataStoreTO getStoreTO(DataStore store); + void createAsync(DataStore store, DataObject data, AsyncCompletionCallback callback); + void deleteAsync(DataStore store, DataObject data, AsyncCompletionCallback callback); + void copyAsync(DataObject srcdata, DataObject destData, AsyncCompletionCallback callback); + boolean canCopy(DataObject srcData, DataObject destData); + void resize(DataObject data, AsyncCompletionCallback callback); } diff --git a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java index 2528a536d64..b124d835dda 100644 --- a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java +++ b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java @@ -22,7 +22,7 @@ import org.apache.cloudstack.framework.async.AsyncCompletionCallback; import org.apache.cloudstack.storage.command.CommandResult; public interface PrimaryDataStoreDriver extends DataStoreDriver { - void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback callback); - - void revertSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback callback); + public ChapInfo getChapInfo(VolumeInfo volumeInfo); + public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback callback); + public void revertSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback callback); } diff --git a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreParameters.java b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreParameters.java index 3b5362a2d06..c05419f6641 100644 --- a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreParameters.java +++ b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreParameters.java @@ -20,6 +20,7 @@ package org.apache.cloudstack.engine.subsystem.api.storage; import java.util.Map; +import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.storage.Storage.StoragePoolType; public class PrimaryDataStoreParameters { @@ -30,12 +31,17 @@ public class PrimaryDataStoreParameters { private Map details; private String tags; private StoragePoolType type; + private HypervisorType hypervisorType; private String host; private String path; private int port; private String uuid; private String name; private String userInfo; + private long capacityBytes; + private long usedBytes; + private boolean managed; + private Long capacityIops; /** * @return the userInfo @@ -187,6 +193,30 @@ public class PrimaryDataStoreParameters { this.providerName = providerName; } + public void setManaged(boolean managed) { + this.managed = managed; + } + + public boolean isManaged() { + return managed; + } + + public void setCapacityIops(Long capacityIops) { + this.capacityIops = capacityIops; + } + + public Long getCapacityIops() { + return capacityIops; + } + + public void setHypervisorType(HypervisorType hypervisorType) { + this.hypervisorType = hypervisorType; + } + + public HypervisorType getHypervisorType() { + return hypervisorType; + } + /** * @return the clusterId */ @@ -231,4 +261,24 @@ public class PrimaryDataStoreParameters { public void setZoneId(Long zoneId) { this.zoneId = zoneId; } + + public long getCapacityBytes() + { + return capacityBytes; + } + + public void setCapacityBytes(long capacityBytes) + { + this.capacityBytes = capacityBytes; + } + + public long getUsedBytes() + { + return usedBytes; + } + + public void setUsedBytes(long usedBytes) + { + this.usedBytes = usedBytes; + } } diff --git a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/VolumeService.java b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/VolumeService.java index f96ea4032d1..7515088f331 100644 --- a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/VolumeService.java +++ b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/VolumeService.java @@ -42,6 +42,8 @@ public interface VolumeService { } } + ChapInfo getChapInfo(VolumeInfo volumeInfo, DataStore dataStore); + /** * Creates the volume based on the given criteria * diff --git a/engine/api/src/org/apache/cloudstack/storage/command/AttachCommand.java b/engine/api/src/org/apache/cloudstack/storage/command/AttachCommand.java index 6b4e9f7ed00..44bce910d02 100644 --- a/engine/api/src/org/apache/cloudstack/storage/command/AttachCommand.java +++ b/engine/api/src/org/apache/cloudstack/storage/command/AttachCommand.java @@ -24,6 +24,14 @@ import com.cloud.agent.api.to.DiskTO; public final class AttachCommand extends Command implements StorageSubSystemCommand { private DiskTO disk; private String vmName; + private String _storageHost; + private int _storagePort; + private boolean _managed; + private String _iScsiName; + private String _chapInitiatorUsername; + private String _chapInitiatorPassword; + private String _chapTargetUsername; + private String _chapTargetPassword; public AttachCommand(DiskTO disk, String vmName) { super(); @@ -52,4 +60,67 @@ public final class AttachCommand extends Command implements StorageSubSystemComm this.vmName = vmName; } + public void setStorageHost(String storageHost) { + _storageHost = storageHost; + } + + public String getStorageHost() { + return _storageHost; + } + + public void setStoragePort(int storagePort) { + _storagePort = storagePort; + } + + public int getStoragePort() { + return _storagePort; + } + + public void setManaged(boolean managed) { + _managed = managed; + } + + public boolean isManaged() { + return _managed; + } + + public void set_iScsiName(String iScsiName) { + this._iScsiName = iScsiName; + } + + public String get_iScsiName() { + return _iScsiName; + } + + public void setChapInitiatorUsername(String chapInitiatorUsername) { + _chapInitiatorUsername = chapInitiatorUsername; + } + + public String getChapInitiatorUsername() { + return _chapInitiatorUsername; + } + + public void setChapInitiatorPassword(String chapInitiatorPassword) { + _chapInitiatorPassword = chapInitiatorPassword; + } + + public String getChapInitiatorPassword() { + return _chapInitiatorPassword; + } + + public void setChapTargetUsername(String chapTargetUsername) { + _chapTargetUsername = chapTargetUsername; + } + + public String getChapTargetUsername() { + return _chapTargetUsername; + } + + public void setChapTargetPassword(String chapTargetPassword) { + _chapTargetPassword = chapTargetPassword; + } + + public String getChapTargetPassword() { + return _chapTargetPassword; + } } diff --git a/engine/api/src/org/apache/cloudstack/storage/command/DettachCommand.java b/engine/api/src/org/apache/cloudstack/storage/command/DettachCommand.java index a0ab4b2e6f4..bb7325c3e81 100644 --- a/engine/api/src/org/apache/cloudstack/storage/command/DettachCommand.java +++ b/engine/api/src/org/apache/cloudstack/storage/command/DettachCommand.java @@ -24,6 +24,8 @@ import com.cloud.agent.api.to.DiskTO; public class DettachCommand extends Command implements StorageSubSystemCommand { private DiskTO disk; private String vmName; + private boolean _managed; + private String _iScsiName; public DettachCommand(DiskTO disk, String vmName) { super(); @@ -52,4 +54,19 @@ public class DettachCommand extends Command implements StorageSubSystemCommand { this.vmName = vmName; } + public void setManaged(boolean managed) { + _managed = managed; + } + + public boolean isManaged() { + return _managed; + } + + public void set_iScsiName(String iScsiName) { + _iScsiName = iScsiName; + } + + public String get_iScsiName() { + return _iScsiName; + } } diff --git a/engine/api/src/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java b/engine/api/src/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java index 9b8de679b08..a8c1e7feb49 100644 --- a/engine/api/src/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java +++ b/engine/api/src/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java @@ -103,6 +103,12 @@ public class StoragePoolVO implements StoragePool { @Enumerated(value = EnumType.STRING) private ScopeType scope; + @Column(name = "managed") + private boolean managed; + + @Column(name = "capacity_iops", updatable = true, nullable = true) + private Long capacityIops; + @Column(name = "hypervisor") @Enumerated(value = EnumType.STRING) private HypervisorType hypervisor; @@ -201,8 +207,24 @@ public class StoragePoolVO implements StoragePool { usedBytes = available; } - public void setCapacityBytes(long capacity) { - capacityBytes = capacity; + public void setCapacityBytes(long capacityBytes) { + this.capacityBytes = capacityBytes; + } + + public void setManaged(boolean managed) { + this.managed = managed; + } + + public boolean isManaged() { + return managed; + } + + public void setCapacityIops(Long capacityIops) { + this.capacityIops = capacityIops; + } + + public Long getCapacityIops() { + return capacityIops; } public Long getClusterId() { diff --git a/engine/schema/src/com/cloud/storage/DiskOfferingVO.java b/engine/schema/src/com/cloud/storage/DiskOfferingVO.java index b7363e7208a..d9656b4c138 100755 --- a/engine/schema/src/com/cloud/storage/DiskOfferingVO.java +++ b/engine/schema/src/com/cloud/storage/DiskOfferingVO.java @@ -94,6 +94,15 @@ public class DiskOfferingVO implements DiskOffering { @Column(name = "uuid") private String uuid; + @Column(name="customized_iops") + private Boolean customizedIops; + + @Column(name="min_iops") + Long minIops; + + @Column(name="max_iops") + Long maxIops; + @Column(name = "sort_key") int sortKey; @@ -116,8 +125,8 @@ public class DiskOfferingVO implements DiskOffering { this.uuid = UUID.randomUUID().toString(); } - public DiskOfferingVO(Long domainId, String name, String displayText, long diskSize, String tags, - boolean isCustomized) { + public DiskOfferingVO(Long domainId, String name, String displayText, long diskSize, String tags, boolean isCustomized, + Boolean isCustomizedIops, Long minIops, Long maxIops) { this.domainId = domainId; this.name = name; this.displayText = displayText; @@ -128,6 +137,9 @@ public class DiskOfferingVO implements DiskOffering { this.useLocalStorage = false; this.customized = isCustomized; this.uuid = UUID.randomUUID().toString(); + this.customizedIops = isCustomizedIops; + this.minIops = minIops; + this.maxIops = maxIops; } public DiskOfferingVO(String name, String displayText, boolean mirrored, String tags, boolean recreatable, @@ -175,6 +187,36 @@ public class DiskOfferingVO implements DiskOffering { } @Override + public Boolean isCustomizedIops() { + return customizedIops; + } + + @Override + public void setCustomizedIops(Boolean customizedIops) { + this.customizedIops = customizedIops; + } + + @Override + public Long getMinIops() { + return minIops; + } + + @Override + public void setMinIops(Long minIops) { + this.minIops = minIops; + } + + @Override + public Long getMaxIops() { + return maxIops; + } + + @Override + public void setMaxIops(Long maxIops) { + this.maxIops = maxIops; + } + + @Override public String getUniqueName() { return uniqueName; } diff --git a/engine/schema/src/com/cloud/storage/VolumeVO.java b/engine/schema/src/com/cloud/storage/VolumeVO.java index 02c09a2f271..7b54f3df538 100755 --- a/engine/schema/src/com/cloud/storage/VolumeVO.java +++ b/engine/schema/src/com/cloud/storage/VolumeVO.java @@ -70,6 +70,12 @@ public class VolumeVO implements Volume { @Column(name = "size") Long size; + @Column(name = "min_iops") + Long minIops; + + @Column(name = "max_iops") + Long maxIops; + @Column(name = "folder") String folder; @@ -141,25 +147,32 @@ public class VolumeVO implements Volume { @Column(name = "display_volume", updatable = true, nullable = false) protected boolean displayVolume; + @Column(name = "iscsi_name") + private String _iScsiName; + @Transient // @Column(name="reservation") String reservationId; // Real Constructor - public VolumeVO(Type type, String name, long dcId, long domainId, long accountId, long diskOfferingId, long size) { + public VolumeVO(Type type, String name, long dcId, long domainId, long accountId, long diskOfferingId, long size, + Long minIops, Long maxIops, String iScsiName) { this.volumeType = type; this.name = name; this.dataCenterId = dcId; this.accountId = accountId; this.domainId = domainId; this.size = size; + this.minIops = minIops; + this.maxIops = maxIops; + this._iScsiName = iScsiName; this.diskOfferingId = diskOfferingId; this.state = State.Allocated; this.uuid = UUID.randomUUID().toString(); } - public VolumeVO(String name, Long dcId, Long podId, long accountId, long domainId, Long instanceId, String folder, - String path, long size, Volume.Type vType) { + public VolumeVO(String name, long dcId, long podId, long accountId, long domainId, Long instanceId, String folder, String path, + long size, Long minIops, Long maxIops, String iScsiName, Volume.Type vType) { this.name = name; this.accountId = accountId; this.domainId = domainId; @@ -167,6 +180,9 @@ public class VolumeVO implements Volume { this.folder = folder; this.path = path; this.size = size; + this.minIops = minIops; + this.maxIops = maxIops; + this._iScsiName = iScsiName; this.podId = podId; this.dataCenterId = dcId; this.volumeType = vType; @@ -177,11 +193,15 @@ public class VolumeVO implements Volume { // Copy Constructor public VolumeVO(Volume that) { - this(that.getName(), that.getDataCenterId(), that.getPodId(), that.getAccountId(), that.getDomainId(), that - .getInstanceId(), that.getFolder(), that.getPath(), that.getSize(), that.getVolumeType()); + this(that.getName(), that.getDataCenterId(), that.getPodId(), that.getAccountId(), that.getDomainId(), that.getInstanceId(), + that.getFolder(), that.getPath(), that.getSize(), that.getMinIops(), that.getMaxIops(), + that.get_iScsiName(), that.getVolumeType()); this.recreatable = that.isRecreatable(); this.state = that.getState(); this.size = that.getSize(); + this.minIops = that.getMinIops(); + this.maxIops = that.getMaxIops(); + this._iScsiName = that.get_iScsiName(); this.diskOfferingId = that.getDiskOfferingId(); this.poolId = that.getPoolId(); this.attached = that.getAttached(); @@ -274,6 +294,24 @@ public class VolumeVO implements Volume { this.size = size; } + @Override + public Long getMinIops() { + return minIops; + } + + public void setMinIops(Long minIops) { + this.minIops = minIops; + } + + @Override + public Long getMaxIops() { + return maxIops; + } + + public void setMaxIops(Long maxIops) { + this.maxIops = maxIops; + } + @Override public Long getInstanceId() { return instanceId; @@ -464,6 +502,15 @@ public class VolumeVO implements Volume { this.uuid = uuid; } + @Override + public String get_iScsiName() { + return this._iScsiName; + } + + public void set_iScsiName(String iScsiName) { + this._iScsiName = iScsiName; + } + public boolean isDisplayVolume() { return displayVolume; } diff --git a/engine/schema/src/com/cloud/storage/dao/VolumeDao.java b/engine/schema/src/com/cloud/storage/dao/VolumeDao.java index 79c0dc37786..fb7dc706f10 100755 --- a/engine/schema/src/com/cloud/storage/dao/VolumeDao.java +++ b/engine/schema/src/com/cloud/storage/dao/VolumeDao.java @@ -58,6 +58,8 @@ public interface VolumeDao extends GenericDao, StateDao findByPoolId(long poolId); + List findByPoolId(long poolId, Volume.Type volumeType); + List findByInstanceAndDeviceId(long instanceId, long deviceId); List findUsableVolumesForInstance(long instanceId); diff --git a/engine/schema/src/com/cloud/storage/dao/VolumeDaoImpl.java b/engine/schema/src/com/cloud/storage/dao/VolumeDaoImpl.java index f82b51157c4..ba85466a930 100755 --- a/engine/schema/src/com/cloud/storage/dao/VolumeDaoImpl.java +++ b/engine/schema/src/com/cloud/storage/dao/VolumeDaoImpl.java @@ -109,6 +109,19 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol sc.setParameters("poolId", poolId); sc.setParameters("notDestroyed", Volume.State.Destroy); sc.setParameters("vType", Volume.Type.ROOT.toString()); + return listBy(sc); + } + + @Override + public List findByPoolId(long poolId, Volume.Type volumeType) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("poolId", poolId); + sc.setParameters("notDestroyed", Volume.State.Destroy); + + if (volumeType != null) { + sc.setParameters("vType", volumeType.toString()); + } + return listBy(sc); } diff --git a/engine/storage/image/src/org/apache/cloudstack/storage/image/TemplateServiceImpl.java b/engine/storage/image/src/org/apache/cloudstack/storage/image/TemplateServiceImpl.java index 96c35f36f34..da6271236f9 100644 --- a/engine/storage/image/src/org/apache/cloudstack/storage/image/TemplateServiceImpl.java +++ b/engine/storage/image/src/org/apache/cloudstack/storage/image/TemplateServiceImpl.java @@ -166,7 +166,7 @@ public class TemplateServiceImpl implements TemplateService { AsyncCallbackDispatcher caller = AsyncCallbackDispatcher.create(this); caller.setCallback(caller.getTarget().createTemplateCallback(null, null)).setContext(context); - store.getDriver().createAsync(templateOnStore, caller); + store.getDriver().createAsync(store, templateOnStore, caller); } @Override @@ -511,7 +511,7 @@ public class TemplateServiceImpl implements TemplateService { TemplateOpContext context = new TemplateOpContext(null, to, future); AsyncCallbackDispatcher caller = AsyncCallbackDispatcher.create(this); caller.setCallback(caller.getTarget().deleteTemplateCallback(null, null)).setContext(context); - to.getDataStore().getDriver().deleteAsync(to, caller); + to.getDataStore().getDriver().deleteAsync(to.getDataStore(), to, caller); return future; } diff --git a/engine/storage/image/src/org/apache/cloudstack/storage/image/store/ImageStoreImpl.java b/engine/storage/image/src/org/apache/cloudstack/storage/image/store/ImageStoreImpl.java index 6d8e8e59c6f..438ab69c399 100644 --- a/engine/storage/image/src/org/apache/cloudstack/storage/image/store/ImageStoreImpl.java +++ b/engine/storage/image/src/org/apache/cloudstack/storage/image/store/ImageStoreImpl.java @@ -145,7 +145,7 @@ public class ImageStoreImpl implements ImageStoreEntity { @Override public boolean delete(DataObject obj) { AsyncCallFuture future = new AsyncCallFuture(); - this.driver.deleteAsync(obj, future); + this.driver.deleteAsync(obj.getDataStore(), obj, future); try { future.get(); } catch (InterruptedException e) { diff --git a/engine/storage/integration-test/test/org/apache/cloudstack/storage/allocator/StorageAllocatorTest.java b/engine/storage/integration-test/test/org/apache/cloudstack/storage/allocator/StorageAllocatorTest.java index 40d9d418938..90696cae806 100644 --- a/engine/storage/integration-test/test/org/apache/cloudstack/storage/allocator/StorageAllocatorTest.java +++ b/engine/storage/integration-test/test/org/apache/cloudstack/storage/allocator/StorageAllocatorTest.java @@ -148,7 +148,8 @@ public class StorageAllocatorTest { diskOffering = diskOfferingDao.persist(diskOffering); diskOfferingId = diskOffering.getId(); - volume = new VolumeVO(Volume.Type.ROOT, "volume", dcId, 1, 1, diskOffering.getId(), diskOffering.getDiskSize()); + volume = new VolumeVO(Volume.Type.ROOT, "volume", dcId, 1, 1, diskOffering.getId(), diskOffering.getDiskSize(), + diskOffering.getMinIops(), diskOffering.getMaxIops(), ""); volume = volumeDao.persist(volume); volumeId = volume.getId(); } diff --git a/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/SnapshotTest.java b/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/SnapshotTest.java index 2579a38157d..f1eed3a4b62 100644 --- a/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/SnapshotTest.java +++ b/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/SnapshotTest.java @@ -347,7 +347,7 @@ public class SnapshotTest extends CloudStackTestNGBase { private VolumeVO createVolume(Long templateId, long dataStoreId) { - VolumeVO volume = new VolumeVO(Volume.Type.DATADISK, UUID.randomUUID().toString(), this.dcId, 1L, 1L, 1L, 1000); + VolumeVO volume = new VolumeVO(Volume.Type.DATADISK, UUID.randomUUID().toString(), this.dcId, 1L, 1L, 1L, 1000, 0L, 0L, ""); volume.setDataCenterId(this.dcId); volume.setPoolId(dataStoreId); volume = volumeDao.persist(volume); diff --git a/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/VolumeTest.java b/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/VolumeTest.java index 70fdb1b5829..cbfafc93333 100644 --- a/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/VolumeTest.java +++ b/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/VolumeTest.java @@ -317,7 +317,7 @@ public class VolumeTest extends CloudStackTestNGBase { } private VolumeVO createVolume(Long templateId, long dataStoreId) { - VolumeVO volume = new VolumeVO(Volume.Type.DATADISK, UUID.randomUUID().toString(), this.dcId, 1L, 1L, 1L, 1000); + VolumeVO volume = new VolumeVO(Volume.Type.DATADISK, UUID.randomUUID().toString(), this.dcId, 1L, 1L, 1L, 1000, 0L, 0L, ""); ; volume.setPoolId(dataStoreId); volume = volumeDao.persist(volume); diff --git a/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/VolumeTestVmware.java b/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/VolumeTestVmware.java index 4acc8dc7de8..be9dd19c6b0 100644 --- a/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/VolumeTestVmware.java +++ b/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/VolumeTestVmware.java @@ -317,7 +317,7 @@ public class VolumeTestVmware extends CloudStackTestNGBase { } private VolumeVO createVolume(Long templateId, long dataStoreId) { - VolumeVO volume = new VolumeVO(Volume.Type.DATADISK, UUID.randomUUID().toString(), this.dcId, 1L, 1L, 1L, 1000); + VolumeVO volume = new VolumeVO(Volume.Type.DATADISK, UUID.randomUUID().toString(), this.dcId, 1L, 1L, 1L, 1000, 0L, 0L, ""); ; volume.setPoolId(dataStoreId); volume = volumeDao.persist(volume); diff --git a/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/volumeServiceTest.java b/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/volumeServiceTest.java index 42b0463c71b..08de7f3a941 100644 --- a/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/volumeServiceTest.java +++ b/engine/storage/integration-test/test/org/apache/cloudstack/storage/test/volumeServiceTest.java @@ -363,7 +363,7 @@ public class volumeServiceTest extends CloudStackTestNGBase { } private VolumeVO createVolume(Long templateId, long dataStoreId) { - VolumeVO volume = new VolumeVO(Volume.Type.DATADISK, UUID.randomUUID().toString(), this.dcId, 1L, 1L, 1L, 1000); + VolumeVO volume = new VolumeVO(Volume.Type.DATADISK, UUID.randomUUID().toString(), this.dcId, 1L, 1L, 1L, 1000, 0L, 0L, ""); volume.setPoolId(dataStoreId); volume = volumeDao.persist(volume); return volume; diff --git a/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java b/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java index 631d220f69d..48ec51265b9 100644 --- a/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java +++ b/engine/storage/snapshot/src/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java @@ -355,7 +355,7 @@ public class SnapshotServiceImpl implements SnapshotService { AsyncCallbackDispatcher caller = AsyncCallbackDispatcher.create(this); caller.setCallback(caller.getTarget().deleteSnapshotCallback(null, null)).setContext(context); DataStore store = snapInfo.getDataStore(); - store.getDriver().deleteAsync(snapInfo, caller); + store.getDriver().deleteAsync(store, snapInfo, caller); SnapshotResult result = null; try { diff --git a/engine/storage/src/org/apache/cloudstack/storage/allocator/ZoneWideStoragePoolAllocator.java b/engine/storage/src/org/apache/cloudstack/storage/allocator/ZoneWideStoragePoolAllocator.java index 29b3400bbbb..300d932a31c 100644 --- a/engine/storage/src/org/apache/cloudstack/storage/allocator/ZoneWideStoragePoolAllocator.java +++ b/engine/storage/src/org/apache/cloudstack/storage/allocator/ZoneWideStoragePoolAllocator.java @@ -49,26 +49,38 @@ public class ZoneWideStoragePoolAllocator extends AbstractStoragePoolAllocator { Volume volume = _volumeDao.findById(dskCh.getVolumeId()); List requestVolumes = new ArrayList(); requestVolumes.add(volume); - return storageMgr.storagePoolHasEnoughSpace(requestVolumes, pool); + + return storageMgr.storagePoolHasEnoughIops(requestVolumes, pool) && + storageMgr.storagePoolHasEnoughSpace(requestVolumes, pool); } - @Override - protected List select(DiskProfile dskCh, VirtualMachineProfile vmProfile, - DeploymentPlan plan, ExcludeList avoid, int returnUpTo) { - s_logger.debug("ZoneWideStoragePoolAllocator to find storage pool"); - List suitablePools = new ArrayList(); - HypervisorType hypervisor = dskCh.getHypervisorType(); - if (hypervisor != null) { - if (hypervisor != HypervisorType.KVM && hypervisor != HypervisorType.VMware) { - s_logger.debug("Only kvm, VMware hypervisors are enabled to support zone wide storage"); - return suitablePools; + @Override + protected List select(DiskProfile dskCh, + VirtualMachineProfile vmProfile, + DeploymentPlan plan, ExcludeList avoid, int returnUpTo) { + s_logger.debug("ZoneWideStoragePoolAllocator to find storage pool"); + List suitablePools = new ArrayList(); + + List storagePools = _storagePoolDao.findZoneWideStoragePoolsByTags(plan.getDataCenterId(), dskCh.getTags()); + + if (storagePools == null) { + storagePools = new ArrayList(); + } + + List anyHypervisorStoragePools = new ArrayList(); + + for (StoragePoolVO storagePool : storagePools) { + if (HypervisorType.Any.equals(storagePool.getHypervisor())) { + anyHypervisorStoragePools.add(storagePool); } } - List storagePools = _storagePoolDao.findZoneWideStoragePoolsByTags(plan.getDataCenterId(), dskCh.getTags()); List storagePoolsByHypervisor = _storagePoolDao.findZoneWideStoragePoolsByHypervisor(plan.getDataCenterId(), dskCh.getHypervisorType()); + storagePools.retainAll(storagePoolsByHypervisor); + storagePools.addAll(anyHypervisorStoragePools); + // add remaining pools in zone, that did not match tags, to avoid set List allPools = _storagePoolDao.findZoneWideStoragePoolsByTags(plan.getDataCenterId(), null); allPools.removeAll(storagePools); diff --git a/engine/storage/src/org/apache/cloudstack/storage/datastore/DataObjectManagerImpl.java b/engine/storage/src/org/apache/cloudstack/storage/datastore/DataObjectManagerImpl.java index fa9f9935bcf..7878d8d144d 100644 --- a/engine/storage/src/org/apache/cloudstack/storage/datastore/DataObjectManagerImpl.java +++ b/engine/storage/src/org/apache/cloudstack/storage/datastore/DataObjectManagerImpl.java @@ -162,7 +162,7 @@ public class DataObjectManagerImpl implements DataObjectManager { AsyncCallbackDispatcher caller = AsyncCallbackDispatcher.create(this); caller.setCallback(caller.getTarget().createAsynCallback(null, null)).setContext(context); - store.getDriver().createAsync(objInStore, caller); + store.getDriver().createAsync(store, objInStore, caller); return; } @@ -321,7 +321,7 @@ public class DataObjectManagerImpl implements DataObjectManager { AsyncCallbackDispatcher caller = AsyncCallbackDispatcher.create(this); caller.setCallback(caller.getTarget().deleteAsynCallback(null, null)).setContext(context); - data.getDataStore().getDriver().deleteAsync(data, caller); + data.getDataStore().getDriver().deleteAsync(data.getDataStore(), data, caller); return; } diff --git a/engine/storage/src/org/apache/cloudstack/storage/datastore/PrimaryDataStoreEntityImpl.java b/engine/storage/src/org/apache/cloudstack/storage/datastore/PrimaryDataStoreEntityImpl.java index e861910116a..0aebee22c11 100644 --- a/engine/storage/src/org/apache/cloudstack/storage/datastore/PrimaryDataStoreEntityImpl.java +++ b/engine/storage/src/org/apache/cloudstack/storage/datastore/PrimaryDataStoreEntityImpl.java @@ -175,6 +175,12 @@ public class PrimaryDataStoreEntityImpl implements StorageEntity { return 0; } + @Override + public Long getCapacityIops() { + // TODO Auto-generated method stub + return 0L; + } + @Override public Long getClusterId() { // TODO Auto-generated method stub diff --git a/engine/storage/src/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java b/engine/storage/src/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java index 93b0c2b373d..97c167164b0 100644 --- a/engine/storage/src/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java +++ b/engine/storage/src/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java @@ -81,7 +81,7 @@ public abstract class BaseImageStoreDriverImpl implements ImageStoreDriver { } @Override - public void createAsync(DataObject data, AsyncCompletionCallback callback) { + public void createAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback callback) { CreateContext context = new CreateContext(callback, data); AsyncCallbackDispatcher caller = AsyncCallbackDispatcher .create(this); @@ -184,7 +184,7 @@ public abstract class BaseImageStoreDriverImpl implements ImageStoreDriver { } @Override - public void deleteAsync(DataObject data, AsyncCompletionCallback callback) { + public void deleteAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback callback) { DeleteCommand cmd = new DeleteCommand(data.getTO()); CommandResult result = new CommandResult(); diff --git a/engine/storage/src/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java b/engine/storage/src/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java index 6815dec4d99..53ead0b99f5 100644 --- a/engine/storage/src/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java +++ b/engine/storage/src/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java @@ -79,6 +79,11 @@ public class PrimaryDataStoreHelper { dataStoreVO.setClusterId(params.getClusterId()); dataStoreVO.setStatus(StoragePoolStatus.Initialized); dataStoreVO.setUserInfo(params.getUserInfo()); + dataStoreVO.setManaged(params.isManaged()); + dataStoreVO.setCapacityIops(params.getCapacityIops()); + dataStoreVO.setCapacityBytes(params.getCapacityBytes()); + dataStoreVO.setUsedBytes(params.getUsedBytes()); + dataStoreVO.setHypervisor(params.getHypervisorType()); Map details = params.getDetails(); String tags = params.getTags(); diff --git a/engine/storage/volume/src/org/apache/cloudstack/storage/datastore/PrimaryDataStoreImpl.java b/engine/storage/volume/src/org/apache/cloudstack/storage/datastore/PrimaryDataStoreImpl.java index cfdb5c0821d..420fd2922f4 100644 --- a/engine/storage/volume/src/org/apache/cloudstack/storage/datastore/PrimaryDataStoreImpl.java +++ b/engine/storage/volume/src/org/apache/cloudstack/storage/datastore/PrimaryDataStoreImpl.java @@ -287,6 +287,11 @@ public class PrimaryDataStoreImpl implements PrimaryDataStore { return this.pdsv.getUsedBytes(); } + @Override + public Long getCapacityIops() { + return this.pdsv.getCapacityIops(); + } + @Override public Long getClusterId() { return this.pdsv.getClusterId(); diff --git a/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeObject.java b/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeObject.java index 071c110da48..55fc3a64c53 100644 --- a/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeObject.java +++ b/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeObject.java @@ -107,6 +107,11 @@ public class VolumeObject implements VolumeInfo { volumeVO.setUuid(uuid); } + @Override + public String get_iScsiName() { + return volumeVO.get_iScsiName(); + } + public void setSize(Long size) { volumeVO.setSize(size); } @@ -126,6 +131,16 @@ public class VolumeObject implements VolumeInfo { return volumeVO.getSize(); } + @Override + public Long getMinIops() { + return volumeVO.getMinIops(); + } + + @Override + public Long getMaxIops() { + return volumeVO.getMaxIops(); + } + public long getVolumeId() { return volumeVO.getId(); } diff --git a/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java b/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java index 56b0b08c0ea..de1e423f126 100644 --- a/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java +++ b/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java @@ -35,8 +35,11 @@ import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService; @@ -143,6 +146,16 @@ public class VolumeServiceImpl implements VolumeService { } + public ChapInfo getChapInfo(VolumeInfo volumeInfo, DataStore dataStore) { + DataStoreDriver dataStoreDriver = dataStore.getDriver(); + + if (dataStoreDriver instanceof PrimaryDataStoreDriver) { + return ((PrimaryDataStoreDriver)dataStoreDriver).getChapInfo(volumeInfo); + } + + return null; + } + @Override public AsyncCallFuture createVolumeAsync(VolumeInfo volume, DataStore dataStore) { AsyncCallFuture future = new AsyncCallFuture(); @@ -154,7 +167,7 @@ public class VolumeServiceImpl implements VolumeService { AsyncCallbackDispatcher caller = AsyncCallbackDispatcher.create(this); caller.setCallback(caller.getTarget().createVolumeCallback(null, null)).setContext(context); - dataStore.getDriver().createAsync(volumeOnStore, caller); + dataStore.getDriver().createAsync(dataStore, volumeOnStore, caller); return future; } @@ -238,7 +251,7 @@ public class VolumeServiceImpl implements VolumeService { AsyncCallbackDispatcher caller = AsyncCallbackDispatcher.create(this); caller.setCallback(caller.getTarget().deleteVolumeCallback(null, null)).setContext(context); - volume.getDataStore().getDriver().deleteAsync(volume, caller); + volume.getDataStore().getDriver().deleteAsync(volume.getDataStore(), volume, caller); return future; } @@ -935,7 +948,7 @@ public class VolumeServiceImpl implements VolumeService { caller.setCallback(caller.getTarget().registerVolumeCallback(null, null)); caller.setContext(context); - store.getDriver().createAsync(volumeOnStore, caller); + store.getDriver().createAsync(store, volumeOnStore, caller); return future; } diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index e0c00fc8f50..914017cf23f 100755 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -2573,7 +2573,7 @@ ServerResource { return new AttachVolumeAnswer(cmd, e.toString()); } - return new AttachVolumeAnswer(cmd, cmd.getDeviceId()); + return new AttachVolumeAnswer(cmd, cmd.getDeviceId(), cmd.getVolumePath()); } private Answer execute(ReadyCommand cmd) { diff --git a/plugins/hypervisors/simulator/src/com/cloud/agent/manager/MockStorageManagerImpl.java b/plugins/hypervisors/simulator/src/com/cloud/agent/manager/MockStorageManagerImpl.java index 21b81e1d7bb..a59949f80a9 100644 --- a/plugins/hypervisors/simulator/src/com/cloud/agent/manager/MockStorageManagerImpl.java +++ b/plugins/hypervisors/simulator/src/com/cloud/agent/manager/MockStorageManagerImpl.java @@ -258,7 +258,7 @@ public class MockStorageManagerImpl extends ManagerBase implements MockStorageMa } txn.commit(); - return new AttachVolumeAnswer(cmd, cmd.getDeviceId()); + return new AttachVolumeAnswer(cmd, cmd.getDeviceId(), cmd.getVolumePath()); } catch (Exception ex) { txn.rollback(); throw new CloudRuntimeException("Error when attaching volume " + cmd.getVolumeName() + " to VM " diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java index 5c51585552c..52f41901418 100755 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -36,6 +36,9 @@ import java.util.Map; import java.util.Random; import java.util.TimeZone; import java.util.UUID; +import java.util.HashSet; +import java.util.Set; +import java.util.Map.Entry; import javax.inject.Inject; import javax.naming.ConfigurationException; @@ -216,6 +219,8 @@ import com.cloud.hypervisor.vmware.mo.HostMO; import com.cloud.hypervisor.vmware.mo.HypervisorHostHelper; import com.cloud.hypervisor.vmware.mo.NetworkDetails; import com.cloud.hypervisor.vmware.mo.VirtualEthernetCardType; +import com.cloud.hypervisor.vmware.mo.HostDatastoreSystemMO; +import com.cloud.hypervisor.vmware.mo.HostStorageSystemMO; import com.cloud.hypervisor.vmware.mo.VirtualMachineMO; import com.cloud.hypervisor.vmware.mo.VirtualSwitchType; import com.cloud.hypervisor.vmware.mo.VmwareHypervisorHost; @@ -229,7 +234,6 @@ import com.cloud.network.HAProxyConfigurator; import com.cloud.network.LoadBalancerConfigurator; import com.cloud.network.Networks; import com.cloud.network.Networks.BroadcastDomainType; -import com.cloud.network.Networks.IsolationType; import com.cloud.network.Networks.TrafficType; import com.cloud.network.VmwareTrafficLabel; import com.cloud.network.rules.FirewallRule; @@ -272,8 +276,16 @@ import com.vmware.vim25.GuestInfo; import com.vmware.vim25.HostCapability; import com.vmware.vim25.HostFirewallInfo; import com.vmware.vim25.HostFirewallRuleset; -import com.vmware.vim25.HostNetworkTrafficShapingPolicy; -import com.vmware.vim25.HostPortGroupSpec; +import com.vmware.vim25.HostHostBusAdapter; +import com.vmware.vim25.HostInternetScsiTargetTransport; +import com.vmware.vim25.HostScsiTopology; +import com.vmware.vim25.HostInternetScsiHba; +import com.vmware.vim25.HostInternetScsiHbaAuthenticationProperties; +import com.vmware.vim25.HostInternetScsiHbaStaticTarget; +import com.vmware.vim25.HostScsiDisk; +import com.vmware.vim25.HostScsiTopologyInterface; +import com.vmware.vim25.HostScsiTopologyLun; +import com.vmware.vim25.HostScsiTopologyTarget; import com.vmware.vim25.ManagedObjectReference; import com.vmware.vim25.ObjectContent; import com.vmware.vim25.OptionValue; @@ -304,10 +316,6 @@ import com.vmware.vim25.VirtualMachineRelocateSpecDiskLocator; import com.vmware.vim25.VirtualMachineRuntimeInfo; import com.vmware.vim25.VirtualSCSISharing; -import java.util.HashSet; -import java.util.Set; -import java.util.Map.Entry; - public class VmwareResource implements StoragePoolResource, ServerResource, VmwareHostService { private static final Logger s_logger = Logger.getLogger(VmwareResource.class); @@ -593,7 +601,6 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa try { VmwareHypervisorHost hyperHost = getHyperHost(getServiceContext()); - ManagedObjectReference morDc = hyperHost.getHyperHostDatacenter(); // find VM through datacenter (VM is not at the target host yet) VirtualMachineMO vmMo = hyperHost.findVmOnPeerHyperHost(vmName); if (vmMo == null) { @@ -3244,7 +3251,7 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa HashMap newStates = getVmStates(); List requestedVmNames = cmd.getVmNames(); - List vmNames = new ArrayList(); + List vmNames = new ArrayList(); if (requestedVmNames != null) { for (String vmName : requestedVmNames) { @@ -3750,8 +3757,6 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa s_logger.info("Executing resource MigrateVolumeCommand: " + _gson.toJson(cmd)); } - VmwareContext context = getServiceContext(); - VmwareManager mgr = context.getStockObject(VmwareManager.CONTEXT_STOCK_NAME); final String vmName = volMgr.getVmNameFromVolumeId(cmd.getVolumeId()); VirtualMachineMO vmMo = null; @@ -3903,6 +3908,45 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa } } + protected ManagedObjectReference handleDatastoreAndVmdk(AttachVolumeCommand cmd) throws Exception { + ManagedObjectReference morDs = null; + + VmwareContext context = getServiceContext(); + VmwareHypervisorHost hyperHost = getHyperHost(context); + + String iqn = cmd.get_iScsiName(); + + if (cmd.getAttach()) { + morDs = createVmfsDatastore(hyperHost, iqn, + cmd.getStorageHost(), cmd.getStoragePort(), iqn, + cmd.getChapInitiatorUsername(), cmd.getChapInitiatorPassword(), + cmd.getChapTargetUsername(), cmd.getChapTargetPassword()); + + DatastoreMO dsMo = new DatastoreMO(context, morDs); + + String volumeDatastorePath = String.format("[%s] %s.vmdk", dsMo.getName(), dsMo.getName()); + + if (!dsMo.fileExists(volumeDatastorePath)) { + String dummyVmName = getWorkerName(context, cmd, 0); + + VirtualMachineMO vmMo = prepareVolumeHostDummyVm(hyperHost, dsMo, dummyVmName); + + if (vmMo == null) { + throw new Exception("Unable to create a dummy VM for volume creation"); + } + + vmMo.createDisk(volumeDatastorePath, (int)(dsMo.getSummary().getFreeSpace() / (1024L * 1024L)), + morDs, vmMo.getScsiDeviceControllerKey()); + vmMo.detachDisk(volumeDatastorePath, false); + } + } + else { + deleteVmfsDatastore(hyperHost, iqn, cmd.getStorageHost(), cmd.getStoragePort(), iqn); + } + + return morDs; + } + protected Answer execute(AttachVolumeCommand cmd) { if (s_logger.isInfoEnabled()) { s_logger.info("Executing resource AttachVolumeCommand: " + _gson.toJson(cmd)); @@ -3922,7 +3966,15 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa throw new Exception(msg); } - ManagedObjectReference morDs = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, cmd.getPoolUuid()); + ManagedObjectReference morDs = null; + + if (cmd.getAttach() && cmd.isManaged()) { + morDs = handleDatastoreAndVmdk(cmd); + } + else { + morDs = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, cmd.getPoolUuid()); + } + if (morDs == null) { String msg = "Unable to find the mounted datastore to execute AttachVolumeCommand, vmName: " + cmd.getVmName(); s_logger.error(msg); @@ -3933,12 +3985,16 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa String datastoreVolumePath = dsMo.searchFileInSubFolders(cmd.getVolumePath() + ".vmdk", true); assert (datastoreVolumePath != null) : "Virtual disk file must exist in specified datastore for attach/detach operations."; - AttachVolumeAnswer answer = new AttachVolumeAnswer(cmd, cmd.getDeviceId()); + AttachVolumeAnswer answer = new AttachVolumeAnswer(cmd, cmd.getDeviceId(), datastoreVolumePath); if (cmd.getAttach()) { vmMo.attachDisk(new String[] { datastoreVolumePath }, morDs); } else { vmMo.removeAllSnapshots(); vmMo.detachDisk(datastoreVolumePath, false); + + if (cmd.isManaged()) { + handleDatastoreAndVmdk(cmd); + } } return answer; @@ -3954,6 +4010,198 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa } } + private ManagedObjectReference createVmfsDatastore(VmwareHypervisorHost hyperHost, String datastoreName, String storageIpAddress, + int storagePortNumber, String iqn, String chapName, String chapSecret, String mutualChapName, String mutualChapSecret) throws Exception { + VmwareContext context = getServiceContext(); + ManagedObjectReference morCluster = hyperHost.getHyperHostCluster(); + ClusterMO cluster = new ClusterMO(context, morCluster); + List> lstHosts = cluster.getClusterHosts(); + + HostInternetScsiHbaStaticTarget target = new HostInternetScsiHbaStaticTarget(); + + target.setAddress(storageIpAddress); + target.setPort(storagePortNumber); + target.setIScsiName(iqn); + + HostInternetScsiHbaAuthenticationProperties auth = new HostInternetScsiHbaAuthenticationProperties(); + + String strAuthType = "chapRequired"; + + auth.setChapAuthEnabled(true); + auth.setChapInherited(false); + auth.setChapAuthenticationType(strAuthType); + auth.setChapName(chapName); + auth.setChapSecret(chapSecret); + auth.setMutualChapInherited(false); + auth.setMutualChapAuthenticationType(strAuthType); + auth.setMutualChapName(mutualChapName); + auth.setMutualChapSecret(mutualChapSecret); + + target.setAuthenticationProperties(auth); + + final List lstTargets = new ArrayList(); + + lstTargets.add(target); + + HostDatastoreSystemMO hostDatastoreSystem = null; + HostStorageSystemMO hostStorageSystem = null; + + final List threads = new ArrayList(); + final List exceptions = new ArrayList(); + + for (Pair hostPair : lstHosts) { + HostMO host = new HostMO(context, hostPair.first()); + hostDatastoreSystem = host.getHostDatastoreSystemMO(); + hostStorageSystem = host.getHostStorageSystemMO(); + + boolean iScsiHbaConfigured = false; + + for (HostHostBusAdapter hba : hostStorageSystem.getStorageDeviceInfo().getHostBusAdapter()) { + if (hba instanceof HostInternetScsiHba) { + // just finding an instance of HostInternetScsiHba means that we have found at least one configured iSCSI HBA + // at least one iSCSI HBA must be configured before a CloudStack user can use this host for iSCSI storage + iScsiHbaConfigured = true; + + final String iScsiHbaDevice = hba.getDevice(); + + final HostStorageSystemMO hss = hostStorageSystem; + + threads.add(new Thread() { + public void run() { + try { + hss.addInternetScsiStaticTargets(iScsiHbaDevice, lstTargets); + + hss.rescanHba(iScsiHbaDevice); + } + catch (Exception ex) { + synchronized (exceptions) { + exceptions.add(ex); + } + } + } + }); + } + } + + if (!iScsiHbaConfigured) { + throw new Exception("An iSCSI HBA must be configured before a host can use iSCSI storage."); + } + } + + for (Thread thread : threads) { + thread.start(); + } + + for (Thread thread : threads) { + thread.join(); + } + + if (exceptions.size() > 0) { + throw new Exception(exceptions.get(0).getMessage()); + } + + ManagedObjectReference morDs = hostDatastoreSystem.findDatastore(iqn); + + if (morDs != null) { + return morDs; + } + + List lstHostScsiDisks = hostDatastoreSystem.queryAvailableDisksForVmfs(); + + HostScsiDisk hostScsiDisk = getHostScsiDisk(hostStorageSystem.getStorageDeviceInfo().getScsiTopology(), lstHostScsiDisks, iqn); + + if (hostScsiDisk == null) { + throw new Exception("A relevant SCSI disk could not be located to use to create a datastore."); + } + + return hostDatastoreSystem.createVmfsDatastore(datastoreName, hostScsiDisk); + } + + // the purpose of this method is to find the HostScsiDisk in the passed-in array that exists (if any) because + // we added the static iqn to an iSCSI HBA + private static HostScsiDisk getHostScsiDisk(HostScsiTopology hst, List lstHostScsiDisks, String iqn) { + for (HostScsiTopologyInterface adapter : hst.getAdapter()) { + if (adapter.getTarget() != null) { + for (HostScsiTopologyTarget target : adapter.getTarget()) { + if (target.getTransport() instanceof HostInternetScsiTargetTransport) { + String iScsiName = ((HostInternetScsiTargetTransport)target.getTransport()).getIScsiName(); + + if (iqn.equals(iScsiName)) { + for (HostScsiDisk hostScsiDisk : lstHostScsiDisks) { + for (HostScsiTopologyLun hstl : target.getLun()) { + if (hstl.getScsiLun().contains(hostScsiDisk.getUuid())) { + return hostScsiDisk; + } + } + } + } + } + } + } + } + + return null; + } + + private void deleteVmfsDatastore(VmwareHypervisorHost hyperHost, String volumeUuid, + String storageIpAddress, int storagePortNumber, String iqn) throws Exception { + // hyperHost.unmountDatastore(volumeUuid); + + VmwareContext context = getServiceContext(); + ManagedObjectReference morCluster = hyperHost.getHyperHostCluster(); + ClusterMO cluster = new ClusterMO(context, morCluster); + List> lstHosts = cluster.getClusterHosts(); + + HostInternetScsiHbaStaticTarget target = new HostInternetScsiHbaStaticTarget(); + + target.setAddress(storageIpAddress); + target.setPort(storagePortNumber); + target.setIScsiName(iqn); + + final List lstTargets = new ArrayList(); + + lstTargets.add(target); + + final List threads = new ArrayList(); + final List exceptions = new ArrayList(); + + for (Pair hostPair : lstHosts) { + final HostMO host = new HostMO(context, hostPair.first()); + final HostStorageSystemMO hostStorageSystem = host.getHostStorageSystemMO(); + + for (HostHostBusAdapter hba : hostStorageSystem.getStorageDeviceInfo().getHostBusAdapter()) { + if (hba instanceof HostInternetScsiHba) { + final String iScsiHbaDevice = hba.getDevice(); + + Thread thread = new Thread() { + public void run() { + try { + hostStorageSystem.removeInternetScsiStaticTargets(iScsiHbaDevice, lstTargets); + + hostStorageSystem.rescanHba(iScsiHbaDevice); + } + catch (Exception ex) { + exceptions.add(ex); + } + } + }; + + threads.add(thread); + + thread.start(); + } + } + } + + for (Thread thread : threads) { + thread.join(); + } + + if (exceptions.size() > 0) { + throw new Exception(exceptions.get(0).getMessage()); + } + } + protected Answer execute(AttachIsoCommand cmd) { if (s_logger.isInfoEnabled()) { s_logger.info("Executing resource AttachIsoCommand: " + _gson.toJson(cmd)); diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java index cbbec7ce7e6..d9c357d073b 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java @@ -4172,11 +4172,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe for (VIF vif : vifs) { networks.add(vif.getNetwork(conn)); } - List vdis = getVdis(conn, vm); vm.destroy(conn); - for( VDI vdi : vdis ){ - umount(conn, vdi); - } state = State.Stopped; SR sr = getISOSRbyVmName(conn, cmd.getVmName()); removeSR(conn, sr); @@ -4479,7 +4475,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe throw new CloudRuntimeException("Could not find available VIF slot in VM with name: " + vmName); } - protected VDI mount(Connection conn, StoragePoolType pooltype, String volumeFolder, String volumePath) { + protected VDI mount(Connection conn, StoragePoolType poolType, String volumeFolder, String volumePath) { return getVDIbyUuid(conn, volumePath); } @@ -5549,7 +5545,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe if (pool.getType() == StoragePoolType.NetworkFilesystem) { getNfsSR(conn, pool); } else if (pool.getType() == StoragePoolType.IscsiLUN) { - getIscsiSR(conn, pool); + getIscsiSR(conn, pool.getUuid(), pool.getHost(), pool.getPath(), null, null, new Boolean[1]); } else if (pool.getType() == StoragePoolType.PreSetup) { } else { return new Answer(cmd, false, "The pool type: " + pool.getType().name() + " is not supported."); @@ -6229,7 +6225,6 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe public Answer execute(ResizeVolumeCommand cmd) { Connection conn = getConnection(); - StorageFilerTO pool = cmd.getPool(); String volid = cmd.getPath(); long newSize = cmd.getNewSize(); @@ -6367,19 +6362,18 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe } } - protected SR getIscsiSR(Connection conn, StorageFilerTO pool) { - synchronized (pool.getUuid().intern()) { + protected SR getIscsiSR(Connection conn, String srNameLabel, String target, String path, + String chapInitiatorUsername, String chapInitiatorPassword, Boolean[] created) { + synchronized (srNameLabel.intern()) { Map deviceConfig = new HashMap(); try { - String target = pool.getHost(); - String path = pool.getPath(); if (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } String tmp[] = path.split("/"); if (tmp.length != 3) { - String msg = "Wrong iscsi path " + pool.getPath() + " it should be /targetIQN/LUN"; + String msg = "Wrong iscsi path " + path + " it should be /targetIQN/LUN"; s_logger.warn(msg); throw new CloudRuntimeException(msg); } @@ -6387,7 +6381,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe String lunid = tmp[2].trim(); String scsiid = ""; - Set srs = SR.getByNameLabel(conn, pool.getUuid()); + Set srs = SR.getByNameLabel(conn, srNameLabel); for (SR sr : srs) { if (!SRType.LVMOISCSI.equals(sr.getType(conn))) { continue; @@ -6412,19 +6406,24 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe } if (target.equals(dc.get("target")) && targetiqn.equals(dc.get("targetIQN")) && lunid.equals(dc.get("lunid"))) { throw new CloudRuntimeException("There is a SR using the same configuration target:" + dc.get("target") + ", targetIQN:" - + dc.get("targetIQN") + ", lunid:" + dc.get("lunid") + " for pool " + pool.getUuid() + "on host:" + _host.uuid); + + dc.get("targetIQN") + ", lunid:" + dc.get("lunid") + " for pool " + srNameLabel + "on host:" + _host.uuid); } } deviceConfig.put("target", target); deviceConfig.put("targetIQN", targetiqn); + if (StringUtils.isNotBlank(chapInitiatorUsername) && + StringUtils.isNotBlank(chapInitiatorPassword)) { + deviceConfig.put("chapuser", chapInitiatorUsername); + deviceConfig.put("chappassword", chapInitiatorPassword); + } + Host host = Host.getByUuid(conn, _host.uuid); Map smConfig = new HashMap(); String type = SRType.LVMOISCSI.toString(); - String poolId = Long.toString(pool.getId()); SR sr = null; try { - sr = SR.create(conn, host, deviceConfig, new Long(0), pool.getUuid(), poolId, type, "user", true, + sr = SR.create(conn, host, deviceConfig, new Long(0), srNameLabel, srNameLabel, type, "user", true, smConfig); } catch (XenAPIException e) { String errmsg = e.toString(); @@ -6463,19 +6462,30 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe if( result.indexOf("") != -1) { pooluuid = result.substring(result.indexOf("") + 6, result.indexOf("")).trim(); } - if( pooluuid == null || pooluuid.length() != 36) { - sr = SR.create(conn, host, deviceConfig, new Long(0), pool.getUuid(), poolId, type, "user", true, + + if (pooluuid == null || pooluuid.length() != 36) + { + sr = SR.create(conn, host, deviceConfig, new Long(0), srNameLabel, srNameLabel, type, "user", true, smConfig); + + created[0] = true; // note that the SR was created (as opposed to introduced) } else { - sr = SR.introduce(conn, pooluuid, pool.getUuid(), poolId, - type, "user", true, smConfig); - Pool.Record pRec = XenServerConnectionPool.getPoolRecord(conn); - PBD.Record rec = new PBD.Record(); - rec.deviceConfig = deviceConfig; - rec.host = pRec.master; - rec.SR = sr; - PBD pbd = PBD.create(conn, rec); - pbd.plug(conn); + sr = SR.introduce(conn, pooluuid, srNameLabel, srNameLabel, + type, "user", true, smConfig); + + Set setHosts = Host.getAll(conn); + + for (Host currentHost : setHosts) { + PBD.Record rec = new PBD.Record(); + + rec.deviceConfig = deviceConfig; + rec.host = currentHost; + rec.SR = sr; + + PBD pbd = PBD.create(conn, rec); + + pbd.plug(conn); + } } sr.scan(conn); return sr; @@ -6636,6 +6646,52 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe } } + // for about 1 GiB of physical size, about 4 MiB seems to be used for metadata + private long getMetadata(long physicalSize) { + return (long)(physicalSize * 0.00390625); // 1 GiB / 4 MiB = 0.00390625 + } + + protected VDI handleSrAndVdiAttach(String iqn, String storageHostName, + String chapInitiatorName, String chapInitiatorPassword) throws Exception { + VDI vdi = null; + + Connection conn = getConnection(); + + Boolean[] created = { false }; + + SR sr = getIscsiSR(conn, iqn, + storageHostName, iqn, + chapInitiatorName, chapInitiatorPassword, created); + + // if created[0] is true, this means the SR was actually created...as opposed to introduced + if (created[0]) { + VDI.Record vdir = new VDI.Record(); + + vdir.nameLabel = iqn; + vdir.SR = sr; + vdir.type = Types.VdiType.USER; + vdir.virtualSize = sr.getPhysicalSize(conn) - sr.getPhysicalUtilisation(conn) - getMetadata(sr.getPhysicalSize(conn)); + + if (vdir.virtualSize < 0) { + throw new Exception("VDI virtual size cannot be less than 0."); + } + + vdi = VDI.create(conn, vdir); + } + else { + vdi = sr.getVDIs(conn).iterator().next(); + } + + return vdi; + } + + protected void handleSrAndVdiDetach(String iqn) throws Exception { + Connection conn = getConnection(); + + SR sr = getStorageRepository(conn, iqn); + + removeSR(conn, sr); + } protected AttachVolumeAnswer execute(final AttachVolumeCommand cmd) { Connection conn = getConnection(); @@ -6652,7 +6708,16 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe try { // Look up the VDI - VDI vdi = mount(conn, cmd.getPooltype(), cmd.getVolumeFolder(),cmd.getVolumePath()); + VDI vdi = null; + + if (cmd.getAttach() && cmd.isManaged()) { + vdi = handleSrAndVdiAttach(cmd.get_iScsiName(), cmd.getStorageHost(), + cmd.getChapInitiatorUsername(), cmd.getChapInitiatorPassword()); + } + else { + vdi = getVDIbyUuid(conn, cmd.getVolumePath()); + } + // Look up the VM VM vm = getVM(conn, vmName); /* For HVM guest, if no pv driver installed, no attach/detach */ @@ -6704,7 +6769,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe // Update the VDI's label to include the VM name vdi.setNameLabel(conn, vmName + "-DATA"); - return new AttachVolumeAnswer(cmd, Long.parseLong(diskNumber)); + return new AttachVolumeAnswer(cmd, Long.parseLong(diskNumber), vdi.getUuid(conn)); } else { // Look up all VBDs for this VDI Set vbds = vdi.getVBDs(conn); @@ -6723,7 +6788,9 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe // Update the VDI's label to be "detached" vdi.setNameLabel(conn, "detached"); - umount(conn, vdi); + if (cmd.isManaged()) { + handleSrAndVdiDetach(cmd.get_iScsiName()); + } return new AttachVolumeAnswer(cmd); } @@ -7606,30 +7673,30 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe } } - protected SR getStorageRepository(Connection conn, String uuid) { + protected SR getStorageRepository(Connection conn, String srNameLabel) { Set srs; try { - srs = SR.getByNameLabel(conn, uuid); + srs = SR.getByNameLabel(conn, srNameLabel); } catch (XenAPIException e) { - throw new CloudRuntimeException("Unable to get SR " + uuid + " due to " + e.toString(), e); + throw new CloudRuntimeException("Unable to get SR " + srNameLabel + " due to " + e.toString(), e); } catch (Exception e) { - throw new CloudRuntimeException("Unable to get SR " + uuid + " due to " + e.getMessage(), e); + throw new CloudRuntimeException("Unable to get SR " + srNameLabel + " due to " + e.getMessage(), e); } if (srs.size() > 1) { - throw new CloudRuntimeException("More than one storage repository was found for pool with uuid: " + uuid); + throw new CloudRuntimeException("More than one storage repository was found for pool with uuid: " + srNameLabel); } else if (srs.size() == 1) { SR sr = srs.iterator().next(); if (s_logger.isDebugEnabled()) { - s_logger.debug("SR retrieved for " + uuid); + s_logger.debug("SR retrieved for " + srNameLabel); } if (checkSR(conn, sr)) { return sr; } - throw new CloudRuntimeException("SR check failed for storage pool: " + uuid + "on host:" + _host.uuid); + throw new CloudRuntimeException("SR check failed for storage pool: " + srNameLabel + "on host:" + _host.uuid); } else { - throw new CloudRuntimeException("Can not see storage pool: " + uuid + " from on host:" + _host.uuid); + throw new CloudRuntimeException("Can not see storage pool: " + srNameLabel + " from on host:" + _host.uuid); } } diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServerStorageProcessor.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServerStorageProcessor.java index 399e2341b56..e6358f26690 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServerStorageProcessor.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServerStorageProcessor.java @@ -55,7 +55,6 @@ import org.apache.xmlrpc.XmlRpcException; import com.cloud.agent.api.Answer; import com.cloud.agent.api.CreateStoragePoolCommand; -import com.cloud.agent.api.CreateVolumeFromSnapshotAnswer; import com.cloud.agent.api.to.DataObjectType; import com.cloud.agent.api.to.DataStoreTO; import com.cloud.agent.api.to.DataTO; @@ -171,7 +170,16 @@ public class XenServerStorageProcessor implements StorageProcessor { try { Connection conn = this.hypervisorResource.getConnection(); // Look up the VDI - VDI vdi = this.hypervisorResource.mount(conn, null, null, data.getPath()); + VDI vdi = null; + + if (cmd.isManaged()) { + vdi = this.hypervisorResource.handleSrAndVdiAttach(cmd.get_iScsiName(), cmd.getStorageHost(), + cmd.getChapInitiatorUsername(), cmd.getChapInitiatorPassword()); + } + else { + vdi = this.hypervisorResource.mount(conn, null, null, data.getPath()); + } + // Look up the VM VM vm = this.hypervisorResource.getVM(conn, vmName); /* For HVM guest, if no pv driver installed, no attach/detach */ @@ -223,7 +231,7 @@ public class XenServerStorageProcessor implements StorageProcessor { // Update the VDI's label to include the VM name vdi.setNameLabel(conn, vmName + "-DATA"); - DiskTO newDisk = new DiskTO(disk.getData(), Long.parseLong(diskNumber), disk.getType()); + DiskTO newDisk = new DiskTO(disk.getData(), Long.parseLong(diskNumber), vdi.getUuid(conn), disk.getType()); return new AttachAnswer(newDisk); } catch (XenAPIException e) { @@ -350,6 +358,10 @@ public class XenServerStorageProcessor implements StorageProcessor { this.hypervisorResource.umount(conn, vdi); + if (cmd.isManaged()) { + this.hypervisorResource.handleSrAndVdiDetach(cmd.get_iScsiName()); + } + return new DettachAnswer(disk); } catch(Exception e) { s_logger.warn("Failed dettach volume: " + data.getPath()); diff --git a/plugins/storage/volume/default/src/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java b/plugins/storage/volume/default/src/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java index 8d7c965ee07..a2334074315 100644 --- a/plugins/storage/volume/default/src/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java +++ b/plugins/storage/volume/default/src/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java @@ -95,7 +95,12 @@ public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDri } @Override - public void createAsync(DataObject data, AsyncCompletionCallback callback) { + public ChapInfo getChapInfo(VolumeInfo volumeInfo) { + return null; + } + + @Override + public void createAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback callback) { String errMsg = null; Answer answer = null; if (data.getType() == DataObjectType.VOLUME) { @@ -118,7 +123,7 @@ public class CloudStackPrimaryDataStoreDriverImpl implements PrimaryDataStoreDri } @Override - public void deleteAsync(DataObject data, AsyncCompletionCallback callback) { + public void deleteAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback callback) { DeleteCommand cmd = new DeleteCommand(data.getTO()); CommandResult result = new CommandResult(); diff --git a/plugins/storage/volume/sample/src/org/apache/cloudstack/storage/datastore/driver/SamplePrimaryDataStoreDriverImpl.java b/plugins/storage/volume/sample/src/org/apache/cloudstack/storage/datastore/driver/SamplePrimaryDataStoreDriverImpl.java index 643c9334964..78f22630b91 100644 --- a/plugins/storage/volume/sample/src/org/apache/cloudstack/storage/datastore/driver/SamplePrimaryDataStoreDriverImpl.java +++ b/plugins/storage/volume/sample/src/org/apache/cloudstack/storage/datastore/driver/SamplePrimaryDataStoreDriverImpl.java @@ -54,6 +54,11 @@ public class SamplePrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver return null; } + @Override + public ChapInfo getChapInfo(VolumeInfo volumeInfo) { + return null; + } + private class CreateVolumeContext extends AsyncRpcConext { private final DataObject volume; public CreateVolumeContext(AsyncCompletionCallback callback, DataObject volume) { @@ -77,7 +82,7 @@ public class SamplePrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver } @Override - public void deleteAsync(DataObject vo, AsyncCompletionCallback callback) { + public void deleteAsync(DataStore dataStore, DataObject vo, AsyncCompletionCallback callback) { /* * DeleteCommand cmd = new DeleteCommand(vo.getUri()); * @@ -146,7 +151,7 @@ public class SamplePrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver */ @Override - public void createAsync(DataObject vol, AsyncCompletionCallback callback) { + public void createAsync(DataStore dataStore, DataObject vol, AsyncCompletionCallback callback) { EndPoint ep = selector.select(vol); CreateObjectCommand createCmd = new CreateObjectCommand(null); diff --git a/plugins/storage/volume/solidfire/pom.xml b/plugins/storage/volume/solidfire/pom.xml index 9db0685e91b..81af8ace135 100644 --- a/plugins/storage/volume/solidfire/pom.xml +++ b/plugins/storage/volume/solidfire/pom.xml @@ -12,7 +12,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 cloud-plugin-storage-volume-solidfire - Apache CloudStack Plugin - Storage Volume solidfire + Apache CloudStack Plugin - Storage Volume SolidFire Provider org.apache.cloudstack cloudstack-plugins @@ -31,6 +31,11 @@ ${cs.mysql.version} provided + + com.google.code.gson + gson + ${cs.gson.version} + install diff --git a/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidfirePrimaryDataStoreDriver.java b/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidfirePrimaryDataStoreDriver.java index 960378c2821..329f27f7779 100644 --- a/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidfirePrimaryDataStoreDriver.java +++ b/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidfirePrimaryDataStoreDriver.java @@ -16,13 +16,46 @@ // under the License. package org.apache.cloudstack.storage.datastore.driver; -import com.cloud.agent.api.to.DataStoreTO; -import com.cloud.agent.api.to.DataTO; +import java.util.List; +import java.util.Set; + +import javax.inject.Inject; + import org.apache.cloudstack.engine.subsystem.api.storage.*; import org.apache.cloudstack.framework.async.AsyncCompletionCallback; import org.apache.cloudstack.storage.command.CommandResult; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.datastore.util.SolidFireUtil; +import org.apache.log4j.Logger; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.to.DataObjectType; +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.agent.api.to.DataTO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.exception.StorageUnavailableException; +import com.cloud.storage.Storage.StoragePoolType; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.dao.VolumeDetailsDao; +import com.cloud.user.AccountVO; +import com.cloud.user.AccountDetailsDao; +import com.cloud.user.AccountDetailVO; +import com.cloud.user.dao.AccountDao; public class SolidfirePrimaryDataStoreDriver implements PrimaryDataStoreDriver { + private static final Logger s_logger = Logger.getLogger(SolidfirePrimaryDataStoreDriver.class); + + @Inject private PrimaryDataStoreDao _storagePoolDao; + @Inject private StoragePoolDetailsDao _storagePoolDetailsDao; + @Inject private VolumeDao _volumeDao; + @Inject private VolumeDetailsDao _volumeDetailsDao; + @Inject private DataCenterDao _zoneDao; + @Inject private AccountDao _accountDao; + @Inject private AccountDetailsDao _accountDetailsDao; @Override public DataTO getTO(DataObject data) { @@ -34,12 +67,450 @@ public class SolidfirePrimaryDataStoreDriver implements PrimaryDataStoreDriver { return null; } - @Override - public void createAsync(DataObject data, AsyncCompletionCallback callback) { + private static class SolidFireConnection { + private final String _managementVip; + private final int _managementPort; + private final String _clusterAdminUsername; + private final String _clusterAdminPassword; + + public SolidFireConnection(String managementVip, int managementPort, + String clusterAdminUsername, String clusterAdminPassword) { + _managementVip = managementVip; + _managementPort = managementPort; + _clusterAdminUsername = clusterAdminUsername; + _clusterAdminPassword = clusterAdminPassword; + } + + public String getManagementVip() { + return _managementVip; + } + + public int getManagementPort() { + return _managementPort; + } + + public String getClusterAdminUsername() { + return _clusterAdminUsername; + } + + public String getClusterAdminPassword() { + return _clusterAdminPassword; + } + } + + private SolidFireConnection getSolidFireConnection(long storagePoolId) { + StoragePoolDetailVO storagePoolDetail = _storagePoolDetailsDao.findDetail(storagePoolId, SolidFireUtil.MANAGEMENT_VIP); + + String mVip = storagePoolDetail.getValue(); + + storagePoolDetail = _storagePoolDetailsDao.findDetail(storagePoolId, SolidFireUtil.MANAGEMENT_PORT); + + int mPort = Integer.parseInt(storagePoolDetail.getValue()); + + storagePoolDetail = _storagePoolDetailsDao.findDetail(storagePoolId, SolidFireUtil.CLUSTER_ADMIN_USERNAME); + + String clusterAdminUsername = storagePoolDetail.getValue(); + + storagePoolDetail = _storagePoolDetailsDao.findDetail(storagePoolId, SolidFireUtil.CLUSTER_ADMIN_PASSWORD); + + String clusterAdminPassword = storagePoolDetail.getValue(); + + return new SolidFireConnection(mVip, mPort, clusterAdminUsername, clusterAdminPassword); + } + + private SolidFireUtil.SolidFireAccount createSolidFireAccount(String sfAccountName, + SolidFireConnection sfConnection) { + try { + String mVip = sfConnection.getManagementVip(); + int mPort = sfConnection.getManagementPort(); + String clusterAdminUsername = sfConnection.getClusterAdminUsername(); + String clusterAdminPassword = sfConnection.getClusterAdminPassword(); + + long accountNumber = SolidFireUtil.createSolidFireAccount(mVip, mPort, + clusterAdminUsername, clusterAdminPassword, sfAccountName); + + return SolidFireUtil.getSolidFireAccountById(mVip, mPort, + clusterAdminUsername, clusterAdminPassword, accountNumber); + } + catch (Exception ex) { + throw new IllegalArgumentException(ex.getMessage()); + } + } + + private void updateCsDbWithAccountInfo(long csAccountId, SolidFireUtil.SolidFireAccount sfAccount) { + AccountDetailVO accountDetails = new AccountDetailVO(csAccountId, + SolidFireUtil.ACCOUNT_ID, + String.valueOf(sfAccount.getId())); + + _accountDetailsDao.persist(accountDetails); + + accountDetails = new AccountDetailVO(csAccountId, + SolidFireUtil.CHAP_INITIATOR_USERNAME, + String.valueOf(sfAccount.getName())); + + _accountDetailsDao.persist(accountDetails); + + accountDetails = new AccountDetailVO(csAccountId, + SolidFireUtil.CHAP_INITIATOR_SECRET, + String.valueOf(sfAccount.getInitiatorSecret())); + + _accountDetailsDao.persist(accountDetails); + + accountDetails = new AccountDetailVO(csAccountId, + SolidFireUtil.CHAP_TARGET_USERNAME, + sfAccount.getName()); + + _accountDetailsDao.persist(accountDetails); + + accountDetails = new AccountDetailVO(csAccountId, + SolidFireUtil.CHAP_TARGET_SECRET, + sfAccount.getTargetSecret()); + + _accountDetailsDao.persist(accountDetails); + } + + private class ChapInfoImpl implements ChapInfo { + private final String _initiatorUsername; + private final String _initiatorSecret; + private final String _targetUsername; + private final String _targetSecret; + + public ChapInfoImpl(String initiatorUsername, String initiatorSecret, + String targetUsername, String targetSecret) { + _initiatorUsername = initiatorUsername; + _initiatorSecret = initiatorSecret; + _targetUsername = targetUsername; + _targetSecret = targetSecret; + } + + public String getInitiatorUsername() { + return _initiatorUsername; + } + + public String getInitiatorSecret() { + return _initiatorSecret; + } + + public String getTargetUsername() { + return _targetUsername; + } + + public String getTargetSecret() { + return _targetSecret; + } } @Override - public void deleteAsync(DataObject data, AsyncCompletionCallback callback) { + public ChapInfo getChapInfo(VolumeInfo volumeInfo) { + long accountId = volumeInfo.getAccountId(); + + AccountDetailVO accountDetail = _accountDetailsDao.findDetail(accountId, SolidFireUtil.CHAP_INITIATOR_USERNAME); + + String chapInitiatorUsername = accountDetail.getValue(); + + accountDetail = _accountDetailsDao.findDetail(accountId, SolidFireUtil.CHAP_INITIATOR_SECRET); + + String chapInitiatorSecret = accountDetail.getValue(); + + accountDetail = _accountDetailsDao.findDetail(accountId, SolidFireUtil.CHAP_TARGET_USERNAME); + + String chapTargetUsername = accountDetail.getValue(); + + accountDetail = _accountDetailsDao.findDetail(accountId, SolidFireUtil.CHAP_TARGET_SECRET); + + String chapTargetSecret = accountDetail.getValue(); + + return new ChapInfoImpl(chapInitiatorUsername, chapInitiatorSecret, + chapTargetUsername, chapTargetSecret); + } + + private SolidFireUtil.SolidFireVolume createSolidFireVolume(VolumeInfo volumeInfo, SolidFireConnection sfConnection) + throws StorageUnavailableException, Exception + { + String mVip = sfConnection.getManagementVip(); + int mPort = sfConnection.getManagementPort(); + String clusterAdminUsername = sfConnection.getClusterAdminUsername(); + String clusterAdminPassword = sfConnection.getClusterAdminPassword(); + + AccountDetailVO accountDetail = _accountDetailsDao.findDetail(volumeInfo.getAccountId(), SolidFireUtil.ACCOUNT_ID); + long sfAccountId = Long.parseLong(accountDetail.getValue()); + + final Iops iops; + + Long minIops = volumeInfo.getMinIops(); + Long maxIops = volumeInfo.getMaxIops(); + + if (minIops == null || minIops <= 0 || + maxIops == null || maxIops <= 0) { + iops = new Iops(100, 15000); + } + else { + iops = new Iops(volumeInfo.getMinIops(), volumeInfo.getMaxIops()); + } + + long sfVolumeId = SolidFireUtil.createSolidFireVolume(mVip, mPort, clusterAdminUsername, clusterAdminPassword, + volumeInfo.getName(), sfAccountId, volumeInfo.getSize(), true, + iops.getMinIops(), iops.getMaxIops(), iops.getBurstIops()); + + return SolidFireUtil.getSolidFireVolume(mVip, mPort, clusterAdminUsername, clusterAdminPassword, sfVolumeId); + } + + private static class Iops + { + private final long _minIops; + private final long _maxIops; + private final long _burstIops; + + public Iops(long minIops, long maxIops) throws Exception + { + if (minIops <= 0 || maxIops <= 0) { + throw new Exception("The 'Min IOPS' and 'Max IOPS' values must be greater than 0."); + } + + if (minIops > maxIops) { + throw new Exception("The 'Min IOPS' value cannot exceed the 'Max IOPS' value."); + } + + _minIops = minIops; + _maxIops = maxIops; + + _burstIops = getBurstIops(_maxIops); + } + + public long getMinIops() + { + return _minIops; + } + + public long getMaxIops() + { + return _maxIops; + } + + public long getBurstIops() + { + return _burstIops; + } + + private static long getBurstIops(long maxIops) + { + return (long)(maxIops * 1.5); + } + } + + private void deleteSolidFireVolume(VolumeInfo volumeInfo, SolidFireConnection sfConnection) + throws StorageUnavailableException, Exception + { + Long storagePoolId = volumeInfo.getPoolId(); + + if (storagePoolId == null) { + return; // this volume was never assigned to a storage pool, so no SAN volume should exist for it + } + + String mVip = sfConnection.getManagementVip(); + int mPort = sfConnection.getManagementPort(); + String clusterAdminUsername = sfConnection.getClusterAdminUsername(); + String clusterAdminPassword = sfConnection.getClusterAdminPassword(); + + long sfVolumeId = Long.parseLong(volumeInfo.getFolder()); + + SolidFireUtil.deleteSolidFireVolume(mVip, mPort, clusterAdminUsername, clusterAdminPassword, sfVolumeId); + } + + private String getSfAccountName(String csAccountUuid, long csAccountId) { + return "CloudStack_" + csAccountUuid + "_" + getRandomNumber() + "_" + csAccountId; + } + + private static long getRandomNumber() + { + return Math.round(Math.random() * 1000000000); + } + + private boolean sfAccountExists(String sfAccountName, SolidFireConnection sfConnection) throws Exception { + String mVip = sfConnection.getManagementVip(); + int mPort = sfConnection.getManagementPort(); + String clusterAdminUsername = sfConnection.getClusterAdminUsername(); + String clusterAdminPassword = sfConnection.getClusterAdminPassword(); + + try { + SolidFireUtil.getSolidFireAccountByName(mVip, mPort, clusterAdminUsername, clusterAdminPassword, sfAccountName); + } + catch (Exception ex) { + return false; + } + + return true; + } + + @Override + public void createAsync(DataStore dataStore, DataObject dataObject, + AsyncCompletionCallback callback) { + String iqn = null; + String errMsg = null; + + if (dataObject.getType() == DataObjectType.VOLUME) { + try { + VolumeInfo volumeInfo = (VolumeInfo)dataObject; + AccountVO account = _accountDao.findById(volumeInfo.getAccountId()); + String sfAccountName = getSfAccountName(account.getUuid(), account.getAccountId()); + + long storagePoolId = dataStore.getId(); + SolidFireConnection sfConnection = getSolidFireConnection(storagePoolId); + + if (!sfAccountExists(sfAccountName, sfConnection)) { + SolidFireUtil.SolidFireAccount sfAccount = createSolidFireAccount(sfAccountName, + sfConnection); + + updateCsDbWithAccountInfo(account.getId(), sfAccount); + } + + SolidFireUtil.SolidFireVolume sfVolume = createSolidFireVolume(volumeInfo, sfConnection); + + iqn = sfVolume.getIqn(); + + VolumeVO volume = this._volumeDao.findById(volumeInfo.getId()); + + volume.set_iScsiName(iqn); + volume.setFolder(String.valueOf(sfVolume.getId())); + volume.setPoolType(StoragePoolType.IscsiLUN); + volume.setPoolId(storagePoolId); + + _volumeDao.update(volume.getId(), volume); + + StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId()); + + long capacityBytes = storagePool.getCapacityBytes(); + long usedBytes = storagePool.getUsedBytes(); + + usedBytes += volumeInfo.getSize(); + + if (usedBytes > capacityBytes) { + usedBytes = capacityBytes; + } + + storagePool.setUsedBytes(usedBytes); + + _storagePoolDao.update(storagePoolId, storagePool); + } catch (StorageUnavailableException e) { + s_logger.error("Failed to create volume (StorageUnavailableException)", e); + errMsg = e.toString(); + } catch (Exception e) { + s_logger.error("Failed to create volume (Exception)", e); + errMsg = e.toString(); + } + } + else { + errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to createAsync"; + } + + // path = iqn + // size is pulled from DataObject instance, if errMsg is null + CreateCmdResult result = new CreateCmdResult(iqn, new Answer(null, errMsg == null, errMsg)); + + result.setResult(errMsg); + + callback.complete(result); + } + + private void deleteSolidFireAccount(long sfAccountId, SolidFireConnection sfConnection) throws Exception { + String mVip = sfConnection.getManagementVip(); + int mPort = sfConnection.getManagementPort(); + String clusterAdminUsername = sfConnection.getClusterAdminUsername(); + String clusterAdminPassword = sfConnection.getClusterAdminPassword(); + + List sfVolumes = SolidFireUtil.getDeletedVolumes(mVip, mPort, + clusterAdminUsername, clusterAdminPassword); + + // if there are volumes for this account in the trash, delete them (so the account can be deleted) + if (sfVolumes != null) { + for (SolidFireUtil.SolidFireVolume sfVolume : sfVolumes) { + if (sfVolume.getAccountId() == sfAccountId) { + SolidFireUtil.purgeSolidFireVolume(mVip, mPort, clusterAdminUsername, clusterAdminPassword, sfVolume.getId()); + } + } + } + + SolidFireUtil.deleteSolidFireAccount(mVip, mPort, clusterAdminUsername, clusterAdminPassword, sfAccountId); + } + + private boolean sfAccountHasVolume(long sfAccountId, SolidFireConnection sfConnection) throws Exception { + String mVip = sfConnection.getManagementVip(); + int mPort = sfConnection.getManagementPort(); + String clusterAdminUsername = sfConnection.getClusterAdminUsername(); + String clusterAdminPassword = sfConnection.getClusterAdminPassword(); + + List sfVolumes = SolidFireUtil.getSolidFireVolumesForAccountId(mVip, mPort, + clusterAdminUsername, clusterAdminPassword, sfAccountId); + + if (sfVolumes != null) { + for (SolidFireUtil.SolidFireVolume sfVolume : sfVolumes) { + if (sfVolume.isActive()) { + return true; + } + } + } + + return false; + } + + @Override + public void deleteAsync(DataStore dataStore, DataObject dataObject, + AsyncCompletionCallback callback) { + String errMsg = null; + + if (dataObject.getType() == DataObjectType.VOLUME) { + try { + VolumeInfo volumeInfo = (VolumeInfo)dataObject; + AccountVO account = _accountDao.findById(volumeInfo.getAccountId()); + AccountDetailVO accountDetails = _accountDetailsDao.findDetail(account.getAccountId(), SolidFireUtil.ACCOUNT_ID); + long sfAccountId = Long.parseLong(accountDetails.getValue()); + + long storagePoolId = dataStore.getId(); + SolidFireConnection sfConnection = getSolidFireConnection(storagePoolId); + + deleteSolidFireVolume(volumeInfo, sfConnection); + + _volumeDao.deleteVolumesByInstance(volumeInfo.getId()); + + if (!sfAccountHasVolume(sfAccountId, sfConnection)) { + // delete the account from the SolidFire SAN + deleteSolidFireAccount(sfAccountId, sfConnection); + + // delete the info in the account_details table + // that's related to the SolidFire account + _accountDetailsDao.deleteDetails(account.getAccountId()); + } + + StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId); + + long usedBytes = storagePool.getUsedBytes(); + + usedBytes -= volumeInfo.getSize(); + + if (usedBytes < 0) { + usedBytes = 0; + } + + storagePool.setUsedBytes(usedBytes); + + _storagePoolDao.update(storagePoolId, storagePool); + } catch (StorageUnavailableException e) { + s_logger.error("Failed to create volume (StorageUnavailableException)", e); + errMsg = e.toString(); + } catch (Exception e) { + s_logger.error("Failed to create volume (Exception)", e); + errMsg = e.toString(); + } + } + else { + errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync"; + } + + CommandResult result = new CommandResult(); + + result.setResult(errMsg); + + callback.complete(result); } @Override @@ -62,5 +533,4 @@ public class SolidfirePrimaryDataStoreDriver implements PrimaryDataStoreDriver { @Override public void takeSnapshot(SnapshotInfo snapshot, AsyncCompletionCallback callback) { } - } diff --git a/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/lifecycle/SolidFirePrimaryDataStoreLifeCycle.java b/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/lifecycle/SolidFirePrimaryDataStoreLifeCycle.java new file mode 100644 index 00000000000..2e25cd531db --- /dev/null +++ b/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/lifecycle/SolidFirePrimaryDataStoreLifeCycle.java @@ -0,0 +1,274 @@ +/* + * 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. + */ +package org.apache.cloudstack.storage.datastore.lifecycle; + +import java.util.Map; +import java.util.StringTokenizer; + +import javax.inject.Inject; + +import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.HostScope; +import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCycle; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreParameters; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; +import org.apache.cloudstack.storage.datastore.util.SolidFireUtil; +import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper; + +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.agent.api.StoragePoolInfo; +import com.cloud.storage.StoragePoolAutomation; +import com.cloud.storage.Storage.StoragePoolType; +import com.cloud.utils.exception.CloudRuntimeException; + +public class SolidFirePrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCycle { + @Inject PrimaryDataStoreDao storagePoolDao; + @Inject PrimaryDataStoreHelper dataStoreHelper; + @Inject StoragePoolAutomation storagePoolAutomation; + @Inject StoragePoolDetailsDao storagePoolDetailsDao; + @Inject DataCenterDao zoneDao; + + private static final int DEFAULT_MANAGEMENT_PORT = 443; + private static final int DEFAULT_STORAGE_PORT = 3260; + + // invoked to add primary storage that is based on the SolidFire plug-in + @Override + public DataStore initialize(Map dsInfos) { + String url = (String)dsInfos.get("url"); + Long zoneId = (Long)dsInfos.get("zoneId"); + String storagePoolName = (String) dsInfos.get("name"); + String providerName = (String)dsInfos.get("providerName"); + Long capacityBytes = (Long)dsInfos.get("capacityBytes"); + Long capacityIops = (Long)dsInfos.get("capacityIops"); + String tags = (String)dsInfos.get("tags"); + Map details = (Map)dsInfos.get("details"); + + String storageVip = getStorageVip(url); + int storagePort = getStoragePort(url); + + DataCenterVO zone = zoneDao.findById(zoneId); + + String uuid = SolidFireUtil.PROVIDER_NAME + "_" + zone.getUuid() + "_" + storageVip; + + if (capacityBytes == null || capacityBytes <= 0) { + throw new IllegalArgumentException("'capacityBytes' must be present and greater than 0."); + } + + if (capacityIops == null || capacityIops <= 0) { + throw new IllegalArgumentException("'capacityIops' must be present and greater than 0."); + } + + PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters(); + + parameters.setHost(storageVip); + parameters.setPort(storagePort); + parameters.setPath(getModifiedUrl(url)); + parameters.setType(StoragePoolType.Iscsi); + parameters.setUuid(uuid); + parameters.setZoneId(zoneId); + parameters.setName(storagePoolName); + parameters.setProviderName(providerName); + parameters.setManaged(true); + parameters.setCapacityBytes(capacityBytes); + parameters.setUsedBytes(0); + parameters.setCapacityIops(capacityIops); + parameters.setHypervisorType(HypervisorType.Any); + parameters.setTags(tags); + parameters.setDetails(details); + + String managementVip = getManagementVip(url); + int managementPort = getManagementPort(url); + + details.put(SolidFireUtil.MANAGEMENT_VIP, managementVip); + details.put(SolidFireUtil.MANAGEMENT_PORT, String.valueOf(managementPort)); + + String clusterAdminUsername = getValue(SolidFireUtil.CLUSTER_ADMIN_USERNAME, url); + String clusterAdminPassword = getValue(SolidFireUtil.CLUSTER_ADMIN_PASSWORD, url); + + details.put(SolidFireUtil.CLUSTER_ADMIN_USERNAME, clusterAdminUsername); + details.put(SolidFireUtil.CLUSTER_ADMIN_PASSWORD, clusterAdminPassword); + + // this adds a row in the cloud.storage_pool table for this SolidFire cluster + return dataStoreHelper.createPrimaryDataStore(parameters); + } + + // remove the clusterAdmin and password key/value pairs + private String getModifiedUrl(String originalUrl) + { + StringBuilder sb = new StringBuilder(); + + String delimiter = ";"; + + StringTokenizer st = new StringTokenizer(originalUrl, delimiter); + + while (st.hasMoreElements()) { + String token = st.nextElement().toString(); + + if (!token.startsWith(SolidFireUtil.CLUSTER_ADMIN_USERNAME) && + !token.startsWith(SolidFireUtil.CLUSTER_ADMIN_PASSWORD)) { + sb.append(token).append(delimiter); + } + } + + String modifiedUrl = sb.toString(); + int lastIndexOf = modifiedUrl.lastIndexOf(delimiter); + + if (lastIndexOf == (modifiedUrl.length() - delimiter.length())) { + return modifiedUrl.substring(0, lastIndexOf); + } + + return modifiedUrl; + } + + private String getManagementVip(String url) + { + return getVip(SolidFireUtil.MANAGEMENT_VIP, url); + } + + private String getStorageVip(String url) + { + return getVip(SolidFireUtil.STORAGE_VIP, url); + } + + private int getManagementPort(String url) + { + return getPort(SolidFireUtil.MANAGEMENT_VIP, url, DEFAULT_MANAGEMENT_PORT); + } + + private int getStoragePort(String url) + { + return getPort(SolidFireUtil.STORAGE_VIP, url, DEFAULT_STORAGE_PORT); + } + + private String getVip(String keyToMatch, String url) + { + String delimiter = ":"; + + String storageVip = getValue(keyToMatch, url); + + int index = storageVip.indexOf(delimiter); + + if (index != -1) + { + return storageVip.substring(0, index); + } + + return storageVip; + } + + private int getPort(String keyToMatch, String url, int defaultPortNumber) + { + String delimiter = ":"; + + String storageVip = getValue(keyToMatch, url); + + int index = storageVip.indexOf(delimiter); + + int portNumber = defaultPortNumber; + + if (index != -1) { + String port = storageVip.substring(index + delimiter.length()); + + try { + portNumber = Integer.parseInt(port); + } + catch (NumberFormatException ex) { + throw new IllegalArgumentException("Invalid URL format (port is not an integer)"); + } + } + + return portNumber; + } + + private String getValue(String keyToMatch, String url) + { + String delimiter1 = ";"; + String delimiter2 = "="; + + StringTokenizer st = new StringTokenizer(url, delimiter1); + + while (st.hasMoreElements()) { + String token = st.nextElement().toString(); + + int index = token.indexOf(delimiter2); + + if (index == -1) + { + throw new RuntimeException("Invalid URL format"); + } + + String key = token.substring(0, index); + + if (key.equalsIgnoreCase(keyToMatch)) { + String valueToReturn = token.substring(index + delimiter2.length()); + + return valueToReturn; + } + } + + throw new RuntimeException("Key not found in URL"); + } + + // do not implement this method for SolidFire's plug-in + @Override + public boolean attachHost(DataStore store, HostScope scope, StoragePoolInfo existingInfo) { + return true; // should be ignored for zone-wide-only plug-ins like SolidFire's + } + + // do not implement this method for SolidFire's plug-in + @Override + public boolean attachCluster(DataStore store, ClusterScope scope) { + return true; // should be ignored for zone-wide-only plug-ins like SolidFire's + } + + @Override + public boolean attachZone(DataStore dataStore, ZoneScope scope, HypervisorType hypervisorType) { + dataStoreHelper.attachZone(dataStore); + + return true; + } + + + @Override + public boolean maintain(DataStore dataStore) { + storagePoolAutomation.maintain(dataStore); + dataStoreHelper.maintain(dataStore); + + return true; + } + + @Override + public boolean cancelMaintain(DataStore store) { + dataStoreHelper.cancelMaintain(store); + storagePoolAutomation.cancelMaintain(store); + + return true; + } + + // invoked to delete primary storage that is based on the SolidFire plug-in + @Override + public boolean deleteDataStore(DataStore store) { + return dataStoreHelper.deletePrimaryDataStore(store); + } +} diff --git a/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/provider/SolidfirePrimaryDataStoreProvider.java b/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/provider/SolidfirePrimaryDataStoreProvider.java index 2965e8ff58e..28864ea801e 100644 --- a/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/provider/SolidfirePrimaryDataStoreProvider.java +++ b/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/provider/SolidfirePrimaryDataStoreProvider.java @@ -1,62 +1,91 @@ -// 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. +/* + * 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. + */ package org.apache.cloudstack.storage.datastore.provider; import java.util.Map; import java.util.Set; +import java.util.HashSet; -import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreLifeCycle; import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreProvider; +import org.apache.cloudstack.storage.datastore.driver.SolidfirePrimaryDataStoreDriver; +import org.apache.cloudstack.storage.datastore.lifecycle.SolidFirePrimaryDataStoreLifeCycle; +import org.apache.cloudstack.storage.datastore.util.SolidFireUtil; import org.springframework.stereotype.Component; +import com.cloud.utils.component.ComponentContext; + @Component public class SolidfirePrimaryDataStoreProvider implements PrimaryDataStoreProvider { - private final String name = "Solidfire Primary Data Store Provider"; + protected DataStoreLifeCycle lifecycle; + protected PrimaryDataStoreDriver driver; + protected HypervisorHostListener listener; + + SolidfirePrimaryDataStoreProvider() { + + } @Override public String getName() { - return name; + return SolidFireUtil.PROVIDER_NAME; } @Override public DataStoreLifeCycle getDataStoreLifeCycle() { - return null; + return lifecycle; } @Override - public DataStoreDriver getDataStoreDriver() { - return null; + public PrimaryDataStoreDriver getDataStoreDriver() { + return driver; } @Override public HypervisorHostListener getHostListener() { - return null; + return listener; } @Override public boolean configure(Map params) { - return false; + lifecycle = ComponentContext.inject(SolidFirePrimaryDataStoreLifeCycle.class); + driver = ComponentContext.inject(SolidfirePrimaryDataStoreDriver.class); + listener = ComponentContext.inject(new HypervisorHostListener() { + public boolean hostConnect(long hostId, long poolId) { + return true; + } + + public boolean hostDisconnected(long hostId, long poolId) { + return true; + } + }); + + return true; } @Override public Set getTypes() { - return null; - } + Set types = new HashSet(); + types.add(DataStoreProviderType.PRIMARY); + + return types; + } } diff --git a/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/util/SolidFireUtil.java b/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/util/SolidFireUtil.java new file mode 100644 index 00000000000..839c5a5e297 --- /dev/null +++ b/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/util/SolidFireUtil.java @@ -0,0 +1,901 @@ +package org.apache.cloudstack.storage.datastore.util; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URI; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.ArrayList; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.conn.BasicClientConnectionManager; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +public class SolidFireUtil +{ + public static final String PROVIDER_NAME = "SolidFire"; + + public static final String MANAGEMENT_VIP = "mVip"; + public static final String STORAGE_VIP = "sVip"; + + public static final String MANAGEMENT_PORT = "mPort"; + public static final String STORAGE_PORT = "sPort"; + + public static final String CLUSTER_ADMIN_USERNAME = "clusterAdminUsername"; + public static final String CLUSTER_ADMIN_PASSWORD = "clusterAdminPassword"; + + public static final String ACCOUNT_ID = "accountId"; + + public static final String CHAP_INITIATOR_USERNAME = "chapInitiatorUsername"; + public static final String CHAP_INITIATOR_SECRET = "chapInitiatorSecret"; + + public static final String CHAP_TARGET_USERNAME = "chapTargetUsername"; + public static final String CHAP_TARGET_SECRET = "chapTargetSecret"; + + public static long createSolidFireVolume(String strSfMvip, int iSfPort, String strSfAdmin, String strSfPassword, + String strSfVolumeName, long lSfAccountId, long lTotalSize, boolean bEnable512e, + long lMinIops, long lMaxIops, long lBurstIops) throws Exception + { + final Gson gson = new GsonBuilder().create(); + + VolumeToCreate volumeToCreate = new VolumeToCreate(strSfVolumeName, lSfAccountId, lTotalSize, bEnable512e, + lMinIops, lMaxIops, lBurstIops); + + String strVolumeToCreateJson = gson.toJson(volumeToCreate); + + String strVolumeCreateResultJson = executeJsonRpc(strVolumeToCreateJson, strSfMvip, iSfPort, strSfAdmin, strSfPassword); + + VolumeCreateResult volumeCreateResult = gson.fromJson(strVolumeCreateResultJson, VolumeCreateResult.class); + + verifyResult(volumeCreateResult.result, strVolumeCreateResultJson, gson); + + return volumeCreateResult.result.volumeID; + } + + public static void deleteSolidFireVolume(String strSfMvip, int iSfPort, String strSfAdmin, String strSfPassword, long lVolumeId) throws Exception + { + final Gson gson = new GsonBuilder().create(); + + VolumeToDelete volumeToDelete = new VolumeToDelete(lVolumeId); + + String strVolumeToDeleteJson = gson.toJson(volumeToDelete); + + executeJsonRpc(strVolumeToDeleteJson, strSfMvip, iSfPort, strSfAdmin, strSfPassword); + } + + public static void purgeSolidFireVolume(String strSfMvip, int iSfPort, String strSfAdmin, String strSfPassword, long lVolumeId) throws Exception + { + final Gson gson = new GsonBuilder().create(); + + VolumeToPurge volumeToPurge = new VolumeToPurge(lVolumeId); + + String strVolumeToPurgeJson = gson.toJson(volumeToPurge); + + executeJsonRpc(strVolumeToPurgeJson, strSfMvip, iSfPort, strSfAdmin, strSfPassword); + } + + public static SolidFireVolume getSolidFireVolume(String strSfMvip, int iSfPort, String strSfAdmin, String strSfPassword, long lVolumeId) throws Exception + { + final Gson gson = new GsonBuilder().create(); + + VolumeToGet volumeToGet = new VolumeToGet(lVolumeId); + + String strVolumeToGetJson = gson.toJson(volumeToGet); + + String strVolumeGetResultJson = executeJsonRpc(strVolumeToGetJson, strSfMvip, iSfPort, strSfAdmin, strSfPassword); + + VolumeGetResult volumeGetResult = gson.fromJson(strVolumeGetResultJson, VolumeGetResult.class); + + verifyResult(volumeGetResult.result, strVolumeGetResultJson, gson); + + String strVolumeName = getVolumeName(volumeGetResult, lVolumeId); + String strVolumeIqn = getVolumeIqn(volumeGetResult, lVolumeId); + long lAccountId = getVolumeAccountId(volumeGetResult, lVolumeId); + String strVolumeStatus = getVolumeStatus(volumeGetResult, lVolumeId); + + return new SolidFireVolume(lVolumeId, strVolumeName, strVolumeIqn, lAccountId, strVolumeStatus); + } + + public static List getSolidFireVolumesForAccountId(String strSfMvip, int iSfPort, + String strSfAdmin, String strSfPassword, long lAccountId) throws Exception + { + final Gson gson = new GsonBuilder().create(); + + VolumesToGetForAccount volumesToGetForAccount = new VolumesToGetForAccount(lAccountId); + + String strVolumesToGetForAccountJson = gson.toJson(volumesToGetForAccount); + + String strVolumesGetForAccountResultJson = executeJsonRpc(strVolumesToGetForAccountJson, strSfMvip, iSfPort, + strSfAdmin, strSfPassword); + + VolumeGetResult volumeGetResult = gson.fromJson(strVolumesGetForAccountResultJson, VolumeGetResult.class); + + verifyResult(volumeGetResult.result, strVolumesGetForAccountResultJson, gson); + + List sfVolumes = new ArrayList(); + + for (VolumeGetResult.Result.Volume volume : volumeGetResult.result.volumes) { + sfVolumes.add(new SolidFireVolume(volume.volumeID, volume.name, volume.iqn, volume.accountID, volume.status)); + } + + return sfVolumes; + } + + private static final String ACTIVE = "active"; + + public static class SolidFireVolume + { + private final long _id; + private final String _name; + private final String _iqn; + private final long _accountId; + private final String _status; + + public SolidFireVolume(long id, String name, String iqn, + long accountId, String status) + { + _id = id; + _name = name; + _iqn = "/" + iqn + "/0"; + _accountId = accountId; + _status = status; + } + + public long getId() + { + return _id; + } + + public String getName() + { + return _name; + } + + public String getIqn() + { + return _iqn; + } + + public long getAccountId() + { + return _accountId; + } + + public boolean isActive() + { + return ACTIVE.equalsIgnoreCase(_status); + } + + @Override + public int hashCode() { + return (int)_id; + } + + @Override + public String toString() { + return _name; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SolidFireVolume)) { + return false; + } + + SolidFireVolume sfv = (SolidFireVolume)obj; + + if (_id == sfv._id && _name.equals(sfv._name) && + _iqn.equals(sfv._iqn) && isActive() == sfv.isActive()) { + return true; + } + + return false; + } + } + + public static long createSolidFireAccount(String strSfMvip, int iSfPort, String strSfAdmin, String strSfPassword, + String strAccountName) throws Exception + { + final Gson gson = new GsonBuilder().create(); + + AccountToAdd accountToAdd = new AccountToAdd(strAccountName); + + String strAccountAddJson = gson.toJson(accountToAdd); + + String strAccountAddResultJson = executeJsonRpc(strAccountAddJson, strSfMvip, iSfPort, strSfAdmin, strSfPassword); + + AccountAddResult accountAddResult = gson.fromJson(strAccountAddResultJson, AccountAddResult.class); + + verifyResult(accountAddResult.result, strAccountAddResultJson, gson); + + return accountAddResult.result.accountID; + } + + public static void deleteSolidFireAccount(String strSfMvip, int iSfPort, String strSfAdmin, String strSfPassword, + long lAccountId) throws Exception + { + final Gson gson = new GsonBuilder().create(); + + AccountToRemove accountToRemove = new AccountToRemove(lAccountId); + + String strAccountToRemoveJson = gson.toJson(accountToRemove); + + executeJsonRpc(strAccountToRemoveJson, strSfMvip, iSfPort, strSfAdmin, strSfPassword); + } + + public static SolidFireAccount getSolidFireAccountById(String strSfMvip, int iSfPort, String strSfAdmin, String strSfPassword, + long lSfAccountId) throws Exception + { + final Gson gson = new GsonBuilder().create(); + + AccountToGetById accountToGetById = new AccountToGetById(lSfAccountId); + + String strAccountToGetByIdJson = gson.toJson(accountToGetById); + + String strAccountGetByIdResultJson = executeJsonRpc(strAccountToGetByIdJson, strSfMvip, iSfPort, strSfAdmin, strSfPassword); + + AccountGetResult accountGetByIdResult = gson.fromJson(strAccountGetByIdResultJson, AccountGetResult.class); + + verifyResult(accountGetByIdResult.result, strAccountGetByIdResultJson, gson); + + String strSfAccountName = accountGetByIdResult.result.account.username; + String strSfAccountInitiatorSecret = accountGetByIdResult.result.account.initiatorSecret; + String strSfAccountTargetSecret = accountGetByIdResult.result.account.targetSecret; + + return new SolidFireAccount(lSfAccountId, strSfAccountName, strSfAccountInitiatorSecret, strSfAccountTargetSecret); + } + + public static SolidFireAccount getSolidFireAccountByName(String strSfMvip, int iSfPort, String strSfAdmin, String strSfPassword, + String strSfAccountName) throws Exception + { + final Gson gson = new GsonBuilder().create(); + + AccountToGetByName accountToGetByName = new AccountToGetByName(strSfAccountName); + + String strAccountToGetByNameJson = gson.toJson(accountToGetByName); + + String strAccountGetByNameResultJson = executeJsonRpc(strAccountToGetByNameJson, strSfMvip, iSfPort, strSfAdmin, strSfPassword); + + AccountGetResult accountGetByNameResult = gson.fromJson(strAccountGetByNameResultJson, AccountGetResult.class); + + verifyResult(accountGetByNameResult.result, strAccountGetByNameResultJson, gson); + + long lSfAccountId = accountGetByNameResult.result.account.accountID; + String strSfAccountInitiatorSecret = accountGetByNameResult.result.account.initiatorSecret; + String strSfAccountTargetSecret = accountGetByNameResult.result.account.targetSecret; + + return new SolidFireAccount(lSfAccountId, strSfAccountName, strSfAccountInitiatorSecret, strSfAccountTargetSecret); + } + + public static class SolidFireAccount + { + private final long _id; + private final String _name; + private final String _initiatorSecret; + private final String _targetSecret; + + public SolidFireAccount(long id, String name, String initiatorSecret, String targetSecret) + { + _id = id; + _name = name; + _initiatorSecret = initiatorSecret; + _targetSecret = targetSecret; + } + + public long getId() + { + return _id; + } + + public String getName() + { + return _name; + } + + public String getInitiatorSecret() + { + return _initiatorSecret; + } + + public String getTargetSecret() + { + return _targetSecret; + } + + @Override + public int hashCode() { + return (int)_id; + } + + @Override + public String toString() { + return _name; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SolidFireAccount)) { + return false; + } + + SolidFireAccount sfa = (SolidFireAccount)obj; + + if (_id == sfa._id && _name.equals(sfa._name) && + _initiatorSecret.equals(sfa._initiatorSecret) && + _targetSecret.equals(sfa._targetSecret)) { + return true; + } + + return false; + } + } + + public static List getDeletedVolumes(String strSfMvip, int iSfPort, String strSfAdmin, String strSfPassword) throws Exception + { + final Gson gson = new GsonBuilder().create(); + + ListDeletedVolumes listDeletedVolumes = new ListDeletedVolumes(); + + String strListDeletedVolumesJson = gson.toJson(listDeletedVolumes); + + String strListDeletedVolumesResultJson = executeJsonRpc(strListDeletedVolumesJson, strSfMvip, iSfPort, + strSfAdmin, strSfPassword); + + VolumeGetResult volumeGetResult = gson.fromJson(strListDeletedVolumesResultJson, VolumeGetResult.class); + + verifyResult(volumeGetResult.result, strListDeletedVolumesResultJson, gson); + + List deletedVolumes = new ArrayList (); + + for (VolumeGetResult.Result.Volume volume : volumeGetResult.result.volumes) { + deletedVolumes.add(new SolidFireVolume(volume.volumeID, volume.name, volume.iqn, volume.accountID, volume.status)); + } + + return deletedVolumes; + } + + public static long createSolidFireVag(String strSfMvip, int iSfPort, String strSfAdmin, String strSfPassword, String strVagName) throws Exception + { + final Gson gson = new GsonBuilder().create(); + + VagToCreate vagToCreate = new VagToCreate(strVagName); + + String strVagCreateJson = gson.toJson(vagToCreate); + + String strVagCreateResultJson = executeJsonRpc(strVagCreateJson, strSfMvip, iSfPort, strSfAdmin, strSfPassword); + + VagCreateResult vagCreateResult = gson.fromJson(strVagCreateResultJson, VagCreateResult.class); + + verifyResult(vagCreateResult.result, strVagCreateResultJson, gson); + + return vagCreateResult.result.volumeAccessGroupID; + } + + public static void deleteSolidFireVag(String strSfMvip, int iSfPort, String strSfAdmin, String strSfPassword, long lVagId) throws Exception + { + final Gson gson = new GsonBuilder().create(); + + VagToDelete vagToDelete = new VagToDelete(lVagId); + + String strVagToDeleteJson = gson.toJson(vagToDelete); + + executeJsonRpc(strVagToDeleteJson, strSfMvip, iSfPort, strSfAdmin, strSfPassword); + } + + @SuppressWarnings("unused") + private static final class VolumeToCreate + { + private final String method = "CreateVolume"; + private final VolumeToCreateParams params; + + private VolumeToCreate(final String strVolumeName, final long lAccountId, final long lTotalSize, + final boolean bEnable512e, final long lMinIOPS, final long lMaxIOPS, final long lBurstIOPS) + { + params = new VolumeToCreateParams(strVolumeName, lAccountId, lTotalSize, bEnable512e, + lMinIOPS, lMaxIOPS, lBurstIOPS); + } + + private static final class VolumeToCreateParams + { + private final String name; + private final long accountID; + private final long totalSize; + private final boolean enable512e; + private final VolumeToCreateParamsQoS qos; + + private VolumeToCreateParams(final String strVolumeName, final long lAccountId, final long lTotalSize, + final boolean bEnable512e, final long lMinIOPS, final long lMaxIOPS, final long lBurstIOPS) + { + name = strVolumeName; + accountID = lAccountId; + totalSize = lTotalSize; + enable512e = bEnable512e; + + qos = new VolumeToCreateParamsQoS(lMinIOPS, lMaxIOPS, lBurstIOPS); + } + + private static final class VolumeToCreateParamsQoS + { + private final long minIOPS; + private final long maxIOPS; + private final long burstIOPS; + + private VolumeToCreateParamsQoS(final long lMinIOPS, final long lMaxIOPS, final long lBurstIOPS) + { + minIOPS = lMinIOPS; + maxIOPS = lMaxIOPS; + burstIOPS = lBurstIOPS; + } + } + } + } + + @SuppressWarnings("unused") + private static final class VolumeToDelete + { + private final String method = "DeleteVolume"; + private final VolumeToDeleteParams params; + + private VolumeToDelete(final long lVolumeId) + { + params = new VolumeToDeleteParams(lVolumeId); + } + + private static final class VolumeToDeleteParams + { + private long volumeID; + + private VolumeToDeleteParams(final long lVolumeId) + { + volumeID = lVolumeId; + } + } + } + + @SuppressWarnings("unused") + private static final class ListDeletedVolumes + { + private final String method = "ListDeletedVolumes"; + } + + @SuppressWarnings("unused") + private static final class VolumeToPurge + { + private final String method = "PurgeDeletedVolume"; + private final VolumeToPurgeParams params; + + private VolumeToPurge(final long lVolumeId) + { + params = new VolumeToPurgeParams(lVolumeId); + } + + private static final class VolumeToPurgeParams + { + private long volumeID; + + private VolumeToPurgeParams(final long lVolumeId) + { + volumeID = lVolumeId; + } + } + } + + @SuppressWarnings("unused") + private static final class VolumeToGet + { + private final String method = "ListActiveVolumes"; + private final VolumeToGetParams params; + + private VolumeToGet(final long lVolumeId) + { + params = new VolumeToGetParams(lVolumeId); + } + + private static final class VolumeToGetParams + { + private final long startVolumeID; + private final long limit = 1; + + private VolumeToGetParams(final long lVolumeId) + { + startVolumeID = lVolumeId; + } + } + } + + @SuppressWarnings("unused") + private static final class VolumesToGetForAccount + { + private final String method = "ListVolumesForAccount"; + private final VolumesToGetForAccountParams params; + + private VolumesToGetForAccount(final long lAccountId) + { + params = new VolumesToGetForAccountParams(lAccountId); + } + + private static final class VolumesToGetForAccountParams + { + private final long accountID; + + private VolumesToGetForAccountParams(final long lAccountId) + { + accountID = lAccountId; + } + } + } + + @SuppressWarnings("unused") + private static final class AccountToAdd + { + private final String method = "AddAccount"; + private final AccountToAddParams params; + + private AccountToAdd(final String strAccountName) + { + params = new AccountToAddParams(strAccountName); + } + + private static final class AccountToAddParams + { + private final String username; + + private AccountToAddParams(final String strAccountName) + { + username = strAccountName; + } + } + } + + @SuppressWarnings("unused") + private static final class AccountToRemove + { + private final String method = "RemoveAccount"; + private final AccountToRemoveParams params; + + private AccountToRemove(final long lAccountId) + { + params = new AccountToRemoveParams(lAccountId); + } + + private static final class AccountToRemoveParams + { + private long accountID; + + private AccountToRemoveParams(final long lAccountId) + { + accountID = lAccountId; + } + } + } + + @SuppressWarnings("unused") + private static final class AccountToGetById + { + private final String method = "GetAccountByID"; + private final AccountToGetByIdParams params; + + private AccountToGetById(final long lAccountId) + { + params = new AccountToGetByIdParams(lAccountId); + } + + private static final class AccountToGetByIdParams + { + private final long accountID; + + private AccountToGetByIdParams(final long lAccountId) + { + accountID = lAccountId; + } + } + } + + @SuppressWarnings("unused") + private static final class AccountToGetByName + { + private final String method = "GetAccountName"; + private final AccountToGetByNameParams params; + + private AccountToGetByName(final String strUsername) + { + params = new AccountToGetByNameParams(strUsername); + } + + private static final class AccountToGetByNameParams + { + private final String username; + + private AccountToGetByNameParams(final String strUsername) + { + username = strUsername; + } + } + } + + @SuppressWarnings("unused") + private static final class VagToCreate + { + private final String method = "CreateVolumeAccessGroup"; + private final VagToCreateParams params; + + private VagToCreate(final String strVagName) + { + params = new VagToCreateParams(strVagName); + } + + private static final class VagToCreateParams + { + private final String name; + + private VagToCreateParams(final String strVagName) + { + name = strVagName; + } + } + } + + @SuppressWarnings("unused") + private static final class VagToDelete + { + private final String method = "DeleteVolumeAccessGroup"; + private final VagToDeleteParams params; + + private VagToDelete(final long lVagId) + { + params = new VagToDeleteParams(lVagId); + } + + private static final class VagToDeleteParams + { + private long volumeAccessGroupID; + + private VagToDeleteParams(final long lVagId) + { + volumeAccessGroupID = lVagId; + } + } + } + + private static final class VolumeCreateResult + { + private Result result; + + private static final class Result + { + private long volumeID; + } + } + + private static final class VolumeGetResult + { + private Result result; + + private static final class Result + { + private Volume[] volumes; + + private static final class Volume + { + private long volumeID; + private String name; + private String iqn; + private long accountID; + private String status; + } + } + } + + private static final class AccountAddResult + { + private Result result; + + private static final class Result + { + private long accountID; + } + } + + private static final class AccountGetResult + { + private Result result; + + private static final class Result + { + private Account account; + + private static final class Account + { + private long accountID; + private String username; + private String initiatorSecret; + private String targetSecret; + } + } + } + + private static final class VagCreateResult + { + private Result result; + + private static final class Result + { + private long volumeAccessGroupID; + } + } + + private static final class JsonError + { + private Error error; + + private static final class Error + { + private String message; + } + } + + private static DefaultHttpClient getHttpClient(int iPort) throws NoSuchAlgorithmException, KeyManagementException { + SSLContext sslContext = SSLContext.getInstance("SSL"); + X509TrustManager tm = new X509TrustManager() { + public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return null; + } + }; + + sslContext.init(null, new TrustManager[] { tm }, new SecureRandom()); + + SSLSocketFactory socketFactory = new SSLSocketFactory(sslContext, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + SchemeRegistry registry = new SchemeRegistry(); + + registry.register(new Scheme("https", iPort, socketFactory)); + + BasicClientConnectionManager mgr = new BasicClientConnectionManager(registry); + DefaultHttpClient client = new DefaultHttpClient(); + + return new DefaultHttpClient(mgr, client.getParams()); + } + + private static String executeJsonRpc(String strJsonToExecute, String strMvip, int iPort, + String strAdmin, String strPassword) throws Exception + { + DefaultHttpClient httpClient = null; + StringBuilder sb = new StringBuilder(); + + try + { + StringEntity input = new StringEntity(strJsonToExecute); + + input.setContentType("application/json"); + + httpClient = getHttpClient(iPort); + + URI uri = new URI("https://" + strMvip + ":" + iPort + "/json-rpc/1.0"); + AuthScope authScope = new AuthScope(uri.getHost(), uri.getPort(), AuthScope.ANY_SCHEME); + UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(strAdmin, strPassword); + + httpClient.getCredentialsProvider().setCredentials(authScope, credentials); + + HttpPost postRequest = new HttpPost(uri); + + postRequest.setEntity(input); + + HttpResponse response = httpClient.execute(postRequest); + + if (!isSuccess(response.getStatusLine().getStatusCode())) + { + throw new RuntimeException("Failed on JSON-RPC API call. HTTP error code = " + response.getStatusLine().getStatusCode()); + } + + BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); + + String strOutput; + + + while ((strOutput = br.readLine()) != null) + { + sb.append(strOutput); + } + } finally { + if (httpClient != null) { + try { + httpClient.getConnectionManager().shutdown(); + } catch (Throwable t) {} + } + } + + return sb.toString(); + } + + private static boolean isSuccess(int iCode) { + return iCode >= 200 && iCode < 300; + } + + private static void verifyResult(Object obj, String strJson, Gson gson) throws IllegalStateException + { + if (obj != null) + { + return; + } + + JsonError jsonError = gson.fromJson(strJson, JsonError.class); + + if (jsonError != null) + { + throw new IllegalStateException(jsonError.error.message); + } + + throw new IllegalStateException("Problem with the following JSON: " + strJson); + } + + private static String getVolumeName(VolumeGetResult volumeGetResult, long lVolumeId) throws Exception + { + if (volumeGetResult.result.volumes != null && volumeGetResult.result.volumes.length == 1 && + volumeGetResult.result.volumes[0].volumeID == lVolumeId) + { + return volumeGetResult.result.volumes[0].name; + } + + throw new Exception("Could not determine the name of the volume, " + + "but the volume was created with an ID of " + lVolumeId + "."); + } + + private static String getVolumeIqn(VolumeGetResult volumeGetResult, long lVolumeId) throws Exception + { + if (volumeGetResult.result.volumes != null && volumeGetResult.result.volumes.length == 1 && + volumeGetResult.result.volumes[0].volumeID == lVolumeId) + { + return volumeGetResult.result.volumes[0].iqn; + } + + throw new Exception("Could not determine the IQN of the volume, " + + "but the volume was created with an ID of " + lVolumeId + "."); + } + + private static long getVolumeAccountId(VolumeGetResult volumeGetResult, long lVolumeId) throws Exception + { + if (volumeGetResult.result.volumes != null && volumeGetResult.result.volumes.length == 1 && + volumeGetResult.result.volumes[0].volumeID == lVolumeId) + { + return volumeGetResult.result.volumes[0].accountID; + } + + throw new Exception("Could not determine the volume's account ID, " + + "but the volume was created with an ID of " + lVolumeId + "."); + } + + private static String getVolumeStatus(VolumeGetResult volumeGetResult, long lVolumeId) throws Exception + { + if (volumeGetResult.result.volumes != null && volumeGetResult.result.volumes.length == 1 && + volumeGetResult.result.volumes[0].volumeID == lVolumeId) + { + return volumeGetResult.result.volumes[0].status; + } + + throw new Exception("Could not determine the status of the volume, " + + "but the volume was created with an ID of " + lVolumeId + "."); + } +} diff --git a/server/src/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java b/server/src/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java index 7022ee60fbb..385ca3625dc 100644 --- a/server/src/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java +++ b/server/src/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java @@ -67,6 +67,8 @@ public class DiskOfferingJoinDaoImpl extends GenericDaoBase implem // Show the virtual size of the volume volResponse.setSize(volume.getSize()); + volResponse.setMinIops(volume.getMinIops()); + volResponse.setMaxIops(volume.getMaxIops()); + volResponse.setCreated(volume.getCreated()); volResponse.setState(volume.getState().toString()); if (volume.getState() == Volume.State.UploadOp) { diff --git a/server/src/com/cloud/api/query/vo/DiskOfferingJoinVO.java b/server/src/com/cloud/api/query/vo/DiskOfferingJoinVO.java index 2336a48ad14..58e8370644c 100644 --- a/server/src/com/cloud/api/query/vo/DiskOfferingJoinVO.java +++ b/server/src/com/cloud/api/query/vo/DiskOfferingJoinVO.java @@ -61,6 +61,15 @@ public class DiskOfferingJoinVO extends BaseViewVO implements InternalIdentity, @Column(name="customized") private boolean customized; + @Column(name="customized_iops") + private Boolean customizedIops; + + @Column(name="min_iops") + private Long minIops; + + @Column(name="max_iops") + private Long maxIops; + @Column(name="sort_key") int sortKey; @@ -179,6 +188,30 @@ public class DiskOfferingJoinVO extends BaseViewVO implements InternalIdentity, this.customized = customized; } + public Boolean isCustomizedIops() { + return customizedIops; + } + + public void setCustomizedIops(Boolean customizedIops) { + this.customizedIops = customizedIops; + } + + public Long getMinIops() { + return minIops; + } + + public void setMinIops(Long minIops) { + this.minIops = minIops; + } + + public Long getMaxIops() { + return maxIops; + } + + public void setMaxIops(Long maxIops) { + this.maxIops = maxIops; + } + public boolean isDisplayOffering() { return displayOffering; } diff --git a/server/src/com/cloud/api/query/vo/StoragePoolJoinVO.java b/server/src/com/cloud/api/query/vo/StoragePoolJoinVO.java index c0d5ee990a5..69f2204d0e3 100644 --- a/server/src/com/cloud/api/query/vo/StoragePoolJoinVO.java +++ b/server/src/com/cloud/api/query/vo/StoragePoolJoinVO.java @@ -60,7 +60,6 @@ public class StoragePoolJoinVO extends BaseViewVO implements InternalIdentity, I @Column(name="host_address") private String hostAddress; - @Column(name="status") @Enumerated(value=EnumType.STRING) private StoragePoolStatus status; @@ -109,7 +108,6 @@ public class StoragePoolJoinVO extends BaseViewVO implements InternalIdentity, I @Column(name="pod_name") private String podName; - @Column(name="tag") private String tag; @@ -119,7 +117,6 @@ public class StoragePoolJoinVO extends BaseViewVO implements InternalIdentity, I @Column(name="disk_reserved_capacity") private long reservedCapacity; - @Column(name="job_id") private Long jobId; @@ -133,6 +130,8 @@ public class StoragePoolJoinVO extends BaseViewVO implements InternalIdentity, I @Enumerated(value = EnumType.STRING) private ScopeType scope; + @Column(name="capacity_iops") + private Long capacityIops; @Column(name = "hypervisor") @Enumerated(value = EnumType.STRING) @@ -243,6 +242,14 @@ public class StoragePoolJoinVO extends BaseViewVO implements InternalIdentity, I this.capacityBytes = capacityBytes; } + public Long getCapacityIops() { + return capacityIops; + } + + public void setCapacityIops(Long capacityIops) { + this.capacityIops = capacityIops; + } + public long getClusterId() { return clusterId; } diff --git a/server/src/com/cloud/api/query/vo/VolumeJoinVO.java b/server/src/com/cloud/api/query/vo/VolumeJoinVO.java index 1f07f52973a..701e195461d 100644 --- a/server/src/com/cloud/api/query/vo/VolumeJoinVO.java +++ b/server/src/com/cloud/api/query/vo/VolumeJoinVO.java @@ -58,6 +58,12 @@ public class VolumeJoinVO extends BaseViewVO implements ControlledViewEntity { @Column(name = "size") long size; + @Column(name = "min_iops") + Long minIops; + + @Column(name = "max_iops") + Long maxIops; + @Column(name = "state") @Enumerated(value = EnumType.STRING) private Volume.State state; @@ -337,14 +343,27 @@ public class VolumeJoinVO extends BaseViewVO implements ControlledViewEntity { this.size = size; } + public Long getMinIops() { + return minIops; + } + public void setMinIops(Long minIops) { + this.minIops = minIops; + } + + public Long getMaxIops() { + return maxIops; + } + + public void setMaxIops(Long maxIops) { + this.maxIops = maxIops; + } public Volume.State getState() { return state; } - public void setState(Volume.State state) { this.state = state; } diff --git a/server/src/com/cloud/configuration/ConfigurationManager.java b/server/src/com/cloud/configuration/ConfigurationManager.java index 98eae37cde1..1b99b63afdf 100755 --- a/server/src/com/cloud/configuration/ConfigurationManager.java +++ b/server/src/com/cloud/configuration/ConfigurationManager.java @@ -102,14 +102,18 @@ public interface ConfigurationManager extends ConfigurationService, Manager { * @param isCustomized * @param localStorageRequired * @param isDisplayOfferingEnabled + * @param isCustomizedIops (is admin allowing users to set custom iops?) + * @param minIops + * @param maxIops * @param bytesReadRate * @param bytesWriteRate * @param iopsReadRate * @param iopsWriteRate * @return newly created disk offering */ - DiskOfferingVO createDiskOffering(Long domainId, String name, String description, Long numGibibytes, String tags, boolean isCustomized, boolean localStorageRequired, boolean isDisplayOfferingEnabled, - Long bytesReadRate, Long bytesWriteRate, Long iopsReadRate, Long iopsWriteRate); + DiskOfferingVO createDiskOffering(Long domainId, String name, String description, Long numGibibytes, String tags, boolean isCustomized, + boolean localStorageRequired, boolean isDisplayOfferingEnabled, Boolean isCustomizedIops, Long minIops, Long maxIops, + Long bytesReadRate, Long bytesWriteRate, Long iopsReadRate, Long iopsWriteRate); /** * Creates a new pod diff --git a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java index 3840c120630..2089f82dfe9 100755 --- a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java @@ -2297,8 +2297,9 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Override @ActionEvent(eventType = EventTypes.EVENT_DISK_OFFERING_CREATE, eventDescription = "creating disk offering") - public DiskOfferingVO createDiskOffering(Long domainId, String name, String description, Long numGibibytes, String tags, boolean isCustomized, boolean localStorageRequired, boolean isDisplayOfferingEnabled, - Long bytesReadRate, Long bytesWriteRate, Long iopsReadRate, Long iopsWriteRate) { + public DiskOfferingVO createDiskOffering(Long domainId, String name, String description, Long numGibibytes, String tags, boolean isCustomized, + boolean localStorageRequired, boolean isDisplayOfferingEnabled, Boolean isCustomizedIops, Long minIops, Long maxIops, + Long bytesReadRate, Long bytesWriteRate, Long iopsReadRate, Long iopsWriteRate) { 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."); @@ -2314,8 +2315,44 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati isCustomized = true; } + if (isCustomizedIops != null) { + bytesReadRate = null; + bytesWriteRate = null; + iopsReadRate = null; + iopsWriteRate = null; + + if (isCustomizedIops) { + minIops = null; + maxIops = null; + } + else { + if (minIops == null && maxIops == null) { + minIops = 0L; + maxIops = 0L; + } + else { + if (minIops == null || minIops <= 0) { + throw new InvalidParameterValueException("The min IOPS must be greater than 0."); + } + + if (maxIops == null) { + maxIops = 0L; + } + + if (minIops > maxIops) { + throw new InvalidParameterValueException("The min IOPS must be less than or equal to the max IOPS."); + } + } + } + } + else { + minIops = null; + maxIops = null; + } + tags = cleanupTags(tags); - DiskOfferingVO newDiskOffering = new DiskOfferingVO(domainId, name, description, diskSize, tags, isCustomized); + DiskOfferingVO newDiskOffering = new DiskOfferingVO(domainId, name, description, diskSize, tags, isCustomized, + isCustomizedIops, minIops, maxIops); newDiskOffering.setUseLocalStorage(localStorageRequired); newDiskOffering.setDisplayOffering(isDisplayOfferingEnabled); @@ -2355,7 +2392,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati Long domainId = cmd.getDomainId(); if (!isCustomized && numGibibytes == null) { - throw new InvalidParameterValueException("Disksize is required for non-customized disk offering"); + throw new InvalidParameterValueException("Disksize is required for a non-customized disk offering"); } boolean localStorageRequired = false; @@ -2369,11 +2406,17 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } } + Boolean isCustomizedIops = cmd.isCustomizedIops(); + Long minIops = cmd.getMinIops(); + Long maxIops = cmd.getMaxIops(); Long bytesReadRate = cmd.getBytesReadRate(); Long bytesWriteRate = cmd.getBytesWriteRate(); Long iopsReadRate = cmd.getIopsReadRate(); Long iopsWriteRate = cmd.getIopsWriteRate(); - return createDiskOffering(domainId, name, description, numGibibytes, tags, isCustomized, localStorageRequired, isDisplayOfferingEnabled, bytesReadRate, bytesWriteRate, iopsReadRate, iopsWriteRate); + + return createDiskOffering(domainId, name, description, numGibibytes, tags, isCustomized, + localStorageRequired, isDisplayOfferingEnabled, isCustomizedIops, minIops, maxIops, + bytesReadRate, bytesWriteRate, iopsReadRate, iopsWriteRate); } @Override diff --git a/server/src/com/cloud/server/ConfigurationServerImpl.java b/server/src/com/cloud/server/ConfigurationServerImpl.java index 1ddfcfaaafb..9e79b762e1c 100755 --- a/server/src/com/cloud/server/ConfigurationServerImpl.java +++ b/server/src/com/cloud/server/ConfigurationServerImpl.java @@ -932,7 +932,7 @@ public class ConfigurationServerImpl extends ManagerBase implements Configuratio diskSize = diskSize * 1024 * 1024 * 1024; tags = cleanupTags(tags); - DiskOfferingVO newDiskOffering = new DiskOfferingVO(domainId, name, description, diskSize, tags, isCustomized); + DiskOfferingVO newDiskOffering = new DiskOfferingVO(domainId, name, description, diskSize, tags, isCustomized, null, null, null); newDiskOffering.setUniqueName("Cloud.Com-" + name); newDiskOffering.setSystemUse(isSystemUse); newDiskOffering = _diskOfferingDao.persistDeafultDiskOffering(newDiskOffering); diff --git a/server/src/com/cloud/storage/StorageManager.java b/server/src/com/cloud/storage/StorageManager.java index 29c7ebcef00..df690929f25 100755 --- a/server/src/com/cloud/storage/StorageManager.java +++ b/server/src/com/cloud/storage/StorageManager.java @@ -99,28 +99,23 @@ public interface StorageManager extends StorageService { void cleanupSecondaryStorage(boolean recurring); - HypervisorType getHypervisorTypeFromFormat(ImageFormat format); + boolean storagePoolHasEnoughIops(List volume, StoragePool pool); + boolean storagePoolHasEnoughSpace(List volume, StoragePool pool); - boolean registerHostListener(String providerUuid, HypervisorHostListener listener); StoragePool findStoragePool(DiskProfile dskCh, DataCenterVO dc, HostPodVO pod, Long clusterId, Long hostId, VMInstanceVO vm, Set avoid); - void connectHostToSharedPool(long hostId, long poolId) throws StorageUnavailableException; void createCapacityEntry(long poolId); - - - - DataStore createLocalStorage(Host host, StoragePoolInfo poolInfo) throws ConnectionException; BigDecimal getStorageOverProvisioningFactor(Long dcId); diff --git a/server/src/com/cloud/storage/StorageManagerImpl.java b/server/src/com/cloud/storage/StorageManagerImpl.java index b3e8b96e745..241f6e627f6 100755 --- a/server/src/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/com/cloud/storage/StorageManagerImpl.java @@ -694,9 +694,10 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C throw new InvalidParameterValueException( "Missing parameter hypervisor. Hypervisor type is required to create zone wide primary storage."); } - if (hypervisorType != HypervisorType.KVM && hypervisorType != HypervisorType.VMware) { + if (hypervisorType != HypervisorType.KVM && hypervisorType != HypervisorType.VMware && + hypervisorType != HypervisorType.Any) { throw new InvalidParameterValueException( - "zone wide storage pool is not suported for hypervisor type " + hypervisor); + "zone wide storage pool is not supported for hypervisor type " + hypervisor); } } @@ -734,6 +735,9 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C params.put("name", cmd.getStoragePoolName()); params.put("details", details); params.put("providerName", storeProvider.getName()); + params.put("managed", cmd.isManaged()); + params.put("capacityBytes", cmd.getCapacityBytes()); + params.put("capacityIops", cmd.getCapacityIops()); DataStoreLifeCycle lifeCycle = storeProvider.getDataStoreLifeCycle(); DataStore store = null; @@ -1561,7 +1565,41 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C } @Override - public boolean storagePoolHasEnoughSpace(List volumes, StoragePool pool) { + public boolean storagePoolHasEnoughIops(List requestedVolumes, + StoragePool pool) { + if (requestedVolumes == null || requestedVolumes.isEmpty() || pool == null) + return false; + + long currentIops = 0; + + List volumesInPool = _volumeDao.findByPoolId(pool.getId(), null); + + for (VolumeVO volumeInPool : volumesInPool) { + Long minIops = volumeInPool.getMinIops(); + + if (minIops != null && minIops > 0) { + currentIops += minIops; + } + } + + long requestedIops = 0; + + for (Volume requestedVolume : requestedVolumes) { + Long minIops = requestedVolume.getMinIops(); + + if (minIops != null && minIops > 0) { + requestedIops += minIops; + } + } + + long futureIops = currentIops + requestedIops; + + return futureIops <= pool.getCapacityIops(); + } + + @Override + public boolean storagePoolHasEnoughSpace(List volumes, + StoragePool pool) { if (volumes == null || volumes.isEmpty()) return false; diff --git a/server/src/com/cloud/storage/VolumeManager.java b/server/src/com/cloud/storage/VolumeManager.java index c84bb67e038..2e44a3c35b3 100644 --- a/server/src/com/cloud/storage/VolumeManager.java +++ b/server/src/com/cloud/storage/VolumeManager.java @@ -45,7 +45,6 @@ import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; public interface VolumeManager extends VolumeApiService { - VolumeInfo moveVolume(VolumeInfo volume, long destPoolDcId, Long destPoolPodId, Long destPoolClusterId, HypervisorType dataDiskHyperType) throws ConcurrentOperationException; diff --git a/server/src/com/cloud/storage/VolumeManagerImpl.java b/server/src/com/cloud/storage/VolumeManagerImpl.java index 4e7b3353ba0..a293da5c9a7 100644 --- a/server/src/com/cloud/storage/VolumeManagerImpl.java +++ b/server/src/com/cloud/storage/VolumeManagerImpl.java @@ -55,6 +55,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService; @@ -65,6 +66,7 @@ import org.apache.cloudstack.storage.command.AttachCommand; import org.apache.cloudstack.storage.command.CommandResult; import org.apache.cloudstack.storage.command.DettachCommand; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; @@ -227,6 +229,8 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { @Inject protected StoragePoolHostDao _storagePoolHostDao; @Inject + StoragePoolDetailsDao storagePoolDetailsDao; + @Inject protected AlertManager _alertMgr; @Inject protected TemplateDataStoreDao _vmTemplateStoreDao = null; @@ -507,7 +511,8 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { VolumeVO newVol = new VolumeVO(oldVol.getVolumeType(), oldVol.getName(), oldVol.getDataCenterId(), oldVol.getDomainId(), oldVol.getAccountId(), - oldVol.getDiskOfferingId(), oldVol.getSize()); + oldVol.getDiskOfferingId(), oldVol.getSize(), + oldVol.getMinIops(), oldVol.getMaxIops(), oldVol.get_iScsiName()); if (templateId != null) { newVol.setTemplateId(templateId); } else { @@ -680,9 +685,9 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { pool = storageMgr.findStoragePool(dskCh, dc, pod, clusterId, vm.getHostId(), vm, avoidPools); if (pool == null) { - s_logger.warn("Unable to find storage poll when create volume " + s_logger.warn("Unable to find storage pool when create volume " + volume.getName()); - throw new CloudRuntimeException("Unable to find storage poll when create volume" + volume.getName()); + throw new CloudRuntimeException("Unable to find storage pool when create volume" + volume.getName()); } if (s_logger.isDebugEnabled()) { @@ -731,8 +736,8 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { Transaction txn = Transaction.currentTxn(); txn.start(); - VolumeVO volume = new VolumeVO(volumeName, zoneId, -1L, -1L, -1, - new Long(-1), null, null, 0, Volume.Type.DATADISK); + VolumeVO volume = new VolumeVO(volumeName, zoneId, -1, -1, -1, + new Long(-1), null, null, 0, null, null, null, Volume.Type.DATADISK); volume.setPoolId(null); volume.setDataCenterId(zoneId); volume.setPodId(null); @@ -835,6 +840,8 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { Long diskOfferingId = null; DiskOfferingVO diskOffering = null; Long size = null; + Long minIops = null; + Long maxIops = null; // Volume VO used for extracting the source template id VolumeVO parentVolume = null; @@ -896,6 +903,37 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { size = diskOffering.getDiskSize(); } + Boolean isCustomizedIops = diskOffering.isCustomizedIops(); + + if (isCustomizedIops != null) { + if (isCustomizedIops) { + minIops = cmd.getMinIops(); + maxIops = cmd.getMaxIops(); + + if (minIops == null && maxIops == null) { + minIops = 0L; + maxIops = 0L; + } + else { + if (minIops == null || minIops <= 0) { + throw new InvalidParameterValueException("The min IOPS must be greater than 0."); + } + + if (maxIops == null) { + maxIops = 0L; + } + + if (minIops > maxIops) { + throw new InvalidParameterValueException("The min IOPS must be less than or equal to the max IOPS."); + } + } + } + else { + minIops = diskOffering.getMinIops(); + maxIops = diskOffering.getMaxIops(); + } + } + if (!validateVolumeSizeRange(size)) {// convert size from mb to gb // for validation throw new InvalidParameterValueException( @@ -970,8 +1008,8 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { Transaction txn = Transaction.currentTxn(); txn.start(); - VolumeVO volume = new VolumeVO(userSpecifiedName, -1L, -1L, -1, -1, - new Long(-1), null, null, 0, Volume.Type.DATADISK); + VolumeVO volume = new VolumeVO(userSpecifiedName, -1, -1, -1, -1, + new Long(-1), null, null, 0, null, null, null, Volume.Type.DATADISK); volume.setPoolId(null); volume.setDataCenterId(zoneId); volume.setPodId(null); @@ -980,6 +1018,8 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { .getDomainId())); volume.setDiskOfferingId(diskOfferingId); volume.setSize(size); + volume.setMinIops(minIops); + volume.setMaxIops(maxIops); volume.setInstanceId(null); volume.setUpdated(new Date()); volume.setDomainId((caller == null) ? Domain.ROOT_DOMAIN : caller @@ -1171,7 +1211,6 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { UserVmVO userVm = _userVmDao.findById(volume.getInstanceId()); - PrimaryDataStoreInfo pool = (PrimaryDataStoreInfo)dataStoreMgr.getDataStore(volume.getPoolId(), DataStoreRole.Primary); long currentSize = volume.getSize(); /* @@ -1358,7 +1397,8 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { size = (size * 1024 * 1024 * 1024); } VolumeVO vol = new VolumeVO(type, name, vm.getDataCenterId(), - owner.getDomainId(), owner.getId(), offering.getId(), size); + owner.getDomainId(), owner.getId(), offering.getId(), size, + offering.getMinIops(), offering.getMaxIops(), null); if (vm != null) { vol.setInstanceId(vm.getId()); } @@ -1398,7 +1438,8 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { Long size = _tmpltMgr.getTemplateSize(template.getId(), vm.getDataCenterId()); VolumeVO vol = new VolumeVO(type, name, vm.getDataCenterId(), - owner.getDomainId(), owner.getId(), offering.getId(), size); + owner.getDomainId(), owner.getId(), offering.getId(), size, + offering.getMinIops(), offering.getMaxIops(), null); vol.setFormat(this.getSupportedImageFormatForCluster(template.getHypervisorType())); if (vm != null) { vol.setInstanceId(vm.getId()); @@ -1542,8 +1583,8 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { return !storeForRootStoreScope.isSameScope(storeForDataStoreScope); } - private VolumeVO sendAttachVolumeCommand(UserVmVO vm, VolumeVO volume, Long deviceId) { - String errorMsg = "Failed to attach volume: " + volume.getName() + private VolumeVO sendAttachVolumeCommand(UserVmVO vm, VolumeVO volumeToAttach, Long deviceId) { + String errorMsg = "Failed to attach volume: " + volumeToAttach.getName() + " to VM: " + vm.getHostName(); boolean sendCommand = (vm.getState() == State.Running); AttachAnswer answer = null; @@ -1557,12 +1598,37 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { } } + StoragePoolVO volumeToAttachStoragePool = null; + if (sendCommand) { - DataTO volTO = volFactory.getVolume(volume.getId()).getTO(); - DiskTO disk = new DiskTO(volTO, deviceId, volume.getVolumeType()); + volumeToAttachStoragePool = _storagePoolDao.findById(volumeToAttach.getPoolId()); + long storagePoolId = volumeToAttachStoragePool.getId(); + + DataTO volTO = volFactory.getVolume(volumeToAttach.getId()).getTO(); + DiskTO disk = new DiskTO(volTO, deviceId, null, volumeToAttach.getVolumeType()); + AttachCommand cmd = new AttachCommand(disk, vm.getInstanceName()); + + cmd.setManaged(volumeToAttachStoragePool.isManaged()); + + cmd.setStorageHost(volumeToAttachStoragePool.getHostAddress()); + cmd.setStoragePort(volumeToAttachStoragePool.getPort()); + + cmd.set_iScsiName(volumeToAttach.get_iScsiName()); + + VolumeInfo volumeInfo = volFactory.getVolume(volumeToAttach.getId()); + DataStore dataStore = dataStoreMgr.getDataStore(storagePoolId, DataStoreRole.Primary); + ChapInfo chapInfo = volService.getChapInfo(volumeInfo, dataStore); + + if (chapInfo != null) { + cmd.setChapInitiatorUsername(chapInfo.getInitiatorUsername()); + cmd.setChapInitiatorPassword(chapInfo.getInitiatorSecret()); + cmd.setChapTargetUsername(chapInfo.getTargetUsername()); + cmd.setChapTargetPassword(chapInfo.getTargetSecret()); + } + try { - answer = (AttachAnswer) _agentMgr.send(hostId, cmd); + answer = (AttachAnswer)_agentMgr.send(hostId, cmd); } catch (Exception e) { throw new CloudRuntimeException(errorMsg + " due to: " + e.getMessage()); @@ -1573,19 +1639,29 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { // Mark the volume as attached if (sendCommand) { DiskTO disk = answer.getDisk(); - _volsDao.attachVolume(volume.getId(), vm.getId(), + _volsDao.attachVolume(volumeToAttach.getId(), vm.getId(), disk.getDiskSeq()); + + volumeToAttach = _volsDao.findById(volumeToAttach.getId()); + + if (volumeToAttachStoragePool.isManaged() && + volumeToAttach.getPath() == null) { + volumeToAttach.setPath(answer.getDisk().getVdiUuid()); + + _volsDao.update(volumeToAttach.getId(), volumeToAttach); + } } else { - _volsDao.attachVolume(volume.getId(), vm.getId(), deviceId); + _volsDao.attachVolume(volumeToAttach.getId(), vm.getId(), deviceId); } + // insert record for disk I/O statistics - VmDiskStatisticsVO diskstats = _vmDiskStatsDao.findBy(vm.getAccountId(), vm.getDataCenterId(),vm.getId(), volume.getId()); + VmDiskStatisticsVO diskstats = _vmDiskStatsDao.findBy(vm.getAccountId(), vm.getDataCenterId(),vm.getId(), volumeToAttach.getId()); if (diskstats == null) { - diskstats = new VmDiskStatisticsVO(vm.getAccountId(), vm.getDataCenterId(),vm.getId(), volume.getId()); + diskstats = new VmDiskStatisticsVO(vm.getAccountId(), vm.getDataCenterId(),vm.getId(), volumeToAttach.getId()); _vmDiskStatsDao.persist(diskstats); } - return _volsDao.findById(volume.getId()); + return _volsDao.findById(volumeToAttach.getId()); } else { if (answer != null) { String details = answer.getDetails(); @@ -1912,9 +1988,17 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { Answer answer = null; if (sendCommand) { + StoragePoolVO volumePool = _storagePoolDao.findById(volume.getPoolId()); + DataTO volTO = volFactory.getVolume(volume.getId()).getTO(); - DiskTO disk = new DiskTO(volTO, volume.getDeviceId(), volume.getVolumeType()); + DiskTO disk = new DiskTO(volTO, volume.getDeviceId(), null, volume.getVolumeType()); + DettachCommand cmd = new DettachCommand(disk, vm.getInstanceName()); + + cmd.setManaged(volumePool.isManaged()); + + cmd.set_iScsiName(volume.get_iScsiName()); + try { answer = _agentMgr.send(vm.getHostId(), cmd); } catch (Exception e) { @@ -1926,6 +2010,7 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { if (!sendCommand || (answer != null && answer.getResult())) { // Mark the volume as detached _volsDao.detachVolume(volume.getId()); + return _volsDao.findById(volumeId); } else { @@ -1940,11 +2025,6 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { } } - - - - - @DB protected VolumeVO switchVolume(VolumeVO existingVolume, VirtualMachineProfile vm) @@ -2232,7 +2312,7 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { for (VolumeVO vol : vols) { DataTO volTO = volFactory.getVolume(vol.getId()).getTO(); - DiskTO disk = new DiskTO(volTO, vol.getDeviceId(), vol.getVolumeType()); + DiskTO disk = new DiskTO(volTO, vol.getDeviceId(), null, vol.getVolumeType()); vm.addDisk(disk); } @@ -2240,7 +2320,7 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { UserVmVO userVM = (UserVmVO) vm.getVirtualMachine(); if (userVM.getIsoId() != null) { DataTO dataTO = tmplFactory.getTemplate(userVM.getIsoId(), DataStoreRole.Image, userVM.getDataCenterId()).getTO(); - DiskTO iso = new DiskTO(dataTO, 3L, Volume.Type.ISO); + DiskTO iso = new DiskTO(dataTO, 3L, null, Volume.Type.ISO); vm.addDisk(iso); } } @@ -2458,7 +2538,7 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { vol = result.first(); } DataTO volumeTO = volFactory.getVolume(vol.getId()).getTO(); - DiskTO disk = new DiskTO(volumeTO, vol.getDeviceId(), vol.getVolumeType()); + DiskTO disk = new DiskTO(volumeTO, vol.getDeviceId(), null, vol.getVolumeType()); vm.addDisk(disk); } } @@ -2745,7 +2825,6 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { @Override public String getVmNameFromVolumeId(long volumeId) { - Long instanceId; VolumeVO volume = _volsDao.findById(volumeId); return getVmNameOnVolume(volume); } diff --git a/server/src/com/cloud/template/TemplateManagerImpl.java b/server/src/com/cloud/template/TemplateManagerImpl.java index f70d44de65a..ca644af270c 100755 --- a/server/src/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/com/cloud/template/TemplateManagerImpl.java @@ -1023,7 +1023,7 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, } DataTO isoTO = tmplt.getTO(); - DiskTO disk = new DiskTO(isoTO, null, Volume.Type.ISO); + DiskTO disk = new DiskTO(isoTO, null, null, Volume.Type.ISO); Command cmd = null; if (attach) { cmd = new AttachCommand(disk, vmName); diff --git a/server/src/com/cloud/test/DatabaseConfig.java b/server/src/com/cloud/test/DatabaseConfig.java index ef0259d7c60..63f77b66520 100755 --- a/server/src/com/cloud/test/DatabaseConfig.java +++ b/server/src/com/cloud/test/DatabaseConfig.java @@ -979,7 +979,7 @@ public class DatabaseConfig { newTags.delete(newTags.length() - 1, newTags.length()); tags = newTags.toString(); } - DiskOfferingVO diskOffering = new DiskOfferingVO(domainId, name, displayText, diskSpace , tags, false); + DiskOfferingVO diskOffering = new DiskOfferingVO(domainId, name, displayText, diskSpace, tags, false, null, null, null); diskOffering.setUseLocalStorage(local); Long bytesReadRate = Long.parseLong(_currentObjectParams.get("bytesReadRate")); diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 3cef1827557..a59fa5b629d 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -2910,12 +2910,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use TemplateObjectTO iso = (TemplateObjectTO)template.getTO(); iso.setGuestOsType(displayName); - DiskTO disk = new DiskTO(iso, 3L, Volume.Type.ISO); + DiskTO disk = new DiskTO(iso, 3L, null, Volume.Type.ISO); profile.addDisk(disk); } else { TemplateObjectTO iso = new TemplateObjectTO(); iso.setFormat(ImageFormat.ISO); - DiskTO disk = new DiskTO(iso, 3L, Volume.Type.ISO); + DiskTO disk = new DiskTO(iso, 3L, null, Volume.Type.ISO); profile.addDisk(disk); } diff --git a/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java b/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java index 95230a5353b..7a61978f289 100755 --- a/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java +++ b/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java @@ -655,8 +655,9 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu * @see com.cloud.configuration.ConfigurationManager#createDiskOffering(java.lang.Long, java.lang.String, java.lang.String, java.lang.Long, java.lang.String, boolean, boolean, boolean) */ @Override - public DiskOfferingVO createDiskOffering(Long domainId, String name, String description, Long numGibibytes, String tags, boolean isCustomized, boolean localStorageRequired, boolean isDisplayOfferingEnabled, - Long bytesReadRate, Long bytesWriteRate, Long iopsReadRate, Long iopsWriteRate) { + public DiskOfferingVO createDiskOffering(Long domainId, String name, String description, Long numGibibytes, String tags, boolean isCustomized, + boolean localStorageRequired, boolean isDisplayOfferingEnabled, Boolean isCustomizedIops, Long minIops, Long maxIops, + Long bytesReadRate, Long bytesWriteRate, Long iopsReadRate, Long iopsWriteRate) { // TODO Auto-generated method stub return null; } diff --git a/setup/db/db/schema-410to420.sql b/setup/db/db/schema-410to420.sql index 2d0f8defa79..0c1d753486e 100644 --- a/setup/db/db/schema-410to420.sql +++ b/setup/db/db/schema-410to420.sql @@ -429,6 +429,20 @@ ALTER TABLE `cloud`.`nics` ADD COLUMN `display_nic` tinyint(1) NOT NULL DEFAULT ALTER TABLE `cloud`.`disk_offering` ADD COLUMN `display_offering` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Should disk offering be displayed to the end user'; +ALTER TABLE `cloud`.`disk_offering` ADD COLUMN `customized_iops` tinyint(1) unsigned COMMENT 'Should customized IOPS be displayed to the end user'; + +ALTER TABLE `cloud`.`disk_offering` ADD COLUMN `min_iops` bigint(20) unsigned COMMENT 'Minimum IOPS'; + +ALTER TABLE `cloud`.`disk_offering` ADD COLUMN `max_iops` bigint(20) unsigned COMMENT 'Maximum IOPS'; + +ALTER TABLE `cloud`.`volumes` ADD COLUMN `min_iops` bigint(20) unsigned COMMENT 'Minimum IOPS'; + +ALTER TABLE `cloud`.`volumes` ADD COLUMN `max_iops` bigint(20) unsigned COMMENT 'Maximum IOPS'; + +ALTER TABLE `cloud`.`storage_pool` ADD COLUMN `managed` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT 'Should CloudStack manage this storage'; + +ALTER TABLE `cloud`.`storage_pool` ADD COLUMN `capacity_iops` bigint(20) unsigned DEFAULT NULL COMMENT 'IOPS CloudStack can provision from this storage pool'; + ALTER TABLE `cloud`.`disk_offering` ADD COLUMN `bytes_read_rate` bigint(20); ALTER TABLE `cloud`.`disk_offering` ADD COLUMN `bytes_write_rate` bigint(20); @@ -871,6 +885,8 @@ CREATE VIEW `cloud`.`volume_view` AS volumes.device_id, volumes.volume_type, volumes.size, + volumes.min_iops, + volumes.max_iops, volumes.created, volumes.state, volumes.attached, @@ -981,6 +997,7 @@ CREATE VIEW `cloud`.`storage_pool_view` AS storage_pool.created, storage_pool.removed, storage_pool.capacity_bytes, + storage_pool.capacity_iops, storage_pool.scope, storage_pool.hypervisor, cluster.id cluster_id, @@ -1521,9 +1538,12 @@ CREATE VIEW `cloud`.`disk_offering_view` AS disk_offering.name, disk_offering.display_text, 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, @@ -1736,6 +1756,8 @@ CREATE VIEW `cloud`.`volume_view` AS volumes.device_id, volumes.volume_type, volumes.size, + volumes.min_iops, + volumes.max_iops, volumes.created, volumes.state, volumes.attached, diff --git a/tools/marvin/marvin/cloudstackConnection.py b/tools/marvin/marvin/cloudstackConnection.py index 9d60ff91026..8129396813a 100644 --- a/tools/marvin/marvin/cloudstackConnection.py +++ b/tools/marvin/marvin/cloudstackConnection.py @@ -203,7 +203,7 @@ class cloudConnection(object): i = i + 1 return cmdname, isAsync, requests - def marvin_request(self, cmd, response_type=None, method='GET'): + def marvin_request(self, cmd, response_type=None, method='GET', data=''): """ Requester for marvin command objects @param cmd: marvin's command from cloudstackAPI diff --git a/ui/dictionary.jsp b/ui/dictionary.jsp index 7809cdb6c9d..af64228ab04 100644 --- a/ui/dictionary.jsp +++ b/ui/dictionary.jsp @@ -25,6 +25,9 @@ under the License. <% long now = System.currentTimeMillis(); %>