diff --git a/client/tomcatconf/applicationContext.xml.in b/client/tomcatconf/applicationContext.xml.in index 26b698af144..2fe51414e3e 100644 --- a/client/tomcatconf/applicationContext.xml.in +++ b/client/tomcatconf/applicationContext.xml.in @@ -733,6 +733,7 @@ + diff --git a/core/src/com/cloud/agent/api/MigrateWithStorageCommand.java b/core/src/com/cloud/agent/api/MigrateWithStorageCommand.java index 058aa15338e..a108a2a7bed 100644 --- a/core/src/com/cloud/agent/api/MigrateWithStorageCommand.java +++ b/core/src/com/cloud/agent/api/MigrateWithStorageCommand.java @@ -24,10 +24,18 @@ import com.cloud.agent.api.to.StorageFilerTO; public class MigrateWithStorageCommand extends Command { VirtualMachineTO vm; Map volumeToFiler; + String tgtHost; public MigrateWithStorageCommand(VirtualMachineTO vm, Map volumeToFiler) { this.vm = vm; this.volumeToFiler = volumeToFiler; + this.tgtHost = null; + } + + public MigrateWithStorageCommand(VirtualMachineTO vm, Map volumeToFiler, String tgtHost) { + this.vm = vm; + this.volumeToFiler = volumeToFiler; + this.tgtHost = tgtHost; } public VirtualMachineTO getVirtualMachine() { @@ -38,6 +46,10 @@ public class MigrateWithStorageCommand extends Command { return volumeToFiler; } + public String getTargetHost() { + return tgtHost; + } + @Override public boolean executeInSequence() { return true; 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 45fd12b063f..f9f5f7e7e39 100755 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareManager.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareManager.java @@ -20,6 +20,7 @@ import java.io.File; import java.util.List; import java.util.Map; +import com.cloud.agent.api.to.VolumeTO; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.vmware.manager.VmwareStorageManager; import com.cloud.hypervisor.vmware.mo.HostMO; 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 f33a614bfa6..9013d312a72 100755 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/manager/VmwareManagerImpl.java @@ -38,6 +38,8 @@ import javax.naming.ConfigurationException; import org.apache.cloudstack.api.command.admin.zone.AddVmwareDcCmd; import org.apache.cloudstack.api.command.admin.zone.RemoveVmwareDcCmd; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreRole; import org.apache.log4j.Logger; import com.cloud.agent.AgentManager; @@ -48,6 +50,8 @@ import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; import com.cloud.agent.api.StartupCommand; import com.cloud.agent.api.StartupRoutingCommand; +import com.cloud.agent.api.storage.MigrateVolumeAnswer; +import com.cloud.agent.api.to.VolumeTO; import com.cloud.cluster.ClusterManager; import com.cloud.configuration.Config; import com.cloud.configuration.dao.ConfigurationDao; @@ -99,6 +103,9 @@ import com.cloud.serializer.GsonHelper; import com.cloud.server.ConfigurationServer; import com.cloud.storage.JavaStorageLayer; import com.cloud.storage.StorageLayer; +import com.cloud.storage.StoragePool; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.secondary.SecondaryStorageVmManager; import com.cloud.utils.FileUtil; import com.cloud.utils.NumbersUtil; @@ -114,6 +121,8 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.script.Script; import com.cloud.utils.ssh.SshHelper; import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.dao.VMInstanceDao; import com.google.gson.Gson; import com.vmware.vim25.AboutInfo; import com.vmware.vim25.HostConnectSpec; @@ -150,6 +159,9 @@ public class VmwareManagerImpl extends ManagerBase implements VmwareManager, Vmw @Inject VmwareDatacenterDao _vmwareDcDao; @Inject VmwareDatacenterZoneMapDao _vmwareDcZoneMapDao; @Inject LegacyZoneDao _legacyZoneDao; + @Inject VMInstanceDao _vmDao; + @Inject VolumeDao _volDao; + @Inject DataStoreManager dataStoreMgr; String _mountParent; StorageLayer _storage; 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 00255216cf3..4ae0f305d99 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 @@ -328,7 +328,7 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { workerVm = vmMo; // attach volume to worker VM - String datastoreVolumePath = String.format("[%s] %s.vmdk", dsMo.getName(), volumePath); + String datastoreVolumePath = getVolumePathInDatastore(dsMo, volumePath + ".vmdk"); vmMo.attachDisk(new String[] { datastoreVolumePath }, morDs); } } @@ -1060,7 +1060,7 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { } //attach volume to worker VM - String datastoreVolumePath = String.format("[%s] %s.vmdk", dsMo.getName(), volumePath); + String datastoreVolumePath = getVolumePathInDatastore(dsMo, volumePath + ".vmdk"); workerVm.attachDisk(new String[] { datastoreVolumePath }, morDs); vmMo = workerVm; } @@ -1081,6 +1081,12 @@ public class VmwareStorageManagerImpl implements VmwareStorageManager { } } + private String getVolumePathInDatastore(DatastoreMO dsMo, String volumeFileName) throws Exception { + String datastoreVolumePath = dsMo.searchFileInSubFolders(volumeFileName, true); + assert (datastoreVolumePath != null) : "Virtual disk file missing from datastore."; + return datastoreVolumePath; + } + private Pair copyVolumeFromSecStorage(VmwareHypervisorHost hyperHost, long volumeId, DatastoreMO dsMo, String secStorageUrl, String exportName) throws Exception { 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 0a15d4b5b3d..5f99a152fdd 100755 --- a/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/plugins/hypervisors/vmware/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -37,6 +37,7 @@ import java.util.Random; import java.util.TimeZone; import java.util.UUID; +import javax.inject.Inject; import javax.naming.ConfigurationException; import org.apache.log4j.Logger; @@ -73,6 +74,9 @@ import com.cloud.agent.api.CreateVolumeFromSnapshotCommand; import com.cloud.agent.api.DeleteStoragePoolCommand; import com.cloud.agent.api.DeleteVMSnapshotAnswer; import com.cloud.agent.api.DeleteVMSnapshotCommand; +import com.cloud.agent.api.MigrateWithStorageAnswer; +import com.cloud.agent.api.MigrateWithStorageCommand; +import com.cloud.agent.api.UnregisterVMCommand; import com.cloud.agent.api.GetDomRVersionAnswer; import com.cloud.agent.api.GetDomRVersionCmd; import com.cloud.agent.api.GetHostStatsAnswer; @@ -162,6 +166,12 @@ import com.cloud.agent.api.routing.VmDataCommand; import com.cloud.agent.api.routing.VpnUsersCfgCommand; import com.cloud.agent.api.storage.CopyVolumeAnswer; import com.cloud.agent.api.storage.CopyVolumeCommand; +import com.cloud.agent.api.storage.CreateVolumeOVACommand; +import com.cloud.agent.api.storage.CreateVolumeOVAAnswer; +import com.cloud.agent.api.storage.MigrateVolumeAnswer; +import com.cloud.agent.api.storage.MigrateVolumeCommand; +import com.cloud.agent.api.storage.PrepareOVAPackingAnswer; +import com.cloud.agent.api.storage.PrepareOVAPackingCommand; import com.cloud.agent.api.storage.CreateAnswer; import com.cloud.agent.api.storage.CreateCommand; import com.cloud.agent.api.storage.CreatePrivateTemplateAnswer; @@ -185,6 +195,7 @@ import com.cloud.agent.api.to.VolumeTO; import com.cloud.dc.DataCenter.NetworkType; import com.cloud.dc.Vlan; import com.cloud.exception.InternalErrorException; +import com.cloud.host.Host; import com.cloud.host.Host.Type; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.vmware.manager.VmwareHostService; @@ -195,10 +206,12 @@ import com.cloud.hypervisor.vmware.mo.CustomFieldsManagerMO; import com.cloud.hypervisor.vmware.mo.DatacenterMO; import com.cloud.hypervisor.vmware.mo.DatastoreMO; import com.cloud.hypervisor.vmware.mo.DiskControllerType; +import com.cloud.hypervisor.vmware.mo.HostDatastoreSystemMO; import com.cloud.hypervisor.vmware.mo.HostFirewallSystemMO; import com.cloud.hypervisor.vmware.mo.HostMO; import com.cloud.hypervisor.vmware.mo.HypervisorHostHelper; import com.cloud.hypervisor.vmware.mo.NetworkDetails; +import com.cloud.hypervisor.vmware.mo.VirtualDiskManagerMO; import com.cloud.hypervisor.vmware.mo.VirtualEthernetCardType; import com.cloud.hypervisor.vmware.mo.VirtualMachineMO; import com.cloud.hypervisor.vmware.mo.VirtualSwitchType; @@ -219,13 +232,17 @@ import com.cloud.network.rules.FirewallRule; import com.cloud.resource.ServerResource; import com.cloud.serializer.GsonHelper; import com.cloud.storage.Storage; +import com.cloud.storage.StoragePool; import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.Volume; +import com.cloud.storage.VolumeManager; +import com.cloud.storage.VolumeManagerImpl; import com.cloud.storage.resource.StoragePoolResource; import com.cloud.storage.template.TemplateInfo; import com.cloud.utils.DateUtil; import com.cloud.utils.Pair; import com.cloud.utils.StringUtils; +import com.cloud.utils.component.ComponentContext; import com.cloud.utils.db.DB; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.ExceptionUtil; @@ -246,10 +263,12 @@ import com.vmware.vim25.DatastoreSummary; import com.vmware.vim25.DynamicProperty; import com.vmware.vim25.GuestInfo; import com.vmware.vim25.HostCapability; +import com.vmware.vim25.HostDatastoreBrowserSearchResults; import com.vmware.vim25.HostFirewallInfo; import com.vmware.vim25.HostFirewallRuleset; import com.vmware.vim25.HostNetworkTrafficShapingPolicy; import com.vmware.vim25.HostPortGroupSpec; +import com.vmware.vim25.ManagedObjectNotFound; import com.vmware.vim25.ManagedObjectReference; import com.vmware.vim25.ObjectContent; import com.vmware.vim25.OptionValue; @@ -265,9 +284,13 @@ import com.vmware.vim25.RuntimeFaultFaultMsg; import com.vmware.vim25.ToolsUnavailableFaultMsg; import com.vmware.vim25.VimPortType; import com.vmware.vim25.VirtualDevice; +import com.vmware.vim25.VirtualDeviceBackingInfo; import com.vmware.vim25.VirtualDeviceConfigSpec; import com.vmware.vim25.VirtualDeviceConfigSpecOperation; import com.vmware.vim25.VirtualDisk; +import com.vmware.vim25.VirtualDiskFlatVer2BackingInfo; +import com.vmware.vim25.VirtualDiskMode; +import com.vmware.vim25.VirtualDiskType; import com.vmware.vim25.VirtualEthernetCard; import com.vmware.vim25.VirtualEthernetCardNetworkBackingInfo; import com.vmware.vim25.VirtualLsiLogicController; @@ -275,9 +298,38 @@ import com.vmware.vim25.VirtualMachineConfigSpec; import com.vmware.vim25.VirtualMachineFileInfo; import com.vmware.vim25.VirtualMachineGuestOsIdentifier; import com.vmware.vim25.VirtualMachinePowerState; +import com.vmware.vim25.VirtualMachineRelocateSpec; +import com.vmware.vim25.VirtualMachineRelocateSpecDiskLocator; import com.vmware.vim25.VirtualMachineRuntimeInfo; import com.vmware.vim25.VirtualSCSISharing; +import org.apache.log4j.Logger; +import org.apache.log4j.NDC; + +import javax.naming.ConfigurationException; +import java.io.File; +import java.io.IOException; +import java.net.ConnectException; +import java.net.InetSocketAddress; +import java.net.URI; +import java.nio.channels.SocketChannel; +import java.rmi.RemoteException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.Random; +import java.util.TimeZone; +import java.util.UUID; public class VmwareResource implements StoragePoolResource, ServerResource, VmwareHostService { private static final Logger s_logger = Logger.getLogger(VmwareResource.class); @@ -288,6 +340,9 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa protected final int _shutdown_waitMs = 300000; // wait up to 5 minutes for shutdown + @Inject + protected VolumeManager volMgr; + // out an operation protected final int _retry = 24; protected final int _sleep = 10000; @@ -398,6 +453,10 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa answer = execute((PrepareForMigrationCommand) cmd); } else if (clz == MigrateCommand.class) { answer = execute((MigrateCommand) cmd); + } else if (clz == MigrateWithStorageCommand.class) { + answer = execute((MigrateWithStorageCommand) cmd); + } else if (clz == MigrateVolumeCommand.class) { + answer = execute((MigrateVolumeCommand) cmd); } else if (clz == DestroyCommand.class) { answer = execute((DestroyCommand) cmd); } else if (clz == CreateStoragePoolCommand.class) { @@ -2506,7 +2565,9 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa Pair volumeDsDetails = dataStoresDetails.get(vol.getPoolUuid()); assert (volumeDsDetails != null); VirtualDevice device; - datastoreDiskPath = String.format("[%s] %s.vmdk", volumeDsDetails.second().getName(), vol.getPath()); + + datastoreDiskPath = volumeDsDetails.second().searchFileInSubFolders(vol.getPath() + ".vmdk", true); + String chainInfo = vol.getChainInfo(); if (chainInfo != null && !chainInfo.isEmpty()) { @@ -3371,6 +3432,254 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa } } + protected Answer execute(MigrateWithStorageCommand cmd) { + + if (s_logger.isInfoEnabled()) { + s_logger.info("Executing resource MigrateWithStorageCommand: " + _gson.toJson(cmd)); + } + + VirtualMachineTO vmTo = cmd.getVirtualMachine(); + final String vmName = vmTo.getName(); + + State state = null; + synchronized (_vms) { + state = _vms.get(vmName); + _vms.put(vmName, State.Stopping); + } + + VmwareHypervisorHost srcHyperHost = null; + VmwareHypervisorHost tgtHyperHost = null; + VirtualMachineMO vmMo = null; + + ManagedObjectReference morDsAtTarget = null; + ManagedObjectReference morDsAtSource = null; + ManagedObjectReference morDc = null; + ManagedObjectReference morDcOfTargetHost = null; + ManagedObjectReference morTgtHost = new ManagedObjectReference(); + VirtualMachineRelocateSpec relocateSpec = new VirtualMachineRelocateSpec(); + List diskLocators = new ArrayList(); + VirtualMachineRelocateSpecDiskLocator diskLocator = null; + + boolean isFirstDs = true; + String srcDiskName = ""; + String srcDsName = ""; + String tgtDsName = ""; + String tgtDsNfsHost; + String tgtDsNfsPath; + int tgtDsNfsPort; + VolumeTO volume; + StorageFilerTO filerTo; + Set mountedDatastoresAtSource = new HashSet(); + + Map volToFiler = cmd.getVolumeToFiler(); + String tgtHost = cmd.getTargetHost(); + String tgtHostMorInfo = tgtHost.split("@")[0]; + morTgtHost.setType(tgtHostMorInfo.split(":")[0]); + morTgtHost.setValue(tgtHostMorInfo.split(":")[1]); + + try { + srcHyperHost = getHyperHost(getServiceContext()); + tgtHyperHost = new HostMO(getServiceContext(), morTgtHost); + morDc = srcHyperHost.getHyperHostDatacenter(); + morDcOfTargetHost = tgtHyperHost.getHyperHostDatacenter(); + if (morDc != morDcOfTargetHost) { + String msg = "Source host & target host are in different datacentesr"; + throw new CloudRuntimeException(msg); + } + VmwareManager mgr = tgtHyperHost.getContext().getStockObject(VmwareManager.CONTEXT_STOCK_NAME); + + // find VM through datacenter (VM is not at the target host yet) + vmMo = srcHyperHost.findVmOnPeerHyperHost(vmName); + if (vmMo == null) { + String msg = "VM " + vmName + " does not exist in VMware datacenter " + morDc.getValue(); + s_logger.error(msg); + throw new Exception(msg); + } + + // Get details of each target datastore & attach to source host. + for (Entry entry : volToFiler.entrySet()) { + volume = entry.getKey(); + filerTo = entry.getValue(); + + srcDsName = volume.getPoolUuid().replace("-", ""); + tgtDsName = filerTo.getUuid().replace("-", ""); + tgtDsNfsHost = filerTo.getHost(); + tgtDsNfsPath = filerTo.getPath(); + tgtDsNfsPort = filerTo.getPort(); + + s_logger.debug("Preparing spec for volume : " + volume.getName()); + morDsAtTarget = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(tgtHyperHost, filerTo.getUuid()); + if (morDsAtTarget == null) { + String msg = "Unable to find the mounted datastore with uuid " + morDsAtTarget + " to execute MigrateWithStorageCommand"; + s_logger.error(msg); + throw new Exception(msg); + } + morDsAtSource = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(srcHyperHost, filerTo.getUuid()); + if (morDsAtSource == null) { + morDsAtSource = srcHyperHost.mountDatastore(false, tgtDsNfsHost, tgtDsNfsPort, tgtDsNfsPath, tgtDsName); + if (morDsAtSource == null) { + throw new Exception("Unable to mount datastore " + tgtDsNfsHost + ":/" + tgtDsNfsPath + " on " + _hostName); + } + mountedDatastoresAtSource.add(tgtDsName); + s_logger.debug("Mounted datastore " + tgtDsNfsHost + ":/" + tgtDsNfsPath + " on " + _hostName); + } + + if (isFirstDs) { + relocateSpec.setDatastore(morDsAtSource); + isFirstDs = false; + } + srcDiskName = String.format("[%s] %s.vmdk", srcDsName, volume.getPath()); + diskLocator = new VirtualMachineRelocateSpecDiskLocator(); + diskLocator.setDatastore(morDsAtSource); + diskLocator.setDiskId(getVirtualDiskInfo(vmMo, srcDiskName)); + + diskLocators.add(diskLocator); + + } + relocateSpec.getDisk().addAll(diskLocators); + + // Prepare network at target before migration + NicTO[] nics = vmTo.getNics(); + for (NicTO nic : nics) { + // prepare network on the host + prepareNetworkFromNicInfo(new HostMO(getServiceContext(), morTgtHost), nic, false, vmTo.getType()); + } + + // Ensure secondary storage mounted on target host + String secStoreUrl = mgr.getSecondaryStorageStoreUrl(Long.parseLong(_dcId)); + if(secStoreUrl == null) { + String msg = "secondary storage for dc " + _dcId + " is not ready yet?"; + throw new Exception(msg); + } + mgr.prepareSecondaryStorageStore(secStoreUrl); + ManagedObjectReference morSecDs = prepareSecondaryDatastoreOnHost(secStoreUrl); + if (morSecDs == null) { + String msg = "Failed to prepare secondary storage on host, secondary store url: " + secStoreUrl; + throw new Exception(msg); + } + + // Change datastore + if (!vmMo.changeDatastore(relocateSpec)) { + throw new Exception("Change datastore operation failed during storage migration"); + } else { + s_logger.debug("Successfully migrated storage of VM " + vmName + " to target datastore(s)"); + } + + // Change host + ManagedObjectReference morPool = tgtHyperHost.getHyperHostOwnerResourcePool(); + if (!vmMo.migrate(morPool, tgtHyperHost.getMor())) { + throw new Exception("Change datastore operation failed during storage migration"); + } else { + s_logger.debug("Successfully relocated VM " + vmName + " from " + _hostName + " to " + tgtHyperHost.getHyperHostName()); + } + + state = State.Stopping; + List volumeToList = null; + return new MigrateWithStorageAnswer(cmd, volumeToList); + } catch (Throwable e) { + if (e instanceof RemoteException) { + s_logger.warn("Encountered remote exception at vCenter, invalidating VMware session context"); + invalidateServiceContext(); + } + + String msg = "MigrationCommand failed due to " + VmwareHelper.getExceptionMessage(e); + s_logger.warn(msg, e); + return new MigrateWithStorageAnswer(cmd, (Exception) e); + } finally { + // Cleanup datastores mounted on source host + for(String mountedDatastore : mountedDatastoresAtSource) { + s_logger.debug("Attempting to unmount datastore " + mountedDatastore + " at " + _hostName); + try { + srcHyperHost.unmountDatastore(mountedDatastore); + } catch (Exception unmountEx) { + s_logger.debug("Failed to unmount datastore " + mountedDatastore + " at " + _hostName + + ". Seems the datastore is still being used by " + _hostName + + ". Please unmount manually to cleanup."); + } + s_logger.debug("Successfully unmounted datastore " + mountedDatastore + " at " + _hostName); + } + synchronized (_vms) { + _vms.put(vmName, state); + } + } + } + + private Answer execute(MigrateVolumeCommand cmd) { + String volumePath = cmd.getVolumePath(); + StorageFilerTO poolTo = cmd.getPool(); + + if (s_logger.isInfoEnabled()) { + s_logger.info("Executing resource MigrateVolumeCommand: " + _gson.toJson(cmd)); + } + + VmwareContext context = getServiceContext(); + VmwareManager mgr = context.getStockObject(VmwareManager.CONTEXT_STOCK_NAME); + final String vmName = volMgr.getVmNameFromVolumeId(cmd.getVolumeId()); + + VirtualMachineMO vmMo = null; + VmwareHypervisorHost srcHyperHost = null; + + ManagedObjectReference morDs = null; + ManagedObjectReference morDc = null; + VirtualMachineRelocateSpec relocateSpec = new VirtualMachineRelocateSpec(); + List diskLocators = new ArrayList(); + VirtualMachineRelocateSpecDiskLocator diskLocator = null; + + String srcDiskName = ""; + String srcDsName = ""; + String tgtDsName = ""; + + try { + srcHyperHost = getHyperHost(getServiceContext()); + morDc = srcHyperHost.getHyperHostDatacenter(); + srcDsName = volMgr.getStoragePoolOfVolume(cmd.getVolumeId()); + tgtDsName = poolTo.getUuid().replace("-", ""); + + // find VM through datacenter (VM is not at the target host yet) + vmMo = srcHyperHost.findVmOnPeerHyperHost(vmName); + if (vmMo == null) { + String msg = "VM " + vmName + " does not exist in VMware datacenter " + morDc.getValue(); + s_logger.error(msg); + throw new Exception(msg); + } + morDs = HypervisorHostHelper.findDatastoreWithBackwardsCompatibility(srcHyperHost, tgtDsName); + if (morDs == null) { + String msg = "Unable to find the mounted datastore with name " + tgtDsName + " to execute MigrateVolumeCommand"; + s_logger.error(msg); + throw new Exception(msg); + } + + srcDiskName = String.format("[%s] %s.vmdk", srcDsName, volumePath); + diskLocator = new VirtualMachineRelocateSpecDiskLocator(); + diskLocator.setDatastore(morDs); + diskLocator.setDiskId(getVirtualDiskInfo(vmMo, srcDiskName)); + + diskLocators.add(diskLocator); + relocateSpec.getDisk().add(diskLocator); + + // Change datastore + if (!vmMo.changeDatastore(relocateSpec)) { + throw new Exception("Change datastore operation failed during volume migration"); + } else { + s_logger.debug("Successfully migrated volume " + volumePath + " to target datastore " + tgtDsName); + } + + return new MigrateVolumeAnswer(cmd, true, null, volumePath); + } catch (Exception e) { + String msg = "Catch Exception " + e.getClass().getName() + " due to " + e.toString(); + s_logger.error(msg, e); + return new MigrateVolumeAnswer(cmd, false, msg, null); + } + } + + private int getVirtualDiskInfo(VirtualMachineMO vmMo, String srcDiskName) throws Exception { + Pair deviceInfo = vmMo.getDiskDevice(srcDiskName, false); + if(deviceInfo == null) { + throw new Exception("No such disk device: " + srcDiskName); + } + return deviceInfo.first().getKey(); + } + private VmwareHypervisorHost getTargetHyperHost(DatacenterMO dcMo, String destIp) throws Exception { VmwareManager mgr = dcMo.getContext().getStockObject(VmwareManager.CONTEXT_STOCK_NAME); @@ -3483,7 +3792,8 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa } DatastoreMO dsMo = new DatastoreMO(getServiceContext(), morDs); - String datastoreVolumePath = String.format("[%s] %s.vmdk", dsMo.getName(), cmd.getVolumePath()); + String datastoreVolumePath = dsMo.searchFileInSubFolders(cmd.getVolumePath() + ".vmdk", true); + assert (datastoreVolumePath != null) : "Virtual disk file must exist in specified datastore for attach/detach operations."; AttachVolumeAnswer answer = new AttachVolumeAnswer(cmd, cmd.getDeviceId()); if (cmd.getAttach()) { @@ -5286,6 +5596,7 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa _guestTrafficInfo = (VmwareTrafficLabel) params.get("guestTrafficInfo"); _publicTrafficInfo = (VmwareTrafficLabel) params.get("publicTrafficInfo"); VmwareContext context = getServiceContext(); + volMgr = ComponentContext.inject(VolumeManagerImpl.class); try { VmwareManager mgr = context.getStockObject(VmwareManager.CONTEXT_STOCK_NAME); mgr.setupResourceStartupParams(params); diff --git a/plugins/hypervisors/vmware/src/org/apache/cloudstack/storage/motion/VmwareStorageMotionStrategy.java b/plugins/hypervisors/vmware/src/org/apache/cloudstack/storage/motion/VmwareStorageMotionStrategy.java new file mode 100644 index 00000000000..c32d3d5abb7 --- /dev/null +++ b/plugins/hypervisors/vmware/src/org/apache/cloudstack/storage/motion/VmwareStorageMotionStrategy.java @@ -0,0 +1,212 @@ +/* + * 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 org.apache.cloudstack.storage.motion; + +import java.util.HashMap; +import java.util.Map; +import java.util.List; + +import javax.inject.Inject; + +import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; +import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.framework.async.AsyncCompletionCallback; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.MigrateWithStorageAnswer; +import com.cloud.agent.api.MigrateWithStorageCommand; +import com.cloud.agent.api.MigrateWithStorageCompleteAnswer; +import com.cloud.agent.api.MigrateWithStorageCompleteCommand; +import com.cloud.agent.api.MigrateWithStorageReceiveAnswer; +import com.cloud.agent.api.MigrateWithStorageReceiveCommand; +import com.cloud.agent.api.MigrateWithStorageSendAnswer; +import com.cloud.agent.api.MigrateWithStorageSendCommand; +import com.cloud.agent.api.to.StorageFilerTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.agent.api.to.VolumeTO; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.host.Host; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.StoragePool; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.dao.VMInstanceDao; + +@Component +public class VmwareStorageMotionStrategy implements DataMotionStrategy { + private static final Logger s_logger = Logger.getLogger(VmwareStorageMotionStrategy.class); + @Inject AgentManager agentMgr; + @Inject VolumeDao volDao; + @Inject VolumeDataFactory volFactory; + @Inject PrimaryDataStoreDao storagePoolDao; + @Inject VMInstanceDao instanceDao; + + @Override + public boolean canHandle(DataObject srcData, DataObject destData) { + return false; + } + + @Override + public boolean canHandle(Map volumeMap, Host srcHost, Host destHost) { + if (srcHost.getHypervisorType() == HypervisorType.VMware && destHost.getHypervisorType() == HypervisorType.VMware) { + s_logger.debug(this.getClass() + " can handle the request because the hosts have VMware hypervisor"); + return true; + } + return false; + } + + @Override + public Void copyAsync(DataObject srcData, DataObject destData, + AsyncCompletionCallback callback) { + CopyCommandResult result = new CopyCommandResult(null, null); + result.setResult("Unsupported operation requested for copying data."); + callback.complete(result); + + return null; + } + + @Override + public Void copyAsync(Map volumeMap, VirtualMachineTO vmTo, Host srcHost, Host destHost, + AsyncCompletionCallback callback) { + Answer answer = null; + String errMsg = null; + try { + VMInstanceVO instance = instanceDao.findById(vmTo.getId()); + if (instance != null) { + if (srcHost.getClusterId() == destHost.getClusterId()) { + answer = migrateVmWithVolumesWithinCluster(instance, vmTo, srcHost, destHost, volumeMap); + } else { + answer = migrateVmWithVolumesAcrossCluster(instance, vmTo, srcHost, destHost, volumeMap); + } + } else { + throw new CloudRuntimeException("Unsupported operation requested for moving data."); + } + } catch (Exception e) { + s_logger.error("copy failed", e); + errMsg = e.toString(); + } + + CopyCommandResult result = new CopyCommandResult(null, answer); + result.setResult(errMsg); + callback.complete(result); + return null; + } + + private Answer migrateVmWithVolumesAcrossCluster(VMInstanceVO vm, VirtualMachineTO to, Host srcHost, + Host destHost, Map volumeToPool) throws AgentUnavailableException { + + // Initiate migration of a virtual machine with it's volumes. + try { + Map volumeToFilerto = new HashMap(); + for (Map.Entry entry : volumeToPool.entrySet()) { + VolumeInfo volume = entry.getKey(); + VolumeTO volumeTo = new VolumeTO(volume, storagePoolDao.findById(volume.getPoolId())); + StorageFilerTO filerTo = new StorageFilerTO((StoragePool)entry.getValue()); + volumeToFilerto.put(volumeTo, filerTo); + } + + // Migration across cluster needs to be done in three phases. + // 1. Send a migrate command to source resource to initiate migration + // Run validations against target!! + // 2. Complete the process. Update the volume details. + MigrateWithStorageCommand migrateWithStorageCmd = new MigrateWithStorageCommand(to, volumeToFilerto, destHost.getGuid()); + MigrateWithStorageAnswer migrateWithStorageAnswer = (MigrateWithStorageAnswer) agentMgr.send( + srcHost.getId(), migrateWithStorageCmd); + if (migrateWithStorageAnswer == null) { + s_logger.error("Migration with storage of vm " + vm+ " to host " + destHost + " failed."); + throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost); + } else if (!migrateWithStorageAnswer.getResult()) { + s_logger.error("Migration with storage of vm " + vm+ " failed. Details: " + migrateWithStorageAnswer.getDetails()); + throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost + + ". " + migrateWithStorageAnswer.getDetails()); + } else { + // Update the volume details after migration. + updateVolumesAfterMigration(volumeToPool); + } + s_logger.debug("Storage migration of VM " + vm.getInstanceName() + " completed successfully. Migrated to host " + destHost.getName()); + + return migrateWithStorageAnswer; + } catch (OperationTimedoutException e) { + s_logger.error("Error while migrating vm " + vm + " to host " + destHost, e); + throw new AgentUnavailableException("Operation timed out on storage motion for " + vm, destHost.getId()); + } + } + + private Answer migrateVmWithVolumesWithinCluster(VMInstanceVO vm, VirtualMachineTO to, Host srcHost, + Host destHost, Map volumeToPool) throws AgentUnavailableException { + + // Initiate migration of a virtual machine with it's volumes. + try { + Map volumeToFilerto = new HashMap(); + for (Map.Entry entry : volumeToPool.entrySet()) { + VolumeInfo volume = entry.getKey(); + VolumeTO volumeTo = new VolumeTO(volume, storagePoolDao.findById(volume.getPoolId())); + StorageFilerTO filerTo = new StorageFilerTO((StoragePool)entry.getValue()); + volumeToFilerto.put(volumeTo, filerTo); + } + + MigrateWithStorageCommand command = new MigrateWithStorageCommand(to, volumeToFilerto, destHost.getGuid()); + MigrateWithStorageAnswer answer = (MigrateWithStorageAnswer) agentMgr.send(srcHost.getId(), command); + if (answer == null) { + s_logger.error("Migration with storage of vm " + vm + " failed."); + throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost); + } else if (!answer.getResult()) { + s_logger.error("Migration with storage of vm " + vm+ " failed. Details: " + answer.getDetails()); + throw new CloudRuntimeException("Error while migrating the vm " + vm + " to host " + destHost + + ". " + answer.getDetails()); + } else { + // Update the volume details after migration. + updateVolumesAfterMigration(volumeToPool); + } + + return answer; + } catch (OperationTimedoutException e) { + s_logger.error("Error while migrating vm " + vm + " to host " + destHost, e); + throw new AgentUnavailableException("Operation timed out on storage motion for " + vm, destHost.getId()); + } + } + + private void updateVolumesAfterMigration(Map volumeToPool) { + for (Map.Entry entry : volumeToPool.entrySet()) { + VolumeInfo volume = entry.getKey(); + StoragePool pool = (StoragePool)entry.getValue(); + + VolumeVO volumeVO = volDao.findById(volume.getId()); + Long oldPoolId = volumeVO.getPoolId(); + volumeVO.setLastPoolId(oldPoolId); + volumeVO.setFolder(pool.getPath()); + volumeVO.setPodId(pool.getPodId()); + volumeVO.setPoolId(pool.getId()); + + volDao.update(volume.getId(), volumeVO); + s_logger.debug("Volume path was successfully updated for volume " + volume.getName() + " after it was migrated."); + } + } +} diff --git a/plugins/hypervisors/vmware/test/org/apache/cloudstack/storage/motion/VmwareStorageMotionStrategyTest.java b/plugins/hypervisors/vmware/test/org/apache/cloudstack/storage/motion/VmwareStorageMotionStrategyTest.java new file mode 100644 index 00000000000..ae4f41d55a5 --- /dev/null +++ b/plugins/hypervisors/vmware/test/org/apache/cloudstack/storage/motion/VmwareStorageMotionStrategyTest.java @@ -0,0 +1,271 @@ +// 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 org.apache.cloudstack.storage.motion; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.engine.subsystem.api.storage.CommandResult; +import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.framework.async.AsyncCallFuture; +import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher; +import org.apache.cloudstack.framework.async.AsyncCompletionCallback; +import org.apache.cloudstack.framework.async.AsyncRpcConext; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.test.utils.SpringUtils; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.MigrateWithStorageAnswer; +import com.cloud.agent.api.MigrateWithStorageCommand; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.host.Host; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.utils.component.ComponentContext; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.dao.VMInstanceDao; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader = AnnotationConfigContextLoader.class) +public class VmwareStorageMotionStrategyTest { + + @Inject VmwareStorageMotionStrategy strategy = new VmwareStorageMotionStrategy(); + @Inject AgentManager agentMgr; + @Inject VolumeDao volDao; + @Inject VolumeDataFactory volFactory; + @Inject PrimaryDataStoreDao storagePoolDao; + @Inject VMInstanceDao instanceDao; + + CopyCommandResult result; + + @BeforeClass + public static void setUp() throws ConfigurationException { + } + + @Before + public void testSetUp() { + ComponentContext.initComponentsLifeCycle(); + } + + @Test + public void testStrategyHandlesVmwareHosts() throws Exception { + Host srcHost = mock(Host.class); + Host destHost = mock(Host.class); + when(srcHost.getHypervisorType()).thenReturn(HypervisorType.VMware); + when(destHost.getHypervisorType()).thenReturn(HypervisorType.VMware); + Map volumeMap = new HashMap(); + boolean canHandle = strategy.canHandle(volumeMap, srcHost, destHost); + assertTrue("The strategy is only supposed to handle vmware hosts", canHandle); + } + + @Test + public void testStrategyDoesnotHandlesNonVmwareHosts() throws Exception { + Host srcHost = mock(Host.class); + Host destHost = mock(Host.class); + when(srcHost.getHypervisorType()).thenReturn(HypervisorType.XenServer); + when(destHost.getHypervisorType()).thenReturn(HypervisorType.XenServer); + Map volumeMap = new HashMap(); + boolean canHandle = strategy.canHandle(volumeMap, srcHost, destHost); + assertFalse("The strategy is only supposed to handle vmware hosts", canHandle); + } + + @Test + public void testMigrateWithinClusterSuccess() throws Exception { + Host srcHost = mock(Host.class); + Host destHost = mock(Host.class); + when(srcHost.getClusterId()).thenReturn(1L); + when(destHost.getClusterId()).thenReturn(1L); + Map volumeMap = new HashMap(); + VirtualMachineTO to = mock(VirtualMachineTO.class); + when(to.getId()).thenReturn(6L); + VMInstanceVO instance = mock(VMInstanceVO.class); + when(instanceDao.findById(6L)).thenReturn(instance); + + MockContext context = new MockContext(null, null, volumeMap); + AsyncCallbackDispatcher caller = AsyncCallbackDispatcher.create(this); + caller.setCallback(caller.getTarget().mockCallBack(null, null)).setContext(context); + + MigrateWithStorageAnswer migAnswerMock = mock(MigrateWithStorageAnswer.class); + when(migAnswerMock.getResult()).thenReturn(true); + when(agentMgr.send(anyLong(), isA(MigrateWithStorageCommand.class))).thenReturn(migAnswerMock); + + strategy.copyAsync(volumeMap, to, srcHost, destHost, caller); + assertTrue("Migration within cluster isn't successful.", this.result.isSuccess()); + } + + @Test + public void testMigrateWithinClusterFailure() throws Exception { + Host srcHost = mock(Host.class); + Host destHost = mock(Host.class); + when(srcHost.getClusterId()).thenReturn(1L); + when(destHost.getClusterId()).thenReturn(1L); + Map volumeMap = new HashMap(); + VirtualMachineTO to = mock(VirtualMachineTO.class); + when(to.getId()).thenReturn(6L); + VMInstanceVO instance = mock(VMInstanceVO.class); + when(instanceDao.findById(6L)).thenReturn(instance); + + MockContext context = new MockContext(null, null, volumeMap); + AsyncCallbackDispatcher caller = AsyncCallbackDispatcher.create(this); + caller.setCallback(caller.getTarget().mockCallBack(null, null)).setContext(context); + + MigrateWithStorageAnswer migAnswerMock = mock(MigrateWithStorageAnswer.class); + when(migAnswerMock.getResult()).thenReturn(false); + when(agentMgr.send(anyLong(), isA(MigrateWithStorageCommand.class))).thenReturn(migAnswerMock); + + strategy.copyAsync(volumeMap, to, srcHost, destHost, caller); + assertFalse("Migration within cluster didn't fail.", this.result.isSuccess()); + } + + @Test + public void testMigrateAcrossClusterSuccess() throws Exception { + Host srcHost = mock(Host.class); + Host destHost = mock(Host.class); + when(srcHost.getClusterId()).thenReturn(1L); + when(destHost.getClusterId()).thenReturn(2L); + Map volumeMap = new HashMap(); + VirtualMachineTO to = mock(VirtualMachineTO.class); + when(to.getId()).thenReturn(6L); + VMInstanceVO instance = mock(VMInstanceVO.class); + when(instanceDao.findById(6L)).thenReturn(instance); + + MockContext context = new MockContext(null, null, volumeMap); + AsyncCallbackDispatcher caller = AsyncCallbackDispatcher.create(this); + caller.setCallback(caller.getTarget().mockCallBack(null, null)).setContext(context); + + MigrateWithStorageAnswer migAnswerMock = mock(MigrateWithStorageAnswer.class); + when(migAnswerMock.getResult()).thenReturn(true); + when(agentMgr.send(anyLong(), isA(MigrateWithStorageCommand.class))).thenReturn(migAnswerMock); + + strategy.copyAsync(volumeMap, to, srcHost, destHost, caller); + assertTrue("Migration across cluster isn't successful.", this.result.isSuccess()); + } + + @Test + public void testMigrateAcrossClusterFailure() throws Exception { + Host srcHost = mock(Host.class); + Host destHost = mock(Host.class); + when(srcHost.getClusterId()).thenReturn(1L); + when(destHost.getClusterId()).thenReturn(2L); + Map volumeMap = new HashMap(); + VirtualMachineTO to = mock(VirtualMachineTO.class); + when(to.getId()).thenReturn(6L); + VMInstanceVO instance = mock(VMInstanceVO.class); + when(instanceDao.findById(6L)).thenReturn(instance); + + MockContext context = new MockContext(null, null, volumeMap); + AsyncCallbackDispatcher caller = AsyncCallbackDispatcher.create(this); + caller.setCallback(caller.getTarget().mockCallBack(null, null)).setContext(context); + + MigrateWithStorageAnswer migAnswerMock = mock(MigrateWithStorageAnswer.class); + when(migAnswerMock.getResult()).thenReturn(false); + when(agentMgr.send(anyLong(), isA(MigrateWithStorageCommand.class))).thenReturn(migAnswerMock); + + strategy.copyAsync(volumeMap, to, srcHost, destHost, caller); + assertFalse("Migration across cluster didn't fail.", this.result.isSuccess()); + } + + private class MockContext extends AsyncRpcConext { + final Map volumeToPool; + final AsyncCallFuture future; + /** + * @param callback + */ + public MockContext(AsyncCompletionCallback callback, AsyncCallFuture future, + Map volumeToPool) { + super(callback); + this.volumeToPool = volumeToPool; + this.future = future; + } + } + + protected Void mockCallBack(AsyncCallbackDispatcher callback, MockContext context) { + this.result = callback.getResult(); + return null; + } + + @Configuration + @ComponentScan(basePackageClasses = { VmwareStorageMotionStrategy.class }, + includeFilters = {@Filter(value = TestConfiguration.Library.class, type = FilterType.CUSTOM)}, + useDefaultFilters = false) + public static class TestConfiguration extends SpringUtils.CloudStackTestConfiguration { + + @Bean + public VolumeDao volumeDao() { + return Mockito.mock(VolumeDao.class); + } + + @Bean + public VolumeDataFactory volumeDataFactory() { + return Mockito.mock(VolumeDataFactory.class); + } + + @Bean + public PrimaryDataStoreDao primaryDataStoreDao() { + return Mockito.mock(PrimaryDataStoreDao.class); + } + + @Bean + public VMInstanceDao vmInstanceDao() { + return Mockito.mock(VMInstanceDao.class); + } + + @Bean + public AgentManager agentManager() { + return Mockito.mock(AgentManager.class); + } + + public static class Library implements TypeFilter { + @Override + public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException { + ComponentScan cs = TestConfiguration.class.getAnnotation(ComponentScan.class); + return SpringUtils.includedInBasePackageClasses(mdr.getClassMetadata().getClassName(), cs); + } + } + } +} \ No newline at end of file diff --git a/server/src/com/cloud/storage/VolumeManager.java b/server/src/com/cloud/storage/VolumeManager.java index d198e5dd7df..47fbda8df9f 100644 --- a/server/src/com/cloud/storage/VolumeManager.java +++ b/server/src/com/cloud/storage/VolumeManager.java @@ -105,4 +105,8 @@ public interface VolumeManager extends VolumeApiService { DiskProfile allocateTemplatedVolume(Type type, String name, DiskOfferingVO offering, VMTemplateVO template, VMInstanceVO vm, Account owner); + + String getVmNameFromVolumeId(long volumeId); + + String getStoragePoolOfVolume(long volumeId); } diff --git a/server/src/com/cloud/storage/VolumeManagerImpl.java b/server/src/com/cloud/storage/VolumeManagerImpl.java index c20625257fe..d064f3becfa 100644 --- a/server/src/com/cloud/storage/VolumeManagerImpl.java +++ b/server/src/com/cloud/storage/VolumeManagerImpl.java @@ -2604,4 +2604,17 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { } } + + @Override + public String getVmNameFromVolumeId(long volumeId) { + Long instanceId; + VolumeVO volume = _volsDao.findById(volumeId); + return getVmNameOnVolume(volume); + } + + @Override + public String getStoragePoolOfVolume(long volumeId) { + VolumeVO vol = _volsDao.findById(volumeId); + return dataStoreMgr.getPrimaryDataStore(vol.getPoolId()).getUuid(); + } } 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 c79605d08ab..75553aeaf43 100755 --- a/vmware-base/src/com/cloud/hypervisor/vmware/mo/DatastoreMO.java +++ b/vmware-base/src/com/cloud/hypervisor/vmware/mo/DatastoreMO.java @@ -133,8 +133,14 @@ public class DatastoreMO extends BaseMO { fullPath = String.format("[%s] %s", datastoreName, path); try { - if(testExistence && !fileExists(fullPath)) - return true; + if(testExistence && !fileExists(fullPath)) { + String searchResult = searchFileInSubFolders(fullPath.split(" ")[1], true); + if (searchResult == null) { + return true; + } else { + fullPath = searchResult; + } + } } catch(Exception e) { s_logger.info("Unable to test file existence due to exception " + e.getClass().getName() + ", skip deleting of it"); return true; @@ -315,4 +321,38 @@ public class DatastoreMO extends BaseMO { s_logger.info("Folder " + folderName + " does not exist on datastore"); return false; } + + public String searchFileInSubFolders(String fileName, boolean caseInsensitive) throws Exception { + String datastorePath = "[" + getName() + "]"; + String rootDirectoryFilePath = String.format("%s %s", datastorePath, fileName); + if(fileExists(rootDirectoryFilePath)) { + return rootDirectoryFilePath; + } + + String parentFolderPath = null; + String absoluteFileName = null; + s_logger.info("Searching file " + fileName + " in " + datastorePath); + + HostDatastoreBrowserMO browserMo = getHostDatastoreBrowserMO(); + ArrayList results = browserMo.searchDatastoreSubFolders("[" + getName() + "]", fileName, caseInsensitive); + if (results.size() > 1) { + s_logger.warn("Multiple files with name " + fileName + " exists in datastore " + datastorePath + ". Trying to choose first file found in search attempt."); + } + for (HostDatastoreBrowserSearchResults result : results) { + if (result != null) { + List info = result.getFile(); + if (info != null && info.size() > 0) { + for (FileInfo fi : info) { + absoluteFileName = parentFolderPath = result.getFolderPath(); + s_logger.info("Found file " + fileName + " in datastore at " + absoluteFileName); + if(parentFolderPath.endsWith("]")) + absoluteFileName += " "; + absoluteFileName += fi.getPath(); + break; + } + } + } + } + return absoluteFileName; + } } diff --git a/vmware-base/src/com/cloud/hypervisor/vmware/mo/HostDatastoreBrowserMO.java b/vmware-base/src/com/cloud/hypervisor/vmware/mo/HostDatastoreBrowserMO.java index 59e754c951d..209aeed4edd 100644 --- a/vmware-base/src/com/cloud/hypervisor/vmware/mo/HostDatastoreBrowserMO.java +++ b/vmware-base/src/com/cloud/hypervisor/vmware/mo/HostDatastoreBrowserMO.java @@ -16,6 +16,8 @@ // under the License. package com.cloud.hypervisor.vmware.mo; +import java.util.ArrayList; + import org.apache.log4j.Logger; import com.cloud.hypervisor.vmware.util.VmwareContext; @@ -76,7 +78,8 @@ public class HostDatastoreBrowserMO extends BaseMO { return searchDatastore(datastorePath, spec); } - public HostDatastoreBrowserSearchResults searchDatastoreSubFolders(String datastorePath, HostDatastoreBrowserSearchSpec searchSpec) throws Exception { + @SuppressWarnings("unchecked") + public ArrayList searchDatastoreSubFolders(String datastorePath, HostDatastoreBrowserSearchSpec searchSpec) throws Exception { if(s_logger.isTraceEnabled()) s_logger.trace("vCenter API trace - searchDatastoreSubFolders(). target mor: " + _mor.getValue() + ", file datastore path: " + datastorePath); @@ -87,7 +90,7 @@ public class HostDatastoreBrowserMO extends BaseMO { if(result) { _context.waitForTaskProgressDone(morTask); - return (HostDatastoreBrowserSearchResults)_context.getVimClient().getDynamicProperty(morTask, "info.result"); + return (ArrayList) _context.getVimClient().getDynamicProperty(morTask, "info.result"); } else { s_logger.error("VMware searchDaastoreSubFolders_Task failed due to " + TaskMO.getTaskFailureInfo(_context, morTask)); } @@ -99,12 +102,11 @@ public class HostDatastoreBrowserMO extends BaseMO { return null; } - public HostDatastoreBrowserSearchResults searchDatastoreSubFolders(String datastorePath, String folderName, boolean caseInsensitive) throws Exception { - HostDatastoreBrowserSearchSpec spec = new HostDatastoreBrowserSearchSpec(); - spec.setSearchCaseInsensitive(caseInsensitive); - spec.getMatchPattern().add(folderName); + public ArrayList searchDatastoreSubFolders(String datastorePath, String fileName, boolean caseInsensitive) throws Exception { + HostDatastoreBrowserSearchSpec spec = new HostDatastoreBrowserSearchSpec(); + spec.setSearchCaseInsensitive(caseInsensitive); + spec.getMatchPattern().add(fileName); - return searchDatastore(datastorePath, spec); - } + return searchDatastoreSubFolders(datastorePath, spec); + } } - diff --git a/vmware-base/src/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java b/vmware-base/src/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java index 20f84784157..157c7a6cd34 100755 --- a/vmware-base/src/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java +++ b/vmware-base/src/com/cloud/hypervisor/vmware/mo/HypervisorHostHelper.java @@ -70,6 +70,7 @@ import com.vmware.vim25.VirtualDeviceConfigSpecOperation; import com.vmware.vim25.VirtualLsiLogicController; import com.vmware.vim25.VirtualMachineConfigSpec; import com.vmware.vim25.VirtualMachineFileInfo; +import com.vmware.vim25.VirtualMachineRelocateSpec; import com.vmware.vim25.VirtualMachineVideoCard; import com.vmware.vim25.VirtualSCSISharing; import com.vmware.vim25.VmwareDistributedVirtualSwitchPvlanSpec; diff --git a/vmware-base/src/com/cloud/hypervisor/vmware/mo/VirtualDiskManagerMO.java b/vmware-base/src/com/cloud/hypervisor/vmware/mo/VirtualDiskManagerMO.java index e21d06adf62..f181d02ce81 100755 --- a/vmware-base/src/com/cloud/hypervisor/vmware/mo/VirtualDiskManagerMO.java +++ b/vmware-base/src/com/cloud/hypervisor/vmware/mo/VirtualDiskManagerMO.java @@ -26,6 +26,10 @@ import com.vmware.vim25.VirtualDiskSpec; public class VirtualDiskManagerMO extends BaseMO { private static final Logger s_logger = Logger.getLogger(VirtualDiskManagerMO.class); + public VirtualDiskManagerMO(VmwareContext context) { + super(context, context.getServiceContent().getVirtualDiskManager()); + } + public VirtualDiskManagerMO(VmwareContext context, ManagedObjectReference morDiskMgr) { super(context, morDiskMgr); } diff --git a/vmware-base/src/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java b/vmware-base/src/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java index 660d9632baa..cf5ffdeb823 100644 --- a/vmware-base/src/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java +++ b/vmware-base/src/com/cloud/hypervisor/vmware/mo/VirtualMachineMO.java @@ -48,6 +48,7 @@ import com.vmware.vim25.GuestInfo; import com.vmware.vim25.HttpNfcLeaseDeviceUrl; import com.vmware.vim25.HttpNfcLeaseInfo; import com.vmware.vim25.HttpNfcLeaseState; +import com.vmware.vim25.InvalidStateFaultMsg; import com.vmware.vim25.ManagedObjectReference; import com.vmware.vim25.ObjectContent; import com.vmware.vim25.ObjectSpec; @@ -57,6 +58,7 @@ import com.vmware.vim25.OvfCreateDescriptorResult; import com.vmware.vim25.OvfFile; import com.vmware.vim25.PropertyFilterSpec; import com.vmware.vim25.PropertySpec; +import com.vmware.vim25.RuntimeFaultFaultMsg; import com.vmware.vim25.SelectionSpec; import com.vmware.vim25.TraversalSpec; import com.vmware.vim25.VirtualCdrom; @@ -335,6 +337,30 @@ public class VirtualMachineMO extends BaseMO { return false; } + public boolean changeDatastore(VirtualMachineRelocateSpec relocateSpec) throws Exception { + ManagedObjectReference morTask = _context.getVimClient().getService().relocateVMTask(_mor, relocateSpec, VirtualMachineMovePriority.DEFAULT_PRIORITY); + boolean result = _context.getVimClient().waitForTask(morTask); + if(result) { + _context.waitForTaskProgressDone(morTask); + return true; + } else { + s_logger.error("VMware RelocateVM_Task to change datastore failed due to " + TaskMO.getTaskFailureInfo(_context, morTask)); + } + return false; + } + + public boolean changeHost(VirtualMachineRelocateSpec relocateSpec) throws Exception { + ManagedObjectReference morTask = _context.getService().relocateVMTask(_mor, relocateSpec, VirtualMachineMovePriority.DEFAULT_PRIORITY); + boolean result = _context.getVimClient().waitForTask(morTask); + if (result) { + _context.waitForTaskProgressDone(morTask); + return true; + } else { + s_logger.error("VMware RelocateVM_Task to change host failed due to " + TaskMO.getTaskFailureInfo(_context, morTask)); + } + return false; + } + public boolean relocate(ManagedObjectReference morTargetHost) throws Exception { VirtualMachineRelocateSpec relocateSpec = new VirtualMachineRelocateSpec(); relocateSpec.setHost(morTargetHost);