From 3f69c83f96f927f4a84b2d4b29bce168caf0530d Mon Sep 17 00:00:00 2001 From: SudharmaJain Date: Tue, 19 Sep 2017 15:12:17 +0530 Subject: [PATCH] CLOUDSTACK-9956: File search on the vmware datastore may select wrong file if there are multiple files with same name (#2153) If there are multiple files with the same name on vmware datastore, search operation may select any one file during volume related operations. This involves volume attach/detach, volume download, volume snapshot etc. While using NetApp as the backup solution. This has .snapshot folder on the datastore and sometimes files from this folder gets selected during volume operations and the operation fails. Because of wrong selection of file following exception can be observed while volume deletion. 2017-02-23 19:39:05,750 ERROR [c.c.s.r.VmwareStorageProcessor] (DirectAgent-304:ctx-a1dbf5d8 ac.local) delete volume failed due to Exception: java.lang.RuntimeException Message: Cannot delete file [4cbcd46d44c53f5c8244c0aad26a97e1] .snapshot/hourly.2017-02-23_1605/r-97-VM/ROOT-97.vmdk To fix this behavior I have added a global configuration by name vmware.search.exclude.folders which can be comma separated list of folder paths. I have also added a unit test to test the new method. --- .../com/cloud/hypervisor/guru/VMwareGuru.java | 1 + .../vmware/manager/VmwareManager.java | 3 + .../vmware/manager/VmwareManagerImpl.java | 2 +- .../manager/VmwareStorageManagerImpl.java | 11 +- .../vmware/resource/VmwareResource.java | 14 +-- .../resource/VmwareStorageLayoutHelper.java | 35 ++++-- .../resource/VmwareStorageProcessor.java | 23 ++-- .../hypervisor/vmware/mo/DatastoreMO.java | 28 ++++- .../hypervisor/vmware/mo/DatastoreMOTest.java | 114 ++++++++++++++++++ 9 files changed, 199 insertions(+), 32 deletions(-) create mode 100644 vmware-base/test/com/cloud/hypervisor/vmware/mo/DatastoreMOTest.java diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/guru/VMwareGuru.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/guru/VMwareGuru.java index 7b6accf5357..d8076fd5d68 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/guru/VMwareGuru.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/guru/VMwareGuru.java @@ -531,6 +531,7 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co // FIXME: Fix long checkPointId2 = _checkPointMgr.pushCheckPoint(new VmwareCleanupMaid(hostDetails.get("guid"), workerName2)); cmd.setContextParam("worker2", workerName2); cmd.setContextParam("checkpoint2", String.valueOf(checkPointId2)); + cmd.setContextParam("searchexludefolders", _vmwareMgr.s_vmwareSearchExcludeFolder.value()); } return new Pair(Boolean.TRUE, cmdTarget.first().getId()); diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareManager.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareManager.java index 65963eb25bd..4a4d2ea7a53 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareManager.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareManager.java @@ -39,6 +39,9 @@ public interface VmwareManager { public static final ConfigKey s_vmwareCleanOldWorderVMs = new ConfigKey("Advanced", Boolean.class, "vmware.clean.old.worker.vms", "false", "If a worker vm is older then twice the 'job.expire.minutes' + 'job.cancel.threshold.minutes' , remove it.", true, ConfigKey.Scope.Global); + static final ConfigKey s_vmwareSearchExcludeFolder = new ConfigKey("Advanced", String.class, "vmware.search.exclude.folders", null, + "Comma seperated list of Datastore Folders to exclude from VMWare search", true, ConfigKey.Scope.Global); + String composeWorkerName(); String getSystemVMIsoFileNameOnDatastore(); diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java index c003b442ffe..53e3239113e 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java @@ -229,7 +229,7 @@ public class VmwareManagerImpl extends ManagerBase implements VmwareManager, Vmw @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] {s_vmwareNicHotplugWaitTimeout, s_vmwareCleanOldWorderVMs, templateCleanupInterval}; + return new ConfigKey[] {s_vmwareNicHotplugWaitTimeout, s_vmwareCleanOldWorderVMs, templateCleanupInterval, s_vmwareSearchExcludeFolder}; } @Override diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java index 3158ad47925..00298cedc75 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareStorageManagerImpl.java @@ -307,6 +307,7 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { String snapshotUuid = cmd.getSnapshotUuid(); // not null: Precondition. String prevSnapshotUuid = cmd.getPrevSnapshotUuid(); String prevBackupUuid = cmd.getPrevBackupUuid(); + String searchExcludedFolders = cmd.getContextParam("searchexludefolders"); VirtualMachineMO workerVm = null; String workerVMName = null; String volumePath = cmd.getVolumePath(); @@ -344,7 +345,7 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { workerVm = vmMo; // attach volume to worker VM - String datastoreVolumePath = getVolumePathInDatastore(dsMo, volumePath + ".vmdk"); + String datastoreVolumePath = getVolumePathInDatastore(dsMo, volumePath + ".vmdk", searchExcludedFolders); vmMo.attachDisk(new String[] {datastoreVolumePath}, morDs); } } @@ -986,6 +987,8 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { VirtualMachineMO workerVm = null; VirtualMachineMO vmMo = null; String exportName = UUID.randomUUID().toString(); + String searchExcludedFolders = cmd.getContextParam("searchexludefolders"); + try { ManagedObjectReference morDs = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, poolId); @@ -1009,7 +1012,7 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { } //attach volume to worker VM - String datastoreVolumePath = getVolumePathInDatastore(dsMo, volumePath + ".vmdk"); + String datastoreVolumePath = getVolumePathInDatastore(dsMo, volumePath + ".vmdk", searchExcludedFolders); workerVm.attachDisk(new String[] {datastoreVolumePath}, morDs); vmMo = workerVm; } @@ -1030,8 +1033,8 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { } } - private String getVolumePathInDatastore(DatastoreMO dsMo, String volumeFileName) throws Exception { - String datastoreVolumePath = dsMo.searchFileInSubFolders(volumeFileName, true); + private String getVolumePathInDatastore(DatastoreMO dsMo, String volumeFileName, String searchExcludeFolders) throws Exception { + String datastoreVolumePath = dsMo.searchFileInSubFolders(volumeFileName, true, searchExcludeFolders); if (datastoreVolumePath == null) { throw new CloudRuntimeException("Unable to find file " + volumeFileName + " in datastore " + dsMo.getName()); } diff --git a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java index b8a3495dd6a..ccb5d0f7abb 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -645,7 +645,7 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa ManagedObjectReference vmFolderMor = dataCenterMo.getVmFolder(); //2nd param - String vmxFilePath = dsMo.searchFileInSubFolders(vmName + ".vmx", false); + String vmxFilePath = dsMo.searchFileInSubFolders(vmName + ".vmx", false, VmwareManager.s_vmwareSearchExcludeFolder.value()); // 5th param ManagedObjectReference morPool = hyperHost.getHyperHostOwnerResourcePool(); @@ -1683,7 +1683,7 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa assert (vmSpec.getMinSpeed() != null) && (rootDiskDataStoreDetails != null); boolean vmFolderExists = rootDiskDataStoreDetails.second().folderExists(String.format("[%s]", rootDiskDataStoreDetails.second().getName()), vmNameOnVcenter); - String vmxFileFullPath = dsRootVolumeIsOn.searchFileInSubFolders(vmNameOnVcenter + ".vmx", false); + String vmxFileFullPath = dsRootVolumeIsOn.searchFileInSubFolders(vmNameOnVcenter + ".vmx", false, VmwareManager.s_vmwareSearchExcludeFolder.value()); if (vmFolderExists && vmxFileFullPath != null) { // VM can be registered only if .vmx is present. registerVm(vmNameOnVcenter, dsRootVolumeIsOn); vmMo = hyperHost.findVmOnHyperHost(vmInternalCSName); @@ -2360,7 +2360,7 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa DatastoreFile file = new DatastoreFile(disks[i]); if (!isManaged && file.getDir() != null && file.getDir().isEmpty()) { s_logger.info("Perform run-time datastore folder upgrade. sync " + disks[i] + " to VM folder"); - disks[i] = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dcMo, vmMo.getName(), dsMo, file.getFileBaseName()); + disks[i] = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dcMo, vmMo.getName(), dsMo, file.getFileBaseName(), VmwareManager.s_vmwareSearchExcludeFolder.value()); } } return disks; @@ -2370,13 +2370,13 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa if (isManaged) { if (volumeTO.getVolumeType() == Volume.Type.ROOT) { - datastoreDiskPath = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dcMo, vmMo.getName(), dsMo, volumeTO.getName()); + datastoreDiskPath = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dcMo, vmMo.getName(), dsMo, volumeTO.getName(), VmwareManager.s_vmwareSearchExcludeFolder.value()); } else { datastoreDiskPath = dsMo.getDatastorePath(dsMo.getName() + ".vmdk"); } } else { - datastoreDiskPath = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dcMo, vmMo.getName(), dsMo, volumeTO.getPath()); + datastoreDiskPath = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dcMo, vmMo.getName(), dsMo, volumeTO.getPath(), VmwareManager.s_vmwareSearchExcludeFolder.value()); } if (!dsMo.fileExists(datastoreDiskPath)) { @@ -2802,7 +2802,7 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa vmFolder = new DatastoreFile(fileInDatastore.getDatastoreName(), fileInDatastore.getDir()); DatastoreMO dsMo = new DatastoreMO(dcMo.getContext(), dcMo.findDatastore(fileInDatastore.getDatastoreName())); s_logger.debug("Deleting file: " + file.getName()); - dsMo.deleteFile(file.getName(), dcMo.getMor(), true); + dsMo.deleteFile(file.getName(), dcMo.getMor(), true, VmwareManager.s_vmwareSearchExcludeFolder.value()); } // Delete files that are present in the VM folder - this will take care of the VM disks as well. DatastoreMO vmFolderDsMo = new DatastoreMO(dcMo.getContext(), dcMo.findDatastore(vmFolder.getDatastoreName())); @@ -2811,7 +2811,7 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa for (String file : files) { String vmDiskFileFullPath = String.format("%s/%s", vmFolder.getPath(), file); s_logger.debug("Deleting file: " + vmDiskFileFullPath); - vmFolderDsMo.deleteFile(vmDiskFileFullPath, dcMo.getMor(), true); + vmFolderDsMo.deleteFile(vmDiskFileFullPath, dcMo.getMor(), true, VmwareManager.s_vmwareSearchExcludeFolder.value()); } } // Delete VM folder diff --git a/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareStorageLayoutHelper.java b/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareStorageLayoutHelper.java index 4096333c493..fc79a4b0faf 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareStorageLayoutHelper.java +++ b/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareStorageLayoutHelper.java @@ -67,6 +67,11 @@ public class VmwareStorageLayoutHelper { } public static String findVolumeDatastoreFullPath(DatastoreMO dsMo, String vmName, String vmdkFileName) throws Exception { + return findVolumeDatastoreFullPath(dsMo, vmName, vmdkFileName, null); + } + + public static String findVolumeDatastoreFullPath(DatastoreMO dsMo, String vmName, String vmdkFileName, String excludeFolders) throws Exception { + if (vmName != null) { String path = getVmwareDatastorePathFromVmdkFileName(dsMo, vmName, vmdkFileName); if (!dsMo.fileExists(path)) { @@ -80,7 +85,7 @@ public class VmwareStorageLayoutHelper { String path = getLegacyDatastorePathFromVmdkFileName(dsMo, vmdkFileName); if (!dsMo.fileExists(path)) { // Datastore file movement is not atomic operations, we need to sync and repair - path = dsMo.searchFileInSubFolders(vmdkFileName, false); + path = dsMo.searchFileInSubFolders(vmdkFileName, false, excludeFolders); // to save one call to vCenter, we won't check file existence for this round, so the caller // may still fail with exception, but if that's case, we will let it fail anyway @@ -90,6 +95,10 @@ public class VmwareStorageLayoutHelper { } public static String syncVolumeToVmDefaultFolder(DatacenterMO dcMo, String vmName, DatastoreMO ds, String vmdkName) throws Exception { + return syncVolumeToVmDefaultFolder(dcMo, vmName, ds, vmdkName, null); + } + + public static String syncVolumeToVmDefaultFolder(DatacenterMO dcMo, String vmName, DatastoreMO ds, String vmdkName, String excludeFolders) throws Exception { assert (ds != null); if (!ds.folderExists(String.format("[%s]", ds.getName()), vmName)) { @@ -109,7 +118,7 @@ public class VmwareStorageLayoutHelper { // be left over in its previous owner VM. We will do a fixup synchronization here by moving it to root // again. // - syncVolumeToRootFolder(dcMo, ds, vmdkName, vmName); + syncVolumeToRootFolder(dcMo, ds, vmdkName, vmName, excludeFolders); } if (ds.fileExists(vmdkFullCloneModeLegacyPair[1])) { @@ -134,7 +143,11 @@ public class VmwareStorageLayoutHelper { } public static void syncVolumeToRootFolder(DatacenterMO dcMo, DatastoreMO ds, String vmdkName, String vmName) throws Exception { - String fileDsFullPath = ds.searchFileInSubFolders(vmdkName + ".vmdk", false); + syncVolumeToRootFolder(dcMo, ds, vmdkName, null); + } + + public static void syncVolumeToRootFolder(DatacenterMO dcMo, DatastoreMO ds, String vmdkName, String vmName, String excludeFolders) throws Exception { + String fileDsFullPath = ds.searchFileInSubFolders(vmdkName + ".vmdk", false, excludeFolders); if (fileDsFullPath == null) return; @@ -259,13 +272,17 @@ public class VmwareStorageLayoutHelper { } public static void deleteVolumeVmdkFiles(DatastoreMO dsMo, String volumeName, DatacenterMO dcMo) throws Exception { + deleteVolumeVmdkFiles(dsMo, volumeName, dcMo, null); + } + + public static void deleteVolumeVmdkFiles(DatastoreMO dsMo, String volumeName, DatacenterMO dcMo, String excludeFolders) throws Exception { String fileName = volumeName + ".vmdk"; String fileFullPath = getLegacyDatastorePathFromVmdkFileName(dsMo, fileName); if (!dsMo.fileExists(fileFullPath)) - fileFullPath = dsMo.searchFileInSubFolders(fileName, false); + fileFullPath = dsMo.searchFileInSubFolders(fileName, false, excludeFolders); if (fileFullPath != null) { - dsMo.deleteFile(fileFullPath, dcMo.getMor(), true); + dsMo.deleteFile(fileFullPath, dcMo.getMor(), true, excludeFolders); } else { s_logger.warn("Unable to locate VMDK file: " + fileName); } @@ -273,9 +290,9 @@ public class VmwareStorageLayoutHelper { fileName = volumeName + "-flat.vmdk"; fileFullPath = getLegacyDatastorePathFromVmdkFileName(dsMo, fileName); if (!dsMo.fileExists(fileFullPath)) - fileFullPath = dsMo.searchFileInSubFolders(fileName, false); + fileFullPath = dsMo.searchFileInSubFolders(fileName, false, excludeFolders); if (fileFullPath != null) { - dsMo.deleteFile(fileFullPath, dcMo.getMor(), true); + dsMo.deleteFile(fileFullPath, dcMo.getMor(), true, excludeFolders); } else { s_logger.warn("Unable to locate VMDK file: " + fileName); } @@ -283,9 +300,9 @@ public class VmwareStorageLayoutHelper { fileName = volumeName + "-delta.vmdk"; fileFullPath = getLegacyDatastorePathFromVmdkFileName(dsMo, fileName); if (!dsMo.fileExists(fileFullPath)) - fileFullPath = dsMo.searchFileInSubFolders(fileName, false); + fileFullPath = dsMo.searchFileInSubFolders(fileName, false, excludeFolders); if (fileFullPath != null) { - dsMo.deleteFile(fileFullPath, dcMo.getMor(), true); + dsMo.deleteFile(fileFullPath, dcMo.getMor(), true, excludeFolders); } else { s_logger.warn("Unable to locate VMDK file: " + fileName); } diff --git a/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareStorageProcessor.java b/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareStorageProcessor.java index 802657515fd..012556eb1ca 100644 --- a/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareStorageProcessor.java +++ b/plugins/hypervisors/vmware/src/com/cloud/storage/resource/VmwareStorageProcessor.java @@ -450,6 +450,7 @@ public class VmwareStorageProcessor implements StorageProcessor { VolumeObjectTO volume = (VolumeObjectTO)destData; DataStoreTO primaryStore = volume.getDataStore(); DataStoreTO srcStore = template.getDataStore(); + String searchExcludedFolders = cmd.getContextParam("searchexludefolders"); try { VmwareContext context = hostService.getServiceContext(null); @@ -481,7 +482,7 @@ public class VmwareStorageProcessor implements StorageProcessor { String volumeDatastorePath = vmdkFilePair[0]; synchronized (this) { s_logger.info("Delete file if exists in datastore to clear the way for creating the volume. file: " + volumeDatastorePath); - VmwareStorageLayoutHelper.deleteVolumeVmdkFiles(dsMo, vmdkName, dcMo); + VmwareStorageLayoutHelper.deleteVolumeVmdkFiles(dsMo, vmdkName, dcMo, searchExcludedFolders); vmMo.createDisk(volumeDatastorePath, (int)(volume.getSize() / (1024L * 1024L)), morDatastore, -1); vmMo.detachDisk(volumeDatastorePath, false); } @@ -527,14 +528,14 @@ public class VmwareStorageProcessor implements StorageProcessor { vmMo.destroy(); String srcFile = dsMo.getDatastorePath(vmdkName, true); - dsMo.deleteFile(srcFile, dcMo.getMor(), true); + dsMo.deleteFile(srcFile, dcMo.getMor(), true, searchExcludedFolders); } // restoreVM - move the new ROOT disk into corresponding VM folder VirtualMachineMO restoreVmMo = dcMo.findVm(volume.getVmName()); if (restoreVmMo != null) { String vmNameInVcenter = restoreVmMo.getName(); // VM folder name in datastore will be VM's name in vCenter. if (dsMo.folderExists(String.format("[%s]", dsMo.getName()), vmNameInVcenter)) { - VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dcMo, vmNameInVcenter, dsMo, vmdkFileBaseName); + VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dcMo, vmNameInVcenter, dsMo, vmdkFileBaseName, searchExcludedFolders); } } @@ -631,8 +632,8 @@ public class VmwareStorageProcessor implements StorageProcessor { } - private String getVolumePathInDatastore(DatastoreMO dsMo, String volumeFileName) throws Exception { - String datastoreVolumePath = dsMo.searchFileInSubFolders(volumeFileName, true); + private String getVolumePathInDatastore(DatastoreMO dsMo, String volumeFileName, String searchExcludedFolders) throws Exception { + String datastoreVolumePath = dsMo.searchFileInSubFolders(volumeFileName, true, searchExcludedFolders); assert (datastoreVolumePath != null) : "Virtual disk file missing from datastore."; return datastoreVolumePath; } @@ -642,6 +643,7 @@ public class VmwareStorageProcessor implements StorageProcessor { VirtualMachineMO workerVm = null; VirtualMachineMO vmMo = null; String exportName = UUID.randomUUID().toString().replace("-", ""); + String searchExcludedFolders = cmd.getContextParam("searchexludefolders"); try { ManagedObjectReference morDs = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(hyperHost, poolId); @@ -665,7 +667,7 @@ public class VmwareStorageProcessor implements StorageProcessor { } // attach volume to worker VM - String datastoreVolumePath = getVolumePathInDatastore(dsMo, volumePath + ".vmdk"); + String datastoreVolumePath = getVolumePathInDatastore(dsMo, volumePath + ".vmdk", searchExcludedFolders); workerVm.attachDisk(new String[] {datastoreVolumePath}, morDs); vmMo = workerVm; } @@ -690,6 +692,7 @@ public class VmwareStorageProcessor implements StorageProcessor { VolumeObjectTO srcVolume = (VolumeObjectTO)cmd.getSrcTO(); VolumeObjectTO destVolume = (VolumeObjectTO)cmd.getDestTO(); String vmName = srcVolume.getVmName(); + String searchExcludedFolders = cmd.getContextParam("searchexludefolders"); VmwareContext context = hostService.getServiceContext(cmd); try { @@ -1380,7 +1383,7 @@ public class VmwareStorageProcessor implements StorageProcessor { if (isManaged) { datastoreVolumePath = dsMo.getDatastorePath(dsMo.getName() + ".vmdk"); } else { - datastoreVolumePath = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dsMo.getOwnerDatacenter().first(), vmName, dsMo, volumeTO.getPath()); + datastoreVolumePath = VmwareStorageLayoutHelper.syncVolumeToVmDefaultFolder(dsMo.getOwnerDatacenter().first(), vmName, dsMo, volumeTO.getPath(), VmwareManager.s_vmwareSearchExcludeFolder.value()); } } else { if (isManaged) { @@ -1415,7 +1418,7 @@ public class VmwareStorageProcessor implements StorageProcessor { if (isManaged) { handleDatastoreAndVmdkDetachManaged(diskUuid, iScsiName, storageHost, storagePort); } else { - VmwareStorageLayoutHelper.syncVolumeToRootFolder(dsMo.getOwnerDatacenter().first(), dsMo, volumeTO.getPath(), vmName); + VmwareStorageLayoutHelper.syncVolumeToRootFolder(dsMo.getOwnerDatacenter().first(), dsMo, volumeTO.getPath(), vmName, VmwareManager.s_vmwareSearchExcludeFolder.value()); } } @@ -1591,7 +1594,7 @@ public class VmwareStorageProcessor implements StorageProcessor { } catch (Exception e) { s_logger.error("Deleting file " + volumeDatastorePath + " due to error: " + e.getMessage()); - VmwareStorageLayoutHelper.deleteVolumeVmdkFiles(dsMo, volumeUuid.toString(), dcMo); + VmwareStorageLayoutHelper.deleteVolumeVmdkFiles(dsMo, volumeUuid.toString(), dcMo, VmwareManager.s_vmwareSearchExcludeFolder.value()); throw new CloudRuntimeException("Unable to create volume due to: " + e.getMessage()); } } @@ -1753,7 +1756,7 @@ public class VmwareStorageProcessor implements StorageProcessor { } } - VmwareStorageLayoutHelper.deleteVolumeVmdkFiles(dsMo, vol.getPath(), new DatacenterMO(context, morDc)); + VmwareStorageLayoutHelper.deleteVolumeVmdkFiles(dsMo, vol.getPath(), new DatacenterMO(context, morDc), VmwareManager.s_vmwareSearchExcludeFolder.value()); return new Answer(cmd, true, "Success"); } catch (Throwable e) { diff --git a/vmware-base/src/com/cloud/hypervisor/vmware/mo/DatastoreMO.java b/vmware-base/src/com/cloud/hypervisor/vmware/mo/DatastoreMO.java index 290e1a0bdb6..1152ba5078a 100644 --- a/vmware-base/src/com/cloud/hypervisor/vmware/mo/DatastoreMO.java +++ b/vmware-base/src/com/cloud/hypervisor/vmware/mo/DatastoreMO.java @@ -166,6 +166,10 @@ public class DatastoreMO extends BaseMO { } public boolean deleteFile(String path, ManagedObjectReference morDc, boolean testExistence) throws Exception { + return deleteFile(path, morDc, testExistence, null); + } + + public boolean deleteFile(String path, ManagedObjectReference morDc, boolean testExistence, String excludeFolders) throws Exception { String datastoreName = getName(); ManagedObjectReference morFileManager = _context.getServiceContent().getFileManager(); @@ -180,7 +184,7 @@ public class DatastoreMO extends BaseMO { try { if (testExistence && !fileExists(fullPath)) { - String searchResult = searchFileInSubFolders(file.getFileName(), true); + String searchResult = searchFileInSubFolders(file.getFileName(), true, excludeFolders); if (searchResult == null) { return true; } else { @@ -352,8 +356,13 @@ public class DatastoreMO extends BaseMO { } public String searchFileInSubFolders(String fileName, boolean caseInsensitive) throws Exception { + return searchFileInSubFolders(fileName,caseInsensitive,null); + } + + public String searchFileInSubFolders(String fileName, boolean caseInsensitive, String excludeFolders) throws Exception { String datastorePath = "[" + getName() + "]"; String rootDirectoryFilePath = String.format("%s %s", datastorePath, fileName); + String[] searchExcludedFolders = getSearchExcludedFolders(excludeFolders); if (fileExists(rootDirectoryFilePath)) { return rootDirectoryFilePath; } @@ -380,6 +389,9 @@ public class DatastoreMO extends BaseMO { if (parentFolderPath.endsWith("]")) absoluteFileName += " "; absoluteFileName += fi.getPath(); + if(isValidCloudStackFolderPath(parentFolderPath, searchExcludedFolders)) { + return absoluteFileName; + } break; } } @@ -387,6 +399,20 @@ public class DatastoreMO extends BaseMO { return absoluteFileName; } + private String[] getSearchExcludedFolders(String excludeFolders) { + return excludeFolders != null ? excludeFolders.replaceAll("\\s","").split(",") : new String[] {}; + } + + private boolean isValidCloudStackFolderPath(String dataStoreFolderPath, String[] searchExcludedFolders) throws Exception { + String dsFolder = dataStoreFolderPath.replaceFirst("\\[" + getName() + "\\]", "").trim(); + for( String excludedFolder : searchExcludedFolders) { + if (dsFolder.startsWith(excludedFolder)) { + return false; + } + } + return true; + } + public boolean isAccessibleToHost(String hostValue) throws Exception { boolean isAccessible = true; List hostMounts = getHostMounts(); diff --git a/vmware-base/test/com/cloud/hypervisor/vmware/mo/DatastoreMOTest.java b/vmware-base/test/com/cloud/hypervisor/vmware/mo/DatastoreMOTest.java new file mode 100644 index 00000000000..394d199ce50 --- /dev/null +++ b/vmware-base/test/com/cloud/hypervisor/vmware/mo/DatastoreMOTest.java @@ -0,0 +1,114 @@ +// 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.hypervisor.vmware.mo; + +import com.cloud.hypervisor.vmware.util.VmwareClient; +import com.cloud.hypervisor.vmware.util.VmwareContext; +import com.vmware.vim25.FileInfo; +import com.vmware.vim25.HostDatastoreBrowserSearchResults; +import com.vmware.vim25.ManagedObjectReference; +import com.vmware.vim25.VimPortType; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.ArrayList; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.when; + +/** + * Created by sudharma_jain on 6/13/17. + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest(DatastoreMO.class) +public class DatastoreMOTest { + @Mock + VmwareContext _context ; + @Mock + VmwareClient _client; + @Mock + ManagedObjectReference _mor; + @Mock + HostDatastoreBrowserMO browserMo; + @Mock + VimPortType vimPortType; + + DatastoreMO datastoreMO ; + String fileName = "ROOT-5.vmdk"; + + + @Before + public void setUp() throws Exception { + + datastoreMO = new DatastoreMO(_context, _mor); + PowerMockito.whenNew(HostDatastoreBrowserMO.class).withAnyArguments().thenReturn(browserMo); + when(_context.getVimClient()).thenReturn(_client); + when(_client.getDynamicProperty(any(ManagedObjectReference.class), eq("name"))).thenReturn("252d36c96cfb32f48ce7756ccb79ae37"); + + ArrayList results = new ArrayList<>(); + + HostDatastoreBrowserSearchResults r1 = new HostDatastoreBrowserSearchResults(); + FileInfo f1 = new FileInfo(); + f1.setPath(fileName); + r1.getFile().add(f1); + r1.setFolderPath("[252d36c96cfb32f48ce7756ccb79ae37] .snapshot/hourly.2017-02-23_1705/i-2-5-VM/"); + + HostDatastoreBrowserSearchResults r2 = new HostDatastoreBrowserSearchResults(); + FileInfo f2 = new FileInfo(); + f2.setPath(fileName); + r2.getFile().add(f2); + r2.setFolderPath("[252d36c96cfb32f48ce7756ccb79ae37] .snapshot/hourly.2017-02-23_1605/i-2-5-VM/"); + + HostDatastoreBrowserSearchResults r3 = new HostDatastoreBrowserSearchResults(); + FileInfo f3 = new FileInfo(); + f3.setPath(fileName); + r3.getFile().add(f3); + r3.setFolderPath("[252d36c96cfb32f48ce7756ccb79ae37] i-2-5-VM/"); + + results.add(r1); + results.add(r2); + results.add(r3); + + when(browserMo.searchDatastore(any(String.class), any(String.class), eq(true))).thenReturn(null); + when(browserMo.searchDatastoreSubFolders(any(String.class),any(String.class), any(Boolean.class) )).thenReturn(results); + } + + @After + public void tearDown() throws Exception { + + } + + @Test + public void testSearchFileInSubFolders() throws Exception { + assertEquals("Unexpected Behavior: search should exclude .snapshot folder", "[252d36c96cfb32f48ce7756ccb79ae37] i-2-5-VM/ROOT-5.vmdk", datastoreMO.searchFileInSubFolders(fileName, false, ".snapshot") ); + } + + @Test + public void testSearchFileInSubFoldersWithExcludeMultipleFolders() throws Exception { + assertEquals("Unexpected Behavior: search should exclude folders", datastoreMO.searchFileInSubFolders(fileName, false, "i-2-5-VM, .snapshot/hourly.2017-02-23_1705"), "[252d36c96cfb32f48ce7756ccb79ae37] .snapshot/hourly.2017-02-23_1605/i-2-5-VM/ROOT-5.vmdk" ); + } + +} \ No newline at end of file