Skip to content

Commit 45f62c3

Browse files
authored
Merge pull request #1813 from priyankparihar/CLOUDSTACK-9604
CLOUDSTACK-9604: Root disk resize support for VMware and XenServer.
2 parents 503c803 + c2c1f01 commit 45f62c3

13 files changed

Lines changed: 1631 additions & 191 deletions

File tree

plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,17 @@ private Answer execute(ResizeVolumeCommand cmd) {
702702
"Please re-try when virtual disk is attached to a VM using SCSI controller.");
703703
}
704704

705+
if (vdisk.second() != null && !vdisk.second().toLowerCase().startsWith("scsi"))
706+
{
707+
s_logger.error("Unsupported disk device bus "+ vdisk.second());
708+
throw new Exception("Unsupported disk device bus "+ vdisk.second());
709+
}
705710
VirtualDisk disk = vdisk.first();
711+
if ((VirtualDiskFlatVer2BackingInfo)disk.getBacking() != null && ((VirtualDiskFlatVer2BackingInfo)disk.getBacking()).getParent() != null)
712+
{
713+
s_logger.error("Resize is not supported because Disk device has Parent "+ ((VirtualDiskFlatVer2BackingInfo)disk.getBacking()).getParent().getUuid());
714+
throw new Exception("Resize is not supported because Disk device has Parent "+ ((VirtualDiskFlatVer2BackingInfo)disk.getBacking()).getParent().getUuid());
715+
}
706716
String vmdkAbsFile = getAbsoluteVmdkFile(disk);
707717
if (vmdkAbsFile != null && !vmdkAbsFile.isEmpty()) {
708718
vmMo.updateAdapterTypeIfRequired(vmdkAbsFile);
@@ -1515,7 +1525,7 @@ protected StartAnswer execute(StartCommand cmd) {
15151525
String vmNameOnVcenter = names.second();
15161526
String dataDiskController = vmSpec.getDetails().get(VmDetailConstants.DATA_DISK_CONTROLLER);
15171527
String rootDiskController = vmSpec.getDetails().get(VmDetailConstants.ROOT_DISK_CONTROLLER);
1518-
1528+
DiskTO rootDiskTO = null;
15191529
// If root disk controller is scsi, then data disk controller would also be scsi instead of using 'osdefault'
15201530
// This helps avoid mix of different scsi subtype controllers in instance.
15211531
if (DiskControllerType.lsilogic == DiskControllerType.getType(rootDiskController)) {
@@ -1888,6 +1898,8 @@ protected StartAnswer execute(StartCommand cmd) {
18881898
volumeDsDetails.first(),
18891899
(controllerKey == vmMo.getIDEControllerKey(ideUnitNumber)) ? ((ideUnitNumber++) % VmwareHelper.MAX_IDE_CONTROLLER_COUNT) : scsiUnitNumber++, i + 1);
18901900

1901+
if (vol.getType() == Volume.Type.ROOT)
1902+
rootDiskTO = vol;
18911903
deviceConfigSpecArray[i].setDevice(device);
18921904
deviceConfigSpecArray[i].setOperation(VirtualDeviceConfigSpecOperation.ADD);
18931905

@@ -2022,6 +2034,11 @@ protected StartAnswer execute(StartCommand cmd) {
20222034
hyperHost.setRestartPriorityForVM(vmMo, DasVmPriority.HIGH.value());
20232035
}
20242036

2037+
//For resizing root disk.
2038+
if (rootDiskTO != null && !hasSnapshot) {
2039+
resizeRootDisk(vmMo, rootDiskTO, hyperHost, context);
2040+
}
2041+
20252042
//
20262043
// Post Configuration
20272044
//
@@ -2081,6 +2098,43 @@ protected StartAnswer execute(StartCommand cmd) {
20812098
}
20822099
}
20832100

2101+
private void resizeRootDisk(VirtualMachineMO vmMo, DiskTO rootDiskTO, VmwareHypervisorHost hyperHost, VmwareContext context) throws Exception
2102+
{
2103+
Pair<VirtualDisk, String> vdisk = getVirtualDiskInfo(vmMo, rootDiskTO.getPath() + ".vmdk");
2104+
assert(vdisk != null);
2105+
2106+
Long reqSize=((VolumeObjectTO)rootDiskTO.getData()).getSize()/1024;
2107+
VirtualDisk disk = vdisk.first();
2108+
if (reqSize > disk.getCapacityInKB())
2109+
{
2110+
VirtualMachineDiskInfo diskInfo = getMatchingExistingDisk(vmMo.getDiskInfoBuilder(), rootDiskTO, hyperHost, context);
2111+
assert (diskInfo != null);
2112+
String[] diskChain = diskInfo.getDiskChain();
2113+
2114+
if (diskChain != null && diskChain.length>1)
2115+
{
2116+
s_logger.error("Unsupported Disk chain length "+ diskChain.length);
2117+
throw new Exception("Unsupported Disk chain length "+ diskChain.length);
2118+
}
2119+
if (diskInfo.getDiskDeviceBusName() == null || !diskInfo.getDiskDeviceBusName().toLowerCase().startsWith("scsi"))
2120+
{
2121+
s_logger.error("Unsupported root disk device bus "+ diskInfo.getDiskDeviceBusName() );
2122+
throw new Exception("Unsupported root disk device bus "+ diskInfo.getDiskDeviceBusName());
2123+
}
2124+
2125+
disk.setCapacityInKB(reqSize);
2126+
VirtualMachineConfigSpec vmConfigSpec = new VirtualMachineConfigSpec();
2127+
VirtualDeviceConfigSpec deviceConfigSpec = new VirtualDeviceConfigSpec();
2128+
deviceConfigSpec.setDevice(disk);
2129+
deviceConfigSpec.setOperation(VirtualDeviceConfigSpecOperation.EDIT);
2130+
vmConfigSpec.getDeviceChange().add(deviceConfigSpec);
2131+
if (!vmMo.configureVm(vmConfigSpec)) {
2132+
throw new Exception("Failed to configure VM for given root disk size. vmName: " + vmMo.getName());
2133+
}
2134+
}
2135+
}
2136+
2137+
20842138
/**
20852139
* Sets video card memory to the one provided in detail svga.vramSize (if provided) on {@code vmConfigSpec}.
20862140
* 64MB was always set before.

plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,13 @@ public Answer cloneVolumeFromBaseTemplate(final CopyCommand cmd) {
970970

971971
tmpltvdi = getVDIbyUuid(conn, srcData.getPath());
972972
vdi = tmpltvdi.createClone(conn, new HashMap<String, String>());
973+
Long virtualSize = vdi.getVirtualSize(conn);
974+
if (volume.getSize() > virtualSize) {
975+
s_logger.debug("Overriding provided template's size with new size " + volume.getSize() + " for volume: " + volume.getName());
976+
vdi.resize(conn, volume.getSize());
977+
} else {
978+
s_logger.debug("Using templates disk size of " + virtualSize + " for volume: " + volume.getName() + " since size passed was " + volume.getSize());
979+
}
973980
vdi.setNameLabel(conn, volume.getName());
974981

975982
VDI.Record vdir;

plugins/hypervisors/xenserver/src/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixResizeVolumeCommandWrapper.java

100644100755
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ public Answer execute(final ResizeVolumeCommand command, final CitrixResourceBas
4848
long newSize = command.getNewSize();
4949

5050
try {
51+
52+
if (command.getCurrentSize() >= newSize) {
53+
s_logger.info("No need to resize volume: " + volId +", current size " + command.getCurrentSize() + " is same as new size " + newSize);
54+
return new ResizeVolumeAnswer(command, true, "success", newSize);
55+
}
5156
if (command.isManaged()) {
5257
resizeSr(conn, command);
5358
}

plugins/hypervisors/xenserver/test/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRequestWrapperTest.java

100644100755
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,6 @@ public void testResizeVolumeCommand() {
436436
final Answer answer = wrapper.execute(resizeCommand, citrixResourceBase);
437437
verify(citrixResourceBase, times(1)).getConnection();
438438

439-
assertFalse(answer.getResult());
440439
}
441440

442441
@Test

server/src/com/cloud/storage/VolumeApiServiceImpl.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -868,8 +868,8 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep
868868
HypervisorType hypervisorType = _volsDao.getHypervisorType(volume.getId());
869869

870870
if (hypervisorType != HypervisorType.KVM && hypervisorType != HypervisorType.XenServer &&
871-
hypervisorType != HypervisorType.VMware && hypervisorType != HypervisorType.Any && hypervisorType != HypervisorType.None) {
872-
throw new InvalidParameterValueException("CloudStack currently supports volume resize only on KVM, VMware, or XenServer.");
871+
hypervisorType != HypervisorType.VMware && hypervisorType != HypervisorType.Any && hypervisorType != HypervisorType.None ) {
872+
throw new InvalidParameterValueException("Hypervisor " + hypervisorType + " does not support rootdisksize override");
873873
}
874874

875875
if (volume.getState() != Volume.State.Ready && volume.getState() != Volume.State.Allocated) {
@@ -1026,6 +1026,10 @@ public VolumeVO resizeVolume(ResizeVolumeCmd cmd) throws ResourceAllocationExcep
10261026
UserVmVO userVm = _userVmDao.findById(volume.getInstanceId());
10271027

10281028
if (userVm != null) {
1029+
if (volume.getVolumeType().equals(Volume.Type.ROOT) && userVm.getPowerState()!= VirtualMachine.PowerState.PowerOff && hypervisorType == HypervisorType.VMware){
1030+
s_logger.error(" For ROOT volume resize VM should be in Power Off state.");
1031+
throw new InvalidParameterValueException("VM current state is : "+userVm.getPowerState()+ ". But VM should be in "+VirtualMachine.PowerState.PowerOff+" state.");
1032+
}
10291033
// serialize VM operation
10301034
AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext();
10311035

server/src/com/cloud/vm/UserVmManagerImpl.java

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3511,27 +3511,17 @@ public UserVmVO doInTransaction(TransactionStatus status) throws InsufficientCap
35113511
}
35123512
rootDiskSize = Long.parseLong(customParameters.get("rootdisksize"));
35133513

3514-
// only KVM supports rootdisksize override
3515-
if (hypervisorType != HypervisorType.KVM) {
3516-
throw new InvalidParameterValueException("Hypervisor " + hypervisorType + " does not support rootdisksize override");
3514+
// only KVM, XenServer and VMware supports rootdisksize override
3515+
if (!(hypervisorType == HypervisorType.KVM || hypervisorType == HypervisorType.XenServer || hypervisorType == HypervisorType.VMware)) {
3516+
throw new InvalidParameterValueException("Hypervisor " + hypervisorType + " does not support rootdisksize override");
35173517
}
35183518

3519-
// rotdisksize must be larger than template
35203519
VMTemplateVO templateVO = _templateDao.findById(template.getId());
35213520
if (templateVO == null) {
35223521
throw new InvalidParameterValueException("Unable to look up template by id " + template.getId());
35233522
}
35243523

3525-
if ((rootDiskSize << 30) < templateVO.getSize()) {
3526-
Long templateVOSizeGB = templateVO.getSize() / 1024 / 1024 / 1024;
3527-
throw new InvalidParameterValueException("unsupported: rootdisksize override is smaller than template size " + templateVO.getSize()
3528-
+ "B (" + templateVOSizeGB + "GB)");
3529-
} else {
3530-
s_logger.debug("rootdisksize of " + (rootDiskSize << 30) + " was larger than template size of " + templateVO.getSize());
3531-
}
3532-
3533-
s_logger.debug("found root disk size of " + rootDiskSize);
3534-
customParameters.remove("rootdisksize");
3524+
validateRootDiskResize(hypervisorType, rootDiskSize, templateVO, vm, customParameters);
35353525
}
35363526

35373527
if (isDisplayVm != null) {
@@ -3614,6 +3604,29 @@ public UserVmVO doInTransaction(TransactionStatus status) throws InsufficientCap
36143604
});
36153605
}
36163606

3607+
public void validateRootDiskResize(final HypervisorType hypervisorType, Long rootDiskSize, VMTemplateVO templateVO, UserVmVO vm, final Map<String, String> customParameters) throws InvalidParameterValueException
3608+
{
3609+
// rootdisksize must be larger than template.
3610+
if ((rootDiskSize << 30) < templateVO.getSize()) {
3611+
Long templateVOSizeGB = templateVO.getSize() / 1024 / 1024 / 1024;
3612+
s_logger.error("unsupported: rootdisksize override is smaller than template size " + templateVO.getSize() + "B (" + templateVOSizeGB + "GB)");
3613+
throw new InvalidParameterValueException("unsupported: rootdisksize override is smaller than template size " + templateVO.getSize() + "B (" + templateVOSizeGB + "GB)");
3614+
} else if ((rootDiskSize << 30) > templateVO.getSize()) {
3615+
if (hypervisorType == HypervisorType.VMware && (vm.getDetails() == null || vm.getDetails().get("rootDiskController") == null)) {
3616+
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.");
3617+
} else if (hypervisorType == HypervisorType.VMware && !vm.getDetails().get("rootDiskController").toLowerCase().contains("scsi")) {
3618+
s_logger.error("Found unsupported root disk controller : " + vm.getDetails().get("rootDiskController"));
3619+
throw new InvalidParameterValueException("Found unsupported root disk controller :" + vm.getDetails().get("rootDiskController"));
3620+
} else {
3621+
s_logger.debug("Rootdisksize override validation successful. Template root disk size "+(templateVO.getSize() / 1024 / 1024 / 1024)+ " GB" + " Root disk size specified "+ rootDiskSize+" GB");
3622+
}
3623+
} else {
3624+
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");
3625+
customParameters.remove("rootdisksize");
3626+
}
3627+
}
3628+
3629+
36173630
@Override
36183631
public void generateUsageEvent(VirtualMachine vm, boolean isDisplay, String eventType){
36193632
ServiceOfferingVO serviceOffering = _offeringDao.findById(vm.getId(), vm.getServiceOfferingId());

server/test/com/cloud/vm/UserVmManagerTest.java

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
// under the License.
1717

1818
package com.cloud.vm;
19-
19+
import static org.hamcrest.Matchers.instanceOf;
20+
import static org.junit.Assert.assertThat;
2021
import static org.junit.Assert.assertFalse;
2122
import static org.junit.Assert.assertTrue;
2223
import static org.mockito.Matchers.any;
@@ -36,14 +37,17 @@
3637

3738
import java.lang.reflect.Field;
3839
import java.util.ArrayList;
40+
import java.util.HashMap;
3941
import java.util.List;
42+
import java.util.Map;
4043
import java.util.UUID;
4144

4245
import com.cloud.network.element.UserDataServiceProvider;
4346
import com.cloud.storage.Storage;
4447
import com.cloud.user.User;
4548
import com.cloud.event.dao.UsageEventDao;
4649
import com.cloud.uservm.UserVm;
50+
import org.junit.Assert;
4751
import org.junit.Before;
4852
import org.junit.Test;
4953
import org.mockito.Mock;
@@ -236,6 +240,7 @@ public void setup() {
236240
_userVmMgr._entityMgr = _entityMgr;
237241
_userVmMgr._storagePoolDao = _storagePoolDao;
238242
_userVmMgr._vmSnapshotDao = _vmSnapshotDao;
243+
_userVmMgr._configDao = _configDao;
239244
_userVmMgr._nicDao = _nicDao;
240245
_userVmMgr._networkModel = _networkModel;
241246
_userVmMgr._networkDao = _networkDao;
@@ -260,6 +265,56 @@ public void setup() {
260265

261266
}
262267

268+
269+
@Test
270+
public void testValidateRootDiskResize()
271+
{
272+
HypervisorType hypervisorType = HypervisorType.Any;
273+
Long rootDiskSize = Long.valueOf(10);
274+
UserVmVO vm = Mockito.mock(UserVmVO.class);
275+
VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class);
276+
Map<String, String> customParameters = new HashMap<String, String>();
277+
Map<String, String> vmDetals = new HashMap<String, String>();
278+
279+
280+
vmDetals.put("rootDiskController","ide");
281+
when(vm.getDetails()).thenReturn(vmDetals);
282+
when(templateVO.getSize()).thenReturn((rootDiskSize<<30)+1);
283+
//Case 1: >
284+
try{
285+
_userVmMgr.validateRootDiskResize(hypervisorType, rootDiskSize, templateVO, vm, customParameters);
286+
Assert.fail("Function should throw InvalidParameterValueException");
287+
}catch(Exception e){
288+
assertThat(e, instanceOf(InvalidParameterValueException.class));
289+
}
290+
291+
//Case 2: =
292+
when(templateVO.getSize()).thenReturn((rootDiskSize<<30));
293+
customParameters.put("rootdisksize","10");
294+
_userVmMgr.validateRootDiskResize(hypervisorType, rootDiskSize, templateVO, vm, customParameters);
295+
assert(!customParameters.containsKey("rootdisksize"));
296+
297+
when(templateVO.getSize()).thenReturn((rootDiskSize<<30)-1);
298+
299+
//Case 3: <
300+
301+
//Case 3.1: HypervisorType!=VMware
302+
_userVmMgr.validateRootDiskResize(hypervisorType, rootDiskSize, templateVO, vm, customParameters);
303+
304+
hypervisorType = HypervisorType.VMware;
305+
//Case 3.2: 0->(rootDiskController!=scsi)
306+
try {
307+
_userVmMgr.validateRootDiskResize(hypervisorType, rootDiskSize, templateVO, vm, customParameters);
308+
Assert.fail("Function should throw InvalidParameterValueException");
309+
}catch(Exception e) {
310+
assertThat(e, instanceOf(InvalidParameterValueException.class));
311+
}
312+
313+
//Case 3.3: 1->(rootDiskController==scsi)
314+
vmDetals.put("rootDiskController","scsi");
315+
_userVmMgr.validateRootDiskResize(hypervisorType, rootDiskSize, templateVO, vm, customParameters);
316+
}
317+
263318
// Test restoreVm when VM state not in running/stopped case
264319
@Test(expected = CloudRuntimeException.class)
265320
public void testRestoreVMF1() throws ResourceAllocationException, InsufficientCapacityException, ResourceUnavailableException {

test/integration/component/test_ps_resize_volume.py

100644100755
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ def setUpClass(cls):
8989

9090
try:
9191
cls.hypervisor = str(get_hypervisor_type(cls.api_client)).lower()
92-
92+
if cls.hypervisor.lower() in ['hyperv']:
93+
raise unittest.SkipTest("Volume resize is not supported on %s" % cls.hypervisor)
9394
# Creating service offering with normal config
9495
cls.service_offering = ServiceOffering.create(
9596
cls.api_client,

0 commit comments

Comments
 (0)