mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-12-27 04:39:52 +01:00
299 lines
14 KiB
C#
299 lines
14 KiB
C#
|
/* Copyright (c) Citrix Systems Inc.
|
|||
|
* All rights reserved.
|
|||
|
*
|
|||
|
* 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 XenAdmin.Core;
|
|||
|
using XenAdmin.Wlb;
|
|||
|
using XenAPI;
|
|||
|
|
|||
|
using XenAdmin.Actions.HostActions;
|
|||
|
|
|||
|
namespace XenAdmin.Actions
|
|||
|
{
|
|||
|
public class EvacuateHostAction:HostAbstractAction
|
|||
|
{
|
|||
|
|
|||
|
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
|||
|
private readonly Dictionary<XenRef<VM>, string[]> _hostRecommendations;
|
|||
|
private readonly Host _newMaster;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
///
|
|||
|
/// NOTE: when creating new HostActions, add Program.MainWindow.action_Completed to the completed event,
|
|||
|
/// and call Program.MainWindow.UpdateToolbars() after starting the action. This ensures the toolbar
|
|||
|
/// buttons are disabled while the action is in progress.
|
|||
|
/// </summary>
|
|||
|
/// <param name="connection"></param>
|
|||
|
/// <param name="kind"></param>
|
|||
|
/// <param name="host">Must not be null.</param>
|
|||
|
/// <param name="acceptNTolChanges"></param>
|
|||
|
/// <param name="acceptNTolChangesOnEnable"></param>
|
|||
|
public EvacuateHostAction(Host host, Host newMaster, Dictionary<XenRef<VM>, String[]> hostRecommendations, Func<HostAbstractAction, Pool, long, long, bool> acceptNTolChanges, Func<Pool, Host, long, long, bool> acceptNTolChangesOnEnable)
|
|||
|
: base(host.Connection, null, Messages.HOST_EVACUATE, acceptNTolChanges, acceptNTolChangesOnEnable)
|
|||
|
{
|
|||
|
if (host == null)
|
|||
|
throw new ArgumentNullException("host");
|
|||
|
Host = host;
|
|||
|
_newMaster = newMaster;
|
|||
|
_hostRecommendations = hostRecommendations;
|
|||
|
}
|
|||
|
|
|||
|
protected override void Run()
|
|||
|
{
|
|||
|
bool isMaster = Host.IsMaster();
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
this.Description = String.Format(Messages.HOSTACTION_EVACUATING, Helpers.GetName(Host));
|
|||
|
|
|||
|
// call "MaybeReduceNtolBeforeOp",
|
|||
|
// if currentNtol > targetNtol, asks users whether to decrease ntol (since disable will fail if it would cause HA overcommit),
|
|||
|
// if users don't cancel, puts MAINTENANCE_MODE=true into the host's other_config,
|
|||
|
// then does a Host.disable using XenAPI.Host.async_disable.
|
|||
|
// Parameters 0 and 20 are for scaling low and high values for progress bar
|
|||
|
Disable(0, 20);
|
|||
|
|
|||
|
|
|||
|
bool tryAgain = false;
|
|||
|
|
|||
|
// WLB: use non-wlb evcaute when wlb is not enabled
|
|||
|
if (Helpers.WlbEnabled(Host.Connection))
|
|||
|
{
|
|||
|
// WLB: get wlb evacuate recommendations
|
|||
|
//Dictionary<XenRef<VM>, String[]> hostRecommendations = XenAPI.Host.retrieve_wlb_evacuate_recommendations(Session, Host.opaque_ref);
|
|||
|
|
|||
|
if (_hostRecommendations != null && _hostRecommendations.Count > 0)
|
|||
|
{
|
|||
|
List<string> error = new List<string>();
|
|||
|
|
|||
|
// WLB: continue only if there are no errors in wlb evacuate recommendations
|
|||
|
if (NoRecommendationError(_hostRecommendations, out error))
|
|||
|
{
|
|||
|
int start = 20;
|
|||
|
int each = (isMaster ? 80 : 90) / _hostRecommendations.Count;
|
|||
|
|
|||
|
IEnumerable<WlbHostEvacuationRecommendation> sortedRecommendations = SortedHostRecommendations(_hostRecommendations);
|
|||
|
|
|||
|
foreach (WlbHostEvacuationRecommendation rec in sortedRecommendations)
|
|||
|
{
|
|||
|
if (string.Compare(rec.Label, "wlb", true) == 0)
|
|||
|
{
|
|||
|
Host toHost = Host.Connection.Cache.Find_By_Uuid<Host>(rec.HostUuid);
|
|||
|
if ((Session.Connection.Resolve(rec.Vm)).is_control_domain)
|
|||
|
{
|
|||
|
if (!toHost.IsLive)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
new HostPowerOnAction(toHost).RunExternal(Session);
|
|||
|
}
|
|||
|
catch (Exception)
|
|||
|
{
|
|||
|
Description = string.Format(Messages.ACTION_HOST_START_FAILED, Helpers.GetName(toHost));
|
|||
|
}
|
|||
|
if (!toHost.enabled)
|
|||
|
{
|
|||
|
RelatedTask = XenAPI.Host.async_enable(Session, toHost.opaque_ref);
|
|||
|
PollToCompletion(start, start);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// sometimes, the SR is not available after host power-on, so give three try
|
|||
|
int retry = 3;
|
|||
|
while (retry > 0)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
RelatedTask = XenAPI.VM.async_live_migrate(Session, rec.Vm.opaque_ref, toHost.opaque_ref);
|
|||
|
PollToCompletion(start, start + each);
|
|||
|
start += each;
|
|||
|
break;
|
|||
|
}
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
log.Error(ex.Message, ex);
|
|||
|
|
|||
|
// sleep for 10s, then try again
|
|||
|
System.Threading.Thread.Sleep(10 * 1000);
|
|||
|
retry--;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// WLB: don't evacuate when there is errors in wlb evacuate recommendations
|
|||
|
throw new XenAPI.Failure(error);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// WLB: when there is no wlb evacuate recommendations, fall through to use the non-WLB evacuate.
|
|||
|
tryAgain = true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (!Helpers.WlbEnabled(Host.Connection) || tryAgain)
|
|||
|
{
|
|||
|
RelatedTask = XenAPI.Host.async_evacuate(Session, Host.opaque_ref);
|
|||
|
PollToCompletion(20, isMaster ? 80 : 90);
|
|||
|
}
|
|||
|
|
|||
|
this.Description = String.Format(Messages.HOSTACTION_EVACUATED, Helpers.GetName(Host));
|
|||
|
|
|||
|
if (isMaster && _newMaster != null)
|
|||
|
{
|
|||
|
// Signal to the connection that the master is going to change underneath us.
|
|||
|
Connection.MasterMayChange = true;
|
|||
|
|
|||
|
//Transition to new master
|
|||
|
this.Description = String.Format(Messages.HOSTACTION_TRANSITIONING_NEW_MASTER, Helpers.GetName(_newMaster));
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
RelatedTask = XenAPI.Pool.async_designate_new_master(Session, _newMaster.opaque_ref);
|
|||
|
PollToCompletion(80, 90);
|
|||
|
}
|
|||
|
catch
|
|||
|
{
|
|||
|
// If theres an error during designate new master, clear flag to prevent leak.
|
|||
|
Connection.MasterMayChange = false;
|
|||
|
throw;
|
|||
|
}
|
|||
|
|
|||
|
this.Description = String.Format(Messages.HOSTACTION_TRANSITIONED_NEW_MASTER, Helpers.GetName(_newMaster));
|
|||
|
}
|
|||
|
|
|||
|
this.PercentComplete = 100;
|
|||
|
}
|
|||
|
catch (Exception e)
|
|||
|
{
|
|||
|
log.ErrorFormat("There was an exception putting the host {0} into maintenance mode. Removing other_config key.", Host.opaque_ref);
|
|||
|
log.Error(e, e);
|
|||
|
Enable(isMaster ? 80 : 90, 100, false);
|
|||
|
throw;
|
|||
|
}
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Check whether there is error in wlb evacaute recommendations, return true if there is no errors
|
|||
|
/// </summary>
|
|||
|
/// <param name="hostRecommendations">evcaute recommendations</param>
|
|||
|
/// <param name="error">output error if there is at least one</param>
|
|||
|
/// <returns>false if there is at least one error in wlbevacute recommendations</returns>
|
|||
|
private static bool NoRecommendationError(Dictionary<XenRef<VM>, String[]> hostRecommendations, out List<string> error)
|
|||
|
{
|
|||
|
bool noError = true;
|
|||
|
error = new List<string>();
|
|||
|
|
|||
|
foreach (KeyValuePair<XenRef<VM>, string[]> rec in hostRecommendations)
|
|||
|
{
|
|||
|
if (string.Compare(rec.Value[0].Trim(), "wlb", true) != 0)
|
|||
|
{
|
|||
|
error.Add(rec.Value[0]);
|
|||
|
error.Add(rec.Value[1]);
|
|||
|
noError = false;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
return noError;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Puts MAINTENANCE_MODE=true into the host's other_config, then does a Host.disable.
|
|||
|
/// If appropriate, first asks the user if they want to decrease ntol (since disable will fail if it would cause HA overcommit).
|
|||
|
/// </summary>
|
|||
|
/// <param name="start"></param>
|
|||
|
/// <param name="finish"></param>
|
|||
|
private void Disable(int start, int finish)
|
|||
|
{
|
|||
|
// ask users if they want to decrease ntol, may throw CancelledException if the user says no.
|
|||
|
MaybeReduceNtolBeforeOp(HostActionKind.Evacuate);
|
|||
|
|
|||
|
RelatedTask = XenAPI.Host.async_disable(Session, Host.opaque_ref);
|
|||
|
|
|||
|
PollToCompletion(start, finish);
|
|||
|
|
|||
|
XenAPI.Host.remove_from_other_config(Session, Host.opaque_ref, XenAPI.Host.MAINTENANCE_MODE);
|
|||
|
XenAPI.Host.add_to_other_config(Session, Host.opaque_ref, XenAPI.Host.MAINTENANCE_MODE, "true");
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Sort the WlbHostEvacuationRecommendation, so host powerOn always on the top, vmMoves is after host powerOn.
|
|||
|
/// </summary>
|
|||
|
/// <param name="hostRecommendations">A instance of raw WlbHostEvacuationRecommendation dictionary.</param>
|
|||
|
/// <returns>A list of WlbHostEvacuationRecommendation.</returns>
|
|||
|
private IEnumerable<WlbHostEvacuationRecommendation> SortedHostRecommendations(Dictionary<XenRef<VM>, String[]> hostRecommendations)
|
|||
|
{
|
|||
|
List<WlbHostEvacuationRecommendation> hostPowerOnRecs = new List<WlbHostEvacuationRecommendation>();
|
|||
|
List<WlbHostEvacuationRecommendation> vmMoveRecs = new List<WlbHostEvacuationRecommendation>();
|
|||
|
List<WlbHostEvacuationRecommendation> sortedRecs = new List<WlbHostEvacuationRecommendation>();
|
|||
|
|
|||
|
foreach (KeyValuePair<XenRef<VM>, string[]> rec in hostRecommendations)
|
|||
|
{
|
|||
|
Host toHost = Host.Connection.Cache.Find_By_Uuid<Host>(rec.Value[(int)RecProperties.ToHost]);
|
|||
|
if (string.Compare(rec.Value[(int)RecProperties.WLB], "wlb", true) == 0)
|
|||
|
{
|
|||
|
WlbHostEvacuationRecommendation hostEvacuatRec = new WlbHostEvacuationRecommendation();
|
|||
|
hostEvacuatRec.Label = rec.Value[(int)RecProperties.WLB];
|
|||
|
hostEvacuatRec.Vm = rec.Key;
|
|||
|
hostEvacuatRec.HostUuid = rec.Value[(int)RecProperties.ToHost];
|
|||
|
if ((Session.Connection.Resolve(rec.Key)).is_control_domain && !toHost.IsLive)
|
|||
|
{
|
|||
|
hostPowerOnRecs.Add(hostEvacuatRec);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
vmMoveRecs.Add(hostEvacuatRec);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
foreach (WlbHostEvacuationRecommendation hpoRec in hostPowerOnRecs)
|
|||
|
{
|
|||
|
sortedRecs.Add(hpoRec);
|
|||
|
}
|
|||
|
|
|||
|
foreach (WlbHostEvacuationRecommendation vmRec in vmMoveRecs)
|
|||
|
{
|
|||
|
sortedRecs.Add(vmRec);
|
|||
|
}
|
|||
|
|
|||
|
return sortedRecs;
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
}
|