xenadmin/XenAdmin/Actions/GUIActions/Wlb/WlbOptimizePoolAction.cs
Konstantina Chremmou d148956a1b CA-376686: There is no API call "live_migrate".
Signed-off-by: Konstantina Chremmou <Konstantina.Chremmou@cloud.com>
2023-04-28 10:12:04 +01:00

434 lines
21 KiB
C#

/* Copyright (c) Cloud Software Group, Inc.
*
* Redistribution and use in source and binary forms,
* with or without modification, are permitted provided
* that the following conditions are met:
*
* * Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using XenAdmin.Commands;
using XenAdmin.Core;
using XenAdmin.Wlb;
using XenAdmin.Dialogs;
using XenAPI;
using XenAdmin.Actions.HostActions;
using XenAdmin.Actions.VMActions;
namespace XenAdmin.Actions.Wlb
{
class WlbOptimizePoolAction : AsyncAction
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private string optId = String.Empty;
private readonly Dictionary<VM, WlbOptimizationRecommendation> vmOptList = new Dictionary<VM, WlbOptimizationRecommendation>();
private bool moveToNext = false;
private string hostActionError = String.Empty;
public WlbOptimizePoolAction(Pool pool, Dictionary<VM, WlbOptimizationRecommendation> vmOptLst, string optId)
: base(pool.Connection, string.Format(Messages.WLB_OPTIMIZING_POOL, Helpers.GetName(pool).Ellipsise(50)))
{
Pool = pool;
vmOptList = vmOptLst ?? throw new ArgumentNullException("vmOptLst");
this.optId = optId;
#region RBAC Dependencies
// HA adjustments
ApiMethodsToRoleCheck.Add("pool.sync_database");
ApiMethodsToRoleCheck.Add("pool.set_ha_host_failures_to_tolerate");
ApiMethodsToRoleCheck.Add("vm.set_ha_restart_priority");
ApiMethodsToRoleCheck.Add("vm.assert_can_boot_here");
ApiMethodsToRoleCheck.Add("vm.assert_agile");
ApiMethodsToRoleCheck.AddRange(Role.CommonTaskApiList);
ApiMethodsToRoleCheck.AddRange(Role.CommonSessionApiList);
#endregion
}
protected override void Run()
{
if (vmOptList.Count == 0)
{
log.ErrorFormat("{0} has no VMs need to be optimized", Helpers.GetName(Pool));
return;
}
this.Description = Messages.ACTION_WLB_POOL_OPTIMIZING;
try
{
log.Debug("Optimizing " + Pool.Name());
// for checking whether to display recommendations on optimize pool listview
Helpers.SetOtherConfig(this.Session, this.Pool,WlbOptimizationRecommendation.OPTIMIZINGPOOL, Messages.IN_PROGRESS);
int start = 0;
int each = 90 / vmOptList.Count;
foreach (KeyValuePair<VM, WlbOptimizationRecommendation> vmItem in vmOptList)
{
VM vm = vmItem.Key;
if (vmItem.Key.is_control_domain)
{
log.Debug(vmItem.Value.powerOperation + " " + Helpers.GetName(vmItem.Value.toHost));
Host fromHost = vmItem.Value.fromHost;
Helpers.SetOtherConfig(fromHost.Connection.Session, fromHost,WlbOptimizationRecommendation.OPTIMIZINGPOOL, vmItem.Value.recId.ToString());
AsyncAction hostAction = null;
int waitingInterval = 10 * 1000; // default to 10s
if (vmItem.Value.fromHost.IsLive())
{
hostAction = new ShutdownHostAction(fromHost, AddHostToPoolCommand.NtolDialog);
}
else
{
hostAction = new HostPowerOnAction(fromHost);
waitingInterval = 30 * 1000; // wait for 30s
}
hostAction.Completed += HostAction_Completed;
hostAction.RunAsync();
while (!moveToNext)
{
if (!String.IsNullOrEmpty(hostActionError))
{
throw new Exception(hostActionError);
}
else
{
//wait
System.Threading.Thread.Sleep(waitingInterval);
}
}
}
else
{
log.Debug("Migrating VM " + vm.Name());
Host toHost = vmItem.Value.toHost;
try
{
// check if there is a conflict with HA, start optimize if we can
RelocateVmWithHa(this, vm, toHost, start, start + each, vmItem.Value.recId);
}
catch (Failure f)
{
// prompt to user if ha notl can be raised, if yes, continue
long newNtol;
if (RaiseHANotl(vm, f, out newNtol))
{
DelegatedAsyncAction action = new DelegatedAsyncAction(vm.Connection, Messages.HA_LOWERING_NTOL, null, null,
delegate(Session session)
{
// Set new ntol, then retry action
XenAPI.Pool.set_ha_host_failures_to_tolerate(session, this.Pool.opaque_ref, newNtol);
// ntol set succeeded, start new action
Program.Invoke(Program.MainWindow, () => XenDialogBase.CloseAll(vm));
new VMMigrateAction(vm,toHost).RunAsync();
});
action.RunAsync();
}
else
{
Helpers.SetOtherConfig(this.Session, this.Pool, WlbOptimizationRecommendation.OPTIMIZINGPOOL, Messages.WLB_OPT_FAILED);
this.Description = Messages.WLB_OPT_FAILED;
throw;
}
}
}
start += each;
// stop if user cancels optimize pool
if (Cancelling)
throw new CancelledException();
}
this.Description = Messages.WLB_OPT_OK_NOTICE_TEXT;
Helpers.SetOtherConfig(this.Session, this.Pool, WlbOptimizationRecommendation.OPTIMIZINGPOOL, optId);
log.Debug("Completed optimizing " + Pool.Name());
}
catch (Failure ex)
{
Helpers.SetOtherConfig(this.Session, this.Pool, WlbOptimizationRecommendation.OPTIMIZINGPOOL, optId);
WlbServerState.SetState(Pool, WlbServerState.ServerState.ConnectionError, ex);
throw;
}
catch (CancelledException)
{
Helpers.SetOtherConfig(this.Session, this.Pool, WlbOptimizationRecommendation.OPTIMIZINGPOOL, optId);
throw;
}
catch (Exception ex)
{
log.Error(ex, ex);
this.Description = Messages.WLB_OPT_FAILED;
Helpers.SetOtherConfig(this.Session, this.Pool, WlbOptimizationRecommendation.OPTIMIZINGPOOL, optId);
log.Debug("Optimizing " + Pool.Name() + " is failed");
}
this.PercentComplete = 100;
}
private void HostAction_Completed(ActionBase sender)
{
AsyncAction action = (AsyncAction)sender;
if (action.IsCompleted)
{
action.Completed -= HostAction_Completed;
if (action is ShutdownHostAction || action is HostPowerOnAction)
{
if (action.Description == Messages.ACTION_HOST_STARTED || action.Description == String.Format(Messages.ACTION_HOST_SHUTDOWN, Helpers.GetName(action.Host)))
{
moveToNext = true;
}
if ((action.Exception != null && !String.IsNullOrEmpty(action.Exception.Message)) || action.Description == String.Format(Messages.ACTION_HOST_START_FAILED, Helpers.GetName(action.Host)))
{
hostActionError = action.Exception == null ? action.Description : action.Exception.Message;
}
}
}
}
private void RelocateVmWithHa(AsyncAction action, VM vm, Host host, int start, int end, int recommendationId)
{
bool setDoNotRestart = false;
if (vm.HaPriorityIsRestart())
{
try
{
XenAPI.VM.assert_agile(action.Session, vm.opaque_ref);
}
catch (Failure)
{
// VM is not agile, but it is 'Protected' by HA. This is an inconsistent state (see CA-20820).
// Tell the user the VM will be started without HA protection.
Program.Invoke(Program.MainWindow, delegate()
{
using (var dlg = new WarningDialog(String.Format(Messages.HA_INVALID_CONFIG_RESUME, Helpers.GetName(vm).Ellipsise(500)))
{WindowTitle = Messages.HIGH_AVAILABILITY})
{
dlg.ShowDialog(Program.MainWindow);
}
});
// Set the VM to 'Do not restart'.
XenAPI.VM.set_ha_restart_priority(action.Session, vm.opaque_ref, XenAPI.VM.RESTART_PRIORITY_DO_NOT_RESTART);
setDoNotRestart = true;
}
}
if (!setDoNotRestart && vm.HasSavedRestartPriority())
{
// If HA is turned on, setting ha_always_run will cause the VM to be started by itself
// but when the VM fails to start we want to know why, so we do a VM.start here too.
// This will fail with a power state exception if HA has already started the VM - but in
// that case we don't care, since the VM successfully started already.
SetHaProtection(true, action, vm, start, end);
try
{
DoAction(action, vm, host, start, end, recommendationId);
}
catch (Failure f)
{
if (f.ErrorDescription.Count == 4 && f.ErrorDescription[0] == Failure.VM_BAD_POWER_STATE && f.ErrorDescription[3] == "running")
{
// The VM started successfully via HA
}
else
{
throw;
}
}
}
else
{
// HA off: just do a regular start
DoAction(action, vm, host, start, end, recommendationId);
}
}
/// <summary>
/// In the case there was nowhere to start/resume the VM (NO_HOSTS_AVAILABLE), shows the reason why the VM could not be started
/// on each host. If the start failed due to HA_OPERATION_WOULD_BREAK_FAILOVER_PLAN, offers to decrement ntol and try the operation
/// again.
/// </summary>
/// <param name="vm">vm</param>
/// <param name="failure">failure, xapi exception</param>
/// <param name="newNtol"></param>
//private static bool StartDiagnosisForm(XenObject<VM> vm, Failure failure, string recommendationId)
private bool RaiseHANotl(VM vm, Failure failure, out long newNtol)
{
bool error = false;
newNtol = 0;
if (failure.ErrorDescription[0] == Failure.NO_HOSTS_AVAILABLE)
{
VMOperationCommand.StartDiagnosisForm(vm, vm.power_state == vm_power_state.Halted);
}
else if (failure.ErrorDescription[0] == Failure.HA_OPERATION_WOULD_BREAK_FAILOVER_PLAN)
{
// The action was blocked by HA because it would reduce the number of tolerable server failures.
// With the user's consent, we'll reduce the number of configured failures to tolerate and try again.
if (this.Pool == null)
{
log.ErrorFormat("Could not get pool for VM {0} in StartDiagnosisForm()", Helpers.GetName(vm));
return error;
}
long ntol = this.Pool.ha_host_failures_to_tolerate;
newNtol = Math.Min(this.Pool.ha_plan_exists_for - 1, ntol - 1);
if (newNtol <= 0)
{
// We would need to basically turn HA off to start this VM
string msg = String.Format(Messages.HA_OPT_VM_RELOCATE_NTOL_ZERO,
Helpers.GetName(this.Pool).Ellipsise(100),
Helpers.GetName(vm).Ellipsise(100));
Program.Invoke(Program.MainWindow, delegate()
{
using (var dlg = new WarningDialog(msg)
{WindowTitle = Messages.HIGH_AVAILABILITY})
{
dlg.ShowDialog(Program.MainWindow);
}
});
}
else
{
// Show 'reduce ntol?' dialog
string msg = String.Format(Messages.HA_OPT_DISABLE_NTOL_DROP,
Helpers.GetName(this.Pool).Ellipsise(100), ntol,
Helpers.GetName(vm).Ellipsise(100), newNtol);
Program.Invoke(Program.MainWindow, delegate()
{
using (var dlg = new WarningDialog(msg,
ThreeButtonDialog.ButtonYes,
new ThreeButtonDialog.TBDButton(Messages.NO_BUTTON_CAPTION, DialogResult.No, selected: true))
{WindowTitle = Messages.HIGH_AVAILABILITY})
{
DialogResult r = dlg.ShowDialog(Program.MainWindow);
if (r != DialogResult.Yes)
{
error = true;
}
}
});
}
}
return error;
}
/// <summary>
/// Migrate vm and log recommendation id for reporting purpose
/// </summary>
/// <param name="action">AsyncAction</param>
/// <param name="vm">VM that needs to be migrated</param>
/// <param name="host">Host that vm will migrate to</param>
/// <param name="start">progress bar start point</param>
/// <param name="end">progress bar end point</param>
/// <param name="recommendationId">recommendation id</param>
private static void DoAction(AsyncAction action, VM vm, Host host, int start, int end, int recommendationId)
{
action.RelatedTask = VM.async_pool_migrate(action.Session, vm.opaque_ref, host.opaque_ref, new Dictionary<string, string> { ["live"] = "true" });
if (recommendationId != 0)
{
// set vm migrate task key values for wlb reporting purpose
Task.add_to_other_config(action.Session, action.RelatedTask.opaque_ref, "wlb_advised", recommendationId.ToString());
Task.add_to_other_config(action.Session, action.RelatedTask.opaque_ref, "wlb_action", "vm_migrate");
Task.add_to_other_config(action.Session, action.RelatedTask.opaque_ref, "wlb_action_obj_ref", vm.opaque_ref);
Task.add_to_other_config(action.Session, action.RelatedTask.opaque_ref, "wlb_action_obj_type", "vm");
}
action.PollToCompletion(start, end);
}
/// <summary>
/// Enables or disables HA protection for a VM (VM.ha_always_run). Also does a pool.sync_database afterwards.
/// Does nothing if the server is a build before ha_always_run was introduced.
/// May throw a XenAPI.Failure.
/// </summary>
/// <param name="protect">true if vm is protected</param>
/// <param name="action">AsyncAction</param>
/// <param name="vm">vm</param>
/// <param name="start">progress bar start point</param>
/// <param name="end">progress bar end point</param>
private static void SetHaProtection(bool protect, AsyncAction action, VM vm, int start, int end)
{
// Do database sync. Helps to ensure that the change persists over coordinator failover.
action.RelatedTask = XenAPI.Pool.async_sync_database(action.Session);
action.PollToCompletion(start, end);
}
/*
/// <summary>
/// offer to bump ntol back up, if we can
/// Asks the user if they want to increase ntol (since hypothetical_max might have gone up).
/// </summary>
// not in use at the moment, keep it for now
//private void BumpNtol()
//{
// if (this.Pool!= null && this.Pool.ha_enabled)
// {
// Dictionary<XenRef<VM>, string> config = Helpers.GetVmHaRestartPrioritiesForApi(Helpers.GetVmHaRestartPriorities(this.Pool.Connection));
// long max = XenAPI.Pool.ha_compute_hypothetical_max_host_failures_to_tolerate(this.Session, config);
// long currentNtol = this.Pool.ha_host_failures_to_tolerate;
// if (currentNtol < max)
// {
// bool doit = false;
// Program.Invoke(Program.MainWindow, delegate()
// {
// string poolName = Helpers.TrimStringIfRequired(Helpers.GetName(this.Pool), 500);
// string msg = String.Format(Messages.HA_OPT_ENABLE_NTOL_RAISE_QUERY, poolName, currentNtol, max);
// if (new ThreeButtonDialog(
// new ThreeButtonDialog.Details(null, msg, Messages.HIGH_AVAILABILITY),
// ThreeButtonDialog.ButtonYes,
// new ThreeButtonDialog.TBDButton(Messages.NO, DialogResult.No, ThreeButtonDialog.ButtonType.CANCEL, true)).ShowDialog(Program.MainWindow) == DialogResult.Yes)
// {
// doit = true;
// }
// });
// if (doit)
// {
// XenAPI.Pool.set_ha_host_failures_to_tolerate(this.Session, this.Pool.opaque_ref, max);
// }
// }
// }
//}
*/
}
}