incubator-cloudstack-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mlsoren...@apache.org
Subject git commit: Summary: adding resizeVolume api call
Date Thu, 17 Jan 2013 00:45:27 GMT
Updated Branches:
  refs/heads/master a22bfd7e7 -> 975021dda


Summary: adding resizeVolume api call

Detail: This merges the resizevolume feature branch, which provides the
ability to migrate a disk between disk offerings, thereby changing its
size, or specifying a new size if current disk offering is custom.

BUG-ID: CLOUDSTACK-644
Signed-off-by: Marcus Sorensen <marcus@betterservers.com> 1358358209 -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/975021dd
Tree: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/tree/975021dd
Diff: http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/diff/975021dd

Branch: refs/heads/master
Commit: 975021dda1aeee24c8729407e323a5e884e9ad31
Parents: a22bfd7
Author: Marcus Sorensen <marcus@betterservers.com>
Authored: Wed Jan 16 17:43:35 2013 -0700
Committer: Marcus Sorensen <marcus@betterservers.com>
Committed: Wed Jan 16 17:43:35 2013 -0700

----------------------------------------------------------------------
 .../agent/api/storage/ResizeVolumeAnswer.java      |   40 +++
 .../agent/api/storage/ResizeVolumeCommand.java     |   86 +++++
 api/src/com/cloud/event/EventTypes.java            |    1 +
 api/src/com/cloud/exception/CloudException.java    |    1 -
 api/src/com/cloud/storage/StorageService.java      |   10 +
 api/src/com/cloud/storage/Volume.java              |    9 +-
 .../org/apache/cloudstack/api/ApiConstants.java    |    1 +
 .../api/command/user/volume/ResizeVolumeCmd.java   |  153 +++++++++
 .../agent/api/test/ResizeVolumeCommandTest.java    |  199 ++++++++++++
 client/tomcatconf/commands.properties.in           |    1 +
 .../kvm/resource/LibvirtComputingResource.java     |   77 +++++
 .../xen/resource/CitrixResourceBase.java           |   22 ++
 scripts/storage/qcow2/resizevolume.sh              |  253 +++++++++++++++
 .../src/com/cloud/storage/StorageManagerImpl.java  |  180 ++++++++++
 test/integration/smoke/test_volumes.py             |  125 +++++++-
 tools/marvin/marvin/integration/lib/base.py        |    6 +
 16 files changed, 1157 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/975021dd/api/src/com/cloud/agent/api/storage/ResizeVolumeAnswer.java
----------------------------------------------------------------------
diff --git a/api/src/com/cloud/agent/api/storage/ResizeVolumeAnswer.java b/api/src/com/cloud/agent/api/storage/ResizeVolumeAnswer.java
new file mode 100644
index 0000000..3434b98
--- /dev/null
+++ b/api/src/com/cloud/agent/api/storage/ResizeVolumeAnswer.java
@@ -0,0 +1,40 @@
+// 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 com.cloud.agent.api.storage;
+
+import com.cloud.agent.api.Answer;
+
+public class ResizeVolumeAnswer extends Answer {
+    private long newSize;
+
+    protected ResizeVolumeAnswer() {
+
+    }
+
+    public ResizeVolumeAnswer(ResizeVolumeCommand cmd, boolean result, String details, long newSize) {
+        super(cmd, result, details);
+        this.newSize = newSize;
+    }
+
+    public ResizeVolumeAnswer(ResizeVolumeCommand cmd, boolean result, String details) {
+        super(cmd, result, details);
+    }
+
+    public long getNewSize() {
+        return newSize;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/975021dd/api/src/com/cloud/agent/api/storage/ResizeVolumeCommand.java
----------------------------------------------------------------------
diff --git a/api/src/com/cloud/agent/api/storage/ResizeVolumeCommand.java b/api/src/com/cloud/agent/api/storage/ResizeVolumeCommand.java
new file mode 100644
index 0000000..8af23a0
--- /dev/null
+++ b/api/src/com/cloud/agent/api/storage/ResizeVolumeCommand.java
@@ -0,0 +1,86 @@
+// 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 com.cloud.agent.api.storage;
+
+import com.cloud.agent.api.Command;
+import com.cloud.agent.api.to.StorageFilerTO;
+import com.cloud.storage.StoragePool;
+
+public class ResizeVolumeCommand extends Command {
+    private String path;
+    private StorageFilerTO pool;
+    private String vmInstance;
+    private Long newSize;
+    private Long currentSize;
+    private boolean shrinkOk;
+    
+    protected ResizeVolumeCommand() {
+        
+    }
+    
+    public ResizeVolumeCommand(String path,
+                           StorageFilerTO pool,
+                           Long currentSize,
+                           Long newSize,
+                           boolean shrinkOk,
+                           String vmInstance) 
+    {
+        this.path = path;
+        this.pool = pool;
+        this.vmInstance = vmInstance;
+        this.currentSize = currentSize;
+        this.newSize = newSize;
+        this.shrinkOk = shrinkOk;
+    }
+
+    public String getPath() {
+    	return path;
+    }
+
+    public String getPoolUuid() {
+        return pool.getUuid();
+    }
+
+    public StorageFilerTO getPool() {
+        return pool;
+    }
+    
+    public long getNewSize() {
+        return newSize;
+    }
+
+    public long getCurrentSize() {
+        return currentSize;
+    }
+
+    public boolean getShrinkOk() {
+        return shrinkOk;
+    }
+
+    public String getInstanceName() {
+        return vmInstance;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean executeInSequence() {
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/975021dd/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 8c62225..87eddca 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/975021dd/api/src/com/cloud/exception/CloudException.java
----------------------------------------------------------------------
diff --git a/api/src/com/cloud/exception/CloudException.java b/api/src/com/cloud/exception/CloudException.java
index 036cb1b..7326702 100644
--- a/api/src/com/cloud/exception/CloudException.java
+++ b/api/src/com/cloud/exception/CloudException.java
@@ -54,7 +54,6 @@ public class CloudException extends Exception {
 		return;
 	}
 
-
 	public ArrayList<String> getIdProxyList() {
 		return idList;
 	}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/975021dd/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 ff8ec13..a06f213 100644
--- a/api/src/com/cloud/storage/StorageService.java
+++ b/api/src/com/cloud/storage/StorageService.java
@@ -23,6 +23,7 @@ import org.apache.cloudstack.api.command.admin.storage.CancelPrimaryStorageMaint
 import org.apache.cloudstack.api.command.admin.storage.UpdateStoragePoolCmd;
 import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.UploadVolumeCmd;
+import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
 import com.cloud.exception.ConcurrentOperationException;
 import com.cloud.exception.InsufficientCapacityException;
 import com.cloud.exception.PermissionDeniedException;
@@ -71,6 +72,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/975021dd/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 bfbd816..38ba2d4 100755
--- a/api/src/com/cloud/storage/Volume.java
+++ b/api/src/com/cloud/storage/Volume.java
@@ -36,6 +36,7 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
         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");
@@ -62,7 +63,10 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
             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);
@@ -92,7 +96,8 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
         MigrationRequested,
         SnapshotRequested,
         DestroyRequested,
-        ExpungingRequested;
+        ExpungingRequested,
+        ResizeRequested;
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/975021dd/api/src/org/apache/cloudstack/api/ApiConstants.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java
index d3bfcd6..58a7831 100644
--- a/api/src/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/org/apache/cloudstack/api/ApiConstants.java
@@ -387,6 +387,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/975021dd/api/src/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java b/api/src/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java
old mode 100755
new mode 100644

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/975021dd/api/src/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoPermissionsCmd.java
----------------------------------------------------------------------
diff --git a/api/src/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoPermissionsCmd.java b/api/src/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoPermissionsCmd.java
old mode 100755
new mode 100644

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/975021dd/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
new file mode 100644
index 0000000..e42bf35
--- /dev/null
+++ b/api/src/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java
@@ -0,0 +1,153 @@
+// 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.api.command.user.volume;
+
+import org.apache.cloudstack.api.response.*;
+import org.apache.log4j.Logger;
+
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseAsyncCmd;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.BaseListTaggedResourcesCmd;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.exception.PermissionDeniedException;
+import com.cloud.exception.ResourceAllocationException;
+import com.cloud.async.AsyncJob;
+import com.cloud.event.EventTypes;
+import com.cloud.projects.Project;
+import com.cloud.storage.Volume;
+import com.cloud.user.Account;
+import com.cloud.user.UserContext;
+
+
+@APICommand(name="resizeVolume", description="Resizes a volume", responseObject=VolumeResponse.class)
+public class ResizeVolumeCmd extends BaseAsyncCmd {
+    public static final Logger s_logger = Logger.getLogger(ResizeVolumeCmd.class.getName());
+
+    private static final String s_name = "resizevolumeresponse";
+
+    /////////////////////////////////////////////////////
+    //////////////// API parameters /////////////////////
+    /////////////////////////////////////////////////////
+
+    @Parameter(name=ApiConstants.ID, entityType=VolumeResponse.class, 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")
+    private Long size;
+
+    @Parameter(name=ApiConstants.SHRINK_OK, type=CommandType.BOOLEAN, required=false, description="Verify OK to Shrink")
+    private boolean shrinkOk;
+
+    @Parameter(name=ApiConstants.DISK_OFFERING_ID, entityType=DiskOfferingResponse.class, type=CommandType.UUID, required=false, description="new disk offering id")
+    private Long newDiskOfferingId;
+    
+    /////////////////////////////////////////////////////
+    /////////////////// Accessors ///////////////////////
+    /////////////////////////////////////////////////////
+
+    public Long getEntityId() {
+        return id;
+    }
+    
+    public Long getSize() {
+        return size;
+    }
+
+    public boolean getShrinkOk() {
+        return shrinkOk;
+    }
+
+    public Long getNewDiskOfferingId() {
+        return newDiskOfferingId;
+    }
+
+    
+    /////////////////////////////////////////////////////
+    /////////////// API Implementation///////////////////
+    /////////////////////////////////////////////////////
+
+    @Override
+    public String getCommandName() {
+        return s_name;
+    }
+    
+    @Override
+    public AsyncJob.Type getInstanceType() {
+    	return AsyncJob.Type.Volume;
+    }
+
+    public static String getResultObjectName() {
+        return "volume";
+    }
+
+   @Override
+    public long getEntityOwnerId() {
+
+        Volume volume = _entityMgr.findById(Volume.class, getEntityId());
+        if (volume == null) {
+                throw new InvalidParameterValueException("Unable to find volume by id=" + id);
+        }
+
+        Account account = _accountService.getAccount(volume.getAccountId());
+        //Can resize volumes for enabled projects/accounts only
+        if (account.getType() == Account.ACCOUNT_TYPE_PROJECT) {
+                Project project = _projectService.findByProjectAccountId(volume.getAccountId());
+            if (project.getState() != Project.State.Active) {
+                throw new PermissionDeniedException("Can't add resources to  project id=" + project.getId() + " in state=" + project.getState() + " as it's no longer active");
+            }
+        } else if (account.getState() == Account.State.disabled) {
+            throw new PermissionDeniedException("The owner of volume " + id + "  is disabled: " + account);
+        }
+
+        return volume.getAccountId();
+    }
+
+
+    @Override
+    public String getEventType() {
+        return EventTypes.EVENT_VOLUME_RESIZE;
+    }
+
+    @Override
+    public String getEventDescription() {
+        return "Volume Id: " + getEntityId() + " to size " + getSize() + "G" ;
+    }
+    
+    @Override
+    public void execute(){
+        UserContext.current().setEventDetails("Volume Id: " + getEntityId() + " to size " + getSize() + "G");
+    	Volume volume = _storageService.resizeVolume(this);
+    	if (volume != null) {
+            VolumeResponse response = _responseGenerator.createVolumeResponse(volume);
+            //FIXME - have to be moved to ApiResponseHelper
+            response.setResponseName(getCommandName());
+            this.setResponseObject(response);
+        } else {
+            throw new ServerApiException(BaseCmd.INTERNAL_ERROR, "Failed to resize volume");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/975021dd/api/test/src/com/cloud/agent/api/test/ResizeVolumeCommandTest.java
----------------------------------------------------------------------
diff --git a/api/test/src/com/cloud/agent/api/test/ResizeVolumeCommandTest.java b/api/test/src/com/cloud/agent/api/test/ResizeVolumeCommandTest.java
new file mode 100644
index 0000000..2d248b2
--- /dev/null
+++ b/api/test/src/com/cloud/agent/api/test/ResizeVolumeCommandTest.java
@@ -0,0 +1,199 @@
+// 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 src.com.cloud.agent.api.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.junit.Test;
+
+import com.cloud.agent.api.storage.ResizeVolumeCommand;
+import com.cloud.agent.api.to.StorageFilerTO;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.Storage.StoragePoolType;
+import com.cloud.storage.StoragePoolStatus;
+
+
+public class ResizeVolumeCommandTest {
+
+    public StoragePool dummypool = new StoragePool() {
+        public long getId() {
+            return 1L;
+        };
+
+        public String getName() {
+            return "name";
+        };
+
+        public String getUuid() {
+            return "bed9f83e-cac3-11e1-ac8a-0050568b007e";
+        };
+
+        public StoragePoolType getPoolType() {
+            return StoragePoolType.Filesystem;
+        };
+
+        public Date getCreated() {
+            Date date = null;
+            try {
+                date = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss")
+                        .parse("01/01/1970 12:12:12");
+            } catch (ParseException e) {
+                e.printStackTrace();
+            }
+            return date;
+        }
+
+        public Date getUpdateTime() {
+            return new Date();
+        };
+
+        public long getDataCenterId() {
+            return 0L;
+        };
+
+        public long getCapacityBytes() {
+            return 0L;
+        };
+
+        public long getAvailableBytes() {
+            return 0L;
+        };
+
+        public Long getClusterId() {
+            return 0L;
+        };
+
+        public String getHostAddress() {
+            return "hostAddress";
+        };
+
+        public String getPath() {
+            return "path";
+        };
+
+        public String getUserInfo() {
+            return "userInfo";
+        };
+
+        public boolean isShared() {
+            return false;
+        };
+
+        public boolean isLocal() {
+            return false;
+        };
+
+        public StoragePoolStatus getStatus() {
+            return StoragePoolStatus.Up;
+        };
+
+        public int getPort() {
+            return 25;
+        };
+
+        public Long getPodId() {
+            return 0L;
+        };
+    };
+
+    Long newSize = 4194304L;
+    Long currentSize = 1048576L;
+
+    ResizeVolumeCommand rv = new ResizeVolumeCommand("dummydiskpath", 
+            new StorageFilerTO(dummypool), currentSize, newSize, false, 
+            "vmName");
+
+    @Test
+    public void testExecuteInSequence() {
+        boolean b = rv.executeInSequence();
+        assertFalse(b);
+    }
+
+    @Test
+    public void testGetPath() {
+        String path = rv.getPath();
+        assertTrue(path.equals("dummydiskpath"));
+    }
+
+    @Test
+    public void testGetPoolUuid() {
+        String poolUuid = rv.getPoolUuid();
+        assertTrue(poolUuid.equals("bed9f83e-cac3-11e1-ac8a-0050568b007e"));
+    }
+
+    @Test
+    public void testGetPool() {
+        StorageFilerTO pool = rv.getPool();
+
+        Long id = pool.getId();
+        Long expectedL = 1L;
+        assertEquals(expectedL, id);
+
+        String uuid = pool.getUuid();
+        assertTrue(uuid.equals("bed9f83e-cac3-11e1-ac8a-0050568b007e"));
+
+        String host = pool.getHost();
+        assertTrue(host.equals("hostAddress"));
+
+        String path = pool.getPath();
+        assertTrue(path.equals("path"));
+
+        String userInfo = pool.getUserInfo();
+        assertTrue(userInfo.equals("userInfo"));
+
+        Integer port = pool.getPort();
+        Integer expectedI = 25;
+        assertEquals(expectedI, port);
+
+        StoragePoolType type = pool.getType();
+        assertEquals(StoragePoolType.Filesystem, type);
+
+        String str = pool.toString();
+        assertTrue(str.equals("Pool[" + id.toString() + "|" + host + ":"
+                + port.toString() + "|" + path + "]"));
+    }
+
+    @Test
+    public void testGetNewSize() {
+        long newSize = rv.getNewSize();
+        assertTrue(newSize == 4194304L);
+    }
+
+    @Test
+    public void testGetCurrentSize() {
+        long currentSize = rv.getCurrentSize();
+        assertTrue(currentSize == 1048576L);
+    }
+
+    @Test
+    public void testGetShrinkOk() {
+        assertFalse(rv.getShrinkOk());
+    }
+
+    @Test
+    public void testGetInstanceName() {
+        String vmName = rv.getInstanceName();
+        assertTrue(vmName.equals("vmName"));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/975021dd/client/tomcatconf/commands.properties.in
----------------------------------------------------------------------
diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in
index 99cb874..182cbd8 100644
--- a/client/tomcatconf/commands.properties.in
+++ b/client/tomcatconf/commands.properties.in
@@ -255,6 +255,7 @@ deleteVolume=15
 listVolumes=15
 extractVolume=15
 migrateVolume=15
+resizeVolume=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/975021dd/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 6b5f6df..b65b531 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/975021dd/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 66a5918..065d3be 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) {
@@ -5618,6 +5622,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");
@@ -7684,4 +7705,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/975021dd/scripts/storage/qcow2/resizevolume.sh
----------------------------------------------------------------------
diff --git a/scripts/storage/qcow2/resizevolume.sh b/scripts/storage/qcow2/resizevolume.sh
new file mode 100644
index 0000000..2de1f9e
--- /dev/null
+++ b/scripts/storage/qcow2/resizevolume.sh
@@ -0,0 +1,253 @@
+#!/usr/bin/env bash
+# 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.
+
+
+
+# resizevolume.sh -- resize a volume
+
+usage() {
+  printf "Usage: %s: -c <current-volume-size> -s <new-volume-size> -p <volume path> -v <vm instance name> -t <storage-type> -r <shrink-bool>\n" $(basename $0) >&2
+}
+
+getdevmappername() {
+  local path=$1
+  local devmappername=`readlink -f $path |cut -d/ -f3`
+  if [[ $devmappername =~ "dm-" ]]
+  then
+    dmname=$devmappername
+    return 0
+  else
+    return 1;
+  fi
+}
+
+getdevmappersize() {
+  local dm=$1
+  if [ ! -e "/sys/block/${dm}/size" ]
+  then
+    log "unable to find ${dm} in /sys/block" 1
+    exit 1
+  fi
+  actualsize=$((`cat /sys/block/${dm}/size`*512));
+
+  if [[ -z "$actualsize" ]]
+  then
+    log "unable to find actual size of ${dm}" 1
+    exit 1
+  fi
+  return 0
+}
+
+# log "message" 1  <-- prints to stdout as well as log, to pass error up to cloudstack
+# log "message" prints only to log file
+# variable shouldwelog controls whether we print to log file
+log() {
+  local d=`date`
+  local msg=${1}
+  local stdout=${2}
+
+  if [ ! -z "$stdout" ]
+  then
+    echo $1
+  fi
+
+  if [ $shouldwelog -eq 1 ]
+  then
+    echo "$d - $1" >> /var/log/cloud/agent/resizevolume.log
+  fi
+}
+
+failshrink() {
+  # if this is a shrink operation, fail if commands will shrink the volume and we haven't signed of on shrinking
+  if [ $actualsize -gt $newsize ]
+  then
+    if [ "$shrink" == "false" ]
+    then
+      log "result would shrink the volume from $actualsize to $newsize, but confirmation to shrink wasn't passed. Shrink='$shrink'" 1
+      exit 1
+    fi
+  fi
+}
+
+notifyqemu() {
+  #move this back into cloudstack libvirt calls once the libvirt java bindings support block resize
+  #we try to inform hypervisor of new size, but don't fail if we can't
+  if `virsh help 2>/dev/null | grep -q blockresize`
+  then
+    if `virsh domstate $vmname >/dev/null 2>&1`
+    then
+      sizeinkb=$(($newsize/1024))
+      virsh blockresize --domain $vmname --path $path --size $sizeinkb >/dev/null 2>&1
+      retval=$?
+      if [ -z $retval ] || [ $retval -ne 0 ]
+      then
+        log "failed to live resize $path to size of $sizeinkb kb" 1
+      else
+        liveresize='true'
+      fi
+    fi
+  fi
+}
+
+resizelvm() {
+  local dmname=''
+  local actualsize=''
+  local liveresize='false'
+
+  ##### sanity checks #####
+  if ! `lvresize --version > /dev/null 2>&1`
+  then
+    log "unable to resolve executable 'lvresize'" 1
+    exit 1
+  fi
+
+  if ! `virsh --version > /dev/null 2>&1`
+  then
+    log "unable to resolve executable 'virsh'" 1
+    exit 1
+  fi
+  ##### end sanity #####
+
+  if ! getdevmappername $path
+  then
+    log "unable to resolve a device mapper dev from $path" 1
+    exit 1
+  fi
+
+  getdevmappersize $dmname
+
+  if [ $actualsize -ne $currentsize ]
+  then
+    log "disk isn't the size we think it is: cloudstack said $currentsize, disk said $actualsize."
+  fi
+
+  # if this is a shrink operation, fail if commands will shrink the volume and we haven't signed of on shrinking
+  failshrink
+
+  output=`lvresize -f -L ${newsize}B $path 2>&1`
+  retval=$?
+
+  if [ -z $retval ] || [ $retval -ne 0 ]
+  then
+    log "lvresize failed: $output " 1
+    exit 1
+  fi
+
+  #move this back into cloudstack libvirt calls once the libvirt java bindings support block resize
+  #we try to inform hypervisor of new size, but don't fail if we can't
+  notifyqemu
+
+  log "performed successful resize - dm:$dmname currentsize:$currentsize newsize:$newsize path:$path type:$ptype vmname:$vmname live:$liveresize shrink:$shrink"
+}
+
+resizeqcow2() {
+
+   ##### sanity checks #####
+  if [ ! -e "$path" ]
+  then
+    log "unable to find file $path" 1
+    exit 1
+  fi
+
+  if ! `qemu-img info /dev/null > /dev/null 2>&1`
+  then
+    log "unable to resolve executable 'qemu-img'" 1
+    exit 1
+  fi
+
+  if ! `virsh --version > /dev/null 2>&1`
+  then
+    log "unable to resolve executable 'virsh'" 1
+    exit 1
+  fi
+  ##### end sanity #####
+
+  $actualsize=`qemu-img info $path | grep "virtual size" | sed -re  's/^.*\(([0-9]+).*$/\1/g'`
+
+  if [ $actualsize -ne $currentsize ]
+  then
+    log "disk isn't the size we think it is: cloudstack said $currentsize, disk said $actualsize."
+  fi
+
+  # if this is a shrink operation, fail if commands will shrink the volume and we haven't signed of on shrinking
+  failshrink
+
+  output=`qemu-img resize $path $newsize 2>&1`
+  retval=$?
+
+  if [ -z $retval ] || [ $retval -ne 0 ]
+  then
+    log "qemu-img resize failed: $output" 1
+    exit 1
+  fi
+
+  #move this back into cloudstack libvirt calls once the libvirt java bindings support block resize
+  #we try to inform hypervisor of new size, but don't fail if we can't
+  notifyqemu
+
+  log "performed successful resize - currentsize:$currentsize newsize:$newsize path:$path type:$ptype vmname:$vmname live:$liveresize shrink:$shrink"
+}
+
+sflag=
+cflag=
+pflag=
+vflag=
+tflag=
+rflag=
+
+while getopts 'c:s:v:p:t:r:' OPTION
+do
+  case $OPTION in
+  s)	sflag=1
+		newsize="$OPTARG"
+		;;
+  c)    cflag=1
+                currentsize="$OPTARG"
+                ;;
+  v)	vflag=1
+		vmname="$OPTARG"
+		;;
+  p)	dflag=1
+		path="$OPTARG"
+		;;
+  t)    tflag=1
+                ptype="$OPTARG"
+                ;;
+  r)    rflag=1
+                shrink="$OPTARG"
+                ;;
+  ?)	usage
+		exit 2
+		;;
+  esac
+done
+
+shouldwelog=1 #set this to 1 while debugging to get output in /var/log/cloud/agent/resizevolume.log
+
+if [ "$ptype" == "CLVM" ]
+then
+  resizelvm
+elif [ "$ptype" == "QCOW2" ]
+then
+  resizeqcow2
+else
+  echo "unsupported type $ptype"
+  exit 1;
+fi
+
+exit 0

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/975021dd/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 96f299c..55b1342 100755
--- a/server/src/com/cloud/storage/StorageManagerImpl.java
+++ b/server/src/com/cloud/storage/StorageManagerImpl.java
@@ -47,6 +47,7 @@ import javax.naming.ConfigurationException;
 import org.apache.cloudstack.api.command.admin.storage.*;
 import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
 import org.apache.cloudstack.api.command.user.volume.UploadVolumeCmd;
+import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
 import org.apache.log4j.Logger;
 
 import com.cloud.agent.AgentManager;
@@ -69,6 +70,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;
@@ -2100,6 +2103,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();
+
+        /* 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() };
+            }
+
+            /*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 or disk detached in order to resize with the Xen HV");
+            }
+        }
+
+        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)) {

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/975021dd/setup/db/create-schema-view.sql
----------------------------------------------------------------------
diff --git a/setup/db/create-schema-view.sql b/setup/db/create-schema-view.sql
old mode 100755
new mode 100644

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/975021dd/test/integration/smoke/test_volumes.py
----------------------------------------------------------------------
diff --git a/test/integration/smoke/test_volumes.py b/test/integration/smoke/test_volumes.py
index 3fe68ec..36eb5de 100644
--- a/test/integration/smoke/test_volumes.py
+++ b/test/integration/smoke/test_volumes.py
@@ -54,12 +54,20 @@ class Services:
                                     "cpunumber": 1,
                                     "cpuspeed": 100,    # in MHz
                                     "memory": 128,       # In MBs
+                                    "storagetype": "local"
                         },
                         "disk_offering": {
                                     "displaytext": "Small",
                                     "name": "Small",
+                                    "storagetype": "local",
                                     "disksize": 1
                         },
+                        'resized_disk_offering': {
+                                    "displaytext": "Resized",
+                                    "name": "Resized",
+                                    "storagetype": "local",
+                                    "disksize": 3
+                        },
                         "volume_offerings": {
                             0: {
                                 "diskname": "TestDiskServ",
@@ -77,8 +85,8 @@ class Services:
                         "diskdevice": "/dev/xvdb",
                         "ostype": 'CentOS 5.3 (64-bit)',
                         "mode": 'basic',
-                        "sleep": 60,
-                        "timeout": 10,
+                        "sleep": 10,
+                        "timeout": 600,
                     }
 
 
@@ -237,7 +245,7 @@ class TestCreateVolume(cloudstackTestCase):
                 ssh = self.virtual_machine.get_ssh_client(
                                                       reconnect=True
                                                       )
-                c = "fdisk -l"
+                c = "/sbin/fdisk -l"
                 res = ssh.execute(c)
 
             except Exception as e:
@@ -283,6 +291,16 @@ class TestVolumes(cloudstackTestCase):
                                     cls.api_client,
                                     cls.services["disk_offering"]
                                     )
+        cls.resized_disk_offering = DiskOffering.create(
+                                    cls.api_client,
+                                    cls.services["resized_disk_offering"]
+                                    )
+        cls.custom_resized_disk_offering = DiskOffering.create(
+                                    cls.api_client,
+                                    cls.services["resized_disk_offering"],
+                                    custom=True
+                                    )
+
         template = get_template(
                             cls.api_client,
                             cls.zone.id,
@@ -292,6 +310,8 @@ class TestVolumes(cloudstackTestCase):
         cls.services["zoneid"] = cls.zone.id
         cls.services["template"] = template.id
         cls.services["diskofferingid"] = cls.disk_offering.id
+        cls.services['resizeddiskofferingid'] = cls.resized_disk_offering.id
+        cls.services['customresizeddiskofferingid'] = cls.custom_resized_disk_offering.id
 
         # Create VMs, VMs etc
         cls.account = Account.create(
@@ -321,6 +341,8 @@ class TestVolumes(cloudstackTestCase):
                                    domainid=cls.account.account.domainid
                                    )
         cls._cleanup = [
+                        cls.resized_disk_offering,
+                        cls.custom_resized_disk_offering,
                         cls.service_offering,
                         cls.disk_offering,
                         cls.account
@@ -500,7 +522,102 @@ class TestVolumes(cloudstackTestCase):
             )
 
     @attr(tags = ["advanced", "advancedns", "smoke"])
-    def test_07_delete_detached_volume(self):
+    def test_07_resize_fail(self):
+        """Verify invalid options fail to Resize a volume"""
+        # Verify the size is the new size is what we wanted it to be.
+        self.debug("Fail Resize Volume ID: %s" % self.volume.id)
+
+        # first, an invalid id
+        cmd                = resizeVolume.resizeVolumeCmd()
+        cmd.id             = "invalid id"
+        cmd.diskofferingid = self.services['resizeddiskofferingid']
+        success            = False
+        try:
+            response = self.apiClient.resizeVolume(cmd)
+        except Exception as ex:
+            if str(ex) == "HTTP Error 431: 431":
+                success = True
+        self.assertEqual(success, True, "ResizeVolume - verify invalid id is handled appropriately")
+
+        # Next, we'll try an invalid disk offering id
+        cmd.id             = self.volume.id
+        cmd.diskofferingid = "invalid id"
+        success            = False
+        try:
+            response = self.apiClient.resizeVolume(cmd)
+        except Exception as ex:
+            if "need to specify a disk offering" in str(ex):
+                success = True
+        self.assertEqual(success, True, "ResizeVolume - verify disk offering is handled appropriately")
+
+        # Ok, now let's try and resize a volume that is not custom.
+        cmd.id             = self.volume.id
+        cmd.diskofferingid = self.services['diskofferingid']
+        cmd.size           = 4
+        currentSize        = self.volume.size
+
+        self.apiClient.resizeVolume(cmd)
+        count = 0
+        success = True
+        while count < 10:
+            list_volume_response = list_volumes(
+                                                self.apiClient,
+                                                id=self.volume.id,
+                                                type='DATADISK'
+                                                )
+            for vol in list_volume_response:
+                if vol.id == self.volume.id and vol.size != currentSize:
+                    success = False
+            if success:
+                break
+            else:
+                time.sleep(1)
+                count += 1
+
+        self.assertEqual(
+                         success,
+                         True,
+                         "Verify the volume did not resize"
+                         )
+
+
+    @attr(tags = ["advanced", "advancedns", "smoke"])
+    def test_08_resize_volume(self):
+        """Resize a volume"""
+        # Verify the size is the new size is what we wanted it to be.
+        self.debug("Resize Volume ID: %s" % self.volume.id)
+
+        cmd                = resizeVolume.resizeVolumeCmd()
+        cmd.id             = self.volume.id
+        cmd.diskofferingid = self.services['resizeddiskofferingid']
+
+        self.apiClient.resizeVolume(cmd)
+
+        count = 0
+        success = False
+        while count < 3:
+            list_volume_response = list_volumes(
+                                                self.apiClient,
+                                                id=self.volume.id,
+                                                type='DATADISK'
+                                                )
+            for vol in list_volume_response:
+                if vol.id == self.volume.id and vol.size == 3221225472L:
+                    success = True
+            if success:
+                break
+            else:
+                time.sleep(10)
+                count += 1
+
+        self.assertEqual(
+                         success,
+                         True,
+                         "Check if the volume resized appropriately"
+                         )
+
+    @attr(tags = ["advanced", "advancedns", "smoke"])
+    def test_09_delete_detached_volume(self):
         """Delete a Volume unattached to an VM
         """
         # Validate the following

http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/975021dd/tools/marvin/marvin/integration/lib/base.py
----------------------------------------------------------------------
diff --git a/tools/marvin/marvin/integration/lib/base.py b/tools/marvin/marvin/integration/lib/base.py
index 726a628..87b0bbb 100644
--- a/tools/marvin/marvin/integration/lib/base.py
+++ b/tools/marvin/marvin/integration/lib/base.py
@@ -509,6 +509,12 @@ class Volume:
         [setattr(cmd, k, v) for k, v in kwargs.items()]
         return(apiclient.listVolumes(cmd))
 
+    def resize(cls, apiclient, **kwargs):
+        """Resize a volume"""
+        cmd = resizeVolume.resizeVolumeCmd()
+        cmd.id = self.id
+        [setattr(cmd, k, v) for k, v in kwargs.items()]
+        return(apiclient.resizeVolume(cmd))
 
 class Snapshot:
     """Manage Snapshot Lifecycle


Mime
View raw message