CLOUDSTACK-9604: Root disk resize support for VMware and XenServer.

This commit is contained in:
Anshul And Priyank 2016-12-02 21:46:49 +05:30 committed by Priyank Parihar
parent 850c07cc8a
commit ec66256149
11 changed files with 224 additions and 61 deletions

View File

@ -702,7 +702,17 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
"Please re-try when virtual disk is attached to a VM using SCSI controller.");
}
if (vdisk.second() != null && !vdisk.second().toLowerCase().startsWith("scsi"))
{
s_logger.error("Unsupported disk device bus "+ vdisk.second());
throw new Exception("Unsupported disk device bus "+ vdisk.second());
}
VirtualDisk disk = vdisk.first();
if ((VirtualDiskFlatVer2BackingInfo)disk.getBacking() != null && ((VirtualDiskFlatVer2BackingInfo)disk.getBacking()).getParent() != null)
{
s_logger.error("Resize is not supported because Disk device has Parent "+ ((VirtualDiskFlatVer2BackingInfo)disk.getBacking()).getParent().getUuid());
throw new Exception("Resize is not supported because Disk device has Parent "+ ((VirtualDiskFlatVer2BackingInfo)disk.getBacking()).getParent().getUuid());
}
String vmdkAbsFile = getAbsoluteVmdkFile(disk);
if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) {
vmMo.updateAdapterTypeIfRequired(vmdkAbsFile);
@ -1514,7 +1524,7 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
String vmNameOnVcenter = names.second();
String dataDiskController = vmSpec.getDetails().get(VmDetailConstants.DATA_DISK_CONTROLLER);
String rootDiskController = vmSpec.getDetails().get(VmDetailConstants.ROOT_DISK_CONTROLLER);
DiskTO rootDiskTO = null;
// If root disk controller is scsi, then data disk controller would also be scsi instead of using 'osdefault'
// This helps avoid mix of different scsi subtype controllers in instance.
if (DiskControllerType.lsilogic == DiskControllerType.getType(rootDiskController)) {
@ -1882,6 +1892,8 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
volumeDsDetails.first(),
(controllerKey == vmMo.getIDEControllerKey(ideUnitNumber)) ? ((ideUnitNumber++) % VmwareHelper.MAX_IDE_CONTROLLER_COUNT) : scsiUnitNumber++, i + 1);
if (vol.getType() == Volume.Type.ROOT)
rootDiskTO = vol;
deviceConfigSpecArray[i].setDevice(device);
deviceConfigSpecArray[i].setOperation(VirtualDeviceConfigSpecOperation.ADD);
@ -2012,6 +2024,10 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
throw new Exception("Failed to configure VM before start. vmName: " + vmInternalCSName);
}
//For resizing root disk.
if (rootDiskTO != null && !hasSnapshot) {
resizeRootDisk(vmMo, rootDiskTO, hyperHost, context);
}
//
// Post Configuration
//
@ -2071,6 +2087,43 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
}
}
private void resizeRootDisk(VirtualMachineMO vmMo, DiskTO rootDiskTO, VmwareHypervisorHost hyperHost, VmwareContext context) throws Exception
{
Pair<VirtualDisk, String> vdisk = getVirtualDiskInfo(vmMo, rootDiskTO.getPath() + ".vmdk");
assert(vdisk != null);
Long reqSize=((VolumeObjectTO)rootDiskTO.getData()).getSize()/1024;
VirtualDisk disk = vdisk.first();
if (reqSize > disk.getCapacityInKB())
{
VirtualMachineDiskInfo diskInfo = getMatchingExistingDisk(vmMo.getDiskInfoBuilder(), rootDiskTO, hyperHost, context);
assert (diskInfo != null);
String[] diskChain = diskInfo.getDiskChain();
if (diskChain != null && diskChain.length>1)
{
s_logger.error("Unsupported Disk chain length "+ diskChain.length);
throw new Exception("Unsupported Disk chain length "+ diskChain.length);
}
if (diskInfo.getDiskDeviceBusName() == null || !diskInfo.getDiskDeviceBusName().toLowerCase().startsWith("scsi"))
{
s_logger.error("Unsupported root disk device bus "+ diskInfo.getDiskDeviceBusName() );
throw new Exception("Unsupported root disk device bus "+ diskInfo.getDiskDeviceBusName());
}
disk.setCapacityInKB(reqSize);
VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec();
VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();
deviceConfigSpec.setDevice(disk);
deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.EDIT);
vmConfigSpec.getDeviceChange().add(deviceConfigSpec);
if (!vmMo.configureVm(vmConfigSpec)) {
throw new Exception("Failed to configure VM for given root disk size. vmName: " + vmMo.getName());
}
}
}
/**
* Sets video card memory to the one provided in detail svga.vramSize (if provided) on {@code vmConfigSpec}.
* 64MB was always set before.

View File

@ -970,6 +970,13 @@ public class XenServerStorageProcessor implements StorageProcessor {
tmpltvdi = getVDIbyUuid(conn, srcData.getPath());
vdi = tmpltvdi.createClone(conn, new HashMap<String, String>());
Long virtualSize = vdi.getVirtualSize(conn);
if (volume.getSize() > virtualSize) {
s_logger.debug("Overriding provided template's size with new size " + volume.getSize() + " for volume: " + volume.getName());
vdi.resize(conn, volume.getSize());
} else {
s_logger.debug("Using templates disk size of " + virtualSize + " for volume: " + volume.getName() + " since size passed was " + volume.getSize());
}
vdi.setNameLabel(conn, volume.getName());
VDI.Record vdir;

View File

@ -48,6 +48,11 @@ public final class CitrixResizeVolumeCommandWrapper extends CommandWrapper<Resiz
long newSize = command.getNewSize();
try {
if (command.getCurrentSize() >= newSize) {
s_logger.info("No need to resize volume: " + volId +", current size " + command.getCurrentSize() + " is same as new size " + newSize);
return new ResizeVolumeAnswer(command, true, "success", newSize);
}
if (command.isManaged()) {
resizeSr(conn, command);
}

View File

@ -436,7 +436,6 @@ public class CitrixRequestWrapperTest {
final Answer answer = wrapper.execute(resizeCommand, citrixResourceBase);
verify(citrixResourceBase, times(1)).getConnection();
assertFalse(answer.getResult());
}
@Test

View File

@ -868,8 +868,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
HypervisorType hypervisorType = _volsDao.getHypervisorType(volume.getId());
if (hypervisorType != HypervisorType.KVM && hypervisorType != HypervisorType.XenServer &&
hypervisorType != HypervisorType.VMware && hypervisorType != HypervisorType.Any && hypervisorType != HypervisorType.None) {
throw new InvalidParameterValueException("CloudStack currently supports volume resize only on KVM, VMware, or XenServer.");
hypervisorType != HypervisorType.VMware && hypervisorType != HypervisorType.Any && hypervisorType != HypervisorType.None ) {
throw new InvalidParameterValueException("Hypervisor " + hypervisorType + " does not support rootdisksize override");
}
if (volume.getState() != Volume.State.Ready && volume.getState() != Volume.State.Allocated) {
@ -1026,6 +1026,10 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
UserVmVO userVm = _userVmDao.findById(volume.getInstanceId());
if (userVm != null) {
if (volume.getVolumeType().equals(Volume.Type.ROOT) && userVm.getPowerState()!= VirtualMachine.PowerState.PowerOff && hypervisorType == HypervisorType.VMware){
s_logger.error(" For ROOT volume resize VM should be in Power Off state.");
throw new InvalidParameterValueException("VM current state is : "+userVm.getPowerState()+ ". But VM should be in "+VirtualMachine.PowerState.PowerOff+" state.");
}
// serialize VM operation
AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext();

View File

@ -3511,27 +3511,17 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
}
rootDiskSize = Long.parseLong(customParameters.get("rootdisksize"));
// only KVM supports rootdisksize override
if (hypervisorType != HypervisorType.KVM) {
throw new InvalidParameterValueException("Hypervisor " + hypervisorType + " does not support rootdisksize override");
// only KVM, XenServer and VMware supports rootdisksize override
if (!(hypervisorType == HypervisorType.KVM || hypervisorType == HypervisorType.XenServer || hypervisorType == HypervisorType.VMware)) {
throw new InvalidParameterValueException("Hypervisor " + hypervisorType + " 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()) {
Long templateVOSizeGB = templateVO.getSize() / 1024 / 1024 / 1024;
throw new InvalidParameterValueException("unsupported: rootdisksize override is smaller than template size " + templateVO.getSize()
+ "B (" + templateVOSizeGB + "GB)");
} 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");
validateRootDiskResize(hypervisorType, rootDiskSize, templateVO, vm, customParameters);
}
if (isDisplayVm != null) {
@ -3614,6 +3604,29 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
});
}
public void validateRootDiskResize(final HypervisorType hypervisorType, Long rootDiskSize, VMTemplateVO templateVO, UserVmVO vm, final Map<String, String> customParameters) throws InvalidParameterValueException
{
// rootdisksize must be larger than template.
if ((rootDiskSize << 30) < templateVO.getSize()) {
Long templateVOSizeGB = templateVO.getSize() / 1024 / 1024 / 1024;
s_logger.error("unsupported: rootdisksize override is smaller than template size " + templateVO.getSize() + "B (" + templateVOSizeGB + "GB)");
throw new InvalidParameterValueException("unsupported: rootdisksize override is smaller than template size " + templateVO.getSize() + "B (" + templateVOSizeGB + "GB)");
} else if ((rootDiskSize << 30) > templateVO.getSize()) {
if (hypervisorType == HypervisorType.VMware && (vm.getDetails() == null || vm.getDetails().get("rootDiskController") == null)) {
s_logger.warn("If Root disk controller parameter is not overridden, then Root disk resize may fail because current Root disk controller value is NULL.");
} else if (hypervisorType == HypervisorType.VMware && !vm.getDetails().get("rootDiskController").toLowerCase().contains("scsi")) {
s_logger.error("Found unsupported root disk controller : " + vm.getDetails().get("rootDiskController"));
throw new InvalidParameterValueException("Found unsupported root disk controller :" + vm.getDetails().get("rootDiskController"));
} else {
s_logger.debug("Rootdisksize override validation successful. Template root disk size "+(templateVO.getSize() / 1024 / 1024 / 1024)+ " GB" + " Root disk size specified "+ rootDiskSize+" GB");
}
} else {
s_logger.debug("Root disk size specified is " + (rootDiskSize << 30) + " and Template root disk size is " + templateVO.getSize()+" . Both are equal so no need to override");
customParameters.remove("rootdisksize");
}
}
@Override
public void generateUsageEvent(VirtualMachine vm, boolean isDisplay, String eventType){
ServiceOfferingVO serviceOffering = _offeringDao.findById(vm.getId(), vm.getServiceOfferingId());

View File

@ -16,7 +16,8 @@
// under the License.
package com.cloud.vm;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
@ -36,7 +37,9 @@ import static org.mockito.Mockito.when;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.cloud.network.element.UserDataServiceProvider;
@ -44,6 +47,7 @@ import com.cloud.storage.Storage;
import com.cloud.user.User;
import com.cloud.event.dao.UsageEventDao;
import com.cloud.uservm.UserVm;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
@ -236,6 +240,7 @@ public class UserVmManagerTest {
_userVmMgr._entityMgr = _entityMgr;
_userVmMgr._storagePoolDao = _storagePoolDao;
_userVmMgr._vmSnapshotDao = _vmSnapshotDao;
_userVmMgr._configDao = _configDao;
_userVmMgr._nicDao = _nicDao;
_userVmMgr._networkModel = _networkModel;
_userVmMgr._networkDao = _networkDao;
@ -260,6 +265,56 @@ public class UserVmManagerTest {
}
@Test
public void testValidateRootDiskResize()
{
HypervisorType hypervisorType = HypervisorType.Any;
Long rootDiskSize = Long.valueOf(10);
UserVmVO vm = Mockito.mock(UserVmVO.class);
VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class);
Map<String, String> customParameters = new HashMap<String, String>();
Map<String, String> vmDetals = new HashMap<String, String>();
vmDetals.put("rootDiskController","ide");
when(vm.getDetails()).thenReturn(vmDetals);
when(templateVO.getSize()).thenReturn((rootDiskSize<<30)+1);
//Case 1: >
try{
_userVmMgr.validateRootDiskResize(hypervisorType, rootDiskSize, templateVO, vm, customParameters);
Assert.fail("Function should throw InvalidParameterValueException");
}catch(Exception e){
assertThat(e, instanceOf(InvalidParameterValueException.class));
}
//Case 2: =
when(templateVO.getSize()).thenReturn((rootDiskSize<<30));
customParameters.put("rootdisksize","10");
_userVmMgr.validateRootDiskResize(hypervisorType, rootDiskSize, templateVO, vm, customParameters);
assert(!customParameters.containsKey("rootdisksize"));
when(templateVO.getSize()).thenReturn((rootDiskSize<<30)-1);
//Case 3: <
//Case 3.1: HypervisorType!=VMware
_userVmMgr.validateRootDiskResize(hypervisorType, rootDiskSize, templateVO, vm, customParameters);
hypervisorType = HypervisorType.VMware;
//Case 3.2: 0->(rootDiskController!=scsi)
try {
_userVmMgr.validateRootDiskResize(hypervisorType, rootDiskSize, templateVO, vm, customParameters);
Assert.fail("Function should throw InvalidParameterValueException");
}catch(Exception e) {
assertThat(e, instanceOf(InvalidParameterValueException.class));
}
//Case 3.3: 1->(rootDiskController==scsi)
vmDetals.put("rootDiskController","scsi");
_userVmMgr.validateRootDiskResize(hypervisorType, rootDiskSize, templateVO, vm, customParameters);
}
// Test restoreVm when VM state not in running/stopped case
@Test(expected = CloudRuntimeException.class)
public void testRestoreVMF1() throws ResourceAllocationException, InsufficientCapacityException, ResourceUnavailableException {

3
test/integration/component/test_ps_resize_volume.py Normal file → Executable file
View File

@ -89,7 +89,8 @@ class TestResizeVolume(cloudstackTestCase):
try:
cls.hypervisor = str(get_hypervisor_type(cls.api_client)).lower()
if cls.hypervisor.lower() in ['hyperv']:
raise unittest.SkipTest("Volume resize is not supported on %s" % cls.hypervisor)
# Creating service offering with normal config
cls.service_offering = ServiceOffering.create(
cls.api_client,

14
test/integration/smoke/test_deploy_vm_root_resize.py Normal file → Executable file
View File

@ -23,7 +23,7 @@ from marvin.cloudstackTestCase import cloudstackTestCase
#Import Integration Libraries
#base - contains all resources as entities and defines create, delete, list operations on them
from marvin.lib.base import Account, VirtualMachine, ServiceOffering
from marvin.lib.base import Account, VirtualMachine, ServiceOffering, Configurations
#utils - utility classes for common cleanup, external library wrappers etc
from marvin.lib.utils import cleanup_resources
@ -114,7 +114,13 @@ class TestDeployVM(cloudstackTestCase):
# 2. root disk has new size per listVolumes
# 3. Rejects non-supported hypervisor types
"""
if(self.hypervisor.lower() == 'kvm'):
full_clone_config = Configurations.list(self.apiclient,
name="vmware.create.full.clone")[0].value
if full_clone_config == 'false' and self.hypervisor.lower() == 'vmware':
self.skipTest("root disk resize is not supported "+
"when vmware.create.full.clone is %s" % full_clone_config)
if(self.hypervisor.lower() == 'kvm' or self.hypervisor.lower() == 'xenserver' or self.hypervisor.lower() == 'vmware'):
newrootsize = (self.template.size >> 30) + 2
self.virtual_machine = VirtualMachine.create(
self.apiclient,
@ -208,7 +214,7 @@ class TestDeployVM(cloudstackTestCase):
def test_01_deploy_vm_root_resize(self):
"""Test proper failure to deploy virtual machine with rootdisksize of 0
"""
if (self.hypervisor.lower() == 'kvm'):
if (self.hypervisor.lower() == 'kvm' or self.hypervisor.lower() == 'xenserver' or self.hypervisor.lower() == 'vmware'):
newrootsize = 0
success = False
try:
@ -236,7 +242,7 @@ class TestDeployVM(cloudstackTestCase):
def test_02_deploy_vm_root_resize(self):
"""Test proper failure to deploy virtual machine with rootdisksize less than template size
"""
if (self.hypervisor.lower() == 'kvm'):
if (self.hypervisor.lower() == 'kvm' or self.hypervisor.lower() == 'xenserver' or self.hypervisor.lower() == 'vmware'):
newrootsize = (self.template.size >> 30) - 1
self.assertEqual(newrootsize > 0, True, "Provided template is less than 1G in size, cannot run test")

View File

@ -1548,17 +1548,20 @@
createForm: {
title: 'label.action.resize.volume',
preFilter: function(args) {
if (args.context.volumes != null && args.context.volumes[0].type == 'ROOT') {
var vol;
if (args.context.volumes != null) vol = args.context.volumes[0];
if (vol.type == "ROOT" && (vol.hypervisor == "XenServer" || vol.hypervisor == "KVM" || vol.hypervisor == "VMware")) {
args.$form.find('.form-item[rel=newdiskoffering]').hide();
selectedDiskOfferingObj = null;
args.$form.find('.form-item[rel=newsize]').css('display', 'inline-block');
} else {
args.$form.find('.form-item[rel=newdiskoffering]').css('display', 'inline-block');
args.$form.find('.form-item[rel=newsize]').hide();
}
},
fields: {
newdiskoffering: {
label: 'label.resize.new.offering.id',
isHidden: true,
select: function(args) {
if (args.context.volumes != null && args.context.volumes[0].type == 'ROOT') {
args.response.success({
@ -1586,6 +1589,11 @@
});
args.$select.change(function() {
if(args.context.volumes[0].type == "ROOT") {
selectedDiskOfferingObj = null;
return;
}
var diskOfferingId = $(this).val();
$(diskofferingObjs).each(function() {
if (this.id == diskOfferingId) {
@ -1636,7 +1644,8 @@
shrinkok: {
label: 'label.resize.shrink.ok',
isBoolean: true,
isChecked: false
isChecked: false,
isHidden: true
},
minIops: {
label: 'label.disk.iops.min',
@ -1658,38 +1667,47 @@
},
action: function(args) {
var array1 = [];
if(args.$form.find('.form-item[rel=shrinkok]').css("display") != "none") {
array1.push("&shrinkok=" + (args.data.shrinkok == "on"));
}
var newDiskOffering = args.data.newdiskoffering;
var newSize;
if (selectedDiskOfferingObj == null || selectedDiskOfferingObj.iscustomized == true) {
newSize = args.data.newsize;
}
if (newDiskOffering != null && newDiskOffering.length > 0) {
array1.push("&diskofferingid=" + todb(newDiskOffering));
}
if (newSize != null && newSize.length > 0) {
array1.push("&size=" + todb(newSize));
if (newSize != null && newSize.length > 0) {
array1.push("&size=" + todb(newSize));
}
} else {
if(args.$form.find('.form-item[rel=shrinkok]').css("display") != "none") {
array1.push("&shrinkok=" + (args.data.shrinkok == "on"));
}
var newDiskOffering = args.data.newdiskoffering;
if (selectedDiskOfferingObj.iscustomized == true) {
newSize = args.data.newsize;
}
if (newDiskOffering != null && newDiskOffering.length > 0) {
array1.push("&diskofferingid=" + todb(newDiskOffering));
}
if (newSize != null && newSize.length > 0) {
array1.push("&size=" + todb(newSize));
}
var minIops;
var maxIops
if (selectedDiskOfferingObj.iscustomizediops == true) {
minIops = args.data.minIops;
maxIops = args.data.maxIops;
}
if (minIops != null && minIops.length > 0) {
array1.push("&miniops=" + todb(minIops));
}
if (maxIops != null && maxIops.length > 0) {
array1.push("&maxiops=" + todb(maxIops));
}
}
var minIops;
var maxIops;
if (selectedDiskOfferingObj != null && selectedDiskOfferingObj.iscustomizediops == true) {
minIops = args.data.minIops;
maxIops = args.data.maxIops;
}
if (minIops != null && minIops.length > 0) {
array1.push("&miniops=" + todb(minIops));
}
if (maxIops != null && maxIops.length > 0) {
array1.push("&maxiops=" + todb(maxIops));
}
$.ajax({
url: createURL("resizeVolume&id=" + args.context.volumes[0].id + array1.join("")),
@ -2708,7 +2726,7 @@
}
}
if (jsonObj.state == "Ready" || jsonObj.state == "Allocated") {
if ((jsonObj.type == "DATADISK" || jsonObj.type == "ROOT") && (jsonObj.state == "Ready" || jsonObj.state == "Allocated")) {
allowedActions.push("resize");
}
@ -2718,6 +2736,8 @@
}
}
if (jsonObj.type == "ROOT" || jsonObj.type == "DATADISK") {
if (jsonObj.state == "Ready" && isAdmin() && jsonObj.virtualmachineid != null) {
allowedActions.push("migrateVolume");

View File

@ -435,22 +435,22 @@
var $target = $(this);
var val = $target.val();
var item = null;
if (item == null) {
if (item == null && args.data.templates.featuredtemplates != undefined) {
item = $.grep(args.data.templates.featuredtemplates, function(elem) {
return elem.id == val;
})[0];
}
if (item == null) {
if (item == null && args.data.templates.communitytemplates != undefined) {
item = $.grep(args.data.templates.communitytemplates, function(elem) {
return elem.id == val;
})[0];
}
if (item == null) {
if (item == null && args.data.templates.mytemplates!=undefined) {
item = $.grep(args.data.templates.mytemplates, function(elem) {
return elem.id == val;
})[0];
}
if (item == null) {
if (item == null && args.data.templates.sharedtemplates!=undefined) {
item = $.grep(args.data.templates.sharedtemplates, function(elem) {
return elem.id == val;
})[0];
@ -459,7 +459,7 @@
if (!item) return true;
var hypervisor = item['hypervisor'];
if (hypervisor == 'KVM') {
if (hypervisor == 'KVM' || hypervisor == 'XenServer' || hypervisor == 'VMware') {
$step.find('.section.custom-size').show();
$step.addClass('custom-disk-size');
} else {