From 30a12d1eadd9d692a9e1e1005338d949a8c44f8d Mon Sep 17 00:00:00 2001 From: Hendrik Linka Date: Wed, 12 Aug 2026 13:17:25 +0200 Subject: [PATCH] vmware: restore legacy VMDK data disk lifecycle (#13249) Restore the classic worker-VM-based VMDK data-disk lifecycle on VMware 8 to avoid vCenter datastore-browser inconsistencies between FCD and VMDK representations that caused HTTP 500 errors during attachVolume. Port workaround from apc..., upstream of the fix-4.20.3.0 branch 'commit 18f2fd2c94 vmware: restore legacy VMDK data disk lifecycle'. Changes: - VmwareHelper: add getVirtualDiskType(Storage.ProvisioningType) mapping (THIN->THIN, SPARSE->PREALLOCATED, FAT->EAGER_ZEROED_THICK). - VirtualMachineMO: add createDisk overload accepting ProvisioningType, delegate via VmwareHelper; attachDisk(maxIops) delegates to new overload with updateVmdkAdapter flag defaulting to true (guard only evaluated when updateVmdkAdapter is true). - VmwareStorageLayoutHelper: syncVolumeToVmDefaultFolder now exposes a 7-arg Pair overload (targetAdapterType + provisioningType) that moves root/deprecated-legacy or fcd/ base-folder VMDKs into the VM default folder using VirtualDiskManagerMO with an optional FileBackedVirtualDiskSpec adapter/disk mapping, returning whether the adapter type had to change. - VmwareStorageProcessor: createVolume always uses the classic worker-VM VMDK path via getDeprecatedLegacyDatastorePathFromVmdkFileName and createDisk(volume.getProvisioningType()); cleanup wrapped in try/catch with warning instead of throwing. attachVolume uses the new overload and skips the vmdk adapter update when the sync already migrated the disk. Tests: - VmwareHelperTest: add getVirtualDiskTypeMapsCloudStackProvisioningTypes. - VmwareStorageLayoutHelperTest: add three tests covering root move, root copy+delete with adapter/provisioning, and base-folder copy+delete. --- .../resource/VmwareStorageLayoutHelper.java | 57 +++++++- .../resource/VmwareStorageProcessor.java | 106 +++++++-------- .../VmwareStorageLayoutHelperTest.java | 125 ++++++++++++++++++ .../vmware/mo/VirtualMachineMO.java | 13 +- .../hypervisor/vmware/util/VmwareHelper.java | 12 ++ .../vmware/util/VmwareHelperTest.java | 10 ++ 6 files changed, 268 insertions(+), 55 deletions(-) create mode 100644 plugins/hypervisors/vmware/src/test/java/com/cloud/storage/resource/VmwareStorageLayoutHelperTest.java diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageLayoutHelper.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageLayoutHelper.java index 69572a3cd177..1ecafa176551 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageLayoutHelper.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageLayoutHelper.java @@ -20,7 +20,9 @@ import java.util.Arrays; import java.util.List; +import com.vmware.vim25.FileBackedVirtualDiskSpec; import com.vmware.vim25.ManagedObjectReference; +import com.vmware.vim25.VirtualDiskAdapterType; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import org.apache.logging.log4j.Logger; @@ -30,6 +32,10 @@ import com.cloud.hypervisor.vmware.mo.DatastoreFile; import com.cloud.hypervisor.vmware.mo.DatastoreMO; import com.cloud.hypervisor.vmware.mo.HypervisorHostHelper; +import com.cloud.hypervisor.vmware.mo.VirtualDiskManagerMO; +import com.cloud.hypervisor.vmware.mo.VmdkAdapterType; +import com.cloud.hypervisor.vmware.util.VmwareHelper; +import com.cloud.storage.Storage; import com.cloud.utils.Pair; @@ -168,6 +174,17 @@ public static String syncVolumeToVmDefaultFolder(DatacenterMO dcMo, String vmNam } public static String syncVolumeToVmDefaultFolder(DatacenterMO dcMo, String vmName, DatastoreMO ds, String vmdkName, String excludeFolders) throws Exception { + return syncVolumeToVmDefaultFolder(dcMo, vmName, ds, vmdkName, excludeFolders, null); + } + + public static String syncVolumeToVmDefaultFolder(DatacenterMO dcMo, String vmName, DatastoreMO ds, String vmdkName, String excludeFolders, + VmdkAdapterType targetAdapterType) throws Exception { + return syncVolumeToVmDefaultFolder(dcMo, vmName, ds, vmdkName, excludeFolders, targetAdapterType, null).first(); + } + + public static Pair syncVolumeToVmDefaultFolder(DatacenterMO dcMo, String vmName, DatastoreMO ds, String vmdkName, + String excludeFolders, VmdkAdapterType targetAdapterType, + Storage.ProvisioningType provisioningType) throws Exception { assert (ds != null); if (!ds.folderExists(String.format("[%s]", ds.getName()), vmName)) { @@ -182,6 +199,21 @@ public static String syncVolumeToVmDefaultFolder(DatacenterMO dcMo, String vmNam String[] vmdkLinkedCloneModePair = getVmdkFilePairDatastorePath(ds, vmName, vmdkName, VmwareStorageLayoutType.VMWARE, true); String[] vmdkFullCloneModePair = getVmdkFilePairDatastorePath(ds, vmName, vmdkName, VmwareStorageLayoutType.VMWARE, false); + String deprecatedLegacyPath = getDeprecatedLegacyDatastorePathFromVmdkFileName(ds, vmdkName + ".vmdk"); + if (ds.fileExists(deprecatedLegacyPath)) { + String vmwarePath = vmdkLinkedCloneModePair[0]; + LOGGER.info("sync " + deprecatedLegacyPath + "->" + vmwarePath); + VirtualDiskManagerMO diskManager = new VirtualDiskManagerMO(ds.getContext()); + if (targetAdapterType == null) { + diskManager.moveVirtualDisk(deprecatedLegacyPath, dcMo.getMor(), vmwarePath, dcMo.getMor(), true); + } else { + FileBackedVirtualDiskSpec diskSpec = createDiskSpec(targetAdapterType, provisioningType); + diskManager.copyVirtualDisk(deprecatedLegacyPath, dcMo.getMor(), vmwarePath, dcMo.getMor(), diskSpec, true); + diskManager.deleteVirtualDisk(deprecatedLegacyPath, dcMo.getMor()); + } + return new Pair<>(vmwarePath, targetAdapterType != null); + } + if (!ds.fileExists(vmdkLinkedCloneModeLegacyPair[0]) && !ds.fileExists(vmdkLinkedCloneModePair[0])) { // To protect against inconsistency caused by non-atomic datastore file management, detached disk may // be left over in its previous owner VM. We will do a fixup synchronization here by moving it to root @@ -190,6 +222,16 @@ public static String syncVolumeToVmDefaultFolder(DatacenterMO dcMo, String vmNam syncVolumeToRootFolder(dcMo, ds, vmdkName, vmName, excludeFolders); } + if (targetAdapterType != null && ds.fileExists(vmdkLinkedCloneModeLegacyPair[0])) { + String vmwarePath = vmdkLinkedCloneModePair[0]; + LOGGER.info("sync " + vmdkLinkedCloneModeLegacyPair[0] + "->" + vmwarePath); + VirtualDiskManagerMO diskManager = new VirtualDiskManagerMO(ds.getContext()); + FileBackedVirtualDiskSpec diskSpec = createDiskSpec(targetAdapterType, provisioningType); + diskManager.copyVirtualDisk(vmdkLinkedCloneModeLegacyPair[0], dcMo.getMor(), vmwarePath, dcMo.getMor(), diskSpec, true); + diskManager.deleteVirtualDisk(vmdkLinkedCloneModeLegacyPair[0], dcMo.getMor()); + return new Pair<>(vmwarePath, true); + } + for (int i=1; i" + vmdkFullCloneModePair[i]); @@ -212,7 +254,20 @@ public static String syncVolumeToVmDefaultFolder(DatacenterMO dcMo, String vmNam } // Note: we will always return a path - return vmdkLinkedCloneModePair[0]; + return new Pair<>(vmdkLinkedCloneModePair[0], false); + } + + private static FileBackedVirtualDiskSpec createDiskSpec(VmdkAdapterType targetAdapterType, Storage.ProvisioningType provisioningType) { + FileBackedVirtualDiskSpec diskSpec = new FileBackedVirtualDiskSpec(); + if (targetAdapterType == VmdkAdapterType.buslogic) { + diskSpec.setAdapterType(VirtualDiskAdapterType.BUS_LOGIC.value()); + } else if (targetAdapterType == VmdkAdapterType.lsilogic) { + diskSpec.setAdapterType(VirtualDiskAdapterType.LSI_LOGIC.value()); + } else { + diskSpec.setAdapterType(targetAdapterType.toString()); + } + diskSpec.setDiskType(VmwareHelper.getVirtualDiskType(provisioningType).value()); + return diskSpec; } public static void syncVolumeToRootFolder(DatacenterMO dcMo, DatastoreMO ds, String vmdkName, String vmName) throws Exception { diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java index 253bbf2d0930..32def8adec74 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/storage/resource/VmwareStorageProcessor.java @@ -84,7 +84,7 @@ import com.cloud.hypervisor.vmware.mo.NetworkDetails; import com.cloud.hypervisor.vmware.mo.VirtualMachineDiskInfoBuilder; import com.cloud.hypervisor.vmware.mo.VirtualMachineMO; -import com.cloud.hypervisor.vmware.mo.VirtualStorageObjectManagerMO; +import com.cloud.hypervisor.vmware.mo.VmdkAdapterType; import com.cloud.hypervisor.vmware.mo.VmwareHypervisorHost; import com.cloud.hypervisor.vmware.resource.VmwareResource; import com.cloud.hypervisor.vmware.util.VmwareContext; @@ -107,7 +107,6 @@ import com.cloud.vm.VirtualMachine.PowerState; import com.cloud.vm.VmDetailConstants; import com.google.gson.Gson; -import com.vmware.vim25.BaseConfigInfoDiskFileBackingInfo; import com.vmware.vim25.DatastoreHostMount; import com.vmware.vim25.HostHostBusAdapter; import com.vmware.vim25.HostInternetScsiHba; @@ -126,7 +125,6 @@ import com.vmware.vim25.HostUnresolvedVmfsVolume; import com.vmware.vim25.InvalidStateFaultMsg; import com.vmware.vim25.ManagedObjectReference; -import com.vmware.vim25.VStorageObject; import com.vmware.vim25.VirtualDeviceBackingInfo; import com.vmware.vim25.VirtualDeviceConfigSpec; import com.vmware.vim25.VirtualDeviceConfigSpecOperation; @@ -2057,7 +2055,24 @@ private Answer attachVolume(Command cmd, DiskTO disk, boolean isAttach, boolean String datastoreVolumePath; boolean datastoreChangeObserved = false; boolean volumePathChangeObserved = false; + boolean updateVmdkAdapter = true; String chainInfo = null; + String diskController = null; + + if (isAttach) { + String rootDiskControllerDetail = DiskControllerType.ide.toString(); + if (controllerInfo != null && StringUtils.isNotEmpty(controllerInfo.get(VmDetailConstants.ROOT_DISK_CONTROLLER))) { + rootDiskControllerDetail = controllerInfo.get(VmDetailConstants.ROOT_DISK_CONTROLLER); + } + String dataDiskControllerDetail = getLegacyVmDataDiskController(); + if (controllerInfo != null && StringUtils.isNotEmpty(controllerInfo.get(VmDetailConstants.DATA_DISK_CONTROLLER))) { + dataDiskControllerDetail = controllerInfo.get(VmDetailConstants.DATA_DISK_CONTROLLER); + } + + VmwareHelper.validateDiskControllerDetails(rootDiskControllerDetail, dataDiskControllerDetail); + Pair chosenDiskControllers = VmwareHelper.chooseRequiredDiskControllers(new Pair<>(rootDiskControllerDetail, dataDiskControllerDetail), vmMo, null, null); + diskController = VmwareHelper.getControllerBasedOnDiskType(chosenDiskControllers, disk); + } if (isAttach) { if (isManaged) { @@ -2066,7 +2081,11 @@ private Answer attachVolume(Command cmd, DiskTO disk, boolean isAttach, boolean if (dsMo.getDatastoreType().equalsIgnoreCase("VVOL")) { datastoreVolumePath = VmwareStorageLayoutHelper.getDatastoreVolumePath(dsMo, vmName, volumePath); } else { - datastoreVolumePath = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dsMo.getOwnerDatacenter().first(), vmName, dsMo, volumePath, VmwareManager.s_vmwareSearchExcludeFolder.value()); + VmdkAdapterType targetAdapterType = VmdkAdapterType.getAdapterType(DiskControllerType.getType(diskController)); + Pair syncResult = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dsMo.getOwnerDatacenter().first(), vmName, dsMo, + volumePath, VmwareManager.s_vmwareSearchExcludeFolder.value(), targetAdapterType, volumeTO.getProvisioningType()); + datastoreVolumePath = syncResult.first(); + updateVmdkAdapter = !syncResult.second(); } } } else { @@ -2102,20 +2121,8 @@ private Answer attachVolume(Command cmd, DiskTO disk, boolean isAttach, boolean AttachAnswer answer = new AttachAnswer(disk); if (isAttach) { - String rootDiskControllerDetail = DiskControllerType.ide.toString(); - if (controllerInfo != null && StringUtils.isNotEmpty(controllerInfo.get(VmDetailConstants.ROOT_DISK_CONTROLLER))) { - rootDiskControllerDetail = controllerInfo.get(VmDetailConstants.ROOT_DISK_CONTROLLER); - } - String dataDiskControllerDetail = getLegacyVmDataDiskController(); - if (controllerInfo != null && StringUtils.isNotEmpty(controllerInfo.get(VmDetailConstants.DATA_DISK_CONTROLLER))) { - dataDiskControllerDetail = controllerInfo.get(VmDetailConstants.DATA_DISK_CONTROLLER); - } - - VmwareHelper.validateDiskControllerDetails(rootDiskControllerDetail, dataDiskControllerDetail); - Pair chosenDiskControllers = VmwareHelper.chooseRequiredDiskControllers(new Pair<>(rootDiskControllerDetail, dataDiskControllerDetail), vmMo, null, null); - String diskController = VmwareHelper.getControllerBasedOnDiskType(chosenDiskControllers, disk); - - vmMo.attachDisk(new String[] { datastoreVolumePath }, morDs, diskController, storagePolicyId, volumeTO.getIopsReadRate() + volumeTO.getIopsWriteRate()); + vmMo.attachDisk(new String[] { datastoreVolumePath }, morDs, diskController, storagePolicyId, + volumeTO.getIopsReadRate() + volumeTO.getIopsWriteRate(), updateVmdkAdapter); VirtualMachineDiskInfoBuilder diskInfoBuilder = vmMo.getDiskInfoBuilder(); VirtualMachineDiskInfo diskInfo = diskInfoBuilder.getDiskInfoByBackingFileBaseName(volumePath, dsMo.getName()); chainInfo = _gson.toJson(diskInfo); @@ -2416,49 +2423,42 @@ public Answer createVolume(CreateObjectCommand cmd) { VirtualMachineMO vmMo = null; String volumeUuid = UUID.randomUUID().toString().replace("-", ""); - String volumeDatastorePath = VmwareStorageLayoutHelper.getDatastorePathBaseFolderFromVmdkFileName(dsMo, volumeUuid + ".vmdk"); - VolumeObjectTO newVol = new VolumeObjectTO(); - + String volumeDatastorePath = VmwareStorageLayoutHelper.getDeprecatedLegacyDatastorePathFromVmdkFileName(dsMo, volumeUuid + ".vmdk"); + String dummyVmName = hostService.getWorkerName(context, cmd, 0, dsMo); try { - VirtualStorageObjectManagerMO vStorageObjectManagerMO = new VirtualStorageObjectManagerMO(context); - VStorageObject virtualDisk = vStorageObjectManagerMO.createDisk(morDatastore, volume.getProvisioningType(), volume.getSize(), volumeDatastorePath, volumeUuid); - DatastoreFile file = new DatastoreFile(((BaseConfigInfoDiskFileBackingInfo)virtualDisk.getConfig().getBacking()).getFilePath()); - newVol.setPath(file.getFileBaseName()); - newVol.setSize(volume.getSize()); - } catch (Exception e) { - logger.error(String.format("Create disk using vStorageObject manager failed due to [%s], retrying using worker VM.", e.getMessage()), e); - String dummyVmName = hostService.getWorkerName(context, cmd, 0, dsMo); - try { - logger.info(String.format("Creating worker VM [%s].", dummyVmName)); - vmMo = HypervisorHostHelper.createWorkerVM(hyperHost, dsMo, dummyVmName, null); - if (vmMo == null) { - throw new CloudRuntimeException("Unable to create a dummy VM for volume creation."); - } + logger.info(String.format("Creating worker VM [%s].", dummyVmName)); + vmMo = HypervisorHostHelper.createWorkerVM(hyperHost, dsMo, dummyVmName, null); + if (vmMo == null) { + throw new CloudRuntimeException("Unable to create a dummy VM for volume creation."); + } - synchronized (this) { - try { - vmMo.createDisk(volumeDatastorePath, (int)(volume.getSize() / (1024L * 1024L)), morDatastore, vmMo.getScsiDeviceControllerKey(), vSphereStoragePolicyId); - vmMo.detachDisk(volumeDatastorePath, false); - } - catch (Exception e1) { - logger.error(String.format("Deleting file [%s] due to [%s].", volumeDatastorePath, e1.getMessage()), e1); - VmwareStorageLayoutHelper.deleteVolumeVmdkFiles(dsMo, volumeUuid, dcMo, VmwareManager.s_vmwareSearchExcludeFolder.value()); - throw new CloudRuntimeException(String.format("Unable to create volume due to [%s].", e1.getMessage())); - } + synchronized (this) { + try { + vmMo.createDisk(volumeDatastorePath, volume.getProvisioningType(), (int)(volume.getSize() / (1024L * 1024L)), morDatastore, + vmMo.getScsiDeviceControllerKey(), vSphereStoragePolicyId); + vmMo.detachDisk(volumeDatastorePath, false); + } + catch (Exception e) { + logger.error(String.format("Deleting file [%s] due to [%s].", volumeDatastorePath, e.getMessage()), e); + VmwareStorageLayoutHelper.deleteVolumeVmdkFiles(dsMo, volumeUuid, dcMo, VmwareManager.s_vmwareSearchExcludeFolder.value()); + throw new CloudRuntimeException(String.format("Unable to create volume due to [%s].", e.getMessage())); } + } - newVol = new VolumeObjectTO(); - newVol.setPath(volumeUuid); - newVol.setSize(volume.getSize()); - return new CreateObjectAnswer(newVol); - } finally { - logger.info("Destroying dummy VM after volume creation."); - if (vmMo != null) { + VolumeObjectTO newVol = new VolumeObjectTO(); + newVol.setPath(volumeUuid); + newVol.setSize(volume.getSize()); + return new CreateObjectAnswer(newVol); + } finally { + logger.info("Destroying dummy VM after volume creation."); + if (vmMo != null) { + try { vmMo.detachAllDisksAndDestroy(); + } catch (Exception e) { + logger.warn(String.format("Failed to destroy worker VM [%s] after volume creation due to: [%s].", dummyVmName, e.getMessage()), e); } } } - return new CreateObjectAnswer(newVol); } catch (Throwable e) { return new CreateObjectAnswer(hostService.createLogMessageException(e, cmd)); } diff --git a/plugins/hypervisors/vmware/src/test/java/com/cloud/storage/resource/VmwareStorageLayoutHelperTest.java b/plugins/hypervisors/vmware/src/test/java/com/cloud/storage/resource/VmwareStorageLayoutHelperTest.java new file mode 100644 index 000000000000..e944e00cd426 --- /dev/null +++ b/plugins/hypervisors/vmware/src/test/java/com/cloud/storage/resource/VmwareStorageLayoutHelperTest.java @@ -0,0 +1,125 @@ +// 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.storage.resource; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.cloud.hypervisor.vmware.mo.DatacenterMO; +import com.cloud.hypervisor.vmware.mo.DatastoreMO; +import com.cloud.hypervisor.vmware.mo.VirtualDiskManagerMO; +import com.cloud.hypervisor.vmware.mo.VmdkAdapterType; +import com.cloud.hypervisor.vmware.util.VmwareContext; +import com.cloud.storage.Storage; +import com.cloud.utils.Pair; +import com.vmware.vim25.FileBackedVirtualDiskSpec; +import com.vmware.vim25.ManagedObjectReference; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.MockedConstruction; +import org.mockito.Mockito; + +public class VmwareStorageLayoutHelperTest { + + @Test + public void syncVolumeToVmDefaultFolderMovesRootVolumeWithVirtualDiskManager() throws Exception { + DatacenterMO datacenter = mock(DatacenterMO.class); + DatastoreMO datastore = mock(DatastoreMO.class); + VmwareContext context = mock(VmwareContext.class); + ManagedObjectReference datacenterMor = mock(ManagedObjectReference.class); + + when(datastore.getName()).thenReturn("datastore"); + when(datastore.folderExists("[datastore]", "vm-name")).thenReturn(true); + when(datastore.fileExists("[datastore] volume.vmdk")).thenReturn(true); + when(datastore.getContext()).thenReturn(context); + when(datacenter.getMor()).thenReturn(datacenterMor); + + try (MockedConstruction diskManagers = mockConstruction(VirtualDiskManagerMO.class)) { + String path = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(datacenter, "vm-name", datastore, "volume"); + + assertEquals("[datastore] vm-name/volume.vmdk", path); + VirtualDiskManagerMO diskManager = diskManagers.constructed().get(0); + verify(diskManager).moveVirtualDisk("[datastore] volume.vmdk", datacenterMor, + "[datastore] vm-name/volume.vmdk", datacenterMor, true); + } + } + + @Test + public void syncVolumeToVmDefaultFolderCopiesRootVolumeWithTargetAdapterAndProvisioningType() throws Exception { + DatacenterMO datacenter = mock(DatacenterMO.class); + DatastoreMO datastore = mock(DatastoreMO.class); + VmwareContext context = mock(VmwareContext.class); + ManagedObjectReference datacenterMor = mock(ManagedObjectReference.class); + + when(datastore.getName()).thenReturn("datastore"); + when(datastore.folderExists("[datastore]", "vm-name")).thenReturn(true); + when(datastore.fileExists("[datastore] volume.vmdk")).thenReturn(true); + when(datastore.getContext()).thenReturn(context); + when(datacenter.getMor()).thenReturn(datacenterMor); + + try (MockedConstruction diskManagers = mockConstruction(VirtualDiskManagerMO.class)) { + Pair result = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(datacenter, "vm-name", datastore, "volume", null, + VmdkAdapterType.lsilogic, Storage.ProvisioningType.FAT); + + assertEquals("[datastore] vm-name/volume.vmdk", result.first()); + assertTrue(result.second()); + VirtualDiskManagerMO diskManager = diskManagers.constructed().get(0); + ArgumentCaptor diskSpec = ArgumentCaptor.forClass(FileBackedVirtualDiskSpec.class); + InOrder inOrder = Mockito.inOrder(diskManager); + inOrder.verify(diskManager).copyVirtualDisk("[datastore] volume.vmdk", datacenterMor, + "[datastore] vm-name/volume.vmdk", datacenterMor, diskSpec.capture(), true); + inOrder.verify(diskManager).deleteVirtualDisk("[datastore] volume.vmdk", datacenterMor); + assertEquals("lsiLogic", diskSpec.getValue().getAdapterType()); + assertEquals("eagerZeroedThick", diskSpec.getValue().getDiskType()); + } + } + + @Test + public void syncVolumeToVmDefaultFolderCopiesBaseFolderVolumeWithTargetAdapter() throws Exception { + DatacenterMO datacenter = mock(DatacenterMO.class); + DatastoreMO datastore = mock(DatastoreMO.class); + VmwareContext context = mock(VmwareContext.class); + ManagedObjectReference datacenterMor = mock(ManagedObjectReference.class); + + when(datastore.getName()).thenReturn("datastore"); + when(datastore.folderExists("[datastore]", "vm-name")).thenReturn(true); + when(datastore.fileExists("[datastore] fcd/volume.vmdk")).thenReturn(true); + when(datastore.getContext()).thenReturn(context); + when(datacenter.getMor()).thenReturn(datacenterMor); + + try (MockedConstruction diskManagers = mockConstruction(VirtualDiskManagerMO.class)) { + Pair result = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(datacenter, "vm-name", datastore, "volume", null, + VmdkAdapterType.lsilogic, Storage.ProvisioningType.THIN); + + assertEquals("[datastore] vm-name/volume.vmdk", result.first()); + assertTrue(result.second()); + VirtualDiskManagerMO diskManager = diskManagers.constructed().get(0); + ArgumentCaptor diskSpec = ArgumentCaptor.forClass(FileBackedVirtualDiskSpec.class); + InOrder inOrder = Mockito.inOrder(diskManager); + inOrder.verify(diskManager).copyVirtualDisk("[datastore] fcd/volume.vmdk", datacenterMor, + "[datastore] vm-name/volume.vmdk", datacenterMor, diskSpec.capture(), true); + inOrder.verify(diskManager).deleteVirtualDisk("[datastore] fcd/volume.vmdk", datacenterMor); + assertEquals("lsiLogic", diskSpec.getValue().getAdapterType()); + assertEquals("thin", diskSpec.getValue().getDiskType()); + } + } +} diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java index 288a108249e1..577fffa9257e 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java @@ -1284,6 +1284,12 @@ public void createDisk(String vmdkDatastorePath, long sizeInMb, ManagedObjectRef createDisk(vmdkDatastorePath, VirtualDiskType.THIN, VirtualDiskMode.PERSISTENT, null, sizeInMb, morDs, controllerKey, vSphereStoragePolicyId); } + public void createDisk(String vmdkDatastorePath, Storage.ProvisioningType provisioningType, long sizeInMb, ManagedObjectReference morDs, + int controllerKey, String vSphereStoragePolicyId) throws Exception { + createDisk(vmdkDatastorePath, VmwareHelper.getVirtualDiskType(provisioningType), VirtualDiskMode.PERSISTENT, null, sizeInMb, morDs, + controllerKey, vSphereStoragePolicyId); + } + // vmdkDatastorePath: [datastore name] vmdkFilePath public void createDisk(String vmdkDatastorePath, VirtualDiskType diskType, VirtualDiskMode diskMode, String rdmDeviceName, long sizeInMb, ManagedObjectReference morDs, int controllerKey, String vSphereStoragePolicyId) throws Exception { @@ -1435,6 +1441,11 @@ public void attachDisk(String[] vmdkDatastorePathChain, ManagedObjectReference m } public void attachDisk(String[] vmdkDatastorePathChain, ManagedObjectReference morDs, String diskController, String vSphereStoragePolicyId, Long maxIops) throws Exception { + attachDisk(vmdkDatastorePathChain, morDs, diskController, vSphereStoragePolicyId, maxIops, true); + } + + public void attachDisk(String[] vmdkDatastorePathChain, ManagedObjectReference morDs, String diskController, String vSphereStoragePolicyId, Long maxIops, + boolean updateVmdkAdapter) throws Exception { if(logger.isTraceEnabled()) logger.trace("vCenter API trace - attachDisk(). target MOR: " + _mor.getValue() + ", vmdkDatastorePath: " + GSON.toJson(vmdkDatastorePathChain) + ", datastore: " + morDs.getValue()); @@ -1465,7 +1476,7 @@ public void attachDisk(String[] vmdkDatastorePathChain, ManagedObjectReference m synchronized (_mor.getValue().intern()) { VirtualDevice newDisk = VmwareHelper.prepareDiskDevice(this, null, controllerKey, vmdkDatastorePathChain, morDs, unitNumber, 1, maxIops); - if (StringUtils.isNotBlank(diskController)) { + if (updateVmdkAdapter && StringUtils.isNotBlank(diskController)) { String vmdkFileName = vmdkDatastorePathChain[0]; updateVmdkAdapter(vmdkFileName, diskController); } diff --git a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareHelper.java b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareHelper.java index 89f6d7abd7d0..a65bfa52edbd 100644 --- a/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareHelper.java +++ b/vmware-base/src/main/java/com/cloud/hypervisor/vmware/util/VmwareHelper.java @@ -47,6 +47,7 @@ import com.cloud.hypervisor.vmware.mo.DistributedVirtualSwitchMO; import com.cloud.hypervisor.vmware.mo.HypervisorHostHelper; import com.cloud.serializer.GsonHelper; +import com.cloud.storage.Storage; import com.cloud.storage.Volume; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; @@ -101,6 +102,7 @@ import com.vmware.vim25.VirtualCdrom; import com.vmware.vim25.VirtualCdromIsoBackingInfo; import com.vmware.vim25.VirtualCdromRemotePassthroughBackingInfo; +import com.vmware.vim25.VirtualDiskType; import com.vmware.vim25.VirtualDevice; import com.vmware.vim25.VirtualDeviceBackingInfo; import com.vmware.vim25.VirtualDeviceConnectInfo; @@ -1143,6 +1145,16 @@ public static Pair chooseRequiredDiskControllers(Pair(convertedRootDiskController, convertedDataDiskController); } + public static VirtualDiskType getVirtualDiskType(Storage.ProvisioningType provisioningType) { + if (provisioningType == Storage.ProvisioningType.FAT) { + return VirtualDiskType.EAGER_ZEROED_THICK; + } + if (provisioningType == Storage.ProvisioningType.SPARSE) { + return VirtualDiskType.PREALLOCATED; + } + return VirtualDiskType.THIN; + } + protected static boolean diskControllersShareTheSameBusType(String rootDiskController, String dataDiskController) { DiskControllerType rootDiskControllerType = DiskControllerType.getType(rootDiskController); DiskControllerType dataDiskControllerType = DiskControllerType.getType(dataDiskController); diff --git a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/util/VmwareHelperTest.java b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/util/VmwareHelperTest.java index 54a312909b3d..d420f312c379 100644 --- a/vmware-base/src/test/java/com/cloud/hypervisor/vmware/util/VmwareHelperTest.java +++ b/vmware-base/src/test/java/com/cloud/hypervisor/vmware/util/VmwareHelperTest.java @@ -22,10 +22,12 @@ import com.cloud.hypervisor.vmware.mo.ClusterMO; import com.cloud.hypervisor.vmware.mo.HostMO; +import com.cloud.storage.Storage; import com.vmware.vim25.DatastoreInfo; import com.vmware.vim25.Description; import com.vmware.vim25.ManagedObjectReference; import com.vmware.vim25.VirtualDiskFlatVer2BackingInfo; +import com.vmware.vim25.VirtualDiskType; import org.apache.cloudstack.vm.UnmanagedInstanceTO; import org.junit.Assert; import org.junit.Before; @@ -98,6 +100,14 @@ public void prepareDiskDeviceTestLimitingIOPSToZero() throws Exception { assertNull(virtualDisk.getStorageIOAllocation()); } + @Test + public void getVirtualDiskTypeMapsCloudStackProvisioningTypes() { + assertEquals(VirtualDiskType.THIN, VmwareHelper.getVirtualDiskType(Storage.ProvisioningType.THIN)); + assertEquals(VirtualDiskType.PREALLOCATED, VmwareHelper.getVirtualDiskType(Storage.ProvisioningType.SPARSE)); + assertEquals(VirtualDiskType.EAGER_ZEROED_THICK, VmwareHelper.getVirtualDiskType(Storage.ProvisioningType.FAT)); + assertEquals(VirtualDiskType.THIN, VmwareHelper.getVirtualDiskType(null)); + } + @Test public void testGetUnmanageInstanceDisks() { List disks = VmwareHelper.getUnmanageInstanceDisks(virtualMachineMO);