2018-06-19 13:56:14 +02:00
/ * 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.Reflection ;
using System.Threading ;
using log4net ;
using XenAdmin.Controls ;
using XenAdmin.Diagnostics.Problems ;
using XenAdmin.Wizards.PatchingWizard.PlanActions ;
using XenAPI ;
using System.Linq ;
using XenAdmin.Core ;
using System.Text ;
namespace XenAdmin.Wizards.PatchingWizard
{
2018-06-21 13:28:49 +02:00
public enum Status { NotStarted , Started , Cancelled , Completed }
2018-06-20 10:42:01 +02:00
public abstract partial class AutomatedUpdatesBasePage : XenTabPage
2018-06-19 13:56:14 +02:00
{
protected static readonly ILog log = LogManager . GetLogger ( MethodBase . GetCurrentMethod ( ) . DeclaringType ) ;
private bool _thisPageIsCompleted = false ;
private bool _someWorkersFailed = false ;
public List < Problem > ProblemsResolvedPreCheck { get ; set ; }
public List < Pool > SelectedPools { private get ; set ; }
2018-06-21 13:28:49 +02:00
public bool ApplyUpdatesToNewVersion { get ; set ; }
public Status Status { get ; private set ; }
2018-06-19 13:56:14 +02:00
private List < UpdateProgressBackgroundWorker > backgroundWorkers = new List < UpdateProgressBackgroundWorker > ( ) ;
private List < UpdateProgressBackgroundWorker > failedWorkers = new List < UpdateProgressBackgroundWorker > ( ) ;
2018-06-22 11:58:08 +02:00
private List < PoolPatchMapping > patchMappings = new List < PoolPatchMapping > ( ) ;
public Dictionary < XenServerPatch , string > AllDownloadedPatches = new Dictionary < XenServerPatch , string > ( ) ;
2018-06-19 13:56:14 +02:00
public AutomatedUpdatesBasePage ( )
{
InitializeComponent ( ) ;
panel1 . Visible = false ;
}
2018-06-20 10:42:01 +02:00
#region XenTabPage overrides
2018-06-19 13:56:14 +02:00
public override bool EnablePrevious ( )
{
return false ;
}
private bool _nextEnabled ;
public override bool EnableNext ( )
{
return _nextEnabled ;
}
private bool _cancelEnabled = true ;
public override bool EnableCancel ( )
{
return _cancelEnabled ;
}
public override void PageCancelled ( )
{
if ( ! _thisPageIsCompleted )
{
2018-06-21 13:28:49 +02:00
Status = Status . Cancelled ;
2018-06-19 13:56:14 +02:00
backgroundWorkers . ForEach ( bgw = > bgw . CancelAsync ( ) ) ;
backgroundWorkers . Clear ( ) ;
}
base . PageCancelled ( ) ;
}
protected override void PageLoadedCore ( PageLoadedDirection direction )
{
if ( _thisPageIsCompleted )
return ;
2018-06-21 13:28:49 +02:00
Status = Status . NotStarted ;
2018-06-20 10:42:01 +02:00
labelTitle . Text = BlurbText ( ) ;
2018-06-19 13:56:14 +02:00
if ( ! StartUpgradeWorkers ( ) )
{
2018-06-21 13:28:49 +02:00
Status = Status . Completed ;
2018-06-19 13:56:14 +02:00
_thisPageIsCompleted = true ;
_nextEnabled = true ;
OnPageUpdated ( ) ;
}
2018-06-21 13:28:49 +02:00
else
{
Status = Status . Started ;
}
2018-06-19 13:56:14 +02:00
}
2018-06-20 10:42:01 +02:00
#endregion
#region Virtual members
2018-06-22 16:57:31 +02:00
protected abstract string BlurbText ( ) ;
protected abstract string SuccessMessageOnCompletion ( bool multiplePools ) ;
protected abstract string FailureMessageOnCompletion ( bool multiplePools ) ;
protected abstract string SuccessMessagePerPool ( ) ;
protected abstract string FailureMessagePerPool ( bool multipleErrors ) ;
2018-06-20 10:42:01 +02:00
2018-06-25 01:59:21 +02:00
protected virtual void GeneratePlanActions ( Pool pool , List < HostPlan > planActions , List < PlanAction > finalActions ) { }
2018-06-20 10:42:01 +02:00
2018-06-21 13:28:49 +02:00
protected virtual bool SkipInitialPlanActions ( Host host )
{
return false ;
}
2018-06-22 11:58:08 +02:00
protected virtual void DoAfterInitialPlanActions ( UpdateProgressBackgroundWorker bgw , Host host , List < Host > hosts ) { }
2018-06-20 10:42:01 +02:00
#endregion
2018-06-19 13:56:14 +02:00
2018-06-20 10:42:01 +02:00
#region background workers
2018-06-19 13:56:14 +02:00
private bool StartUpgradeWorkers ( )
{
bool atLeastOneWorkerStarted = false ;
foreach ( var pool in SelectedPools )
{
2018-06-25 01:59:21 +02:00
var planActions = new List < HostPlan > ( ) ;
2018-06-19 13:56:14 +02:00
var finalActions = new List < PlanAction > ( ) ;
GeneratePlanActions ( pool , planActions , finalActions ) ;
if ( planActions . Count > 0 )
{
atLeastOneWorkerStarted = true ;
StartNewWorker ( pool . Name ( ) , planActions , finalActions ) ;
}
}
return atLeastOneWorkerStarted ;
}
2018-06-25 01:59:21 +02:00
private void StartNewWorker ( string poolName , List < HostPlan > planActions , List < PlanAction > finalActions )
2018-06-19 13:56:14 +02:00
{
var bgw = new UpdateProgressBackgroundWorker ( planActions , finalActions ) { Name = poolName } ;
backgroundWorkers . Add ( bgw ) ;
bgw . DoWork + = WorkerDoWork ;
bgw . WorkerReportsProgress = true ;
bgw . ProgressChanged + = WorkerProgressChanged ;
bgw . RunWorkerCompleted + = WorkerCompleted ;
bgw . WorkerSupportsCancellation = true ;
bgw . RunWorkerAsync ( ) ;
}
private void WorkerProgressChanged ( object sender , ProgressChangedEventArgs e )
{
var actionsWorker = sender as UpdateProgressBackgroundWorker ;
if ( actionsWorker = = null )
return ;
if ( ! actionsWorker . CancellationPending )
{
PlanAction action = ( PlanAction ) e . UserState ;
if ( action ! = null )
{
if ( ! action . IsComplete )
{
if ( ! actionsWorker . InProgressActions . Contains ( action ) )
actionsWorker . InProgressActions . Add ( action ) ;
}
else
{
if ( ! actionsWorker . DoneActions . Contains ( action ) )
actionsWorker . DoneActions . Add ( action ) ;
actionsWorker . 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)
actionsWorker . CleanupActions . Remove ( action ) ;
// only increase the progress if the action succeeded
progressBar . Value + = e . ProgressPercentage / backgroundWorkers . Count ;
}
}
}
UpdateStatusTextBox ( ) ;
}
}
private void UpdateStatusTextBox ( )
{
var allsb = new StringBuilder ( ) ;
foreach ( var bgw in backgroundWorkers )
{
int bgwErrorCount = 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 )
{
if ( pa . Error ! = null )
{
sb . AppendIndented ( pa . ProgressDescription ? ? pa . ToString ( ) ) ;
2018-06-21 13:28:49 +02:00
if ( pa . Error is CancelledException )
{
sb . AppendLine ( Messages . CANCELLED_BY_USER ) ;
continue ;
}
2018-06-19 13:56:14 +02:00
sb . AppendLine ( Messages . ERROR ) ;
var innerEx = pa . Error . InnerException as Failure ;
if ( innerEx ! = null )
{
log . Error ( innerEx ) ;
errorSb . AppendLine ( innerEx . Message ) ;
}
else
{
log . Error ( pa . Error ) ;
errorSb . AppendLine ( pa . Error . Message ) ;
}
bgwErrorCount + + ;
}
else if ( pa . Visible )
{
sb . AppendIndented ( pa . ProgressDescription ? ? pa . ToString ( ) ) ;
sb . AppendLine ( Messages . DONE ) ;
}
}
foreach ( var pa in bgw . InProgressActions )
{
if ( pa . Visible )
{
sb . AppendIndented ( pa . ProgressDescription ? ? pa . ToString ( ) ) ;
sb . AppendLine ( ) ;
}
}
sb . AppendLine ( ) ;
if ( bgwErrorCount > 0 )
{
2018-06-22 16:57:31 +02:00
sb . AppendIndented ( FailureMessagePerPool ( bgwErrorCount > 1 ) ) . AppendLine ( ) ;
2018-06-19 13:56:14 +02:00
sb . AppendIndented ( errorSb ) ;
}
else if ( ! bgw . IsBusy )
{
2018-06-22 16:57:31 +02:00
sb . AppendIndented ( SuccessMessagePerPool ( ) ) . AppendLine ( ) ;
2018-06-19 13:56:14 +02:00
}
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
{
2018-06-25 01:59:21 +02:00
foreach ( var hp in bgw . HostPlans )
2018-06-19 13:56:14 +02:00
{
2018-06-25 01:59:21 +02:00
var host = hp . Host ;
2018-06-19 13:56:14 +02:00
2018-06-22 11:58:08 +02:00
// Step 1: InitialPlanActions (e.g. upgrade the host in the RPU case)
2018-06-25 01:59:21 +02:00
bgw . ProgressIncrement = bgw . InitialActionsIncrement ( hp ) ;
2018-06-21 13:28:49 +02:00
if ( ! SkipInitialPlanActions ( host ) )
2018-06-19 13:56:14 +02:00
{
2018-06-25 01:59:21 +02:00
var initialActions = hp . InitialPlanActions ;
2018-06-19 13:56:14 +02:00
2018-06-21 13:28:49 +02:00
foreach ( var a in initialActions )
2018-06-19 13:56:14 +02:00
{
2018-06-21 13:28:49 +02:00
action = a ;
2018-06-25 01:09:25 +02:00
RunPlanAction ( bgw , action , ref doWorkEventArgs ) ;
2018-06-21 13:28:49 +02:00
}
}
2018-06-22 11:58:08 +02:00
2018-06-25 01:59:21 +02:00
DoAfterInitialPlanActions ( bgw , host , bgw . HostPlans . Select ( h = > h . Host ) . ToList ( ) ) ;
2018-06-22 11:58:08 +02:00
// Step 2: UpdatesPlanActions (priority update action)
2018-06-25 01:59:21 +02:00
bgw . ProgressIncrement = bgw . UpdatesActionsIncrement ( hp ) ;
var planActions = hp . UpdatesPlanActions ;
2018-06-19 13:56:14 +02:00
foreach ( var a in planActions )
{
action = a ;
2018-06-25 01:09:25 +02:00
RunPlanAction ( bgw , action , ref doWorkEventArgs ) ;
2018-06-19 13:56:14 +02:00
}
2018-06-22 11:58:08 +02:00
// Step 3: DelayedActions
2018-06-25 01:59:21 +02:00
bgw . ProgressIncrement = bgw . DelayedActionsIncrement ( hp ) ;
2018-06-19 13:56:14 +02:00
// running delayed actions, but skipping the ones that should be skipped
2018-06-25 01:59:21 +02:00
var delayedActions = hp . DelayedPlanActions ;
2018-06-19 13:56:14 +02:00
var restartActions = delayedActions . Where ( a = > a is RestartHostPlanAction ) . ToList ( ) ;
foreach ( var a in restartActions )
{
action = a ;
2018-06-25 01:09:25 +02:00
RunPlanAction ( bgw , action , ref doWorkEventArgs ) ;
2018-06-19 13:56:14 +02:00
}
var otherActions = delayedActions . Where ( a = > ! ( a is RestartHostPlanAction ) ) . ToList ( ) ;
foreach ( var a in otherActions )
{
action = a ;
// any non-restart-alike delayed action needs to be run if:
// - this host is pre-Ely and there isn't any delayed restart plan action, or
// - this host is Ely or above and live patching must have succeeded or there isn't any delayed restart plan action
if ( restartActions . Count < = 0 | |
( Helpers . ElyOrGreater ( host ) & & host . Connection . TryResolveWithTimeout ( new XenRef < Host > ( host . opaque_ref ) ) . updates_requiring_reboot . Count < = 0 ) )
{
2018-06-25 01:09:25 +02:00
RunPlanAction ( bgw , action , ref doWorkEventArgs ) ;
2018-06-19 13:56:14 +02:00
}
else
{
//skip running it, but still need to report progress, mainly for the progress bar
2018-06-25 01:09:25 +02:00
if ( bgw . CancellationPending )
{
doWorkEventArgs . Cancel = true ;
return ;
}
2018-06-19 13:56:14 +02:00
action . Visible = false ;
2018-06-22 11:58:08 +02:00
bgw . ReportProgress ( bgw . ProgressIncrement , action ) ;
2018-06-19 13:56:14 +02:00
}
}
}
2018-06-22 11:58:08 +02:00
// Step 4: FinalActions (eg. revert pre-checks)
2018-06-25 01:59:21 +02:00
bgw . ProgressIncrement = bgw . FinalActionsIncrement ;
2018-06-19 13:56:14 +02:00
foreach ( var a in bgw . FinalActions )
{
action = a ;
2018-06-25 01:09:25 +02:00
RunPlanAction ( bgw , action , ref doWorkEventArgs ) ;
2018-06-19 13:56:14 +02:00
}
}
catch ( Exception e )
{
if ( action . Error = = null )
action . Error = new Exception ( Messages . ERROR_UNKNOWN ) ;
if ( ! bgw . DoneActions . Contains ( action ) )
bgw . DoneActions . Add ( action ) ;
bgw . InProgressActions . Remove ( action ) ;
log . Error ( "Failed to carry out plan." , e ) ;
log . Debug ( action . Title ) ;
doWorkEventArgs . Result = new Exception ( action . Title , e ) ;
failedWorkers . Add ( bgw ) ;
bgw . ReportProgress ( 0 ) ;
}
}
2018-06-25 01:09:25 +02:00
private void RunPlanAction ( UpdateProgressBackgroundWorker bgw , PlanAction action , ref DoWorkEventArgs e )
2018-06-19 13:56:14 +02:00
{
2018-06-25 01:09:25 +02:00
if ( bgw . CancellationPending )
{
e . Cancel = true ;
return ;
}
2018-06-19 13:56:14 +02:00
if ( bgw . DoneActions . Contains ( action ) & & action . Error = = null ) // this action was completed successfully, do not run it again
return ;
// if we retry a failed action, we need to firstly remove it from DoneActions and reset its Error
bgw . DoneActions . Remove ( action ) ;
action . Error = null ;
action . OnProgressChange + = action_OnProgressChange ;
bgw . ReportProgress ( 0 , action ) ;
2018-06-21 13:28:49 +02:00
2018-06-23 04:06:36 +02:00
action . Run ( ) ;
2018-06-19 13:56:14 +02:00
Thread . Sleep ( 1000 ) ;
action . OnProgressChange - = action_OnProgressChange ;
2018-06-22 11:58:08 +02:00
bgw . ReportProgress ( bgw . ProgressIncrement , action ) ;
2018-06-19 13:56:14 +02:00
}
private void action_OnProgressChange ( object sender , EventArgs e )
{
Program . Invoke ( Program . MainWindow , UpdateStatusTextBox ) ;
}
private void WorkerCompleted ( object sender , RunWorkerCompletedEventArgs e )
{
if ( ! e . Cancelled )
{
var bgw = sender as UpdateProgressBackgroundWorker ;
2018-06-21 13:28:49 +02:00
if ( bgw ! = null & & bgw . DoneActions . Any ( a = > a . Error ! = null & & ! ( a . Error is CancelledException ) ) )
2018-06-19 13:56:14 +02:00
{
_someWorkersFailed = true ;
}
//if all finished
if ( backgroundWorkers . All ( w = > ! w . IsBusy ) )
{
2018-06-21 13:28:49 +02:00
Status = Status . Completed ;
2018-06-19 13:56:14 +02:00
panel1 . Visible = true ;
if ( _someWorkersFailed )
{
2018-06-22 16:57:31 +02:00
labelError . Text = FailureMessageOnCompletion ( backgroundWorkers . Count > 1 ) ;
2018-06-19 13:56:14 +02:00
pictureBox1 . Image = Images . StaticImages . _000_error_h32bit_16 ;
buttonRetry . Visible = true ;
}
else
{
2018-06-22 16:57:31 +02:00
labelError . Text = SuccessMessageOnCompletion ( backgroundWorkers . Count > 1 ) ;
2018-06-19 13:56:14 +02:00
pictureBox1 . Image = Images . StaticImages . _000_Tick_h32bit_16 ;
buttonRetry . Visible = false ;
progressBar . Value = 100 ;
}
_thisPageIsCompleted = true ;
_cancelEnabled = false ;
_nextEnabled = true ;
}
}
UpdateStatusTextBox ( ) ;
OnPageUpdated ( ) ;
}
private void RetryFailedActions ( )
{
_someWorkersFailed = false ;
panel1 . Visible = false ;
var workers = new List < UpdateProgressBackgroundWorker > ( failedWorkers ) ;
failedWorkers . Clear ( ) ;
foreach ( var failedWorker in workers )
{
failedWorker . RunWorkerAsync ( ) ;
}
_thisPageIsCompleted = false ;
_cancelEnabled = true ;
_nextEnabled = false ;
OnPageUpdated ( ) ;
}
#endregion
private void buttonRetry_Click ( object sender , EventArgs e )
{
RetryFailedActions ( ) ;
}
2018-06-22 11:58:08 +02:00
2018-06-25 17:31:46 +02:00
protected HostPlan GetUpdatePlanActionsForHost ( Host host , List < Host > hosts , List < XenServerPatch > minimalPatches ,
List < XenServerPatch > uploadedPatches , KeyValuePair < XenServerPatch , string > patchFromDisk , bool repatriateVms = true )
2018-06-22 11:58:08 +02:00
{
var patchSequence = Updates . GetPatchSequenceForHost ( host , minimalPatches ) ;
if ( patchSequence = = null )
2018-06-25 01:59:21 +02:00
return new HostPlan ( host , null , null , null ) ;
2018-06-22 11:58:08 +02:00
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 ( host . Connection , patch , patchMappings , AllDownloadedPatches , patchFromDisk ) ) ;
uploadedPatches . Add ( patch ) ;
}
planActionsPerHost . Add ( new PatchPrecheckOnHostPlanAction ( host . Connection , patch , host , patchMappings ) ) ;
planActionsPerHost . Add ( new ApplyXenServerPatchPlanAction ( host , patch , patchMappings ) ) ;
if ( patch . GuidanceMandatory )
{
var action = patch . after_apply_guidance = = after_apply_guidance . restartXAPI & & delayedActionsPerHost . Any ( a = > a is RestartHostPlanAction )
? new RestartHostPlanAction ( host , host . GetRunningVMs ( ) , true , true )
: GetAfterApplyGuidanceAction ( host , patch . after_apply_guidance ) ;
if ( action ! = null )
{
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
{
var action = GetAfterApplyGuidanceAction ( host , patch . after_apply_guidance ) ;
// add the action if it's not already in the list
if ( action ! = null & & 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 ) ) ;
}
}
2018-06-25 17:31:46 +02:00
if ( repatriateVms )
{
var lastRestart = delayedActionsPerHost . FindLast ( a = > a is RestartHostPlanAction )
? ? planActionsPerHost . FindLast ( a = > a is RestartHostPlanAction ) ;
2018-06-22 11:58:08 +02:00
2018-06-25 17:31:46 +02:00
if ( lastRestart ! = null )
( ( RestartHostPlanAction ) lastRestart ) . EnableOnly = false ;
}
2018-06-22 11:58:08 +02:00
2018-06-25 01:59:21 +02:00
return new HostPlan ( host , null , planActionsPerHost , delayedActionsPerHost ) ;
2018-06-22 11:58:08 +02:00
}
private static PlanAction GetAfterApplyGuidanceAction ( Host host , after_apply_guidance guidance )
{
switch ( guidance )
{
case after_apply_guidance . restartHost :
return new RestartHostPlanAction ( host , host . GetRunningVMs ( ) , true ) ;
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 ;
}
}
2018-06-19 13:56:14 +02:00
}
}