/* 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.Text; using System.Windows.Forms; using XenAPI; using XenAdmin.Controls; using XenAdmin.Controls.Ballooning; using XenAdmin.Core; namespace XenAdmin.Wizards.NewVMWizard { public partial class Page_CpuMem : XenTabPage { private VM Template; // number of spinners to show enum MemoryMode { JustMemory = 1, MinimumAndMaximum = 2, MinimumMaximumAndStaticMax = 3 } MemoryMode memoryMode = MemoryMode.JustMemory; double memoryRatio = 0.0; // the permitted ratio of dynamic_min / static_max bool initialising = true; private bool isVcpuHotplugSupported; // Please note that the comboBoxVCPUs control can represent two different VM properties, depending whether the VM supports vCPU hotplug or not: // When vCPU hotplug is not supported, comboBoxVCPUs represents the initial number of vCPUs (VCPUs_at_startup). In this case we will also set the VM property VCPUs_max to the same value. // When vCPU hotplug is supported, comboBoxVCPUs represents the maximum number of vCPUs (VCPUs_max). And the initial number of vCPUs is represented in comboBoxInitialVCPUs (which is only visible in this case) public Page_CpuMem() { InitializeComponent(); } public override string Text { get { return Messages.NEWVMWIZARD_CPUMEMPAGE_NAME; } } public override string PageTitle { get { return Messages.NEWVMWIZARD_CPUMEMPAGE_TITLE; } } public override string HelpID { get { return "CPU&Memory"; } } protected override void PageLoadedCore(PageLoadedDirection direction) { if (SelectedTemplate == Template) return; initialising = true; Template = SelectedTemplate; if (Template.has_ballooning() && !Helpers.FeatureForbidden(Template, Host.RestrictDMC)) memoryMode = Template.memory_dynamic_max == Template.memory_static_max ? MemoryMode.MinimumAndMaximum : MemoryMode.MinimumMaximumAndStaticMax; else memoryMode = MemoryMode.JustMemory; memoryRatio = VMMemoryControlsEdit.GetMemoryRatio(Template); FreeSpinnerLimits(); if (memoryMode == MemoryMode.JustMemory) { spinnerDynMin.Initialize(Template.memory_static_max, Template.memory_static_max); labelDynMin.Text = Messages.MEMORY_COLON; } else { labelDynMin.Text = Messages.DYNAMIC_MIN_COLON; spinnerDynMin.Initialize(Template.memory_dynamic_min, Template.memory_static_max); FreeSpinnerLimits(); // same as CA-33831 spinnerDynMax.Initialize(Template.memory_dynamic_max, Template.memory_static_max); if (memoryMode == MemoryMode.MinimumMaximumAndStaticMax) { FreeSpinnerLimits(); spinnerStatMax.Initialize(Template.memory_static_max, Template.memory_static_max); } } labelDynMaxInfo.Visible = labelDynMax.Visible = spinnerDynMax.Visible = memoryMode == MemoryMode.MinimumAndMaximum || memoryMode == MemoryMode.MinimumMaximumAndStaticMax; labelStatMaxInfo.Visible = labelStatMax.Visible = spinnerStatMax.Visible = memoryMode == MemoryMode.MinimumMaximumAndStaticMax; isVcpuHotplugSupported = Template.SupportsVcpuHotplug(); _prevVCPUsMax = Template.VCPUs_max; // we use variable in RefreshCurrentVCPUs for checking if VcpusAtStartup and VcpusMax were equal before VcpusMax changed label5.Text = GetRubric(); InitialiseVcpuControls(); SetSpinnerLimitsAndIncrement(); ValuesUpdated(); initialising = false; } public override void SelectDefaultControl() { comboBoxVCPUs.Select(); } private void InitialiseVcpuControls() { labelVCPUs.Text = isVcpuHotplugSupported ? Messages.VM_CPUMEMPAGE_MAX_VCPUS_LABEL : Messages.VM_CPUMEMPAGE_VCPUS_LABEL; labelInitialVCPUs.Text = Messages.VM_CPUMEMPAGE_INITIAL_VCPUS_LABEL; labelInitialVCPUs.Visible = comboBoxInitialVCPUs.Visible = isVcpuHotplugSupported; comboBoxTopology.Populate(Template.VCPUs_at_startup, Template.VCPUs_max, Template.GetCoresPerSocket(), Template.MaxCoresPerSocket()); PopulateVCPUs(Template.MaxVCPUsAllowed(), isVcpuHotplugSupported ? Template.VCPUs_max : Template.VCPUs_at_startup); if (isVcpuHotplugSupported) PopulateVCPUsAtStartup(Template.VCPUs_max, Template.VCPUs_at_startup); } private void PopulateVCPUComboBox(ComboBox comboBox, long min, long max, long currentValue, Predicate isValid) { comboBox.BeginUpdate(); comboBox.Items.Clear(); for (long i = min; i <= max; ++i) { if (i == currentValue || isValid(i)) comboBox.Items.Add(i); } if (currentValue > max) comboBox.Items.Add(currentValue); comboBox.SelectedItem = currentValue; comboBox.EndUpdate(); } private void PopulateVCPUs(long maxVCPUs, long currentVCPUs) { PopulateVCPUComboBox(comboBoxVCPUs, 1, maxVCPUs, currentVCPUs, i => comboBoxTopology.IsValidVCPU(i)); } private void PopulateVCPUsAtStartup(long max, long currentValue) { PopulateVCPUComboBox(comboBoxInitialVCPUs, 1, max, currentValue, i => true); } private string GetRubric() { StringBuilder sb = new StringBuilder(); sb.Append(Messages.NEWVMWIZARD_CPUMEMPAGE_RUBRIC); // add hotplug text if (isVcpuHotplugSupported) sb.Append(Messages.VM_CPUMEMPAGE_RUBRIC_HOTPLUG); return sb.ToString(); } public VM SelectedTemplate { private get; set; } // Return the larger of the template's MaxMemAllowed and the template's static max, // to avoid crashes in the spinners (CA-40041). private long MaxMemAllowed { get { long msm = Template.memory_static_max; long mma = Template.MaxMemAllowed(); return (msm > mma ? msm : mma); } } private void FreeSpinnerLimits() { long maxMemAllowed = MaxMemAllowed; spinnerDynMin.SetRange(0, maxMemAllowed); spinnerDynMax.SetRange(0, maxMemAllowed); spinnerStatMax.SetRange(0, maxMemAllowed); } private void SetSpinnerLimitsAndIncrement() { spinnerDynMin.Increment = spinnerDynMax.Increment = spinnerStatMax.Increment = VMMemoryControlsEdit.CalcIncrement(Template.memory_static_max, spinnerDynMin.Units); long maxMemAllowed = MaxMemAllowed; double min = Template.memory_static_min; if (memoryMode == MemoryMode.JustMemory) { spinnerDynMin.SetRange(min, maxMemAllowed); ShowMemoryMinMaxInformation(labelDynMinInfo, min, maxMemAllowed); return; } long min2 = (long)(SelectedMemoryStaticMax * memoryRatio); if (min < min2) min = min2; double max = SelectedMemoryDynamicMax; if (max < min) max = min; spinnerDynMin.SetRange(min, max); ShowMemoryMinMaxInformation(labelDynMinInfo, min, max); spinnerDynMax.SetRange(SelectedMemoryDynamicMin, memoryMode == MemoryMode.MinimumAndMaximum ? maxMemAllowed : SelectedMemoryStaticMax); ShowMemoryMinMaxInformation(labelDynMaxInfo, SelectedMemoryDynamicMin, memoryMode == MemoryMode.MinimumAndMaximum ? maxMemAllowed : SelectedMemoryStaticMax); spinnerStatMax.SetRange(SelectedMemoryDynamicMax, maxMemAllowed); ShowMemoryMinMaxInformation(labelStatMaxInfo, SelectedMemoryDynamicMax, maxMemAllowed); } public void DisableMemoryControls() { spinnerDynMin.Enabled = false; spinnerDynMax.Enabled = false; spinnerStatMax.Enabled = false; } public double SelectedMemoryDynamicMin { get { return spinnerDynMin.Value; } } public double SelectedMemoryDynamicMax { get { return memoryMode == MemoryMode.JustMemory ? spinnerDynMin.Value : spinnerDynMax.Value; } } public double SelectedMemoryStaticMax { get { return memoryMode == MemoryMode.JustMemory ? spinnerDynMin.Value : memoryMode == MemoryMode.MinimumAndMaximum ? spinnerDynMax.Value : spinnerStatMax.Value; } } public long SelectedVcpusMax { get { return (long)comboBoxVCPUs.SelectedItem; } } public long SelectedVcpusAtStartup { get { return isVcpuHotplugSupported ? (long)comboBoxInitialVCPUs.SelectedItem : (long)comboBoxVCPUs.SelectedItem; } } public long SelectedCoresPerSocket { get { return comboBoxTopology.CoresPerSocket; } } public override List> PageSummary { get { List> sum = new List>(); if (isVcpuHotplugSupported) { sum.Add(new KeyValuePair(Messages.NEWVMWIZARD_CPUMEMPAGE_MAX_VCPUS, SelectedVcpusMax.ToString())); sum.Add(new KeyValuePair(Messages.NEWVMWIZARD_CPUMEMPAGE_INITIAL_VCPUS, SelectedVcpusAtStartup.ToString())); } else { sum.Add(new KeyValuePair(Messages.NEWVMWIZARD_CPUMEMPAGE_VCPUS, SelectedVcpusAtStartup.ToString())); } sum.Add(new KeyValuePair(Messages.NEWVMWIZARD_CPUMEMPAGE_TOPOLOGY, comboBoxTopology.Text)); if (memoryMode == MemoryMode.JustMemory) sum.Add(new KeyValuePair(Messages.MEMORY, Util.MemorySizeStringSuitableUnits(SelectedMemoryStaticMax, false))); else { sum.Add(new KeyValuePair(Messages.DYNAMIC_MIN, Util.MemorySizeStringSuitableUnits(SelectedMemoryDynamicMin, false))); sum.Add(new KeyValuePair(Messages.DYNAMIC_MAX, Util.MemorySizeStringSuitableUnits(SelectedMemoryDynamicMax, false))); if (memoryMode == MemoryMode.MinimumMaximumAndStaticMax) sum.Add(new KeyValuePair(Messages.STATIC_MAX, Util.MemorySizeStringSuitableUnits(SelectedMemoryStaticMax, false))); } return sum; } } private void ValuesUpdated() { CheckForError(); OnPageUpdated(); } private void CheckForError() { long max_mem_total = 0; long max_mem_free = 0; long max_vcpus = 0; Host max_mem_total_host = null; Host max_mem_free_host = null; Host max_vcpus_host = null; foreach (Host host in Connection.Cache.Hosts) { long host_cpus = 0; foreach (Host_cpu cpu in Connection.Cache.Host_cpus) { if (cpu.host.opaque_ref.Equals(host.opaque_ref)) host_cpus++; } Host_metrics metrics = Connection.Resolve(host.metrics); if (host_cpus > max_vcpus) { max_vcpus = host_cpus; max_vcpus_host = host; } if (metrics != null && metrics.memory_total > max_mem_total) { max_mem_total = metrics.memory_total; max_mem_total_host = host; } // The available memory of a server is taken to be the current memory_free, // plus however much we can squeeze down the existing VMs. This assumes // that the overhead won't increase when we create the new VM, so it // has false negatives. long memory_free = host.memory_available_calc(); if (metrics != null && memory_free > max_mem_free) { max_mem_free = memory_free; max_mem_free_host = host; } } if (max_mem_total_host != null && SelectedMemoryDynamicMin > max_mem_total) { ErrorPanel.Visible = true; ErrorLabel.Text = string.Format(Messages.NEWVMWIZARD_CPUMEMPAGE_MEMORYWARN1, Helpers.GetName(max_mem_total_host).Ellipsise(50), Util.MemorySizeStringSuitableUnits(max_mem_total, false)); } else if (max_mem_free_host != null && SelectedMemoryDynamicMin > max_mem_free) { ErrorPanel.Visible = true; ErrorLabel.Text = string.Format(Messages.NEWVMWIZARD_CPUMEMPAGE_MEMORYWARN2, Helpers.GetName(max_mem_free_host).Ellipsise(50), Util.MemorySizeStringSuitableUnits(max_mem_free, false)); } else if (max_vcpus_host != null && SelectedVcpusMax > max_vcpus) { ErrorPanel.Visible = true; ErrorLabel.Text = string.Format(Messages.NEWVMWIZARD_CPUMEMPAGE_VCPUSWARN, Helpers.GetName(max_vcpus_host).Ellipsise(50), max_vcpus); } else { ErrorPanel.Visible = false; } } private void ShowMemoryMinMaxInformation(Label label, double min, double max) { label.Text = string.Format( Messages.NEWVMWIZARD_CPUMEMPAGE_MEMORYINFO, FormatMemory(min), FormatMemory(max)); } private string FormatMemory(double numberOfBytes) { return Util.MemorySizeStringSuitableUnits(numberOfBytes, true); } private void vCPU_ValueChanged(object sender, EventArgs e) { comboBoxTopology.Update((long)comboBoxVCPUs.SelectedItem); ValuesUpdated(); ValidateVCPUSettings(); RefreshCurrentVCPUs(); } private void memory_ValueChanged(object sender, EventArgs e) { if (initialising) return; SetSpinnerLimitsAndIncrement(); ValuesUpdated(); } private void ValidateVCPUSettings() { if (comboBoxVCPUs.SelectedItem != null) labelInvalidVCPUWarning.Text = VM.ValidVCPUConfiguration((long)comboBoxVCPUs.SelectedItem, comboBoxTopology.CoresPerSocket); } private long _prevVCPUsMax; private void RefreshCurrentVCPUs() { // refresh comboBoxInitialVCPUs if it's visible and populated if (comboBoxInitialVCPUs.Visible && comboBoxInitialVCPUs.Items.Count > 0) { // VcpusAtStartup is always <= VcpusMax // So if VcpusMax is decreased below VcpusAtStartup, then VcpusAtStartup is decreased to that number too // If VcpusAtStartup and VcpusMax are equal, and VcpusMax is changed, then VcpusAtStartup is changed to match // But if the numbers are unequal, and VcpusMax is changed but is still higher than VcpusAtStartup, then VcpusAtStartup is unchanged var newValue = SelectedVcpusAtStartup; if (SelectedVcpusMax < SelectedVcpusAtStartup) newValue = SelectedVcpusMax; else if (SelectedVcpusAtStartup == _prevVCPUsMax && SelectedVcpusMax != _prevVCPUsMax) newValue = SelectedVcpusMax; PopulateVCPUsAtStartup(SelectedVcpusMax, newValue); _prevVCPUsMax = SelectedVcpusMax; } } private void comboBoxTopology_SelectedIndexChanged(object sender, EventArgs e) { ValidateVCPUSettings(); } } }