/* 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.Linq; using System.Text; using System.Windows.Forms; using XenAdmin.Actions; using XenAdmin.Controls; using XenAdmin.Core; using XenAdmin.Wizards.GenericPages; using XenAPI; using XenAdmin.Actions.DR; namespace XenAdmin.Wizards.DRWizards { public enum DRWizardType { Failover, Failback, Dryrun, Unknown } public partial class DRFailoverWizard : XenWizardBase { private readonly RBACWarningPage RBACWarningPage; private readonly DRFailoverWizardFirstPage DRFailoverWizardFirstPage; private readonly DRFailoverWizardStoragePage DRFailoverWizardStoragePage1; private readonly DRFailoverWizardPrecheckPage DRFailoverWizardPrecheckPage1; private readonly DRFailoverWizardRecoverPage DRFailoverWizardRecoverPage1; private readonly DRFailoverWizardAppliancesPage DRFailoverWizardAppliancesPage1; private readonly DRFailoverWizardWelcomePage DRFailoverWizardWelcomePage; private readonly DRFailoverWizardReportPage DRFailoverWizardReportPage1; private readonly Pool Pool; private DRWizardType WizardType; private SummaryReport SummaryReport = new SummaryReport(); public DRFailoverWizard(Pool pool) : this(pool, DRWizardType.Unknown) { } public DRFailoverWizard(Pool pool, DRWizardType wizardType) : base(pool.Connection) { InitializeComponent(); RBACWarningPage = new RBACWarningPage(); DRFailoverWizardFirstPage = new DRFailoverWizardFirstPage(); DRFailoverWizardStoragePage1 = new DRFailoverWizardStoragePage(); DRFailoverWizardPrecheckPage1 = new DRFailoverWizardPrecheckPage(); DRFailoverWizardRecoverPage1 = new DRFailoverWizardRecoverPage(); DRFailoverWizardAppliancesPage1 = new DRFailoverWizardAppliancesPage(); DRFailoverWizardWelcomePage = new DRFailoverWizardWelcomePage(); DRFailoverWizardReportPage1 = new DRFailoverWizardReportPage(); Pool = pool; WizardType = wizardType; #region RBAC Warning Page Checks if (Pool.Connection.Session.IsLocalSuperuser || Helpers.GetMaster(Pool.Connection).external_auth_type == Auth.AUTH_TYPE_NONE) { } else { RBACWarningPage.WizardPermissionCheck check = new RBACWarningPage.WizardPermissionCheck(Messages.RBAC_DR_WIZARD_MESSAGE); check.AddApiCheck("DR_task.async_create"); check.Blocking = true; RBACWarningPage.AddPermissionChecks(xenConnection, check); AddPage(RBACWarningPage, 0); } #endregion DRFailoverWizardReportPage1.SummaryRetreiver = GetSummaryReport; DRFailoverWizardWelcomePage.WizardTypeChanged += DRFailoverWizardWelcomePage_WizardTypeChanged; DRFailoverWizardWelcomePage.SetWizardType(wizardType); DRFailoverWizardRecoverPage1.ReportStarted += DRFailoverWizardRecoverPage1_ReportStarted; DRFailoverWizardRecoverPage1.ReportLineGot += DRFailoverWizardRecoverPage1_ReportLineGot; DRFailoverWizardRecoverPage1.ReportActionResultGot += DRFailoverWizardRecoverPage1_ReportActionResultGot; DRFailoverWizardAppliancesPage1.Pool = pool; DRFailoverWizardPrecheckPage1.Pool = pool; DRFailoverWizardStoragePage1.NewDrTaskIntroduced += NewDrTaskIntroduced; DRFailoverWizardPrecheckPage1.NewDrTaskIntroduced += NewDrTaskIntroduced; DRFailoverWizardPrecheckPage1.SrIntroduced += DRFailoverWizardPrecheckPage1_SrIntroduced; AddPages(DRFailoverWizardWelcomePage, DRFailoverWizardFirstPage, DRFailoverWizardStoragePage1, DRFailoverWizardAppliancesPage1, DRFailoverWizardPrecheckPage1, DRFailoverWizardRecoverPage1, DRFailoverWizardReportPage1); } protected override string WizardPaneHelpID() { if (CurrentStepTabPage is RBACWarningPage) { string helpId; switch (WizardType) { case DRWizardType.Failover: helpId = "Failover_Rbac"; break; case DRWizardType.Failback: helpId = "Failback_Rbac"; break; case DRWizardType.Dryrun: helpId = "Dryrun_Rbac"; break; default: helpId = "Rbac"; break; } return FormatHelpId(helpId); } return base.WizardPaneHelpID(); } protected override void UpdateWizardContent(XenTabPage senderPage) { var prevPageType = senderPage.GetType(); if (prevPageType == typeof(DRFailoverWizardStoragePage)) DRFailoverWizardAppliancesPage1.AllPoolMetadata = DRFailoverWizardStoragePage1.AllPoolMetadata; else if (prevPageType == typeof(DRFailoverWizardAppliancesPage)) { DRFailoverWizardRecoverPage1.StartActionAfterRecovery = DRFailoverWizardAppliancesPage1.StartActionAfterRecovery; DRFailoverWizardPrecheckPage1.SelectedPoolMetadata = DRFailoverWizardAppliancesPage1.SelectedPoolMetadata; DRFailoverWizardRecoverPage1.SelectedPoolMetadata = DRFailoverWizardAppliancesPage1.SelectedPoolMetadata; } else if (prevPageType == typeof(DRFailoverWizardRecoverPage)) DoFinalCleanup(); } private List DrTasks = new List(); private List> IntroducedSrs = new List>(); private void NewDrTask(string opaqueRef) { DR_task drTask = DR_task.get_record(Pool.Connection.Session, opaqueRef); drTask.opaque_ref = opaqueRef; DrTasks.Add(drTask); IntroducedSrs.AddRange(drTask.introduced_SRs); } private bool cleanupExecuted = false; private void DoFinalCleanup() { if (cleanupExecuted) return; SummaryReport.AddLine(""); // destroy DR tasks DestroyDrTasks(); // dry-run clean-up if (WizardType == DRWizardType.Dryrun) { DoDryRunCleanup(); } // revert pre-check resolved problems if (DRFailoverWizardPrecheckPage1.RevertActions.Count > 0) { SummaryReport.AddLine(Messages.REVERT_PRECHECK_ACTIONS); MultipleAction action = new MultipleAction(xenConnection, Messages.REVERT_PRECHECK_ACTIONS, "", "", DRFailoverWizardPrecheckPage1.RevertActions); new Dialogs.ActionProgressDialog(action, ProgressBarStyle.Blocks).ShowDialog(); foreach (var subAction in DRFailoverWizardPrecheckPage1.RevertActions) SummaryReport.AddActionResult(subAction); } cleanupExecuted = true; } private void DestroyDrTasks() { // destroy DR tasks if (DrTasks.Count > 0) SummaryReport.AddLine(Messages.DR_WIZARD_REPORT_DR_CLEANUP); foreach (var drTask in DrTasks.ToList()) { List srs = Pool.Connection.ResolveAll(drTask.introduced_SRs); string srNames = string.Join(", ", (from sr in srs select sr.Name()).ToArray()); DR_task task = drTask; var action = new DelegatedAsyncAction(xenConnection, string.Format(Messages.ACTION_DR_TASK_DESTROY_TITLE, srNames) , Messages.ACTION_DR_TASK_DESTROY_STATUS, Messages.ACTION_DR_TASK_DESTROY_DONE, s => DR_task.destroy(s, task.opaque_ref)) { Pool = this.Pool }; new Dialogs.ActionProgressDialog(action, ProgressBarStyle.Blocks).ShowDialog(); if (action.Succeeded) DrTasks.Remove(drTask); SummaryReport.AddActionResult(action); } } private void DoDryRunCleanup() { SummaryReport.AddLine(Messages.DR_WIZARD_REPORT_DRYRUN_CLEANUP); List recoveredVms = Pool.Connection.Cache.VMs.Where(vm => DRFailoverWizardRecoverPage1.RecoveredVmsUuids.Contains(vm.uuid)).ToList(); List recoveredAppliances = Pool.Connection.Cache.VM_appliances.Where(appliance => DRFailoverWizardRecoverPage1.RecoveredVmAppliancesUuids.Contains(appliance.uuid)).ToList(); // call hard_shutdown on the recovered VMs and delete the recovered VMs if (recoveredVms.Count > 0) ShutdownAndDestroyVMs(recoveredVms); // call hard_shutdown on the recovered appliances and delete the recovered appliances if (recoveredAppliances.Count > 0) ShutdownAndDestroyAppliances(recoveredAppliances); // detach and forget SRs DetachAndForgetSRs(); } private void ShutdownAndDestroyVMs(List recoveredVMs) { var action = new ShutdownAndDestroyVMsAction(Pool.Connection, recoveredVMs); new Dialogs.ActionProgressDialog(action, ProgressBarStyle.Blocks).ShowDialog(); SummaryReport.AddActionResult(action); } private void ShutdownAndDestroyAppliances(List recoveredAppliances) { var action = new ShutdownAndDestroyVmAppliancesAction(Pool.Connection, recoveredAppliances); new Dialogs.ActionProgressDialog(action, ProgressBarStyle.Blocks).ShowDialog(); SummaryReport.AddActionResult(action); } private void DetachAndForgetSRs() { if (IntroducedSrs.Count == 0) return; List srs = Pool.Connection.ResolveAll(IntroducedSrs); List actions = new List(); foreach (SR sr in srs) { actions.Add(new SrAction(SrActionKind.Forget, sr)); } if (actions.Count == 0) return; var action = new MultipleAction(Pool.Connection, Messages.ACTION_SRS_FORGETTING, Messages.FORGETTING_SRS, Messages.SRS_FORGOTTEN, actions); new Dialogs.ActionProgressDialog(action, ProgressBarStyle.Blocks).ShowDialog(); foreach (var subAction in actions) { SummaryReport.AddActionResult(subAction); } } private void StartSummaryReport() { // wizard type SummaryReport.AddLine(Text); // selected SRs SummaryReport.AddLine(Messages.DR_WIZARD_REPORT_SELECTED_SRS); foreach (var srName in DRFailoverWizardStoragePage1.GetSelectedSRsNames()) { SummaryReport.AddLine(srName, 1); } // selected VMs and appliances SummaryReport.AddLine(Messages.DR_WIZARD_REPORT_SELECTED_METADATA); foreach (var poolMetadata in DRFailoverWizardAppliancesPage1.SelectedPoolMetadata.Values) { SummaryReport.AddLine(poolMetadata.Pool.Name(), 1); foreach (var vmAppliance in poolMetadata.VmAppliances.Values) { SummaryReport.AddLine(vmAppliance.Name(), 2); foreach (XenRef vmRef in vmAppliance.VMs) { if (poolMetadata.Vms.ContainsKey(vmRef)) { SummaryReport.AddLine(poolMetadata.Vms[vmRef].Name(), 3); } } } 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") continue; SummaryReport.AddLine(vm.Name(), 2); } } // power state after recovery SummaryReport.AddLine(String.Format(Messages.DR_WIZARD_REPORT_SELECTED_POWER_STATE, DRFailoverWizardAppliancesPage1.GetSelectedPowerStateDescription())); // introduced SRs if (IntroducedSrs.Count > 0) { List srs = Pool.Connection.ResolveAll(IntroducedSrs); string srNames = string.Join(", ", (from sr in srs select sr.Name()).ToArray()); SummaryReport.AddLine(string.Format(Messages.DR_WIZARD_REPORT_INTRODUCED_SRS, srNames)); } else { SummaryReport.AddLine(Messages.DR_WIZARD_REPORT_INTRODUCED_SRS_NONE); } // Pre-check warnings List warnings = DRFailoverWizardPrecheckPage1.GetWarnings(); if (warnings.Count > 0) { SummaryReport.AddLine(Messages.DR_WIZARD_REPORT_PRECHECK_WARNINGS); foreach (var warning in warnings) SummaryReport.AddLine(warning, 1); } else { SummaryReport.AddLine(Messages.DR_WIZARD_REPORT_PRECHECK_WARNINGS_NONE); } SummaryReport.AddLine(""); SummaryReport.AddLine(Messages.DR_WIZARD_REPORT_RECOVERY_STARTED, 0, true); } private string GetSummaryReport() { return SummaryReport.ToString(); } protected override void OnCancel(ref bool cancel) { DoFinalCleanup(); base.OnCancel(ref cancel); } #region Page event handlers private void DRFailoverWizardWelcomePage_WizardTypeChanged(DRWizardType type) { WizardType = type; switch (type) { case DRWizardType.Failover: Text = string.Format(Messages.DR_WIZARD_FAILOVER_TITLE, xenConnection.Name); pictureBoxWizard.Image = Properties.Resources._000_Failover_h32bit_32; break; case DRWizardType.Failback: Text = string.Format(Messages.DR_WIZARD_FAILBACK_TITLE, xenConnection.Name); pictureBoxWizard.Image = Properties.Resources._000_Failback_h32bit_32; break; case DRWizardType.Dryrun: Text = string.Format(Messages.DR_WIZARD_DRYRUN_TITLE, xenConnection.Name); pictureBoxWizard.Image = Properties.Resources._000_TestFailover_h32bit_32; break; default: pictureBoxWizard.Image = Properties.Resources._000_DisasterRecovery_h32bit_32; break; } DRFailoverWizardReportPage1.WizardType = WizardType; DRFailoverWizardStoragePage1.WizardType = WizardType; DRFailoverWizardAppliancesPage1.WizardType = WizardType; DRFailoverWizardRecoverPage1.WizardType = WizardType; DRFailoverWizardFirstPage.WizardType = WizardType; DRFailoverWizardPrecheckPage1.WizardType = WizardType; } private void NewDrTaskIntroduced(string opaqueRef) { NewDrTask(opaqueRef); } private void DRFailoverWizardPrecheckPage1_SrIntroduced(XenRef srRef) { IntroducedSrs.Add(srRef); } private void DRFailoverWizardRecoverPage1_ReportStarted() { StartSummaryReport(); } private void DRFailoverWizardRecoverPage1_ReportActionResultGot(AsyncAction action) { SummaryReport.AddActionResult(action); } private void DRFailoverWizardRecoverPage1_ReportLineGot(string text, int indent, bool timeStamp) { SummaryReport.AddLine(text, indent, timeStamp); } #endregion } public class SummaryReport { private StringBuilder report; public SummaryReport() { report = new StringBuilder(); } public void AddLine(string line, int indent, bool timeStamp) { const string space = " "; const string bullet = "\u2022"; var indentText = ""; if (indent > 0) { // add space for (var i = 0; i < indent; i++) indentText = String.Format("{0}{1}", space, indentText); // add bullet indentText = String.Format("{0}{1} ", indentText, bullet); } report.AppendLine(timeStamp ? String.Format("{0} - {1}{2}", HelpersGUI.DateTimeToString(DateTime.Now, Messages.DATEFORMAT_DMY_HMS, true), indentText, line) : String.Format("{0}{1}", indentText, line)); } public void AddLine(string line, int indent) { AddLine(line, indent, false); } public void AddLine(string line) { AddLine(line, 0, false); } public void AddActionResult(AsyncAction action) { AddLine(action.Succeeded ? String.Format(Messages.DR_WIZARD_REPORT_ACTION_SUCCEEDED, action.Title) : String.Format(Messages.DR_WIZARD_REPORT_ACTION_FAILED, action.Title, action.Exception.Message), 1); } public override string ToString() { return report.ToString(); } } }