mirror of
https://github.com/apache/cloudstack.git
synced 2025-12-16 10:32:34 +01:00
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.
This commit is contained in:
parent
f293c94bc0
commit
2aff39f8c7
@ -16,26 +16,38 @@
|
|||||||
// under the License.
|
// under the License.
|
||||||
package com.cloud.agent.api;
|
package com.cloud.agent.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import com.cloud.agent.api.to.StorageFilerTO;
|
import com.cloud.agent.api.to.StorageFilerTO;
|
||||||
import com.cloud.agent.api.to.VirtualMachineTO;
|
import com.cloud.agent.api.to.VirtualMachineTO;
|
||||||
import com.cloud.agent.api.to.VolumeTO;
|
import com.cloud.agent.api.to.VolumeTO;
|
||||||
|
import com.cloud.utils.Pair;
|
||||||
|
|
||||||
public class MigrateWithStorageCommand extends Command {
|
public class MigrateWithStorageCommand extends Command {
|
||||||
VirtualMachineTO vm;
|
VirtualMachineTO vm;
|
||||||
Map<VolumeTO, StorageFilerTO> volumeToFiler;
|
Map<VolumeTO, StorageFilerTO> volumeToFiler;
|
||||||
|
List<Pair<VolumeTO, StorageFilerTO>> volumeToFilerAsList;
|
||||||
String tgtHost;
|
String tgtHost;
|
||||||
|
|
||||||
public MigrateWithStorageCommand(VirtualMachineTO vm, Map<VolumeTO, StorageFilerTO> volumeToFiler) {
|
public MigrateWithStorageCommand(VirtualMachineTO vm, Map<VolumeTO, StorageFilerTO> volumeToFiler) {
|
||||||
this.vm = vm;
|
this.vm = vm;
|
||||||
this.volumeToFiler = volumeToFiler;
|
this.volumeToFiler = volumeToFiler;
|
||||||
|
this.volumeToFilerAsList = null;
|
||||||
this.tgtHost = null;
|
this.tgtHost = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MigrateWithStorageCommand(VirtualMachineTO vm, Map<VolumeTO, StorageFilerTO> volumeToFiler, String tgtHost) {
|
public MigrateWithStorageCommand(VirtualMachineTO vm, Map<VolumeTO, StorageFilerTO> volumeToFiler, String tgtHost) {
|
||||||
this.vm = vm;
|
this.vm = vm;
|
||||||
this.volumeToFiler = volumeToFiler;
|
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;
|
this.tgtHost = tgtHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,6 +59,10 @@ public class MigrateWithStorageCommand extends Command {
|
|||||||
return volumeToFiler;
|
return volumeToFiler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Pair<VolumeTO, StorageFilerTO>> getVolumeToFilerAsList() {
|
||||||
|
return volumeToFilerAsList;
|
||||||
|
}
|
||||||
|
|
||||||
public String getTargetHost() {
|
public String getTargetHost() {
|
||||||
return tgtHost;
|
return tgtHost;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,5 +21,6 @@ package org.apache.cloudstack.engine.subsystem.api.storage;
|
|||||||
public enum StorageAction {
|
public enum StorageAction {
|
||||||
TAKESNAPSHOT,
|
TAKESNAPSHOT,
|
||||||
BACKUPSNAPSHOT,
|
BACKUPSNAPSHOT,
|
||||||
DELETESNAPSHOT
|
DELETESNAPSHOT,
|
||||||
|
MIGRATEVOLUME
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,5 +30,6 @@
|
|||||||
class="org.apache.cloudstack.storage.motion.AncientDataMotionStrategy" />
|
class="org.apache.cloudstack.storage.motion.AncientDataMotionStrategy" />
|
||||||
<bean id="xenserverStorageMotionStrategy"
|
<bean id="xenserverStorageMotionStrategy"
|
||||||
class="org.apache.cloudstack.storage.motion.XenServerStorageMotionStrategy" />
|
class="org.apache.cloudstack.storage.motion.XenServerStorageMotionStrategy" />
|
||||||
|
<bean id="hypervStorageMotionStrategy"
|
||||||
|
class="org.apache.cloudstack.storage.motion.HypervStorageMotionStrategy" />
|
||||||
</beans>
|
</beans>
|
||||||
|
|||||||
@ -372,7 +372,7 @@ public class AncientDataMotionStrategy implements DataMotionStrategy {
|
|||||||
VolumeInfo volume = (VolumeInfo)srcData;
|
VolumeInfo volume = (VolumeInfo)srcData;
|
||||||
StoragePool destPool = (StoragePool)dataStoreMgr.getDataStore(destData.getDataStore().getId(), DataStoreRole.Primary);
|
StoragePool destPool = (StoragePool)dataStoreMgr.getDataStore(destData.getDataStore().getId(), DataStoreRole.Primary);
|
||||||
MigrateVolumeCommand command = new MigrateVolumeCommand(volume.getId(), volume.getPath(), destPool, volume.getAttachedVmName());
|
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;
|
Answer answer = null;
|
||||||
if (ep == null) {
|
if (ep == null) {
|
||||||
String errMsg = "No remote endpoint to send command, check if host or ssvm is down?";
|
String errMsg = "No remote endpoint to send command, check if host or ssvm is down?";
|
||||||
|
|||||||
@ -305,6 +305,15 @@ public class DefaultEndPointSelector implements EndPointSelector {
|
|||||||
return getEndPointFromHostId(hostId);
|
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);
|
return select(object);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,6 +77,7 @@ import com.cloud.host.Host;
|
|||||||
import com.cloud.host.dao.HostDao;
|
import com.cloud.host.dao.HostDao;
|
||||||
import com.cloud.storage.DataStoreRole;
|
import com.cloud.storage.DataStoreRole;
|
||||||
import com.cloud.storage.ScopeType;
|
import com.cloud.storage.ScopeType;
|
||||||
|
import com.cloud.storage.Storage.StoragePoolType;
|
||||||
import com.cloud.storage.StoragePool;
|
import com.cloud.storage.StoragePool;
|
||||||
import com.cloud.storage.VMTemplateStoragePoolVO;
|
import com.cloud.storage.VMTemplateStoragePoolVO;
|
||||||
import com.cloud.storage.VMTemplateStorageResourceAssoc;
|
import com.cloud.storage.VMTemplateStorageResourceAssoc;
|
||||||
@ -814,9 +815,16 @@ public class VolumeServiceImpl implements VolumeService {
|
|||||||
|
|
||||||
protected VolumeVO duplicateVolumeOnAnotherStorage(Volume volume, StoragePool pool) {
|
protected VolumeVO duplicateVolumeOnAnotherStorage(Volume volume, StoragePool pool) {
|
||||||
Long lastPoolId = volume.getPoolId();
|
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);
|
VolumeVO newVol = new VolumeVO(volume);
|
||||||
newVol.setPoolId(pool.getId());
|
newVol.setPoolId(pool.getId());
|
||||||
newVol.setFolder(pool.getPath());
|
newVol.setFolder(folder);
|
||||||
newVol.setPodId(pool.getPodId());
|
newVol.setPodId(pool.getPodId());
|
||||||
newVol.setPoolId(pool.getId());
|
newVol.setPoolId(pool.getId());
|
||||||
newVol.setLastPoolId(lastPoolId);
|
newVol.setLastPoolId(lastPoolId);
|
||||||
|
|||||||
@ -173,33 +173,35 @@ namespace HypervResource
|
|||||||
PrimaryDataStoreTO store = this.primaryDataStore;
|
PrimaryDataStoreTO store = this.primaryDataStore;
|
||||||
if (store.isLocal)
|
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
|
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);
|
fileName = Utils.NormalizePath(fileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (this.nfsDataStore != null)
|
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 (fileName != null && !File.Exists(fileName))
|
||||||
if (Directory.Exists(fileName))
|
{
|
||||||
{
|
fileName = Utils.NormalizePath(fileName + @"\" + this.uuid);
|
||||||
fileName = Utils.NormalizePath(fileName + @"\" + this.uuid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -344,8 +346,17 @@ namespace HypervResource
|
|||||||
}
|
}
|
||||||
else if (this.nfsDataStoreTO != null)
|
else if (this.nfsDataStoreTO != null)
|
||||||
{
|
{
|
||||||
NFSTO store = this.nfsDataStoreTO;
|
fileName = this.nfsDataStoreTO.UncPath;
|
||||||
fileName = store.UncPath + @"\" + this.path + @"\" + this.uuid;
|
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"))
|
if (!this.format.Equals("RAW"))
|
||||||
{
|
{
|
||||||
fileName = fileName + '.' + this.format.ToLowerInvariant();
|
fileName = fileName + '.' + this.format.ToLowerInvariant();
|
||||||
@ -769,6 +780,8 @@ namespace HypervResource
|
|||||||
public const string ManageSnapshotCommand = "com.cloud.agent.api.ManageSnapshotCommand";
|
public const string ManageSnapshotCommand = "com.cloud.agent.api.ManageSnapshotCommand";
|
||||||
public const string MigrateAnswer = "com.cloud.agent.api.MigrateAnswer";
|
public const string MigrateAnswer = "com.cloud.agent.api.MigrateAnswer";
|
||||||
public const string MigrateCommand = "com.cloud.agent.api.MigrateCommand";
|
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 ModifySshKeysCommand = "com.cloud.agent.api.ModifySshKeysCommand";
|
||||||
public const string ModifyStoragePoolAnswer = "com.cloud.agent.api.ModifyStoragePoolAnswer";
|
public const string ModifyStoragePoolAnswer = "com.cloud.agent.api.ModifyStoragePoolAnswer";
|
||||||
public const string ModifyStoragePoolCommand = "com.cloud.agent.api.ModifyStoragePoolCommand";
|
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 CreateCommand = "com.cloud.agent.api.storage.CreateCommand";
|
||||||
public const string CreatePrivateTemplateAnswer = "com.cloud.agent.api.storage.CreatePrivateTemplateAnswer";
|
public const string CreatePrivateTemplateAnswer = "com.cloud.agent.api.storage.CreatePrivateTemplateAnswer";
|
||||||
public const string DestroyCommand = "com.cloud.agent.api.storage.DestroyCommand";
|
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 PrimaryStorageDownloadAnswer = "com.cloud.agent.api.storage.PrimaryStorageDownloadAnswer";
|
||||||
public const string PrimaryStorageDownloadCommand = "com.cloud.agent.api.storage.PrimaryStorageDownloadCommand";
|
public const string PrimaryStorageDownloadCommand = "com.cloud.agent.api.storage.PrimaryStorageDownloadCommand";
|
||||||
public const string ResizeVolumeAnswer = "com.cloud.agent.api.storage.ResizeVolumeAnswer";
|
public const string ResizeVolumeAnswer = "com.cloud.agent.api.storage.ResizeVolumeAnswer";
|
||||||
|
|||||||
@ -1187,7 +1187,7 @@ namespace HypervResource
|
|||||||
volumePath = Utils.NormalizePath(volumePath);
|
volumePath = Utils.NormalizePath(volumePath);
|
||||||
Utils.ConnectToRemote(primary.UncPath, primary.Domain, primary.User, primary.Password);
|
Utils.ConnectToRemote(primary.UncPath, primary.Domain, primary.User, primary.Password);
|
||||||
}
|
}
|
||||||
volume.path = volumePath;
|
volume.path = volume.uuid;
|
||||||
wmiCallsV2.CreateDynamicVirtualHardDisk(volumeSize, volumePath);
|
wmiCallsV2.CreateDynamicVirtualHardDisk(volumeSize, volumePath);
|
||||||
if (File.Exists(volumePath))
|
if (File.Exists(volumePath))
|
||||||
{
|
{
|
||||||
@ -1578,7 +1578,7 @@ namespace HypervResource
|
|||||||
{
|
{
|
||||||
// TODO: thin provision instead of copying the full file.
|
// TODO: thin provision instead of copying the full file.
|
||||||
File.Copy(srcFile, destFile);
|
File.Copy(srcFile, destFile);
|
||||||
destVolumeObjectTO.path = destFile;
|
destVolumeObjectTO.path = destVolumeObjectTO.uuid;
|
||||||
JObject ansObj = Utils.CreateCloudStackObject(CloudStackTypes.VolumeObjectTO, destVolumeObjectTO);
|
JObject ansObj = Utils.CreateCloudStackObject(CloudStackTypes.VolumeObjectTO, destVolumeObjectTO);
|
||||||
newData = ansObj;
|
newData = ansObj;
|
||||||
result = true;
|
result = true;
|
||||||
@ -1612,8 +1612,25 @@ namespace HypervResource
|
|||||||
// doesn't do anything if the directory is already present.
|
// doesn't do anything if the directory is already present.
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(destFile));
|
Directory.CreateDirectory(Path.GetDirectoryName(destFile));
|
||||||
File.Copy(srcFile, 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
|
// Create volumeto object deserialize and send it
|
||||||
destVolumeObjectTO.path = destFile;
|
|
||||||
JObject ansObj = Utils.CreateCloudStackObject(CloudStackTypes.VolumeObjectTO, destVolumeObjectTO);
|
JObject ansObj = Utils.CreateCloudStackObject(CloudStackTypes.VolumeObjectTO, destVolumeObjectTO);
|
||||||
newData = ansObj;
|
newData = ansObj;
|
||||||
result = true;
|
result = true;
|
||||||
@ -1656,7 +1673,11 @@ namespace HypervResource
|
|||||||
TemplateObjectTO destTemplateObject = new TemplateObjectTO();
|
TemplateObjectTO destTemplateObject = new TemplateObjectTO();
|
||||||
destTemplateObject.size = srcVolumeObjectTO.size.ToString();
|
destTemplateObject.size = srcVolumeObjectTO.size.ToString();
|
||||||
destTemplateObject.format = srcVolumeObjectTO.format;
|
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.nfsDataStoreTO = destTemplateObjectTO.nfsDataStoreTO;
|
||||||
destTemplateObject.checksum = destTemplateObjectTO.checksum;
|
destTemplateObject.checksum = destTemplateObjectTO.checksum;
|
||||||
newData = destTemplateObject;
|
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
|
// POST api/HypervResource/StartupCommand
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[ActionName(CloudStackTypes.StartupCommand)]
|
[ActionName(CloudStackTypes.StartupCommand)]
|
||||||
@ -2016,7 +2144,8 @@ namespace HypervResource
|
|||||||
strtRouteCmd.storageNetmask = subnet;
|
strtRouteCmd.storageNetmask = subnet;
|
||||||
strtRouteCmd.storageMacAddress = storageNic.GetPhysicalAddress().ToString();
|
strtRouteCmd.storageMacAddress = storageNic.GetPhysicalAddress().ToString();
|
||||||
strtRouteCmd.gatewayIpAddress = 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";
|
strtRouteCmd.caps = "hvm";
|
||||||
|
|
||||||
dynamic details = strtRouteCmd.hostDetails;
|
dynamic details = strtRouteCmd.hostDetails;
|
||||||
|
|||||||
@ -40,6 +40,8 @@ namespace HypervResource
|
|||||||
void DestroyVm(dynamic jsonObj);
|
void DestroyVm(dynamic jsonObj);
|
||||||
void DestroyVm(string displayName);
|
void DestroyVm(string displayName);
|
||||||
void MigrateVm(string vmName, string destination);
|
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);
|
void DetachDisk(string displayName, string diskFileName);
|
||||||
ComputerSystem GetComputerSystem(string displayName);
|
ComputerSystem GetComputerSystem(string displayName);
|
||||||
ComputerSystem.ComputerSystemCollection GetComputerSystemCollection();
|
ComputerSystem.ComputerSystemCollection GetComputerSystemCollection();
|
||||||
|
|||||||
@ -1058,6 +1058,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>
|
/// <summary>
|
||||||
/// Create new storage media resources, e.g. hard disk images and ISO disk images
|
/// 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
|
/// see http://msdn.microsoft.com/en-us/library/hh859775(v=vs.85).aspx
|
||||||
@ -2081,6 +2200,26 @@ namespace HypervResource
|
|||||||
return result.ToArray();
|
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)
|
public EthernetSwitchPortVlanSettingData GetVlanSettings(EthernetPortAllocationSettingData ethernetConnection)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -36,7 +36,7 @@ namespace CloudStack.Plugin.WmiWrappers.ROOT.VIRTUALIZATION.V2 {
|
|||||||
private static string CreatedWmiNamespace = "ROOT\\virtualization\\v2";
|
private static string CreatedWmiNamespace = "ROOT\\virtualization\\v2";
|
||||||
|
|
||||||
// Private property to hold the name of WMI class which created this class.
|
// 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 member variable to hold the ManagementScope which is used by the various methods.
|
||||||
private static System.Management.ManagementScope statMgmtScope = null;
|
private static System.Management.ManagementScope statMgmtScope = null;
|
||||||
|
|||||||
@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
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");
|
throw new InvalidParameterValueException("Unsupported hypervisor type for vm migration, we support" + " XenServer/VMware/KVM only");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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='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 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`;
|
DROP VIEW IF EXISTS `cloud`.`disk_offering_view`;
|
||||||
CREATE VIEW `cloud`.`disk_offering_view` AS
|
CREATE VIEW `cloud`.`disk_offering_view` AS
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user