mirror of
https://github.com/xcp-ng/xenadmin.git
synced 2024-12-05 01:21:07 +01:00
cb26b4a259
CP-28926: Block export of VMs with >2TiB disks in ova/ovf format Signed-off-by: serenc <seren.corbett@citrix.com>
419 lines
13 KiB
C#
419 lines
13 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.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Windows.Forms;
|
|
using XenAdmin.Commands;
|
|
using XenAdmin.Controls.Common;
|
|
using XenAdmin.Core;
|
|
using XenAPI;
|
|
using XenAdmin.Controls;
|
|
using XenCenterLib;
|
|
|
|
|
|
namespace XenAdmin.Wizards.ExportWizard
|
|
{
|
|
/// <summary>
|
|
/// Class representing the page of the ExportAppliance wizard where the user specifies
|
|
/// the VMs to be included in the exported appliance
|
|
/// </summary>
|
|
internal partial class ExportSelectVMsPage : XenTabPage
|
|
{
|
|
#region Nested classes
|
|
|
|
private static class NativeMethods
|
|
{
|
|
[DllImport("Kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
internal static extern bool GetDiskFreeSpaceEx(string lpszPath, ref long lpFreeBytesAvailable, ref long lpTotalNumberOfBytes, ref long lpTotalNumberOfFreeBytes);
|
|
}
|
|
|
|
#endregion
|
|
|
|
private bool m_buttonNextEnabled;
|
|
|
|
public ExportSelectVMsPage()
|
|
{
|
|
InitializeComponent();
|
|
m_tlpWarning.Visible = false;
|
|
VMsToExport = new List<VM>();
|
|
}
|
|
|
|
#region Accessors
|
|
|
|
/// <summary>
|
|
/// Gets a list of the VMs that will be included in the exported appliance
|
|
/// </summary>
|
|
public List<VM> VMsToExport { get; private set; }
|
|
|
|
public string ApplianceDirectory { get; set; }
|
|
|
|
/// <summary>
|
|
/// The items selected on the main window treeview when the wizard was launched.
|
|
/// These determine the VMs selected by default.
|
|
/// </summary>
|
|
public SelectedItemCollection SelectedItems { private get; set; }
|
|
|
|
public bool ExportAsXva { private get; set; }
|
|
|
|
#endregion
|
|
|
|
#region Base class (XenTabPage) overrides
|
|
|
|
/// <summary>
|
|
/// Gets the page's title (headline)
|
|
/// </summary>
|
|
public override string PageTitle { get { return ExportAsXva ? Messages.EXPORT_SELECTVMS_PAGE_TITLE_XVA : Messages.EXPORT_SELECTVMS_PAGE_TITLE_OVF; } }
|
|
|
|
/// <summary>
|
|
/// Gets the page's label in the (left hand side) wizard progress panel
|
|
/// </summary>
|
|
public override string Text { get { return Messages.EXPORT_SELECTVMS_PAGE_TEXT; } }
|
|
|
|
/// <summary>
|
|
/// Gets the value by which the help files section for this page is identified
|
|
/// </summary>
|
|
public override string HelpID { get { return ExportAsXva ? "SelectVMsXva" : "SelectVMsOvf"; } }
|
|
|
|
protected override bool ImplementsIsDirty()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
protected override void PageLoadedCore(PageLoadedDirection direction)
|
|
{
|
|
m_ctrlError.HideError();
|
|
EnableButtons();
|
|
}
|
|
|
|
protected override void PageLeaveCore(PageLoadedDirection direction, ref bool cancel)
|
|
{
|
|
if (direction == PageLoadedDirection.Forward && IsDirty)
|
|
cancel = !PerformCheck(CheckDiskSizeForTransfer, CheckSpaceRequirements);
|
|
}
|
|
|
|
public override void PopulatePage()
|
|
{
|
|
var pool = Helpers.GetPoolOfOne(Connection);
|
|
label2.Text = string.Format(Helpers.IsPool(pool.Connection) ? Messages.VMS_IN_POOL : Messages.VMS_IN_SERVER,
|
|
pool.Name().Ellipsise(60));
|
|
VMsToExport.Clear();
|
|
|
|
try
|
|
{
|
|
m_dataGridView.SuspendLayout();
|
|
m_dataGridView.Rows.Clear();
|
|
|
|
var applianceVMs = new List<XenRef<VM>>();
|
|
if (SelectedItems != null && SelectedItems.FirstIs<VM_appliance>())
|
|
applianceVMs.AddRange(((VM_appliance)SelectedItems.FirstAsXenObject).VMs);
|
|
|
|
foreach (var vm in Connection.Cache.VMs.Where(vm => IsVmExportable(vm) && MatchesSearchText(vm)))
|
|
{
|
|
VM curVm = vm;//closure below
|
|
bool selected = SelectedItems != null
|
|
&& (SelectedItems.AsXenObjects().Contains(vm) || applianceVMs.FirstOrDefault(xenref => xenref.opaque_ref == curVm.opaque_ref) != null);
|
|
|
|
m_dataGridView.Rows.Add(GetDataGridViewRow(vm, selected));
|
|
|
|
if (selected)
|
|
VMsToExport.Add(vm);
|
|
}
|
|
m_dataGridView.Sort(columnTick, ListSortDirection.Descending);
|
|
}
|
|
finally
|
|
{
|
|
m_dataGridView.ResumeLayout();
|
|
}
|
|
UpdateCounterLabel();
|
|
EnableButtons();
|
|
}
|
|
|
|
public override bool EnableNext()
|
|
{
|
|
return m_buttonNextEnabled;
|
|
}
|
|
|
|
#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)
|
|
{
|
|
m_buttonNextEnabled = m_ctrlError.PerformCheck(checks);
|
|
OnPageUpdated();
|
|
return m_buttonNextEnabled;
|
|
}
|
|
|
|
private bool IsVmExportable(VM vm)
|
|
{
|
|
if (!vm.is_a_real_vm())
|
|
return false;
|
|
|
|
if (!vm.Show(Properties.Settings.Default.ShowHiddenVMs))
|
|
return false;
|
|
|
|
if (vm.power_state != vm_power_state.Halted && vm.power_state != vm_power_state.Suspended)
|
|
return false;
|
|
|
|
if (vm.Locked)
|
|
return false;
|
|
|
|
return vm.allowed_operations != null && vm.allowed_operations.Contains(vm_operations.export);
|
|
}
|
|
|
|
private bool MatchesSearchText(VM vm)
|
|
{
|
|
return m_searchTextBox.Matches(vm.Name());
|
|
}
|
|
|
|
private DataGridViewRow GetDataGridViewRow(VM vm, bool selected)
|
|
{
|
|
var row = new DataGridViewRow {Tag = vm};
|
|
var vmAppliance = Connection.Resolve(vm.appliance);
|
|
long totalVmSize = GetTotalVmSize(vm);
|
|
row.Cells.AddRange(new DataGridViewCell[]
|
|
{
|
|
new DataGridViewCheckBoxCell {Value = selected},
|
|
new DataGridViewTextBoxCell {Value = vm.Name()},
|
|
new DataGridViewTextBoxCell {Value = vm.Description()},
|
|
new DataGridViewTextBoxCell {Value = Util.DiskSizeString(totalVmSize), Tag = totalVmSize},
|
|
new DataGridViewTextBoxCell {Value = vmAppliance == null ? Messages.NONE : vmAppliance.Name()}
|
|
});
|
|
return row;
|
|
}
|
|
|
|
private long GetTotalVmSize(VM vm)
|
|
{
|
|
long virtualSize = 0;
|
|
|
|
foreach (var vbdRef in vm.VBDs)
|
|
{
|
|
var vbd = Connection.Resolve(vbdRef);
|
|
if (vbd == null)
|
|
continue;
|
|
|
|
if (vbd.type == vbd_type.CD)
|
|
continue;
|
|
|
|
VDI vdi = Connection.Resolve(vbd.VDI);
|
|
if (vdi == null)
|
|
continue;
|
|
|
|
virtualSize += vdi.virtual_size;
|
|
}
|
|
|
|
return virtualSize;
|
|
}
|
|
|
|
private bool CheckSpaceRequirements(out string errorMsg)
|
|
{
|
|
errorMsg = string.Empty;
|
|
long spaceNeeded = 0;
|
|
|
|
foreach (DataGridViewRow row in m_dataGridView.Rows)
|
|
{
|
|
if ((bool)row.Cells[0].Value)
|
|
spaceNeeded += (long)row.Cells[3].Tag;
|
|
}
|
|
|
|
long availableSpace = GetFreeSpace(ApplianceDirectory);
|
|
if (spaceNeeded > availableSpace)
|
|
{
|
|
errorMsg = String.Format(Messages.EXPORT_SELECTVMS_PAGE_ERROR_TARGET_SPACE_NOT_ENOUGH, Util.DiskSizeString(availableSpace), Util.DiskSizeString(spaceNeeded));
|
|
//Log.Error(Messages.EXPORT_SELECTVMS_PAGE_ERROR_TargeSpaceNotEnough);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool CheckDiskSizeForTransfer(out string errorMsg)
|
|
{
|
|
errorMsg = string.Empty;
|
|
var maxDiskSizeString = Util.DiskSizeString(SR.DISK_MAX_SIZE, 0);
|
|
|
|
foreach (VM vm in VMsToExport)
|
|
{
|
|
if (!ExportAsXva && GetTotalVmSize(vm) > SR.DISK_MAX_SIZE)
|
|
{
|
|
errorMsg = string.Format(Messages.EXPORT_ERROR_EXCEEDS_MAX_SIZE_VDI_OVA_OVF, maxDiskSizeString);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//TODO: improve method
|
|
private long GetFreeSpace(string drivename)
|
|
{
|
|
if (!drivename.EndsWith(@"\"))
|
|
drivename += @"\";
|
|
|
|
long space = 0;
|
|
long lpTotalNumberOfBytes = 0;
|
|
long lpTotalNumberOfFreeBytes = 0;
|
|
|
|
if (NativeMethods.GetDiskFreeSpaceEx(drivename, ref space, ref lpTotalNumberOfBytes, ref lpTotalNumberOfFreeBytes))
|
|
return space;
|
|
|
|
return -1;
|
|
}
|
|
|
|
private void EnableButtons()
|
|
{
|
|
var count = VMsToExport.Count;
|
|
m_tlpWarning.Visible = ExportAsXva && count > 1;
|
|
m_buttonNextEnabled = ExportAsXva ? count == 1 : count > 0;
|
|
m_buttonClearAll.Enabled = count > 0;
|
|
m_buttonSelectAll.Enabled = count < m_dataGridView.RowCount;
|
|
OnPageUpdated();
|
|
}
|
|
|
|
private void UpdateCounterLabel()
|
|
{
|
|
var count = VMsToExport.Count;
|
|
m_labelCounter.Text = count == 1 ? Messages.ONE_VM_SELECTED : string.Format(Messages.MOREONE_VM_SELECTED, count);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Control event handlers
|
|
|
|
private void m_buttonSelectAll_Click(object sender, EventArgs e)
|
|
{
|
|
foreach (DataGridViewRow dataGridViewRow in m_dataGridView.Rows)
|
|
{
|
|
dataGridViewRow.Cells[0].Value = true;
|
|
|
|
var vm = dataGridViewRow.Tag as VM;
|
|
if (vm != null && !VMsToExport.Contains(vm))
|
|
VMsToExport.Add(vm);
|
|
}
|
|
}
|
|
|
|
private void m_buttonClearAll_Click(object sender, EventArgs e)
|
|
{
|
|
//clear all visible ones
|
|
|
|
foreach (DataGridViewRow dataGridViewRow in m_dataGridView.Rows)
|
|
{
|
|
dataGridViewRow.Cells[0].Value = false;
|
|
|
|
var vm = dataGridViewRow.Tag as VM;
|
|
if (vm != null)
|
|
VMsToExport.Remove(vm);
|
|
}
|
|
}
|
|
|
|
private void m_searchTextBox_TextChanged(object sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
var sortedColumn = m_dataGridView.SortedColumn;
|
|
var sortOrder = m_dataGridView.SortOrder;
|
|
|
|
m_dataGridView.SuspendLayout();
|
|
m_dataGridView.Rows.Clear();
|
|
|
|
foreach (var vm in Connection.Cache.VMs.Where(IsVmExportable))
|
|
{
|
|
if (MatchesSearchText(vm))
|
|
m_dataGridView.Rows.Add(GetDataGridViewRow(vm, VMsToExport.Contains(vm)));
|
|
}
|
|
|
|
if (sortOrder != SortOrder.None && sortedColumn != null)
|
|
m_dataGridView.Sort(sortedColumn, (sortOrder == SortOrder.Ascending) ? ListSortDirection.Ascending : ListSortDirection.Descending);
|
|
}
|
|
finally
|
|
{
|
|
m_dataGridView.ResumeLayout();
|
|
}
|
|
}
|
|
|
|
private void m_dataGridView_CurrentCellDirtyStateChanged(object sender, EventArgs e)
|
|
{
|
|
if (m_dataGridView.IsCurrentCellDirty)
|
|
m_dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
|
|
}
|
|
|
|
private void m_dataGridView_CellValueChanged(object sender, DataGridViewCellEventArgs e)
|
|
{
|
|
if (e.ColumnIndex != 0 || e.RowIndex < 0 || e.RowIndex >= m_dataGridView.RowCount)
|
|
return;
|
|
|
|
var row = m_dataGridView.Rows[e.RowIndex];
|
|
var vm = row.Tag as VM;
|
|
|
|
if (vm != null)
|
|
{
|
|
if ((bool)row.Cells[0].Value)
|
|
{
|
|
if (!VMsToExport.Contains(vm))
|
|
VMsToExport.Add(vm);
|
|
}
|
|
else
|
|
VMsToExport.Remove(vm);
|
|
}
|
|
|
|
m_ctrlError.HideError();
|
|
UpdateCounterLabel();
|
|
IsDirty = true;
|
|
EnableButtons();
|
|
}
|
|
|
|
private void m_dataGridView_RowStateChanged(object sender, DataGridViewRowStateChangedEventArgs e)
|
|
{
|
|
if ((e.Row.State & DataGridViewElementStates.Selected) != 0)
|
|
e.Row.Selected = false;
|
|
}
|
|
|
|
private void m_dataGridView_SortCompare(object sender, DataGridViewSortCompareEventArgs e)
|
|
{
|
|
if (e.Column == columnSize)
|
|
{
|
|
e.SortResult = StringUtility.NaturalCompare(e.CellValue1.ToString(), e.CellValue2.ToString());
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|