CA-309758: Optimisations to the way we assert if Vms can be migrated to a destination pool in the Cross Pool Migrate wizard

- When asserting if a VM can be migrated to a pool we don't have to check all the hosts in the pool at this point, we can enable the pool when we find the first host where migration is possible; we will check the rest of the hosts only if that pool is selected.
- If the destination pool is older than the source, then we don't need to do any server calls because we know that migration to an older host is not allowed.

Signed-off-by: Mihaela Stoica <mihaela.stoica@citrix.com>
This commit is contained in:
Mihaela Stoica 2019-02-28 15:09:33 +00:00 committed by Konstantina Chremmou
parent 0df39a9d83
commit 4abe4ddb30
7 changed files with 199 additions and 166 deletions

View File

@ -32,7 +32,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using XenAdmin.Commands; using XenAdmin.Commands;
using XenAdmin.Core; using XenAdmin.Core;
using XenAdmin.Wizards.GenericPages; using XenAdmin.Wizards.GenericPages;
@ -69,135 +68,150 @@ namespace XenAdmin.Wizards.CrossPoolMigrateWizard.Filters
canceled = true; canceled = true;
} }
public override bool FailureFound public override bool FailureFoundFor(IXenObject itemToFilterOn)
{ {
get Pool targetPool;
List<Host> targets = CollateHosts(itemToFilterOn, out targetPool);
foreach (VM vm in preSelectedVMs)
{ {
log.InfoFormat("Asserting can migrate to {0}...", ItemToFilterOn); log.InfoFormat("Asserting can migrate VM {0} to {1}...", vm.Name(), itemToFilterOn);
bool vmIsMigratable = false;
Pool targetPool;
List<Host> targets = CollateHosts(out targetPool);
var excludedHosts = new List<string>();
foreach (Host host in targets) foreach (Host host in targets)
{ {
var targetSrs = host.Connection.Cache.SRs.Where(sr => sr.SupportsStorageMigration()).ToList(); if (canceled)
var targetNetwork = GetANetwork(host); return false;
foreach (VM vm in preSelectedVMs) // obtain the cache data for a vm
IDictionary<string, string> vmCache;
lock (cacheLock)
{ {
if (canceled) if (!cache.ContainsKey(vm.opaque_ref))
return excludedHosts.Count == targets.Count; {
cache.Add(vm.opaque_ref, new Dictionary<string, string>());
}
vmCache = cache[vm.opaque_ref];
}
// obtain the cache data for a vm try
IDictionary<string, string> vmCache; {
//CA-220218: for intra-pool motion of halted VMs we do a move, so no need to assert we can migrate
Pool vmPool = Helpers.GetPoolOfOne(vm.Connection);
if (_wizardMode == WizardMode.Move && vmPool != null && targetPool != null && vmPool.opaque_ref == targetPool.opaque_ref)
{
// vm is migratable, no need to itearate through all the pool members
vmIsMigratable = true;
break;
}
//Skip the resident host as there's a filter for it and
//if not then you could exclude intrapool migration
//CA-205799: do not offer the host the VM is currently on
Host homeHost = vm.Home();
if (homeHost != null && homeHost.opaque_ref == host.opaque_ref)
continue;
if (vmCache.ContainsKey(host.opaque_ref))
{
disableReason = vmCache[host.opaque_ref];
if (string.IsNullOrEmpty(disableReason))
{
// vm is migratable to at least one host in the pool, no need to itearate through all the pool members
vmIsMigratable = true;
break;
}
continue;
}
//if pool_migrate can be done, then we will allow it in the wizard, even if storage migration is not allowed (i.e. users can use the wizard to live-migrate a VM inside the pool)
if (_wizardMode == WizardMode.Migrate && vmPool != null && targetPool != null && vmPool.opaque_ref == targetPool.opaque_ref)
{
var reason = VMOperationHostCommand.GetVmCannotBootOnHostReason(vm, host, vm.Connection.Session, vm_operations.pool_migrate);
if (string.IsNullOrEmpty(reason))
{
lock (cacheLock)
{
vmCache.Add(host.opaque_ref, reason);
}
// vm is migratable to at least one host in the pool, no need to itearate through all the pool members
vmIsMigratable = true;
break;
}
}
//check if the destination host is older than the source host
var destinationVersion = Helpers.HostPlatformVersion(host);
var sourceVersion = Helpers.HostPlatformVersion(vm.Home() ?? Helpers.GetMaster(vmPool));
if (Helpers.productVersionCompare(destinationVersion, sourceVersion) < 0)
{
throw new Failure(Messages.OLDER_THAN_CURRENT_SERVER);
}
PIF managementPif = host.Connection.Cache.PIFs.First(p => p.management);
XenAPI.Network managementNetwork = host.Connection.Cache.Resolve(managementPif.network);
Session session = host.Connection.DuplicateSession();
Dictionary<string, string> receiveMapping = Host.migrate_receive(session, host.opaque_ref, managementNetwork.opaque_ref, new Dictionary<string, string>());
var targetSrs = host.Connection.Cache.SRs.Where(sr => sr.SupportsStorageMigration()).ToList();
var targetNetwork = GetANetwork(host);
VM.assert_can_migrate(vm.Connection.Session,
vm.opaque_ref,
receiveMapping,
true,
GetVdiMap(vm, targetSrs),
vm.Connection == host.Connection ? new Dictionary<XenRef<VIF>, XenRef<XenAPI.Network>>() : GetVifMap(vm, targetNetwork),
new Dictionary<string, string>());
lock (cacheLock) lock (cacheLock)
{ {
if (!cache.ContainsKey(vm.opaque_ref)) vmCache.Add(host.opaque_ref, string.Empty);
{
cache.Add(vm.opaque_ref, new Dictionary<string, string>());
}
vmCache = cache[vm.opaque_ref];
} }
// vm is migratable to at least one host in the pool, no need to itearate through all the pool members
vmIsMigratable = true;
break;
}
catch (Failure failure)
{
if (failure.ErrorDescription.Count > 0 && failure.ErrorDescription[0] == Failure.RBAC_PERMISSION_DENIED)
disableReason = failure.Message.Split('\n')[0].TrimEnd('\r'); // we want the first line only
else
disableReason = failure.Message;
try lock (cacheLock)
{ {
//CA-220218: for intra-pool motion of halted VMs we do a move, so no need to assert we can migrate vmCache.Add(host.opaque_ref, disableReason.Clone().ToString());
Pool vmPool = Helpers.GetPoolOfOne(vm.Connection);
if (_wizardMode == WizardMode.Move && vmPool != null && targetPool != null && vmPool.opaque_ref == targetPool.opaque_ref)
continue;
//Skip the resident host as there's a filter for it and
//if not then you could exclude intrapool migration
//CA-205799: do not offer the host the VM is currently on
Host homeHost = vm.Home();
if (homeHost != null && homeHost.opaque_ref == host.opaque_ref)
{
if (!excludedHosts.Contains(host.opaque_ref))
excludedHosts.Add(host.opaque_ref);
continue;
}
if (vmCache.ContainsKey(host.opaque_ref))
{
disableReason = vmCache[host.opaque_ref];
if (!string.IsNullOrEmpty(disableReason) && !excludedHosts.Contains(host.opaque_ref))
{
excludedHosts.Add(host.opaque_ref);
}
continue;
}
//if pool_migrate can be done, then we will allow it in the wizard, even if storage migration is not allowed (i.e. users can use the wizard to live-migrate a VM inside the pool)
if (_wizardMode == WizardMode.Migrate && vmPool != null && targetPool != null && vmPool.opaque_ref == targetPool.opaque_ref)
{
var reason = VMOperationHostCommand.GetVmCannotBootOnHostReason(vm, host, vm.Connection.Session, vm_operations.pool_migrate);
if (string.IsNullOrEmpty(reason))
{
lock (cacheLock)
{
vmCache.Add(host.opaque_ref, reason);
}
continue;
}
}
PIF managementPif = host.Connection.Cache.PIFs.First(p => p.management);
XenAPI.Network network = host.Connection.Cache.Resolve(managementPif.network);
Session session = host.Connection.DuplicateSession();
Dictionary<string, string> receiveMapping = Host.migrate_receive(session, host.opaque_ref, network.opaque_ref, new Dictionary<string, string>());
VM.assert_can_migrate(vm.Connection.Session,
vm.opaque_ref,
receiveMapping,
true,
GetVdiMap(vm, targetSrs),
vm.Connection == host.Connection ? new Dictionary<XenRef<VIF>, XenRef<XenAPI.Network>>() : GetVifMap(vm, targetNetwork),
new Dictionary<string, string>());
lock (cacheLock)
{
vmCache.Add(host.opaque_ref, string.Empty);
}
} }
catch (Failure failure)
{
if (failure.ErrorDescription.Count > 0 && failure.ErrorDescription[0] == Failure.RBAC_PERMISSION_DENIED)
disableReason = failure.Message.Split('\n')[0].TrimEnd('\r'); // we want the first line only
else
disableReason = failure.Message;
lock (cacheLock) log.ErrorFormat("VM: {0}, Host: {1} - Reason: {2};", vm.Name(), host.Name(), failure.Message);
{
vmCache.Add(host.opaque_ref, disableReason.Clone().ToString());
}
log.ErrorFormat("VM: {0}, Host: {1} - Reason: {2};", vm.opaque_ref, host.opaque_ref, failure.Message); vmIsMigratable = false;
if (!excludedHosts.Contains(host.opaque_ref))
excludedHosts.Add(host.opaque_ref);
}
} }
} }
return excludedHosts.Count == targets.Count; // if at least one VM is not migratable to the target pool, then there is no point checking the remaining VMs
if (!vmIsMigratable)
return true;
} }
return false;
} }
private List<Host> CollateHosts(out Pool thePool) private List<Host> CollateHosts(IXenObject itemToFilterOn, out Pool thePool)
{ {
thePool = null; thePool = null;
List<Host> target = new List<Host>(); List<Host> target = new List<Host>();
if (ItemToFilterOn is Host) if (itemToFilterOn is Host)
{ {
target.Add(ItemToFilterOn as Host); target.Add(itemToFilterOn as Host);
thePool = Helpers.GetPoolOfOne(ItemToFilterOn.Connection); thePool = Helpers.GetPoolOfOne(itemToFilterOn.Connection);
} }
if (ItemToFilterOn is Pool) if (itemToFilterOn is Pool)
{ {
Pool pool = ItemToFilterOn as Pool; Pool pool = itemToFilterOn as Pool;
target.AddRange(pool.Connection.Cache.Hosts); target.AddRange(pool.Connection.Cache.Hosts);
target.Sort();
thePool = pool; thePool = pool;
} }
return target; return target;

View File

@ -49,31 +49,29 @@ namespace XenAdmin.Wizards.CrossPoolMigrateWizard.Filters
this.preSelectedVMs = preSelectedVMs; this.preSelectedVMs = preSelectedVMs;
} }
public override bool FailureFound public override bool FailureFoundFor(IXenObject itemToFilterOn)
{ {
get var residentHosts = from VM vm in preSelectedVMs
let home = vm.Home()
where home != null
select home;
if (itemToFilterOn is Host)
return residentHosts.Any(h => h == itemToFilterOn);
Pool tempPool = itemToFilterOn as Pool;
if (tempPool != null)
{ {
var residentHosts = from VM vm in preSelectedVMs if (tempPool.Connection.Cache.Hosts.Length > 1)
let home = vm.Home() return false;
where home != null
select home;
if (ItemToFilterOn is Host) //Pool with one host (or less)
return residentHosts.Any(h => h == ItemToFilterOn); Host master = tempPool.Connection.Resolve(tempPool.master);
return residentHosts.Any(h => h == master);
Pool tempPool = ItemToFilterOn as Pool;
if (tempPool != null)
{
if (tempPool.Connection.Cache.Hosts.Length > 1)
return false;
//Pool with one host (or less)
Host master = tempPool.Connection.Resolve(tempPool.master);
return residentHosts.Any(h => h == master);
}
return false;
} }
return false;
} }
public override string Reason public override string Reason

View File

@ -50,21 +50,18 @@ namespace XenAdmin.Wizards.CrossPoolMigrateWizard.Filters
this.preSelectedVMs = preSelectedVMs; this.preSelectedVMs = preSelectedVMs;
} }
public override bool FailureFound public override bool FailureFoundFor(IXenObject itemToFilterOn)
{ {
get bool targetWlb = false;
{
bool targetWlb = false;
if(ItemToFilterOn != null) if(itemToFilterOn != null)
targetWlb = Helpers.CrossPoolMigrationRestrictedWithWlb(ItemToFilterOn.Connection); targetWlb = Helpers.CrossPoolMigrationRestrictedWithWlb(itemToFilterOn.Connection);
bool sourceWlb = preSelectedVMs.Any(vm => Helpers.CrossPoolMigrationRestrictedWithWlb(vm.Connection)); bool sourceWlb = preSelectedVMs.Any(vm => Helpers.CrossPoolMigrationRestrictedWithWlb(vm.Connection));
reason = targetWlb ? Messages.CPM_WLB_ENABLED_ON_HOST_FAILURE_REASON : Messages.CPM_WLB_ENABLED_ON_VM_FAILURE_REASON; reason = targetWlb ? Messages.CPM_WLB_ENABLED_ON_HOST_FAILURE_REASON : Messages.CPM_WLB_ENABLED_ON_VM_FAILURE_REASON;
return targetWlb || sourceWlb; return targetWlb || sourceWlb;
}
} }
private string reason = Messages.UNKNOWN; private string reason = Messages.UNKNOWN;

View File

@ -32,6 +32,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Windows.Forms;
using XenAdmin.Controls; using XenAdmin.Controls;
using XenAPI; using XenAPI;
@ -48,6 +49,7 @@ namespace XenAdmin.Wizards.GenericPages
/// Event raised when the reason is updated /// Event raised when the reason is updated
/// </summary> /// </summary>
public event Action<DelayLoadingOptionComboBoxItem> ReasonUpdated; public event Action<DelayLoadingOptionComboBoxItem> ReasonUpdated;
public Object ParentComboBox;
private string failureReason; private string failureReason;
private IXenObject xenObject; private IXenObject xenObject;
private const int DEFAULT_RETRIES = 10; private const int DEFAULT_RETRIES = 10;

View File

@ -41,21 +41,19 @@ namespace XenAdmin.Wizards.GenericPages
if (!(itemToFilterOn is Host) && !(itemToFilterOn is Pool)) if (!(itemToFilterOn is Host) && !(itemToFilterOn is Pool))
throw new ArgumentException("Target should be host or pool"); throw new ArgumentException("Target should be host or pool");
ItemToFilterOn = itemToFilterOn; baseItemToFilterOn = itemToFilterOn;
} }
/// <summary> /// <summary>
/// Base item that should be used to filter on /// Base item that should be used to filter on
/// </summary> /// </summary>
protected IXenObject ItemToFilterOn { get; set; } private IXenObject baseItemToFilterOn { get; set; }
public abstract bool FailureFound { get; } //public abstract bool FailureFound { get; }
public abstract string Reason { get; } public abstract string Reason { get; }
public bool FailureFoundFor(IXenObject xenObject) public abstract bool FailureFoundFor(IXenObject xenObject);
{
ItemToFilterOn = xenObject; public bool FailureFound => FailureFoundFor(baseItemToFilterOn);
return FailureFound;
}
public virtual void Cancel() { } public virtual void Cancel() { }
} }

View File

@ -409,13 +409,13 @@ namespace XenAdmin.Wizards.GenericPages
foreach (var host in sortedHosts) foreach (var host in sortedHosts)
{ {
var item = new DelayLoadingOptionComboBoxItem(host, homeserverFilters); var item = new DelayLoadingOptionComboBoxItem(host, homeserverFilters);
item.LoadSync();
cb.Items.Add(item); cb.Items.Add(item);
if (item.Enabled && ((m_selectedObject != null && m_selectedObject.opaque_ref == host.opaque_ref) || item.ParentComboBox = cb;
(target.Item.opaque_ref == host.opaque_ref))) item.PreferAsSelectedItem = m_selectedObject != null && m_selectedObject.opaque_ref == host.opaque_ref ||
cb.Value = item; target.Item.opaque_ref == host.opaque_ref;
item.ReasonUpdated += DelayLoadedGridComboBoxItem_ReasonChanged;
item.LoadAsync();
} }
} }
SetComboBoxPreSelection(cb); SetComboBoxPreSelection(cb);
@ -524,7 +524,34 @@ namespace XenAdmin.Wizards.GenericPages
}); });
} }
private void PropertyChanged(object sender, PropertyChangedEventArgs e) private void DelayLoadedGridComboBoxItem_ReasonChanged(DelayLoadingOptionComboBoxItem item)
{
if (item == null)
throw new NullReferenceException("Trying to update delay loaded reason but failed to extract reason");
var cb = item.ParentComboBox as DataGridViewEnableableComboBoxCell;
if (cb == null)
return;
Program.Invoke(this, () =>
{
try
{
var selectedValue = cb.Value;
cb.DataGridView.RefreshEdit();
if (item.Enabled && item.PreferAsSelectedItem)
cb.Value = item;
else
cb.Value = selectedValue;
}
finally
{
item.ReasonUpdated -= DelayLoadedGridComboBoxItem_ReasonChanged;
}
});
}
private void PropertyChanged(object sender, PropertyChangedEventArgs e)
{ {
if (e.PropertyName == "name_label" || e.PropertyName == "metrics" || if (e.PropertyName == "name_label" || e.PropertyName == "metrics" ||
e.PropertyName == "enabled" || e.PropertyName == "live" || e.PropertyName == "patches") e.PropertyName == "enabled" || e.PropertyName == "live" || e.PropertyName == "patches")

View File

@ -47,37 +47,34 @@ namespace XenAdmin.Wizards.ImportWizard.Filters
{ {
_hardwarePlatformSettings = hardwarePlatformSettings; _hardwarePlatformSettings = hardwarePlatformSettings;
if (ItemToFilterOn is Host host) if (itemAddedToComboBox is Host host)
_hosts.Add(host); _hosts.Add(host);
if (ItemToFilterOn is Pool pool) if (itemAddedToComboBox is Pool pool)
_hosts.AddRange(pool.Connection.Cache.Hosts); _hosts.AddRange(pool.Connection.Cache.Hosts);
} }
public override bool FailureFound public override bool FailureFoundFor(IXenObject itemToFilterOn)
{ {
get foreach (var setting in _hardwarePlatformSettings)
{ {
foreach (var setting in _hardwarePlatformSettings) long hardwarePlatformVersion;
if (!long.TryParse(setting.Value.Value, out hardwarePlatformVersion))
continue;
if (_hosts.Count > 0)
{ {
long hardwarePlatformVersion; if (_hosts.Any(h => !h.virtual_hardware_platform_versions.Contains(hardwarePlatformVersion)))
if (!long.TryParse(setting.Value.Value, out hardwarePlatformVersion)) return true;
continue; }
else
if (_hosts.Count > 0) {
{ if (hardwarePlatformVersion > 0)
if (_hosts.Any(h => !h.virtual_hardware_platform_versions.Contains(hardwarePlatformVersion))) return true;
return true;
}
else
{
if (hardwarePlatformVersion > 0)
return true;
}
} }
return false;
} }
return false;
} }
public override string Reason => Messages.CPM_FAILURE_REASON_HARDWARE_PLATFORM; public override string Reason => Messages.CPM_FAILURE_REASON_HARDWARE_PLATFORM;