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);