/* 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.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 { /// /// Class representing the page of the ImportAppliance wizard where the user specifies /// storage repositories for the VMs in the improted appliance /// 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 stringWrapper; private readonly ulong spaceRequired; public EnableableSrComboboxItem(ToStringWrapper 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 m_spaceRequirements = new Dictionary(); private Dictionary 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 AddAdditionalMappings(Dictionary 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; /// /// The connection from which the target storage is read /// Defaults to the base class connection if not set /// 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(); foreach (var mapping in m_vmMappings.Values.Where(mapping => !targetRefs.Contains(mapping.XenRef))) targetRefs.Add(mapping.XenRef); var commonSRs = new List>(); 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.DisplayMember; cb.ValueMember = ToStringWrapper.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> commonSRs) { List listToAdd = new List(); 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 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; 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; } } } AddAdditionalMappings(m_vmMappings); return m_vmMappings; } set { m_vmMappings = value; } } #endregion #region Private methods /// /// Performs certain checks on the page's input data and shows/hides an error accordingly /// /// The checks to perform 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(); 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; 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 targetRefs, IStorageResource resource, ref List> 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 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, 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 existingItem in commonSRs where existingItem.item.opaque_ref == sr.opaque_ref select existingItem).Count(); if (num <= 0) commonSRs.Add(newItem); } } } return cb; } /// /// Determine the location of the SR that has been selected as the default option /// /// Position of the default SR 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 } }