You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ml...@apache.org on 2013/01/08 19:25:58 UTC

git commit: Summary: resizeVolume initial implementation

Updated Branches:
  refs/heads/resizevolume [created] 8f1e6bebe


Summary: resizeVolume initial implementation

Detail: Supports KVM qcow2,CLVM online resize, Xen offline resize

BUG-ID: CLOUDSTACK-644
Signed-off-by: Marcus Sorensen <ma...@betterservers.com> 1357669498 -0700


Project: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/commit/8f1e6beb
Tree: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/tree/8f1e6beb
Diff: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/diff/8f1e6beb

Branch: refs/heads/resizevolume
Commit: 8f1e6bebed35ae27577d1e867c95749f28ac9c29
Parents: c6110a8
Author: Marcus Sorensen <ma...@betterservers.com>
Authored: Tue Jan 8 11:24:58 2013 -0700
Committer: Marcus Sorensen <ma...@betterservers.com>
Committed: Tue Jan 8 11:24:58 2013 -0700

----------------------------------------------------------------------
 api/src/com/cloud/api/ApiConstants.java            |    1 +
 api/src/com/cloud/event/EventTypes.java            |    1 +
 api/src/com/cloud/storage/StorageService.java      |   10 +
 api/src/com/cloud/storage/Volume.java              |    9 +-
 client/tomcatconf/commands.properties.in           |    1 +
 .../kvm/resource/LibvirtComputingResource.java     |   77 ++++++
 .../xen/resource/CitrixResourceBase.java           |   22 ++
 .../src/com/cloud/storage/StorageManagerImpl.java  |  180 +++++++++++++++
 8 files changed, 299 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8f1e6beb/api/src/com/cloud/api/ApiConstants.java
----------------------------------------------------------------------
diff --git a/api/src/com/cloud/api/ApiConstants.java b/api/src/com/cloud/api/ApiConstants.java
index 588cdc6..c32cc5b 100755
--- a/api/src/com/cloud/api/ApiConstants.java
+++ b/api/src/com/cloud/api/ApiConstants.java
@@ -381,6 +381,7 @@ public class ApiConstants {
     public static final String ESP_LIFETIME = "esplifetime";
     public static final String DPD = "dpd";
     public static final String FOR_VPC = "forvpc";
+    public static final String SHRINK_OK = "shrinkok";
     public static final String NICIRA_NVP_DEVICE_ID = "nvpdeviceid";
     public static final String NICIRA_NVP_TRANSPORT_ZONE_UUID = "transportzoneuuid";
     public static final String NICIRA_NVP_DEVICE_NAME = "niciradevicename";

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8f1e6beb/api/src/com/cloud/event/EventTypes.java
----------------------------------------------------------------------
diff --git a/api/src/com/cloud/event/EventTypes.java b/api/src/com/cloud/event/EventTypes.java
index e69e4a4..e7873e3 100755
--- a/api/src/com/cloud/event/EventTypes.java
+++ b/api/src/com/cloud/event/EventTypes.java
@@ -108,6 +108,7 @@ public class EventTypes {
     public static final String EVENT_VOLUME_EXTRACT = "VOLUME.EXTRACT";
     public static final String EVENT_VOLUME_UPLOAD = "VOLUME.UPLOAD";
     public static final String EVENT_VOLUME_MIGRATE = "VOLUME.MIGRATE";
+    public static final String EVENT_VOLUME_RESIZE = "VOLUME.RESIZE";
 
     // Domains
     public static final String EVENT_DOMAIN_CREATE = "DOMAIN.CREATE";

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8f1e6beb/api/src/com/cloud/storage/StorageService.java
----------------------------------------------------------------------
diff --git a/api/src/com/cloud/storage/StorageService.java b/api/src/com/cloud/storage/StorageService.java
index 587c138..426c4c5 100644
--- a/api/src/com/cloud/storage/StorageService.java
+++ b/api/src/com/cloud/storage/StorageService.java
@@ -26,6 +26,7 @@ import com.cloud.api.commands.DeletePoolCmd;
 import com.cloud.api.commands.ListVolumesCmd;
 import com.cloud.api.commands.UpdateStoragePoolCmd;
 import com.cloud.api.commands.UploadVolumeCmd;
+import com.cloud.api.commands.ResizeVolumeCmd;
 import com.cloud.exception.ConcurrentOperationException;
 import com.cloud.exception.InsufficientCapacityException;
 import com.cloud.exception.PermissionDeniedException;
@@ -75,6 +76,15 @@ public interface StorageService{
 
 
     /**
+     * Resizes the volume based on the given criteria
+     * 
+     * @param cmd
+     *            the API command wrapping the criteria
+     * @return the volume object
+     */
+    Volume resizeVolume(ResizeVolumeCmd cmd);
+
+    /**
      * Delete the storage pool
      * 
      * @param cmd

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8f1e6beb/api/src/com/cloud/storage/Volume.java
----------------------------------------------------------------------
diff --git a/api/src/com/cloud/storage/Volume.java b/api/src/com/cloud/storage/Volume.java
index 6e8e48e..e7e9476 100755
--- a/api/src/com/cloud/storage/Volume.java
+++ b/api/src/com/cloud/storage/Volume.java
@@ -34,6 +34,7 @@ public interface Volume extends ControlledEntity, BasedOn, StateObject<Volume.St
         Ready("The volume is ready to be used."),
         Migrating("The volume is migrating to other storage pool"),
         Snapshotting("There is a snapshot created on this volume, not backed up to secondary storage yet"),
+        Resizing("The volume is being resized"),
         Expunging("The volume is being expunging"),
         Destroy("The volume is destroyed, and can't be recovered."),        
         UploadOp ("The volume upload operation is in progress or in short the volume is on secondary storage");            
@@ -60,7 +61,10 @@ public interface Volume extends ControlledEntity, BasedOn, StateObject<Volume.St
             s_fsm.addTransition(Creating, Event.OperationFailed, Allocated);
             s_fsm.addTransition(Creating, Event.OperationSucceeded, Ready);
             s_fsm.addTransition(Creating, Event.DestroyRequested, Destroy);
-            s_fsm.addTransition(Creating, Event.CreateRequested, Creating);            
+            s_fsm.addTransition(Creating, Event.CreateRequested, Creating);  
+            s_fsm.addTransition(Ready, Event.ResizeRequested, Resizing);
+            s_fsm.addTransition(Resizing, Event.OperationSucceeded, Ready);
+            s_fsm.addTransition(Resizing, Event.OperationFailed, Ready);          
             s_fsm.addTransition(Allocated, Event.UploadRequested, UploadOp);
             s_fsm.addTransition(UploadOp, Event.CopyRequested, Creating);// CopyRequested for volume from sec to primary storage            
             s_fsm.addTransition(Creating, Event.CopySucceeded, Ready);
@@ -90,7 +94,8 @@ public interface Volume extends ControlledEntity, BasedOn, StateObject<Volume.St
         MigrationRequested,
         SnapshotRequested,
         DestroyRequested,
-        ExpungingRequested;
+        ExpungingRequested,
+        ResizeRequested;
     }
 
     long getId();

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8f1e6beb/client/tomcatconf/commands.properties.in
----------------------------------------------------------------------
diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in
index 1e70c0f..42c498d 100755
--- a/client/tomcatconf/commands.properties.in
+++ b/client/tomcatconf/commands.properties.in
@@ -254,6 +254,7 @@ deleteVolume=com.cloud.api.commands.DeleteVolumeCmd;15
 listVolumes=com.cloud.api.commands.ListVolumesCmd;15
 extractVolume=com.cloud.api.commands.ExtractVolumeCmd;15
 migrateVolume=com.cloud.api.commands.MigrateVolumeCmd;15
+resizeVolume=com.cloud.api.commands.ResizeVolumeCmd;15
 
 #### registration command:  FIXME -- this really should be something in management server that
 ####                                 generates a new key for the user and they just have to

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8f1e6beb/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
----------------------------------------------------------------------
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 b52e2d8..4fda177 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
@@ -162,6 +162,8 @@ import com.cloud.agent.api.storage.CreatePrivateTemplateAnswer;
 import com.cloud.agent.api.storage.DestroyCommand;
 import com.cloud.agent.api.storage.PrimaryStorageDownloadAnswer;
 import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand;
+import com.cloud.agent.api.storage.ResizeVolumeCommand;
+import com.cloud.agent.api.storage.ResizeVolumeAnswer;
 import com.cloud.agent.api.to.IpAddressTO;
 import com.cloud.agent.api.to.NicTO;
 import com.cloud.agent.api.to.StorageFilerTO;
@@ -255,6 +257,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements
     private String _patchdomrPath;
     private String _createvmPath;
     private String _manageSnapshotPath;
+    private String _resizeVolumePath;
     private String _createTmplPath;
     private String _heartBeatPath;
     private String _securityGroupPath;
@@ -534,6 +537,12 @@ public class LibvirtComputingResource extends ServerResourceBase implements
                     "Unable to find the managesnapshot.sh");
         }
 
+        _resizeVolumePath = Script.findScript(storageScriptsDir, "resizevolume.sh");
+        if (_resizeVolumePath == null) {
+            throw new ConfigurationException(
+                    "Unable to find the resizevolume.sh");
+        }
+
         _createTmplPath = Script
                 .findScript(storageScriptsDir, "createtmplt.sh");
         if (_createTmplPath == null) {
@@ -1062,6 +1071,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements
                 return execute((CleanupNetworkRulesCmd) cmd);
             } else if (cmd instanceof CopyVolumeCommand) {
                 return execute((CopyVolumeCommand) cmd);
+            } else if (cmd instanceof ResizeVolumeCommand) {
+                return execute((ResizeVolumeCommand) cmd);
             } else if (cmd instanceof CheckNetworkCommand) {
                 return execute((CheckNetworkCommand) cmd);
             } else {
@@ -1268,6 +1279,72 @@ public class LibvirtComputingResource extends ServerResourceBase implements
         }
     }
 
+    private String getResizeScriptType (KVMStoragePool pool, KVMPhysicalDisk vol) {
+        StoragePoolType poolType = pool.getType();
+        PhysicalDiskFormat volFormat = vol.getFormat();
+         
+        if(pool.getType() == StoragePoolType.CLVM && volFormat == KVMPhysicalDisk.PhysicalDiskFormat.RAW) {
+            return "CLVM";
+        } else if ((poolType == StoragePoolType.NetworkFilesystem
+                  || poolType == StoragePoolType.SharedMountPoint
+                  || poolType == StoragePoolType.Filesystem)
+                  && volFormat == KVMPhysicalDisk.PhysicalDiskFormat.QCOW2 ) {
+            return "QCOW2";
+        }
+        return null;
+    }
+
+    /* uses a local script now, eventually support for virStorageVolResize() will maybe work on 
+       qcow2 and lvm and we can do this in libvirt calls */
+    public Answer execute(ResizeVolumeCommand cmd) {
+        String volid = cmd.getPath();
+        long newSize = cmd.getNewSize();
+        long currentSize = cmd.getCurrentSize();
+        String vmInstanceName = cmd.getInstanceName();
+        boolean shrinkOk = cmd.getShrinkOk();
+        StorageFilerTO spool = cmd.getPool();
+
+        try {
+            KVMStoragePool pool = _storagePoolMgr.getStoragePool(spool.getType(), spool.getUuid());
+            KVMPhysicalDisk vol = pool.getPhysicalDisk(volid);
+            String path = vol.getPath();
+            String type = getResizeScriptType(pool, vol);
+
+            if (type == null) {
+                return new ResizeVolumeAnswer(cmd, false, "Unsupported volume format: pool type '" 
+                                + pool.getType() + "' and volume format '" + vol.getFormat() + "'");
+            }
+
+            s_logger.debug("got to the stage where we execute the volume resize, params:" 
+                           + path + "," + currentSize + "," + newSize + "," + type + "," + vmInstanceName + "," + shrinkOk);
+            final Script resizecmd = new Script(_resizeVolumePath,
+                        _cmdsTimeout, s_logger); 
+            resizecmd.add("-s",String.valueOf(newSize));
+            resizecmd.add("-c",String.valueOf(currentSize));
+            resizecmd.add("-p",path);
+            resizecmd.add("-t",type);
+            resizecmd.add("-r",String.valueOf(shrinkOk));
+            resizecmd.add("-v",vmInstanceName);
+            String result = resizecmd.execute();
+
+            if (result == null) {
+
+                /* fetch new size as seen from libvirt, don't want to assume anything */
+                pool = _storagePoolMgr.getStoragePool(spool.getType(), spool.getUuid());
+                long finalSize = pool.getPhysicalDisk(volid).getVirtualSize();
+                s_logger.debug("after resize, size reports as " + finalSize + ", requested " + newSize);
+                return new ResizeVolumeAnswer(cmd, true, "success", finalSize);
+            }
+
+            return new ResizeVolumeAnswer(cmd, false, result);
+        } catch (CloudRuntimeException e) {
+            String error = "failed to resize volume: " + e;
+            s_logger.debug(error);
+            return new ResizeVolumeAnswer(cmd, false, error);
+        }
+        
+    } 
+
     public Answer execute(DestroyCommand cmd) {
         VolumeTO vol = cmd.getVolume();
 

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8f1e6beb/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java
----------------------------------------------------------------------
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 36a12b1..9ebf7e8 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
@@ -182,6 +182,8 @@ import com.cloud.agent.api.storage.CreatePrivateTemplateAnswer;
 import com.cloud.agent.api.storage.DestroyCommand;
 import com.cloud.agent.api.storage.PrimaryStorageDownloadAnswer;
 import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand;
+import com.cloud.agent.api.storage.ResizeVolumeCommand;
+import com.cloud.agent.api.storage.ResizeVolumeAnswer;
 import com.cloud.agent.api.to.IpAddressTO;
 import com.cloud.agent.api.to.NicTO;
 import com.cloud.agent.api.to.PortForwardingRuleTO;
@@ -468,6 +470,8 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
             return execute((DeleteStoragePoolCommand) cmd);
         } else if (clazz == CopyVolumeCommand.class) {
             return execute((CopyVolumeCommand) cmd);
+        } else if (clazz == ResizeVolumeCommand.class) {
+            return execute((ResizeVolumeCommand) cmd);
         } else if (clazz == AttachVolumeCommand.class) {
             return execute((AttachVolumeCommand) cmd);
         } else if (clazz == AttachIsoCommand.class) {
@@ -5616,6 +5620,23 @@ 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();
+
+        try {
+            VDI vdi = getVDIbyUuid(conn, volid);
+            vdi.resize(conn, newSize);
+            return new ResizeVolumeAnswer(cmd, true, "success", newSize);
+        } catch (Exception e) {
+            s_logger.warn("Unable to resize volume",e);
+            String error = "failed to resize volume:"  +e;
+            return new ResizeVolumeAnswer(cmd, false, error );
+        }
+    }
+
     protected SR getISOSRbyVmName(Connection conn, String vmName) {
         try {
             Set<SR> srs = SR.getByNameLabel(conn, vmName + "-ISO");
@@ -7682,4 +7703,5 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
             return new SetStaticRouteAnswer(cmd, false, null);
         }
     }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8f1e6beb/server/src/com/cloud/storage/StorageManagerImpl.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/storage/StorageManagerImpl.java b/server/src/com/cloud/storage/StorageManagerImpl.java
index 54b6dc0..aed827f 100755
--- a/server/src/com/cloud/storage/StorageManagerImpl.java
+++ b/server/src/com/cloud/storage/StorageManagerImpl.java
@@ -66,6 +66,8 @@ import com.cloud.agent.api.storage.CreateCommand;
 import com.cloud.agent.api.storage.DeleteTemplateCommand;
 import com.cloud.agent.api.storage.DeleteVolumeCommand;
 import com.cloud.agent.api.storage.DestroyCommand;
+import com.cloud.agent.api.storage.ResizeVolumeCommand;
+import com.cloud.agent.api.storage.ResizeVolumeAnswer;
 import com.cloud.agent.api.to.StorageFilerTO;
 import com.cloud.agent.api.to.VolumeTO;
 import com.cloud.agent.manager.Commands;
@@ -76,6 +78,7 @@ import com.cloud.api.commands.CreateStoragePoolCmd;
 import com.cloud.api.commands.CreateVolumeCmd;
 import com.cloud.api.commands.DeletePoolCmd;
 import com.cloud.api.commands.ListVolumesCmd;
+import com.cloud.api.commands.ResizeVolumeCmd;
 import com.cloud.api.commands.UpdateStoragePoolCmd;
 import com.cloud.api.commands.UploadVolumeCmd;
 import com.cloud.async.AsyncJobManager;
@@ -2108,6 +2111,183 @@ public class StorageManagerImpl implements StorageManager, Manager, ClusterManag
 
     @Override
     @DB
+    @ActionEvent(eventType = EventTypes.EVENT_VOLUME_RESIZE, eventDescription = "resizing volume", async = true)
+    public VolumeVO resizeVolume(ResizeVolumeCmd cmd) {
+        VolumeVO volume = _volsDao.findById(cmd.getEntityId());
+        Long newSize = null;
+        boolean shrinkOk = cmd.getShrinkOk();
+        boolean success = false;
+        DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId());
+        DiskOfferingVO newDiskOffering = null;
+
+        newDiskOffering = _diskOfferingDao.findById(cmd.getNewDiskOfferingId());
+
+        /* Volumes with no hypervisor have never been assigned, and can be resized by recreating.
+           perhaps in the future we can just update the db entry for the volume */
+        if(_volsDao.getHypervisorType(volume.getId()) == HypervisorType.None){
+            throw new InvalidParameterValueException("Can't resize a volume that has never been attached, not sure which hypervisor type. Recreate volume to resize.");
+        }
+
+        /* Only works for KVM/Xen for now */
+        if(_volsDao.getHypervisorType(volume.getId()) != HypervisorType.KVM 
+           && _volsDao.getHypervisorType(volume.getId()) != HypervisorType.XenServer){
+            throw new InvalidParameterValueException("Cloudstack currently only supports volumes marked as KVM or XenServer hypervisor for resize");
+        }
+
+        if (volume == null) {
+            throw new InvalidParameterValueException("No such volume");
+        }
+
+        if (volume.getState() != Volume.State.Ready) {
+            throw new InvalidParameterValueException("Volume should be in ready state before attempting a resize");
+        }
+
+        if (!volume.getVolumeType().equals(Volume.Type.DATADISK)) {
+            throw new InvalidParameterValueException("Can only resize DATA volumes");
+        }
+
+        /* figure out whether or not a new disk offering or size parameter is required, get the correct size value */
+        if (newDiskOffering == null) {
+            if (diskOffering.isCustomized()) {
+                newSize = cmd.getSize();
+
+                if (newSize == null) {
+                    throw new InvalidParameterValueException("new offering is of custom size, need to specify a size");
+                }
+
+                newSize = ( newSize << 30 );
+            } else {
+                throw new InvalidParameterValueException("current offering" + volume.getDiskOfferingId()  + " cannot be resized, need to specify a disk offering");
+            }
+        } else {
+
+            if (newDiskOffering.getRemoved() != null || !DiskOfferingVO.Type.Disk.equals(newDiskOffering.getType())) {
+                throw new InvalidParameterValueException("Disk offering ID is missing or invalid");
+            }
+
+            if(diskOffering.getTags() != null) {
+                if(!newDiskOffering.getTags().equals(diskOffering.getTags())){
+                    throw new InvalidParameterValueException("Tags on 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");
+            }
+
+            if (newDiskOffering.getDomainId() == null) {
+                // do nothing as offering is public
+            } else {
+                _configMgr.checkDiskOfferingAccess(UserContext.current().getCaller(), newDiskOffering);
+            }
+ 
+            if (newDiskOffering.isCustomized()) {
+                newSize = cmd.getSize();
+
+                if (newSize == null) {
+                    throw new InvalidParameterValueException("new offering is of custom size, need to specify a size");
+                }
+
+                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 (!validateVolumeSizeRange(newSize)) {
+            throw new InvalidParameterValueException("Requested size out of range");
+        }
+
+        /* does the caller have the authority to act on this volume? */
+        _accountMgr.checkAccess(UserContext.current().getCaller(), null, true, volume);
+
+        UserVmVO userVm = _userVmDao.findById(volume.getInstanceId());
+
+        StoragePool pool = _storagePoolDao.findById(volume.getPoolId());
+        long currentSize = volume.getSize();
+
+        /*Xen only works offline, SR does not support VDI.resizeOnline*/
+        if(_volsDao.getHypervisorType(volume.getId()) == HypervisorType.XenServer
+           && ! userVm.getState().equals(State.Stopped)) {
+            throw new InvalidParameterValueException("VM must be stopped in order to resize with the Xen HV");
+        }
+
+        /* 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");
+        }
+
+        /* get a list of hosts to send the commands to, try the system the 
+        associated vm is running on first, then the last known place it ran.
+        If not attached to a userVm, we pass 'none' and resizevolume.sh is
+        ok with that since it only needs the vm name to live resize */
+        long[] hosts = null;
+        String instanceName = "none";
+        if (userVm != null) {
+            instanceName = userVm.getInstanceName();
+            if(userVm.getHostId() != null) {
+                hosts = new long[] { userVm.getHostId() };
+            } else if(userVm.getLastHostId() != null) {
+                hosts = new long[] { userVm.getLastHostId() };
+            }
+        }
+
+        try {
+            try {
+                    stateTransitTo(volume, Volume.Event.ResizeRequested);
+            } catch (NoTransitionException etrans) {
+                    throw new CloudRuntimeException("Unable to change volume state for resize: " + etrans.toString());
+            }
+
+            ResizeVolumeCommand resizeCmd = new ResizeVolumeCommand(volume.getPath(), new StorageFilerTO(pool), 
+                                                    currentSize, newSize, shrinkOk, instanceName);
+            ResizeVolumeAnswer answer = (ResizeVolumeAnswer) sendToPool(pool, hosts, resizeCmd);
+
+            /* need to fetch/store new volume size in database. This value comes from 
+            hypervisor rather than trusting that a success means we have a volume of the 
+            size we requested */
+            if (answer != null && answer.getResult()) {
+                long finalSize = answer.getNewSize();
+                s_logger.debug("Resize: volume started at size " + currentSize + " and ended at size " + finalSize);
+                volume.setSize(finalSize);
+                if (newDiskOffering != null) {
+                    volume.setDiskOfferingId(cmd.getNewDiskOfferingId());
+                }
+                _volsDao.update(volume.getId(), volume);
+
+                success  = true;
+                return volume;
+            } else if (answer != null) {
+                s_logger.debug("Resize: returned '" + answer.getDetails() + "'");
+            }
+        } catch (StorageUnavailableException e) {
+            s_logger.debug("volume failed to resize: "+e);
+            return null;
+        } finally {
+            if(success) {
+                try {
+                    stateTransitTo(volume, Volume.Event.OperationSucceeded);
+                } catch (NoTransitionException etrans) {
+                    throw new CloudRuntimeException("Failed to change volume state: " + etrans.toString());
+                }
+            } else {
+                try {
+                    stateTransitTo(volume, Volume.Event.OperationFailed);
+                } catch (NoTransitionException etrans) {
+                    throw new CloudRuntimeException("Failed to change volume state: " + etrans.toString());
+                }
+            } 
+        }
+        return null;
+    }
+
+    @Override
+    @DB
     public boolean destroyVolume(VolumeVO volume) throws ConcurrentOperationException {
         try {
             if (!stateTransitTo(volume, Volume.Event.DestroyRequested)) {