diff --git a/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index 6ad092e06f0..14147741229 100755 --- a/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -121,6 +121,9 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd { @Parameter(name = ApiConstants.SIZE, type = CommandType.LONG, description = "the arbitrary size for the DATADISK volume. Mutually exclusive with diskOfferingId") private Long size; + @Parameter(name = ApiConstants.ROOT_DISK_SIZE, type = CommandType.LONG, description = "Optional field to resize root disk on deploy. Only applies to template-based deployments. Analogous to details[0].rootdisksize, which takes precedence over this parameter if both are provided") + private Long rootdisksize; + @Parameter(name = ApiConstants.GROUP, type = CommandType.STRING, description = "an optional group for the virtual machine") private String group; @@ -226,6 +229,9 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd { } } } + if (rootdisksize != null && !customparameterMap.containsKey("rootdisksize")) { + customparameterMap.put("rootdisksize", rootdisksize.toString()); + } return customparameterMap; } diff --git a/debian/rules b/debian/rules index 4edf8930605..197e243bcfe 100755 --- a/debian/rules +++ b/debian/rules @@ -35,7 +35,7 @@ build: build-indep build-indep: build-indep-stamp build-indep-stamp: configure - mvn clean package -Pawsapi -DskipTests -Dsystemvm \ + mvn -T C1.5 clean package -Pawsapi -DskipTests -Dsystemvm \ -Dcs.replace.properties=replace.properties.tmp \ ${ACS_BUILD_OPTS} touch $@ diff --git a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java index cfa788f0878..59211284297 100644 --- a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java +++ b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@ -612,8 +612,14 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati assert (template.getFormat() != ImageFormat.ISO) : "ISO is not a template really...."; Long size = _tmpltMgr.getTemplateSize(template.getId(), vm.getDataCenterId()); - if (rootDisksize != null) { - size = (rootDisksize * 1024 * 1024 * 1024); + if (rootDisksize != null ) { + rootDisksize = rootDisksize * 1024 * 1024 * 1024; + if (rootDisksize > size) { + s_logger.debug("Using root disk size of " + rootDisksize + " for volume " + name); + size = rootDisksize; + } else { + s_logger.debug("Using root disk size of " + size + " for volume " + name + "since specified root disk size of " + rootDisksize + " is smaller than template"); + } } minIops = minIops != null ? minIops : offering.getMinIops(); 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 ec8bc110372..49f23f5a13a 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 @@ -1827,6 +1827,12 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv boolean shrinkOk = cmd.getShrinkOk(); StorageFilerTO spool = cmd.getPool(); + if ( currentSize == newSize) { + // nothing to do + s_logger.info("No need to resize volume: current size " + currentSize + " is same as new size " + newSize); + return new ResizeVolumeAnswer(cmd, true, "success", currentSize); + } + try { KVMStoragePool pool = _storagePoolMgr.getStoragePool(spool.getType(), spool.getUuid()); KVMPhysicalDisk vol = pool.getPhysicalDisk(volid); diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java index 9e07e4bea29..583d48af524 100644 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java @@ -302,17 +302,21 @@ public class KVMStoragePoolManager { } public KVMPhysicalDisk createDiskFromTemplate(KVMPhysicalDisk template, String name, KVMStoragePool destPool, int timeout) { + return createDiskFromTemplate(template, name, destPool, template.getSize(), timeout); + } + + public KVMPhysicalDisk createDiskFromTemplate(KVMPhysicalDisk template, String name, KVMStoragePool destPool, long size, int timeout) { StorageAdaptor adaptor = getStorageAdaptor(destPool.getType()); // LibvirtStorageAdaptor-specific statement if (destPool.getType() == StoragePoolType.RBD) { - return adaptor.createDiskFromTemplate(template, name, PhysicalDiskFormat.RAW, template.getSize(), destPool, timeout); + return adaptor.createDiskFromTemplate(template, name, PhysicalDiskFormat.RAW, size, destPool, timeout); } else if (destPool.getType() == StoragePoolType.CLVM) { - return adaptor.createDiskFromTemplate(template, name, PhysicalDiskFormat.RAW, template.getSize(), destPool, timeout); + return adaptor.createDiskFromTemplate(template, name, PhysicalDiskFormat.RAW, size, destPool, timeout); } else if (template.getFormat() == PhysicalDiskFormat.DIR) { - return adaptor.createDiskFromTemplate(template, name, PhysicalDiskFormat.DIR, template.getSize(), destPool, timeout); + return adaptor.createDiskFromTemplate(template, name, PhysicalDiskFormat.DIR, size, destPool, timeout); } else { - return adaptor.createDiskFromTemplate(template, name, PhysicalDiskFormat.QCOW2, template.getSize(), destPool, timeout); + return adaptor.createDiskFromTemplate(template, name, PhysicalDiskFormat.QCOW2, size, destPool, timeout); } } diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java index 2ea3b42d0fa..98133890bec 100644 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java @@ -182,13 +182,14 @@ public class KVMStorageProcessor implements StorageProcessor { break; } } - if (tmplVol == null) { - return new PrimaryStorageDownloadAnswer("Failed to get template from pool: " + secondaryPool.getUuid()); - } } else { tmplVol = secondaryPool.getPhysicalDisk(tmpltname); } + if (tmplVol == null) { + return new PrimaryStorageDownloadAnswer("Failed to get template from pool: " + secondaryPool.getUuid()); + } + /* Copy volume to primary storage */ s_logger.debug("Copying template to primary storage, template format is " + tmplVol.getFormat() ); KVMStoragePool primaryPool = storagePoolMgr.getStoragePool(primaryStore.getPoolType(), primaryStore.getUuid()); @@ -196,6 +197,14 @@ public class KVMStorageProcessor implements StorageProcessor { KVMPhysicalDisk primaryVol = null; if (destData instanceof VolumeObjectTO) { VolumeObjectTO volume = (VolumeObjectTO)destData; + // pass along volume's target size if it's bigger than template's size, for storage types that copy template rather than cloning on deploy + if (volume.getSize() != null && volume.getSize() > tmplVol.getVirtualSize()) { + s_logger.debug("Using configured size of " + volume.getSize()); + tmplVol.setSize(volume.getSize()); + tmplVol.setVirtualSize(volume.getSize()); + } else { + s_logger.debug("Using template's size of " + tmplVol.getVirtualSize()); + } primaryVol = storagePoolMgr.copyPhysicalDisk(tmplVol, volume.getUuid(), primaryPool, cmd.getWaitInMillSeconds()); } else if (destData instanceof TemplateObjectTO) { TemplateObjectTO destTempl = (TemplateObjectTO)destData; @@ -239,7 +248,7 @@ public class KVMStorageProcessor implements StorageProcessor { } // this is much like PrimaryStorageDownloadCommand, but keeping it separate. copies template direct to root disk - private KVMPhysicalDisk templateToPrimaryDownload(String templateUrl, KVMStoragePool primaryPool, String volUuid, int timeout) { + private KVMPhysicalDisk templateToPrimaryDownload(String templateUrl, KVMStoragePool primaryPool, String volUuid, Long size, int timeout) { int index = templateUrl.lastIndexOf("/"); String mountpoint = templateUrl.substring(0, index); String templateName = null; @@ -275,6 +284,14 @@ public class KVMStorageProcessor implements StorageProcessor { /* Copy volume to primary storage */ + if (size > templateVol.getSize()) { + s_logger.debug("Overriding provided template's size with new size " + size); + templateVol.setSize(size); + templateVol.setVirtualSize(size); + } else { + s_logger.debug("Using templates disk size of " + templateVol.getVirtualSize() + "since size passed was " + size); + } + KVMPhysicalDisk primaryVol = storagePoolMgr.copyPhysicalDisk(templateVol, volUuid, primaryPool, timeout); return primaryVol; } catch (CloudRuntimeException e) { @@ -306,14 +323,14 @@ public class KVMStorageProcessor implements StorageProcessor { if (primaryPool.getType() == StoragePoolType.CLVM) { templatePath = ((NfsTO)imageStore).getUrl() + File.separator + templatePath; - vol = templateToPrimaryDownload(templatePath, primaryPool, volume.getUuid(), cmd.getWaitInMillSeconds()); + vol = templateToPrimaryDownload(templatePath, primaryPool, volume.getUuid(), volume.getSize(), cmd.getWaitInMillSeconds()); } else { if (templatePath.contains("/mnt")) { //upgrade issue, if the path contains path, need to extract the volume uuid from path templatePath = templatePath.substring(templatePath.lastIndexOf(File.separator) + 1); } BaseVol = storagePoolMgr.getPhysicalDisk(primaryStore.getPoolType(), primaryStore.getUuid(), templatePath); - vol = storagePoolMgr.createDiskFromTemplate(BaseVol, volume.getUuid(), BaseVol.getPool(), cmd.getWaitInMillSeconds()); + vol = storagePoolMgr.createDiskFromTemplate(BaseVol, volume.getUuid(), BaseVol.getPool(), volume.getSize(), cmd.getWaitInMillSeconds()); } if (vol == null) { return new CopyCmdAnswer(" Can't create storage volume on storage pool"); diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java index 38ce32b69ea..5de8bd26ae2 100644 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/LibvirtStorageAdaptor.java @@ -823,11 +823,21 @@ public class LibvirtStorageAdaptor implements StorageAdaptor { } else if (format == PhysicalDiskFormat.QCOW2) { QemuImgFile backingFile = new QemuImgFile(template.getPath(), template.getFormat()); QemuImgFile destFile = new QemuImgFile(disk.getPath()); + if (size > template.getVirtualSize()) { + destFile.setSize(size); + } else { + destFile.setSize(template.getVirtualSize()); + } QemuImg qemu = new QemuImg(timeout); qemu.create(destFile, backingFile); } else if (format == PhysicalDiskFormat.RAW) { QemuImgFile sourceFile = new QemuImgFile(template.getPath(), template.getFormat()); QemuImgFile destFile = new QemuImgFile(disk.getPath(), PhysicalDiskFormat.RAW); + if (size > template.getVirtualSize()) { + destFile.setSize(size); + } else { + destFile.setSize(template.getVirtualSize()); + } QemuImg qemu = new QemuImg(timeout); qemu.convert(sourceFile, destFile); } @@ -835,8 +845,14 @@ public class LibvirtStorageAdaptor implements StorageAdaptor { format = PhysicalDiskFormat.RAW; disk = new KVMPhysicalDisk(destPool.getSourceDir() + "/" + newUuid, newUuid, destPool); disk.setFormat(format); - disk.setSize(template.getVirtualSize()); - disk.setVirtualSize(disk.getSize()); + if (size > template.getVirtualSize()) { + disk.setSize(size); + disk.setVirtualSize(size); + } else { + // leave these as they were if size isn't applicable + disk.setSize(template.getVirtualSize()); + disk.setVirtualSize(disk.getSize()); + } QemuImg qemu = new QemuImg(timeout); QemuImgFile srcFile; @@ -844,6 +860,11 @@ public class LibvirtStorageAdaptor implements StorageAdaptor { new QemuImgFile(KVMPhysicalDisk.RBDStringBuilder(destPool.getSourceHost(), destPool.getSourcePort(), destPool.getAuthUserName(), destPool.getAuthSecret(), disk.getPath())); destFile.setFormat(format); + if (size > template.getVirtualSize()) { + destFile.setSize(size); + } else { + destFile.setSize(template.getVirtualSize()); + } if (srcPool.getType() != StoragePoolType.RBD) { srcFile = new QemuImgFile(template.getPath(), template.getFormat()); @@ -877,9 +898,9 @@ public class LibvirtStorageAdaptor implements StorageAdaptor { if (srcImage.isOldFormat()) { /* The source image is RBD format 1, we have to do a regular copy */ s_logger.debug("The source image " + srcPool.getSourceDir() + "/" + template.getName() + - " is RBD format 1. We have to perform a regular copy (" + template.getVirtualSize() + " bytes)"); + " is RBD format 1. We have to perform a regular copy (" + disk.getVirtualSize() + " bytes)"); - rbd.create(disk.getName(), template.getVirtualSize(), rbdFeatures, rbdOrder); + rbd.create(disk.getName(), disk.getVirtualSize(), rbdFeatures, rbdOrder); RbdImage destImage = rbd.open(disk.getName()); s_logger.debug("Starting to copy " + srcImage.getName() + " to " + destImage.getName() + " in Ceph pool " + srcPool.getSourceDir()); @@ -923,7 +944,7 @@ public class LibvirtStorageAdaptor implements StorageAdaptor { s_logger.debug("Creating " + disk.getName() + " on the destination cluster " + rDest.confGet("mon_host") + " in pool " + destPool.getSourceDir()); - dRbd.create(disk.getName(), template.getVirtualSize(), rbdFeatures, rbdOrder); + dRbd.create(disk.getName(), disk.getVirtualSize(), rbdFeatures, rbdOrder); RbdImage srcImage = sRbd.open(template.getName()); RbdImage destImage = dRbd.open(disk.getName()); diff --git a/plugins/hypervisors/kvm/src/org/apache/cloudstack/utils/qemu/QemuImg.java b/plugins/hypervisors/kvm/src/org/apache/cloudstack/utils/qemu/QemuImg.java index b380815d04b..4bec37535e2 100644 --- a/plugins/hypervisors/kvm/src/org/apache/cloudstack/utils/qemu/QemuImg.java +++ b/plugins/hypervisors/kvm/src/org/apache/cloudstack/utils/qemu/QemuImg.java @@ -111,10 +111,12 @@ public class QemuImg { } s.add(file.getFileName()); - - if (backingFile == null) { + if (file.getSize() != 0L) { s.add(Long.toString(file.getSize())); + } else if (backingFile == null) { + throw new QemuImgException("No size was passed, and no backing file was passed"); } + String result = s.execute(); if (result != null) { throw new QemuImgException(result); @@ -206,6 +208,10 @@ public class QemuImg { if (result != null) { throw new QemuImgException(result); } + + if (srcFile.getSize() < destFile.getSize()) { + this.resize(destFile, destFile.getSize()); + } } /** diff --git a/server/src/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/com/cloud/storage/VolumeApiServiceImpl.java index 30b5479b630..d820d02ac68 100644 --- a/server/src/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/com/cloud/storage/VolumeApiServiceImpl.java @@ -701,27 +701,17 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic 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/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"); } - /* Only works for KVM/Xen for now */ - if (_volsDao.getHypervisorType(volume.getId()) != HypervisorType.KVM && _volsDao.getHypervisorType(volume.getId()) != HypervisorType.XenServer - && _volsDao.getHypervisorType(volume.getId()) != HypervisorType.VMware) { - throw new InvalidParameterValueException("Cloudstack currently only supports volumes marked as KVM or XenServer hypervisor for resize"); - } - - 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"); + 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()); } /* @@ -729,7 +719,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic * required, get the correct size value */ if (newDiskOffering == null) { - if (diskOffering.isCustomized()) { + if (diskOffering.isCustomized() || volume.getVolumeType().equals(Volume.Type.ROOT)) { newSize = cmd.getSize(); if (newSize == null) { @@ -741,6 +731,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic throw new InvalidParameterValueException("current offering" + volume.getDiskOfferingId() + " cannot be resized, need to specify a disk offering"); } } else { + if (!volume.getVolumeType().equals(Volume.Type.DATADISK)) { + throw new InvalidParameterValueException("Can only resize Data volumes via new disk offering"); + } if (newDiskOffering.getRemoved() != null || !DiskOfferingVO.Type.Disk.equals(newDiskOffering.getType())) { throw new InvalidParameterValueException("Disk offering ID is missing or invalid"); @@ -784,8 +777,6 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic /* does the caller have the authority to act on this volume? */ _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, volume); - UserVmVO userVm = _userVmDao.findById(volume.getInstanceId()); - long currentSize = volume.getSize(); /* @@ -805,6 +796,20 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic - currentSize)); } + /* 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"); + volume.setSize(newSize); + 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(); diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 393613a140e..f9063cf7d7f 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -2835,11 +2835,31 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir vm.setIsoId(template.getId()); } Long rootDiskSize = null; + // custom root disk size, resizes base template to larger size if (customParameters.containsKey("rootdisksize")) { if (NumbersUtil.parseLong(customParameters.get("rootdisksize"), -1) <= 0) { throw new InvalidParameterValueException("rootdisk size should be a non zero number."); } - rootDiskSize = Long.parseLong(customParameters.get("rootDisksize")); + rootDiskSize = Long.parseLong(customParameters.get("rootdisksize")); + + // only KVM supports rootdisksize override + if (hypervisor != HypervisorType.KVM) { + throw new InvalidParameterValueException("Hypervisor " + hypervisor + " does not support rootdisksize override"); + } + + // rotdisksize must be larger than template + VMTemplateVO templateVO = _templateDao.findById(template.getId()); + if (templateVO == null) { + throw new InvalidParameterValueException("Unable to look up template by id " + template.getId()); + } + + if ((rootDiskSize << 30) < templateVO.getSize()) { + throw new InvalidParameterValueException("unsupported: rootdisksize override is smaller than template size " + templateVO.getSize()); + } else { + s_logger.debug("rootdisksize of " + (rootDiskSize << 30) + " was larger than template size of " + templateVO.getSize()); + } + + s_logger.debug("found root disk size of " + rootDiskSize); customParameters.remove("rootdisksize"); } diff --git a/test/integration/smoke/test_deploy_vm_root_resize.py b/test/integration/smoke/test_deploy_vm_root_resize.py new file mode 100644 index 00000000000..e17d2df1650 --- /dev/null +++ b/test/integration/smoke/test_deploy_vm_root_resize.py @@ -0,0 +1,263 @@ +# 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. + +#Test from the Marvin - Testing in Python wiki + +#All tests inherit from cloudstackTestCase +from marvin.cloudstackTestCase import cloudstackTestCase + +#Import Integration Libraries + +#base - contains all resources as entities and defines create, delete, list operations on them +from marvin.integration.lib.base import Account, VirtualMachine, ServiceOffering + +#utils - utility classes for common cleanup, external library wrappers etc +from marvin.integration.lib.utils import cleanup_resources + +#common - commonly used methods for all tests are listed here +from marvin.integration.lib.common import get_zone, get_domain, get_template, list_volumes + +from nose.plugins.attrib import attr + +class TestData(object): + """Test data object that is required to create resources + """ + def __init__(self): + self.testdata = { + #data to create an account + "account": { + "email": "test@test.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + "password": "password", + }, + #data reqd for virtual machine creation + "virtual_machine" : { + "name" : "testvm", + "displayname" : "Test VM", + }, + #small service offering + "service_offering": { + "small": { + "name": "Small Instance", + "displaytext": "Small Instance", + "cpunumber": 1, + "cpuspeed": 100, + "memory": 256, + }, + }, + "ostype": 'CentOS 5.3 (64-bit)', + } + +class TestDeployVM(cloudstackTestCase): + """Test deploy a VM into a user account + """ + + def setUp(self): + self.testdata = TestData().testdata + self.apiclient = self.testClient.getApiClient() + + # Get Zone, Domain and Default Built-in template + self.domain = get_domain(self.apiclient, self.testdata) + self.zone = get_zone(self.apiclient, self.testdata) + self.testdata["mode"] = self.zone.networktype + self.template = get_template(self.apiclient, self.zone.id, self.testdata["ostype"]) +# for testing with specific template +# self.template = get_template(self.apiclient, self.zone.id, self.testdata["ostype"], templatetype='USER', services = {"template":'31f52a4d-5681-43f7-8651-ad4aaf823618'}) + + + #create a user account + self.account = Account.create( + self.apiclient, + self.testdata["account"], + domainid=self.domain.id + ) + #create a service offering + self.service_offering = ServiceOffering.create( + self.apiclient, + self.testdata["service_offering"]["small"] + ) + #build cleanup list + self.cleanup = [ + self.service_offering, + self.account + ] + + @attr(tags = ['advanced', 'simulator', 'basic', 'sg']) + def test_00_deploy_vm_root_resize(self): + """Test deploy virtual machine with root resize + + # Validate the following: + # 1. listVirtualMachines returns accurate information + # 2. root disk has new size per listVolumes + # 3. Rejects non-supported hypervisor types + """ + if(self.apiclient.hypervisor == 'kvm'): + newrootsize = (self.template.size >> 30) + 2 + self.virtual_machine = VirtualMachine.create( + self.apiclient, + self.testdata["virtual_machine"], + accountid=self.account.name, + zoneid=self.zone.id, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + templateid=self.template.id, + rootdisksize=newrootsize + ) + + list_vms = VirtualMachine.list(self.apiclient, id=self.virtual_machine.id) + + self.debug( + "Verify listVirtualMachines response for virtual machine: %s"\ + % self.virtual_machine.id + ) + + self.assertEqual( + isinstance(list_vms, list), + True, + "List VM response was not a valid list" + ) + self.assertNotEqual( + len(list_vms), + 0, + "List VM response was empty" + ) + + vm = list_vms[0] + self.assertEqual( + vm.id, + self.virtual_machine.id, + "Virtual Machine ids do not match" + ) + self.assertEqual( + vm.name, + self.virtual_machine.name, + "Virtual Machine names do not match" + ) + self.assertEqual( + vm.state, + "Running", + msg="VM is not in Running state" + ) + + # get root vol from created vm, verify it is correct size + list_volume_response = list_volumes( + self.apiclient, + virtualmachineid=self.virtual_machine.id, + type='ROOT', + listall=True + ) + + rootvolume = list_volume_response[0] + success = False + if rootvolume is not None and rootvolume.size == (newrootsize << 30): + success = True + + self.assertEqual( + success, + True, + "Check if the root volume resized appropriately" + ) + else: + self.debug("hypervisor %s unsupported for test 00, verifying it errors properly" % self.apiclient.hypervisor) + + newrootsize = (self.template.size >> 30) + 2 + success = False + try: + self.virtual_machine = VirtualMachine.create( + self.apiclient, + self.testdata["virtual_machine"], + accountid=self.account.name, + zoneid=self.zone.id, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + templateid=self.template.id, + rootdisksize=newrootsize + ) + except Exception as ex: + if "Hypervisor XenServer does not support rootdisksize override" in str(ex): + success = True + else: + self.debug("virtual machine create did not fail appropriately. Error was actually : " + str(ex)); + + self.assertEqual(success, True, "Check if unsupported hypervisor %s fails appropriately" % self.apiclient.hypervisor) + + @attr(tags = ['advanced', 'simulator', 'basic', 'sg']) + def test_01_deploy_vm_root_resize(self): + """Test proper failure to deploy virtual machine with rootdisksize of 0 + """ + if (self.apiclient.hypervisor == 'kvm'): + newrootsize = 0 + success = False + try: + self.virtual_machine = VirtualMachine.create( + self.apiclient, + self.testdata["virtual_machine"], + accountid=self.account.name, + zoneid=self.zone.id, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + templateid=self.template.id, + rootdisksize=newrootsize + ) + except Exception as ex: + if "rootdisk size should be a non zero number" in str(ex): + success = True + else: + self.debug("virtual machine create did not fail appropriately. Error was actually : " + str(ex)); + + self.assertEqual(success, True, "Check if passing 0 as rootdisksize fails appropriately") + else: + self.debug("test 01 does not support hypervisor type " + self.apiclient.hypervisor); + + @attr(tags = ['advanced', 'simulator', 'basic', 'sg']) + def test_02_deploy_vm_root_resize(self): + """Test proper failure to deploy virtual machine with rootdisksize less than template size + """ + if (self.apiclient.hypervisor == 'kvm'): + newrootsize = (self.template.size >> 30) - 1 + + self.assertEqual(newrootsize > 0, True, "Provided template is less than 1G in size, cannot run test") + + success = False + try: + self.virtual_machine = VirtualMachine.create( + self.apiclient, + self.testdata["virtual_machine"], + accountid=self.account.name, + zoneid=self.zone.id, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + templateid=self.template.id, + rootdisksize=newrootsize + ) + except Exception as ex: + if "rootdisksize override is smaller than template size" in str(ex): + success = True + else: + self.debug("virtual machine create did not fail appropriately. Error was actually : " + str(ex)); + + self.assertEqual(success, True, "Check if passing rootdisksize < templatesize fails appropriately") + else: + self.debug("test 01 does not support hypervisor type " + self.apiclient.hypervisor); + + def tearDown(self): + try: + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + self.debug("Warning! Exception in tearDown: %s" % e) diff --git a/test/integration/smoke/test_volumes.py b/test/integration/smoke/test_volumes.py index 8f418036385..719c824b732 100644 --- a/test/integration/smoke/test_volumes.py +++ b/test/integration/smoke/test_volumes.py @@ -571,6 +571,31 @@ class TestVolumes(cloudstackTestCase): success, True, "ResizeVolume - verify disk offering is handled appropriately") + + # try to resize a root disk with a disk offering, root can only be resized by size= + # get root vol from created vm + list_volume_response = list_volumes( + self.apiClient, + virtualmachineid=self.virtual_machine.id, + type='ROOT', + listall=True + ) + + rootvolume = list_volume_response[0] + + cmd.id = rootvolume.id + cmd.diskofferingid = self.services['diskofferingid'] + success = False + try: + response = self.apiClient.resizeVolume(cmd) + except Exception as ex: + if "Can only resize Data volumes" in str(ex): + success = True + self.assertEqual( + success, + True, + "ResizeVolume - verify root disks cannot be resized by disk offering id") + # Ok, now let's try and resize a volume that is not custom. cmd.id = self.volume.id cmd.diskofferingid = self.services['diskofferingid'] @@ -647,6 +672,7 @@ class TestVolumes(cloudstackTestCase): elif hosts[0].hypervisor.lower() == "vmware": self.skipTest("Resize Volume is unsupported on VmWare") + # resize the data disk self.debug("Resize Volume ID: %s" % self.volume.id) cmd = resizeVolume.resizeVolumeCmd() @@ -675,7 +701,48 @@ class TestVolumes(cloudstackTestCase): self.assertEqual( success, True, - "Check if the volume resized appropriately" + "Check if the data volume resized appropriately" + ) + + # resize the root disk + self.debug("Resize Root for : %s" % self.virtual_machine.id) + + # get root vol from created vm + list_volume_response = list_volumes( + self.apiClient, + virtualmachineid=self.virtual_machine.id, + type='ROOT', + listall=True + ) + + rootvolume = list_volume_response[0] + + cmd = resizeVolume.resizeVolumeCmd() + cmd.id = rootvolume.id + cmd.size = 10 + + self.apiClient.resizeVolume(cmd) + + count = 0 + success = False + while count < 3: + list_volume_response = list_volumes( + self.apiClient, + id=rootvolume.id + ) + for vol in list_volume_response: + if vol.id == rootvolume.id and vol.size == 10737418240L and vol.state == 'Ready': + success = True + if success: + break + else: + time.sleep(10) + count += 1 + + self.assertEqual( + success, + True, + "Check if the root volume resized appropriately" ) #start the vm if it is on xenserver diff --git a/tools/devcloud-kvm/devcloud-kvm-advanced-fusion.cfg b/tools/devcloud-kvm/devcloud-kvm-advanced-fusion.cfg index 53e1a441b26..2084417cbea 100644 --- a/tools/devcloud-kvm/devcloud-kvm-advanced-fusion.cfg +++ b/tools/devcloud-kvm/devcloud-kvm-advanced-fusion.cfg @@ -120,20 +120,14 @@ "port": 3306, "user": "cloud" }, - "logger": [ - { - "name": "TestClient", - "file": "/var/log/testclient.log" - }, - { - "name": "TestCase", - "file": "/var/log/testcase.log" - } - ], + "logger": { + "LogFolderPath": "/tmp/" + }, "mgtSvr": [ { "mgtSvrIp": "172.17.10.10", - "port": 8096 + "port": 8096, + "hypervisor": "kvm" } ] } diff --git a/tools/devcloud-kvm/devcloud-kvm-advanced.cfg b/tools/devcloud-kvm/devcloud-kvm-advanced.cfg index 53e1a441b26..74c36b718d7 100644 --- a/tools/devcloud-kvm/devcloud-kvm-advanced.cfg +++ b/tools/devcloud-kvm/devcloud-kvm-advanced.cfg @@ -133,7 +133,8 @@ "mgtSvr": [ { "mgtSvrIp": "172.17.10.10", - "port": 8096 + "port": 8096, + "hypervisor": "kvm" } ] } diff --git a/tools/devcloud-kvm/devcloud-kvm.cfg b/tools/devcloud-kvm/devcloud-kvm.cfg index 3122e5a9790..ab7f9a52762 100644 --- a/tools/devcloud-kvm/devcloud-kvm.cfg +++ b/tools/devcloud-kvm/devcloud-kvm.cfg @@ -104,7 +104,8 @@ "mgtSvr": [ { "mgtSvrIp": "127.0.0.1", - "port": 8096 + "port": 8096, + "hypervisor": "kvm" } ], "dbSvr": diff --git a/tools/marvin/marvin/integration/lib/base.py b/tools/marvin/marvin/integration/lib/base.py index b7e2be47557..ca9f2b440d5 100755 --- a/tools/marvin/marvin/integration/lib/base.py +++ b/tools/marvin/marvin/integration/lib/base.py @@ -325,7 +325,7 @@ class VirtualMachine: securitygroupids=None, projectid=None, startvm=None, diskofferingid=None, affinitygroupnames=None, affinitygroupids=None, group=None, hostid=None, keypair=None, ipaddress=None, mode='default', method='GET', - customcpunumber=None, customcpuspeed=None, custommemory=None): + customcpunumber=None, customcpuspeed=None, custommemory=None, rootdisksize=None): """Create the instance""" cmd = deployVirtualMachine.deployVirtualMachineCmd() @@ -413,7 +413,7 @@ class VirtualMachine: if "userdata" in services: cmd.userdata = base64.urlsafe_b64encode(services["userdata"]) - cmd.details = [{"cpuNumber": "","cpuSpeed":"","memory":""}] + cmd.details = [{"cpuNumber": "","cpuSpeed":"","memory":"","rootdisksize":""}] if customcpunumber: cmd.details[0]["cpuNumber"] = customcpunumber @@ -424,6 +424,9 @@ class VirtualMachine: if custommemory: cmd.details[0]["memory"] = custommemory + if rootdisksize: + cmd.details[0]["rootdisksize"] = rootdisksize + if group: cmd.group = group