You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by mt...@apache.org on 2014/07/18 05:30:28 UTC
git commit: updated refs/heads/master to b080eaf
Repository: cloudstack
Updated Branches:
refs/heads/master ef28fd367 -> b080eaf32
Updates to the way resizing a volume works
Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/b080eaf3
Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/b080eaf3
Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/b080eaf3
Branch: refs/heads/master
Commit: b080eaf3270ffae32f7809e5d492139f33603e89
Parents: ef28fd3
Author: Mike Tutkowski <mi...@solidfire.com>
Authored: Wed Jul 16 08:23:49 2014 -0600
Committer: Mike Tutkowski <mi...@solidfire.com>
Committed: Thu Jul 17 21:09:19 2014 -0600
----------------------------------------------------------------------
.../command/user/volume/ResizeVolumeCmd.java | 16 +-
.../subsystem/api/storage/VolumeService.java | 2 +
.../storage/volume/VolumeServiceImpl.java | 30 +++
.../driver/SolidFirePrimaryDataStoreDriver.java | 58 ++++-
.../SolidFirePrimaryDataStoreLifeCycle.java | 2 +-
.../com/cloud/network/NetworkServiceImpl.java | 4 +-
.../com/cloud/storage/ResizeVolumePayload.java | 6 +-
.../com/cloud/storage/VmWorkResizeVolume.java | 14 +-
.../com/cloud/storage/VolumeApiServiceImpl.java | 240 +++++++++++++------
utils/src/com/cloud/utils/StringUtils.java | 32 +++
10 files changed, 319 insertions(+), 85 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b080eaf3/api/src/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java b/api/src/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java
index 1cf4f29..49fd6ca 100644
--- a/api/src/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java
+++ b/api/src/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java
@@ -55,7 +55,13 @@ public class ResizeVolumeCmd extends BaseAsyncCmd {
@Parameter(name = ApiConstants.ID, entityType = VolumeResponse.class, required = true, type = CommandType.UUID, description = "the ID of the disk volume")
private Long id;
- @Parameter(name = ApiConstants.SIZE, type = CommandType.LONG, required = false, description = "New volume size in G")
+ @Parameter(name = ApiConstants.MIN_IOPS, type = CommandType.LONG, required = false, description = "New minimum number of IOPS")
+ private Long minIops;
+
+ @Parameter(name = ApiConstants.MAX_IOPS, type = CommandType.LONG, required = false, description = "New maximum number of IOPS")
+ private Long maxIops;
+
+ @Parameter(name = ApiConstants.SIZE, type = CommandType.LONG, required = false, description = "New volume size in GB")
private Long size;
@Parameter(name = ApiConstants.SHRINK_OK, type = CommandType.BOOLEAN, required = false, description = "Verify OK to Shrink")
@@ -81,6 +87,14 @@ public class ResizeVolumeCmd extends BaseAsyncCmd {
return getEntityId();
}
+ public Long getMinIops() {
+ return minIops;
+ }
+
+ public Long getMaxIops() {
+ return maxIops;
+ }
+
public Long getSize() {
return size;
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b080eaf3/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/VolumeService.java
----------------------------------------------------------------------
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 b6e6106..cadce56 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
@@ -96,6 +96,8 @@ public interface VolumeService {
AsyncCallFuture<VolumeApiResult> resize(VolumeInfo volume);
+ void resizeVolumeOnHypervisor(long volumeId, long newSize, long destHostId, String instanceName);
+
void handleVolumeSync(DataStore store);
SnapshotInfo takeSnapshot(VolumeInfo volume);
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b080eaf3/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java
----------------------------------------------------------------------
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 3a71147..3fc43ea 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
@@ -51,6 +51,7 @@ import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher;
import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
import org.apache.cloudstack.framework.async.AsyncRpcContext;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.storage.RemoteHostEndPoint;
import org.apache.cloudstack.storage.command.CommandResult;
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
import org.apache.cloudstack.storage.command.DeleteCommand;
@@ -65,6 +66,8 @@ import org.springframework.stereotype.Component;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.storage.ListVolumeAnswer;
import com.cloud.agent.api.storage.ListVolumeCommand;
+import com.cloud.agent.api.storage.ResizeVolumeCommand;
+import com.cloud.agent.api.to.StorageFilerTO;
import com.cloud.agent.api.to.VirtualMachineTO;
import com.cloud.alert.AlertManager;
import com.cloud.configuration.Config;
@@ -1280,6 +1283,33 @@ public class VolumeServiceImpl implements VolumeService {
return future;
}
+ @Override
+ public void resizeVolumeOnHypervisor(long volumeId, long newSize, long destHostId, String instanceName) {
+ final String errMsg = "Resize command failed";
+
+ try {
+ Answer answer = null;
+ Host destHost = _hostDao.findById(destHostId);
+ EndPoint ep = RemoteHostEndPoint.getHypervisorHostEndPoint(destHost);
+
+ if (ep != null) {
+ VolumeVO volume = _volumeDao.findById(volumeId);
+ PrimaryDataStore primaryDataStore = this.dataStoreMgr.getPrimaryDataStore(volume.getPoolId());
+ ResizeVolumeCommand resizeCmd = new ResizeVolumeCommand(volume.getPath(), new StorageFilerTO(primaryDataStore), volume.getSize(), newSize, true, instanceName);
+
+ answer = ep.sendMessage(resizeCmd);
+ } else {
+ throw new CloudRuntimeException("Could not find a remote endpoint to send command to. Check if host or SSVM is down.");
+ }
+
+ if (answer == null || !answer.getResult()) {
+ throw new CloudRuntimeException(answer != null ? answer.getDetails() : errMsg);
+ }
+ } catch (Exception e) {
+ throw new CloudRuntimeException(errMsg, e);
+ }
+ }
+
protected Void resizeVolumeCallback(AsyncCallbackDispatcher<VolumeServiceImpl, CreateCmdResult> callback, CreateVolumeContext<VolumeApiResult> context) {
CreateCmdResult result = callback.getResult();
AsyncCallFuture<VolumeApiResult> future = context.future;
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b080eaf3/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/driver/SolidFirePrimaryDataStoreDriver.java
----------------------------------------------------------------------
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 c06a728..5a23fcf 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
@@ -42,6 +42,7 @@ 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.capacity.CapacityManager;
import com.cloud.dc.ClusterDetailsVO;
import com.cloud.dc.ClusterDetailsDao;
import com.cloud.dc.dao.DataCenterDao;
@@ -49,6 +50,7 @@ import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.storage.Storage.StoragePoolType;
+import com.cloud.storage.ResizeVolumePayload;
import com.cloud.storage.StoragePool;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeVO;
@@ -57,10 +59,12 @@ import com.cloud.user.AccountDetailVO;
import com.cloud.user.AccountDetailsDao;
import com.cloud.user.AccountVO;
import com.cloud.user.dao.AccountDao;
+import com.cloud.utils.exception.CloudRuntimeException;
public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
@Inject private AccountDao _accountDao;
@Inject private AccountDetailsDao _accountDetailsDao;
+ @Inject private CapacityManager _capacityMgr;
@Inject private ClusterDetailsDao _clusterDetailsDao;
@Inject private DataCenterDao _zoneDao;
@Inject private HostDao _hostDao;
@@ -400,8 +404,58 @@ public class SolidFirePrimaryDataStoreDriver implements PrimaryDataStoreDriver {
}
@Override
- public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) {
- throw new UnsupportedOperationException();
+ public void resize(DataObject dataObject, AsyncCompletionCallback<CreateCmdResult> callback) {
+ String iqn = null;
+ String errMsg = null;
+
+ if (dataObject.getType() == DataObjectType.VOLUME) {
+ VolumeInfo volumeInfo = (VolumeInfo)dataObject;
+ iqn = volumeInfo.get_iScsiName();
+ long storagePoolId = volumeInfo.getPoolId();
+ long sfVolumeId = Long.parseLong(volumeInfo.getFolder());
+ ResizeVolumePayload payload = (ResizeVolumePayload)volumeInfo.getpayload();
+
+ SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, _storagePoolDetailsDao);
+ SolidFireUtil.SolidFireVolume sfVolume = SolidFireUtil.getSolidFireVolume(sfConnection, sfVolumeId);
+
+ verifySufficientIopsForStoragePool(storagePoolId, volumeInfo.getId(), payload.newMinIops);
+
+ SolidFireUtil.modifySolidFireVolume(sfConnection, sfVolumeId, sfVolume.getTotalSize(), payload.newMinIops, payload.newMaxIops,
+ getDefaultBurstIops(storagePoolId, payload.newMaxIops));
+
+ VolumeVO volume = _volumeDao.findById(sfVolumeId);
+
+ volume.setMinIops(payload.newMinIops);
+ volume.setMaxIops(payload.newMaxIops);
+
+ _volumeDao.update(volume.getId(), volume);
+ } else {
+ errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to resize";
+ }
+
+ CreateCmdResult result = new CreateCmdResult(iqn, new Answer(null, errMsg == null, errMsg));
+
+ result.setResult(errMsg);
+
+ callback.complete(result);
+ }
+
+ private void verifySufficientIopsForStoragePool(long storagePoolId, long volumeId, long newMinIops) {
+ StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
+ VolumeVO volume = _volumeDao.findById(volumeId);
+
+ long currentMinIops = volume.getMinIops();
+ long diffInMinIops = newMinIops - currentMinIops;
+
+ // if the desire is for more IOPS
+ if (diffInMinIops > 0) {
+ long usedIops = _capacityMgr.getUsedIops(storagePool);
+ long capacityIops = storagePool.getCapacityIops();
+
+ if (usedIops + diffInMinIops > capacityIops) {
+ throw new CloudRuntimeException("Insufficient number of IOPS available in this storage pool");
+ }
+ }
}
@Override
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b080eaf3/plugins/storage/volume/solidfire/src/org/apache/cloudstack/storage/datastore/lifecycle/SolidFirePrimaryDataStoreLifeCycle.java
----------------------------------------------------------------------
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
index c23db14..bc08704 100644
--- 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
@@ -53,7 +53,7 @@ import com.cloud.utils.exception.CloudRuntimeException;
public class SolidFirePrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCycle {
private static final Logger s_logger = Logger.getLogger(SolidFirePrimaryDataStoreLifeCycle.class);
- @Inject CapacityManager _capacityMgr;
+ @Inject private CapacityManager _capacityMgr;
@Inject private DataCenterDao zoneDao;
@Inject private PrimaryDataStoreDao storagePoolDao;
@Inject private PrimaryDataStoreHelper dataStoreHelper;
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b080eaf3/server/src/com/cloud/network/NetworkServiceImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/network/NetworkServiceImpl.java b/server/src/com/cloud/network/NetworkServiceImpl.java
index c8105e8..c5d7134 100755
--- a/server/src/com/cloud/network/NetworkServiceImpl.java
+++ b/server/src/com/cloud/network/NetworkServiceImpl.java
@@ -158,6 +158,7 @@ import com.cloud.user.dao.UserDao;
import com.cloud.utils.Journal;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
+import com.cloud.utils.StringUtils;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.EntityManager;
@@ -2403,7 +2404,8 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService {
s_logger.debug("New network offering id=" + newNetworkOfferingId + " has tags and old network offering id=" + oldNetworkOfferingId + " doesn't, can't upgrade");
return false;
}
- if (!oldNetworkOffering.getTags().equalsIgnoreCase(newNetworkOffering.getTags())) {
+
+ if (!StringUtils.areTagsEqual(oldNetworkOffering.getTags(), newNetworkOffering.getTags())) {
s_logger.debug("Network offerings " + newNetworkOffering.getUuid() + " and " + oldNetworkOffering.getUuid() + " have different tags, can't upgrade");
return false;
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b080eaf3/server/src/com/cloud/storage/ResizeVolumePayload.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/storage/ResizeVolumePayload.java b/server/src/com/cloud/storage/ResizeVolumePayload.java
index 55dc661..7a927b2 100644
--- a/server/src/com/cloud/storage/ResizeVolumePayload.java
+++ b/server/src/com/cloud/storage/ResizeVolumePayload.java
@@ -19,12 +19,16 @@ package com.cloud.storage;
public class ResizeVolumePayload {
public final Long newSize;
+ public final Long newMinIops;
+ public final Long newMaxIops;
public final boolean shrinkOk;
public final String instanceName;
public final long[] hosts;
- public ResizeVolumePayload(Long newSize, boolean shrinkOk, String instanceName, long[] hosts) {
+ public ResizeVolumePayload(Long newSize, Long newMinIops, Long newMaxIops, boolean shrinkOk, String instanceName, long[] hosts) {
this.newSize = newSize;
+ this.newMinIops = newMinIops;
+ this.newMaxIops = newMaxIops;
this.shrinkOk = shrinkOk;
this.instanceName = instanceName;
this.hosts = hosts;
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b080eaf3/server/src/com/cloud/storage/VmWorkResizeVolume.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/storage/VmWorkResizeVolume.java b/server/src/com/cloud/storage/VmWorkResizeVolume.java
index 3ccaecd..1caab10 100644
--- a/server/src/com/cloud/storage/VmWorkResizeVolume.java
+++ b/server/src/com/cloud/storage/VmWorkResizeVolume.java
@@ -24,17 +24,21 @@ public class VmWorkResizeVolume extends VmWork {
private long volumeId;
private long currentSize;
private long newSize;
+ private Long newMinIops;
+ private Long newMaxIops;
private Long newServiceOfferingId;
private boolean shrinkOk;
public VmWorkResizeVolume(long userId, long accountId, long vmId, String handlerName,
- long volumeId, long currentSize, long newSize, Long newServiceOfferingId, boolean shrinkOk) {
+ long volumeId, long currentSize, long newSize, Long newMinIops, Long newMaxIops, Long newServiceOfferingId, boolean shrinkOk) {
super(userId, accountId, vmId, handlerName);
this.volumeId = volumeId;
this.currentSize = currentSize;
this.newSize = newSize;
+ this.newMinIops = newMinIops;
+ this.newMaxIops = newMaxIops;
this.newServiceOfferingId = newServiceOfferingId;
this.shrinkOk = shrinkOk;
}
@@ -51,6 +55,14 @@ public class VmWorkResizeVolume extends VmWork {
return newSize;
}
+ public long getNewMinIops() {
+ return newMinIops;
+ }
+
+ public long getNewMaxIops() {
+ return newMaxIops;
+ }
+
public Long getNewServiceOfferingId() {
return newServiceOfferingId;
}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b080eaf3/server/src/com/cloud/storage/VolumeApiServiceImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/com/cloud/storage/VolumeApiServiceImpl.java
index e788cb2..49cb520 100644
--- a/server/src/com/cloud/storage/VolumeApiServiceImpl.java
+++ b/server/src/com/cloud/storage/VolumeApiServiceImpl.java
@@ -121,6 +121,7 @@ import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.Predicate;
import com.cloud.utils.ReflectionUse;
+import com.cloud.utils.StringUtils;
import com.cloud.utils.UriUtils;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.db.DB;
@@ -637,9 +638,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
public boolean validateVolumeSizeRange(long size) {
if (size < 0 || (size > 0 && size < (1024 * 1024 * 1024))) {
- throw new InvalidParameterValueException("Please specify a size of at least 1 Gb.");
+ throw new InvalidParameterValueException("Please specify a size of at least 1 GB.");
} else if (size > (_maxVolumeSizeInGb * 1024 * 1024 * 1024)) {
- throw new InvalidParameterValueException("volume size " + size + ", but the maximum size allowed is " + _maxVolumeSizeInGb + " Gb.");
+ throw new InvalidParameterValueException("Requested volume size is " + size + ", but the maximum size allowed is " + _maxVolumeSizeInGb + " GB.");
}
return true;
@@ -716,67 +717,99 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_RESIZE, eventDescription = "resizing volume", async = true)
public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationException {
Long newSize = null;
+ Long newMinIops = null;
+ Long newMaxIops = null;
boolean shrinkOk = cmd.getShrinkOk();
VolumeVO volume = _volsDao.findById(cmd.getEntityId());
+
if (volume == null) {
throw new InvalidParameterValueException("No such volume");
}
+ /* Does the caller have authority to act on this volume? */
+ _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, volume);
+
DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId());
- DiskOfferingVO newDiskOffering = null;
+ DiskOfferingVO newDiskOffering = _diskOfferingDao.findById(cmd.getNewDiskOfferingId());
- newDiskOffering = _diskOfferingDao.findById(cmd.getNewDiskOfferingId());
+ /* Only works for KVM/XenServer/VMware for now, and volumes with 'None' since they're just allocated in DB */
- /* Only works for KVM/Xen/VMware for now, and volumes with 'None' since they're just allocated in db */
- if (_volsDao.getHypervisorType(volume.getId()) != HypervisorType.KVM
- && _volsDao.getHypervisorType(volume.getId()) != HypervisorType.XenServer
- && _volsDao.getHypervisorType(volume.getId()) != HypervisorType.VMware
- && _volsDao.getHypervisorType(volume.getId()) != HypervisorType.None) {
- throw new InvalidParameterValueException("Cloudstack currently only supports volumes marked as KVM, VMware, XenServer hypervisor for resize");
+ HypervisorType hypervisorType = _volsDao.getHypervisorType(volume.getId());
+
+ if (hypervisorType != HypervisorType.KVM && hypervisorType != HypervisorType.XenServer &&
+ hypervisorType != HypervisorType.VMware && hypervisorType != HypervisorType.None) {
+ throw new InvalidParameterValueException("CloudStack currently only supports volumes marked as the KVM, VMware, or XenServer hypervisor type for resize.");
}
if (volume.getState() != Volume.State.Ready && volume.getState() != Volume.State.Allocated) {
- throw new InvalidParameterValueException("Volume should be in ready or allocated state before attempting a resize. "
- + "Volume " + volume.getUuid() + " state is:" + volume.getState());
+ throw new InvalidParameterValueException("Volume should be in ready or allocated state before attempting a resize. Volume " +
+ volume.getUuid() + " is in state " + volume.getState() + ".");
}
- /*
- * figure out whether or not a new disk offering or size parameter is
- * required, get the correct size value
- */
+ // if we are to use the existing disk offering
if (newDiskOffering == null) {
- if (diskOffering.isCustomized() || volume.getVolumeType().equals(Volume.Type.ROOT)) {
- newSize = cmd.getSize();
+ newSize = cmd.getSize();
- if (newSize == null) {
- throw new InvalidParameterValueException("new offering is of custom size, need to specify a size");
+ // if the caller is looking to change the size of the volume
+ if (newSize != null) {
+ if (!diskOffering.isCustomized() && !volume.getVolumeType().equals(Volume.Type.ROOT)) {
+ throw new InvalidParameterValueException("To change a volume's size without providing a new disk offering, its current disk offering must be " +
+ "customizable or it must be a root volume.");
}
- newSize = (newSize << 30);
- } else {
- throw new InvalidParameterValueException("current offering" + volume.getDiskOfferingId() + " cannot be resized, need to specify a disk offering");
+ // convert from bytes to GiB
+ newSize = newSize << 30;
+ }
+ else {
+ // no parameter provided; just use the original size of the volume
+ newSize = volume.getSize();
+ }
+
+ newMinIops = cmd.getMinIops();
+
+ if (newMinIops != null) {
+ if (diskOffering.isCustomizedIops() == null || !diskOffering.isCustomizedIops()) {
+ throw new InvalidParameterValueException("The current disk offering does not support customization of the 'Min IOPS' parameter.");
+ }
+ }
+ else {
+ // no parameter provided; just use the original min IOPS of the volume
+ newMinIops = volume.getMinIops();
+ }
+
+ newMaxIops = cmd.getMaxIops();
+
+ if (newMaxIops != null) {
+ if (diskOffering.isCustomizedIops() == null || !diskOffering.isCustomizedIops()) {
+ throw new InvalidParameterValueException("The current disk offering does not support customization of the 'Max IOPS' parameter.");
+ }
+ }
+ else {
+ // no parameter provided; just use the original max IOPS of the volume
+ newMaxIops = volume.getMaxIops();
}
+
+ validateIops(newMinIops, newMaxIops);
} else {
- if (!volume.getVolumeType().equals(Volume.Type.DATADISK)) {
- throw new InvalidParameterValueException("Can only resize Data volumes via new disk offering");
+ if (newDiskOffering.getRemoved() != null) {
+ throw new InvalidParameterValueException("Requested disk offering has been removed.");
}
- if (newDiskOffering.getRemoved() != null || !DiskOfferingVO.Type.Disk.equals(newDiskOffering.getType())) {
- throw new InvalidParameterValueException("Disk offering ID is missing or invalid");
+ if (!DiskOfferingVO.Type.Disk.equals(newDiskOffering.getType())) {
+ throw new InvalidParameterValueException("Requested disk offering type is invalid.");
}
if (diskOffering.getTags() != null) {
- if (newDiskOffering.getTags() == null || !newDiskOffering.getTags().equals(diskOffering.getTags())) {
- throw new InvalidParameterValueException("Tags on new and old disk offerings must match");
+ if (!StringUtils.areTagsEqual(diskOffering.getTags(), newDiskOffering.getTags())) {
+ throw new InvalidParameterValueException("The tags on the new and old disk offerings must match.");
}
} else if (newDiskOffering.getTags() != null) {
- throw new InvalidParameterValueException("There are no tags on current disk offering, new disk offering needs to have no tags");
+ throw new InvalidParameterValueException("There are no tags on the current disk offering. The new disk offering needs to have no tags, as well.");
}
- if (newDiskOffering.getDomainId() == null) {
- // do nothing as offering is public
- } else {
+ if (newDiskOffering.getDomainId() != null) {
+ // not a public offering; check access
_configMgr.checkDiskOfferingAccess(CallContext.current().getCallingAccount(), newDiskOffering);
}
@@ -784,108 +817,147 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
newSize = cmd.getSize();
if (newSize == null) {
- throw new InvalidParameterValueException("new offering is of custom size, need to specify a size");
+ throw new InvalidParameterValueException("The new disk offering requires that a size be specified.");
}
- newSize = (newSize << 30);
+ // convert from bytes to GiB
+ newSize = newSize << 30;
} else {
newSize = newDiskOffering.getDiskSize();
}
- }
- if (newSize == null) {
- throw new InvalidParameterValueException("could not detect a size parameter or fetch one from the diskofferingid parameter");
- }
+ if (volume.getSize() != newSize && !volume.getVolumeType().equals(Volume.Type.DATADISK)) {
+ throw new InvalidParameterValueException("Only data volumes can be resized via a new disk offering.");
+ }
- if (!validateVolumeSizeRange(newSize)) {
- throw new InvalidParameterValueException("Requested size out of range");
- }
+ if (newDiskOffering.isCustomizedIops() != null && newDiskOffering.isCustomizedIops()) {
+ newMinIops = cmd.getMinIops() != null ? cmd.getMinIops() : volume.getMinIops();
+ newMaxIops = cmd.getMaxIops() != null ? cmd.getMaxIops() : volume.getMaxIops();
- /* does the caller have the authority to act on this volume? */
- _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, volume);
+ validateIops(newMinIops, newMaxIops);
+ }
+ else {
+ newMinIops = newDiskOffering.getMinIops();
+ newMaxIops = newDiskOffering.getMaxIops();
+ }
+ }
long currentSize = volume.getSize();
- /*
- * lets make certain they (think they) know what they're doing if they
- * want to shrink, by forcing them to provide the shrinkok parameter.
- * This will be checked again at the hypervisor level where we can see
- * the actual disk size
- */
- if (currentSize > newSize && !shrinkOk) {
- throw new InvalidParameterValueException("Going from existing size of " + currentSize + " to size of " + newSize
- + " would shrink the volume, need to sign off by supplying the shrinkok parameter with value of true");
- }
+ // if the caller is looking to change the size of the volume
+ if (currentSize != newSize) {
+ if (!validateVolumeSizeRange(newSize)) {
+ throw new InvalidParameterValueException("Requested size out of range");
+ }
- if (!shrinkOk) {
- /* Check resource limit for this account on primary storage resource */
- _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(volume.getAccountId()), ResourceType.primary_storage, volume.isDisplayVolume(), new Long(newSize
- - currentSize).longValue());
+ /*
+ * Let's make certain they (think they) know what they're doing if they
+ * want to shrink by forcing them to provide the shrinkok parameter.
+ * This will be checked again at the hypervisor level where we can see
+ * the actual disk size.
+ */
+ if (currentSize > newSize && !shrinkOk) {
+ throw new InvalidParameterValueException("Going from existing size of " + currentSize + " to size of " + newSize + " would shrink the volume." +
+ "Need to sign off by supplying the shrinkok parameter with value of true.");
+ }
+
+ if (newSize > currentSize) {
+ /* Check resource limit for this account on primary storage resource */
+ _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(volume.getAccountId()), ResourceType.primary_storage, volume.isDisplayVolume(),
+ new Long(newSize - currentSize).longValue());
+ }
}
- /* If this volume has never been beyond allocated state, short circuit everything and simply update the database */
+ // Note: The storage plug-in in question should perform validation on the IOPS to check if a sufficient number of IOPS are available to perform
+ // the requested change
+
+ /* If this volume has never been beyond allocated state, short circuit everything and simply update the database. */
if (volume.getState() == Volume.State.Allocated) {
- s_logger.debug("Volume is allocated, but never created, simply updating database with new size");
+ s_logger.debug("Volume is in the allocated state, but has never been created. Simply updating database with new size and IOPS.");
+
volume.setSize(newSize);
+ volume.setMinIops(newMinIops);
+ volume.setMaxIops(newMaxIops);
+
if (newDiskOffering != null) {
volume.setDiskOfferingId(cmd.getNewDiskOfferingId());
}
+
_volsDao.update(volume.getId(), volume);
+
return volume;
}
UserVmVO userVm = _userVmDao.findById(volume.getInstanceId());
-
if (userVm != null) {
// serialize VM operation
AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext();
+
if (!VmJobEnabled.value() || jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) {
// avoid re-entrance
VmWorkJobVO placeHolder = null;
+
if (VmJobEnabled.value()) {
placeHolder = createPlaceHolderWork(userVm.getId());
}
+
try {
- return orchestrateResizeVolume(volume.getId(), currentSize, newSize,
+ return orchestrateResizeVolume(volume.getId(), currentSize, newSize, newMinIops, newMaxIops,
newDiskOffering != null ? cmd.getNewDiskOfferingId() : null, shrinkOk);
} finally {
- if (VmJobEnabled.value())
+ if (VmJobEnabled.value()) {
_workJobDao.expunge(placeHolder.getId());
+ }
}
-
} else {
- Outcome<Volume> outcome = resizeVolumeThroughJobQueue(userVm.getId(), volume.getId(), currentSize, newSize,
+ Outcome<Volume> outcome = resizeVolumeThroughJobQueue(userVm.getId(), volume.getId(), currentSize, newSize, newMinIops, newMaxIops,
newDiskOffering != null ? cmd.getNewDiskOfferingId() : null, shrinkOk);
- Volume vol = null;
try {
outcome.get();
} catch (InterruptedException e) {
- throw new RuntimeException("Operation is interrupted", e);
+ throw new RuntimeException("Operation was interrupted", e);
} catch (java.util.concurrent.ExecutionException e) {
- throw new RuntimeException("Execution excetion", e);
+ throw new RuntimeException("Execution exception", e);
}
Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob());
+
if (jobResult != null) {
- if (jobResult instanceof ConcurrentOperationException)
+ if (jobResult instanceof ConcurrentOperationException) {
throw (ConcurrentOperationException)jobResult;
- else if (jobResult instanceof Throwable)
+ }
+ else if (jobResult instanceof Throwable) {
throw new RuntimeException("Unexpected exception", (Throwable)jobResult);
+ }
else if (jobResult instanceof Long) {
- vol = _volsDao.findById((Long)jobResult);
+ return _volsDao.findById((Long)jobResult);
}
}
+
return volume;
}
}
- return orchestrateResizeVolume(volume.getId(), currentSize, newSize,
+
+ return orchestrateResizeVolume(volume.getId(), currentSize, newSize, newMinIops, newMaxIops,
newDiskOffering != null ? cmd.getNewDiskOfferingId() : null, shrinkOk);
+ }
+
+ private void validateIops(Long minIops, Long maxIops) {
+ if ((minIops == null && maxIops != null) || (minIops != null && maxIops == null)) {
+ throw new InvalidParameterValueException("Either 'miniops' and 'maxiops' must both be provided or neither must be provided.");
}
- private VolumeVO orchestrateResizeVolume(long volumeId, long currentSize, long newSize, Long newDiskOfferingId, boolean shrinkOk) {
+ if (minIops != null && maxIops != null) {
+ if (minIops > maxIops) {
+ throw new InvalidParameterValueException("The 'miniops' parameter must be less than or equal to the 'maxiops' parameter.");
+ }
+ }
+ }
+
+ private VolumeVO orchestrateResizeVolume(long volumeId, long currentSize, long newSize, Long newMinIops, Long newMaxIops, Long newDiskOfferingId, boolean shrinkOk) {
VolumeVO volume = _volsDao.findById(volumeId);
UserVmVO userVm = _userVmDao.findById(volume.getInstanceId());
/*
@@ -905,12 +977,12 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
}
/* Xen only works offline, SR does not support VDI.resizeOnline */
- if (_volsDao.getHypervisorType(volume.getId()) == HypervisorType.XenServer && !userVm.getState().equals(State.Stopped)) {
+ if (currentSize != newSize && _volsDao.getHypervisorType(volume.getId()) == HypervisorType.XenServer && !userVm.getState().equals(State.Stopped)) {
throw new InvalidParameterValueException("VM must be stopped or disk detached in order to resize with the Xen HV");
}
}
- ResizeVolumePayload payload = new ResizeVolumePayload(newSize, shrinkOk, instanceName, hosts);
+ ResizeVolumePayload payload = new ResizeVolumePayload(newSize, newMinIops, newMaxIops, shrinkOk, instanceName, hosts);
try {
VolumeInfo vol = volFactory.getVolume(volume.getId());
@@ -925,6 +997,18 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
volume = _volsDao.findById(volume.getId());
+ StoragePoolVO storagePool = _storagePoolDao.findById(vol.getPoolId());
+
+ if (storagePool.isManaged()) {
+ if (hosts.length > 0) {
+ volService.resizeVolumeOnHypervisor(volumeId, newSize, hosts[0], instanceName);
+ }
+
+ volume.setSize(newSize);
+
+ /** @todo let the storage driver know the CloudStack volume within the storage volume in question has a new size */
+ }
+
if (newDiskOfferingId != null) {
volume.setDiskOfferingId(newDiskOfferingId);
}
@@ -2364,7 +2448,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
}
public Outcome<Volume> resizeVolumeThroughJobQueue(final Long vmId, final long volumeId,
- final long currentSize, final long newSize, final Long newServiceOfferingId, final boolean shrinkOk) {
+ final long currentSize, final long newSize, final Long newMinIops, final Long newMaxIops, final Long newServiceOfferingId, final boolean shrinkOk) {
final CallContext context = CallContext.current();
final User callingUser = context.getCallingUser();
@@ -2394,7 +2478,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
// save work context info (there are some duplications)
VmWorkResizeVolume workInfo = new VmWorkResizeVolume(callingUser.getId(), callingAccount.getId(), vm.getId(),
- VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId, currentSize, newSize, newServiceOfferingId, shrinkOk);
+ VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId, currentSize, newSize, newMinIops, newMaxIops, newServiceOfferingId, shrinkOk);
workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo));
_jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId());
@@ -2529,7 +2613,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
@ReflectionUse
private Pair<JobInfo.Status, String> orchestrateResizeVolume(VmWorkResizeVolume work) throws Exception {
- Volume vol = orchestrateResizeVolume(work.getVolumeId(), work.getCurrentSize(), work.getNewSize(),
+ Volume vol = orchestrateResizeVolume(work.getVolumeId(), work.getCurrentSize(), work.getNewSize(), work.getNewMinIops(), work.getNewMaxIops(),
work.getNewServiceOfferingId(), work.isShrinkOk());
return new Pair<JobInfo.Status, String>(JobInfo.Status.SUCCEEDED,
_jobMgr.marshallResultObject(new Long(vol.getId())));
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/b080eaf3/utils/src/com/cloud/utils/StringUtils.java
----------------------------------------------------------------------
diff --git a/utils/src/com/cloud/utils/StringUtils.java b/utils/src/com/cloud/utils/StringUtils.java
index 09045aa..3b8cf71 100644
--- a/utils/src/com/cloud/utils/StringUtils.java
+++ b/utils/src/com/cloud/utils/StringUtils.java
@@ -198,6 +198,38 @@ public class StringUtils {
return cleanResult;
}
+ public static boolean areTagsEqual(String tags1, String tags2) {
+ if (tags1 == null && tags2 == null) {
+ return true;
+ }
+
+ if (tags1 != null && tags2 == null) {
+ return false;
+ }
+
+ if (tags1 == null && tags2 != null) {
+ return false;
+ }
+
+ final String delimiter = ",";
+
+ List<String> lstTags1 = new ArrayList<String>();
+ String[] aTags1 = tags1.split(delimiter);
+
+ for (String tag1 : aTags1) {
+ lstTags1.add(tag1.toLowerCase());
+ }
+
+ List<String> lstTags2 = new ArrayList<String>();
+ String[] aTags2 = tags2.split(delimiter);
+
+ for (String tag2 : aTags2) {
+ lstTags2.add(tag2.toLowerCase());
+ }
+
+ return lstTags1.containsAll(lstTags2) && lstTags2.containsAll(lstTags1);
+ }
+
public static String stripControlCharacters(String s) {
return StringUtilities.stripControls(s);
}