mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-11-25 06:16:37 +01:00
f16b0fed35
Signed-off-by: Danilo Del Busso <danilo.delbusso@cloud.com>
893 lines
37 KiB
C#
893 lines
37 KiB
C#
/* Copyright (c) Cloud Software Group, Inc.
|
|
*
|
|
* 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.Core;
|
|
using XenAdmin.Wlb;
|
|
using XenAPI;
|
|
using XenAdmin.Actions;
|
|
using XenAdmin.Actions.Wlb;
|
|
using XenAdmin.Commands;
|
|
using XenAdmin.Network;
|
|
|
|
|
|
namespace XenAdmin.Controls.Wlb
|
|
{
|
|
public partial class WlbOptimizePool : UserControl
|
|
{
|
|
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
|
|
private readonly ListViewColumnSorter columnSorter = new ListViewColumnSorter();
|
|
|
|
private IXenObject _xenObject;
|
|
private Pool _pool;
|
|
private Dictionary<VM, WlbOptimizationRecommendation> _vmOptList = new Dictionary<VM, WlbOptimizationRecommendation>();
|
|
private Dictionary<XenRef<VM>, string[]> _recommendations = new Dictionary<XenRef<VM>, string[]>();
|
|
private string _optId = String.Empty;
|
|
private bool _autoOptEnabled = false;
|
|
private bool _powerManagementEnabled = false;
|
|
|
|
// TODO: change to proper report file name
|
|
private readonly string ReportFile = "pool_optimization_history";
|
|
|
|
// All used in the listview.
|
|
private readonly Font OperationTextFont;
|
|
private readonly Color OperationTextColor;
|
|
private readonly Color ShadedRowColor;
|
|
private readonly Color DisabledShadedRowColor;
|
|
|
|
// Listview minimum column widths
|
|
private int[] minimumColumnWidths = { 0, 25, 25, 25 };
|
|
|
|
/// <summary>
|
|
/// Initialize optimize pool listview
|
|
/// </summary>
|
|
public WlbOptimizePool()
|
|
{
|
|
InitializeComponent();
|
|
Host_CollectionChangedWithInvoke += Program.ProgramInvokeHandler(Host_CollectionChanged);
|
|
VM_CollectionChangedWithInvoke=Program.ProgramInvokeHandler(VM_CollectionChanged);
|
|
OperationTextFont = new Font(this.Font, FontStyle.Bold);
|
|
OperationTextColor = Color.Black;
|
|
ShadedRowColor = Color.FromArgb(230, 240, 255);
|
|
DisabledShadedRowColor = Color.FromArgb(224, 222, 205);
|
|
|
|
optimizePoolListView.SmallImageList = Images.ImageList16;
|
|
optimizePoolListView.ListViewItemSorter = columnSorter;
|
|
|
|
recommendationUpdateTimer = new System.Threading.Timer(TimerCallback, null, 0, 0);
|
|
StartTimer();
|
|
}
|
|
|
|
#region public methods
|
|
|
|
/// <summary>
|
|
/// Set XenModelOpject and populate optimize pool listview properly
|
|
/// </summary>
|
|
public IXenObject XenObject
|
|
{
|
|
set
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
if (_xenObject != null)
|
|
{
|
|
if (_xenObject is Pool)
|
|
{
|
|
this._pool.PropertyChanged -= Pool_PropertyChanged;
|
|
|
|
_pool.Connection.Cache.DeregisterCollectionChanged<XenAPI.Host>(Host_CollectionChangedWithInvoke);
|
|
foreach (Host host in _pool.Connection.Cache.Hosts)
|
|
{
|
|
host.PropertyChanged -= Host_PropertyChanged;
|
|
|
|
host.Connection.Cache.DeregisterCollectionChanged<VM>(VM_CollectionChangedWithInvoke);
|
|
foreach (VM vm in host.Connection.Cache.VMs)
|
|
{
|
|
vm.PropertyChanged -= VM_PropertyChanged;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_xenObject = value;
|
|
_pool = null;
|
|
|
|
if (_xenObject != null)
|
|
{
|
|
if (_xenObject is Pool)
|
|
{
|
|
_pool = (Pool)_xenObject;
|
|
|
|
this._pool.PropertyChanged += Pool_PropertyChanged;
|
|
|
|
_pool.Connection.Cache.RegisterCollectionChanged<XenAPI.Host>(Host_CollectionChangedWithInvoke);
|
|
foreach (Host host in _pool.Connection.Cache.Hosts)
|
|
{
|
|
host.PropertyChanged -= Host_PropertyChanged;
|
|
host.PropertyChanged += Host_PropertyChanged;
|
|
|
|
host.Connection.Cache.RegisterCollectionChanged<VM>(VM_CollectionChangedWithInvoke);
|
|
foreach (VM vm in host.Connection.Cache.VMs)
|
|
{
|
|
vm.PropertyChanged -= VM_PropertyChanged;
|
|
vm.PropertyChanged += VM_PropertyChanged;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BuildRecList();
|
|
}
|
|
}
|
|
|
|
public void SetOptControlProperties(bool autoOptEnabled, bool powerManagementEnabled)
|
|
{
|
|
_autoOptEnabled = autoOptEnabled;
|
|
_powerManagementEnabled = powerManagementEnabled;
|
|
|
|
this.SuspendLayout();
|
|
if (WlbServerState.GetState(_pool) == WlbServerState.ServerState.Enabled)
|
|
{
|
|
// Update listView VM/Host header text
|
|
this.optimizePoolListView.BeginUpdate();
|
|
this.columnHeader1.Text = Messages.WLB_OPT_HEADER_VMHOST;
|
|
this.optimizePoolListView.EndUpdate();
|
|
}
|
|
else
|
|
{
|
|
// Update listView VM/Host header text
|
|
this.optimizePoolListView.BeginUpdate();
|
|
this.columnHeader1.Text = Messages.WLB_OPT_HEADER_VM;
|
|
this.optimizePoolListView.EndUpdate();
|
|
}
|
|
|
|
this.ResumeLayout();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called from wlb page, used to disable/enable optimize pool controls properly when WLB is Enabled/Disabled
|
|
/// </summary>
|
|
/// <param name="disable">disable controls if it's true, else enable controls</param>
|
|
public void WLBOptDisable(bool disable)
|
|
{
|
|
optimizePoolListView.BeginUpdate();
|
|
if (disable)
|
|
{
|
|
this.statusLabel.Visible = false;
|
|
this.optimizePoolListView.Items.Clear();
|
|
this.optimizePoolListView.Visible = true;
|
|
this.optimizePoolListView.Enabled = false;
|
|
this.applyButton.Visible = true;
|
|
this.applyButton.Enabled = false;
|
|
}
|
|
else
|
|
{
|
|
if (!this.optimizePoolListView.Visible)
|
|
{
|
|
this.statusLabel.Visible = true;
|
|
this.optimizePoolListView.Items.Clear();
|
|
this.optimizePoolListView.Visible = true;
|
|
this.applyButton.Visible = true;
|
|
this.applyButton.Enabled = false;
|
|
}
|
|
else
|
|
{
|
|
this.optimizePoolListView.Enabled = true;
|
|
if (this.optimizePoolListView.Items.Count > 0)
|
|
this.applyButton.Enabled = true;
|
|
else
|
|
this.statusLabel.Visible = true;
|
|
}
|
|
}
|
|
optimizePoolListView.EndUpdate();
|
|
}
|
|
#endregion
|
|
|
|
#region Event Handlers
|
|
|
|
/// <summary>
|
|
/// Triggered when pool wlb enabled/disabled and optimize pool
|
|
/// </summary>
|
|
/// <param name="sender">XenObject<Pool></param>
|
|
/// <param name="e">PropertyChangedEventArgs</param>
|
|
private void Pool_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
Program.Invoke(this, delegate
|
|
{
|
|
if (_xenObject != null && Program.MainWindow.TheTabControl.SelectedTab == Program.MainWindow.TabPageWLB)
|
|
{
|
|
if (e.PropertyName == "wlb_enabled")
|
|
{
|
|
// enable/disble controls and display message properly
|
|
WLBOptDisable(!Helpers.WlbEnabled(((Pool)sender).Connection));
|
|
}
|
|
|
|
if (e.PropertyName == "other_config")
|
|
{
|
|
// check whether it's optimize pool event
|
|
if (((Pool)sender).other_config.ContainsKey(WlbOptimizationRecommendation.OPTIMIZINGPOOL))
|
|
{
|
|
BuildRecList();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triggered when hosts leave/join pool
|
|
/// </summary>
|
|
/// <param name="sender">object</param>
|
|
/// <param name="e">CollectionChangeEventArgs</param>
|
|
private void Host_CollectionChanged(object sender, CollectionChangeEventArgs e)
|
|
{
|
|
switch (e.Action)
|
|
{
|
|
case CollectionChangeAction.Add:
|
|
((Host)e.Element).PropertyChanged -= Host_PropertyChanged;
|
|
((Host)e.Element).PropertyChanged += Host_PropertyChanged;
|
|
break;
|
|
case CollectionChangeAction.Remove:
|
|
((Host)e.Element).PropertyChanged -= Host_PropertyChanged;
|
|
break;
|
|
}
|
|
// It's not necessary to call BuildRecList here
|
|
// because Remove/add Hosts also triger VM_CollectionChange,
|
|
}
|
|
|
|
/// <summary>
|
|
/// respond to host name change and vms leaving/joining the host.
|
|
/// </summary>
|
|
/// <param name="sender">XenObject<Host></param>
|
|
/// <param name="e">PropertyChangedEventArgs</param>
|
|
private void Host_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (_pool != null &&
|
|
Helpers.WlbEnabled(_pool.Connection) &&
|
|
applyButton.Enabled &&
|
|
IsHostOnListView(((Host)sender), false))
|
|
{
|
|
if (e.PropertyName == "name_label")
|
|
{
|
|
optimizePoolListView.BeginUpdate();
|
|
foreach (ListViewItem item in optimizePoolListView.Items)
|
|
{
|
|
UpdateRow(item);
|
|
}
|
|
optimizePoolListView.EndUpdate();
|
|
}
|
|
|
|
if ((e.PropertyName == "resident_VMs" && IsHostOnListView(((Host)sender), true))
|
|
|| (e.PropertyName == "enabled" && !((Host)sender).enabled))
|
|
{
|
|
Program.Invoke(this, delegate()
|
|
{
|
|
BuildRecList();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check whether Host property change relates to optimize pool list view. Will return true if the host is on the list view.
|
|
/// Must be called on the event thread.
|
|
/// </summary>
|
|
/// <param name="host">XenObject<Host></param>
|
|
/// <param name="checkVMHost">Whether to check VM host</param>
|
|
/// <returns>True if host is on the optimize pool list view, else return false</returns>
|
|
private bool IsHostOnListView(Host host, bool checkVMHost)
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
bool onList = false;
|
|
|
|
// should find if any vms that not on host but in listview
|
|
foreach (ListViewItem item in optimizePoolListView.Items)
|
|
{
|
|
Host fromHost = ((WlbOptimizationRecommendation)item.Tag).fromHost;
|
|
Host toHost = ((WlbOptimizationRecommendation)item.Tag).toHost;
|
|
VM vm = ((WlbOptimizationRecommendation)item.Tag).vm;
|
|
|
|
// whether check vm's host changes
|
|
if (checkVMHost)
|
|
{
|
|
// update optimize pool listview if vm from host is to move to host
|
|
if (vm.Connection.Resolve(vm.resident_on) == toHost)
|
|
{
|
|
onList = true;
|
|
}
|
|
}
|
|
// whether host is related to optimize pool listview
|
|
else if ((fromHost != null && fromHost == host)
|
|
|| (toHost != null && toHost== host))
|
|
{
|
|
onList = true;
|
|
}
|
|
}
|
|
return onList;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triggered when vm collection is changed, pool is connected and optimize pool listview is visiable.
|
|
/// </summary>
|
|
/// <param name="sender">object</param>
|
|
/// <param name="e">CollectionChangeEventArgs</param>
|
|
private void VM_CollectionChanged(object sender, CollectionChangeEventArgs e)
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
switch (e.Action)
|
|
{
|
|
case CollectionChangeAction.Add:
|
|
((VM)e.Element).PropertyChanged -= VM_PropertyChanged;
|
|
((VM)e.Element).PropertyChanged += VM_PropertyChanged;
|
|
break;
|
|
case CollectionChangeAction.Remove:
|
|
((VM)e.Element).PropertyChanged -= VM_PropertyChanged;
|
|
break;
|
|
}
|
|
|
|
// only continue if pool is connect and pool is not null
|
|
if (_xenObject == null || !_xenObject.Connection.IsConnected)
|
|
return;
|
|
|
|
// only update recommendation listview if applyButton is enabled and a vm has been removed from collection
|
|
if (applyButton.Enabled && e.Action == CollectionChangeAction.Remove)
|
|
BuildRecList();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Triggered when vm state is changed
|
|
/// </summary>
|
|
/// <param name="sender">XenObject<VM></param>
|
|
/// <param name="e">PropertyChangedEventArgs</param>
|
|
private void VM_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
Program.Invoke(Program.MainWindow, () =>
|
|
{
|
|
|
|
// Find row for VM
|
|
ListViewItem item = FindItemFromVM((VM) sender);
|
|
if (item != null)
|
|
{
|
|
optimizePoolListView.BeginUpdate();
|
|
try
|
|
{
|
|
UpdateRow(item);
|
|
}
|
|
finally
|
|
{
|
|
optimizePoolListView.EndUpdate();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the ListViewItem for the given VM. Will return null if no corresponding item could be found.
|
|
/// Must be called on the event thread.
|
|
/// </summary>
|
|
/// <param name="vm">XenObject<VM></param>
|
|
/// <returns>Return the listview item when found, null if nothing is found</returns>
|
|
private ListViewItem FindItemFromVM(VM vm)
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
foreach (ListViewItem item in optimizePoolListView.Items)
|
|
{
|
|
if (((WlbOptimizationRecommendation)item.Tag).vm == vm)
|
|
{
|
|
return item;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply optimization to a pool
|
|
/// </summary>
|
|
/// <param name="sender">object</param>
|
|
/// <param name="e">EventArgs</param>
|
|
private void ButtonApply_Click(object sender, EventArgs e)
|
|
{
|
|
if (_pool == null || _vmOptList == null)
|
|
return;
|
|
|
|
applyButton.Enabled = false;
|
|
|
|
action = HelpersGUI.FindActiveOptAction(_xenObject.Connection);
|
|
if ((action != null && action.GetType() != typeof(WlbOptimizePoolAction)) || action == null)
|
|
{
|
|
new WlbOptimizePoolAction(_pool, _vmOptList, _optId).RunAsync();
|
|
Program.MainWindow.UpdateToolbars();
|
|
}
|
|
}
|
|
|
|
private void linkLabelReportHistory_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
|
{
|
|
new ViewWorkloadReportsCommand(Program.MainWindow, _pool, ReportFile, true).Run();
|
|
}
|
|
|
|
private static void UpdateRow(ListViewItem row)
|
|
{
|
|
|
|
try // In try block to fix CA-40197
|
|
{
|
|
|
|
Program.AssertOnEventThread();
|
|
|
|
WlbOptimizationRecommendation optVMmSetting = (WlbOptimizationRecommendation)row.Tag;
|
|
|
|
if (String.IsNullOrEmpty(optVMmSetting.toHost.name_label) || String.IsNullOrEmpty(optVMmSetting.fromHost.name_label))
|
|
{
|
|
row.ImageIndex = (int)Images.GetIconFor(optVMmSetting.toHost ?? optVMmSetting.fromHost);
|
|
row.SubItems[1].Text = Helpers.GetName(optVMmSetting.toHost ?? optVMmSetting.fromHost);
|
|
row.SubItems[2].Text = optVMmSetting.toHost != null ? Messages.WLB_OPT_OPERATION_HOST_POWERON : Messages.WLB_OPT_REASON_POWEROFF;
|
|
}
|
|
else
|
|
{
|
|
row.ImageIndex = (int)Images.GetIconFor(optVMmSetting.vm);
|
|
row.SubItems[1].Text = optVMmSetting.vm.name_label;
|
|
row.SubItems[2].Text = String.Format(Messages.WLB_OPT_OPERATION_VM, optVMmSetting.fromHost.name_label, optVMmSetting.toHost.name_label);
|
|
}
|
|
|
|
row.SubItems[3].Text = optVMmSetting.reason;
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
log.Error(ex, ex);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Refresh Timer
|
|
private System.Threading.Timer recommendationUpdateTimer;
|
|
private const int RECOMMENDATION_INITIAL_INTERVAL = 1000 * 5; // 5 seconds interval
|
|
// Default to 2 minutes due to Kirkwood recommendation is generated every 2 minutes
|
|
private const int RECOMMENDATION_UPDATE_INTERVAL = 1000 * 30; // 30 seconds interval
|
|
AsyncAction action = null;
|
|
|
|
/// <summary>
|
|
/// start timer with 5 seconds interval
|
|
/// </summary>
|
|
private void StartTimer()
|
|
{
|
|
recommendationUpdateTimer.Change(RECOMMENDATION_INITIAL_INTERVAL, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Timer call back, used to refresh optimize pool listview.
|
|
/// Dispose if program exits, do nothing if wlb tab is not focused
|
|
/// </summary>
|
|
/// <param name="o">object</param>
|
|
private void TimerCallback(object o)
|
|
{
|
|
if (recommendationUpdateTimer == null)
|
|
return;
|
|
|
|
if (Program.Exiting)
|
|
{
|
|
recommendationUpdateTimer.Dispose();
|
|
return;
|
|
}
|
|
|
|
if (Program.MainWindow != null && Program.MainWindow.WlbPage != null)
|
|
{
|
|
Program.Invoke(Program.MainWindow, delegate()
|
|
{
|
|
if (_xenObject != null && Program.MainWindow.TheTabControl.SelectedTab == Program.MainWindow.TabPageWLB
|
|
&& _pool != null &&_pool.Connection.IsConnected && _pool.Connection.Session != null)
|
|
{
|
|
BuildRecList();
|
|
}
|
|
});
|
|
}
|
|
recommendationUpdateTimer.Change(RECOMMENDATION_UPDATE_INTERVAL, 0);
|
|
}
|
|
#endregion
|
|
|
|
|
|
#region load optimizePoolListView properly
|
|
/// <summary>
|
|
/// Build optimize pool list view properly
|
|
/// </summary>
|
|
private void BuildRecList()
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
if (_xenObject == null || _xenObject.Connection is XenConnection conn && conn.IsSimulatedConnection)
|
|
return;
|
|
|
|
if (Helpers.WlbEnabled(_xenObject.Connection))
|
|
{
|
|
try
|
|
{
|
|
if (_xenObject is Pool)
|
|
{
|
|
_pool = (Pool)_xenObject;
|
|
|
|
// get any active WLB action
|
|
action = HelpersGUI.FindActiveWLBAction(_xenObject.Connection);
|
|
|
|
// make sure we are not initializing, starting or stopping WLB
|
|
if (action is DisableWLBAction || action is EnableWLBAction || action is InitializeWLBAction)
|
|
return;
|
|
|
|
optimizePoolListView.BeginUpdate();
|
|
|
|
// check whether optimize pool is running before load optimize pool listview
|
|
if ((action != null && action is WlbOptimizePoolAction)
|
|
|| (action == null && _pool.other_config.ContainsKey(WlbOptimizationRecommendation.OPTIMIZINGPOOL)
|
|
&& _pool.other_config[WlbOptimizationRecommendation.OPTIMIZINGPOOL] == Messages.IN_PROGRESS))
|
|
{
|
|
//statusLabel.Text = Messages.WLB_OPT_OPTIMIZING;
|
|
this.applyButton.Text = Messages.WLB_OPT_OPTIMIZING;
|
|
EnableControls(false, false);
|
|
}
|
|
else if (action as WlbRetrieveRecommendationsAction == null)
|
|
{
|
|
this.applyButton.Text = Messages.WLB_OPT_APPLY_RECOMMENDATIONS;
|
|
// retrieve recommendations, and load optimize pool listview properly
|
|
var optAction = new WlbRetrieveRecommendationsAction(_pool);
|
|
optAction.Completed += this.OptRecRetrieveAction_Completed;
|
|
optAction.RunAsync();
|
|
}
|
|
}
|
|
}
|
|
catch (Failure f)
|
|
{
|
|
statusLabel.Text = Messages.WLB_OPT_LOADING_ERROR;
|
|
log.Error(f, f);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
statusLabel.Text = Messages.WLB_OPT_LOADING_ERROR;
|
|
log.Error($"There was an error calling retrieve_wlb_recommendations on pool {_pool.name_label}.", e);
|
|
}
|
|
|
|
finally
|
|
{
|
|
optimizePoolListView.EndUpdate();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.WLBOptDisable(true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieve wlb optimize pool recommendation action complete handler.
|
|
/// Populate optimize pool listview and enable controls properly.
|
|
/// </summary>
|
|
protected void OptRecRetrieveAction_Completed(ActionBase sender)
|
|
{
|
|
if (!(sender is WlbRetrieveRecommendationsAction asyncAction))
|
|
return;
|
|
|
|
asyncAction.Completed -= OptRecRetrieveAction_Completed;
|
|
|
|
_recommendations = asyncAction.Recommendations;
|
|
|
|
if (_recommendations != null && IsGoodRecommendation(_recommendations) && _xenObject.Connection == asyncAction.Connection)
|
|
{
|
|
Program.Invoke(this, delegate ()
|
|
{
|
|
PopulateData(_recommendations);
|
|
|
|
// In case optimizePoolListView is empty
|
|
if (optimizePoolListView.Items.Count == 0)
|
|
{
|
|
statusLabel.Text = Messages.WLB_OPT_POOL_NO_RECOMMENDATION;
|
|
EnableControls(true, false);
|
|
}
|
|
else
|
|
EnableControls(false, true);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
Program.Invoke(this, delegate ()
|
|
{
|
|
statusLabel.Text = Messages.WLB_OPT_POOL_NO_RECOMMENDATION;
|
|
EnableControls(true, false);
|
|
});
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if there are recommendations and verify if recommendations are valid and good;
|
|
/// return false if no valid recommendations
|
|
/// </summary>
|
|
/// <param name="recommendations">Optimize pool recommendations</param>
|
|
/// <returns>true if recommendations are valid, else return false</returns>
|
|
private bool IsGoodRecommendation(Dictionary<XenRef<VM>, string[]> recommendations)
|
|
{
|
|
bool goodRec = false;
|
|
|
|
if (recommendations != null)
|
|
{
|
|
foreach (KeyValuePair<XenRef<VM>, string[]> rec in recommendations)
|
|
{
|
|
// make sure at least one recommendation is good
|
|
if (rec.Value[(int)RecProperties.WLB].Trim().ToLower() == "wlb")
|
|
{
|
|
// get opt id from new recommendations
|
|
_optId = rec.Value[(int)RecProperties.OptId];
|
|
|
|
// get opt id of the most recent optimized action from pool's other configuration property
|
|
string optimizeId = String.Empty;
|
|
(Helpers.GetOtherConfig(this._pool)).TryGetValue(WlbOptimizationRecommendation.OPTIMIZINGPOOL, out optimizeId);
|
|
|
|
// make sure pool's recommendations are not old/just optimized recommendations
|
|
if (optimizeId != _optId)
|
|
{
|
|
goodRec = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return goodRec;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks to see if all the wlb recommendations are for PowerOff or PowerOn operations
|
|
/// </summary>
|
|
/// <param name="recommendations"></param>
|
|
/// <returns></returns>
|
|
private bool IsPowerOnlyRecommendation(Dictionary<XenRef<VM>, string[]> recommendations)
|
|
{
|
|
bool powerOnly = true;
|
|
|
|
if (recommendations != null)
|
|
{
|
|
foreach (KeyValuePair<XenRef<VM>, string[]> rec in recommendations)
|
|
{
|
|
if (rec.Value[(int)RecProperties.WLB].Trim().ToLower() == "wlb" &&
|
|
!(rec.Value[(int)RecProperties.Reason].Trim().ToLower().StartsWith("power")))
|
|
{
|
|
powerOnly = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return powerOnly;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populate optimize pool listview properly
|
|
/// </summary>
|
|
/// <param name="recommendations">optimize pool recommendations</param>
|
|
private void PopulateData(Dictionary<XenRef<VM>, string[]> recommendations)
|
|
{
|
|
Program.AssertOnEventThread();
|
|
|
|
optimizePoolListView.BeginUpdate();
|
|
try
|
|
{
|
|
if (_vmOptList != null)
|
|
_vmOptList.Clear();
|
|
|
|
optimizePoolListView.Items.Clear();
|
|
List<ListViewItem> newItems = new List<ListViewItem>();
|
|
|
|
WlbOptimizationRecommendationCollection sortedRecommendations = new WlbOptimizationRecommendationCollection(_pool, recommendations);
|
|
|
|
// WLB: recommendation return string format ["WLB"; vm/vm dom0; optId; recID; reason]
|
|
|
|
foreach (WlbOptimizationRecommendation optVmSetting in sortedRecommendations)
|
|
{
|
|
_optId = optVmSetting.optId.ToString();
|
|
_vmOptList.Add(optVmSetting.vm, optVmSetting);
|
|
|
|
ListViewItem row = new ListViewItem();
|
|
row.Tag = optVmSetting;
|
|
newItems.Add(row);
|
|
|
|
row.SubItems.Add(new ListViewItem.ListViewSubItem());
|
|
row.SubItems.Add(new ListViewItem.ListViewSubItem());
|
|
row.SubItems.Add(new ListViewItem.ListViewSubItem());
|
|
row.SubItems.Add(new ListViewItem.ListViewSubItem());
|
|
|
|
// Power on/off hosts
|
|
if (optVmSetting.toHost == null)
|
|
{
|
|
row.ImageIndex = (int)Images.GetIconFor(optVmSetting.toHost != null ? optVmSetting.toHost : optVmSetting.fromHost);
|
|
row.SubItems[1].Text = Helpers.GetName(optVmSetting.toHost != null ? optVmSetting.toHost : optVmSetting.fromHost);
|
|
row.SubItems[2].Text = optVmSetting.powerOperation;
|
|
}
|
|
else // VMs
|
|
{
|
|
row.ImageIndex = (int)Images.GetIconFor(optVmSetting.vm);
|
|
row.SubItems[1].Text = Helpers.GetName(optVmSetting.vm);
|
|
row.SubItems[2].Text = String.Format(Messages.WLB_OPT_OPERATION_VM, Helpers.GetName(optVmSetting.fromHost), Helpers.GetName(optVmSetting.toHost));
|
|
}
|
|
row.SubItems[3].Text = optVmSetting.reason;
|
|
}
|
|
|
|
optimizePoolListView.Items.AddRange(newItems.ToArray());
|
|
}
|
|
finally
|
|
{
|
|
optimizePoolListView.EndUpdate();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enable optimize pool controls properly
|
|
/// </summary>
|
|
private void EnableControls(bool enableLabel, bool enableButton)
|
|
{
|
|
// Rules for enabling the button:
|
|
// Passed RBAC checks, and
|
|
// If we are not in automatic mode, or
|
|
// ( We are in automatic mode, and
|
|
// We are in automated mode but PowerManagement is not enabled, and
|
|
// All recommdations are power-related)
|
|
|
|
if (enableLabel)
|
|
{
|
|
optimizePoolListView.Items.Clear();
|
|
}
|
|
optimizePoolListView.Visible = true;
|
|
optimizePoolListView.Enabled = PassedRbacChecks();
|
|
|
|
statusLabel.Visible = enableLabel;
|
|
|
|
applyButton.Visible = true;
|
|
if (PassedRbacChecks() &&
|
|
(!_autoOptEnabled ||
|
|
(_autoOptEnabled && !_powerManagementEnabled && IsPowerOnlyRecommendation(_recommendations))))
|
|
{
|
|
applyButton.Enabled = enableButton;
|
|
}
|
|
else
|
|
{
|
|
applyButton.Enabled = false;
|
|
}
|
|
|
|
}
|
|
|
|
private RbacMethodList WLB_PERMISSION_CHECKS = new RbacMethodList(
|
|
"pool.initialize_wlb",
|
|
"pool.set_wlb_enabled",
|
|
"pool.send_wlb_configuration"
|
|
);
|
|
|
|
private readonly CollectionChangeEventHandler Host_CollectionChangedWithInvoke;
|
|
private readonly CollectionChangeEventHandler VM_CollectionChangedWithInvoke;
|
|
|
|
private bool PassedRbacChecks()
|
|
{
|
|
return Role.CanPerform(WLB_PERMISSION_CHECKS, this._pool.Connection, out _);
|
|
}
|
|
#endregion
|
|
|
|
|
|
#region OptimizePoolListView item drawing event and colum event handlers
|
|
/// <summary>
|
|
/// Draw default column header
|
|
/// </summary>
|
|
/// <param name="sender">object</param>
|
|
/// <param name="e">DrawListViewColumnHeaderEventArgs</param>
|
|
private void OptimizePoolListView_DrawColumnHeader(object sender, DrawListViewColumnHeaderEventArgs e)
|
|
{
|
|
// Draw default column header
|
|
e.DrawDefault = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw items on optimize pool listview properly
|
|
/// </summary>
|
|
/// <param name="sender">object</param>
|
|
/// <param name="e">DrawListViewItemEventArgs</param>
|
|
private void OptimizePoolListView_DrawItem(object sender, DrawListViewItemEventArgs e)
|
|
{
|
|
// Fill the full width of the control, not just to the end of the column headers.
|
|
Rectangle r = e.Bounds;
|
|
int i = 0;
|
|
foreach (ColumnHeader col in optimizePoolListView.Columns)
|
|
{
|
|
i += col.Width;
|
|
}
|
|
// Fill from the right of the rightmost item to the end of the control
|
|
r.Width = Math.Max(i, optimizePoolListView.ClientSize.Width);
|
|
r.X = i;
|
|
|
|
using (Brush backBrush = new SolidBrush(e.Item.Index % 2 == 1 ? ShadedRowColor : SystemColors.Window))
|
|
e.Graphics.FillRectangle(backBrush, r);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw sub items in optimize pool list view
|
|
/// </summary>
|
|
/// <param name="sender">object</param>
|
|
/// <param name="e">DrawListViewSubItemEventArgs</param>
|
|
private void OptimizePoolListView_DrawSubItem(object sender, DrawListViewSubItemEventArgs e)
|
|
{
|
|
Rectangle rect = e.Bounds;
|
|
using (Brush backBrush = new SolidBrush(e.Item.Index % 2 == 1 ? ShadedRowColor : SystemColors.Window))
|
|
e.Graphics.FillRectangle(backBrush, rect);
|
|
|
|
Color fontColor = Color.Black;
|
|
|
|
if (e.ColumnIndex == 2)
|
|
{
|
|
TextRenderer.DrawText(e.Graphics, e.Item.SubItems[e.ColumnIndex].Text, OperationTextFont, e.Bounds, OperationTextColor, TextFormatFlags.EndEllipsis);
|
|
}
|
|
else if (e.ColumnIndex == 3)
|
|
{
|
|
TextRenderer.DrawText(e.Graphics, e.Item.SubItems[e.ColumnIndex].Text, this.Font, e.Bounds, fontColor, TextFormatFlags.EndEllipsis);
|
|
}
|
|
else
|
|
{
|
|
DrawSubItem1(fontColor, e);
|
|
}
|
|
}
|
|
|
|
|
|
private void DrawSubItem1(Color fontColor, DrawListViewSubItemEventArgs e)
|
|
{
|
|
Image icon = e.Item.ImageList.Images[e.Item.ImageIndex];
|
|
Point p = new Point(e.Bounds.Left, e.Bounds.Top + (e.Bounds.Height - icon.Height) / 2);
|
|
|
|
// Draw normally
|
|
e.Graphics.DrawImageUnscaled(icon, p);
|
|
|
|
Point p2 = new Point(p.X + icon.Width + 2, p.Y);
|
|
Rectangle r = new Rectangle(p2.X, p2.Y, e.Bounds.Right - p2.X, e.Bounds.Height);
|
|
TextRenderer.DrawText(e.Graphics, e.Item.SubItems[e.ColumnIndex].Text, this.Font, r, fontColor, TextFormatFlags.EndEllipsis);
|
|
}
|
|
|
|
private void optimizePoolListView_ColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e)
|
|
{
|
|
//Maintain a minimum column widths
|
|
|
|
|
|
if (minimumColumnWidths.Length>=e.ColumnIndex &&
|
|
optimizePoolListView.Columns[e.ColumnIndex].Width < minimumColumnWidths[e.ColumnIndex])
|
|
{
|
|
optimizePoolListView.Columns[e.ColumnIndex].Width = minimumColumnWidths[e.ColumnIndex];
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
|
|
|
|
}
|
|
}
|