mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-11-25 14:27:26 +01:00
90589a30c9
This PR is aimimg to (1) fix a regression introduced by #2223 whereby the toolbar items did not have the correct state on first launch; (2) improve performance by removing some updates which I believe are not needed. * Removed some duplicate calls to RefershTreeView and UpdateToolbars because they are called within the action's Complete event handler. Also, normalise the way the treeview refresh is requested by the various actions the commands are launching. Signed-off-by: Konstantina Chremmou <konstantina.chremmou@citrix.com> * Removed some explicit calls to refresh the treeview since this is done by the connection result handlers. Signed-off-by: Konstantina Chremmou <konstantina.chremmou@citrix.com> * The toolbars and tabs should be updated every time the treeview is refreshed. This should happen when the refresh event is handled rather than calling UpdateToolbars explicitly after a treeview refresh is requested; also, it is the treeview refreshes that should be throttled and not the toolbar and tab updates (the throttling mechanism may need correction). Also, removed unnecessary Invoke as we are already on the UI thread. Signed-off-by: Konstantina Chremmou <konstantina.chremmou@citrix.com> * Simplified UpdateManager.Update event declaration. Signed-off-by: Konstantina Chremmou <konstantina.chremmou@citrix.com>
701 lines
28 KiB
C#
701 lines
28 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 XenAdmin.Actions;
|
|
using XenAdmin.Controls;
|
|
using XenAdmin.Core;
|
|
using XenAdmin.Dialogs;
|
|
using XenAdmin.Wizards;
|
|
using XenAPI;
|
|
using XenCenterLib;
|
|
|
|
namespace XenAdmin.TabPages
|
|
{
|
|
internal partial class HAPage : BaseTabPage
|
|
{
|
|
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
private Pool pool;
|
|
|
|
private IXenObject xenObject;
|
|
|
|
private bool restartHBInitializationTimer;
|
|
|
|
private bool initializationDelayElapsed;
|
|
|
|
private System.Timers.Timer initializationDelayTimer;
|
|
|
|
private const int HB_INITIALIZATION_DELAY = 30000;
|
|
|
|
private readonly CollectionChangeEventHandler Host_CollectionChangedWithInvoke;
|
|
/// <summary>
|
|
/// The object that the panel is displaying HA info for. Must be set on the event thread.
|
|
/// </summary>
|
|
public IXenObject XenObject
|
|
{
|
|
set
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
UnregisterHandlers();
|
|
|
|
xenObject = value;
|
|
pool = xenObject as Pool;
|
|
if (pool != null)
|
|
{
|
|
pool.PropertyChanged += pool_PropertyChanged;
|
|
foreach (var host in pool.Connection.Cache.Hosts)
|
|
{
|
|
host.PropertyChanged += host_PropertyChanged;
|
|
}
|
|
pool.Connection.Cache.RegisterCollectionChanged<Host>(Host_CollectionChangedWithInvoke);
|
|
}
|
|
|
|
Rebuild();
|
|
}
|
|
}
|
|
|
|
public HAPage()
|
|
{
|
|
InitializeComponent();
|
|
|
|
customListPanel.ContextMenuRequest += GP_ContextShow;
|
|
Host_CollectionChangedWithInvoke = Program.ProgramInvokeHandler(Host_CollectionChanged);
|
|
ConnectionsManager.History.CollectionChanged += History_CollectionChanged;
|
|
base.Text = Messages.HIGH_AVAILABILITY;
|
|
|
|
pictureBoxWarningTriangle.Image = SystemIcons.Warning.ToBitmap();
|
|
restartHBInitializationTimer = true;
|
|
}
|
|
|
|
private void History_CollectionChanged(object sender, CollectionChangeEventArgs e)
|
|
{
|
|
//Program.AssertOnEventThread();
|
|
Program.BeginInvoke(Program.MainWindow, () =>
|
|
{
|
|
if (e.Action == CollectionChangeAction.Add &&
|
|
(e.Element is EnableHAAction || e.Element is DisableHAAction))
|
|
{
|
|
AsyncAction action = (AsyncAction)e.Element;
|
|
action.Changed += action_Changed;
|
|
if (xenObject != null && xenObject.Connection == action.Connection)
|
|
Rebuild();
|
|
}
|
|
});
|
|
}
|
|
|
|
private void action_Changed(ActionBase sender)
|
|
{
|
|
// This seems to be called off the event thread
|
|
AsyncAction action = (AsyncAction)sender;
|
|
if (action.IsCompleted)
|
|
{
|
|
action.Changed -= action_Changed;
|
|
Program.Invoke(Program.MainWindow, delegate()
|
|
{
|
|
if (xenObject != null && xenObject.Connection == action.Connection)
|
|
{
|
|
Rebuild();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registered on the connection's Host collection when the HAPage is showing a pool.
|
|
/// Means we respond to hosts joining/leaving the pool.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void Host_CollectionChanged(object sender, CollectionChangeEventArgs e)
|
|
{
|
|
switch (e.Action)
|
|
{
|
|
case CollectionChangeAction.Add:
|
|
((Host)e.Element).PropertyChanged += host_PropertyChanged;
|
|
break;
|
|
case CollectionChangeAction.Remove:
|
|
((Host)e.Element).PropertyChanged -= host_PropertyChanged;
|
|
break;
|
|
case CollectionChangeAction.Refresh:
|
|
// As of writing, ChangeableDictionary never raises this kind of event
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
private void UnregisterHandlers()
|
|
{
|
|
if (pool == null)
|
|
return;
|
|
pool.PropertyChanged -= pool_PropertyChanged;
|
|
foreach (var host in pool.Connection.Cache.Hosts)
|
|
{
|
|
host.PropertyChanged -= host_PropertyChanged;
|
|
}
|
|
pool.Connection.Cache.DeregisterCollectionChanged<Host>(Host_CollectionChangedWithInvoke);
|
|
}
|
|
|
|
public override void PageHidden()
|
|
{
|
|
UnregisterHandlers();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rebuilds the panel contents. Must be called on the event thread.
|
|
/// </summary>
|
|
private void Rebuild()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
if (!this.Visible)
|
|
return;
|
|
customListPanel.BeginUpdate();
|
|
try
|
|
{
|
|
customListPanel.ClearRows();
|
|
tableLatencies.Controls.Clear();
|
|
|
|
if (xenObject == null)
|
|
return;
|
|
|
|
AsyncAction action = HelpersGUI.FindActiveHaAction(xenObject.Connection);
|
|
if (action != null)
|
|
{
|
|
// There is an EnableHAAction or DisableHAAction in progress relating to this connection.
|
|
// Show some text and disable editing
|
|
buttonConfigure.Enabled = false;
|
|
buttonDisableHa.Enabled = false;
|
|
|
|
pictureBoxWarningTriangle.Visible = false;
|
|
labelStatus.Text = String.Format(action is EnableHAAction ? Messages.HA_PAGE_ENABLING : Messages.HA_PAGE_DISABLING,
|
|
Helpers.GetName(xenObject.Connection));
|
|
}
|
|
else
|
|
{
|
|
//CA-250234 no need to rebuild HA page if we lost connection to the pool
|
|
if (pool.Connection == null || pool.Connection.Session == null || !pool.Connection.IsConnected)
|
|
return;
|
|
|
|
// Generate the tab contents depending on what XenObject we're displaying
|
|
|
|
if (pool.ha_enabled)
|
|
{
|
|
if (PassedRbacChecks())
|
|
{
|
|
bool haRestricted = Helpers.FeatureForbidden(pool, Host.RestrictHA);
|
|
|
|
buttonConfigure.Visible = !haRestricted;
|
|
buttonConfigure.Enabled = !haRestricted;
|
|
buttonDisableHa.Visible = true;
|
|
buttonDisableHa.Enabled = true;
|
|
|
|
pictureBoxWarningTriangle.Visible = false;
|
|
labelStatus.Text = string.Format(haRestricted ? Messages.HA_TAB_CONFIGURED_UNLICENSED : Messages.HA_TAB_CONFIGURED_BLURB, Helpers.GetName(pool).Ellipsise(30));
|
|
}
|
|
else
|
|
{
|
|
buttonConfigure.Visible = false;
|
|
buttonDisableHa.Visible = false;
|
|
|
|
pictureBoxWarningTriangle.Visible = true;
|
|
labelStatus.Text = String.Format(Messages.RBAC_HA_TAB_WARNING,
|
|
Role.FriendlyCSVRoleList(Role.ValidRoleList(HA_PERMISSION_CHECKS, pool.Connection)),
|
|
Role.FriendlyCSVRoleList(pool.Connection.Session.Roles));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
buttonConfigure.Visible = true;
|
|
buttonConfigure.Enabled = true;
|
|
buttonDisableHa.Visible = false;
|
|
|
|
pictureBoxWarningTriangle.Visible = false;
|
|
labelStatus.Text = String.Format(Messages.HAPANEL_BLURB, Helpers.GetName(pool).Ellipsise(30));
|
|
}
|
|
|
|
var sr = xenObject as SR;
|
|
if (sr != null)
|
|
{
|
|
// Currently unused
|
|
generateSRHABox(sr);
|
|
}
|
|
else if (xenObject is Pool)
|
|
{
|
|
generatePoolHABox(pool);
|
|
tableLayoutPanel1.AutoScrollMinSize = new Size(0, tableLatencies.Height + customListPanel.Height);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
customListPanel.EndUpdate();
|
|
}
|
|
}
|
|
|
|
private RbacMethodList HA_PERMISSION_CHECKS = new RbacMethodList(
|
|
"pool.set_ha_host_failures_to_tolerate",
|
|
"pool.sync_database",
|
|
"vm.set_ha_restart_priority",
|
|
"pool.ha_compute_hypothetical_max_host_failures_to_tolerate"
|
|
);
|
|
|
|
private bool PassedRbacChecks()
|
|
{
|
|
return Role.CanPerform(HA_PERMISSION_CHECKS, pool.Connection);
|
|
}
|
|
|
|
private void pool_PropertyChanged(object sender1, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == "ha_enabled" || e.PropertyName == "ha_host_failures_to_tolerate"
|
|
|| e.PropertyName == "ha_overcommitted" || e.PropertyName == "ha_plan_exists_for"
|
|
|| e.PropertyName == "name_label")
|
|
{
|
|
Rebuild();
|
|
}
|
|
}
|
|
|
|
private void host_PropertyChanged(object sender1, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == "name_label" || e.PropertyName == "ha_statefiles"
|
|
|| e.PropertyName == "ha_network_peers")
|
|
{
|
|
Rebuild();
|
|
}
|
|
}
|
|
|
|
private string getPoolHAStatus(Pool pool)
|
|
{
|
|
return pool.ha_enabled ? Messages.YES : Messages.NO;
|
|
}
|
|
|
|
private void generatePoolHABox(Pool pool)
|
|
{
|
|
if (!pool.ha_enabled)
|
|
{
|
|
restartHBInitializationTimer = true;
|
|
return;
|
|
}
|
|
|
|
if (restartHBInitializationTimer)
|
|
{
|
|
restartHBInitializationTimer = false;
|
|
SetNetworkHBInitDelay();
|
|
}
|
|
|
|
// 'High Availability' heading
|
|
CustomListRow header = CreateHeader(Messages.HA_CONFIGURATION_TITLE);
|
|
customListPanel.AddRow(header);
|
|
AddRow(header, GetFriendlyName("pool.ha_enabled"), pool, getPoolHAStatus, false);
|
|
{
|
|
// ntol row. May be red and bold.
|
|
bool redBold = pool.ha_host_failures_to_tolerate == 0;
|
|
|
|
CustomListRow newChild = CreateNewRow(Messages.HA_CONFIGURED_CAPACITY, new ToStringWrapper<Pool>(pool, getNtol), redBold);
|
|
header.AddChild(newChild);
|
|
|
|
if (redBold)
|
|
{
|
|
newChild.Items[1].ForeColor = Color.Red;
|
|
ToolStripMenuItem editHa = new ToolStripMenuItem(Messages.CONFIGURE_HA_ELLIPSIS);
|
|
editHa.Click += delegate { EditHA(pool); };
|
|
newChild.MenuItems.Add(editHa);
|
|
newChild.DefaultMenuItem = editHa;
|
|
}
|
|
else
|
|
{
|
|
newChild.Items[1].ForeColor = BaseTabPage.ItemValueForeColor;
|
|
}
|
|
}
|
|
|
|
{
|
|
// plan_exists_for row needs some special work: the text may be red and bold
|
|
bool redBold = haStatusRed(pool);
|
|
CustomListRow newChild = CreateNewRow(Messages.HA_CURRENT_CAPACITY, new ToStringWrapper<Pool>(pool, getPlanExistsFor), redBold);
|
|
header.AddChild(newChild);
|
|
|
|
if (redBold)
|
|
{
|
|
newChild.Items[1].ForeColor = Color.Red;
|
|
ToolStripMenuItem editHa = new ToolStripMenuItem(Messages.CONFIGURE_HA_ELLIPSIS);
|
|
editHa.Click += delegate { EditHA(pool); };
|
|
newChild.MenuItems.Add(editHa);
|
|
newChild.DefaultMenuItem = editHa;
|
|
}
|
|
else
|
|
{
|
|
newChild.Items[1].ForeColor = BaseTabPage.ItemValueForeColor;
|
|
}
|
|
}
|
|
AddBottomSpacing(header);
|
|
|
|
// 'Heartbeating status' heading
|
|
header = CreateHeader(Messages.HEARTBEATING_STATUS);
|
|
customListPanel.AddRow(header);
|
|
|
|
// Now build the heartbeat target health table
|
|
|
|
List<SR> heartbeatSRs = pool.GetHAHeartbeatSRs();
|
|
// Sort heartbeat SRs using NaturalCompare
|
|
heartbeatSRs.Sort((Comparison<SR>)delegate(SR a, SR b)
|
|
{
|
|
return StringUtility.NaturalCompare(a.Name(), b.Name());
|
|
});
|
|
|
|
List<Host> members = new List<Host>(pool.Connection.Cache.Hosts);
|
|
// Sort pool members using NaturalCompare
|
|
members.Sort((Comparison<Host>)delegate(Host a, Host b)
|
|
{
|
|
return StringUtility.NaturalCompare(a.Name(), b.Name());
|
|
});
|
|
int numCols = 1 + 2 + (2 * heartbeatSRs.Count); // Hostnames col, then 2 each for each HB target (network + SRs)
|
|
int numRows = 1 + members.Count;
|
|
|
|
// Create rows and cols
|
|
tableLatencies.SuspendLayout();
|
|
tableLatencies.ColumnCount = numCols;
|
|
tableLatencies.ColumnStyles.Clear();
|
|
for (int i = 0; i < numCols; i++)
|
|
{
|
|
tableLatencies.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
|
|
}
|
|
tableLatencies.RowCount = numRows;
|
|
tableLatencies.RowStyles.Clear();
|
|
for (int i = 0; i < numRows; i++)
|
|
{
|
|
tableLatencies.RowStyles.Add(new RowStyle(SizeType.AutoSize));
|
|
}
|
|
|
|
{
|
|
// Network icon
|
|
PictureBox p = new PictureBox();
|
|
p.Image = Images.GetImage16For(Icons.Network);
|
|
p.SizeMode = PictureBoxSizeMode.AutoSize;
|
|
p.Padding = new Padding(0);
|
|
tableLatencies.Controls.Add(p);
|
|
tableLatencies.SetCellPosition(p, new TableLayoutPanelCellPosition(1, 0));
|
|
// Network text
|
|
Label l = new Label();
|
|
l.Padding = new Padding(0, 5, 5, 5);
|
|
l.Font = BaseTabPage.ItemLabelFont;
|
|
l.ForeColor = BaseTabPage.ItemLabelForeColor;
|
|
l.AutoSize = true;
|
|
l.Text = Messages.NETWORK;
|
|
tableLatencies.Controls.Add(l);
|
|
tableLatencies.SetCellPosition(l, new TableLayoutPanelCellPosition(2, 0));
|
|
}
|
|
|
|
for (int i = 0; i < heartbeatSRs.Count; i++)
|
|
{
|
|
// SR icon
|
|
PictureBox p = new PictureBox();
|
|
p.Image = Images.GetImage16For(Images.GetIconFor(heartbeatSRs[i]));
|
|
p.SizeMode = PictureBoxSizeMode.AutoSize;
|
|
p.Padding = new Padding(0);
|
|
tableLatencies.Controls.Add(p);
|
|
tableLatencies.SetCellPosition(p, new TableLayoutPanelCellPosition((2 * i) + 3, 0));
|
|
// SR name
|
|
Label l = new Label();
|
|
l.Padding = new Padding(0, 5, 5, 5);
|
|
l.Font = BaseTabPage.ItemLabelFont;
|
|
l.ForeColor = BaseTabPage.ItemLabelForeColor;
|
|
l.AutoSize = false;
|
|
l.Size = new Size(200, 25);
|
|
l.AutoEllipsis = true;
|
|
l.Text = heartbeatSRs[i].Name();
|
|
tableLatencies.Controls.Add(l);
|
|
tableLatencies.SetCellPosition(l, new TableLayoutPanelCellPosition((2 * i) + 4, 0));
|
|
}
|
|
|
|
for (int i = 0; i < members.Count; i++)
|
|
{
|
|
// Server name label
|
|
Label l = new Label();
|
|
l.Padding = new Padding(5);
|
|
l.Font = BaseTabPage.ItemLabelFont;
|
|
l.ForeColor = BaseTabPage.ItemLabelForeColor;
|
|
l.AutoSize = true;
|
|
l.Text = members[i].Name().Ellipsise(30);
|
|
tableLatencies.Controls.Add(l);
|
|
tableLatencies.SetCellPosition(l, new TableLayoutPanelCellPosition(0, i + 1));
|
|
|
|
// Network HB status
|
|
l = new Label();
|
|
l.Padding = new Padding(0, 5, 0, 5);
|
|
l.Font = BaseTabPage.ItemValueFont;
|
|
l.AutoSize = true;
|
|
l.ForeColor = (members[i].ha_network_peers.Length == members.Count && initializationDelayElapsed) ? Color.Green : BaseTabPage.ItemValueForeColor;
|
|
|
|
if (initializationDelayElapsed)
|
|
{
|
|
if (members[i].ha_network_peers.Length == 0)
|
|
{
|
|
l.Text = Messages.HA_HEARTBEAT_UNHEALTHY;
|
|
}
|
|
else if (members[i].ha_network_peers.Length == members.Count)
|
|
{
|
|
l.Text = Messages.HA_HEARTBEAT_HEALTHY;
|
|
}
|
|
else
|
|
{
|
|
l.Text = String.Format(Messages.HA_HEARTBEAT_SERVERS, members[i].ha_network_peers.Length, members.Count);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
l.Text = Messages.HA_HEARTBEAT_SERVERS_INITIALISING;
|
|
}
|
|
|
|
tableLatencies.Controls.Add(l);
|
|
tableLatencies.SetCellPosition(l, new TableLayoutPanelCellPosition(1, i + 1));
|
|
tableLatencies.SetColumnSpan(l, 2);
|
|
|
|
// For each heartbeat SR, show health from this host's perspective
|
|
for (int j = 0; j < heartbeatSRs.Count; j++)
|
|
{
|
|
l = new Label();
|
|
l.Padding = new Padding(0, 5, 0, 5);
|
|
l.Font = BaseTabPage.ItemValueFont;
|
|
l.ForeColor = BaseTabPage.ItemValueForeColor;
|
|
l.AutoSize = true;
|
|
l.Text = Messages.HA_HEARTBEAT_UNHEALTHY;
|
|
foreach (string opaqueRef in members[i].ha_statefiles)
|
|
{
|
|
XenRef<VDI> vdiRef = new XenRef<VDI>(opaqueRef);
|
|
VDI vdi = pool.Connection.Resolve(vdiRef);
|
|
if (vdi == null)
|
|
continue;
|
|
SR sr = pool.Connection.Resolve(vdi.SR);
|
|
if (sr == null)
|
|
continue;
|
|
if (sr == heartbeatSRs[j])
|
|
{
|
|
l.ForeColor = Color.Green;
|
|
l.Text = Messages.HA_HEARTBEAT_HEALTHY;
|
|
break;
|
|
}
|
|
}
|
|
tableLatencies.Controls.Add(l);
|
|
tableLatencies.SetCellPosition(l, new TableLayoutPanelCellPosition((2 * j) + 2, i + 1));
|
|
tableLatencies.SetColumnSpan(l, 2);
|
|
}
|
|
}
|
|
|
|
tableLatencies.ResumeLayout();
|
|
tableLatencies.PerformLayout();
|
|
}
|
|
|
|
private static string getNtol(Pool pool)
|
|
{
|
|
long ntol = pool.ha_host_failures_to_tolerate;
|
|
if (ntol > 0)
|
|
{
|
|
return ntol.ToString();
|
|
}
|
|
else
|
|
{
|
|
return Messages.HA_NTOL_ZERO_HAPAGE;
|
|
}
|
|
}
|
|
|
|
private static string getPlanExistsFor(Pool pool)
|
|
{
|
|
long plan = pool.ha_plan_exists_for;
|
|
long ntol = pool.ha_host_failures_to_tolerate;
|
|
return plan == ntol ? plan.ToString() : String.Format(Messages.HA_OVERCOMMITTED_NTOL, plan);
|
|
}
|
|
|
|
private static bool haStatusRed(Pool pool)
|
|
{
|
|
return pool.ha_host_failures_to_tolerate > pool.ha_plan_exists_for;
|
|
}
|
|
|
|
private void generateSRHABox(SR sr)
|
|
{
|
|
CustomListRow header = CreateHeader(Messages.HA_CONFIGURATION_TITLE);
|
|
customListPanel.AddRow(header);
|
|
|
|
// We could do something here, e.g.
|
|
// HB SR for pool: xyz
|
|
// latency to h1: 123
|
|
// latency to h2: 456
|
|
// HB SR for pool: abc
|
|
// latency to j1: 123
|
|
// latency to j2: 456
|
|
|
|
AddBottomSpacing(header);
|
|
}
|
|
|
|
private void GP_ContextShow(object sender, ListPanelItemClickedEventArgs e)
|
|
{
|
|
ContextMenuStrip menu = new ContextMenuStrip();
|
|
foreach (ToolStripMenuItem item in e.Item.Row.MenuItems)
|
|
{
|
|
menu.Items.Add(item);
|
|
}
|
|
ToolStripMenuItem copyItem = new ToolStripMenuItem(Messages.COPY) { Image = Properties.Resources.copy_16 };
|
|
copyItem.Click += delegate
|
|
{
|
|
String text = Helpers.ToWindowsLineEndings(e.Item.Tag != null ? e.Item.Tag.ToString() : e.Item.Text);
|
|
Clip.SetClipboardText(text);
|
|
};
|
|
menu.Items.Add(copyItem);
|
|
menu.Show(this, PointToClient(MousePosition));
|
|
}
|
|
|
|
private static CustomListRow CreateHeader(string text)
|
|
{
|
|
return new CustomListRow(text, BaseTabPage.HeaderBackColor, BaseTabPage.HeaderForeColor, BaseTabPage.HeaderBorderColor, Program.DefaultFontHeader);
|
|
}
|
|
|
|
private static CustomListRow AddRow<T>(CustomListRow parent, string key, T obj, ToStringDelegate<T> del, bool valueBold) where T : IEquatable<T>
|
|
{
|
|
CustomListRow newChild = CreateNewRow(key + ": ", new ToStringWrapper<T>(obj, del), valueBold);
|
|
parent.AddChild(newChild);
|
|
return newChild;
|
|
}
|
|
|
|
private static CustomListRow CreateNewRow<T>(string key, ToStringWrapper<T> value, bool valueBold) where T : IEquatable<T>
|
|
{
|
|
CustomListItem Label = new CustomListItem(key, BaseTabPage.ItemLabelFont, BaseTabPage.ItemLabelForeColor);
|
|
Label.Anchor = AnchorStyles.Top;
|
|
Label.itemBorder.Bottom = BaseTabPage.ITEM_SPACING;
|
|
CustomListItem Value = new CustomListItem(value, valueBold ? BaseTabPage.ItemValueFontBold : BaseTabPage.ItemValueFont, BaseTabPage.ItemValueForeColor);
|
|
Value.Anchor = AnchorStyles.Top;
|
|
Value.itemBorder.Bottom = BaseTabPage.ITEM_SPACING;
|
|
return new CustomListRow(BaseTabPage.ItemBackColor, Label, Value);
|
|
}
|
|
|
|
private static void AddBottomSpacing(CustomListRow header)
|
|
{
|
|
if (header.Children.Count != 0)
|
|
{
|
|
header.AddChild(new CustomListRow(header.BackColor, 5));
|
|
}
|
|
}
|
|
|
|
private static string GetFriendlyName(string propertyName)
|
|
{
|
|
return XenAdmin.Core.PropertyManager.GetFriendlyName(string.Format("Label-{0}", propertyName));
|
|
}
|
|
|
|
private void buttonConfigure_Click(object sender, EventArgs e)
|
|
{
|
|
if (pool == null)
|
|
return;
|
|
EditHA(pool);
|
|
}
|
|
|
|
private void buttonDisableHa_Click(object sender, EventArgs e)
|
|
{
|
|
if (pool == null)
|
|
return;
|
|
|
|
if (!pool.ha_enabled)
|
|
{
|
|
// Start HA wizard
|
|
EditHA(pool);
|
|
return;
|
|
}
|
|
|
|
// Confirm the user wants to disable HA
|
|
using (var dlg = new ThreeButtonDialog(
|
|
new ThreeButtonDialog.Details(
|
|
null,
|
|
string.Format(Messages.HA_DISABLE_QUERY, Helpers.GetName(pool).Ellipsise(30)),
|
|
Messages.DISABLE_HA),
|
|
"HADisable",
|
|
ThreeButtonDialog.ButtonYes,
|
|
ThreeButtonDialog.ButtonNo))
|
|
{
|
|
if (dlg.ShowDialog(this) != DialogResult.Yes)
|
|
return;
|
|
}
|
|
|
|
DisableHAAction action = new DisableHAAction(pool);
|
|
// We will need to re-enable buttons when the action completes
|
|
action.Completed += Program.MainWindow.action_Completed;
|
|
action.RunAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Shows the appropriate dialog (HA wizard if HA is disabled for the pool, or VM restart priority editor otherwise).
|
|
/// </summary>
|
|
/// <param name="pool">Must not be null.</param>
|
|
internal static void EditHA(Pool pool)
|
|
{
|
|
// Do nothing if there is an HA action in progress
|
|
if (HelpersGUI.FindActiveHaAction(pool.Connection) != null)
|
|
{
|
|
log.Debug("Not opening HA dialog: an HA action is in progress");
|
|
return;
|
|
}
|
|
|
|
if (!pool.Connection.IsConnected)
|
|
{
|
|
log.Debug("Not opening HA dialog: the connection to the pool is now closed");
|
|
return;
|
|
}
|
|
|
|
if (pool.ha_enabled)
|
|
{
|
|
// Show VM restart priority editor
|
|
Program.MainWindow.ShowPerConnectionWizard(pool.Connection, new EditVmHaPrioritiesDialog(pool));
|
|
}
|
|
else
|
|
{
|
|
// Show wizard to enable HA
|
|
Program.MainWindow.ShowPerConnectionWizard(pool.Connection, new HAWizard(pool));
|
|
}
|
|
}
|
|
|
|
private void SetNetworkHBInitDelay()
|
|
{
|
|
initializationDelayElapsed = false;
|
|
|
|
//30 second delay to allow network HB status to initialize
|
|
initializationDelayTimer = new System.Timers.Timer(HB_INITIALIZATION_DELAY);
|
|
initializationDelayTimer.Elapsed += HeartbeatInitialization_TimeElapsed;
|
|
initializationDelayTimer.AutoReset = false;
|
|
initializationDelayTimer.Enabled = true;
|
|
}
|
|
|
|
private void HeartbeatInitialization_TimeElapsed(Object source, System.Timers.ElapsedEventArgs e)
|
|
{
|
|
initializationDelayElapsed = true;
|
|
Program.Invoke(Program.MainWindow, Rebuild);
|
|
}
|
|
}
|
|
}
|