CP-18012: Use extended precheck information in Update Wizard

This commit implements the check for LiveUpdate capability of a patch. If
the patch is completely live-updatable, XenCenter will not restart the
host (but will do further checks once it has finished installing the
update - separate ticket)

Signed-off-by: Gabor Apati-Nagy <gabor.apati-nagy@citrix.com>
This commit is contained in:
Gabor Apati-Nagy 2016-08-23 18:16:33 +01:00
parent 90c4e79c8c
commit 7ee2e605ee
8 changed files with 122 additions and 22 deletions

View File

@ -40,12 +40,20 @@ using XenAdmin.Diagnostics.Problems.VMProblem;
using XenAdmin.Diagnostics.Problems.HostProblem;
using System.Linq;
using XenAdmin.Wizards.PatchingWizard;
namespace XenAdmin.Diagnostics.Checks
{
public class AssertCanEvacuateCheck : Check
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private readonly Dictionary<string, LivePatchCode> livePatchCodesByHost;
public AssertCanEvacuateCheck(Host host, Dictionary<string, LivePatchCode> livePatchCodesByHost)
: base(host)
{
this.livePatchCodesByHost = livePatchCodesByHost;
}
public AssertCanEvacuateCheck(Host host)
: base(host)
@ -54,6 +62,13 @@ namespace XenAdmin.Diagnostics.Checks
protected List<Problem> CheckHost()
{
// when livepatching is available, no restart is expected, so this check is not needed
if (livePatchCodesByHost != null && livePatchCodesByHost.ContainsKey(Host.uuid) && livePatchCodesByHost[Host.uuid] == LivePatchCode.PATCH_PRECHECK_LIVEPATCH_COMPLETE)
{
log.DebugFormat("Check not needed for host {0}, because pool_patch.Precheck() returned PATCH_PRECHECK_LIVEPATCH_COMPLETE for update.", Host);
return new List<Problem>();
}
var problems = new List<Problem>();
var restrictMigration = Helpers.FeatureForbidden(Host.Connection, Host.RestrictIntraPoolMigrate);

View File

@ -39,6 +39,7 @@ using System.Text.RegularExpressions;
using System.Xml;
using System.Collections.Generic;
using XenAdmin.Actions;
using XenAdmin.Wizards.PatchingWizard;
namespace XenAdmin.Diagnostics.Checks
@ -48,12 +49,15 @@ namespace XenAdmin.Diagnostics.Checks
private readonly Pool_patch _patch;
private static Regex PrecheckErrorRegex = new Regex("(<error).+(</error>)");
private static Regex LivePatchResponseRegex = new Regex("(<livepatch).+(</livepatch>)");
private readonly Dictionary<string, LivePatchCode> livePatchCodesByHost;
public PatchPrecheckCheck(Host host, Pool_patch patch)
public PatchPrecheckCheck(Host host, Pool_patch patch, Dictionary<string, LivePatchCode> livePatchCodesByHost)
: base(host)
{
_patch = patch;
this.livePatchCodesByHost = livePatchCodesByHost;
}
@ -78,7 +82,13 @@ namespace XenAdmin.Diagnostics.Checks
try
{
return FindProblem(Pool_patch.precheck(session, Patch.opaque_ref, Host.opaque_ref));
var result = Pool_patch.precheck(session, Patch.opaque_ref, Host.opaque_ref);
var livePatchCode = TryToFindLivePatchCode(result);
if (livePatchCodesByHost != null)
livePatchCodesByHost[Host.uuid] = livePatchCode;
return FindProblem(result);
}
catch (Failure f)
@ -98,6 +108,38 @@ namespace XenAdmin.Diagnostics.Checks
}
}
private LivePatchCode TryToFindLivePatchCode(string result)
{
LivePatchCode livePatchCodeResult = LivePatchCode.UNKNOWN;
if (Helpers.ElyOrGreater(Host.Connection))
{
result = result.Replace("\n", "");
if (LivePatchResponseRegex.IsMatch(result))
{
var m = LivePatchResponseRegex.Match(result);
var doc = new XmlDocument();
doc.LoadXml(m.ToString());
var firstNode = doc.FirstChild;
if (firstNode == null)
return LivePatchCode.UNKNOWN;
var livePatchCodeNode = firstNode.Attributes["code"];
if (livePatchCodeNode == null)
return LivePatchCode.UNKNOWN;
if (Enum.TryParse(livePatchCodeNode.Value, out livePatchCodeResult))
return livePatchCodeResult;
}
}
return LivePatchCode.UNKNOWN;
}
public override string Description
{
get { return Messages.SERVER_SIDE_CHECK_DESCRIPTION; }

View File

@ -173,6 +173,8 @@ namespace XenAdmin.Wizards.PatchingWizard
else if (prevPageType == typeof(PatchingWizard_PrecheckPage))
{
PatchingWizard_PatchingPage.ProblemsResolvedPreCheck = PatchingWizard_PrecheckPage.ProblemsResolvedPreCheck;
PatchingWizard_PatchingPage.LivePatchCodesByHost = PatchingWizard_PrecheckPage.LivePatchCodesByHost;
PatchingWizard_ModePage.LivePatchCodesByHost = PatchingWizard_PrecheckPage.LivePatchCodesByHost;
}
}
@ -323,4 +325,12 @@ namespace XenAdmin.Wizards.PatchingWizard
base.FinishWizard();
}
}
public enum LivePatchCode
{
UNKNOWN,
PATCH_PRECHECK_LIVEPATCH_COMPLETE, // An applicable live patch exists for every required component
PATCH_PRECHECK_LIVEPATCH_INCOMPLETE, // An applicable live patch exists but it is not sufficient
PATCH_PRECHECK_LIVEPATCH_MISSING, // There is no applicable live patch
}
}

View File

@ -38,9 +38,9 @@ namespace XenAdmin.Wizards.PatchingWizard
{
public class PatchingWizardModeGuidanceBuilder
{
public static string ModeRetailPatch(List<Host> servers, Pool_patch patch)
public static string ModeRetailPatch(List<Host> servers, Pool_patch patch, Dictionary<string, LivePatchCode> LivePatchCodesByHost)
{
return Build(servers, patch != null ? patch.after_apply_guidance : new List<after_apply_guidance>());
return Build(servers, patch != null ? patch.after_apply_guidance : new List<after_apply_guidance>(), LivePatchCodesByHost);
}
public static string ModeSuppPack(List<Host> servers)
@ -50,11 +50,20 @@ namespace XenAdmin.Wizards.PatchingWizard
}
private static string Build(List<Host> servers, List<after_apply_guidance> guidance)
{
return Build(servers, guidance);
}
private static string Build(List<Host> servers, List<after_apply_guidance> guidance, Dictionary<string, LivePatchCode> LivePatchCodesByHost)
{
StringBuilder sbLog = new StringBuilder();
foreach (after_apply_guidance guide in guidance)
{
if (guide == after_apply_guidance.restartHost
&& (LivePatchCodesByHost != null && servers.TrueForAll(h => LivePatchCodesByHost.ContainsKey(h.uuid) && LivePatchCodesByHost[h.uuid] == LivePatchCode.PATCH_PRECHECK_LIVEPATCH_COMPLETE)))
break;
sbLog.AppendLine(GetGuideMessage(guide));
switch (guide)
@ -63,6 +72,9 @@ namespace XenAdmin.Wizards.PatchingWizard
case after_apply_guidance.restartXAPI:
foreach (Host host in servers)
{
if (LivePatchCodesByHost != null && LivePatchCodesByHost.ContainsKey(host.uuid) && LivePatchCodesByHost[host.uuid] == LivePatchCode.PATCH_PRECHECK_LIVEPATCH_COMPLETE)
continue;
if (host.IsMaster())
sbLog.AppendFormat("\t{0} ({1})\r\n", host.Name, Messages.MASTER);
else
@ -89,7 +101,7 @@ namespace XenAdmin.Wizards.PatchingWizard
}
}
if (guidance.Count == 0)
if (sbLog.Length == 0)
sbLog.Append(Messages.PATCHINGWIZARD_MODEPAGE_NOACTION);
return sbLog.ToString();

View File

@ -77,6 +77,12 @@ namespace XenAdmin.Wizards.PatchingWizard
{
return Messages.UPDATES_WIZARD_APPLY_UPDATE;
}
public Dictionary<string, LivePatchCode> LivePatchCodesByHost
{
get;
set;
}
public override void PageLoaded(PageLoadedDirection direction)
{
@ -88,7 +94,7 @@ namespace XenAdmin.Wizards.PatchingWizard
{
case UpdateType.NewRetail:
case UpdateType.Existing:
textBoxLog.Text = PatchingWizardModeGuidanceBuilder.ModeRetailPatch(SelectedServers, Patch);
textBoxLog.Text = PatchingWizardModeGuidanceBuilder.ModeRetailPatch(SelectedServers, Patch, LivePatchCodesByHost);
AutomaticRadioButton.Enabled = true;
AutomaticRadioButton.Checked = true;
break;

View File

@ -50,6 +50,12 @@ namespace XenAdmin.Wizards.PatchingWizard
{
protected static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public Dictionary<string, LivePatchCode> LivePatchCodesByHost
{
get;
set;
}
private AsyncAction actionManualMode = null;
private bool _thisPageHasBeenCompleted = false;
private BackgroundWorker actionsWorker = null;
@ -318,7 +324,8 @@ namespace XenAdmin.Wizards.PatchingWizard
actions.Add(new ApplyPatchPlanAction(host, patch));
if (patch.after_apply_guidance.Contains(after_apply_guidance.restartHost))
if (patch.after_apply_guidance.Contains(after_apply_guidance.restartHost)
&& !(LivePatchCodesByHost !=null && LivePatchCodesByHost.ContainsKey(host.uuid) && LivePatchCodesByHost[host.uuid] == LivePatchCode.PATCH_PRECHECK_LIVEPATCH_COMPLETE))
{
actions.Add(new EvacuateHostPlanAction(host));
actions.Add(new RebootHostPlanAction(host));

View File

@ -303,9 +303,16 @@ namespace XenAdmin.Wizards.PatchingWizard
}
}
public Dictionary<string, LivePatchCode> LivePatchCodesByHost
{
get;
set;
}
protected virtual List<KeyValuePair<string, List<Check>>> GenerateChecks(Pool_patch patch)
{
List<KeyValuePair<string, List<Check>>> checks = new List<KeyValuePair<string, List<Check>>>();
LivePatchCodesByHost = new Dictionary<string, LivePatchCode>();
//HostLivenessCheck checks
checks.Add(new KeyValuePair<string, List<Check>>(Messages.CHECKING_HOST_LIVENESS_STATUS, new List<Check>()));
@ -324,19 +331,6 @@ namespace XenAdmin.Wizards.PatchingWizard
checkGroup.Add(new HAOffCheck(host));
}
//Checking can evacuate host
//CA-97061 - evacuate host -> suspended VMs. This is only needed for restartHost
//Also include this check for the supplemental packs (patch == null), as their guidance is restartHost
if (patch == null || patch.after_apply_guidance.Contains(after_apply_guidance.restartHost))
{
checks.Add(new KeyValuePair<string, List<Check>>(Messages.CHECKING_CANEVACUATE_STATUS, new List<Check>()));
checkGroup = checks[checks.Count - 1].Value;
foreach (Host host in SelectedServers)
{
checkGroup.Add(new AssertCanEvacuateCheck(host));
}
}
//PBDsPluggedCheck
checks.Add(new KeyValuePair<string, List<Check>>(Messages.CHECKING_STORAGE_CONNECTIONS_STATUS, new List<Check>()));
checkGroup = checks[checks.Count - 1].Value;
@ -354,9 +348,23 @@ namespace XenAdmin.Wizards.PatchingWizard
{
List<Pool_patch> poolPatches = new List<Pool_patch>(host.Connection.Cache.Pool_patches);
Pool_patch poolPatchFromHost = poolPatches.Find(otherPatch => string.Equals(otherPatch.uuid, patch.uuid, StringComparison.OrdinalIgnoreCase));
checkGroup.Add(new PatchPrecheckCheck(host, poolPatchFromHost));
checkGroup.Add(new PatchPrecheckCheck(host, poolPatchFromHost, LivePatchCodesByHost));
}
}
//Checking can evacuate host
//CA-97061 - evacuate host -> suspended VMs. This is only needed for restartHost
//Also include this check for the supplemental packs (patch == null), as their guidance is restartHost
if (patch == null || patch.after_apply_guidance.Contains(after_apply_guidance.restartHost))
{
checks.Add(new KeyValuePair<string, List<Check>>(Messages.CHECKING_CANEVACUATE_STATUS, new List<Check>()));
checkGroup = checks[checks.Count - 1].Value;
foreach (Host host in SelectedServers)
{
checkGroup.Add(new AssertCanEvacuateCheck(host, LivePatchCodesByHost));
}
}
return checks;
}

View File

@ -73,7 +73,7 @@ namespace XenAdminTests.WizardTests
host.Setup(h => h.IsMaster()).Returns(true);
host.Setup(h => h.Name).Returns("MyHost");
patch.Setup(p => p.after_apply_guidance).Returns(new List<after_apply_guidance> { guidance });
msg = PatchingWizardModeGuidanceBuilder.ModeRetailPatch(new List<Host> { host.Object }, patch.Object);
msg = PatchingWizardModeGuidanceBuilder.ModeRetailPatch(new List<Host> { host.Object }, patch.Object, new Dictionary<string,LivePatchCode>());
return patch;
}
}