xenadmin/XenAdmin/SettingsPanels/EditNetworkPage.cs
Mihaela Stoica a6d7a7fea8 CA-121385 Allow creation of VLAN 0 in XenCenter. This applies to the New Network Wizard and the Network Properties.
- The VLAN can be set to 0 only for Creedence or greater hosts on the vSwitch backend.
- When VLAN 0 is selected, an info is shown next to the control, saying "VLAN 0 will receive all traffic not on any other VLAN"
- Also fixed an error on New Network Wizard, where the "VLAN in use" message was still visible when changing the NIC.

Signed-off-by: Mihaela Stoica <mihaela.stoica@citrix.com>
2014-05-20 12:38:03 +01:00

793 lines
30 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.Drawing;
using System.Windows.Forms;
using XenAdmin.Actions;
using XenAdmin.Core;
using XenAPI;
using XenAdmin.Commands;
namespace XenAdmin.SettingsPanels
{
public partial class EditNetworkPage : UserControl, IEditPage
{
private XenAPI.Network network;
private Host host;
private bool nolicenseRestriction = false;
private bool _ValidToSave = true;
private readonly ToolTip InvalidParamToolTip;
private bool runningVMsWithoutTools = false;
public EditNetworkPage()
{
InitializeComponent();
Text = Messages.NETWORK_SETTINGS;
InvalidParamToolTip = new ToolTip();
InvalidParamToolTip.IsBalloon = true;
InvalidParamToolTip.ToolTipIcon = ToolTipIcon.Warning;
InvalidParamToolTip.ToolTipTitle = Messages.INVALID_PARAMETER;
}
public bool ValidToSave
{
get { return _ValidToSave; }
}
public Image Image
{
get
{
return Properties.Resources._000_Network_h32bit_16;
}
}
public void SetXenObjects(IXenObject orig, IXenObject clone)
{
network = clone as XenAPI.Network;
if (network == null)
return;
// use the pif of the master to populate the controls. We use it later in the create_VLAN_from_PIF call in Network Action
host = Helpers.GetMaster(network.Connection);
Repopulate();
EnableDisable();
}
// Can the network's NIC and VLAN be edited?
private bool Editable(PIF pif)
{
return (pif == null || (!pif.IsPhysical && !pif.IsTunnelAccessPIF));
}
private void EnableDisable()
{
SetMTUControlEnablement();
SetNetSettingsEnablement();
panelDisruptionWarning.Visible = WillDisrupt();
ShowHideLacpWarning();
labelVLAN0Info.Visible = numUpDownVLAN.Enabled && numUpDownVLAN.Value == 0;
}
private bool VLANEnabled
{
set
{
numUpDownVLAN.Enabled = value;
}
}
private bool NICSpinnerEnabled
{
set
{
HostPNICList.Enabled = value;
}
}
private void SetNetSettingsEnablement()
{
// The non MTU controls block if any VMs are attached, presumably as their PIFs won't unplug
bool blockDueToAttachedVMs = network.Connection.ResolveAll(network.VIFs).Exists(
delegate(VIF vif)
{
return vif.currently_attached;
});
bool blockDueToManagement = network.Connection.ResolveAll<PIF>(network.PIFs).Exists(
delegate(PIF p)
{
return p.IsManagementInterface(XenAdmin.Properties.Settings.Default.ShowHiddenVMs);
});
bool physical = network.Connection.ResolveAll(network.PIFs).Exists(
delegate(PIF pif)
{
return pif.physical;
});
bool bond = network.Connection.ResolveAll(network.PIFs).Exists(
delegate(PIF pif)
{
return pif.IsBondNIC;
});
bool tunnel = network.Connection.ResolveAll(network.PIFs).Exists(
delegate(PIF pif)
{
return pif.IsTunnelAccessPIF;
});
// If the original network restricts our settings we enable/disable controls here - this could actually only be run once if needed
HostPNICList.Enabled = !blockDueToAttachedVMs && !blockDueToManagement;
if (network.PIFs.Count == 0)
{
//internal - we can do what we want with these, even if they have VMs running on them
VLANEnabled = true;
NICSpinnerEnabled = true;
warningText.Visible = false;
}
else if (!physical && !bond && !tunnel)
{
//external, not using physical pif
numUpDownVLAN.Enabled = !blockDueToAttachedVMs && !blockDueToManagement;
warningText.Visible = blockDueToAttachedVMs || blockDueToManagement;
warningText.Text =
blockDueToManagement ? string.Format(Messages.CANNOT_CONFIGURE_NET_DISTURB_MANAGEMENT, GetNetworksPIF().ManagementInterfaceNameOrUnknown) :
blockDueToAttachedVMs ? Messages.CANNOT_CONFIGURE_NET_VMS_ATTACHED :
"";
}
else
{
// physical or tunnel or bond: no warning needed as the controls are either all invisible or all allowed
warningText.Visible = false;
}
//Now we additionally disable the VLAN spinner if we have virtual selected - this needs to be run every time we change the controls state
if (SelectedIsInternal)
{
VLANEnabled = false;
}
}
private void SetMTUControlEnablement()
{
if (!network.CanUseJumboFrames)
{
labelCannotConfigureMTU.Visible = false;
labelMTU.Visible = numericUpDownMTU.Visible = false;
return;
}
if (SelectedIsInternal)
{
// internal
// MTU doesn't really do much here
labelCannotConfigureMTU.Visible = false;
numericUpDownMTU.Enabled = false;
return;
}
PIF networksPIF = GetSelectedPIF(); // returns null for new VLAN
if (networksPIF == null || !networksPIF.IsManagementInterface(XenAdmin.Properties.Settings.Default.ShowHiddenVMs))
{
// non management external (could be bond)
if (runningVMsWithoutTools)
{
// The MTU controls have been designed to be more relaxed than the rest of the page, we will only block if we can't unplug the vifs
// due to lack of tools (which then lets us unplug the PIFs)
labelCannotConfigureMTU.Text = Messages.CANNOT_CONFIGURE_JUMBO_VM_NO_TOOLS;
labelCannotConfigureMTU.Visible = true;
numericUpDownMTU.Enabled = false;
}
else if (networksPIF != null && networksPIF.IsTunnelAccessPIF)
{
// This branch is currently not in use as setting the MTU is disabled on CHINs.
// Left in in case future support is added
// with no other more danger warnings we should tell the user it's recommended that they set the MTU on the underlying networks to match
XenAPI.Network mainNetwork = FindCHINMainNetwork(networksPIF);
labelCannotConfigureMTU.Text = string.Format(Messages.SET_MTU_ON_CHINS_UNDER_NETWORK, mainNetwork.Name);
// incase some odd value has been set on the CLI
numericUpDownMTU.Maximum = Math.Max(network.MTU, XenAPI.Network.MTU_MAX);
numericUpDownMTU.Minimum = Math.Min(network.MTU, XenAPI.Network.MTU_MIN);
numericUpDownMTU.Enabled = true;
labelCannotConfigureMTU.Visible = true;
}
else
{
labelCannotConfigureMTU.Visible = false;
// in case some odd value has been set on the CLI
numericUpDownMTU.Maximum = Math.Max(network.MTU, XenAPI.Network.MTU_MAX);
numericUpDownMTU.Minimum = Math.Min(network.MTU, XenAPI.Network.MTU_MIN);
numericUpDownMTU.Enabled = true;
}
}
else
{
// physical or virtual external management (could be bond)
numericUpDownMTU.Enabled = false;
labelCannotConfigureMTU.Text = string.Format(Messages.CANNOT_CONFIGURE_JUMBO_DISTURB_MANAGEMENT, networksPIF.ManagementInterfaceNameOrUnknown);
labelCannotConfigureMTU.Visible = true;
}
}
private XenAPI.Network FindCHINMainNetwork(PIF networksPIF)
{
// Assumes that the pif is the access pif of only one CHIN. If there's more than one, you get the first.
if (networksPIF.tunnel_access_PIF_of.Count < 1)
return null;
Tunnel t = networksPIF.Connection.Resolve<Tunnel>(networksPIF.tunnel_access_PIF_of[0]);
PIF transportPIF = networksPIF.Connection.Resolve<PIF>(t.transport_PIF);
if (transportPIF == null)
return null; //unexepected
return networksPIF.Connection.Resolve<XenAPI.Network>(transportPIF.network);
}
// Get the selected PIF.
// Return null for single-host internal network, or a new VLAN.
private PIF GetSelectedPIF()
{
// If we are on a physical network we don't look at the VLAN combo box, as it is obv not used
PIF p = GetNetworksPIF();
if (p != null && p.physical)
return p;
if (p != null && p.IsBondNIC)
return p;
// also no need to look in the combo box if we're a CHIN as they can't be edited either
if (p != null && p.IsTunnelAccessPIF)
return p;
p = NICNameToVirtualPIF((string)HostPNICList.SelectedItem, (long)numUpDownVLAN.Value);
// this is either now null (on an internal network or a new VLAN) or a non phys pif representing a vlan (external network)
return p;
}
private bool SelectedIsInternal
{
get { return (string)HostPNICList.SelectedItem == Messages.NETWORKPANEL_INTERNAL; }
}
public void Repopulate()
{
if (network == null || host == null)
return;
nolicenseRestriction = host != null && !host.RestrictVLAN;
populateHostNicList();
//set minimum value for VLAN
numUpDownVLAN.Minimum = Helpers.VLAN0Allowed(network.Connection) ? 0 : 1;
PIF pif = GetNetworksPIF();
if (pif != null)
{
bool editable = Editable(pif);
HostVLanLabel.Visible = editable;
HostNicLabel.Visible = editable;
numUpDownVLAN.Visible = editable;
HostPNICList.Visible = editable;
nicHelpLabel.Visible = editable;
if (editable)
{
// virtual pif (external network on VLAN)
numUpDownVLAN.Value = pif.VLAN;
PIF ThePhysicalPIF = FindAssociatedPhysicalPIF();
if (ThePhysicalPIF != null)
HostPNICList.SelectedItem = ThePhysicalPIF.Name;
else
HostPNICList.SelectedItem = pif.Name;
}
bool hasBondMode = HasBondMode;
groupBoxBondMode.Visible = hasBondMode;
bool supportsLinkAggregation = Helpers.SupportsLinkAggregationBond(network.Connection);
radioButtonLacpSrcMac.Visible = radioButtonLacpTcpudpPorts.Visible = supportsLinkAggregation;
if (hasBondMode)
{
switch (NetworkBondMode)
{
case bond_mode.balance_slb:
radioButtonBalanceSlb.Checked = true;
break;
case bond_mode.active_backup:
radioButtonActiveBackup.Checked = true;
break;
case bond_mode.lacp:
if (supportsLinkAggregation)
{
switch (HashingAlgorithm)
{
case Bond.hashing_algoritm.tcpudp_ports:
radioButtonLacpTcpudpPorts.Checked = true;
break;
default:
radioButtonLacpSrcMac.Checked = true;
break;
}
}
break;
}
}
}
else
{
// internal network
HostVLanLabel.Visible = true;
HostNicLabel.Visible = true;
numUpDownVLAN.Visible = true;
HostVLanLabel.Visible = true;
HostPNICList.Visible = true;
nicHelpLabel.Visible = nolicenseRestriction;
groupBoxBondMode.Visible = false;
numUpDownVLAN.Enabled = false;
HostPNICList.SelectedItem = HostPNICList.Items[0];
}
foreach (VIF v in network.Connection.ResolveAll<VIF>(network.VIFs))
{
VM vm = network.Connection.Resolve<VM>(v.VM);
if (vm.power_state != vm_power_state.Running || vm.GetVirtualisationStatus == VM.VirtualisationStatus.OPTIMIZED)
continue;
runningVMsWithoutTools = true;
break;
}
// Populate Automatic checkbox
autoCheckBox.Checked = network.AutoPlug;
// in case some odd value has been set on the CLI
numericUpDownMTU.Maximum = Math.Max(network.MTU, XenAPI.Network.MTU_MAX);
numericUpDownMTU.Minimum = Math.Min(network.MTU, XenAPI.Network.MTU_MIN);
numericUpDownMTU.Value = network.MTU;
numericUpDownMTU.Visible = network.CanUseJumboFrames;
}
private PIF GetNetworksPIF()
{
foreach (PIF pif in network.Connection.ResolveAll(network.PIFs))
if (host.opaque_ref == pif.host.opaque_ref)
return pif;
return null;
}
private PIF FindAssociatedPhysicalPIF()
{
// Gets a pif from the network object
PIF networkPif = GetNetworksPIF();
// virtual network, no associated pif
if (networkPif == null)
return null;
// so actually our network pif is a physical one, return that
if (networkPif.physical)
return networkPif;
// try to find the physical counterpart to our virtual pif
foreach (PIF pif in network.Connection.Cache.PIFs)
if (pif.IsPhysical &&
pif.host.opaque_ref == host.opaque_ref &&
pif.device == networkPif.device &&
pif.opaque_ref != networkPif.opaque_ref)
return pif;
return null;
}
private void populateHostNicList()
{
HostPNICList.BeginUpdate();
try
{
HostPNICList.Items.Clear();
HostPNICList.Items.Add(Messages.NETWORKPANEL_INTERNAL);
if (!nolicenseRestriction)
return;
foreach (PIF pif in network.Connection.Cache.PIFs)
{
if (!Properties.Settings.Default.ShowHiddenVMs &&
!pif.Show(Properties.Settings.Default.ShowHiddenVMs))
continue;
if (!pif.IsPhysical || pif.IsBondSlave)
continue;
if (pif.host.opaque_ref != host.opaque_ref)
continue;
HostPNICList.Items.Add(pif.Name);
}
}
finally
{
HostPNICList.EndUpdate();
}
}
private void HostPNICList_SelectedIndexChanged(object sender, EventArgs e)
{
EnableDisable();
}
private bool MtuHasChanged
{
get { return numericUpDownMTU.Enabled && numericUpDownMTU.Value != network.MTU; }
}
private bond_mode NewBondMode
{
get
{
return radioButtonBalanceSlb.Checked
? bond_mode.balance_slb
: radioButtonActiveBackup.Checked ? bond_mode.active_backup : bond_mode.lacp;
}
}
private bool BondModeHasChanged
{
get { return (radioButtonBalanceSlb.Visible && radioButtonBalanceSlb.Enabled && NetworkBondMode != NewBondMode); }
}
private Bond.hashing_algoritm NewHashingAlgorithm
{
get
{
return radioButtonLacpSrcMac.Checked
? Bond.hashing_algoritm.src_mac
: radioButtonLacpTcpudpPorts.Checked ? Bond.hashing_algoritm.tcpudp_ports : Bond.hashing_algoritm.unknown;
}
}
private bool HashingAlgorithmHasChanged
{
get
{
var newValue = NewHashingAlgorithm;
// has the hashing algorithm (load balancing method) changed to a valid value (i.e. not hashing_algoritm.unknown) ?
return radioButtonLacpSrcMac.Visible && radioButtonLacpSrcMac.Enabled && HashingAlgorithm != newValue &&
newValue != Bond.hashing_algoritm.unknown;
}
}
public bool HasChanged
{
get
{
if (autoCheckBox.Checked != network.AutoPlug || MtuHasChanged || BondModeHasChanged || HashingAlgorithmHasChanged)
return true;
PIF pif = GetNetworksPIF();
if (pif != null)
{
if (!Editable(pif))
return false;
if (pif.Name != (String)HostPNICList.SelectedItem)
return true;
if (pif.VLAN != (long)numUpDownVLAN.Value)
return true;
}
else if (HostPNICList.SelectedIndex != 0)
return true;
return false;
}
}
public void ShowLocalValidationMessages()
{
}
public void Cleanup()
{
InvalidParamToolTip.Dispose();
}
private bool WillDisrupt()
{
// This should follow the same logic as the HasChanged and SaveSettings() methods, and return true if we will do
// a pif unplug/plug or a networkaction
return MtuHasChanged || BondModeHasChanged || InternalToExternal || ExternalToInternal || ExternalChangedDeviceOrVlan || HashingAlgorithmHasChanged;
}
// Gone from a private network to an external one
private bool InternalToExternal
{
get { return network.PIFs.Count == 0 && !SelectedIsInternal; }
}
// Gone from an external network to an internal one
private bool ExternalToInternal
{
get { return network.PIFs.Count > 0 && SelectedIsInternal; }
}
// Gone from external to external on another device or another VLAN
private bool ExternalChangedDeviceOrVlan
{
get
{
if (network.PIFs.Count == 0)
return false;
PIF pif = network.Connection.Resolve<PIF>(network.PIFs[0]);
PIF selectedPif = NICNameToVirtualPIF((string)HostPNICList.SelectedItem, (long)numUpDownVLAN.Value);
return pif != null && Editable(pif) && pif != selectedPif;
}
}
public AsyncAction SaveSettings()
{
List<AsyncAction> actions = new List<AsyncAction>();
network.AutoPlug = autoCheckBox.Checked;
bool needPlugUnplug = MtuHasChanged;
if (MtuHasChanged)
network.MTU = (long)numericUpDownMTU.Value;
if (BondModeHasChanged)
{
List<AsyncAction> bondActions = SetBondModeActions();
if (bondActions != null && bondActions.Count > 0)
actions.AddRange(bondActions);
}
if (HashingAlgorithmHasChanged)
{
List<AsyncAction> bondPropertiesActions = SetBondPropertiesActions();
if (bondPropertiesActions != null && bondPropertiesActions.Count > 0)
actions.AddRange(bondPropertiesActions);
}
// Have the pifs changed? Just key off the first PIF
bool pifsChanged = false;
bool external = false;
PIF new_pif = null;
long vlan = -1;
if (InternalToExternal)
{
pifsChanged = true;
external = true;
new_pif = NICNameToPIF((string)HostPNICList.SelectedItem);
vlan = (long)this.numUpDownVLAN.Value;
}
else if (ExternalToInternal)
{
pifsChanged = true;
external = false;
}
else if (ExternalChangedDeviceOrVlan)
{
pifsChanged = true;
external = true;
new_pif = NICNameToPIF((string)HostPNICList.SelectedItem);
vlan = (long)this.numUpDownVLAN.Value;
}
if (pifsChanged || external)
{
// even if we needPlugUnplug this is ok, the network update action destroys/recreates pifs anyway
// ASSUMPTION: currently we don't allow network reconfigure that leads us here if ANY VMs are attached, so there are no VIFs to plug/unplug
actions.Add(new NetworkAction(network.Connection, network, pifsChanged, external, new_pif, vlan));
}
else if (needPlugUnplug)
{
List<PIF> pifs = network.Connection.ResolveAll<PIF>(network.PIFs);
AsyncAction a = new UnplugPlugNetworkAction(network);
foreach (SelectedItem i in Program.MainWindow.SelectionManager.Selection)
{
Host h = i.XenObject as Host;
if (h == null)
continue;
a.Host = h;
break;
}
actions.Add(a);
}
if (actions.Count == 0)
return null;
return
new MultipleAction(network.Connection, Messages.ACTION_SAVE_CHANGES_TITLE,
Messages.ACTION_SAVE_CHANGES_IN_PROGRESS, Messages.ACTION_SAVE_CHANGES_SUCCESSFUL,
actions);
}
private PIF NICNameToPIF(string p)
{
foreach (PIF pif in network.Connection.Cache.PIFs)
{
if (pif.Name == p && pif.IsPhysical && pif.host.opaque_ref == host.opaque_ref)
return pif;
}
return null;
}
private PIF NICNameToVirtualPIF(string p, long vlan)
{
foreach (PIF pif in network.Connection.Cache.PIFs)
{
if (pif.Name == p && !pif.IsPhysical && !pif.IsTunnelAccessPIF && pif.VLAN == vlan && pif.host.opaque_ref == host.opaque_ref)
return pif;
}
return null;
}
/// <summary>
/// Does the network being edited support bond mode (i.e., is it a bond of Boston or later)?
/// </summary>
private bool HasBondMode
{
get
{
return network.IsBond && Helpers.BostonOrGreater(network.Connection);
}
}
/// <summary>
/// The mode of the bond associated with this network. Assumes HasBondMode has already been tested,
/// and that the Bond objects on each host have the same mode.
/// </summary>
private bond_mode NetworkBondMode
{
get
{
List<Bond> bonds = network.Connection.ResolveAll(network.TheBonds);
return ((bonds == null || bonds.Count == 0) ? bond_mode.unknown : bonds[0].mode);
}
}
/// <summary>
/// The hashing algorithm of the bond associated with this network. Assumes HasBondMode and SupportsLinkAggregation has already been tested,
/// and that the Bond objects on each host have the same hashing algorithm.
/// </summary>
private Bond.hashing_algoritm HashingAlgorithm
{
get
{
List<Bond> bonds = network.Connection.ResolveAll(network.TheBonds);
return ((bonds == null || bonds.Count == 0) ? Bond.hashing_algoritm.unknown : bonds[0].HashingAlgoritm);
}
}
private List<AsyncAction> SetBondModeActions()
{
var ans = new List<AsyncAction>();
foreach (var bond in network.Connection.ResolveAll(network.TheBonds))
{
Bond b = bond; // have to copy it otherwise it will change to the last bond before the delegates are called
ans.Add(new DelegatedAsyncAction(bond.Connection,
Messages.SET_BOND_MODE_ACTION_TITLE,
Messages.SET_BOND_MODE_ACTION_START,
Messages.SET_BOND_MODE_ACTION_END,
session => Bond.set_mode(session, b.opaque_ref, NewBondMode),
"bond.set_mode"));
}
return (ans.Count == 0 ? null : ans);
}
private List<AsyncAction> SetBondPropertiesActions()
{
var ans = new List<AsyncAction>();
foreach (var bond in network.Connection.ResolveAll(network.TheBonds))
{
Bond b = bond; // have to copy it otherwise it will change to the last bond before the delegates are called
ans.Add(new DelegatedAsyncAction(bond.Connection,
Messages.SET_BOND_HASHING_ALGORITHM_ACTION_TITLE,
Messages.SET_BOND_HASHING_ALGORITHM_ACTION_START,
Messages.SET_BOND_HASHING_ALGORITHM_ACTION_END,
session => Bond.set_property(session, b.opaque_ref, "hashing_algorithm", Bond.HashingAlgoritmToString(NewHashingAlgorithm)),
"bond.set_property"));
}
return (ans.Count == 0 ? null : ans);
}
public String SubText
{
get
{
if (network == null)
return "";
PIF pif = GetNetworksPIF();
if (pif != null && pif.IsPhysical)
return Messages.PHYSICAL_DEVICE;
if (pif != null && pif.IsTunnelAccessPIF)
return Messages.CHIN;
if (HostPNICList.SelectedIndex == 0)
return Messages.NETWORKPANEL_INTERNAL;
return String.Format(Messages.NIC_VLAN, HostPNICList.SelectedItem, numUpDownVLAN.Value);
}
}
private void numUpDownVLAN_ValueChanged(object sender, EventArgs e)
{
EnableDisable();
}
private void numericUpDownMTU_ValueChanged(object sender, EventArgs e)
{
EnableDisable();
}
private void BondMode_CheckedChanged(object sender, EventArgs e)
{
EnableDisable();
}
private void ShowHideLacpWarning()
{
panelLACPWarning.Visible = radioButtonLacpSrcMac.Checked || radioButtonLacpTcpudpPorts.Checked;
}
}
}