xenadmin/XenAdmin/Wizards/NewVMWizard/Page_Storage.cs
Konstantina Chremmou ada6f7dac7 CA-296490: Upload single update to multiple pools in parallel. (#2338)
* Simplification: use auto-properties.

* Removed unnecessary null check. Converted private methods to linq method chain.

* Removed unused file. Added null check.

* Corrected property names as it was difficult to distinguish between the alert,
the patch and the filepath.

* Removed duplicate lock.

* CA-296490: Rewrote the logic uploading single updates so as to facilitate uploading to multiple pools in parallel.

* Corrections as per code review.

* Account for the case of uploading from disk legacy updates that do not correspond to an update alert.
Removed unfriendly error messages (the failure is written out in the logs anyway).

* Wizard test corrections and some modernising:
- Only call cancel on the last page if the test allows us to.
- Do finish the UpdatesWizard test.
- Finish the RunWizardKeyboardTests using key strokes instead of button clicks.
- Ensure the Cancel/Previous buttons do not have focus when sending an Enter stroke.
- Use optional parameters in the WizardTest constructor and string interpolation.

* Renamed badly named property.

* Improved code cloning VMs by assigning new opaque_refs to cloned VBDs.
Compacted test object provider: use optional parameters instead of method overloads.

* Modifications to prevent multiple update of enabled state and ensure the wizard
buttons are updated last.

Signed-off-by: Konstantina Chremmou <konstantina.chremmou@citrix.com>
2019-01-10 13:24:42 +00:00

572 lines
21 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.Drawing;
using System.Linq;
using System.Windows.Forms;
using XenAdmin.Actions.VMActions;
using XenAdmin.Dialogs;
using XenAPI;
using System.Xml;
using XenAdmin.Network;
using XenAdmin.Core;
using XenAdmin.Controls;
namespace XenAdmin.Wizards.NewVMWizard
{
public partial class Page_Storage : XenTabPage
{
private VM Template;
private bool InstallMethodIsNetwork;
private bool loading = false;
public Page_Storage()
{
InitializeComponent();
}
public override string Text
{
get { return Messages.NEWVMWIZARD_STORAGEPAGE_NAME; }
}
public override string PageTitle
{
get { return Messages.NEWVMWIZARD_STORAGEPAGE_TITLE; }
}
public override string HelpID
{
get { return "Storage"; }
}
protected override void PageLoadedCore(PageLoadedDirection direction)
{
VM template = SelectedTemplate;
bool installMethodIsNetwork = SelectedInstallMethod == InstallMethod.Network;
if (template == Template && InstallMethodIsNetwork == installMethodIsNetwork)
return;
loading = true;
Template = template;
InstallMethodIsNetwork = installMethodIsNetwork;
if ((!Template.DefaultTemplate() && !Template.HasAtLeastOneDisk())
|| (Template.IsHVM() && InstallMethodIsNetwork)) // CA-46213 The default should be "diskless" if the install method is "boot from network"
{
DisklessVMRadioButton.Checked = true;
}
else
DisksRadioButton.Checked = true;
LoadDisks();
loading = false;
UpdateEnablement(true);
}
public override void SelectDefaultControl()
{
DisksGridView.Select();
}
private void LoadDisks()
{
DisksGridView.Rows.Clear();
var rowList = new List<DataGridViewRow>();
XmlNode provision = Template.ProvisionXml();
if (provision != null)
{
foreach (XmlNode disk in provision.ChildNodes)
rowList.Add(new DiskGridRowItem(Connection, disk, SelectedName, Affinity));
}
else
{
var vbds = Connection.ResolveAll(Template.VBDs);
foreach (VBD vbd in vbds)
{
if (vbd.type != vbd_type.Disk)
continue;
VDI vdi = Connection.Resolve(vbd.VDI);
if (vdi != null)
rowList.Add(new DiskGridRowItem(Connection, vdi, vbd, false, Affinity));
}
}
DisksGridView.Rows.AddRange(rowList.ToArray());
}
public override bool EnableNext()
{
return (DisklessVMRadioButton.Checked || (DisksGridView.Rows.Count > 0 && AllDisksHaveSRs())) && CheckForOverCommit() != DiskOverCommit.Error;
}
private bool AllDisksHaveSRs()
{
foreach (DiskGridRowItem item in DisksGridView.Rows)
{
if (item.Disk.SR.opaque_ref == Helper.NullOpaqueRef)
return false;
}
return true;
}
private IEnumerable<VDI> AddedVDIs
{
get
{
return from DiskGridRowItem row in DisksGridView.Rows select row.Disk;
}
}
private void AddButton_Click(object sender, EventArgs e)
{
NewDiskDialog dialog = new NewDiskDialog(Connection, Template, SrPicker.SRPickerType.LunPerVDI, null, Affinity, true, 0, AddedVDIs);
dialog.DontCreateVDI = true;
if (dialog.ShowDialog() != DialogResult.OK)
return;
DisksGridView.Rows.Add(new DiskGridRowItem(Connection, dialog.NewDisk(), dialog.NewDevice(), true, Affinity));
UpdateEnablement();
}
private void DisksRadioButton_CheckedChanged(object sender, EventArgs e)
{
if (DisksRadioButton.Checked)
UpdateEnablement();
}
private void DisklessVMRadioButton_CheckedChanged(object sender, EventArgs e)
{
if (DisklessVMRadioButton.Checked)
UpdateEnablement();
}
private void UpdateEnablement(bool pageLoad = false)
{
if (loading)
return;
AddButton.Enabled = DisksRadioButton.Checked && DisksGridView.Rows.Count < Template.MaxVBDsAllowed() - 1;
PropertiesButton.Enabled = DisksRadioButton.Checked && DisksGridView.SelectedRows.Count > 0;
DeleteButton.Enabled = DisksRadioButton.Checked && DisksGridView.SelectedRows.Count > 0 && ((DiskGridRowItem)DisksGridView.SelectedRows[0]).CanDelete;
DisksGridView.Enabled = DisksRadioButton.Checked;
DisklessVMRadioButton.Enabled = Template.IsHVM() && InstallMethodIsNetwork;
CheckForOverCommit();
CloneCheckBox.Enabled = false;
if (!Template.DefaultTemplate())
{
foreach (DiskGridRowItem row in DisksGridView.Rows)
{
if (!row.CanDelete)
{
SR src = row.SourceDisk == null ? null : Connection.Resolve<SR>(row.SourceDisk.SR);
SR dest = Connection.Resolve<SR>(row.Disk.SR);
if (src != null && src.Equals(dest))
{
CloneCheckBox.Enabled = true;
if (pageLoad)
CloneCheckBox.Checked = true;
break;
}
}
}
}
if (!CloneCheckBox.Enabled)
CloneCheckBox.Checked = false;
OnPageUpdated();
}
private DiskOverCommit CheckForOverCommit()
{
Dictionary<string, long> totalDiskSize = new Dictionary<string, long>(); // total size of the new disks on each SR (calculated using vdi.virtual_size)
Dictionary<string, long> totalDiskInitialAllocation = new Dictionary<string, long>(); // total initial allocation of the new disks on each SR (calculated using vdi.InitialAllocation)
foreach (DiskGridRowItem item in DisksGridView.Rows)
{
item.OverCommit = DiskOverCommit.None; // reset all errors
item.ImageToolTip = "";
SR sr = Connection.Resolve(item.Disk.SR);
if (sr == null) // no sr assigned
continue;
if(sr.HBALunPerVDI()) //No over commit in this case
continue;
if (totalDiskSize.ContainsKey(sr.opaque_ref))
totalDiskSize[sr.opaque_ref] += item.Disk.virtual_size;
else
totalDiskSize[sr.opaque_ref] = item.Disk.virtual_size;
var initialSpace = Helpers.GetRequiredSpaceToCreateVdiOnSr(sr, item.Disk);
if (totalDiskInitialAllocation.ContainsKey(sr.opaque_ref))
totalDiskInitialAllocation[sr.opaque_ref] += initialSpace;
else
totalDiskInitialAllocation[sr.opaque_ref] = initialSpace;
}
DiskOverCommit overcommitedDisk = DiskOverCommit.None;
foreach (DiskGridRowItem item in DisksGridView.Rows)
{
SR sr = Connection.Resolve(item.Disk.SR);
if (sr == null)
continue;
if (sr.HBALunPerVDI()) //No over commit in this case
continue;
if (item.Disk.SR.opaque_ref != sr.opaque_ref)
continue;
if (!sr.VdiCreationCanProceed(totalDiskInitialAllocation[sr.opaque_ref]))
overcommitedDisk = item.OverCommit = DiskOverCommit.Error;
else if (sr.FreeSpace() < totalDiskInitialAllocation[sr.opaque_ref])
overcommitedDisk = item.OverCommit = DiskOverCommit.Warning;
if (item.OverCommit != DiskOverCommit.None)
{
item.ImageToolTip =
string.Format(Messages.NEWVMWIZARD_STORAGEPAGE_SROVERCOMMIT,
Helpers.GetName(sr),
Util.DiskSizeString(sr.FreeSpace()),
Util.DiskSizeString(totalDiskSize[sr.opaque_ref]));
}
item.UpdateDetails();
}
return overcommitedDisk;
}
private void DeleteButton_Click(object sender, EventArgs e)
{
if (DisksGridView.SelectedRows.Count <= 0)
return;
DisksGridView.Rows.Remove(DisksGridView.SelectedRows[0]);
UpdateEnablement();
}
private void PropertiesButton_Click(object sender, EventArgs e)
{
if (DisksGridView.SelectedRows.Count <= 0)
return;
DiskGridRowItem selectedItem = ((DiskGridRowItem)DisksGridView.SelectedRows[0]);
NewDiskDialog dialog = new NewDiskDialog(Connection, Template, SrPicker.SRPickerType.LunPerVDI, selectedItem.Disk, Affinity, selectedItem.CanResize, selectedItem.MinSize, AddedVDIs);
dialog.DontCreateVDI = true;
if (dialog.ShowDialog() != DialogResult.OK)
return;
selectedItem.Disk = dialog.NewDisk();
selectedItem.UpdateDetails();
UpdateEnablement();
}
#region Accessors
/// <summary>
/// Gets the SR that should be used as the parameter for VM.copy when the VM is created by the New VM Wizard. If null
/// is returned then VM.clone should be used.
/// </summary>
public SR FullCopySR
{
get
{
if (!Template.DefaultTemplate() && !Template.is_a_snapshot && !CloneCheckBox.Checked)
{
// if target disks are all on the same SR then use that SR
// otherwise iterate through disks and find first target disks that is on same SR as source disk
SR sr = null;
List<SR> targetSRs = new List<SR>();
foreach (DiskGridRowItem row in DisksGridView.Rows)
{
if (!row.CanDelete && row.SourceDisk != null && row.Disk != null)
{
SR src = Connection.Resolve<SR>(row.SourceDisk.SR);
SR target = Connection.Resolve<SR>(row.Disk.SR);
if (sr == null && src != null && src.Equals(target))
{
sr = src;
}
if (!targetSRs.Contains(target))
{
targetSRs.Add(target);
}
}
}
if (targetSRs.Count == 1)
{
return targetSRs[0];
}
return sr;
}
return null;
}
}
public override List<KeyValuePair<string, string>> PageSummary
{
get
{
var sum = new List<KeyValuePair<string, string>>();
foreach (DiskDescription d in SelectedDisks)
{
sum.Add(new KeyValuePair<string, string>(
string.Format(Messages.NEWVMWIZARD_STORAGEPAGE_DISK, Helpers.GetName(d.Disk).Ellipsise(30)),
Util.DiskSizeString(d.Disk.virtual_size)));
}
return sum;
}
}
public List<DiskDescription> SelectedDisks
{
get
{
if (DisklessVMRadioButton.Checked)
return new List<DiskDescription>();
List<DiskDescription> disks = new List<DiskDescription>();
foreach (DiskGridRowItem item in DisksGridView.Rows)
{
disks.Add(new DiskDescription(item.Disk, item.Device));
}
return disks;
}
}
public VM SelectedTemplate { private get; set; }
public string SelectedName { private get; set; }
public InstallMethod SelectedInstallMethod { private get; set; }
public Host Affinity { private get; set; }
#endregion
private void DisksGridView_SelectionChanged(object sender, EventArgs e)
{
UpdateEnablement();
}
}
public class DiskGridRowItem : DataGridViewRow
{
public readonly VDI SourceDisk;
public VDI Disk;
public readonly VBD Device;
public readonly IXenConnection Connection;
public readonly bool CanDelete;
public readonly bool CanResize;
public readonly long MinSize;
public DiskOverCommit OverCommit = DiskOverCommit.None;
public string ImageToolTip;
private DataGridViewImageCell ImageCell;
private DataGridViewTextBoxCell SizeCell;
private DataGridViewTextBoxCell NameCell;
private DataGridViewTextBoxCell SrCell;
private DataGridViewTextBoxCell SharedCell;
public DiskGridRowItem(IXenConnection connection, XmlNode diskNode, string vmName, Host affinity)
{
Disk = new VDI();
Device = new VBD();
Connection = connection;
Disk.virtual_size = long.Parse(diskNode.Attributes["size"].Value);
SR sruuid = connection.Cache.Find_By_Uuid<SR>(diskNode.Attributes["sr"].Value);
SR sr = GetBeskDiskStorage(Connection, Disk, affinity, sruuid == null ? null : sruuid);
Disk.SR = new XenRef<SR>(sr != null ? sr.opaque_ref : Helper.NullOpaqueRef);
Disk.type = (vdi_type)Enum.Parse(typeof(vdi_type), diskNode.Attributes["type"].Value);
Device.userdevice = diskNode.Attributes["device"].Value;
Device.bootable = diskNode.Attributes["bootable"].Value == "true";
Disk.name_label = string.Format(Messages.NEWVMWIZARD_STORAGEPAGE_VDINAME, vmName, Device.userdevice); //Device.userdevice;
Disk.read_only = false;
Disk.name_description = Messages.NEWVMWIZARD_STORAGEPAGE_DISK_DESCRIPTION;
Device.mode = vbd_mode.RW;
CanDelete = Disk.type == vdi_type.user;
CanResize = true;
MinSize = Disk.virtual_size;
AddCells();
}
public DiskGridRowItem(IXenConnection connection, VDI vdi, VBD vbd, bool isNew, Host affinity)
{
SourceDisk = vdi;
Disk = new VDI();
Device = new VBD();
Connection = connection;
Disk.virtual_size = vdi.virtual_size;
SR sr = GetBeskDiskStorage(Connection, vdi, affinity, Connection.Resolve(vdi.SR));
Disk.SR = new XenRef<SR>(sr != null ? sr.opaque_ref : Helper.NullOpaqueRef);
Disk.type = vdi.type;
Device.userdevice = vbd.userdevice;
Device.bootable = vbd.bootable;
Disk.name_label = vdi.name_label;
Disk.read_only = vdi.read_only;
Disk.name_description = vdi.name_description;
Disk.sm_config = vdi.sm_config;
Device.mode = vbd.mode;
CanDelete = Disk.type == vdi_type.user && isNew;
CanResize = isNew;
MinSize = 0;
AddCells();
}
private void AddCells()
{
ImageCell = new DataGridViewImageCell(false) {ValueType = typeof(Image)};
NameCell = new DataGridViewTextBoxCell();
SizeCell = new DataGridViewTextBoxCell();
SrCell = new DataGridViewTextBoxCell();
SharedCell = new DataGridViewTextBoxCell();
Cells.AddRange(ImageCell, NameCell, SrCell, SizeCell, SharedCell);
UpdateDetails();
}
public void UpdateDetails()
{
switch (OverCommit)
{
case DiskOverCommit.None:
ImageCell.Value = Properties.Resources._000_VirtualStorage_h32bit_16;
break;
case DiskOverCommit.Warning:
ImageCell.Value = Properties.Resources._000_Alert2_h32bit_16;
break;
case DiskOverCommit.Error:
ImageCell.Value = Properties.Resources._000_error_h32bit_16;
break;
}
ImageCell.ToolTipText = ImageToolTip;
SizeCell.ToolTipText = ImageToolTip;
NameCell.ToolTipText = ImageToolTip;
SrCell.ToolTipText = ImageToolTip;
SharedCell.ToolTipText = ImageToolTip;
SR sr = Connection.Resolve(Disk.SR);
if(sr == null)
SizeCell.Value = Util.DiskSizeString(Disk.virtual_size);
else
SizeCell.Value = sr.HBALunPerVDI() ? String.Empty : Util.DiskSizeString(Disk.virtual_size);
NameCell.Value = Helpers.GetName(Disk);
if (Disk.SR.opaque_ref != Helper.NullOpaqueRef)
{
SrCell.Value = Helpers.GetName(sr);
SharedCell.Value = sr.shared ? Messages.TRUE : Messages.FALSE;
}
else
{
SrCell.Value = Messages.NEWVMWIZARD_STORAGEPAGE_NOSTORAGE;
SharedCell.Value = "";
}
}
/// <summary>
/// Tries to find the best SR for the given VDI considering the suggestedSR which has priority over other SRs in this check.
/// SuggestedSR, default SR, other SRs are checked.
/// Returns first suitable SR or NULL.
/// </summary>
private static SR GetBeskDiskStorage(IXenConnection connection, VDI disk, Host affinity, SR suggestedSR)
{
// try suggestion
if (suggestedSR != null && suggestedSR.CanBeSeenFrom(affinity) && IsSufficientFreeSpaceAvailableOnSrForVdi(suggestedSR, disk))
return suggestedSR;
// try default sr
SR defaultSR = connection.Resolve(Helpers.GetPoolOfOne(connection).default_SR);
if (defaultSR != null && defaultSR.CanBeSeenFrom(affinity) && IsSufficientFreeSpaceAvailableOnSrForVdi(defaultSR, disk))
return defaultSR;
// pick an sr
foreach (SR sr in connection.Cache.SRs)
{
if (!sr.CanCreateVmOn())
continue;
if (sr.CanBeSeenFrom(affinity) && IsSufficientFreeSpaceAvailableOnSrForVdi(sr, disk))
return sr;
}
// there has been no suitable SR found
return null;
}
/// <summary>
/// Checks whether there is enough space available on the SR to accommodate a VDI.
/// </summary>
private static bool IsSufficientFreeSpaceAvailableOnSrForVdi(SR sr, VDI disk)
{
return sr != null && sr.VdiCreationCanProceed(Helpers.GetRequiredSpaceToCreateVdiOnSr(sr, disk));
}
}
public enum DiskOverCommit : int
{
None = 0,
Warning = 1,
Error = 2
}
}