/* 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.Drawing; using System.Linq; using System.Windows.Forms; using XenAdmin.Controls; using XenAPI; using XenAdmin.Core; using XenAdmin.Actions; using XenAdmin.Help; using PifTuple = System.Tuple; namespace XenAdmin.Dialogs { public partial class NetworkingProperties : VerticallyTabbedDialog { private Host Host; private Pool Pool; private string ObjectName; private List ShownPIFs; private List AllPIFs; private bool AllowManagementOnVLAN; private readonly ToolTip toolTipRemove = new ToolTip(); private bool tooltipVisible; public NetworkingProperties() { InitializeComponent(); } public NetworkingProperties(Host host, PIF selectedPIF) :base(host.Connection) { InitializeComponent(); Host = host; Pool = null; ObjectName = Helpers.GetName(host); AllowManagementOnVLAN = !Helpers.FeatureForbidden(connection, Host.RestrictManagementOnVLAN); BlurbLabel.Text = string.Format(Messages.NETWORKING_PROPERTIES_BLURB_HOST, ObjectName); Configure(selectedPIF); } public NetworkingProperties(Pool pool, PIF selectedPIF) : base(pool.Connection) { InitializeComponent(); Pool = pool; ObjectName = Helpers.GetName(Pool); AllowManagementOnVLAN = !Helpers.FeatureForbidden(connection, Host.RestrictManagementOnVLAN); Host =connection.Resolve(pool.master); if (Host == null) throw new Failure(Failure.INTERNAL_ERROR, "Could not resolve coordinator"); BlurbLabel.Text = string.Format(Messages.NETWORKING_PROPERTIES_BLURB_POOL, ObjectName); Configure(selectedPIF); } private void Configure(PIF selectedPIF) { Text = string.Format(Messages.NETWORKING_PROPERTIES_TITLE, ObjectName); ShownPIFs = GetKnownPIFs(false); AllPIFs = GetKnownPIFs(true); PIF management_pif = AllPIFs.Find(pif => pif.management); if (management_pif == null) { // Cache has not been populated yet, or is being cleared. return; } bool ha = (Pool != null && Pool.ha_enabled); // CA-24714 NetworkingPropertiesPage management_page = new NetworkingPropertiesPage(ha ? NetworkingPropertiesPage.Type.PRIMARY_WITH_HA : NetworkingPropertiesPage.Type.PRIMARY); management_page.Purpose = Messages.MANAGEMENT; AddTabContents(management_page); management_page.Tag = management_pif; foreach (PIF pif in ShownPIFs) { if (pif.opaque_ref != management_pif.opaque_ref && pif.IsSecondaryManagementInterface(true)) { MakeSecondaryPage(pif, Purpose(pif)); } } var inusemap = MakeProposedInUseMap(); foreach (NetworkingPropertiesPage page in verticalTabs.Items) { page.Pool = Pool != null; RefreshNetworkComboBox(inusemap, page); } foreach (NetworkingPropertiesPage page in verticalTabs.Items) { ConfigurePage(page, page.Tag as PIF); } if (selectedPIF != null) { foreach (NetworkingPropertiesPage item in verticalTabs.Items) { if (item.Tag == selectedPIF) { verticalTabs.SelectedItem = item; break; } } } else verticalTabs.SelectedIndex = 0; ResizeVerticalTabs(verticalTabs.Items.Count); verticalTabs.AdjustItemTextBounds = GetItemTextBounds; } private void ResizeVerticalTabs(int itemCount) { int maxHeight = splitContainer.Panel1.Height - AddButton.Height; verticalTabs.Height = Math.Min(maxHeight, itemCount * verticalTabs.ItemHeight); AddButton.Top = verticalTabs.Top + verticalTabs.Height; } /// /// /// /// /// May be null, in which case the page is configured with reasonable defaults. private void ConfigurePage(NetworkingPropertiesPage page, PIF pif) { if (pif == null) { page.SelectFirstUnusedNetwork(); page.SubnetTextBox.Text = ""; page.GatewayTextBox.Text = ""; page.IPAddressTextBox.Text = ""; page.DHCPIPRadioButton.Checked = true; } else { page.NetworkComboBox.SelectedItem = connection.Resolve(pif.network); page.SubnetTextBox.Text = pif.netmask; page.GatewayTextBox.Text = pif.gateway; page.IPAddressTextBox.Text = pif.IP; page.DHCPIPRadioButton.Checked = pif.ip_configuration_mode == ip_configuration_mode.DHCP; } string[] addresses = pif == null ? new string[] { "" } : pif.DNS.Split(','); page.PreferredDNSTextBox.Text = addresses[0]; page.AlternateDNS1TextBox.Text = addresses.Length == 1 ? "" : addresses[1]; page.AlternateDNS2TextBox.Text = addresses.Length <= 2 ? "" : addresses[2]; } private void RefreshNetworkComboBox(Dictionary> InUseMap, NetworkingPropertiesPage page) { page.RefreshNetworkComboBox(InUseMap, ManagementNetwork(), AllowManagementOnVLAN); } private List GetKnownPIFs(bool include_invisible) { List result = new List(); foreach (XenAPI.Network network in connection.Cache.Networks) { if (network.Show(include_invisible || Properties.Settings.Default.ShowHiddenVMs)) { PIF pif = FindPIFForThisHost(network.PIFs); if (pif != null) result.Add(pif); } } return result; } private Dictionary> MakeProposedInUseMap() { var inusemap = new Dictionary>(); foreach (NetworkingPropertiesPage page in verticalTabs.Items) { XenAPI.Network network = (XenAPI.Network)page.NetworkComboBox.SelectedItem; if (network == null) { PIF pif = page.Tag as PIF; if (pif == null) continue; network = pif.Connection.Resolve(pif.network); if (network == null) continue; } if (network.PIFs.Count == 0) continue; if (!inusemap.ContainsKey(network)) inusemap[network] = new List(); inusemap[network].Add(page); } foreach (XenAPI.Network network in connection.Cache.Networks) { if (network.Show(Properties.Settings.Default.ShowHiddenVMs)) { if (connection.ResolveAll(network.PIFs).Find(p => !p.IsTunnelAccessPIF()) == null) // no PIFs, or all the PIFs are tunnel access PIFs so the network is a CHIN continue; PIF pif = FindPIFForThisHost(network.PIFs); if (pif != null && pif.IsInUseBondMember()) continue; if (!inusemap.ContainsKey(network)) inusemap[network] = null; } } return inusemap; } private void AddTabContents(NetworkingPropertiesPage prop_page) { prop_page.HostCount = connection.Cache.HostCount; prop_page.ValidChanged += prop_page_ValidChanged; prop_page.DeleteButtonClicked += prop_page_DeleteButtonClicked; prop_page.NetworkComboBoxChanged += prop_page_NetworkComboBoxChanged; prop_page.Parent = ContentPanel; prop_page.Dock = DockStyle.Fill; verticalTabs.Items.Add(prop_page); RefreshButtons(); } void RefreshNetworkComboBoxes() { var inusemap = MakeProposedInUseMap(); foreach (NetworkingPropertiesPage page in verticalTabs.Items) RefreshNetworkComboBox(inusemap, page); } void RefreshButtons() { foreach (NetworkingPropertiesPage page in verticalTabs.Items) page.RefreshButtons(); okButton.Enabled = AllPagesValid(); AddButton.Enabled = ShownPIFs.Count > verticalTabs.Items.Count; } private bool AllPagesValid() { foreach (NetworkingPropertiesPage page in verticalTabs.Items) if (!page.Valid||!page.NameValid) return false; return true; } private void AddButton_Click(object sender, EventArgs e) { ResizeVerticalTabs(verticalTabs.Items.Count + 1); verticalTabs.SelectedItem = MakeSecondaryPage(null, AuxTabName()); NetworkingPropertiesPage page = verticalTabs.SelectedItem as NetworkingPropertiesPage; RefreshNetworkComboBox(MakeProposedInUseMap(), page); ConfigurePage(page, null); } void prop_page_NetworkComboBoxChanged(object sender, EventArgs e) { RefreshNetworkComboBoxes(); } void prop_page_DeleteButtonClicked(object sender, EventArgs e) { NetworkingPropertiesPage prop_page = (NetworkingPropertiesPage)sender; int selectedIndex = verticalTabs.SelectedIndex; verticalTabs.Items.Remove(prop_page); verticalTabs.SelectedIndex = selectedIndex < verticalTabs.Items.Count - 1 ? selectedIndex : verticalTabs.Items.Count - 1; ContentPanel.Controls.Remove(prop_page); RefreshNetworkComboBoxes(); RefreshButtons(); ResizeVerticalTabs(verticalTabs.Items.Count); } void prop_page_ValidChanged(object sender, EventArgs e) { RefreshButtons(); } /// /// /// /// May be null, in which case the page is configured with reasonable defaults. /// /// private NetworkingPropertiesPage MakeSecondaryPage(PIF pif, string purpose) { NetworkingPropertiesPage prop_page = MakeSecondaryPage(purpose); if (pif != null) prop_page.Tag = pif; if (pif == null) prop_page.SelectName(); return prop_page; } private NetworkingPropertiesPage MakeSecondaryPage(string purpose) { NetworkingPropertiesPage prop_page = new NetworkingPropertiesPage(NetworkingPropertiesPage.Type.SECONDARY); prop_page.Pool = Pool != null; prop_page.Purpose = purpose; prop_page.PurposeTextBox.Text = purpose; prop_page.RefreshButtons(); prop_page.PurposeTextBox.TextChanged += (sender, e) => { if (sender is NetworkingPropertiesPage page) page.Text = page.PurposeTextBox.Text; }; AddTabContents(prop_page); return prop_page; } private string Purpose(PIF pif) { string purpose = pif.GetManagementPurpose(); return string.IsNullOrEmpty(purpose) ? Messages.NETWORKING_PROPERTIES_PURPOSE_UNKNOWN : purpose; } private string AuxTabName() { List tabnames = GetTabNames(); return Helpers.MakeUniqueNameFromPattern(Messages.NETWORKING_PROPERTIES_AUX_TAB_NAME, tabnames, 1); } private List GetTabNames() { List result = new List(); foreach (NetworkingPropertiesPage page in verticalTabs.Items) { result.Add(page.Text); } return result; } private bool IPAddressSettingsChanged(PIF pif1, PIF pif2) { return !pif1.IP.Equals(pif2.IP) || !pif1.netmask.Equals(pif2.netmask) || !pif1.gateway.Equals(pif2.gateway); } private bool GetManagementInterfaceIPChanged(PIF oldManagement, PIF newManagement) { if (oldManagement == null || newManagement == null) throw new Failure(Failure.INTERNAL_ERROR, "Management interface is null."); if (oldManagement.ip_configuration_mode == ip_configuration_mode.DHCP) { if (newManagement.ip_configuration_mode == ip_configuration_mode.DHCP) return !oldManagement.Equals(newManagement); if (newManagement.ip_configuration_mode == ip_configuration_mode.Static) return IPAddressSettingsChanged(oldManagement, newManagement); } else if (oldManagement.ip_configuration_mode == ip_configuration_mode.Static) { if (newManagement.ip_configuration_mode == ip_configuration_mode.Static) return IPAddressSettingsChanged(oldManagement, newManagement); if (newManagement.ip_configuration_mode == ip_configuration_mode.DHCP) return true; } throw new Failure(Failure.INTERNAL_ERROR, "Unexpected IP configuration mode."); } private void AcceptBtn_Click(object sender, EventArgs e) { var newPIFs = new List(); var newNamePIFs = new List(); List downPIFs = new List(); foreach (PIF pif in AllPIFs) { if (pif.IsManagementInterface(XenAdmin.Properties.Settings.Default.ShowHiddenVMs)) downPIFs.Add(pif); } try { foreach (NetworkingPropertiesPage page in verticalTabs.Items) CollateChanges(page, page.Tag as PIF, newPIFs, newNamePIFs); } catch (Failure) { using (var dlg = new WarningDialog(Messages.NETWORK_RECONFIG_CONNECTION_LOST)) dlg.ShowDialog(this); this.Close(); return; } bool displayWarning = false; bool managementInterfaceIPChanged = false; PIF down_management = downPIFs.Find(p => p.management); PIF new_management = newPIFs.Select(p => p.Item1).FirstOrDefault(p => p.management); if (down_management != null) { if (new_management == null) throw new Failure(Failure.INTERNAL_ERROR, "Bringing down the management interface without bringing another one up is impossible!"); managementInterfaceIPChanged = GetManagementInterfaceIPChanged(down_management, new_management); displayWarning = managementInterfaceIPChanged || (down_management.uuid != new_management.uuid || down_management.ip_configuration_mode != new_management.ip_configuration_mode); if (down_management.Equals(new_management)) { // Management interface has not changed down_management = null; } } // Any PIFs that are in downPIFs but also in newPIFs need to be removed from the former. // downPIFs should contain all those that we no longer want to keep up. downPIFs.RemoveAll(p => newPIFs.Any(np => np.Item1.uuid == p.uuid)); // Remove any PIFs that haven't changed -- there's nothing to do for these ones. They are in this // list originally so that they can be used as a filter against downPIFs. newPIFs.RemoveAll(p => !p.Item2); newNamePIFs.RemoveAll(p => !p.Item2); if (newNamePIFs.Count > 0) { new SetSecondaryManagementPurposeAction(connection, Pool, newNamePIFs.Select(p=>p.Item1).ToList()).RunAsync(); } if (newPIFs.Count > 0 || downPIFs.Count > 0) { if (displayWarning) { string title = string.Format(Pool == null ? Messages.NETWORKING_PROPERTIES_WARNING_CHANGING_MANAGEMENT_HOST : Messages.NETWORKING_PROPERTIES_WARNING_CHANGING_MANAGEMENT_POOL, BrandManager.BrandConsole); DialogResult dialogResult; using (var dlg = new WarningDialog(title, new ThreeButtonDialog.TBDButton(Messages.NETWORKING_PROPERTIES_CHANGING_MANAGEMENT_CONTINUE, DialogResult.OK), ThreeButtonDialog.ButtonCancel){HelpNameSetter = "NetworkingPropertiesPMIWarning"}) { dialogResult = dlg.ShowDialog(this); } if (DialogResult.OK != dialogResult) { DialogResult = DialogResult.None; return; } } if (down_management == null) { // We're actually just changing the IP address, not moving the management interface, so we just pass it in through newPIFs. new_management = null; } else { // We're switching the management interface over, so remove the old one from downPIFs -- it will be special-cased through // down_management and new_management. downPIFs.Remove(down_management); } // Reverse the lists so that the management interface is always last to be done. If something's going to go wrong, then we'd like // the management interface to be the one that we don't break. newPIFs.Reverse(); downPIFs.Reverse(); new ChangeNetworkingAction(connection, Pool, Host, newPIFs.Select(p => p.Item1).ToList(), downPIFs, new_management, down_management, managementInterfaceIPChanged).RunAsync(); } Close(); } /// /// Will throw an exception if the network has gone away. /// /// /// /// /// private void CollateChanges(NetworkingPropertiesPage page, PIF oldPIF, List newPIFs, List newNamePIFs) { bool changed = false; bool changedName = false; if (oldPIF == null) { // This tab is newly created. XenAPI.Network network = (XenAPI.Network)page.NetworkComboBox.SelectedItem; oldPIF = FindPIFForThisHost(network.PIFs); if (oldPIF == null) throw new Failure(Failure.INTERNAL_ERROR, "Network has gone away"); changed = true; } else { // This tab was populated when this dialog was launched. XenAPI.Network network = connection.Resolve(oldPIF.network); if ((XenAPI.Network)page.NetworkComboBox.SelectedItem != network) { // The user has changed the network, so find the one we're using now. XenAPI.Network new_network = (XenAPI.Network)page.NetworkComboBox.SelectedItem; oldPIF = FindPIFForThisHost(new_network.PIFs); if (oldPIF == null) throw new Failure(Failure.INTERNAL_ERROR, "Network has gone away"); changed = true; } } PIF newPIF = (PIF)oldPIF.Clone(); newPIF.PropertyChanged += (sender, e) => { changed = true; }; PIF newNamePIF = (PIF)oldPIF.Clone(); newNamePIF.PropertyChanged += (sender, e) => { changedName = true; }; if (page.DHCPIPRadioButton.Checked) { newPIF.ip_configuration_mode = ip_configuration_mode.DHCP; } else { newPIF.ip_configuration_mode = ip_configuration_mode.Static; newPIF.netmask = page.SubnetTextBox.Text; newPIF.gateway = page.GatewayTextBox.Text; newPIF.IP = page.IPAddressTextBox.Text; List dns = new List(); if (page.PreferredDNSTextBox.Text.Length > 0) dns.Add(page.PreferredDNSTextBox.Text); if (page.AlternateDNS1TextBox.Text.Length > 0) dns.Add(page.AlternateDNS1TextBox.Text); if (page.AlternateDNS2TextBox.Text.Length > 0) dns.Add(page.AlternateDNS2TextBox.Text); var newDns = string.Join(",", dns.ToArray()); newPIF.DNS = newDns; } newPIF.management = page.type != NetworkingPropertiesPage.Type.SECONDARY; if (page.type == NetworkingPropertiesPage.Type.SECONDARY) { if (changed) newPIF.SetManagementPurpose(page.PurposeTextBox.Text); else newNamePIF.SetManagementPurpose(page.PurposeTextBox.Text); } newPIFs.Add(new PifTuple(newPIF, changed)); newNamePIFs.Add(new PifTuple(newNamePIF, changedName)); } private PIF FindPIFForThisHost(List> pifs) { foreach (PIF pif in connection.ResolveAll(pifs)) { if (pif.host.opaque_ref == Host.opaque_ref) return pif; } return null; } private XenAPI.Network ManagementNetwork() { return (XenAPI.Network)((NetworkingPropertiesPage)verticalTabs.Items[0]).NetworkComboBox.SelectedItem; } private void splitContainer_Panel1_Resize(object sender, EventArgs e) { ResizeVerticalTabs(verticalTabs.Items.Count); } private Rectangle GetItemTextBounds(Rectangle itemBounds) { return new Rectangle(itemBounds.X, itemBounds.Y, itemBounds.Width - 20, itemBounds.Height); } private void verticalTabs_DrawItem(object sender, DrawItemEventArgs e) { if (e.Index < 0 || e.Index >= verticalTabs.Items.Count) return; NetworkingPropertiesPage page = verticalTabs.Items[e.Index] as NetworkingPropertiesPage; if (page == null|| page.type != NetworkingPropertiesPage.Type.SECONDARY || page.ClusteringEnabled) return; Graphics g = e.Graphics; Rectangle b = e.Bounds; // draw Delete icon Image deleteIcon = Images.StaticImages._000_Abort_h32bit_16; if (deleteIcon != null) { page.DeleteIconBounds = new Rectangle(b.Right - deleteIcon.Width - ((32 - deleteIcon.Width) / 2), b.Y + ((32 - deleteIcon.Height) / 2), deleteIcon.Width, deleteIcon.Height); g.DrawImage(deleteIcon, page.DeleteIconBounds); } } private bool MouseIsOnDeleteIcon(Point mouseLocation) { int pageIndex = verticalTabs.IndexFromPoint(mouseLocation); if (pageIndex < 0) return false; NetworkingPropertiesPage page = verticalTabs.Items[pageIndex] as NetworkingPropertiesPage; if (page == null) return false; var bounds = page.DeleteIconBounds; return bounds.Contains(mouseLocation); } private void verticalTabs_MouseMove(object sender, MouseEventArgs e) { if (MouseIsOnDeleteIcon(e.Location)) ShowTooltip(e.Location); else HideTooltip(); } private void verticalTabs_MouseClick(object sender, MouseEventArgs e) { int pageIndex = verticalTabs.IndexFromPoint(e.Location); if (pageIndex < 0 || !MouseIsOnDeleteIcon(e.Location)) return; NetworkingPropertiesPage page = verticalTabs.Items[pageIndex] as NetworkingPropertiesPage; if (page != null) { prop_page_DeleteButtonClicked(page, new EventArgs()); HideTooltip(); } } private void linkLabelTellMeMore_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { HelpManager.Launch("NetworkingProperties"); } private void ShowTooltip(Point location) { if (!tooltipVisible) { toolTipRemove.Show(Messages.NETWORKING_PROPERTIES_REMOVE_TOOLTIP, verticalTabs, location.X, location.Y + 20); tooltipVisible = true; Cursor = Cursors.Hand; } } private void HideTooltip() { toolTipRemove.Hide(verticalTabs); tooltipVisible = false; Cursor = Cursors.Default; } protected override string GetTabTitle(VerticalTabs.IVerticalTab verticalTab) { NetworkingPropertiesPage page = verticalTab as NetworkingPropertiesPage; if (page != null) { return page.type == NetworkingPropertiesPage.Type.SECONDARY ? page.Text : Messages.NETWORKING_PROPERTIES_TAB_TITLE_PRIMARY; } return base.GetTabTitle(verticalTab); } } }