/* 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.Drawing; using System.Reflection; using System.Windows.Forms; using log4net; using XenAdmin.Actions; using XenAdmin.Actions.DR; using XenAdmin.Commands; using XenAdmin.Controls; using XenAdmin.Properties; using XenAPI; namespace XenAdmin.Wizards.DRWizards { public partial class DRFailoverWizardRecoverPage : XenTabPage { private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); public event Action ReportStarted; public event Action ReportActionResultGot; public event Action ReportLineGot; public DRFailoverWizardRecoverPage() { InitializeComponent(); } public override string PageTitle { get { switch (WizardType) { case DRWizardType.Failback: return String.Format(Messages.DR_WIZARD_RECOVERPAGE_TITLE_FAILBACK, Connection.Name); case DRWizardType.Dryrun: return String.Format(Messages.DR_WIZARD_RECOVERPAGE_TITLE_DRYRUN, Connection.Name); default: return Messages.DR_WIZARD_RECOVERPAGE_TITLE_FAILOVER; } } } public override string Text { get { return Messages.DR_WIZARD_RECOVERPAGE_TEXT; } } public override string HelpID { get { switch (WizardType) { case DRWizardType.Failback: return "Failback_Recover"; case DRWizardType.Dryrun: return "Dryrun_Recover"; default: return "Failover_Recover"; } } } private static Bitmap animatedImage = Resources.ajax_loader; private void onFrameChanged(object sender, EventArgs e) { try { ImageAnimator.UpdateFrames(); Program.BeginInvoke(dataGridView1, () => dataGridView1.InvalidateColumn(1)); } catch (Exception) { } } public override void PageLeave(PageLoadedDirection direction, ref bool cancel) { ImageAnimator.StopAnimate(animatedImage, onFrameChanged); if (direction == PageLoadedDirection.Back) { actions.Clear(); } base.PageLeave(direction, ref cancel); } public override bool EnableNext() { SetupLabels(); return (progressBar1.Value == 100); } public override bool EnablePrevious() { return false; } private void SetupLabels() { switch (WizardType) { case DRWizardType.Failback: labelTitle.Text = progressBar1.Value == 100 ? String.Format(Messages.DR_WIZARD_RECOVERPAGE_COMPLETE_FAILBACK, Connection.Name) : Messages.DR_WIZARD_RECOVERPAGE_IN_PROGRESS_FAILBACK; labelContinue.Text = Messages.DR_WIZARD_RECOVERPAGE_CONTINUE_FAILBACK; break; case DRWizardType.Dryrun: labelTitle.Text = progressBar1.Value == 100 ? String.Format(Messages.DR_WIZARD_RECOVERPAGE_COMPLETE_DRYRUN, Connection.Name) : Messages.DR_WIZARD_RECOVERPAGE_IN_PROGRESS_DRYRUN; labelContinue.Text = Messages.DR_WIZARD_RECOVERPAGE_CONTINUE_DRYRUN; break; default: labelTitle.Text = progressBar1.Value == 100 ? Messages.DR_WIZARD_RECOVERPAGE_COMPLETE_FAILOVER : Messages.DR_WIZARD_RECOVERPAGE_IN_PROGRESS_FAILOVER; labelContinue.Text = Messages.DR_WIZARD_RECOVERPAGE_CONTINUE_FAILOVER; break; } labelContinue.Visible = progressBar1.Value == 100; } public DRWizardType WizardType { private get; set; } private Dictionary> actions = new Dictionary>(); private int objectsToBeRecovered; private StartActionAfterRecovery startActionAfterRecovery; public StartActionAfterRecovery StartActionAfterRecovery { set { startActionAfterRecovery = value; } } public Dictionary, PoolMetadata> SelectedPoolMetadata { private get; set; } public override void PageLoaded(PageLoadedDirection direction) { base.PageLoaded(direction); if (direction == PageLoadedDirection.Back || actions.Count > 0) return; ImageAnimator.Animate(animatedImage, onFrameChanged); if (ReportStarted != null) ReportStarted(); OnPageUpdated(); dataGridView1.Rows.Clear(); actions.Clear(); objectsToBeRecovered = 0; // add "recovery" tasks foreach (var poolMetadata in SelectedPoolMetadata.Values) { actions.Add(poolMetadata.Vdi, CreateSubActionsFor(poolMetadata)); } // add a row for "Start VMs and Appliances" task, if required if (startActionAfterRecovery != StartActionAfterRecovery.None) dataGridView1.Rows.Add(new DataGridViewRowRecover(Messages.ACTION_START_VMS_AND_APPLIANCES_TITLE)); labelOverallProgress.Text = string.Format(Messages.DR_WIZARD_RECOVERPAGE_OVERALL_PROGRESS, 0, dataGridView1.Rows.Count); RecoverNextPool(); } List CreateSubActionsFor(PoolMetadata poolMetadata) { log.DebugFormat("Generating recovery actions from pool {0} (VDI {1})", poolMetadata.Pool.Name, poolMetadata.Vdi.Name); List subActions = new List(); VdiOpenDatabaseAction openDatabaseAction = new VdiOpenDatabaseAction(Connection, poolMetadata.Vdi); openDatabaseAction.Completed += OpenDatabaseActionCompleted; subActions.Add(openDatabaseAction); foreach (var vmAppliance in poolMetadata.VmAppliances.Values) { DrRecoverAction drRecoverAction = new DrRecoverAction(Connection, vmAppliance); drRecoverAction.Completed += SingleRecoverActionCompleted; drRecoverAction.Changed += SingleRecoverActionChanged; subActions.Add(drRecoverAction); dataGridView1.Rows.Add(new DataGridViewRowRecover(vmAppliance)); objectsToBeRecovered++; } foreach (var vm in poolMetadata.Vms.Values) { if (vm.appliance.opaque_ref != null && vm.appliance.opaque_ref.StartsWith("OpaqueRef:") && vm.appliance.opaque_ref != "OpaqueRef:NULL") { //VM included in an appliance continue; } DrRecoverAction drRecoverAction = new DrRecoverAction(Connection, vm); drRecoverAction.Completed += SingleRecoverActionCompleted; drRecoverAction.Changed += SingleRecoverActionChanged; subActions.Add(drRecoverAction); dataGridView1.Rows.Add(new DataGridViewRowRecover(vm)); objectsToBeRecovered++; } log.DebugFormat("Done - {0} actions generated", subActions.Count); return subActions; } internal List RecoveredVmsUuids = new List(); internal List RecoveredVmAppliancesUuids = new List(); private void SingleRecoverActionCompleted(object sender, EventArgs e) { DrRecoverAction senderAction = (DrRecoverAction)sender; senderAction.Completed -= SingleRecoverActionCompleted; senderAction.Changed -= SingleRecoverActionChanged; objectsToBeRecovered--; Program.BeginInvoke(this, () => { progressBar1.Value = progressBar1.Value < 100 ? progressBar1.Value + (100 / dataGridView1.Rows.Count) : 100; var row = FindRow(senderAction.XenObject); if (row != null) { if (senderAction.Succeeded) row.UpdateStatus(RecoverState.Recovered, Messages.DR_WIZARD_RECOVERPAGE_STATUS_COMPLETED); else row.UpdateStatus(RecoverState.Error, Messages.DR_WIZARD_RECOVERPAGE_STATUS_FAILED, senderAction.Exception.Message); labelOverallProgress.Text = string.Format(Messages.DR_WIZARD_RECOVERPAGE_OVERALL_PROGRESS, row.Index + 1, dataGridView1.Rows.Count); } }); if (senderAction.Succeeded) { if (senderAction.XenObject is VM) RecoveredVmsUuids.Add((senderAction.XenObject as VM).uuid); else if (senderAction.XenObject is VM_appliance) RecoveredVmAppliancesUuids.Add((senderAction.XenObject as VM_appliance).uuid); } if (ReportActionResultGot != null) ReportActionResultGot(senderAction); } private void SingleRecoverActionChanged(object sender, EventArgs e) { DrRecoverAction senderAction = (DrRecoverAction)sender; if (senderAction.IsCompleted) return; Program.BeginInvoke(this, () => { var row = FindRow(senderAction.XenObject); if (row != null && !senderAction.IsCompleted) row.UpdateStatus(RecoverState.Recovering, Messages.DR_WIZARD_RECOVERPAGE_STATUS_WORKING, senderAction.Title); }); } private MultipleAction multipleRecoverAction; private Session metadataSession; private void OpenDatabaseActionCompleted(object sender, EventArgs e) { VdiOpenDatabaseAction senderAction = (VdiOpenDatabaseAction)sender; senderAction.Completed -= OpenDatabaseActionCompleted; log.DebugFormat("Metadata database open ({0}). Now start recovering", senderAction.Vdi.Name); metadataSession = senderAction.MetadataSession; if (metadataSession == null) return; // assign metadata session to all recover actions List recoverSubActions = new List(); foreach (var action in actions[senderAction.Vdi]) { if (action is DrRecoverAction) { ((DrRecoverAction)action).MetadataSession = metadataSession; recoverSubActions.Add(action); } } multipleRecoverAction = new MultipleAction(Connection, String.Format(Messages.DR_WIZARD_RECOVERPAGE_RECOVER_FROM, senderAction.Vdi.Name), String.Format(Messages.DR_WIZARD_RECOVERPAGE_RECOVERING_FROM, senderAction.Vdi.Name), Messages.COMPLETED, recoverSubActions); multipleRecoverAction.Completed += MultipleRecoverActionCompleted; multipleRecoverAction.RunAsync(); } private void MultipleRecoverActionCompleted(object sender, EventArgs e) { MultipleAction senderAction = (MultipleAction)sender; senderAction.Completed -= MultipleRecoverActionCompleted; log.Debug("Finished recovery. Close metadata database"); // logout from metadata session metadataSession.logout(); Program.BeginInvoke(this, () => { if (objectsToBeRecovered == 0) // finished recovering, now start VMs, if required { switch (startActionAfterRecovery) { case StartActionAfterRecovery.Start : StartRecoveredVMs(false); break; case StartActionAfterRecovery.StartPaused: StartRecoveredVMs(true); break; default: progressBar1.Value = 100; OnPageUpdated(); if (ReportLineGot != null) ReportLineGot(labelTitle.Text, 0, true); break; } } else // recover next pool RecoverNextPool(); }); } private void RecoverNextPool() { foreach (var actionList in actions.Values) { bool startRecovery = false; foreach (var action in actionList) { VdiOpenDatabaseAction openDatabaseAction = action as VdiOpenDatabaseAction; if (openDatabaseAction != null && !openDatabaseAction.IsCompleted) { startRecovery = true; log.DebugFormat("Open metadata database ({0})", openDatabaseAction.Vdi.Name); openDatabaseAction.RunAsync(); break; } } if (startRecovery) break; // start recovery of first "unrecovered" pool (unrecovered = !openDatabaseAction.IsCompleted) } } private DataGridViewRowRecover FindRow(IXenObject xenObject) { foreach (DataGridViewRowRecover row in dataGridView1.Rows) { if (row.XenObject == xenObject) return row; } throw new Exception("Row not found"); } private void StartRecoveredVMs(bool paused) { List vmsToStart = new List(); foreach (var uuid in RecoveredVmsUuids) { foreach (VM vm in Connection.Cache.VMs) { if (vm.uuid == uuid) vmsToStart.Add(vm); } } List vmAppliancesToStart = new List(); foreach (var uuid in RecoveredVmAppliancesUuids) { foreach (VM_appliance vmAppliance in Connection.Cache.VM_appliances) { if (vmAppliance.uuid == uuid) vmAppliancesToStart.Add(vmAppliance); } } var action = new StartVMsAndAppliancesAction(Connection, vmsToStart, vmAppliancesToStart, VMOperationCommand.WarningDialogHAInvalidConfig, VMOperationCommand.StartDiagnosisForm, paused); if (action != null) { action.Completed += StartVMsActionCompleted; action.Changed += StartVMsActionChanged; action.RunAsync(); } } private void StartVMsActionChanged(object sender, EventArgs e) { StartVMsAndAppliancesAction senderAction = (StartVMsAndAppliancesAction)sender; if (senderAction.IsCompleted) return; Program.BeginInvoke(this, () => { var row = dataGridView1.Rows[dataGridView1.RowCount - 1] as DataGridViewRowRecover; //last row is "Start VMs and Appliances" row if (row != null && !senderAction.IsCompleted) row.UpdateStatus(RecoverState.Recovering, Messages.DR_WIZARD_RECOVERPAGE_STATUS_WORKING, senderAction.Description); }); } private void StartVMsActionCompleted(object sender, EventArgs e) { StartVMsAndAppliancesAction senderAction = (StartVMsAndAppliancesAction)sender; senderAction.Completed -= StartVMsActionCompleted; senderAction.Changed -= StartVMsActionChanged; if (ReportActionResultGot != null) ReportActionResultGot(senderAction); log.Debug("Finished starting VMs and appliances"); Program.BeginInvoke(this, () => { progressBar1.Value = 100; var row = dataGridView1.Rows[dataGridView1.RowCount - 1] as DataGridViewRowRecover; //last row is "Start VMs and Appliances" row if (row != null) { if (senderAction.Succeeded) row.UpdateStatus(RecoverState.Recovered, Messages.DR_WIZARD_RECOVERPAGE_STATUS_COMPLETED); else row.UpdateStatus(RecoverState.Error, Messages.DR_WIZARD_RECOVERPAGE_STATUS_FAILED, senderAction.Exception.Message); labelOverallProgress.Text = string.Format(Messages.DR_WIZARD_RECOVERPAGE_OVERALL_PROGRESS, row.Index + 1, dataGridView1.Rows.Count); } OnPageUpdated(); if (ReportLineGot != null) ReportLineGot(labelTitle.Text, 0, true); }); } private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { Program.ViewLogFiles(); } public class DataGridViewRowRecover : DataGridViewRow { public readonly IXenObject XenObject; // it can be VM or VM_appliance private DataGridViewImageCell imageCell = new DataGridViewImageCell(); private DataGridViewTextBoxCell taskCell = new DataGridViewTextBoxCell(); private DataGridViewTextBoxCell statusCell = new DataGridViewTextBoxCell(); private DataGridViewRowRecover() { this.Cells.Add(taskCell); this.Cells.Add(imageCell); this.Cells.Add(statusCell); } public DataGridViewRowRecover(IXenObject xenObject) : this() { XenObject = xenObject; taskCell.Value = XenObject is VM ? string.Format(Messages.ACTION_DR_RECOVER_VM_TITLE, XenObject.Name) : string.Format(Messages.ACTION_DR_RECOVER_APPLIANCE_TITLE, XenObject.Name); UpdateStatus(RecoverState.NotRecovered, Messages.DR_WIZARD_RECOVERPAGE_STATUS_PENDING); } public DataGridViewRowRecover(string title) : this() { taskCell.Value = title; UpdateStatus(RecoverState.NotRecovered, Messages.DR_WIZARD_RECOVERPAGE_STATUS_PENDING); } public void UpdateStatus(RecoverState state, string value) { UpdateStatus(state, value, ""); } public void UpdateStatus(RecoverState state, string value, string toolTipText) { switch (state) { case RecoverState.Recovered: imageCell.Value = Resources._000_Tick_h32bit_16; break; case RecoverState.Recovering: imageCell.Value = animatedImage; break; case RecoverState.Error: imageCell.Value = Resources._000_Abort_h32bit_16; break; case RecoverState.NotRecovered: imageCell.Value = new Bitmap(1, 1); break; } statusCell.Value = value; statusCell.ToolTipText = toolTipText; } } public enum RecoverState { NotRecovered, Recovered, Recovering, Error } } }