/* 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.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using XenAdmin.Controls;
using XenAdmin.Core;
using XenAdmin.Network;
using XenAdmin.Properties;
using XenAPI;
using XenCenterLib;
namespace XenAdmin.Wizards.HAWizard_Pages
{
public partial class AssignPriorities : XenTabPage
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private IXenConnection connection;
private readonly CollectionChangeEventHandler VM_CollectionChangedWithInvoke;
private readonly QueuedBackgroundWorker m_worker;
///
/// May not be set to null.
///
public new IXenConnection Connection
{
set
{
if (value == null)
throw new ArgumentNullException();
if (connection != null)
DeregisterEvents();
this.connection = value;
RegisterEvents();
UpdateMenuItems();
PopulateVMs();
haNtolIndicator.Connection = value;
haNtolIndicator.Settings = getCurrentSettings();
}
}
private void UpdateMenuItems()
{
List restartPriorities = VM.GetAvailableRestartPriorities(connection);
//When this line: m_dropDownButtonRestartPriority.ContextMenuStrip = this.contextMenuStrip
//was called in the designer a "dummy" item was added to the contextMenuStrip by the m_dropDownButtonRestartPriority,
//however here it is cleared so no need to remove it explicitly later when contextMenuStrip.Items is called
contextMenuStrip.Items.Clear();
foreach (var restartPriority in restartPriorities)
{
var menuItem = contextMenuStrip.Items.Add(Helpers.RestartPriorityI18n(restartPriority));
menuItem.Tag = restartPriority;
menuItem.Click += priority_Click;
}
}
///
/// Called when the current IXenConnection's VM dictionary changes.
///
private void VM_CollectionChanged(object sender, CollectionChangeEventArgs e)
{
Program.AssertOnEventThread();
VM vm = (VM)e.Element;
switch (e.Action)
{
case CollectionChangeAction.Add:
vm.PropertyChanged -= vm_PropertyChanged;
vm.PropertyChanged += vm_PropertyChanged;
AddVmRow(vm);
UpdateVMsAgility(new List {vm});
break;
case CollectionChangeAction.Remove:
vm.PropertyChanged -= vm_PropertyChanged;
RemoveVmRow(vm);
break;
}
}
///
/// The current (uncommitted) VM restart priorities.
///
public Dictionary CurrentSettings
{
get { return haNtolIndicator.Settings; }
}
///
/// The Ntol from the HaNtolIndicator control contained within.
///
public long Ntol
{
get
{
return haNtolIndicator.Ntol;
}
}
///
/// Whether the user has made any changes to VM restart priorities on the server.
///
public bool ChangesMade { get; private set; }
public AssignPriorities()
{
InitializeComponent();
VM_CollectionChangedWithInvoke = Program.ProgramInvokeHandler(VM_CollectionChanged);
haNtolIndicator.UpdateInProgressChanged += haNtolIndicator_UpdateInProgressChanged;
nudStartDelay.Maximum = long.MaxValue;
nudOrder.Maximum = long.MaxValue;
m_worker = new QueuedBackgroundWorker();
}
///
/// Clean up any resources being used.
///
/// true if managed resources should be disposed; otherwise, false.
protected override void Dispose(bool disposing)
{
DeregisterEvents();
if (disposing && (components != null))
{
haNtolIndicator.UpdateInProgressChanged -= haNtolIndicator_UpdateInProgressChanged;
components.Dispose();
}
StopNtolUpdate();
base.Dispose(disposing);
}
private void RegisterEvents()
{
// Add listeners
connection.Cache.RegisterCollectionChanged(VM_CollectionChangedWithInvoke);
foreach (VM vm in connection.Cache.VMs)
{
vm.PropertyChanged -= vm_PropertyChanged;
vm.PropertyChanged += vm_PropertyChanged;
}
}
private void DeregisterEvents()
{
// Remove listeners
connection.Cache.DeregisterCollectionChanged(VM_CollectionChangedWithInvoke);
foreach (VM vm in connection.Cache.VMs)
vm.PropertyChanged -= vm_PropertyChanged;
}
///
/// Sets all agile VMs to state 'Protected' and all non-agile VMs to 'Restart if possible'.
/// Important: call before setting the Connection property.
///
internal bool ProtectVmsByDefault { get; set; }
internal void StartNtolUpdate()
{
haNtolIndicator.StartNtolUpdate();
}
internal void StopNtolUpdate()
{
haNtolIndicator.StopNtolUpdate();
}
///
/// Must be called on the event thread.
///
private void PopulateVMs()
{
Program.AssertOnEventThread();
try
{
dataGridViewVms.SuspendLayout();
dataGridViewVms.Rows.Clear();
var newRows = new List();
var vms = connection.Cache.VMs.Where(v => v.HaCanProtect(Properties.Settings.Default.ShowHiddenVMs));
// see if HA is being activated for the first time.
bool firstTime = IsHaActivatedFirstTime(vms);
foreach (VM vm in connection.Cache.VMs)
{
if (!vm.HaCanProtect(Properties.Settings.Default.ShowHiddenVMs))
continue;
// Create a new row for this VM.
// The priority for this row is either initially null (which means 'fill in the restart priority with
// Protected/Restart if possible when we've determined if the VM is agile'), or its current restart priority.
// The first case is for the HA wizard when priorities are being configured for the first time,
// the second is for the edit dialog, when HA is already enabled.
VM.HA_Restart_Priority? priority = firstTime ? (VM.HA_Restart_Priority?)null : vm.HARestartPriority;
var row = new VmWithSettingsRow(vm, priority);
newRows.Add(row);
}
dataGridViewVms.Rows.AddRange(newRows.ToArray());
var addedVms = from row in dataGridViewVms.Rows.Cast() select row.Vm;
UpdateVMsAgility(addedVms);
}
finally
{
dataGridViewVms.ResumeLayout();
}
}
///
/// Starts a new background thread that updates the displayed agility status for each VM.
///
private void UpdateVMsAgility(IEnumerable vms)
{
Debug.Assert(connection != null, "Connection property must have been set to non-null before calling this function");
//worker starts on UI (main) thread
m_worker.RunWorkerAsync((sender, arg) => worker_DoWork(null, vms), worker_RunWorkerCompleted);
}
private object worker_DoWork(object sender, object arg)
{
var vms = arg as IEnumerable;
Debug.Assert(vms != null);
Session session = connection.DuplicateSession();
var results = new Dictionary();
foreach (VM vm in vms)
{
try
{
VM.assert_agile(session, vm.opaque_ref);
results[vm] = null;
}
catch (Failure failure)//The VM wasn't agile
{
results[vm] = failure.ErrorDescription[0];
}
}
return results;
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
log.Error(e.Error);
return;
}
var result = e.Result as Dictionary;
Debug.Assert(result != null);
foreach (var pair in result)
{
VM vm = pair.Key;
string nonAgileReason = pair.Value;
bool isNowAgile = nonAgileReason == null;
//worker started on main thread => this event handler will be on
//main thread too, so no need to invoke
VmWithSettingsRow row = findItemFromVM(vm);
if (row == null)
return;
// We previously had no restart priority assigned => protectVmsByDefault.
// Now we know whether the VM is agile, so we can assign it the highest protection available.
if (row.RestartPriority == null)
{
Debug.Assert(ProtectVmsByDefault);
var priority = isNowAgile ? VM.HaHighestProtectionAvailable(connection) : VM.HA_Restart_Priority.BestEffort;
row.UpdateRestartPriority(priority);
haNtolIndicator.Settings = getCurrentSettings();
}
else if (!isNowAgile
&& row.RestartPriority != VM.HA_Restart_Priority.BestEffort
&& row.RestartPriority != VM.HA_Restart_Priority.DoNotRestart)
{
row.UpdateRestartPriority(VM.HA_Restart_Priority.BestEffort);
haNtolIndicator.Settings = getCurrentSettings();
}
row.UpdateAgile(isNowAgile);
row.FriendlyNonAgileReason = nonAgileReason;
}
updateButtons();
}
private bool IsHaActivatedFirstTime(IEnumerable vms)
{
return ProtectVmsByDefault && vms.All(v => string.IsNullOrEmpty(v.ha_restart_priority));
}
private void AddVmRow(VM vm)
{
Program.AssertOnEventThread();
if (!vm.HaCanProtect(Properties.Settings.Default.ShowHiddenVMs))
return;
// see if HA is being activated for the first time
var vms = connection.Cache.VMs.Where(v => v.HaCanProtect(Properties.Settings.Default.ShowHiddenVMs));
bool firstTime = IsHaActivatedFirstTime(vms);
VM.HA_Restart_Priority? priority = firstTime ? (VM.HA_Restart_Priority?)null : vm.HARestartPriority;
var row = new VmWithSettingsRow(vm, priority);
dataGridViewVms.Rows.Add(row);
}
private void RemoveVmRow(VM vm)
{
Program.AssertOnEventThread();
var row = findItemFromVM(vm);
if (row != null)
dataGridViewVms.Rows.Remove(row);
}
///
/// Finds the ListViewItem for the given VM. Will return null if no corresponding item could be found.
/// Must be called on the event thread.
///
private VmWithSettingsRow findItemFromVM(VM vm)
{
Program.AssertOnEventThread();
return dataGridViewVms.Rows.Cast().FirstOrDefault(r => r.Vm == vm);
}
private void vm_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Program.Invoke(this, () =>
{
VM vm = (VM)sender;
if (vm == null)
return;
// Find row for VM
var row = findItemFromVM(vm);
try
{
dataGridViewVms.SuspendLayout();
if (row == null)
AddVmRow(vm);
else
{
row.UpdateVm(vm);
row.SetAgileCalculating();
}
UpdateVMsAgility(new List {vm});
}
finally
{
dataGridViewVms.ResumeLayout();
}
});
}
private void haNtolIndicator_UpdateInProgressChanged(object sender, EventArgs e)
{
// Signal to anyone who cares that this page is good to continue, or not.
OnPageUpdated();
if (!haNtolIndicator.UpdateInProgress)
{
if (haNtolIndicator.Ntol == -1)
{
labelHaStatus.Text = Messages.HA_UNABLE_TO_CALCULATE_MESSAGE;
labelHaStatus.ForeColor = Color.Red;
pictureBoxStatus.Image = Resources._000_Alert2_h32bit_16;
return;
}
if (haNtolIndicator.Overcommitted || haNtolIndicator.Ntol == 0)
{
labelHaStatus.Text = Messages.HA_OVERCOMMIT_MESSAGE;
labelHaStatus.ForeColor = Color.Red;
pictureBoxStatus.Image = Resources._000_Alert2_h32bit_16;
return;
}
labelHaStatus.Text = string.Format(Messages.HA_GUARANTEED_MESSAGE, haNtolIndicator.NtolMax);
labelHaStatus.ForeColor = SystemColors.ControlText;
pictureBoxStatus.Image = Resources._000_Tick_h32bit_16;
}
}
private void dataGridViewVms_KeyDown(object sender, KeyEventArgs e)
{
if (e.Control && e.KeyCode == Keys.A)
{
try
{
dataGridViewVms.SuspendLayout();
dataGridViewVms.SelectAll();
}
finally
{
dataGridViewVms.ResumeLayout();
}
}
}
///
/// Called when the user clicks one of the restart priorities in the context menu.
///
private void priority_Click(object sender, EventArgs e)
{
var menuitem = (ToolStripMenuItem)sender;
VM.HA_Restart_Priority pri = (VM.HA_Restart_Priority)menuitem.Tag;
bool changesMade = false;
foreach (var row in dataGridViewVms.SelectedRows.Cast())
{
if (row.RestartPriority != pri)
{
changesMade = true;
row.UpdateRestartPriority(pri);
}
}
if (changesMade)
{
ChangesMade = true;
haNtolIndicator.Settings = getCurrentSettings();
// Will trigger a ntol update under this revised plan
updateButtons();
}
}
///
/// Gets the current (uncommitted) VM restart priorities. Must be called on the GUI thread.
///
private Dictionary getCurrentSettings()
{
Program.AssertOnEventThread();
Dictionary result = new Dictionary();
foreach (var row in dataGridViewVms.Rows.Cast())
{
// If the restart priority is null, it means we don't know if the VM is agile yet, and we have
// protectVmsByDefault == true.
result[row.Vm] = row.RestartPriority ?? VM.HA_Restart_Priority.BestEffort;
}
return result;
}
private void dataGridViewVms_SelectionChanged(object sender, EventArgs e)
{
updateButtons();
}
private void m_dropDownButtonRestartPriority_Click(object sender, EventArgs e)
{
var selectedRows = dataGridViewVms.SelectedRows.Cast();
foreach (ToolStripMenuItem item in contextMenuStrip.Items)
{
var itemRestartPriority = ((VM.HA_Restart_Priority)item.Tag);
item.Checked = selectedRows.Count() > 0 && selectedRows.All(s => s.RestartPriority == itemRestartPriority);
}
}
private void updateButtons()
{
Program.AssertOnEventThread();
var selectedRows = dataGridViewVms.SelectedRows.Cast();
if (dataGridViewVms.SelectedRows.Count == 0)
{
m_dropDownButtonRestartPriority.Enabled = false;
m_dropDownButtonRestartPriority.Text = "";
nudOrder.Enabled = nudStartDelay.Enabled = false;
nudOrder.Text = nudStartDelay.Text = "";
return;
}
// if there is a VM with null priority in the selection disable all buttons. We are waiting on the background thread
// to see if the VM is agile before giving it a starting value
if (selectedRows.Any(r => r.RestartPriority == null))
{
m_dropDownButtonRestartPriority.Enabled = true;
m_dropDownButtonRestartPriority.Text = "";
nudOrder.Enabled = nudStartDelay.Enabled = true;
nudOrder.Text = nudStartDelay.Text = "";
return;
}
m_dropDownButtonRestartPriority.Enabled = true;
//now set the drop down button text
bool allSamePriority = false;
foreach (ToolStripMenuItem item in contextMenuStrip.Items)
{
VM.HA_Restart_Priority itemRestartPriority = (VM.HA_Restart_Priority)item.Tag;
if (selectedRows.All(r => r.RestartPriority == itemRestartPriority))
{
allSamePriority = true;
m_dropDownButtonRestartPriority.Text = Helpers.RestartPriorityI18n(itemRestartPriority);
break;
}
}
if (!allSamePriority && dataGridViewVms.SelectedRows.Count > 1)
{
m_dropDownButtonRestartPriority.Text = Messages.HA_ASSIGN_PRIORITIES_MIXED_PROTECTION_LEVELS;
}
// set the order and delay NUDs
nudOrder.Enabled = nudStartDelay.Enabled = true;
var orderDistList = (from row in selectedRows select row.StartOrder).Distinct();
nudOrder.Text = orderDistList.Count() == 1 ? orderDistList.ElementAt(0).ToString() : "";
var delayDistList = (from row in selectedRows select row.StartDelay).Distinct();
nudStartDelay.Text = delayDistList.Count() == 1 ? delayDistList.ElementAt(0).ToString() : "";
// check that all the VMs selected in the list are agile and make sure the protect button is disabled with the relevant reason
VmWithSettingsRow nonAgileRow = selectedRows.FirstOrDefault(r => !r.IsAgile);
// Now set the buttons and tooltips)
foreach (ToolStripMenuItem menuItem in contextMenuStrip.Items)
{
var priority = (VM.HA_Restart_Priority)menuItem.Tag;
if (VM.HaPriorityIsRestart(connection, priority))
{
menuItem.Enabled = (nonAgileRow == null);
menuItem.ToolTipText = (nonAgileRow == null)
? ""
: nonAgileRow.FriendlyNonAgileReason;
}
else
{
menuItem.Enabled = true;
}
}
}
private void nudOrder_ValueChanged(object sender, EventArgs e)
{
long newValue = (long)nudOrder.Value;
bool changesMade = false;
foreach (var row in dataGridViewVms.SelectedRows.Cast())
{
if (row.StartOrder != newValue)
{
changesMade = true;
row.UpdateStartOrder(newValue);
}
}
if (changesMade)
{
ChangesMade = true;
updateButtons();
}
}
private void nudStartDelay_ValueChanged(object sender, EventArgs e)
{
long newValue = (long)nudStartDelay.Value;
bool changesMade = false;
foreach (var row in dataGridViewVms.SelectedRows.Cast())
{
if (row.StartDelay != newValue)
{
changesMade = true;
row.UpdateStartDelay(newValue);
}
}
if (changesMade)
{
ChangesMade = true;
updateButtons();
}
}
///
/// Gets the changed (uncommitted) VM startup options. Must be called on the GUI thread.
///
///
public Dictionary GetChangedStartupOptions()
{
Program.AssertOnEventThread();
Dictionary result = new Dictionary();
foreach (var curRow in dataGridViewVms.Rows.Cast())
{
if (curRow.HasChanged())
result[curRow.Vm] = new VMStartupOptions(curRow.StartOrder,
curRow.StartDelay,
curRow.RestartPriority ?? VM.HA_Restart_Priority.BestEffort);
}
return result;
}
private void linkLabelTellMeMore_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
Help.HelpManager.Launch("HaNtolZero");
}
#region XentabPage overrides
public override string Text
{
get { return Messages.HAWIZARD_ASSIGNPRIORITIESPAGE_TEXT; }
}
public override string PageTitle
{
get
{
return Messages.HAWIZARD_ASSIGNPRIORITIESPAGE_TITLE;
}
}
public override bool EnableNext()
{
return !haNtolIndicator.UpdateInProgress && Ntol >= 0 && !haNtolIndicator.Overcommitted;
}
public override void PageCancelled()
{
StopNtolUpdate();
}
public override void PageLoaded(PageLoadedDirection direction)
{
base.PageLoaded(direction);
StartNtolUpdate();
}
public override void SelectDefaultControl()
{
dataGridViewVms.Select();
}
public override void PageLeave(PageLoadedDirection direction, ref bool cancel)
{
StopNtolUpdate();
base.PageLeave(direction, ref cancel);
}
#endregion
#region Nested classes
private class VmWithSettingsRow : DataGridViewRow
{
private readonly DataGridViewImageCell cellImage;
private readonly DataGridViewTextBoxCell cellVm;
private readonly DataGridViewTextBoxCell cellRestartPriority;
private readonly DataGridViewTextBoxCell cellStartOrder;
private readonly DataGridViewTextBoxCell cellDelay;
private readonly DataGridViewTextBoxCell cellAgile;
private string _nonAgileReason;
public VM Vm { get; private set; }
public long StartDelay { get; private set; }
public long StartOrder { get; private set; }
public VM.HA_Restart_Priority? RestartPriority { get; private set; }
public bool IsAgile { get; private set; }
///
/// Returns a short version of the agility violation for the error type stored in NonAgileReason.
/// Returns empty string if there is no error.
/// For a more detailed, technical description use the error translation in FriendlyErrorNames.resx
///
public string FriendlyNonAgileReason
{
get
{
switch (_nonAgileReason)
{
case null:
return "";
case "HA_CONSTRAINT_VIOLATION_SR_NOT_SHARED":
return Messages.NOT_AGILE_SR_NOT_SHARED;
case "HA_CONSTRAINT_VIOLATION_NETWORK_NOT_SHARED":
return Messages.NOT_AGILE_NETWORK_NOT_SHARED;
case "VM_HAS_VGPU":
return Messages.NOT_AGILE_VM_HAS_VGPU;
default:
// We shouldn't really be here unless we have not iterated all the return errors from vm.assert_agile
return Messages.NOT_AGILE_UNKOWN;
}
}
set
{
_nonAgileReason = value;
}
}
public VmWithSettingsRow(VM vm, VM.HA_Restart_Priority? priority)
{
cellImage = new DataGridViewImageCell {ValueType = typeof(Image)};
cellVm = new DataGridViewTextBoxCell();
cellRestartPriority = new DataGridViewTextBoxCell();
cellStartOrder = new DataGridViewTextBoxCell();
cellDelay = new DataGridViewTextBoxCell();
cellAgile = new DataGridViewTextBoxCell();
Cells.AddRange(cellImage, cellVm, cellRestartPriority, cellStartOrder, cellDelay, cellAgile);
UpdateVm(vm);
UpdateRestartPriority(priority);
UpdateStartOrder(vm.order);
UpdateStartDelay(vm.start_delay);
SetAgileCalculating();
}
public void UpdateVm(VM vm)
{
Vm = vm;
cellImage.Value = Images.GetImage16For(vm);
cellVm.Value = vm.Name();
}
public void UpdateStartDelay(long startDelay)
{
StartDelay = startDelay;
cellDelay.Value = string.Format(Messages.TIME_SECONDS, startDelay);
}
public void UpdateStartOrder(long startOrder)
{
StartOrder = startOrder;
cellStartOrder.Value = startOrder.ToString();
}
public void UpdateRestartPriority(VM.HA_Restart_Priority? restartPriority)
{
RestartPriority = restartPriority;
cellRestartPriority.Value = Helpers.RestartPriorityI18n(restartPriority);
}
public void UpdateAgile(bool isAgile)
{
IsAgile = isAgile;
cellAgile.Value = isAgile.ToYesNoStringI18n();
}
public void SetAgileCalculating()
{
cellAgile.Value = Messages.HA_CALCULATING_AGILITY;
}
public bool HasChanged()
{
return Vm.order != StartOrder || Vm.start_delay != StartDelay || Vm.HARestartPriority != RestartPriority;
}
}
#endregion
}
}