/* 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.Drawing; using System.Windows.Forms; using XenAPI; using XenAdmin.Actions; using XenAdmin.Core; using XenAdmin.Wlb; using XenAdmin.Commands; using XenAdmin.Actions.VMActions; namespace XenAdmin.Dialogs { public partial class EvacuateHostDialog : DialogWithProgress { #region Private fields private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); private readonly Host host; private EvacuateHostAction hostAction; private Dictionary vms; private Dictionary, String[]> reasons; private string elevatedUName; private string elevatedPass; private Session elevatedSession; private readonly string[] rbacMethods = new[] { "host.remove_from_other_config", // save VM list "host.add_to_other_config", "host.disable", // disable the host "pbd.plug", // Repair SR and install tools "pbd.create", "vbd.eject", "vbd.insert", "vm.suspend", // Suspend VMs "vbd.async_eject", // Change ISO "vbd.async_insert" }; #endregion /* README * * This dialog inherits from DialogWithProgress. Be extremely careful about resizing it, as the DialogWithProgress * class asserts that the controls which inherit it must match its dimensions... otherwise the expanding progress * bar may not show. * */ public EvacuateHostDialog(Host host) : base(host.Connection) { this.host = host; InitializeComponent(); Shrink(); NewMasterComboBox.DrawMode = DrawMode.OwnerDrawFixed; NewMasterComboBox.DrawItem += new DrawItemEventHandler(NewMasterComboBox_DrawItem); NewMasterComboBox.ItemHeight = 16; if (!host.IsMaster() || connection.Cache.HostCount <= 1) { NewMasterComboBox.Enabled = false; NewMasterComboBox.Visible = false; NewMasterLabel.Visible = false; labelMasterBlurb.Visible = false; // Move the panel containing the VM listbox up to fill the gap where the labels were const int pad = 6; int extraSize = panel2.Top - labelMasterBlurb.Top - pad; panel2.Top = labelMasterBlurb.Top + pad; panel2.Height += extraSize; } Pool pool = Core.Helpers.GetPool(host.Connection); if (Helpers.WlbEnabled(host.Connection) && WlbServerState.GetState(pool) == WlbServerState.ServerState.Enabled) lableWLBEnabled.Visible = true; vms = new Dictionary(); this.host.PropertyChanged += new PropertyChangedEventHandler(hostUpdate); ActiveControl = CloseButton; } void NewMasterComboBox_DrawItem(object sender, DrawItemEventArgs e) { if (NewMasterComboBox.Enabled) { using (SolidBrush backBrush = new SolidBrush(NewMasterComboBox.BackColor)) { e.Graphics.FillRectangle(backBrush, e.Bounds); } if (e.Index == -1) return; ToStringWrapper host = NewMasterComboBox.Items[e.Index] as ToStringWrapper; if (host == null) return; Graphics g = e.Graphics; Rectangle bounds = e.Bounds; Image icon = Images.GetImage16For(host.item); // Now draw the image g.DrawImage(icon, bounds.Left + 1, bounds.Top + 1, bounds.Height - 2, bounds.Height - 2); Rectangle bump = new Rectangle(bounds.Height, bounds.Y, bounds.Width - bounds.Height - RIGHT_PADDING, bounds.Height); // And the text Drawing.DrawText(g, host.ToString(), e.Font, bump, e.ForeColor, e.BackColor, TextFormatFlags.Left | TextFormatFlags.VerticalCenter); } else { using (SolidBrush backBrush = new SolidBrush(SystemColors.Control)) { e.Graphics.FillRectangle(backBrush, e.Bounds); } if (e.Index == -1) return; ToStringWrapper host = NewMasterComboBox.Items[e.Index] as ToStringWrapper; if (host == null) return; Graphics g = e.Graphics; Rectangle bounds = e.Bounds; Image icon = Images.GetImage16For(host.item); // Now draw the image g.DrawImage(icon, new Rectangle(bounds.Left + 1, bounds.Top + 1, bounds.Height - 2, bounds.Height - 2), 0, 0, icon.Width, icon.Height, GraphicsUnit.Pixel, Core.Drawing.GreyScaleAttributes); Rectangle bump = new Rectangle(bounds.Height, bounds.Y, bounds.Width - bounds.Height - RIGHT_PADDING, bounds.Height); // And the text Drawing.DrawText(g, host.ToString(), e.Font, bump, SystemColors.GrayText, SystemColors.Control, TextFormatFlags.Left | TextFormatFlags.VerticalCenter); } } const int RIGHT_PADDING = 5; private void dataGridViewVms_CellMouseMove(object sender, DataGridViewCellMouseEventArgs e) { if (e.RowIndex >= 0 && e.RowIndex < dataGridViewVms.RowCount && e.ColumnIndex == columnAction.Index) { var row = dataGridViewVms.Rows[e.RowIndex] as VmPrecheckRow; if (row != null && row.hasSolution()) { Cursor.Current = Cursors.Hand; return; } } Cursor.Current = Cursors.Default; } private void dataGridViewVms_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e) { if (e.RowIndex >= 0 && e.RowIndex < dataGridViewVms.RowCount && e.ColumnIndex == columnAction.Index) { var row = dataGridViewVms.Rows[e.RowIndex] as VmPrecheckRow; if (row != null && row.hasSolution()) { AsyncAction a = row.Solve(); if (a != null) a.Completed += solveActionCompleted; row.Update(); } } } void solveActionCompleted(ActionBase sender) { // this should rescan the vm errors and update the dialog. Program.Invoke(this, update); } private void hostUpdate(object o, PropertyChangedEventArgs args) { if (args == null || args.PropertyName == "name_label" || args.PropertyName == "resident_VMs") { Program.Invoke(this, update); } } private void update() { Program.AssertOnEventThread(); populateVMs(); populateHosts(); Scan(); } private void Scan() { DelegatedAsyncAction saveVMsAction = new DelegatedAsyncAction(connection, Messages.SAVING_VMS_ACTION_TITLE, Messages.SAVING_VMS_ACTION_DESC, Messages.COMPLETED, delegate(Session session) { //Save Evacuated VMs for later host.SaveEvacuatedVMs(session); }, "host.remove_from_other_config", "host.add_to_other_config"); DelegatedAsyncAction action = new DelegatedAsyncAction(connection, Messages.MAINTENANCE_MODE, Messages.SCANNING_VMS, Messages.SCANNING_VMS, delegate(Session session) { reasons = new Dictionary, string[]>(); // WLB: get host wlb evacuate recommendation if wlb is enabled if (Helpers.WlbEnabled(host.Connection)) { try { reasons = XenAPI.Host.retrieve_wlb_evacuate_recommendations(session, host.opaque_ref); } catch (Exception ex) { log.Debug(ex.Message, ex); } } // WLB: in case wlb has no recommendations or get errors when retrieve recommendation, // assume retrieve_wlb_evacuate_recommendations returns 0 recommendation // or return recommendations for all running vms on this host if (reasons.Count == 0 || !ValidRecommendation(reasons)) reasons = Host.get_vms_which_prevent_evacuation(session, host.opaque_ref); // take care of errors Program.Invoke(this, delegate() { foreach (KeyValuePair, String[]> kvp in reasons) { //WLB: filter out errors if (string.Compare(kvp.Value[0].Trim(), "wlb", true) != 0) ProcessError(kvp.Key.opaque_ref, kvp.Value); //Update NewMasterComboBox for host power on recommendation if ((session.Connection.Resolve(kvp.Key)).is_control_domain) { Host powerOnHost = session.Connection.Cache.Find_By_Uuid(kvp.Value[(int)RecProperties.ToHost]); if (powerOnHost != null) { var previousSelection = NewMasterComboBox.SelectedItem as ToStringWrapper; var hostToAdd = new ToStringWrapper(powerOnHost, powerOnHost.Name); if (NewMasterComboBox.Items.Count == 0) { powerOnHost.PropertyChanged -= new PropertyChangedEventHandler(host_PropertyChanged); powerOnHost.PropertyChanged += new PropertyChangedEventHandler(host_PropertyChanged); NewMasterComboBox.Items.Add(hostToAdd); } else { foreach (ToStringWrapper tswh in NewMasterComboBox.Items) { if (tswh.item.CompareTo(powerOnHost) != 0) { powerOnHost.PropertyChanged -= new PropertyChangedEventHandler(host_PropertyChanged); powerOnHost.PropertyChanged += new PropertyChangedEventHandler(host_PropertyChanged); NewMasterComboBox.Items.Add(hostToAdd); } } } SelectProperItemInNewMasterComboBox(previousSelection); } } } }); }, true); SetSession(saveVMsAction); SetSession(action); saveVMsAction.RunAsync(); new ActionProgressDialog(action, ProgressBarStyle.Blocks).ShowDialog(this); RefreshEntermaintenanceButton(); } private void VM_PropertyChanged(object sender, PropertyChangedEventArgs args) { if (args.PropertyName == "resident_on" || args.PropertyName == "allowed_operations") { Program.Invoke(this, dataGridViewVms.Refresh); } else if (args.PropertyName == "virtualisation_status") { Program.Invoke(this, update); } else if (args.PropertyName == "guest_metrics") { VM v = sender as VM; VM_guest_metrics gm = connection.Resolve(v.guest_metrics); if (gm == null) return; gm.PropertyChanged -= new PropertyChangedEventHandler(gm_PropertyChanged); gm.PropertyChanged += new PropertyChangedEventHandler(gm_PropertyChanged); } } void gm_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "PV_drivers_version") Program.Invoke(this, update); } private void host_PropertyChanged(object sender, PropertyChangedEventArgs args) { populateHosts(); Program.Invoke(this, NewMasterComboBox.Refresh); } private void populateVMs() { Program.AssertOnEventThread(); deregisterVMEvents(); Dictionary solveActionsByUuid = new Dictionary(); foreach (VmPrecheckRow i in vms.Values) solveActionsByUuid.Add(i.vm.uuid, i.solutionAction); vms = new Dictionary(); try { dataGridViewVms.SuspendLayout(); dataGridViewVms.Rows.Clear(); foreach (VM vm in connection.ResolveAll(host.resident_VMs)) { if (vm.is_control_domain || vm.is_a_template) continue; vm.PropertyChanged += VM_PropertyChanged; var row = new VmPrecheckRow(this, vm); if (solveActionsByUuid.ContainsKey(vm.uuid)) row.solutionAction = solveActionsByUuid[vm.uuid]; vms.Add(vm.opaque_ref, row); dataGridViewVms.Rows.Add(row); } } finally { dataGridViewVms.ResumeLayout(); RefreshEntermaintenanceButton(); } } private void RefreshEntermaintenanceButton() { EvacuateButton.Enabled = true; foreach (var row in dataGridViewVms.Rows) { var precheckRow = row as VmPrecheckRow; if (precheckRow != null && precheckRow.hasSolution()) { EvacuateButton.Enabled = false; break; } } } private void deregisterVMEvents() { //Deregister event handlers from these VMs foreach (var row in vms.Values) { VM v = row.vm; v.PropertyChanged -= VM_PropertyChanged; VM_guest_metrics gm = connection.Resolve(v.guest_metrics); if (gm == null) return; gm.PropertyChanged -= gm_PropertyChanged; } } private void populateHosts() { Program.AssertOnEventThread(); ToStringWrapper previousSelection = NewMasterComboBox.SelectedItem as ToStringWrapper; NewMasterComboBox.BeginUpdate(); try { NewMasterComboBox.Items.Clear(); foreach (Host host in connection.Cache.Hosts) { Host_metrics metrics = connection.Resolve(host.metrics); if (host.opaque_ref == this.host.opaque_ref) continue; host.PropertyChanged -= new PropertyChangedEventHandler(host_PropertyChanged); host.PropertyChanged += new PropertyChangedEventHandler(host_PropertyChanged); if (host.enabled && metrics != null && metrics.live) NewMasterComboBox.Items.Add(new ToStringWrapper(host, host.Name)); } SelectProperItemInNewMasterComboBox(previousSelection); } finally { NewMasterComboBox.EndUpdate(); } } private class VmPrecheckRow : DataGridViewRow, IComparable { public readonly VM vm; EvacuateHostDialog dialog; Solution solution; public AsyncAction solutionAction; public string error { get; private set; } private DataGridViewImageCell cellImage = new DataGridViewImageCell(); private DataGridViewTextBoxCell cellVm = new DataGridViewTextBoxCell(); private DataGridViewTextBoxCell cellAction = new DataGridViewTextBoxCell(); public VmPrecheckRow(EvacuateHostDialog dialog, VM vm) { this.dialog = dialog; this.vm = vm; this.error = ""; this.solution = Solution.None; Cells.AddRange(cellImage, cellVm, cellAction); Update(); } public void Update() { cellImage.Value = Images.GetImage16For(vm); cellVm.Value = ToString(); cellAction.Value = error; if (hasSolution()) { cellAction.Style.Font = new Font(Program.DefaultFont, FontStyle.Underline); cellAction.Style.ForeColor = Color.Blue; } else { cellAction.Style.Font = Program.DefaultFont; cellAction.Style.ForeColor = DefaultForeColor; } cellAction.Style.SelectionForeColor = cellAction.Style.ForeColor; cellAction.Style.SelectionBackColor = cellAction.Style.BackColor; } public override string ToString() { return vm.Name; } public int CompareTo(VmPrecheckRow otherVM) { return vm.CompareTo(otherVM.vm); } public void UpdateError(string message, Solution solution) { // still running action to solve a previous error, no point in overwriting or we could end up 'solving' it twice if (solutionAction != null && !solutionAction.IsCompleted) { this.error = Messages.EVACUATE_SOLUTION_IN_PROGRESS; return; } this.solution = solution; switch (solution) { case Solution.EjectCD: error = String.Format(Messages.EVACUATE_HOST_EJECT_CD_PROMPT, message); break; case Solution.Suspend: error = String.Format(Messages.EVACUATE_HOST_SUSPEND_VM_PROMPT, message); break; case Solution.Shutdown: error = String.Format(Messages.EVACUATE_HOST_SHUTDOWN_VM_PROMPT, message); break; case Solution.InstallPVDrivers: error = String.Format(vm.HasNewVirtualisationStates ? Messages.EVACUATE_HOST_INSTALL_MGMNT_PROMPT : Messages.EVACUATE_HOST_INSTALL_TOOLS_PROMPT, message); break; case Solution.InstallPVDriversNoSolution: // if the state is not unknown we have metrics and can show a detailed message. // Otherwise go with the server and just say they aren't installed error = !vm.GetVirtualisationStatus.HasFlag(XenAPI.VM.VirtualisationStatus.UNKNOWN) ? vm.GetVirtualisationWarningMessages() : Messages.PV_DRIVERS_NOT_INSTALLED; break; } Update(); } public AsyncAction Solve() { AsyncAction a = null; switch (solution) { case Solution.EjectCD: a = new ChangeVMISOAction(vm.Connection, vm, null, vm.FindVMCDROM()); dialog.SetSession(a); dialog.DoAction(a); break; case Solution.Suspend: a = new VMSuspendAction(vm); dialog.SetSession(a); dialog.DoAction(a); break; case Solution.Shutdown: a = new VMHardShutdown(vm); dialog.SetSession(a); dialog.DoAction(a); break; case Solution.InstallPVDrivers: a = new InstallToolsCommand(Program.MainWindow, vm).ExecuteGetAction(); // The install pv tools action is marked as complete after they have taken the user to the console and loaded the disc // Rescanning when the action is 'complete' in this case doesn't gain us anything then. Keep showing the "Click here to install PV drivers" text. dialog.SetSession(a); solutionAction = a; return a; } solutionAction = a; solution = Solution.None; // we show this, then register an event handler on the action completed to re-update the text/solution this.error = Messages.EVACUATE_SOLUTION_IN_PROGRESS; return a; } internal bool hasSolution() { return solution != Solution.None && solution != Solution.InstallPVDriversNoSolution; } } private void RepairButton_Click(object sender, EventArgs e) { CloseButton.Text = Messages.CLOSE; NewMasterComboBox.Enabled = false; ToStringWrapper newMaster = NewMasterComboBox.SelectedItem as ToStringWrapper; hostAction = new EvacuateHostAction(host, newMaster != null ? newMaster.item : null, reasons ?? new Dictionary, string[]>(), AddHostToPoolCommand.NtolDialog, AddHostToPoolCommand.EnableNtolDialog); hostAction.Completed += Program.MainWindow.action_Completed; SetSession(hostAction); Program.MainWindow.UpdateToolbars(); //Closes all per-Connection and per-VM wizards for the given connection. Program.MainWindow.CloseActiveWizards(host.Connection); EvacuateButton.Enabled = false; // disable evac button, it will get re-enabled when action completes DoAction(hostAction); } private void SetSession(AsyncAction action) { Program.AssertOnEventThread(); if (elevatedUName == null) return; action.sudoPassword = elevatedPass; action.sudoUsername = elevatedUName; if (elevatedSession != null) { // we still have the session from the role elevation dialog // use this first action.Session = elevatedSession; elevatedSession = null; } else action.Session = action.NewSession(); } private void DoAction(AsyncAction action) { action.Changed += action_Changed; action.Completed += action_Completed; Grow(action.RunAsync); } private void action_Changed(ActionBase action) { Program.Invoke(this, () => UpdateProgressControls(action)); } private void action_Completed(ActionBase sender) { Program.Invoke(this, delegate() { if (sender == null) return; if (sender != hostAction) { //re-enable the buttons this.EvacuateButton.Enabled = true; this.CloseButton.Enabled = true; return; } if (hostAction.Exception == null) { Close(); } this.EvacuateButton.Enabled = true; this.CloseButton.Enabled = true; this.NewMasterComboBox.Enabled = true; Failure failure = hostAction.Exception as Failure; if (failure == null) return; if (failure.ErrorDescription.Count > 0 && failure.ErrorDescription[0] == Failure.HOST_NOT_ENOUGH_FREE_MEMORY) { failure.ErrorDescription[0] = Failure.HA_NO_PLAN; failure.Setup(); } ProcessError(null, failure.ErrorDescription.ToArray()); }); Program.Invoke(this, () => FinalizeProgressControls(sender)); } private bool CanSuspendVm(String vmRef) { VM vm = connection.Resolve(new XenRef(vmRef)); return vm != null && vm.allowed_operations != null && vm.allowed_operations.Contains(vm_operations.suspend); } private void ProcessError(String vmRef, String[] ErrorDescription) { try { if (ErrorDescription.Length == 0) return; switch (ErrorDescription[0]) { case Failure.VM_REQUIRES_SR: vmRef = ErrorDescription[1]; SR sr = connection.Resolve(new XenRef(ErrorDescription[2])); if (sr == null) return; if (sr.content_type == SR.Content_Type_ISO) { UpdateVMWithError(vmRef, Messages.EVACUATE_HOST_LOCAL_CD, Solution.EjectCD); } else { UpdateVMWithError(vmRef, Messages.EVACUATE_HOST_LOCAL_STORAGE, CanSuspendVm(vmRef) ? Solution.Suspend : Solution.Shutdown); } break; case Failure.VM_MISSING_PV_DRIVERS: vmRef = ErrorDescription[1]; VM vm = connection.Resolve(new XenRef(vmRef)); if (vm != null && InstallToolsCommand.CanExecute(vm)) UpdateVMWithError(vmRef, String.Empty, Solution.InstallPVDrivers); else UpdateVMWithError(vmRef, String.Empty, Solution.InstallPVDriversNoSolution); break; case Failure.HOST_NOT_ENOUGH_FREE_MEMORY: if (vmRef == null) new ThreeButtonDialog( new ThreeButtonDialog.Details( SystemIcons.Error, Messages.EVACUATE_HOST_NOT_ENOUGH_MEMORY, Messages.EVACUATE_HOST_NOT_ENOUGH_MEMORY_TITLE)).ShowDialog(this); vmRef = ErrorDescription[1]; UpdateVMWithError(vmRef, String.Empty, CanSuspendVm(vmRef) ? Solution.Suspend : Solution.Shutdown); break; case Failure.HA_NO_PLAN: foreach (string _vmRef in vms.Keys) { UpdateVMWithError(_vmRef, String.Empty, CanSuspendVm(vmRef) ? Solution.Suspend : Solution.Shutdown); } if (vmRef == null) new ThreeButtonDialog( new ThreeButtonDialog.Details( SystemIcons.Error, Messages.EVACUATE_HOST_NO_OTHER_HOSTS, Messages.EVACUATE_HOST_NO_OTHER_HOSTS_TITLE)).ShowDialog(this); break; default: AddDefaultSuspendOperation(vmRef); break; } } catch (Exception e) { log.Debug("Exception processing exception", e); log.Debug(e, e); AddDefaultSuspendOperation(vmRef); } } private void AddDefaultSuspendOperation(String vmRef) { if (vmRef == null) return; UpdateVMWithError(vmRef, String.Empty, CanSuspendVm(vmRef) ? Solution.Suspend : Solution.Shutdown); } enum Solution { None, EjectCD, Suspend, InstallPVDrivers, InstallPVDriversNoSolution, Shutdown }; void UpdateVMWithError(string opaqueRef, string message, Solution solution) { var row = vms[opaqueRef]; row.UpdateError(message, solution); Program.Invoke(this, dataGridViewVms.Refresh); } private void CloseButton_Click(object sender, EventArgs e) { this.Close(); } protected override void OnShown(EventArgs e) { base.OnShown(e); Text = string.Format(Messages.EVACUATE_HOST_DIALOG_TITLE, host.Name); //This dialog uses several different actions all of which might need an elevated session //We sudo once for all of them and store the session, or close the dialog. List validRoles = new List(); if (!connection.Session.IsLocalSuperuser && !Registry.DontSudo && !Role.CanPerform(new RbacMethodList(rbacMethods), connection, out validRoles)) { var sudoDialog = XenAdminConfigManager.Provider.SudoDialogDelegate; var result = sudoDialog(validRoles, connection, Text); if (!result.Result) { Close(); return; } elevatedPass = result.ElevatedPassword; elevatedUName = result.ElevatedUsername; elevatedSession = result.ElevatedSession; } update(); } private void SelectProperItemInNewMasterComboBox(ToStringWrapper previousSelection) { bool selected = false; if (previousSelection != null && !selected) { foreach (ToStringWrapper host in NewMasterComboBox.Items) { if (host.item.opaque_ref == previousSelection.item.opaque_ref) { NewMasterComboBox.SelectedItem = host; selected = true; break; } } } if (NewMasterComboBox.Items.Count > 0 && !selected) { NewMasterComboBox.SelectedIndex = 0; selected = true; } } private bool ValidRecommendation(Dictionary, String[]> reasons) { bool valid = true; List controlDomain = new List(); foreach (KeyValuePair, String[]> kvp in reasons) { if ((this.connection.Resolve(kvp.Key)).is_control_domain) { controlDomain.Add(this.connection.Cache.Find_By_Uuid(kvp.Value[(int)RecProperties.ToHost])); } } foreach (KeyValuePair, String[]> kvp in reasons) { if (string.Compare(kvp.Value[0].Trim(), "wlb", true) == 0) { Host toHost = this.connection.Cache.Find_By_Uuid(kvp.Value[(int)RecProperties.ToHost]); if (!(this.connection.Resolve(kvp.Key)).is_control_domain && !toHost.IsLive && !controlDomain.Contains(toHost)) { valid = false; break; } } } return valid; } private void EvacuateHostDialog_FormClosed(object sender, FormClosedEventArgs e) { foreach (Host host in connection.Cache.Hosts) { host.PropertyChanged -= new PropertyChangedEventHandler(host_PropertyChanged); } deregisterVMEvents(); if (elevatedSession != null && elevatedSession.uuid != null) { // NOTE: This doesnt happen currently, as we always scan once. Here as cheap insurance. // we still have the session from the role elevation dialog // it hasn't been used by an action so needs to be logged out elevatedSession.logout(); } } } }