CLOUDSTACK-6143: Storage motion support for hyper-v. With these changes a volume on a shared storage pool (SMB) and attached to a running vm can be live migrated to another shared storage pool. Also a vm and its volumes can be live migrated to another host and storage pool respectively.
Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/2aff39f8 Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/2aff39f8 Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/2aff39f8 Branch: refs/heads/resize-root Commit: 2aff39f8c75d20ea086dbb873030f3e513fc6719 Parents: f293c94 Author: Devdeep Singh <devd...@gmail.com> Authored: Wed Mar 12 02:22:22 2014 +0530 Committer: Devdeep Singh <devd...@gmail.com> Committed: Fri Mar 14 16:27:58 2014 +0530 ---------------------------------------------------------------------- .../agent/api/MigrateWithStorageCommand.java | 16 ++ .../subsystem/api/storage/StorageAction.java | 3 +- ...ngine-storage-datamotion-storage-context.xml | 3 +- .../motion/AncientDataMotionStrategy.java | 2 +- .../endpoint/DefaultEndPointSelector.java | 9 + .../storage/volume/VolumeServiceImpl.java | 10 +- .../HypervResource/CloudStackTypes.cs | 51 ++++-- .../HypervResource/HypervResourceController.cs | 139 +++++++++++++- .../HypervResource/IWmiCallsV2.cs | 2 + .../ServerResource/HypervResource/WmiCallsV2.cs | 139 ++++++++++++++ ...tion.v2.Msvm_StorageAllocationSettingData.cs | 2 +- .../motion/HypervStorageMotionStrategy.java | 179 +++++++++++++++++++ server/src/com/cloud/vm/UserVmManagerImpl.java | 3 +- setup/db/db/schema-430to440.sql | 1 + 14 files changed, 530 insertions(+), 29 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2aff39f8/core/src/com/cloud/agent/api/MigrateWithStorageCommand.java ---------------------------------------------------------------------- diff --git a/core/src/com/cloud/agent/api/MigrateWithStorageCommand.java b/core/src/com/cloud/agent/api/MigrateWithStorageCommand.java index a94697a..43ae854 100644 --- a/core/src/com/cloud/agent/api/MigrateWithStorageCommand.java +++ b/core/src/com/cloud/agent/api/MigrateWithStorageCommand.java @@ -16,26 +16,38 @@ // under the License. package com.cloud.agent.api; +import java.util.List; import java.util.Map; import com.cloud.agent.api.to.StorageFilerTO; import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.agent.api.to.VolumeTO; +import com.cloud.utils.Pair; public class MigrateWithStorageCommand extends Command { VirtualMachineTO vm; Map<VolumeTO, StorageFilerTO> volumeToFiler; + List<Pair<VolumeTO, StorageFilerTO>> volumeToFilerAsList; String tgtHost; public MigrateWithStorageCommand(VirtualMachineTO vm, Map<VolumeTO, StorageFilerTO> volumeToFiler) { this.vm = vm; this.volumeToFiler = volumeToFiler; + this.volumeToFilerAsList = null; this.tgtHost = null; } public MigrateWithStorageCommand(VirtualMachineTO vm, Map<VolumeTO, StorageFilerTO> volumeToFiler, String tgtHost) { this.vm = vm; this.volumeToFiler = volumeToFiler; + this.volumeToFilerAsList = null; + this.tgtHost = tgtHost; + } + + public MigrateWithStorageCommand(VirtualMachineTO vm, List<Pair<VolumeTO, StorageFilerTO>> volumeToFilerAsList, String tgtHost) { + this.vm = vm; + this.volumeToFiler = null; + this.volumeToFilerAsList = volumeToFilerAsList; this.tgtHost = tgtHost; } @@ -47,6 +59,10 @@ public class MigrateWithStorageCommand extends Command { return volumeToFiler; } + public List<Pair<VolumeTO, StorageFilerTO>> getVolumeToFilerAsList() { + return volumeToFilerAsList; + } + public String getTargetHost() { return tgtHost; } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2aff39f8/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/StorageAction.java ---------------------------------------------------------------------- diff --git a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/StorageAction.java b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/StorageAction.java index 4fbb20e..965df84 100644 --- a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/StorageAction.java +++ b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/StorageAction.java @@ -21,5 +21,6 @@ package org.apache.cloudstack.engine.subsystem.api.storage; public enum StorageAction { TAKESNAPSHOT, BACKUPSNAPSHOT, - DELETESNAPSHOT + DELETESNAPSHOT, + MIGRATEVOLUME } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2aff39f8/engine/storage/datamotion/resources/META-INF/cloudstack/storage/spring-engine-storage-datamotion-storage-context.xml ---------------------------------------------------------------------- diff --git a/engine/storage/datamotion/resources/META-INF/cloudstack/storage/spring-engine-storage-datamotion-storage-context.xml b/engine/storage/datamotion/resources/META-INF/cloudstack/storage/spring-engine-storage-datamotion-storage-context.xml index 725f7d3..ade22db 100644 --- a/engine/storage/datamotion/resources/META-INF/cloudstack/storage/spring-engine-storage-datamotion-storage-context.xml +++ b/engine/storage/datamotion/resources/META-INF/cloudstack/storage/spring-engine-storage-datamotion-storage-context.xml @@ -30,5 +30,6 @@ class="org.apache.cloudstack.storage.motion.AncientDataMotionStrategy" /> <bean id="xenserverStorageMotionStrategy" class="org.apache.cloudstack.storage.motion.XenServerStorageMotionStrategy" /> - + <bean id="hypervStorageMotionStrategy" + class="org.apache.cloudstack.storage.motion.HypervStorageMotionStrategy" /> </beans> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2aff39f8/engine/storage/datamotion/src/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java ---------------------------------------------------------------------- diff --git a/engine/storage/datamotion/src/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java b/engine/storage/datamotion/src/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java index df81199..fbdacfa 100644 --- a/engine/storage/datamotion/src/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java +++ b/engine/storage/datamotion/src/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java @@ -372,7 +372,7 @@ public class AncientDataMotionStrategy implements DataMotionStrategy { VolumeInfo volume = (VolumeInfo)srcData; StoragePool destPool = (StoragePool)dataStoreMgr.getDataStore(destData.getDataStore().getId(), DataStoreRole.Primary); MigrateVolumeCommand command = new MigrateVolumeCommand(volume.getId(), volume.getPath(), destPool, volume.getAttachedVmName()); - EndPoint ep = selector.select(volume.getDataStore()); + EndPoint ep = selector.select(srcData, StorageAction.MIGRATEVOLUME); Answer answer = null; if (ep == null) { String errMsg = "No remote endpoint to send command, check if host or ssvm is down?"; http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2aff39f8/engine/storage/src/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java ---------------------------------------------------------------------- diff --git a/engine/storage/src/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java b/engine/storage/src/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java index d64f755..304f959 100644 --- a/engine/storage/src/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java +++ b/engine/storage/src/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java @@ -305,6 +305,15 @@ public class DefaultEndPointSelector implements EndPointSelector { return getEndPointFromHostId(hostId); } } + } else if (action == StorageAction.MIGRATEVOLUME) { + VolumeInfo volume = (VolumeInfo)object; + if (volume.getHypervisorType() == Hypervisor.HypervisorType.Hyperv) { + VirtualMachine vm = volume.getAttachedVM(); + if ((vm != null) && (vm.getState() == VirtualMachine.State.Running)) { + Long hostId = vm.getHostId(); + return getEndPointFromHostId(hostId); + } + } } return select(object); } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2aff39f8/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java ---------------------------------------------------------------------- diff --git a/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java b/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java index 1c01401..68e5a56 100644 --- a/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java +++ b/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java @@ -77,6 +77,7 @@ import com.cloud.host.Host; import com.cloud.host.dao.HostDao; import com.cloud.storage.DataStoreRole; import com.cloud.storage.ScopeType; +import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.StoragePool; import com.cloud.storage.VMTemplateStoragePoolVO; import com.cloud.storage.VMTemplateStorageResourceAssoc; @@ -814,9 +815,16 @@ public class VolumeServiceImpl implements VolumeService { protected VolumeVO duplicateVolumeOnAnotherStorage(Volume volume, StoragePool pool) { Long lastPoolId = volume.getPoolId(); + String folder = pool.getPath(); + // For SMB, pool credentials are also stored in the uri query string. We trim the query string + // part here to make sure the credentials do not get stored in the db unencrypted. + if (pool.getPoolType() == StoragePoolType.SMB && folder != null && folder.contains("?")) { + folder = folder.substring(0, folder.indexOf("?")); + } + VolumeVO newVol = new VolumeVO(volume); newVol.setPoolId(pool.getId()); - newVol.setFolder(pool.getPath()); + newVol.setFolder(folder); newVol.setPodId(pool.getPodId()); newVol.setPoolId(pool.getId()); newVol.setLastPoolId(lastPoolId); http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2aff39f8/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/CloudStackTypes.cs ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/CloudStackTypes.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/CloudStackTypes.cs index 869f108..c222102 100644 --- a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/CloudStackTypes.cs +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/CloudStackTypes.cs @@ -173,33 +173,35 @@ namespace HypervResource PrimaryDataStoreTO store = this.primaryDataStore; if (store.isLocal) { - fileName = Path.Combine(store.Path, this.uuid); + String volume = this.path; + if (String.IsNullOrEmpty(volume)) + { + volume = this.uuid; + } + fileName = Path.Combine(store.Path, volume); } else { - fileName = @"\\" + store.uri.Host + store.uri.LocalPath + @"\" + this.uuid; + String volume = this.path; + if (String.IsNullOrEmpty(volume)) + { + volume = this.uuid; + } + fileName = @"\\" + store.uri.Host + store.uri.LocalPath + @"\" + volume; fileName = Utils.NormalizePath(fileName); } } else if (this.nfsDataStore != null) { - if (this.path != null && File.Exists(this.path)) + fileName = this.nfsDataStore.UncPath; + if (this.path != null) { - fileName = this.path; + fileName = Utils.NormalizePath(fileName + @"\" + this.path); } - else - { - fileName = this.nfsDataStore.UncPath; - if (this.path != null) - { - fileName += @"\" + this.path; - } - fileName = Utils.NormalizePath(fileName); - if (Directory.Exists(fileName)) - { - fileName = Utils.NormalizePath(fileName + @"\" + this.uuid); - } + if (fileName != null && !File.Exists(fileName)) + { + fileName = Utils.NormalizePath(fileName + @"\" + this.uuid); } } else @@ -344,8 +346,17 @@ namespace HypervResource } else if (this.nfsDataStoreTO != null) { - NFSTO store = this.nfsDataStoreTO; - fileName = store.UncPath + @"\" + this.path + @"\" + this.uuid; + fileName = this.nfsDataStoreTO.UncPath; + if (this.path != null) + { + fileName = Utils.NormalizePath(fileName + @"\" + this.path); + } + + if (fileName != null && !File.Exists(fileName)) + { + fileName = Utils.NormalizePath(fileName + @"\" + this.uuid); + } + if (!this.format.Equals("RAW")) { fileName = fileName + '.' + this.format.ToLowerInvariant(); @@ -769,6 +780,8 @@ namespace HypervResource public const string ManageSnapshotCommand = "com.cloud.agent.api.ManageSnapshotCommand"; public const string MigrateAnswer = "com.cloud.agent.api.MigrateAnswer"; public const string MigrateCommand = "com.cloud.agent.api.MigrateCommand"; + public const string MigrateWithStorageAnswer = "com.cloud.agent.api.MigrateWithStorageAnswer"; + public const string MigrateWithStorageCommand = "com.cloud.agent.api.MigrateWithStorageCommand"; public const string ModifySshKeysCommand = "com.cloud.agent.api.ModifySshKeysCommand"; public const string ModifyStoragePoolAnswer = "com.cloud.agent.api.ModifyStoragePoolAnswer"; public const string ModifyStoragePoolCommand = "com.cloud.agent.api.ModifyStoragePoolCommand"; @@ -853,6 +866,8 @@ namespace HypervResource public const string CreateCommand = "com.cloud.agent.api.storage.CreateCommand"; public const string CreatePrivateTemplateAnswer = "com.cloud.agent.api.storage.CreatePrivateTemplateAnswer"; public const string DestroyCommand = "com.cloud.agent.api.storage.DestroyCommand"; + public const string MigrateVolumeAnswer = "com.cloud.agent.api.storage.MigrateVolumeAnswer"; + public const string MigrateVolumeCommand = "com.cloud.agent.api.storage.MigrateVolumeCommand"; public const string PrimaryStorageDownloadAnswer = "com.cloud.agent.api.storage.PrimaryStorageDownloadAnswer"; public const string PrimaryStorageDownloadCommand = "com.cloud.agent.api.storage.PrimaryStorageDownloadCommand"; public const string ResizeVolumeAnswer = "com.cloud.agent.api.storage.ResizeVolumeAnswer"; http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2aff39f8/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs index e233d03..2d44753 100644 --- a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs @@ -1187,7 +1187,7 @@ namespace HypervResource volumePath = Utils.NormalizePath(volumePath); Utils.ConnectToRemote(primary.UncPath, primary.Domain, primary.User, primary.Password); } - volume.path = volumePath; + volume.path = volume.uuid; wmiCallsV2.CreateDynamicVirtualHardDisk(volumeSize, volumePath); if (File.Exists(volumePath)) { @@ -1578,7 +1578,7 @@ namespace HypervResource { // TODO: thin provision instead of copying the full file. File.Copy(srcFile, destFile); - destVolumeObjectTO.path = destFile; + destVolumeObjectTO.path = destVolumeObjectTO.uuid; JObject ansObj = Utils.CreateCloudStackObject(CloudStackTypes.VolumeObjectTO, destVolumeObjectTO); newData = ansObj; result = true; @@ -1612,8 +1612,25 @@ namespace HypervResource // doesn't do anything if the directory is already present. Directory.CreateDirectory(Path.GetDirectoryName(destFile)); File.Copy(srcFile, destFile); + + if (srcVolumeObjectTO.nfsDataStore != null && srcVolumeObjectTO.primaryDataStore == null) + { + logger.Info("Copied volume from secondary data store to primary. Path: " + destVolumeObjectTO.path); + } + else if (srcVolumeObjectTO.primaryDataStore != null && srcVolumeObjectTO.nfsDataStore == null) + { + destVolumeObjectTO.path = destVolumeObjectTO.path + "/" + destVolumeObjectTO.uuid; + if (destVolumeObjectTO.format != null) + { + destVolumeObjectTO.path += "." + destVolumeObjectTO.format.ToLower(); + } + } + else + { + logger.Error("Destination volume path wasn't set. Unsupported source volume data store."); + } + // Create volumeto object deserialize and send it - destVolumeObjectTO.path = destFile; JObject ansObj = Utils.CreateCloudStackObject(CloudStackTypes.VolumeObjectTO, destVolumeObjectTO); newData = ansObj; result = true; @@ -1656,7 +1673,11 @@ namespace HypervResource TemplateObjectTO destTemplateObject = new TemplateObjectTO(); destTemplateObject.size = srcVolumeObjectTO.size.ToString(); destTemplateObject.format = srcVolumeObjectTO.format; - destTemplateObject.path = destFile; + destTemplateObject.path = destTemplateObjectTO.path + "/" + destTemplateObjectTO.uuid; + if (destTemplateObject.format != null) + { + destTemplateObject.path += "." + destTemplateObject.format.ToLower(); + } destTemplateObject.nfsDataStoreTO = destTemplateObjectTO.nfsDataStoreTO; destTemplateObject.checksum = destTemplateObjectTO.checksum; newData = destTemplateObject; @@ -1990,6 +2011,113 @@ namespace HypervResource } } + // POST api/HypervResource/MigrateVolumeCommand + [HttpPost] + [ActionName(CloudStackTypes.MigrateVolumeCommand)] + public JContainer MigrateVolumeCommand([FromBody]dynamic cmd) + { + using (log4net.NDC.Push(Guid.NewGuid().ToString())) + { + logger.Info(CloudStackTypes.MigrateVolumeCommand + cmd.ToString()); + + string details = null; + bool result = false; + + try + { + string vm = (string)cmd.attachedVmName; + string volume = (string)cmd.volumePath; + wmiCallsV2.MigrateVolume(vm, volume, GetStoragePoolPath(cmd.pool)); + result = true; + } + catch (Exception sysEx) + { + details = CloudStackTypes.MigrateVolumeCommand + " failed due to " + sysEx.Message; + logger.Error(details, sysEx); + } + + object ansContent = new + { + result = result, + volumePath = (string)cmd.volumePath, + details = details, + contextMap = contextMap + }; + + return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.MigrateVolumeAnswer); + } + } + + // POST api/HypervResource/MigrateWithStorageCommand + [HttpPost] + [ActionName(CloudStackTypes.MigrateWithStorageCommand)] + public JContainer MigrateWithStorageCommand([FromBody]dynamic cmd) + { + using (log4net.NDC.Push(Guid.NewGuid().ToString())) + { + logger.Info(CloudStackTypes.MigrateWithStorageCommand + cmd.ToString()); + + string details = null; + bool result = false; + List<dynamic> volumeTos = new List<dynamic>(); + + try + { + string vm = (string)cmd.vm.name; + string destination = (string)cmd.tgtHost; + var volumeToPoolList = cmd.volumeToFilerAsList; + var volumeToPool = new Dictionary<string, string>(); + foreach (var item in volumeToPoolList) + { + volumeTos.Add(item.t); + string poolPath = GetStoragePoolPath(item.u); + volumeToPool.Add((string)item.t.path, poolPath); + } + + wmiCallsV2.MigrateVmWithVolume(vm, destination, volumeToPool); + result = true; + } + catch (Exception sysEx) + { + details = CloudStackTypes.MigrateWithStorageCommand + " failed due to " + sysEx.Message; + logger.Error(details, sysEx); + } + + object ansContent = new + { + result = result, + volumeTos = JArray.FromObject(volumeTos), + details = details, + contextMap = contextMap + }; + + return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.MigrateWithStorageAnswer); + } + } + + private string GetStoragePoolPath(dynamic pool) + { + string poolTypeStr = pool.type; + StoragePoolType poolType; + if (!Enum.TryParse<StoragePoolType>(poolTypeStr, out poolType)) + { + throw new ArgumentException("Invalid pool type " + poolTypeStr); + } + else if (poolType == StoragePoolType.SMB) + { + NFSTO share = new NFSTO(); + String uriStr = "cifs://" + (string)pool.host + (string)pool.path; + share.uri = new Uri(uriStr); + return Utils.NormalizePath(share.UncPath); + } + else if (poolType == StoragePoolType.Filesystem) + { + return pool.path; + } + + throw new ArgumentException("Couldn't parse path for pool type " + poolTypeStr); + } + // POST api/HypervResource/StartupCommand [HttpPost] [ActionName(CloudStackTypes.StartupCommand)] @@ -2016,7 +2144,8 @@ namespace HypervResource strtRouteCmd.storageNetmask = subnet; strtRouteCmd.storageMacAddress = storageNic.GetPhysicalAddress().ToString(); strtRouteCmd.gatewayIpAddress = storageNic.GetPhysicalAddress().ToString(); - + strtRouteCmd.hypervisorVersion = System.Environment.OSVersion.Version.Major.ToString() + "." + + System.Environment.OSVersion.Version.Minor.ToString(); strtRouteCmd.caps = "hvm"; dynamic details = strtRouteCmd.hostDetails; http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2aff39f8/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/IWmiCallsV2.cs ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/IWmiCallsV2.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/IWmiCallsV2.cs index da6ad83..6018896 100644 --- a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/IWmiCallsV2.cs +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/IWmiCallsV2.cs @@ -40,6 +40,8 @@ namespace HypervResource void DestroyVm(dynamic jsonObj); void DestroyVm(string displayName); void MigrateVm(string vmName, string destination); + void MigrateVolume(string vmName, string volume, string destination); + void MigrateVmWithVolume(string vmName, string destination, Dictionary<string, string> volumeToPool); void DetachDisk(string displayName, string diskFileName); ComputerSystem GetComputerSystem(string displayName); ComputerSystem.ComputerSystemCollection GetComputerSystemCollection(); http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2aff39f8/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCallsV2.cs ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCallsV2.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCallsV2.cs index d2b9ce1..73156c5 100644 --- a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCallsV2.cs +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCallsV2.cs @@ -1059,6 +1059,125 @@ namespace HypervResource } /// <summary> + /// Migrates the volume of a vm to a given destination storage + /// </summary> + /// <param name="displayName"></param> + /// <param name="volume"></param> + /// <param name="destination storage pool"></param> + public void MigrateVolume(string vmName, string volume, string destination) + { + ComputerSystem vm = GetComputerSystem(vmName); + VirtualSystemSettingData vmSettings = GetVmSettings(vm); + VirtualSystemMigrationSettingData migrationSettingData = VirtualSystemMigrationSettingData.CreateInstance(); + VirtualSystemMigrationService service = GetVirtualisationSystemMigrationService(); + StorageAllocationSettingData[] sasd = GetStorageSettings(vm); + + string[] rasds = null; + if (sasd != null) + { + rasds = new string[sasd.Length]; + uint index = 0; + foreach (StorageAllocationSettingData item in sasd) + { + string vhdFileName = Path.GetFileNameWithoutExtension(item.HostResource[0]); + if (!String.IsNullOrEmpty(vhdFileName) && vhdFileName.Equals(volume)) + { + string newVhdPath = Path.Combine(destination, Path.GetFileName(item.HostResource[0])); + item.LateBoundObject["HostResource"] = new string[] { newVhdPath }; + item.LateBoundObject["PoolId"] = ""; + } + + rasds[index++] = item.LateBoundObject.GetText(System.Management.TextFormat.CimDtd20); + } + } + + migrationSettingData.LateBoundObject["MigrationType"] = MigrationType.Storage; + migrationSettingData.LateBoundObject["TransportType"] = TransportType.TCP; + string migrationSettings = migrationSettingData.LateBoundObject.GetText(System.Management.TextFormat.CimDtd20); + + ManagementPath jobPath; + var ret_val = service.MigrateVirtualSystemToHost(vm.Path, null, migrationSettings, rasds, null, out jobPath); + if (ret_val == ReturnCode.Started) + { + MigrationJobCompleted(jobPath); + } + else if (ret_val != ReturnCode.Completed) + { + var errMsg = string.Format( + "Failed migrating volume {0} of VM {1} (GUID {2}) due to {3}", + volume, + vm.ElementName, + vm.Name, + ReturnCode.ToString(ret_val)); + var ex = new WmiException(errMsg); + logger.Error(errMsg, ex); + throw ex; + } + } + + /// <summary> + /// Migrates the volume of a vm to a given destination storage + /// </summary> + /// <param name="displayName"></param> + /// <param name="destination host"></param> + /// <param name="volumeToPool"> volume to me migrated to which pool</param> + public void MigrateVmWithVolume(string vmName, string destination, Dictionary<string, string> volumeToPool) + { + ComputerSystem vm = GetComputerSystem(vmName); + VirtualSystemSettingData vmSettings = GetVmSettings(vm); + VirtualSystemMigrationSettingData migrationSettingData = VirtualSystemMigrationSettingData.CreateInstance(); + VirtualSystemMigrationService service = GetVirtualisationSystemMigrationService(); + StorageAllocationSettingData[] sasd = GetStorageSettings(vm); + + string[] rasds = null; + if (sasd != null) + { + rasds = new string[sasd.Length]; + uint index = 0; + foreach (StorageAllocationSettingData item in sasd) + { + string vhdFileName = Path.GetFileNameWithoutExtension(item.HostResource[0]); + if (!String.IsNullOrEmpty(vhdFileName) && volumeToPool.ContainsKey(vhdFileName)) + { + string newVhdPath = Path.Combine(volumeToPool[vhdFileName], Path.GetFileName(item.HostResource[0])); + item.LateBoundObject["HostResource"] = new string[] { newVhdPath }; + item.LateBoundObject["PoolId"] = ""; + } + + rasds[index++] = item.LateBoundObject.GetText(System.Management.TextFormat.CimDtd20); + } + } + + IPAddress addr = IPAddress.Parse(destination); + IPHostEntry entry = Dns.GetHostEntry(addr); + string[] destinationHost = new string[] { destination }; + + migrationSettingData.LateBoundObject["MigrationType"] = MigrationType.VirtualSystemAndStorage; + migrationSettingData.LateBoundObject["TransportType"] = TransportType.TCP; + migrationSettingData.LateBoundObject["DestinationIPAddressList"] = destinationHost; + string migrationSettings = migrationSettingData.LateBoundObject.GetText(System.Management.TextFormat.CimDtd20); + + ManagementPath jobPath; + var ret_val = service.MigrateVirtualSystemToHost(vm.Path, entry.HostName, migrationSettings, rasds, null, out jobPath); + if (ret_val == ReturnCode.Started) + { + MigrationJobCompleted(jobPath); + } + else if (ret_val != ReturnCode.Completed) + { + var errMsg = string.Format( + "Failed migrating VM {0} and its volumes to destination {1} (GUID {2}) due to {3}", + vm.ElementName, + destination, + vm.Name, + ReturnCode.ToString(ret_val)); + var ex = new WmiException(errMsg); + logger.Error(errMsg, ex); + throw ex; + } + } + + /// <summary> /// Create new storage media resources, e.g. hard disk images and ISO disk images /// see http://msdn.microsoft.com/en-us/library/hh859775(v=vs.85).aspx /// </summary> @@ -2081,6 +2200,26 @@ namespace HypervResource return result.ToArray(); } + public StorageAllocationSettingData[] GetStorageSettings(ComputerSystem vm) + { + // ComputerSystem -> VirtualSystemSettingData -> EthernetPortAllocationSettingData + VirtualSystemSettingData vmSettings = GetVmSettings(vm); + + var wmiObjQuery = new RelatedObjectQuery(vmSettings.Path.Path, StorageAllocationSettingData.CreatedClassName); + + // NB: default scope of ManagementObjectSearcher is '\\.\root\cimv2', which does not contain + // the virtualisation objects. + var wmiObjectSearch = new ManagementObjectSearcher(vmSettings.Scope, wmiObjQuery); + var wmiObjCollection = new StorageAllocationSettingData.StorageAllocationSettingDataCollection(wmiObjectSearch.Get()); + + var result = new List<StorageAllocationSettingData>(wmiObjCollection.Count); + foreach (StorageAllocationSettingData item in wmiObjCollection) + { + result.Add(item); + } + return result.ToArray(); + } + public EthernetSwitchPortVlanSettingData GetVlanSettings(EthernetPortAllocationSettingData ethernetConnection) { http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2aff39f8/plugins/hypervisors/hyperv/DotNet/ServerResource/WmiWrappers/ROOT.virtualization.v2.Msvm_StorageAllocationSettingData.cs ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/WmiWrappers/ROOT.virtualization.v2.Msvm_StorageAllocationSettingData.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/WmiWrappers/ROOT.virtualization.v2.Msvm_StorageAllocationSettingData.cs index 8553fed..602347f 100644 --- a/plugins/hypervisors/hyperv/DotNet/ServerResource/WmiWrappers/ROOT.virtualization.v2.Msvm_StorageAllocationSettingData.cs +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/WmiWrappers/ROOT.virtualization.v2.Msvm_StorageAllocationSettingData.cs @@ -36,7 +36,7 @@ namespace CloudStack.Plugin.WmiWrappers.ROOT.VIRTUALIZATION.V2 { private static string CreatedWmiNamespace = "ROOT\\virtualization\\v2"; // Private property to hold the name of WMI class which created this class. - private static string CreatedClassName = "Msvm_StorageAllocationSettingData"; + public static string CreatedClassName = "Msvm_StorageAllocationSettingData"; // Private member variable to hold the ManagementScope which is used by the various methods. private static System.Management.ManagementScope statMgmtScope = null; http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2aff39f8/plugins/hypervisors/hyperv/src/org/apache/cloudstack/storage/motion/HypervStorageMotionStrategy.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/hyperv/src/org/apache/cloudstack/storage/motion/HypervStorageMotionStrategy.java b/plugins/hypervisors/hyperv/src/org/apache/cloudstack/storage/motion/HypervStorageMotionStrategy.java new file mode 100644 index 0000000..011bd12 --- /dev/null +++ b/plugins/hypervisors/hyperv/src/org/apache/cloudstack/storage/motion/HypervStorageMotionStrategy.java @@ -0,0 +1,179 @@ +/* + * 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.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; +import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy; +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.StrategyPriority; +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.cloudstack.storage.to.VolumeObjectTO; +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.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.Pair; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.dao.VMInstanceDao; + +@Component +public class HypervStorageMotionStrategy implements DataMotionStrategy { + private static final Logger s_logger = Logger.getLogger(HypervStorageMotionStrategy.class); + @Inject AgentManager agentMgr; + @Inject VolumeDao volDao; + @Inject VolumeDataFactory volFactory; + @Inject PrimaryDataStoreDao storagePoolDao; + @Inject VMInstanceDao instanceDao; + + @Override + public StrategyPriority canHandle(DataObject srcData, DataObject destData) { + return StrategyPriority.CANT_HANDLE; + } + + @Override + public StrategyPriority canHandle(Map<VolumeInfo, DataStore> volumeMap, Host srcHost, Host destHost) { + if (srcHost.getHypervisorType() == HypervisorType.Hyperv && + destHost.getHypervisorType() == HypervisorType.Hyperv) { + return StrategyPriority.HYPERVISOR; + } + + return StrategyPriority.CANT_HANDLE; + } + + @Override + public Void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) { + throw new UnsupportedOperationException(); + } + + @Override + public Void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback<CopyCommandResult> 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<VolumeInfo, DataStore> volumeMap, VirtualMachineTO vmTo, Host srcHost, Host destHost, + AsyncCompletionCallback<CopyCommandResult> callback) { + Answer answer = null; + String errMsg = null; + try { + VMInstanceVO instance = instanceDao.findById(vmTo.getId()); + if (instance != null) { + answer = migrateVmWithVolumes(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 migrateVmWithVolumes(VMInstanceVO vm, VirtualMachineTO to, Host srcHost, + Host destHost, Map<VolumeInfo, DataStore> volumeToPool) throws AgentUnavailableException { + + // Initiate migration of a virtual machine with it's volumes. + try { + List<Pair<VolumeTO, StorageFilerTO>> volumeToFilerto = new ArrayList<Pair<VolumeTO, StorageFilerTO>>(); + for (Map.Entry<VolumeInfo, DataStore> entry : volumeToPool.entrySet()) { + VolumeInfo volume = entry.getKey(); + VolumeTO volumeTo = new VolumeTO(volume, storagePoolDao.findById(volume.getPoolId())); + StorageFilerTO filerTo = new StorageFilerTO((StoragePool)entry.getValue()); + volumeToFilerto.add(new Pair<VolumeTO, StorageFilerTO>(volumeTo, filerTo)); + } + + MigrateWithStorageCommand command = new MigrateWithStorageCommand(to, volumeToFilerto, destHost.getPrivateIpAddress()); + 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. + updateVolumePathsAfterMigration(volumeToPool, answer.getVolumeTos()); + } + + 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 updateVolumePathsAfterMigration(Map<VolumeInfo, DataStore> volumeToPool, List<VolumeObjectTO> volumeTos) { + for (Map.Entry<VolumeInfo, DataStore> entry : volumeToPool.entrySet()) { + boolean updated = false; + VolumeInfo volume = entry.getKey(); + StoragePool pool = (StoragePool)entry.getValue(); + for (VolumeObjectTO volumeTo : volumeTos) { + if (volume.getId() == volumeTo.getId()) { + VolumeVO volumeVO = volDao.findById(volume.getId()); + Long oldPoolId = volumeVO.getPoolId(); + volumeVO.setPath(volumeTo.getPath()); + volumeVO.setFolder(pool.getPath()); + volumeVO.setPodId(pool.getPodId()); + volumeVO.setPoolId(pool.getId()); + volumeVO.setLastPoolId(oldPoolId); + volDao.update(volume.getId(), volumeVO); + updated = true; + break; + } + } + + if (!updated) { + s_logger.error("Volume path wasn't updated for volume " + volume + " after it was migrated."); + } + } + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2aff39f8/server/src/com/cloud/vm/UserVmManagerImpl.java ---------------------------------------------------------------------- diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 0e4fb5e..393613a 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -3996,7 +3996,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } if (!vm.getHypervisorType().equals(HypervisorType.XenServer) && !vm.getHypervisorType().equals(HypervisorType.VMware) && !vm.getHypervisorType().equals(HypervisorType.KVM) - && !vm.getHypervisorType().equals(HypervisorType.Ovm) && !vm.getHypervisorType().equals(HypervisorType.Simulator)) { + && !vm.getHypervisorType().equals(HypervisorType.Ovm) && !vm.getHypervisorType().equals(HypervisorType.Hyperv) + && !vm.getHypervisorType().equals(HypervisorType.Simulator)) { throw new InvalidParameterValueException("Unsupported hypervisor type for vm migration, we support" + " XenServer/VMware/KVM only"); } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2aff39f8/setup/db/db/schema-430to440.sql ---------------------------------------------------------------------- diff --git a/setup/db/db/schema-430to440.sql b/setup/db/db/schema-430to440.sql index 9d41fe9..51bce2d 100644 --- a/setup/db/db/schema-430to440.sql +++ b/setup/db/db/schema-430to440.sql @@ -26,6 +26,7 @@ ALTER TABLE `cloud`.`disk_offering` ADD `cache_mode` VARCHAR( 16 ) DEFAULT 'none UPDATE `cloud`.`hypervisor_capabilities` set max_guests_limit='150' WHERE hypervisor_version='6.1.0'; UPDATE `cloud`.`hypervisor_capabilities` set max_guests_limit='500' WHERE hypervisor_version='6.2.0'; +UPDATE `cloud`.`hypervisor_capabilities` set storage_motion_supported='1' WHERE hypervisor_version='6.2' AND hypervisor_type="Hyperv"; DROP VIEW IF EXISTS `cloud`.`disk_offering_view`; CREATE VIEW `cloud`.`disk_offering_view` AS