xenadmin/XenAdmin/Wizards/GenericPages/SelectVMStorageWithMultipleVirtualDisksPage.cs
Konstantina Chremmou e59019858c Ensure we never attempt getting the file size if file is null
Fixes https://github.com/xenserver/xenadmin/issues/2356
Also, some code modernising.

Signed-off-by: Konstantina Chremmou <konstantina.chremmou@citrix.com>
2019-01-07 14:19:02 +00:00

614 lines
20 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.Linq;
using System.Windows.Forms;
using XenAdmin.Controls;
using XenAdmin.Controls.Common;
using XenAdmin.Core;
using XenAdmin.Mappings;
using XenAdmin.Network;
using XenAPI;
using XenCenterLib;
namespace XenAdmin.Wizards.GenericPages
{
/// <summary>
/// Class representing the page of the ImportAppliance wizard where the user specifies
/// storage repositories for the VMs in the improted appliance
/// </summary>
internal abstract partial class SelectVMStorageWithMultipleVirtualDisksPage : XenTabPage
{
private class StorageDetail
{
public string SysId { get; set; }
public IStorageResource ResourceData { get; set; }
public ulong RequiredSpace
{
get
{
if (ResourceData == null || !ResourceData.TryCalcRequiredDiskCapacity(out ulong capacity))
return 0;
return capacity;
}
}
}
private class EnableableSrComboboxItem : IEnableableXenObjectComboBoxItem
{
private readonly ToStringWrapper<SR> stringWrapper;
private readonly ulong spaceRequired;
public EnableableSrComboboxItem(ToStringWrapper<SR> stringWrapper, ulong spaceRequired)
{
this.stringWrapper = stringWrapper;
this.spaceRequired = spaceRequired;
}
public IXenObject Item
{
get { return stringWrapper.item; }
}
public SR Sr
{
get { return Item as SR; }
}
public bool Enabled
{
get { return (ulong)stringWrapper.item.FreeSpace() > spaceRequired; }
}
public override string ToString()
{
if(Enabled)
return stringWrapper.ToString();
return String.Format(Messages.SELECT_STORAGE_DROPDOWN_ERROR_NOT_ENOUGH_SPACE, stringWrapper);
}
public string SrName
{
get { return stringWrapper.item.name_label; }
}
}
#region Private fields
private Dictionary<string, ulong> m_spaceRequirements = new Dictionary<string, ulong>();
private Dictionary<string, VmMapping> m_vmMappings;
private bool m_buttonNextEnabled;
private bool m_buttonPreviousEnabled;
#endregion
public SelectVMStorageWithMultipleVirtualDisksPage()
{
InitializeComponent();
InitializeText();
}
protected void InitializeText()
{
m_labelIntro.Text = IntroductionText;
m_radioAllOnSameSr.Text = AllOnSameSRRadioButtonText;
m_radioSpecifySr.Text = OnSpecificSRRadioButtonText;
m_comboBoxSr.GotFocus += m_comboBoxSr_GotFocus;
m_colVmDisk.HeaderText = VmDiskColumnHeaderText;
SetupTabIndices();
}
~SelectVMStorageWithMultipleVirtualDisksPage()
{
m_comboBoxSr.GotFocus -= m_comboBoxSr_GotFocus;
}
private void SetupTabIndices()
{
m_radioAllOnSameSr.TabIndex = 3;
m_comboBoxSr.TabIndex = 4;
m_radioSpecifySr.TabIndex = 5;
m_dataGridView.TabIndex = 6;
}
protected abstract string IntroductionText { get; }
protected abstract string AllOnSameSRRadioButtonText { get; }
protected abstract string OnSpecificSRRadioButtonText { get; }
protected virtual string VmDiskColumnHeaderText
{
get
{
return m_colVmDisk.HeaderText;
}
}
protected virtual void AddAditionalMappings(Dictionary<string, VmMapping> vmMappings)
{}
#region Base class (XenTabPage) overrides
protected override void PageLeaveCore(PageLoadedDirection direction, ref bool cancel)
{
TargetConnection = null;
if (!cancel && direction == PageLoadedDirection.Forward && IsDirty && ImplementsIsDirty())
cancel = !PerformCheck(CheckStorageRequirements);
}
protected override void PageLoadedCore(PageLoadedDirection direction)
{
SetButtonPreviousEnabled(true);
}
public abstract StorageResourceContainer ResourceData(string sysId);
private bool displayDiskCapacity = true;
public bool DisplayDiskCapacity
{
set { displayDiskCapacity = value; }
}
private IXenConnection targetConnection;
/// <summary>
/// The connection from which the target storage is read
/// Defaults to the base class connection if not set
/// </summary>
public IXenConnection TargetConnection
{
get
{
if(targetConnection == null)
return Connection;
return targetConnection;
}
set { targetConnection = value; }
}
protected virtual bool SrsAreSuitable(SR sourceSr, SR targetSr)
{
return true;
}
protected virtual bool IsExtraSpaceNeeded(SR sourceSr, SR targetSr)
{
return true;
}
public override void PopulatePage()
{
m_dataGridView.Rows.Clear();
m_ctrlError.HideError();
bool checkSpaceRequirements = true;
var targetRefs = new List<object>();
foreach (var mapping in m_vmMappings.Values.Where(mapping => !targetRefs.Contains(mapping.XenRef)))
targetRefs.Add(mapping.XenRef);
var commonSRs = new List<ToStringWrapper<SR>>();
foreach (var kvp in m_vmMappings)
{
string sysId = kvp.Key;
var vmMapping = kvp.Value;
int i = 0;
foreach (IStorageResource resourceData in ResourceData(sysId))
{
if(!resourceData.TryCalcRequiredDiskCapacity(out _))
continue;
string disklabel = !string.IsNullOrEmpty(resourceData.DiskLabel)
? string.Format("{0} - {1}", vmMapping.VmNameLabel, resourceData.DiskLabel)
: string.Format("{0} - {1} {2}", vmMapping.VmNameLabel, Messages.STORAGE_DISK, i);
var cellVmDisk = new DataGridViewTextBoxCell
{
Tag = new StorageDetail
{SysId = sysId, ResourceData = resourceData},
Value = FormatDiskValueText(resourceData, disklabel)
};
DataGridViewRow row = new DataGridViewRow();
row.Cells.AddRange(cellVmDisk);
var cb = FillGridComboBox(vmMapping.XenRef, targetRefs, resourceData, ref commonSRs);
if (cb.Items.Count > 0)
{
cb.DisplayMember = ToStringWrapper<SR>.DisplayMember;
cb.ValueMember = ToStringWrapper<SR>.ValueMember;
if (cb.Value == null)
cb.Value = cb.Items[0];
cb.Tag = resourceData.Tag;
row.Cells.Add(cb);
}
else
{
var cellError = new DataGridViewTextBoxCell { Value = Messages.IMPORT_SELECT_STORAGE_PAGE_ERROR_NO_SR_AVAILABLE};
row.Cells.Add(cellError);
cellError.ReadOnly = true; //this has to be set after the cell is added to a row
SetButtonNextEnabled(false);
checkSpaceRequirements = false;
}
m_dataGridView.Rows.Add(row);
i++;
}
}
HelpersGUI.ResizeGridViewColumnToAllCells(m_colStorage);//set properly the width of the last column
m_comboBoxSr.Items.Clear();
PopulateSrComboBox(commonSRs);
if (m_comboBoxSr.Items.Count == 0)
{
m_comboBoxSr.Items.Add(Messages.IMPORT_SELECT_STORAGE_PAGE_ERROR_NO_COMMON_SR_AVAILABLE);
SetButtonNextEnabled(false);
checkSpaceRequirements = false;
m_radioSpecifySr.Checked = true;
}
else
m_radioAllOnSameSr.Checked = true;
m_comboBoxSr.SelectedItem = m_comboBoxSr.Items[DetermineDefaultSrPositionInComboBox()];
if (checkSpaceRequirements)
PerformCheck(CheckStorageRequirements);
}
private void PopulateSrComboBox(List<ToStringWrapper<SR>> commonSRs)
{
List<EnableableSrComboboxItem> listToAdd = new List<EnableableSrComboboxItem>();
foreach (var toStringWrapper in commonSRs)
{
ulong requiredSpace = 0;
bool isSuitableForAll = true;
foreach (var kvp in m_vmMappings)
{
foreach (IStorageResource resourceData in ResourceData(kvp.Key))
{
if (IsExtraSpaceNeeded(resourceData.SR, toStringWrapper.item))
{
if (resourceData.TryCalcRequiredDiskCapacity(out ulong capacity))
requiredSpace += capacity;
if (!SrsAreSuitable(resourceData.SR, toStringWrapper.item))
{
isSuitableForAll = false;
break;
}
}
}
if (!isSuitableForAll)
break;
}
if (isSuitableForAll)
listToAdd.Add(new EnableableSrComboboxItem(toStringWrapper, requiredSpace));
}
if (listToAdd.Any(item => item.Enabled))
{
var sortedlistToAdd = from item in listToAdd
orderby item.Enabled descending , item.SrName
select item;
m_comboBoxSr.Items.AddRange(sortedlistToAdd.ToArray());
}
}
private string FormatDiskValueText(IStorageResource resourceData, string diskLabel)
{
if(displayDiskCapacity && resourceData.TryCalcRequiredDiskCapacity(out ulong capacity))
return $"{diskLabel} ({Util.DiskSizeString(capacity)})";
return diskLabel;
}
public override bool EnableNext()
{
return m_buttonNextEnabled;
}
public override bool EnablePrevious()
{
return m_buttonPreviousEnabled;
}
#endregion
#region Accessors
public Dictionary<string, VmMapping> VmMappings
{
get
{
var comboItem = m_comboBoxSr.SelectedItem as EnableableSrComboboxItem;
foreach (DataGridViewRow row in m_dataGridView.Rows)
{
var storageDetail = (StorageDetail)row.Cells[0].Tag;
if (m_vmMappings.ContainsKey(storageDetail.SysId))
{
var mapping = m_vmMappings[storageDetail.SysId];
var gridItem = row.Cells[1].Value as ToStringWrapper<SR>;
var rasdId = row.Cells[1].Tag as string;
if (!string.IsNullOrEmpty(rasdId))
{
if (m_radioAllOnSameSr.Checked && comboItem != null)
mapping.Storage[rasdId] = comboItem.Sr;
else if (gridItem != null)
mapping.Storage[rasdId] = gridItem.item;
}
}
}
AddAditionalMappings(m_vmMappings);
return m_vmMappings;
}
set { m_vmMappings = value; }
}
#endregion
#region Private methods
/// <summary>
/// Performs certain checks on the pages's input data and shows/hides an error accordingly
/// </summary>
/// <param name="checks">The checks to perform</param>
private bool PerformCheck(params CheckDelegate[] checks)
{
bool success = m_ctrlError.PerformCheck(checks);
SetButtonNextEnabled(success);
return success;
}
protected void SetButtonNextEnabled(bool enabled)
{
m_buttonNextEnabled = enabled;
OnPageUpdated();
}
protected void SetButtonPreviousEnabled(bool enabled)
{
m_buttonPreviousEnabled = enabled;
OnPageUpdated();
}
private bool CheckStorageRequirements(out string error)
{
error = string.Empty;
m_spaceRequirements = new Dictionary<string, ulong>();
if (m_radioAllOnSameSr.Checked)
{
var selectedItem = m_comboBoxSr.SelectedItem as EnableableSrComboboxItem;
if (selectedItem == null) //no storage available
return false;
if (!selectedItem.Enabled)
{
error = String.Format(Messages.IMPORT_SELECT_STORAGE_PAGE_ERROR_SPACE_LIMITATION_COMMON, selectedItem.SrName);
return false;
}
}
else if (m_radioSpecifySr.Checked)
{
foreach (DataGridViewRow row in m_dataGridView.Rows)
{
var selectedItem = row.Cells[1].Value as ToStringWrapper<SR>;
if (selectedItem == null) //no storage available
return false;
var storageDetail = (StorageDetail)row.Cells[0].Tag;
string uuid = selectedItem.item.uuid;
ulong requiredSpace =
IsExtraSpaceNeeded(storageDetail.ResourceData.SR, selectedItem.item)
? storageDetail.RequiredSpace
: 0;
if (m_spaceRequirements.ContainsKey(uuid))
m_spaceRequirements[uuid] += requiredSpace;
else
m_spaceRequirements.Add(uuid, requiredSpace);
if ((ulong)selectedItem.item.FreeSpace() <= m_spaceRequirements[uuid])
{
error = String.Format(Messages.IMPORT_SELECT_STORAGE_PAGE_ERROR_SPACE_LIMITATION, selectedItem.item.Name());
return false;
}
}
}
return true;
}
private DataGridViewComboBoxCell FillGridComboBox(object xenRef, List<object> targetRefs, IStorageResource resource, ref List<ToStringWrapper<SR>> commonSRs)
{
var cb = new DataGridViewComboBoxCell { FlatStyle = FlatStyle.Flat };
foreach (var pbd in TargetConnection.Cache.PBDs)
{
if (pbd.SR == null)
continue;
var sr = TargetConnection.Resolve(pbd.SR);
if (sr == null || sr.IsDetached() || !sr.Show(XenAdminConfigManager.Provider.ShowHiddenVMs))
continue;
if ((sr.content_type.ToLower() == "iso" || sr.type.ToLower() == "iso") && !resource.SRTypeInvalid)
continue;
if (sr.content_type.ToLower() != "iso" && resource.SRTypeInvalid)
continue;
bool srOnHost = pbd.host != null && pbd.host.Equals(xenRef);
if ((sr.shared || srOnHost) &&
(!IsExtraSpaceNeeded(resource.SR, sr) ||
resource.TryCalcRequiredDiskCapacity(out ulong capacity) && (ulong)sr.FreeSpace() > capacity &&
SrsAreSuitable(resource.SR, sr)))
{
var count = (from ToStringWrapper<SR> existingItem in cb.Items
where existingItem.item.opaque_ref == sr.opaque_ref
select existingItem).Count();
if (count > 0)
continue; //iterating through pbds
var newItem = new ToStringWrapper<SR>(sr, GetSRDropDownItemDisplayString);
cb.Items.Add(newItem);
if (SR.IsDefaultSr(sr))
cb.Value = newItem;
//store items to populate the m_comboBoxSr
if ((sr.shared || (targetRefs.Count == 1 && srOnHost && targetRefs[0].Equals(xenRef))))
{
var num = (from ToStringWrapper<SR> existingItem in commonSRs
where existingItem.item.opaque_ref == sr.opaque_ref
select existingItem).Count();
if (num <= 0)
commonSRs.Add(newItem);
}
}
}
return cb;
}
/// <summary>
/// Determine the location of the SR that has been selected as the default option
/// </summary>
/// <returns>Position of the default SR</returns>
private int DetermineDefaultSrPositionInComboBox()
{
int defaultSrLocation = 0;
foreach (var item in m_comboBoxSr.Items)
{
var sr = item as EnableableSrComboboxItem;
if (sr != null && SR.IsDefaultSr(sr.Sr) && sr.Enabled)
defaultSrLocation = m_comboBoxSr.Items.IndexOf(sr);
}
return defaultSrLocation;
}
private string GetSRDropDownItemDisplayString(SR sr)
{
var availableSpace = Util.DiskSizeString(sr.FreeSpace());
return String.Format("{0}, {1} {2}", sr.Name(), availableSpace, Messages.AVAILABLE);
}
#endregion
#region Control event handlers
private void m_radioAllOnSameSr_CheckedChanged(object sender, EventArgs e)
{
IsDirty = true;
PerformCheck(CheckStorageRequirements);
if(m_radioAllOnSameSr.Checked)
{
m_dataGridView.Enabled = false;
m_comboBoxSr.Enabled = true;
m_comboBoxSr.Focus();
}
}
private void m_radioSpecifySr_CheckedChanged(object sender, EventArgs e)
{
IsDirty = true;
PerformCheck(CheckStorageRequirements);
//Draw focus on the 0,0 cell
if(m_radioSpecifySr.Checked)
{
m_comboBoxSr.Enabled = false;
m_dataGridView.Enabled = true;
if (m_dataGridView.Rows.Count > 0 && m_dataGridView.Rows[0].Cells.Count > 0)
{
m_dataGridView.ClearSelection();
m_dataGridView.Focus();
m_dataGridView.Rows[0].Cells[0].Selected = true;
m_dataGridView.CurrentCell = m_dataGridView.SelectedCells[0];
m_dataGridView.BeginEdit(true);
}
}
}
private void m_comboBoxSr_SelectedIndexChanged(object sender, EventArgs e)
{
IsDirty = true;
PerformCheck(CheckStorageRequirements);
}
private void m_comboBoxSr_GotFocus(object sender, EventArgs e)
{
m_radioAllOnSameSr.Checked = true;
}
private void m_dataGridView_CellEnter(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex != m_colStorage.Index || e.RowIndex < 0 || e.RowIndex >= m_dataGridView.RowCount)
return;
m_dataGridView.BeginEdit(false);
if (m_dataGridView.EditingControl != null && m_dataGridView.EditingControl is ComboBox)
(m_dataGridView.EditingControl as ComboBox).DroppedDown = true;
}
private void m_dataGridView_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
m_dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
IsDirty = true;
}
private void m_dataGridView_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
PerformCheck(CheckStorageRequirements);
}
#endregion
}
}