mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-12-30 07:40:13 +01:00
425 lines
17 KiB
C#
425 lines
17 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.Text;
|
|||
|
using System.Windows.Forms;
|
|||
|
|
|||
|
using XenAdmin.Controls.Ballooning;
|
|||
|
using XenAdmin.Core;
|
|||
|
using XenAdmin.Network;
|
|||
|
using XenAPI;
|
|||
|
|
|||
|
|
|||
|
namespace XenAdmin.TabPages
|
|||
|
{
|
|||
|
public partial class BallooningPage : BaseTabPage
|
|||
|
{
|
|||
|
private const int ROW_GAP = 10;
|
|||
|
|
|||
|
public BallooningPage()
|
|||
|
{
|
|||
|
InitializeComponent();
|
|||
|
// http://alt.pluralsight.com/wiki/default.aspx/Craig.FlickerFreeControlDrawing
|
|||
|
SetStyle(ControlStyles.DoubleBuffer |
|
|||
|
ControlStyles.AllPaintingInWmPaint |
|
|||
|
ControlStyles.Opaque |
|
|||
|
ControlStyles.UserPaint, true);
|
|||
|
Text = Messages.DYNAMIC_MEMORY_CONTROL;
|
|||
|
Host_CollectionChangedWithInvoke = Program.ProgramInvokeHandler(Host_CollectionChanged);
|
|||
|
VM_CollectionChangedWithInvoke = Program.ProgramInvokeHandler(VM_CollectionChanged);
|
|||
|
}
|
|||
|
|
|||
|
IXenObject xenObject;
|
|||
|
List<Host> hosts = new List<Host>();
|
|||
|
List<VM> vms = new List<VM>();
|
|||
|
private readonly CollectionChangeEventHandler Host_CollectionChangedWithInvoke;
|
|||
|
public IXenObject XenObject
|
|||
|
{
|
|||
|
set
|
|||
|
{
|
|||
|
System.Diagnostics.Trace.Assert(value is Pool || value is Host || value is VM);
|
|||
|
xenObject = value;
|
|||
|
|
|||
|
UnregisterHandlers();
|
|||
|
|
|||
|
vms.Clear();
|
|||
|
hosts.Clear();
|
|||
|
|
|||
|
if (value is VM)
|
|||
|
vms.Add((VM)value);
|
|||
|
|
|||
|
else if (value is Host)
|
|||
|
{
|
|||
|
Host host = value as Host;
|
|||
|
hosts.Add(host);
|
|||
|
foreach (VM vm in host.Connection.Cache.VMs)
|
|||
|
{
|
|||
|
if (VMWanted(vm, host))
|
|||
|
vms.Add(vm);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
else // value is XenObject<Pool>
|
|||
|
{
|
|||
|
value.Connection.Cache.RegisterCollectionChanged<Host>(Host_CollectionChangedWithInvoke);
|
|||
|
hosts.AddRange(value.Connection.Cache.Hosts);
|
|||
|
}
|
|||
|
|
|||
|
value.Connection.XenObjectsUpdated += XenObjectsUpdated;
|
|||
|
|
|||
|
foreach (Host host in hosts)
|
|||
|
RegisterHostHandlers(host);
|
|||
|
|
|||
|
foreach (VM vm in vms)
|
|||
|
RegisterVMHandlers(vm);
|
|||
|
|
|||
|
Rebuild();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private bool VMWanted(VM vm, Host host)
|
|||
|
{
|
|||
|
return vm.is_a_real_vm && vm.Show(Properties.Settings.Default.ShowHiddenVMs) && vm.Home() == host;
|
|||
|
}
|
|||
|
|
|||
|
private readonly CollectionChangeEventHandler VM_CollectionChangedWithInvoke;
|
|||
|
private void RegisterHostHandlers(Host host)
|
|||
|
{
|
|||
|
host.PropertyChanged += host_PropertyChanged;
|
|||
|
host.Connection.Cache.RegisterCollectionChanged<VM>(VM_CollectionChangedWithInvoke);
|
|||
|
foreach (VM vm in host.Connection.Cache.VMs)
|
|||
|
RegisterAllVMHandlers(vm);
|
|||
|
}
|
|||
|
|
|||
|
private void host_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
|||
|
{
|
|||
|
if (e.PropertyName == "name_label" && hosts.Count >= 2)
|
|||
|
_rebuild_needed = true; // might change the sort order
|
|||
|
}
|
|||
|
|
|||
|
private void VM_CollectionChanged(object sender, CollectionChangeEventArgs e)
|
|||
|
{
|
|||
|
if (e.Action == CollectionChangeAction.Remove)
|
|||
|
{
|
|||
|
VM vm = e.Element as VM;
|
|||
|
UnregisterVMHandlers(vm);
|
|||
|
}
|
|||
|
XenObject = xenObject; // have to recalculate list of VMs etc.
|
|||
|
}
|
|||
|
|
|||
|
private void Host_CollectionChanged(object sender, CollectionChangeEventArgs e)
|
|||
|
{
|
|||
|
if (e.Action == CollectionChangeAction.Remove)
|
|||
|
{
|
|||
|
Host host = e.Element as Host;
|
|||
|
UnregisterHostHandlers(host);
|
|||
|
}
|
|||
|
XenObject = xenObject;
|
|||
|
}
|
|||
|
|
|||
|
private void RegisterVMHandlers(VM vm)
|
|||
|
{
|
|||
|
vm.PropertyChanged -= vm_PropertyChanged;
|
|||
|
vm.PropertyChanged += vm_PropertyChanged;
|
|||
|
RegisterVMGuestMetrics(vm);
|
|||
|
}
|
|||
|
|
|||
|
private void RegisterAllVMHandlers(VM vm)
|
|||
|
{
|
|||
|
vm.PropertyChanged -= vm_PropertyChanged_allVMs;
|
|||
|
vm.PropertyChanged += vm_PropertyChanged_allVMs;
|
|||
|
}
|
|||
|
|
|||
|
private void RegisterVMGuestMetrics(VM vm)
|
|||
|
{
|
|||
|
VM_guest_metrics guest_metrics = vm.Connection.Resolve(vm.guest_metrics);
|
|||
|
if (guest_metrics != null)
|
|||
|
{
|
|||
|
guest_metrics.PropertyChanged -= guest_metrics_PropertyChanged;
|
|||
|
guest_metrics.PropertyChanged += guest_metrics_PropertyChanged;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void UnregisterHandlers()
|
|||
|
{
|
|||
|
foreach (Host host in hosts)
|
|||
|
UnregisterHostHandlers(host);
|
|||
|
|
|||
|
if (xenObject != null)
|
|||
|
{
|
|||
|
Pool pool = xenObject as Pool;
|
|||
|
if (pool != null)
|
|||
|
pool.Connection.Cache.DeregisterCollectionChanged<Host>(Host_CollectionChangedWithInvoke);
|
|||
|
|
|||
|
xenObject.Connection.XenObjectsUpdated -= XenObjectsUpdated;
|
|||
|
foreach (VM vm in xenObject.Connection.Cache.VMs)
|
|||
|
UnregisterVMHandlers(vm);
|
|||
|
}
|
|||
|
|
|||
|
foreach (VM vm in vms)
|
|||
|
UnregisterVMHandlers(vm); // Should duplicate above line, but let's be safe
|
|||
|
}
|
|||
|
|
|||
|
private void UnregisterHostHandlers(Host host)
|
|||
|
{
|
|||
|
host.Connection.Cache.DeregisterCollectionChanged<VM>(VM_CollectionChangedWithInvoke);
|
|||
|
}
|
|||
|
|
|||
|
private void UnregisterVMHandlers(VM vm)
|
|||
|
{
|
|||
|
vm.PropertyChanged -= vm_PropertyChanged;
|
|||
|
vm.PropertyChanged -= vm_PropertyChanged_allVMs;
|
|||
|
VM_guest_metrics guest_metrics = vm.Connection.Resolve(vm.guest_metrics);
|
|||
|
if (guest_metrics != null)
|
|||
|
guest_metrics.PropertyChanged -= guest_metrics_PropertyChanged;
|
|||
|
}
|
|||
|
|
|||
|
public void IsHidden()
|
|||
|
{
|
|||
|
UnregisterHandlers();
|
|||
|
}
|
|||
|
|
|||
|
private void vm_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
|||
|
{
|
|||
|
if (e.PropertyName == "guest_metrics")
|
|||
|
RegisterVMGuestMetrics((VM)sender);
|
|||
|
|
|||
|
if (e.PropertyName == "memory_static_min" || e.PropertyName == "memory_static_max" ||
|
|||
|
e.PropertyName == "memory_dynamic_min" || e.PropertyName == "memory_dynamic_max" ||
|
|||
|
e.PropertyName == "metrics" || e.PropertyName == "guest_metrics")
|
|||
|
{
|
|||
|
// We could just redraw the row for this VM (and for the host), provided it doesn't share a row
|
|||
|
// with any other VMs before or after. But doesn't seem necessary to figure all that out.
|
|||
|
_rebuild_needed = true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void vm_PropertyChanged_allVMs(object sender, PropertyChangedEventArgs e)
|
|||
|
{
|
|||
|
// Only observe real VMs (but templates come through here too because
|
|||
|
// they change into real VMs during VM creation).
|
|||
|
if (!((VM)sender).is_a_real_vm)
|
|||
|
return;
|
|||
|
|
|||
|
// These are used by MainWindow.VMHome() to determine which host the VM belongs to
|
|||
|
if (e.PropertyName == "power_state" || e.PropertyName == "VBDs" || e.PropertyName == "affinity" ||
|
|||
|
|
|||
|
// These can change whether the VM is shown
|
|||
|
e.PropertyName == "name_label" || e.PropertyName == "other_config"|| e.PropertyName=="resident_on")
|
|||
|
{
|
|||
|
if (xenObject is Pool)
|
|||
|
{
|
|||
|
XenObject = xenObject; // have to recalculate list of VMs etc.
|
|||
|
return;
|
|||
|
}
|
|||
|
if (xenObject is Host)
|
|||
|
{
|
|||
|
bool vmWanted = VMWanted((VM)sender, (Host)xenObject);
|
|||
|
if (vmWanted != vms.Contains((VM)sender))
|
|||
|
{
|
|||
|
XenObject = xenObject; // have to recalculate list of VMs etc.
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
// We also have to redisplay if the power_state or name_label changes (can change the sort order)
|
|||
|
if ((e.PropertyName == "power_state" || e.PropertyName == "name_label") && vms.Contains((VM)sender))
|
|||
|
_rebuild_needed = true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void guest_metrics_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
|||
|
{
|
|||
|
if (e.PropertyName == "other")
|
|||
|
_rebuild_needed = true;
|
|||
|
}
|
|||
|
|
|||
|
// Whether a change to the properties of any object means we need to Rebuild.
|
|||
|
// This is in order to batch up changes to several objects at once (see CA-35330).
|
|||
|
private bool _rebuild_needed = false;
|
|||
|
private void XenObjectsUpdated(object sender, EventArgs e)
|
|||
|
{
|
|||
|
if (_rebuild_needed)
|
|||
|
Rebuild();
|
|||
|
}
|
|||
|
|
|||
|
private bool _rebuilding = false;
|
|||
|
|
|||
|
private void Rebuild()
|
|||
|
{
|
|||
|
Program.AssertOnEventThread();
|
|||
|
_rebuild_needed = false;
|
|||
|
if (!this.Visible)
|
|||
|
return;
|
|||
|
_rebuilding = true;
|
|||
|
pageContainerPanel.SuspendLayout();
|
|||
|
|
|||
|
// Store a list of the current controls. We remove them at the end because it makes less flicker that way.
|
|||
|
// While we're going through them, remember which VMs were on expanded rows.
|
|||
|
List<Control> oldControls = new List<Control>(pageContainerPanel.Controls.Count);
|
|||
|
List<VM> expandedVMs = new List<VM>(vms.Count);
|
|||
|
foreach (Control c in pageContainerPanel.Controls)
|
|||
|
{
|
|||
|
oldControls.Add(c);
|
|||
|
VMMemoryRow vmRow = c as VMMemoryRow;
|
|||
|
if (vmRow != null && vmRow.Expanded)
|
|||
|
expandedVMs.AddRange(vmRow.VMs);
|
|||
|
}
|
|||
|
|
|||
|
// Group VMs with the same settings
|
|||
|
Dictionary<MemSettings, List<VM>> settingsToVMs = new Dictionary<MemSettings,List<VM>>(); // all VMs with a particular setting
|
|||
|
List<MemSettings> listSettings = new List<MemSettings>(); // also make a list of MemSettings to preserve the order
|
|||
|
|
|||
|
vms.Sort();
|
|||
|
foreach (VM vm in vms)
|
|||
|
{
|
|||
|
MemSettings settings =
|
|||
|
vm.has_ballooning?
|
|||
|
new MemSettings(true, vm.power_state, vm.memory_static_min, vm.memory_static_max,
|
|||
|
vm.memory_dynamic_min, vm.memory_dynamic_max) :
|
|||
|
new MemSettings(false, vm.power_state, 0, vm.memory_static_max, 0, 0); // don't consider other mem settings if ballooning off
|
|||
|
if (!settingsToVMs.ContainsKey(settings)) // we've not seen these settings on another VM
|
|||
|
{
|
|||
|
settingsToVMs.Add(settings, new List<VM>());
|
|||
|
listSettings.Add(settings);
|
|||
|
}
|
|||
|
settingsToVMs[settings].Add(vm);
|
|||
|
}
|
|||
|
|
|||
|
// Add server rows
|
|||
|
int initScroll = pageContainerPanel.VerticalScroll.Value;
|
|||
|
int top = pageContainerPanel.Padding.Top - initScroll;
|
|||
|
hosts.Sort();
|
|||
|
foreach (Host host in hosts)
|
|||
|
{
|
|||
|
Host_metrics metrics = host.Connection.Resolve(host.metrics);
|
|||
|
if (metrics == null || !metrics.live)
|
|||
|
continue;
|
|||
|
AddRowToPanel(new HostMemoryRow(host), ref top);
|
|||
|
}
|
|||
|
|
|||
|
// Add VM rows.
|
|||
|
// Sort the rows first by power state then by usual sort order of first VM (because of vms.Sort() above).
|
|||
|
// Easier to traverse listSettings five times, but more complicated sorts could be achieved by listSettings.Sort().
|
|||
|
vm_power_state[] power_state_order = {vm_power_state.Running, vm_power_state.Paused, vm_power_state.Suspended, vm_power_state.Halted, vm_power_state.unknown};
|
|||
|
foreach (vm_power_state ps in power_state_order)
|
|||
|
{
|
|||
|
foreach (MemSettings settings in listSettings)
|
|||
|
{
|
|||
|
if (settings.power_state == ps)
|
|||
|
{
|
|||
|
List<VM> rowVMs = settingsToVMs[settings];
|
|||
|
bool expand = Helpers.ListsIntersect(expandedVMs, rowVMs); // expand header if any of its VMs were expanded before
|
|||
|
AddRowToPanel(new VMMemoryRow(rowVMs, expand), ref top);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Remove old controls
|
|||
|
foreach (Control c in oldControls)
|
|||
|
{
|
|||
|
pageContainerPanel.Controls.Remove(c);
|
|||
|
int scroll = initScroll;
|
|||
|
if (scroll > pageContainerPanel.VerticalScroll.Maximum)
|
|||
|
scroll = pageContainerPanel.VerticalScroll.Maximum;
|
|||
|
pageContainerPanel.VerticalScroll.Value = scroll; // Without this, the scroll bar can jump around while the page is being rebuilt
|
|||
|
c.Dispose();
|
|||
|
}
|
|||
|
_rebuilding = false;
|
|||
|
pageContainerPanel.ResumeLayout();
|
|||
|
ReLayout();
|
|||
|
}
|
|||
|
|
|||
|
private void ReLayout()
|
|||
|
{
|
|||
|
Program.AssertOnEventThread();
|
|||
|
if (_rebuilding)
|
|||
|
return;
|
|||
|
|
|||
|
int initScroll = pageContainerPanel.VerticalScroll.Value;
|
|||
|
int top = pageContainerPanel.Padding.Top - initScroll;
|
|||
|
foreach (Control row in pageContainerPanel.Controls)
|
|||
|
{
|
|||
|
row.Top = top;
|
|||
|
top += row.Height + ROW_GAP;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void row_Resize(object sender, EventArgs e)
|
|||
|
{
|
|||
|
ReLayout();
|
|||
|
}
|
|||
|
|
|||
|
private void AddRowToPanel(UserControl row, ref int top)
|
|||
|
{
|
|||
|
row.Top = top;
|
|||
|
row.Left = pageContainerPanel.Padding.Left - pageContainerPanel.HorizontalScroll.Value;
|
|||
|
SetRowWidth(row);
|
|||
|
row.Anchor = AnchorStyles.Top | AnchorStyles.Left;
|
|||
|
top += row.Height + ROW_GAP;
|
|||
|
row.Resize += row_Resize;
|
|||
|
pageContainerPanel.Controls.Add(row);
|
|||
|
}
|
|||
|
|
|||
|
private struct MemSettings
|
|||
|
{
|
|||
|
public readonly bool has_ballooning;
|
|||
|
public readonly vm_power_state power_state;
|
|||
|
public readonly long static_min, static_max, dynamic_min, dynamic_max;
|
|||
|
|
|||
|
public MemSettings(bool has_ballooning, vm_power_state power_state,
|
|||
|
long static_min, long static_max, long dynamic_min, long dynamic_max)
|
|||
|
{
|
|||
|
this.has_ballooning = has_ballooning;
|
|||
|
this.power_state = power_state;
|
|||
|
this.static_min = static_min;
|
|||
|
this.static_max = static_max;
|
|||
|
this.dynamic_min = dynamic_min;
|
|||
|
this.dynamic_max = dynamic_max;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void pageContainerPanel_SizeChanged(object sender, EventArgs e)
|
|||
|
{
|
|||
|
foreach (Control row in pageContainerPanel.Controls)
|
|||
|
SetRowWidth(row);
|
|||
|
}
|
|||
|
|
|||
|
private void SetRowWidth(Control row)
|
|||
|
{
|
|||
|
row.Width = pageContainerPanel.Width - pageContainerPanel.Padding.Left - 25; // It won't drop below row.MinimumSize.Width though
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|