mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-12-29 23:30:10 +01:00
975fe4f303
- removed unnecessary casts - no need to catch exception only to rethrow it; do not interrupt the call stack by using "throw ex" - redundant check and value setting
944 lines
37 KiB
C#
944 lines
37 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.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<string, VmPrecheckRow> vms;
|
|
private Dictionary<XenRef<VM>, 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<string, VmPrecheckRow>();
|
|
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> host = NewMasterComboBox.Items[e.Index] as ToStringWrapper<Host>;
|
|
|
|
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> host = NewMasterComboBox.Items[e.Index] as ToStringWrapper<Host>;
|
|
|
|
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<XenRef<VM>, 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<XenRef<VM>, 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<Host>(kvp.Value[(int)RecProperties.ToHost]);
|
|
if (powerOnHost != null)
|
|
{
|
|
var previousSelection = NewMasterComboBox.SelectedItem as ToStringWrapper<Host>;
|
|
var hostToAdd = new ToStringWrapper<Host>(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<Host> 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();
|
|
using (var dlg = new ActionProgressDialog(action, ProgressBarStyle.Blocks))
|
|
dlg.ShowDialog(this);
|
|
RefreshEntermaintenanceButton();
|
|
}
|
|
|
|
private void VM_PropertyChanged(object sender, PropertyChangedEventArgs args)
|
|
{
|
|
if (args.PropertyName == "resident_on" || args.PropertyName == "allowed_operations" || args.PropertyName == "current_operations")
|
|
{
|
|
Program.Invoke(this, () => RefreshVM(sender as VM));
|
|
}
|
|
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<string, AsyncAction> solveActionsByUuid = new Dictionary<string,AsyncAction>();
|
|
|
|
foreach (VmPrecheckRow i in vms.Values)
|
|
solveActionsByUuid.Add(i.vm.uuid, i.solutionAction);
|
|
|
|
vms = new Dictionary<string, VmPrecheckRow>();
|
|
|
|
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.UpdateSolutionAction(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() || precheckRow.hasSolutionActionInProgress()))
|
|
{
|
|
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<Host> previousSelection = NewMasterComboBox.SelectedItem as ToStringWrapper<Host>;
|
|
|
|
NewMasterComboBox.BeginUpdate();
|
|
try
|
|
{
|
|
NewMasterComboBox.Items.Clear();
|
|
|
|
foreach (Host host in connection.Cache.Hosts)
|
|
{
|
|
Host_metrics metrics = connection.Resolve<Host_metrics>(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, host.Name));
|
|
}
|
|
|
|
SelectProperItemInNewMasterComboBox(previousSelection);
|
|
}
|
|
finally
|
|
{
|
|
NewMasterComboBox.EndUpdate();
|
|
}
|
|
}
|
|
|
|
private void RefreshVM(VM vm)
|
|
{
|
|
foreach (var row in dataGridViewVms.Rows)
|
|
{
|
|
var precheckRow = row as VmPrecheckRow;
|
|
if (precheckRow != null && (precheckRow.vm == vm))
|
|
{
|
|
precheckRow.Update();
|
|
break;
|
|
}
|
|
}
|
|
dataGridViewVms.Refresh();
|
|
}
|
|
|
|
|
|
private class VmPrecheckRow : DataGridViewRow, IComparable<VmPrecheckRow>
|
|
{
|
|
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 (hasSolutionActionInProgress())
|
|
{
|
|
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 void UpdateSolutionAction(AsyncAction action)
|
|
{
|
|
this.solutionAction = action;
|
|
this.error = hasSolutionActionInProgress() ? Messages.EVACUATE_SOLUTION_IN_PROGRESS : "";
|
|
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;
|
|
}
|
|
|
|
internal bool hasSolutionActionInProgress()
|
|
{
|
|
return solutionAction != null && !solutionAction.IsCompleted;
|
|
}
|
|
|
|
}
|
|
|
|
private void RepairButton_Click(object sender, EventArgs e)
|
|
{
|
|
CloseButton.Text = Messages.CLOSE;
|
|
|
|
NewMasterComboBox.Enabled = false;
|
|
ToStringWrapper<Host> newMaster = NewMasterComboBox.SelectedItem as ToStringWrapper<Host>;
|
|
|
|
hostAction = new EvacuateHostAction(host, newMaster != null ? newMaster.item : null, reasons ?? new Dictionary<XenRef<VM>, 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)
|
|
{
|
|
if (vmRef == null)
|
|
return false;
|
|
VM vm = connection.Resolve(new XenRef<VM>(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<SR>(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<VM>(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)
|
|
using (var dlg = new ThreeButtonDialog(
|
|
new ThreeButtonDialog.Details(
|
|
SystemIcons.Error,
|
|
Messages.EVACUATE_HOST_NOT_ENOUGH_MEMORY,
|
|
Messages.EVACUATE_HOST_NOT_ENOUGH_MEMORY_TITLE)))
|
|
{
|
|
dlg.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)
|
|
using (var dlg = new ThreeButtonDialog(
|
|
new ThreeButtonDialog.Details(
|
|
SystemIcons.Error,
|
|
Messages.EVACUATE_HOST_NO_OTHER_HOSTS,
|
|
Messages.EVACUATE_HOST_NO_OTHER_HOSTS_TITLE)))
|
|
{
|
|
dlg.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)
|
|
{
|
|
if (opaqueRef == null || !vms.ContainsKey(opaqueRef))
|
|
return;
|
|
|
|
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<Role> validRoles = new List<Role>();
|
|
|
|
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<Host> previousSelection)
|
|
{
|
|
bool selected = false;
|
|
|
|
if (previousSelection != null)
|
|
{
|
|
foreach (ToStringWrapper<Host> 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;
|
|
}
|
|
}
|
|
|
|
private bool ValidRecommendation(Dictionary<XenRef<VM>, String[]> reasons)
|
|
{
|
|
bool valid = true;
|
|
List<Host> controlDomain = new List<Host>();
|
|
|
|
foreach (KeyValuePair<XenRef<VM>, String[]> kvp in reasons)
|
|
{
|
|
if ((this.connection.Resolve(kvp.Key)).is_control_domain)
|
|
{
|
|
controlDomain.Add(this.connection.Cache.Find_By_Uuid<Host>(kvp.Value[(int)RecProperties.ToHost]));
|
|
}
|
|
}
|
|
|
|
foreach (KeyValuePair<XenRef<VM>, String[]> kvp in reasons)
|
|
{
|
|
if (string.Compare(kvp.Value[0].Trim(), "wlb", true) == 0)
|
|
{
|
|
Host toHost = this.connection.Cache.Find_By_Uuid<Host>(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();
|
|
}
|
|
}
|
|
}
|
|
}
|