/* 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.Drawing; using System.Linq; using System.Windows.Forms; using XenAdmin.Controls; using XenAdmin.Core; using XenAdmin.Dialogs; using XenAdmin.Network; using XenAPI; namespace XenAdmin.Wizards.ImportWizard { public partial class NetworkPickerPage : XenTabPage { #region Private fields private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); private IXenConnection m_selectedConnection; private Host m_selectedAffinity; private VM m_vm; private bool m_buttonNextEnabled; #endregion public NetworkPickerPage() { InitializeComponent(); m_invalidMacLabel.Visible = false; } #region Base class (XenTabPage) overrides /// /// Gets the page's title (headline) /// public override string PageTitle { get { return Messages.IMPORT_SELECT_NETWORK_PAGE_TITLE; } } /// /// Gets the page's label in the (left hand side) wizard progress panel /// public override string Text { get { return Messages.IMPORT_SELECT_NETWORK_PAGE_TEXT; } } /// /// Gets the value by which the help files section for this page is identified /// public override string HelpID { get { return "NetworkPicker"; } } protected override bool ImplementsIsDirty() { return true; } public override void PageLoaded(PageLoadedDirection direction) { base.PageLoaded(direction);//call first so the page gets populated m_buttonNextEnabled = true; } public override void PopulatePage() { SetNetworkList(); BuildVIFList(); } public override bool EnablePrevious() { return false; } public override bool EnableNext() { return m_buttonNextEnabled; } #endregion #region Accessors public List ProxyVIFs { get { var vifs = new List(); foreach (DataGridViewRow row in m_networkGridView.Rows) { var vifRow = row as VifRow; if (vifRow == null) continue; VIF vif = vifRow.Vif; if (vif == null) continue; if (vif.MAC == Messages.MAC_AUTOGENERATE) vif.MAC = ""; vifs.Add(vif.ToProxy()); } return vifs; } } #endregion public void SetAffinity(Host host) { m_selectedAffinity = host; } public void SetVm(VM vm) { m_vm = vm; } /// /// Should be called before the Affinity is set. /// public void SetConnection(IXenConnection con) { m_selectedConnection = con; } #region Private methods private void UpdateControlsEnabledState(bool enabled) { m_buttonNextEnabled = enabled; m_invalidMacLabel.Visible = !enabled; OnPageUpdated(); } private void SetNetworkList() { DataGridViewComboBoxColumn col = (DataGridViewComboBoxColumn)m_networkGridView.Columns["NetworkNetworkColumn"]; col.Items.Clear(); var networks = m_selectedConnection.Cache.Networks.Where(ShowNetwork); foreach (XenAPI.Network network in networks) col.Items.Add(new ToStringWrapper(network, network.Name)); col.DisplayMember = ToStringWrapper.DisplayMember; col.ValueMember = ToStringWrapper.ValueMember; col.Sorted = true; } private void BuildVIFList() { try { m_networkGridView.SuspendLayout(); m_networkGridView.Rows.Clear(); if (m_vm.is_a_template && m_vm.VIFs.Count < 1) { // We need to automatically generate VIFs for Networks marked AutoPlug=true var networks = m_selectedConnection.Cache.Networks; foreach (XenAPI.Network network in networks) { if (m_networkGridView.Rows.Count < m_vm.MaxVIFsAllowed && ShowNetwork(network) && network.AutoPlug) { AddVIFRow(new VIF { Connection = m_selectedConnection, device = m_networkGridView.Rows.Count.ToString(), network = new XenRef(network.opaque_ref), MAC = Messages.MAC_AUTOGENERATE }); } } } else if (m_vm.is_a_template) { // We need to create off the _vmTemplate var vifs = m_selectedConnection.ResolveAll(m_vm.VIFs); foreach (VIF vif in vifs) { AddVIFRow(new VIF { Connection = m_selectedConnection, device = vif.device, network = vif.network, MAC = Messages.MAC_AUTOGENERATE }); } } else { //We need to recreate off vm var vifs = m_selectedConnection.ResolveAll(m_vm.VIFs); foreach (VIF vif in vifs) AddVIFRow(vif); } m_networkGridView.Sort(m_networkGridView.Columns[0], ListSortDirection.Ascending); } finally { m_networkGridView.ResumeLayout(); m_buttonDeleteNetwork.Enabled = m_networkGridView.SelectedRows.Count > 0; } } private XenAPI.Network GetDefaultNetwork() { foreach (XenAPI.Network network in m_selectedConnection.Cache.Networks) if (ShowNetwork(network)) return network; return null; } /// /// Function tells you when you can / cannot show the network based on the following rules /// 1) Don't show the guest installer network or networks with HideFromXenCenter==true. /// 2) If you selected an affinity, only show networks that host can see /// 3) If you haven't selected an affinity, only show networks all hosts can see /// private bool ShowNetwork(XenAPI.Network network) { if (!network.Show(Properties.Settings.Default.ShowHiddenVMs)) return false; if (network.IsSlave) return false; if (m_selectedAffinity != null && !m_selectedAffinity.CanSeeNetwork(network)) return false; if (m_selectedAffinity == null && !network.AllHostsCanSeeNetwork) return false; return true; } private void AddVIFRow(VIF vif) { var row = new VifRow(vif); XenAPI.Network network = m_selectedConnection.Resolve(vif.network); bool isGuestInstallerNetwork = network != null && network.IsGuestInstallerNetwork; ToStringWrapper comboBoxEntry = FindComboBoxEntryForNetwork(network); // CA-66962: Don't choose disallowed networks: choose a better one instead. // CA-79930/CA-73056: Except for the guest installer network, which we let // through for now, but hide it below. if (comboBoxEntry == null && !isGuestInstallerNetwork) { network = GetDefaultNetwork(); comboBoxEntry = FindComboBoxEntryForNetwork(network); vif.network = new XenRef(network.opaque_ref); } row.CreateCells(m_networkGridView, String.Format(Messages.NETWORKPICKER_INTERFACE, vif.device), vif.MAC, comboBoxEntry); row.Cells[0].ReadOnly = true; // CA-73056: A row for the guest installer network shouldn't show up. But we still need // it present but invisible, otherwise the corresponding VIF doesn't get created at all. if (isGuestInstallerNetwork) row.Visible = false; m_networkGridView.Rows.Add(row); } private ToStringWrapper FindComboBoxEntryForNetwork(XenAPI.Network network) { if (network == null) return null; DataGridViewComboBoxColumn column = (DataGridViewComboBoxColumn)m_networkGridView.Columns["NetworkNetworkColumn"]; foreach (ToStringWrapper entry in column.Items) { if (entry.item.uuid == network.uuid) return entry; } return null; } private bool AllMacAddressesValid() { foreach (var row in m_networkGridView.Rows) { var vifRow = row as VifRow; if (vifRow != null && !vifRow.HasValidMac) return false; } return true; } #endregion #region Event handlers private void m_buttonDeleteNetwork_Click(object sender, EventArgs e) { foreach (DataGridViewRow row in m_networkGridView.SelectedRows) m_networkGridView.Rows.Remove(row); UpdateControlsEnabledState(AllMacAddressesValid()); } private void m_buttonAddNetwork_Click(object sender, EventArgs e) { if (m_networkGridView.Rows.Count >= m_vm.MaxVIFsAllowed) { new ThreeButtonDialog(new ThreeButtonDialog.Details( SystemIcons.Error, FriendlyErrorNames.VIFS_MAX_ALLOWED, FriendlyErrorNames.VIFS_MAX_ALLOWED_TITLE)).ShowDialog(Program.MainWindow); return; } VIF vif = new VIF {Connection = m_selectedConnection}; int i = 0; while (true) { bool exists = false; foreach (DataGridViewRow row in m_networkGridView.Rows) { VifRow vifRow = row as VifRow; if (vifRow == null) continue; VIF v = vifRow.Vif; if (v == null) continue; if (int.Parse(v.device) == i) exists = true; } if (exists) i++; else break; } vif.device = i.ToString(); vif.MAC = Messages.MAC_AUTOGENERATE; if (GetDefaultNetwork() != null) vif.network = new XenRef(GetDefaultNetwork().opaque_ref); AddVIFRow(vif); } private void m_networkGridView_SelectionChanged(object sender, EventArgs e) { m_buttonDeleteNetwork.Enabled = m_networkGridView.SelectedRows.Count > 0; } private void m_networkGridView_CellEndEdit(object sender, DataGridViewCellEventArgs e) { VifRow row = m_networkGridView.Rows[e.RowIndex] as VifRow; if (row == null) return; VIF vif = row.Vif; if (vif == null) return; if (e.ColumnIndex == MACAddressNetworkColumn.Index) { row.HasValidMac = true; string mac = (string)row.Cells[e.ColumnIndex].Value; if (mac == null || mac.Trim().Length == 0) { // We take it that zero-length mac means the user wants to auto-generate row.Cells[e.ColumnIndex].Value = Messages.MAC_AUTOGENERATE; } else if (mac.Trim() == Messages.MAC_AUTOGENERATE) { row.Cells[e.ColumnIndex].Value = Messages.MAC_AUTOGENERATE; vif.MAC = Messages.MAC_AUTOGENERATE; } else if (Helpers.IsValidMAC(mac)) { vif.MAC = mac; } else { row.HasValidMac = false; } UpdateControlsEnabledState(AllMacAddressesValid()); } else if (e.ColumnIndex == NetworkNetworkColumn.Index) { object enteredValue = row.Cells[e.ColumnIndex].Value; var entry = enteredValue as ToStringWrapper; XenAPI.Network network = (entry == null) ? (XenAPI.Network)enteredValue : entry.item; if (network != null) vif.network = new XenRef(network.opaque_ref); } } private void m_networkGridView_CellClick(object sender, DataGridViewCellEventArgs e) { if (e.RowIndex < 0 || e.RowIndex >= m_networkGridView.RowCount) return; if (e.ColumnIndex == MACAddressNetworkColumn.Index) { m_networkGridView.BeginEdit(false); return; } if (e.ColumnIndex == NetworkNetworkColumn.Index) { m_networkGridView.BeginEdit(false); var combobox = m_networkGridView.EditingControl as ComboBox; if (combobox != null) combobox.DroppedDown = true; } } private void m_networkGridView_CurrentCellDirtyStateChanged(object sender, EventArgs e) { m_networkGridView.CommitEdit(DataGridViewDataErrorContexts.Commit); IsDirty = true; } private void m_networkGridView_DataError(object sender, DataGridViewDataErrorEventArgs e) { e.ThrowException = false; log.Error(String.Format(Messages.NETWORKPICKER_LOG_VIF_ERROR, e.Exception.Message)); } private void m_networkGridView_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e) { if (e.RowIndex < 0 || e.RowIndex >= m_networkGridView.RowCount) return; if (e.ColumnIndex == MACAddressNetworkColumn.Index) { bool enable = true; foreach (DataGridViewRow row in m_networkGridView.Rows) { if (row.Index == e.RowIndex) continue; var vifRow = row as VifRow; if (vifRow != null && !vifRow.HasValidMac) { enable = false; break; } } UpdateControlsEnabledState(enable); } } #endregion #region Nested classes private class VifRow : DataGridViewRow { public readonly VIF Vif; public bool HasValidMac = true; public VifRow(VIF vif) { Vif = vif; } } #endregion } }