mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2025-01-12 13:32:27 +01:00
ada6f7dac7
* Simplification: use auto-properties. * Removed unnecessary null check. Converted private methods to linq method chain. * Removed unused file. Added null check. * Corrected property names as it was difficult to distinguish between the alert, the patch and the filepath. * Removed duplicate lock. * CA-296490: Rewrote the logic uploading single updates so as to facilitate uploading to multiple pools in parallel. * Corrections as per code review. * Account for the case of uploading from disk legacy updates that do not correspond to an update alert. Removed unfriendly error messages (the failure is written out in the logs anyway). * Wizard test corrections and some modernising: - Only call cancel on the last page if the test allows us to. - Do finish the UpdatesWizard test. - Finish the RunWizardKeyboardTests using key strokes instead of button clicks. - Ensure the Cancel/Previous buttons do not have focus when sending an Enter stroke. - Use optional parameters in the WizardTest constructor and string interpolation. * Renamed badly named property. * Improved code cloning VMs by assigning new opaque_refs to cloned VBDs. Compacted test object provider: use optional parameters instead of method overloads. * Modifications to prevent multiple update of enabled state and ensure the wizard buttons are updated last. Signed-off-by: Konstantina Chremmou <konstantina.chremmou@citrix.com>
649 lines
26 KiB
C#
649 lines
26 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 System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.Reflection;
|
|
using log4net;
|
|
using XenAdmin.Controls;
|
|
using XenAdmin.Wizards.PatchingWizard.PlanActions;
|
|
using XenAPI;
|
|
using System.Linq;
|
|
using XenAdmin.Core;
|
|
using System.Text;
|
|
using System.Windows.Forms;
|
|
using XenAdmin.Diagnostics.Problems;
|
|
using XenAdmin.Dialogs;
|
|
using XenAdmin.Wizards.RollingUpgradeWizard.PlanActions;
|
|
|
|
|
|
namespace XenAdmin.Wizards.PatchingWizard
|
|
{
|
|
public enum Status { NotStarted, Started, Cancelled, Completed }
|
|
public abstract partial class AutomatedUpdatesBasePage : XenTabPage
|
|
{
|
|
protected static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
protected bool _thisPageIsCompleted;
|
|
|
|
public List<Problem> PrecheckProblemsActuallyResolved { private get; set; }
|
|
public List<Pool> SelectedPools { private get; set; }
|
|
public bool ApplyUpdatesToNewVersion { get; set; }
|
|
public Status Status { get; private set; }
|
|
|
|
protected bool IsSuccess
|
|
{
|
|
get { return _thisPageIsCompleted && !failedWorkers.Any(); }
|
|
}
|
|
|
|
private List<UpdateProgressBackgroundWorker> backgroundWorkers = new List<UpdateProgressBackgroundWorker>();
|
|
private List<UpdateProgressBackgroundWorker> failedWorkers = new List<UpdateProgressBackgroundWorker>();
|
|
|
|
private List<HostUpdateMapping> patchMappings = new List<HostUpdateMapping>();
|
|
protected List<string> hostsThatWillRequireReboot = new List<string>();
|
|
public Dictionary<XenServerPatch, string> AllDownloadedPatches = new Dictionary<XenServerPatch, string>();
|
|
|
|
public AutomatedUpdatesBasePage()
|
|
{
|
|
InitializeComponent();
|
|
panel1.Visible = false;
|
|
}
|
|
|
|
#region XenTabPage overrides
|
|
public override bool EnablePrevious()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override bool EnableNext()
|
|
{
|
|
return _thisPageIsCompleted;
|
|
}
|
|
|
|
private bool _cancelEnabled = true;
|
|
public override bool EnableCancel()
|
|
{
|
|
return _cancelEnabled;
|
|
}
|
|
|
|
public override void PageCancelled()
|
|
{
|
|
if (!_thisPageIsCompleted)
|
|
{
|
|
Status = Status.Cancelled;
|
|
backgroundWorkers.ForEach(bgw => bgw.CancelAsync());
|
|
}
|
|
|
|
base.PageCancelled();
|
|
}
|
|
|
|
protected override void PageLoadedCore(PageLoadedDirection direction)
|
|
{
|
|
if (_thisPageIsCompleted)
|
|
return;
|
|
|
|
panel1.Visible = false;
|
|
Status = Status.NotStarted;
|
|
labelTitle.Text = BlurbText();
|
|
|
|
if (!StartUpgradeWorkers())
|
|
{
|
|
Status = Status.Completed;
|
|
_thisPageIsCompleted = true;
|
|
OnPageUpdated();
|
|
}
|
|
else
|
|
{
|
|
Status = Status.Started;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Virtual members
|
|
|
|
protected abstract string BlurbText();
|
|
protected abstract string SuccessMessageOnCompletion(bool multiplePools);
|
|
protected abstract string FailureMessageOnCompletion(bool multiplePools);
|
|
protected abstract string SuccessMessagePerPool(Pool pool);
|
|
protected abstract string FailureMessagePerPool(bool multipleErrors);
|
|
protected abstract string UserCancellationMessage();
|
|
|
|
protected abstract List<HostPlan> GenerateHostPlans(Pool pool, out List<Host> applicableHosts);
|
|
|
|
protected virtual bool SkipInitialPlanActions(Host host)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
protected virtual void DoAfterInitialPlanActions(UpdateProgressBackgroundWorker bgw, Host host, List<Host> hosts) { }
|
|
#endregion
|
|
|
|
#region background workers
|
|
private bool StartUpgradeWorkers()
|
|
{
|
|
//reset the background workers
|
|
backgroundWorkers = new List<UpdateProgressBackgroundWorker>();
|
|
failedWorkers = new List<UpdateProgressBackgroundWorker>();
|
|
bool atLeastOneWorkerStarted = false;
|
|
|
|
foreach (var pool in SelectedPools)
|
|
{
|
|
List<Host> applicableHosts;
|
|
var planActions = GenerateHostPlans(pool, out applicableHosts);
|
|
|
|
var finalActions = new List<PlanAction>();
|
|
|
|
if (PrecheckProblemsActuallyResolved != null)
|
|
{
|
|
//add a revert pre-check action for this pool
|
|
var curPool = pool;
|
|
var problemsToRevert = PrecheckProblemsActuallyResolved.Where(a =>
|
|
a.SolutionAction != null && Helpers.GetPoolOfOne(a.SolutionAction.Connection).Equals(curPool)).ToList();
|
|
|
|
if (problemsToRevert.Count > 0)
|
|
finalActions.Add(new UnwindProblemsAction(problemsToRevert, pool.Connection));
|
|
}
|
|
|
|
if (planActions.Count > 0)
|
|
{
|
|
atLeastOneWorkerStarted = true;
|
|
StartNewWorker(pool, planActions, finalActions);
|
|
}
|
|
}
|
|
|
|
return atLeastOneWorkerStarted;
|
|
}
|
|
|
|
private void StartNewWorker(Pool pool, List<HostPlan> planActions, List<PlanAction> finalActions)
|
|
{
|
|
var bgw = new UpdateProgressBackgroundWorker(pool, planActions, finalActions)
|
|
{
|
|
WorkerReportsProgress = true,
|
|
WorkerSupportsCancellation = true
|
|
};
|
|
bgw.DoWork += WorkerDoWork;
|
|
bgw.ProgressChanged += WorkerProgressChanged;
|
|
bgw.RunWorkerCompleted += WorkerCompleted;
|
|
backgroundWorkers.Add(bgw);
|
|
bgw.RunWorkerAsync();
|
|
}
|
|
|
|
private void WorkerProgressChanged(object sender, ProgressChangedEventArgs e)
|
|
{
|
|
var bgw = sender as UpdateProgressBackgroundWorker;
|
|
if (bgw == null)
|
|
return;
|
|
|
|
if (!bgw.CancellationPending)
|
|
{
|
|
PlanAction action = (PlanAction)e.UserState;
|
|
if (action != null)
|
|
{
|
|
if (!action.IsComplete)
|
|
{
|
|
if (!bgw.InProgressActions.Contains(action))
|
|
bgw.InProgressActions.Add(action);
|
|
}
|
|
else
|
|
{
|
|
if (!bgw.DoneActions.Contains(action))
|
|
bgw.DoneActions.Add(action);
|
|
bgw.InProgressActions.Remove(action);
|
|
|
|
if (action.Error == null)
|
|
{
|
|
// remove the successful action from the cleanup actions (we are running the cleanup actions in case of failures or if the user cancelled the process, but we shouldn't re-run the actions that have already been run)
|
|
bgw.CleanupActions.Remove(action);
|
|
}
|
|
else
|
|
{
|
|
if (!failedWorkers.Contains(bgw))
|
|
failedWorkers.Add(bgw);
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdateStatus();
|
|
}
|
|
}
|
|
|
|
private void UpdateStatus()
|
|
{
|
|
var newVal = backgroundWorkers.Sum(b => b.PercentComplete) / backgroundWorkers.Count;
|
|
if (newVal < 0)
|
|
newVal = 0;
|
|
else if (newVal > 100)
|
|
newVal = 100;
|
|
progressBar.Value = (int)newVal;
|
|
|
|
var allsb = new StringBuilder();
|
|
|
|
foreach (var bgw in backgroundWorkers)
|
|
{
|
|
int bgwErrorCount = 0;
|
|
int bgwCancellationCount = 0;
|
|
var sb = new StringBuilder();
|
|
var errorSb = new StringBuilder();
|
|
|
|
if (!string.IsNullOrEmpty(bgw.Name))
|
|
sb.AppendLine(string.Format("{0}:", bgw.Name));
|
|
|
|
foreach (var pa in bgw.DoneActions)
|
|
{
|
|
pa.ProgressHistory.ForEach(step => sb.AppendIndented(step).AppendLine());
|
|
|
|
if (pa.Error != null)
|
|
{
|
|
if (pa.Error is CancelledException)
|
|
{
|
|
bgwCancellationCount++;
|
|
continue;
|
|
}
|
|
|
|
var innerEx = pa.Error.InnerException as Failure;
|
|
errorSb.AppendLine(innerEx == null ? pa.Error.Message : innerEx.Message);
|
|
|
|
if (pa.IsSkippable)
|
|
{
|
|
Debug.Assert(!string.IsNullOrEmpty(pa.Title));
|
|
errorSb.AppendLine(string.Format(Messages.RPU_WIZARD_ERROR_SKIP_MSG, pa.Title)).AppendLine();
|
|
}
|
|
|
|
bgwErrorCount++;
|
|
}
|
|
}
|
|
|
|
foreach (var pa in bgw.InProgressActions)
|
|
{
|
|
pa.ProgressHistory.ForEach(step => sb.AppendIndented(step).AppendLine());
|
|
}
|
|
|
|
sb.AppendLine();
|
|
|
|
if (bgwCancellationCount > 0)
|
|
{
|
|
sb.AppendIndented(UserCancellationMessage()).AppendLine();
|
|
}
|
|
else if (bgwErrorCount > 0)
|
|
{
|
|
sb.AppendIndented(FailureMessagePerPool(bgwErrorCount > 1)).AppendLine();
|
|
sb.AppendIndented(errorSb);
|
|
}
|
|
else if (!bgw.IsBusy)
|
|
{
|
|
sb.AppendIndented(SuccessMessagePerPool(bgw.Pool)).AppendLine();
|
|
}
|
|
|
|
sb.AppendLine();
|
|
allsb.Append(sb);
|
|
}
|
|
|
|
textBoxLog.Text = allsb.ToString();
|
|
textBoxLog.SelectionStart = textBoxLog.Text.Length;
|
|
textBoxLog.ScrollToCaret();
|
|
}
|
|
|
|
private void WorkerDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
|
|
{
|
|
var bgw = sender as UpdateProgressBackgroundWorker;
|
|
if (bgw == null)
|
|
return;
|
|
|
|
PlanAction action = null;
|
|
|
|
try
|
|
{
|
|
foreach (var hp in bgw.HostPlans)
|
|
{
|
|
var host = hp.Host;
|
|
|
|
// Step 1: InitialPlanActions (e.g. upgrade the host in the RPU case)
|
|
bgw.ProgressIncrement = bgw.InitialActionsIncrement(hp);
|
|
if (!SkipInitialPlanActions(host))
|
|
{
|
|
var initialActions = hp.InitialPlanActions;
|
|
|
|
foreach (var a in initialActions)
|
|
{
|
|
action = a;
|
|
bgw.RunPlanAction(action, ref doWorkEventArgs);
|
|
}
|
|
}
|
|
|
|
DoAfterInitialPlanActions(bgw, host, bgw.HostPlans.Select(h => h.Host).ToList());
|
|
|
|
// Step 2: UpdatesPlanActions (priority update action)
|
|
bgw.ProgressIncrement = bgw.UpdatesActionsIncrement(hp);
|
|
var planActions = hp.UpdatesPlanActions;
|
|
foreach (var a in planActions)
|
|
{
|
|
action = a;
|
|
bgw.RunPlanAction(action, ref doWorkEventArgs);
|
|
}
|
|
|
|
// Step 3: Rearrange DelayedActions
|
|
var suppPackPlanAction = (RpuUploadAndApplySuppPackPlanAction)planActions.FirstOrDefault(pa => pa is RpuUploadAndApplySuppPackPlanAction);
|
|
|
|
if (suppPackPlanAction != null)
|
|
{
|
|
foreach (var dpa in suppPackPlanAction.DelayedPlanActions)
|
|
{
|
|
if (!hp.DelayedPlanActions.Exists(a => a.GetType() == dpa.GetType()))
|
|
hp.DelayedPlanActions.Add(dpa);
|
|
}
|
|
}
|
|
|
|
var restartHostPlanAction = (RestartHostPlanAction)hp.DelayedPlanActions.FirstOrDefault(a => a is RestartHostPlanAction);
|
|
if (restartHostPlanAction != null)
|
|
{
|
|
if (restartHostPlanAction.SkipRestartHost(host))
|
|
{
|
|
log.Debug("Did not find patches requiring reboot (live patching succeeded)."
|
|
+ " Skipping scheduled restart.");
|
|
hp.DelayedPlanActions.RemoveAll(a => a is RestartHostPlanAction);
|
|
}
|
|
else
|
|
{
|
|
hp.DelayedPlanActions.RemoveAll(a => a is RestartAgentPlanAction);
|
|
}
|
|
}
|
|
|
|
// Step 4: DelayedActions
|
|
bgw.ProgressIncrement = bgw.DelayedActionsIncrement(hp);
|
|
// running delayed actions
|
|
var delayedActions = hp.DelayedPlanActions;
|
|
var restartActions = delayedActions.Where(a => a is RestartHostPlanAction).ToList();
|
|
|
|
foreach (var a in restartActions)
|
|
{
|
|
action = a;
|
|
bgw.RunPlanAction(action, ref doWorkEventArgs);
|
|
}
|
|
|
|
var otherActions = delayedActions.Where(a => !(a is RestartHostPlanAction)).ToList();
|
|
|
|
foreach (var a in otherActions)
|
|
{
|
|
action = a;
|
|
bgw.RunPlanAction(action, ref doWorkEventArgs);
|
|
}
|
|
}
|
|
|
|
// Step 5: FinalActions (eg. revert pre-checks)
|
|
bgw.ProgressIncrement = bgw.FinalActionsIncrement;
|
|
foreach (var a in bgw.FinalActions)
|
|
{
|
|
action = a;
|
|
bgw.RunPlanAction(action, ref doWorkEventArgs);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (action.Error == null)
|
|
action.Error = new Exception(Messages.ERROR_UNKNOWN);
|
|
|
|
log.ErrorFormat("Failed to carry out plan. {0} {1}", action.CurrentProgressStep, e);
|
|
doWorkEventArgs.Result = new Exception(action.CurrentProgressStep, e);
|
|
}
|
|
}
|
|
|
|
private void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
|
|
{
|
|
if (e.Cancelled)
|
|
{
|
|
Status = Status.Completed;
|
|
panel1.Visible = true;
|
|
labelError.Text = UserCancellationMessage();
|
|
pictureBox1.Image = Images.StaticImages.cancelled_action_16;
|
|
buttonRetry.Visible = buttonSkip.Visible = false;
|
|
_thisPageIsCompleted = true;
|
|
_cancelEnabled = false;
|
|
}
|
|
else
|
|
{
|
|
var bgw = sender as UpdateProgressBackgroundWorker;
|
|
var someWorkersCancelled = false;
|
|
|
|
if (bgw != null)
|
|
{
|
|
var workerSucceeded = true;
|
|
|
|
var failedAction = bgw.DoneActions.FirstOrDefault(a => a.Error != null && !(a.Error is CancelledException));
|
|
if (failedAction != null)
|
|
{
|
|
workerSucceeded = false;
|
|
if (failedAction.IsSkippable)
|
|
{
|
|
Debug.Assert(!string.IsNullOrEmpty(failedAction.Title));
|
|
bgw.FirstFailedSkippableAction = failedAction;
|
|
}
|
|
}
|
|
|
|
if (bgw.DoneActions.Any(a => a.Error is CancelledException))
|
|
{
|
|
workerSucceeded = false;
|
|
someWorkersCancelled = true;
|
|
}
|
|
|
|
if (workerSucceeded)
|
|
bgw.PercentComplete = 100;
|
|
}
|
|
//if all finished
|
|
if (backgroundWorkers.All(w => !w.IsBusy))
|
|
{
|
|
Status = Status.Completed;
|
|
panel1.Visible = true;
|
|
|
|
if (failedWorkers.Any())
|
|
{
|
|
labelError.Text = FailureMessageOnCompletion(backgroundWorkers.Count > 1);
|
|
pictureBox1.Image = Images.StaticImages._000_error_h32bit_16;
|
|
buttonRetry.Visible = true;
|
|
buttonSkip.Visible = false;
|
|
|
|
if (failedWorkers.Any(w => w.FirstFailedSkippableAction != null))
|
|
buttonSkip.Visible = true;
|
|
}
|
|
|
|
else if (someWorkersCancelled)
|
|
{
|
|
labelError.Text = UserCancellationMessage();
|
|
pictureBox1.Image = Images.StaticImages.cancelled_action_16;
|
|
buttonRetry.Visible = buttonSkip.Visible = false;
|
|
}
|
|
|
|
else
|
|
{
|
|
labelError.Text = SuccessMessageOnCompletion(backgroundWorkers.Count > 1);
|
|
pictureBox1.Image = Images.StaticImages._000_Tick_h32bit_16;
|
|
buttonRetry.Visible = buttonSkip.Visible = false;
|
|
}
|
|
|
|
_thisPageIsCompleted = true;
|
|
_cancelEnabled = false;
|
|
}
|
|
}
|
|
|
|
UpdateStatus();
|
|
OnPageUpdated();
|
|
}
|
|
#endregion
|
|
|
|
private void RetryFailedActions()
|
|
{
|
|
panel1.Visible = false;
|
|
failedWorkers.ForEach(bgw => bgw.FirstFailedSkippableAction = null);
|
|
|
|
var workers = new List<UpdateProgressBackgroundWorker>(failedWorkers);
|
|
failedWorkers.Clear();
|
|
|
|
foreach (var failedWorker in workers)
|
|
{
|
|
failedWorker.RunWorkerAsync();
|
|
}
|
|
|
|
_thisPageIsCompleted = false;
|
|
_cancelEnabled = true;
|
|
OnPageUpdated();
|
|
}
|
|
|
|
private void SkipFailedActions()
|
|
{
|
|
var skippableWorkers = failedWorkers.Where(w => w.FirstFailedSkippableAction != null).ToList();
|
|
var msg = string.Join(Environment.NewLine, skippableWorkers.Select(w => w.FirstFailedSkippableAction.Title));
|
|
|
|
using (var dlg = new ThreeButtonDialog(
|
|
new ThreeButtonDialog.Details(SystemIcons.Warning,
|
|
string.Format(skippableWorkers.Count > 1 ? Messages.MESSAGEBOX_SKIP_RPU_STEPS : Messages.MESSAGEBOX_SKIP_RPU_STEP, msg),
|
|
ParentForm != null ? ParentForm.Text : Messages.XENCENTER),
|
|
ThreeButtonDialog.ButtonYes,
|
|
ThreeButtonDialog.ButtonNo))
|
|
{
|
|
if (dlg.ShowDialog(this) != DialogResult.Yes)
|
|
return;
|
|
}
|
|
|
|
panel1.Visible = false;
|
|
|
|
foreach (var worker in skippableWorkers)
|
|
{
|
|
failedWorkers.Remove(worker);
|
|
worker.RunWorkerAsync();
|
|
}
|
|
|
|
_thisPageIsCompleted = false;
|
|
_cancelEnabled = true;
|
|
OnPageUpdated();
|
|
}
|
|
|
|
private void buttonRetry_Click(object sender, EventArgs e)
|
|
{
|
|
RetryFailedActions();
|
|
}
|
|
|
|
private void buttonSkip_Click(object sender, EventArgs e)
|
|
{
|
|
SkipFailedActions();
|
|
}
|
|
|
|
protected HostPlan GetUpdatePlanActionsForHost(Host host, List<Host> hosts, List<XenServerPatch> minimalPatches,
|
|
List<XenServerPatch> uploadedPatches, KeyValuePair<XenServerPatch, string> patchFromDisk, bool repatriateVms = true)
|
|
{
|
|
var patchSequence = Updates.GetPatchSequenceForHost(host, minimalPatches);
|
|
if (patchSequence == null)
|
|
return new HostPlan(host, null, null, null);
|
|
|
|
var planActionsPerHost = new List<PlanAction>();
|
|
var delayedActionsPerHost = new List<PlanAction>();
|
|
|
|
foreach (var patch in patchSequence)
|
|
{
|
|
if (!uploadedPatches.Contains(patch))
|
|
{
|
|
planActionsPerHost.Add(new DownloadPatchPlanAction(host.Connection, patch, AllDownloadedPatches, patchFromDisk));
|
|
planActionsPerHost.Add(new UploadPatchToMasterPlanAction(this, host.Connection, patch, patchMappings, AllDownloadedPatches, patchFromDisk, true));
|
|
uploadedPatches.Add(patch);
|
|
}
|
|
|
|
planActionsPerHost.Add(new PatchPrecheckOnHostPlanAction(host.Connection, patch, host, patchMappings, hostsThatWillRequireReboot));
|
|
planActionsPerHost.Add(new ApplyXenServerPatchPlanAction(host, patch, patchMappings));
|
|
|
|
var action = GetAfterApplyGuidanceAction(host, patch.after_apply_guidance);
|
|
if (action != null)
|
|
{
|
|
if (patch.GuidanceMandatory)
|
|
{
|
|
// if this update requires a mandatory toolstack restart and there is a pending host reboot in the delayed actions,
|
|
// then the pending reboot should be carried out instead
|
|
if (patch.after_apply_guidance == after_apply_guidance.restartXAPI && delayedActionsPerHost.Any(a => a is RestartHostPlanAction))
|
|
{
|
|
// replace the action with a host reboot action which will fall back to a toolstack restart if the reboot is not needed because the live patching succedeed
|
|
action = new RestartHostPlanAction(host, host.GetRunningVMs(), true, true, hostsThatWillRequireReboot);
|
|
}
|
|
|
|
planActionsPerHost.Add(action);
|
|
// remove all delayed actions of the same kind that has already been added
|
|
// (because this action is guidance-mandatory=true, therefore
|
|
// it will run immediately, making delayed ones obsolete)
|
|
delayedActionsPerHost.RemoveAll(a => action.GetType() == a.GetType());
|
|
}
|
|
else
|
|
{
|
|
// add the action if it's not already in the list
|
|
if (delayedActionsPerHost.All(a => a.GetType() != action.GetType()))
|
|
delayedActionsPerHost.Add(action);
|
|
}
|
|
}
|
|
|
|
var isLastHostInPool = hosts.IndexOf(host) == hosts.Count - 1;
|
|
if (isLastHostInPool)
|
|
{
|
|
// add cleanup action for current patch at the end of the update seuence for the last host in the pool
|
|
var master = Helpers.GetMaster(host.Connection);
|
|
planActionsPerHost.Add(new RemoveUpdateFileFromMasterPlanAction(master, patchMappings, patch));
|
|
}
|
|
}
|
|
|
|
if (repatriateVms)
|
|
{
|
|
var lastRestart = delayedActionsPerHost.FindLast(a => a is RestartHostPlanAction)
|
|
?? planActionsPerHost.FindLast(a => a is RestartHostPlanAction);
|
|
|
|
if (lastRestart != null)
|
|
((RestartHostPlanAction) lastRestart).EnableOnly = false;
|
|
}
|
|
|
|
return new HostPlan(host, null, planActionsPerHost, delayedActionsPerHost);
|
|
}
|
|
|
|
private PlanAction GetAfterApplyGuidanceAction(Host host, after_apply_guidance guidance)
|
|
{
|
|
switch (guidance)
|
|
{
|
|
case after_apply_guidance.restartHost:
|
|
return new RestartHostPlanAction(host, host.GetRunningVMs(), true, false, hostsThatWillRequireReboot);
|
|
case after_apply_guidance.restartXAPI:
|
|
return new RestartAgentPlanAction(host);
|
|
case after_apply_guidance.restartHVM:
|
|
return new RebootVMsPlanAction(host, host.GetRunningHvmVMs());
|
|
case after_apply_guidance.restartPV:
|
|
return new RebootVMsPlanAction(host, host.GetRunningPvVMs());
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
}
|