Merge "Bring up VLAN interfaces and include in introspection report"
This commit is contained in:
commit
1a9491e651
@ -291,6 +291,15 @@ cli_opts = [
|
||||
'This is an advanced override setting which may only '
|
||||
'be useful if the environment requires API version '
|
||||
'auto-detection to be disabled or blocked.'),
|
||||
cfg.StrOpt('enable_vlan_interfaces',
|
||||
default=APARAMS.get('ipa-enable-vlan-interfaces', ''),
|
||||
help='Comma-separated list of VLAN interfaces to enable, '
|
||||
'in the format "interface.vlan". If only an '
|
||||
'interface is provided, then IPA should attempt to '
|
||||
'bring up all VLANs on that interface detected '
|
||||
'via lldp. If "all" is set then IPA should attempt '
|
||||
'to bring up all VLANs from lldp on all interfaces. '
|
||||
'By default, no VLANs will be brought up.'),
|
||||
]
|
||||
|
||||
CONF.register_cli_opts(cli_opts)
|
||||
|
@ -1071,6 +1071,12 @@ class GenericHardwareManager(HardwareManager):
|
||||
as None.
|
||||
"""
|
||||
global WARN_BIOSDEVNAME_NOT_FOUND
|
||||
|
||||
if self._is_vlan(interface_name):
|
||||
LOG.debug('Interface %s is a VLAN, biosdevname not called',
|
||||
interface_name)
|
||||
return
|
||||
|
||||
try:
|
||||
stdout, _ = utils.execute('biosdevname', '-i',
|
||||
interface_name)
|
||||
@ -1093,10 +1099,19 @@ class GenericHardwareManager(HardwareManager):
|
||||
interface_name)
|
||||
return os.path.exists(device_path)
|
||||
|
||||
def _is_vlan(self, interface_name):
|
||||
# A VLAN interface does not have /device, check naming convention
|
||||
# used when adding VLAN interface
|
||||
|
||||
interface, sep, vlan = interface_name.partition('.')
|
||||
|
||||
return vlan.isdigit()
|
||||
|
||||
def list_network_interfaces(self):
|
||||
network_interfaces_list = []
|
||||
iface_names = os.listdir('{}/class/net'.format(self.sys_path))
|
||||
iface_names = [name for name in iface_names if self._is_device(name)]
|
||||
iface_names = [name for name in iface_names
|
||||
if self._is_vlan(name) or self._is_device(name)]
|
||||
|
||||
if CONF.collect_lldp:
|
||||
self.lldp_data = dispatch_to_managers('collect_lldp_data',
|
||||
@ -1108,6 +1123,16 @@ class GenericHardwareManager(HardwareManager):
|
||||
result.lldp = self._get_lldp_data(iface_name)
|
||||
network_interfaces_list.append(result)
|
||||
|
||||
# If configured, bring up vlan interfaces. If the actual vlans aren't
|
||||
# defined they are derived from LLDP data
|
||||
if CONF.enable_vlan_interfaces:
|
||||
vlan_iface_names = netutils.bring_up_vlan_interfaces(
|
||||
network_interfaces_list)
|
||||
for vlan_iface_name in vlan_iface_names:
|
||||
result = dispatch_to_managers(
|
||||
'get_interface_info', interface_name=vlan_iface_name)
|
||||
network_interfaces_list.append(result)
|
||||
|
||||
return network_interfaces_list
|
||||
|
||||
def get_cpus(self):
|
||||
|
@ -24,6 +24,8 @@ from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import netutils
|
||||
|
||||
from ironic_python_agent import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
@ -33,6 +35,14 @@ SIOCGIFFLAGS = 0x8913
|
||||
SIOCSIFFLAGS = 0x8914
|
||||
INFINIBAND_ADDR_LEN = 59
|
||||
|
||||
# LLDP definitions needed to extract vlan information
|
||||
LLDP_TLV_ORG_SPECIFIC = 127
|
||||
# 802.1Q defines from http://www.ieee802.org/1/pages/802.1Q-2014.html, Annex D
|
||||
LLDP_802dot1_OUI = "0080c2"
|
||||
# subtypes
|
||||
dot1_VLAN_NAME = "03"
|
||||
VLAN_ID_LEN = len(LLDP_802dot1_OUI + dot1_VLAN_NAME)
|
||||
|
||||
|
||||
class ifreq(ctypes.Structure):
|
||||
"""Class for setting flags on a socket."""
|
||||
@ -258,3 +268,110 @@ def get_wildcard_address():
|
||||
if netutils.is_ipv6_enabled():
|
||||
return "::"
|
||||
return "0.0.0.0"
|
||||
|
||||
|
||||
def _get_configured_vlans():
|
||||
return [x.strip() for x in CONF.enable_vlan_interfaces.split(',')
|
||||
if x.strip()]
|
||||
|
||||
|
||||
def _add_vlan_interface(interface, vlan, interfaces_list):
|
||||
|
||||
vlan_name = interface + '.' + vlan
|
||||
|
||||
# if any(x for x in interfaces_list if x.name == vlan_name):
|
||||
if any(x.name == vlan_name for x in interfaces_list):
|
||||
LOG.info("VLAN interface %s has already been added", vlan_name)
|
||||
return ''
|
||||
|
||||
try:
|
||||
LOG.info('Adding VLAN interface %s', vlan_name)
|
||||
# Add the interface
|
||||
utils.execute('ip', 'link', 'add', 'link', interface, 'name',
|
||||
vlan_name, 'type', 'vlan', 'id', vlan,
|
||||
check_exit_code=[0, 2])
|
||||
|
||||
# Bring up interface
|
||||
utils.execute('ip', 'link', 'set', 'dev', vlan_name, 'up')
|
||||
|
||||
except Exception as exc:
|
||||
LOG.warning('Exception when running ip commands to add VLAN '
|
||||
'interface: %s', exc)
|
||||
return ''
|
||||
|
||||
return vlan_name
|
||||
|
||||
|
||||
def _add_vlans_from_lldp(lldp, interface, interfaces_list):
|
||||
interfaces = []
|
||||
|
||||
# Get the lldp packets received on this interface
|
||||
if lldp:
|
||||
for type, value in lldp:
|
||||
if (type == LLDP_TLV_ORG_SPECIFIC
|
||||
and value.startswith(LLDP_802dot1_OUI
|
||||
+ dot1_VLAN_NAME)):
|
||||
vlan = str(int(value[VLAN_ID_LEN: VLAN_ID_LEN + 4], 16))
|
||||
name = _add_vlan_interface(interface, vlan,
|
||||
interfaces_list)
|
||||
if name:
|
||||
interfaces.append(name)
|
||||
else:
|
||||
LOG.debug('VLAN interface %s does not have lldp info', interface)
|
||||
|
||||
return interfaces
|
||||
|
||||
|
||||
def bring_up_vlan_interfaces(interfaces_list):
|
||||
"""Bring up vlan interfaces based on kernel params
|
||||
|
||||
Use the configured value of ``enable_vlan_interfaces`` to determine
|
||||
if VLAN interfaces should be brought up using ``ip`` commands. If
|
||||
``enable_vlan_interfaces`` defines a particular vlan then bring up
|
||||
that vlan. If it defines an interface or ``all`` then use LLDP info
|
||||
to figure out which VLANs should be brought up.
|
||||
|
||||
:param interfaces_list: List of current interfaces
|
||||
:return: List of vlan interface names that have been added
|
||||
"""
|
||||
interfaces = []
|
||||
vlan_interfaces = _get_configured_vlans()
|
||||
for vlan_int in vlan_interfaces:
|
||||
# TODO(bfournie) skip if pxe boot interface
|
||||
if '.' in vlan_int:
|
||||
# interface and vlan are provided
|
||||
interface, vlan = vlan_int.split('.', 1)
|
||||
if any(x.name == interface for x in interfaces_list):
|
||||
name = _add_vlan_interface(interface, vlan,
|
||||
interfaces_list)
|
||||
if name:
|
||||
interfaces.append(name)
|
||||
else:
|
||||
LOG.warning('Provided VLAN interface %s does not exist',
|
||||
interface)
|
||||
elif CONF.collect_lldp:
|
||||
# Get the vlans from lldp info
|
||||
if vlan_int == 'all':
|
||||
# Use all interfaces
|
||||
for iface in interfaces_list:
|
||||
names = _add_vlans_from_lldp(
|
||||
iface.lldp, iface.name, interfaces_list)
|
||||
if names:
|
||||
interfaces.extend(names)
|
||||
else:
|
||||
# Use provided interface
|
||||
lldp = next((x.lldp for x in interfaces_list
|
||||
if x.name == vlan_int), None)
|
||||
if lldp:
|
||||
names = _add_vlans_from_lldp(lldp, vlan_int,
|
||||
interfaces_list)
|
||||
if names:
|
||||
interfaces.extend(names)
|
||||
else:
|
||||
LOG.warning('Provided interface name %s was not found',
|
||||
vlan_int)
|
||||
else:
|
||||
LOG.warning('Attempting to add VLAN interfaces but specific '
|
||||
'interface not provided and LLDP not enabled')
|
||||
|
||||
return interfaces
|
||||
|
@ -1344,6 +1344,186 @@ class TestGenericHardwareManager(base.IronicAgentTest):
|
||||
self.assertEqual('0x1014', interfaces[0].product)
|
||||
self.assertEqual('em0', interfaces[0].biosdevname)
|
||||
|
||||
@mock.patch('ironic_python_agent.hardware.get_managers', autospec=True)
|
||||
@mock.patch('netifaces.ifaddresses', autospec=True)
|
||||
@mock.patch('os.listdir', autospec=True)
|
||||
@mock.patch('os.path.exists', autospec=True)
|
||||
@mock.patch('builtins.open', autospec=True)
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
@mock.patch.object(netutils, 'get_mac_addr', autospec=True)
|
||||
@mock.patch.object(netutils, 'interface_has_carrier', autospec=True)
|
||||
def test_list_network_vlan_interfaces(self,
|
||||
mock_has_carrier,
|
||||
mock_get_mac,
|
||||
mocked_execute,
|
||||
mocked_open,
|
||||
mocked_exists,
|
||||
mocked_listdir,
|
||||
mocked_ifaddresses,
|
||||
mockedget_managers):
|
||||
mockedget_managers.return_value = [hardware.GenericHardwareManager()]
|
||||
CONF.set_override('enable_vlan_interfaces', 'eth0.100')
|
||||
mocked_listdir.return_value = ['lo', 'eth0']
|
||||
mocked_exists.side_effect = [False, True, False]
|
||||
mocked_open.return_value.__enter__ = lambda s: s
|
||||
mocked_open.return_value.__exit__ = mock.Mock()
|
||||
read_mock = mocked_open.return_value.read
|
||||
read_mock.side_effect = ['1']
|
||||
mocked_ifaddresses.return_value = {
|
||||
netifaces.AF_INET: [{'addr': '192.168.1.2'}],
|
||||
netifaces.AF_INET6: [{'addr': 'fd00::101'}]
|
||||
}
|
||||
mocked_execute.return_value = ('em0\n', '')
|
||||
mock_get_mac.mock_has_carrier = True
|
||||
mock_get_mac.return_value = '00:0c:29:8c:11:b1'
|
||||
interfaces = self.hardware.list_network_interfaces()
|
||||
self.assertEqual(2, len(interfaces))
|
||||
self.assertEqual('eth0', interfaces[0].name)
|
||||
self.assertEqual('00:0c:29:8c:11:b1', interfaces[0].mac_address)
|
||||
self.assertEqual('192.168.1.2', interfaces[0].ipv4_address)
|
||||
self.assertEqual('fd00::101', interfaces[0].ipv6_address)
|
||||
self.assertIsNone(interfaces[0].lldp)
|
||||
self.assertEqual('eth0.100', interfaces[1].name)
|
||||
self.assertEqual('00:0c:29:8c:11:b1', interfaces[1].mac_address)
|
||||
self.assertIsNone(interfaces[1].lldp)
|
||||
|
||||
@mock.patch('ironic_python_agent.hardware.get_managers', autospec=True)
|
||||
@mock.patch('ironic_python_agent.netutils.get_lldp_info', autospec=True)
|
||||
@mock.patch('netifaces.ifaddresses', autospec=True)
|
||||
@mock.patch('os.listdir', autospec=True)
|
||||
@mock.patch('os.path.exists', autospec=True)
|
||||
@mock.patch('builtins.open', autospec=True)
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
@mock.patch.object(netutils, 'get_mac_addr', autospec=True)
|
||||
@mock.patch.object(netutils, 'interface_has_carrier', autospec=True)
|
||||
def test_list_network_vlan_interfaces_using_lldp(self,
|
||||
mock_has_carrier,
|
||||
mock_get_mac,
|
||||
mocked_execute,
|
||||
mocked_open,
|
||||
mocked_exists,
|
||||
mocked_listdir,
|
||||
mocked_ifaddresses,
|
||||
mocked_lldp_info,
|
||||
mockedget_managers):
|
||||
mockedget_managers.return_value = [hardware.GenericHardwareManager()]
|
||||
CONF.set_override('collect_lldp', True)
|
||||
CONF.set_override('enable_vlan_interfaces', 'eth0')
|
||||
mocked_listdir.return_value = ['lo', 'eth0']
|
||||
mocked_execute.return_value = ('em0\n', '')
|
||||
mocked_exists.side_effect = [False, True, False]
|
||||
mocked_open.return_value.__enter__ = lambda s: s
|
||||
mocked_open.return_value.__exit__ = mock.Mock()
|
||||
read_mock = mocked_open.return_value.read
|
||||
read_mock.side_effect = ['1']
|
||||
mocked_lldp_info.return_value = {'eth0': [
|
||||
(0, b''),
|
||||
(127, b'\x00\x80\xc2\x03\x00d\x08vlan-100'),
|
||||
(127, b'\x00\x80\xc2\x03\x00e\x08vlan-101')]
|
||||
}
|
||||
mock_has_carrier.return_value = True
|
||||
mock_get_mac.return_value = '00:0c:29:8c:11:b1'
|
||||
interfaces = self.hardware.list_network_interfaces()
|
||||
self.assertEqual(3, len(interfaces))
|
||||
self.assertEqual('eth0', interfaces[0].name)
|
||||
self.assertEqual('00:0c:29:8c:11:b1', interfaces[0].mac_address)
|
||||
expected_lldp_info = [
|
||||
(0, ''),
|
||||
(127, "0080c203006408766c616e2d313030"),
|
||||
(127, "0080c203006508766c616e2d313031")
|
||||
]
|
||||
self.assertEqual(expected_lldp_info, interfaces[0].lldp)
|
||||
self.assertEqual('eth0.100', interfaces[1].name)
|
||||
self.assertEqual('00:0c:29:8c:11:b1', interfaces[1].mac_address)
|
||||
self.assertIsNone(interfaces[1].lldp)
|
||||
self.assertEqual('eth0.101', interfaces[2].name)
|
||||
self.assertEqual('00:0c:29:8c:11:b1', interfaces[2].mac_address)
|
||||
self.assertIsNone(interfaces[2].lldp)
|
||||
|
||||
@mock.patch.object(netutils, 'LOG', autospec=True)
|
||||
@mock.patch('ironic_python_agent.hardware.get_managers', autospec=True)
|
||||
@mock.patch('netifaces.ifaddresses', autospec=True)
|
||||
@mock.patch('os.listdir', autospec=True)
|
||||
@mock.patch('os.path.exists', autospec=True)
|
||||
@mock.patch('builtins.open', autospec=True)
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
@mock.patch.object(netutils, 'get_mac_addr', autospec=True)
|
||||
@mock.patch.object(netutils, 'interface_has_carrier', autospec=True)
|
||||
def test_list_network_vlan_invalid_int(self,
|
||||
mock_has_carrier,
|
||||
mock_get_mac,
|
||||
mocked_execute,
|
||||
mocked_open,
|
||||
mocked_exists,
|
||||
mocked_listdir,
|
||||
mocked_ifaddresses,
|
||||
mockedget_managers,
|
||||
mocked_log):
|
||||
mockedget_managers.return_value = [hardware.GenericHardwareManager()]
|
||||
CONF.set_override('collect_lldp', True)
|
||||
CONF.set_override('enable_vlan_interfaces', 'enp0s1')
|
||||
mocked_listdir.return_value = ['lo', 'eth0']
|
||||
mocked_exists.side_effect = [False, True, False]
|
||||
mocked_open.return_value.__enter__ = lambda s: s
|
||||
mocked_open.return_value.__exit__ = mock.Mock()
|
||||
read_mock = mocked_open.return_value.read
|
||||
read_mock.side_effect = ['1']
|
||||
mocked_ifaddresses.return_value = {
|
||||
netifaces.AF_INET: [{'addr': '192.168.1.2'}],
|
||||
netifaces.AF_INET6: [{'addr': 'fd00::101'}]
|
||||
}
|
||||
mocked_execute.return_value = ('em0\n', '')
|
||||
mock_get_mac.mock_has_carrier = True
|
||||
mock_get_mac.return_value = '00:0c:29:8c:11:b1'
|
||||
|
||||
self.hardware.list_network_interfaces()
|
||||
mocked_log.warning.assert_called_once_with(
|
||||
'Provided interface name %s was not found', 'enp0s1')
|
||||
|
||||
@mock.patch('ironic_python_agent.hardware.get_managers', autospec=True)
|
||||
@mock.patch('ironic_python_agent.netutils.get_lldp_info', autospec=True)
|
||||
@mock.patch('os.listdir', autospec=True)
|
||||
@mock.patch('os.path.exists', autospec=True)
|
||||
@mock.patch('builtins.open', autospec=True)
|
||||
@mock.patch.object(utils, 'execute', autospec=True)
|
||||
@mock.patch.object(netutils, 'get_mac_addr', autospec=True)
|
||||
def test_list_network_vlan_interfaces_using_lldp_all(self,
|
||||
mock_get_mac,
|
||||
mocked_execute,
|
||||
mocked_open,
|
||||
mocked_exists,
|
||||
mocked_listdir,
|
||||
mocked_lldp_info,
|
||||
mockedget_managers):
|
||||
mockedget_managers.return_value = [hardware.GenericHardwareManager()]
|
||||
CONF.set_override('collect_lldp', True)
|
||||
CONF.set_override('enable_vlan_interfaces', 'all')
|
||||
mocked_listdir.return_value = ['lo', 'eth0', 'eth1']
|
||||
mocked_execute.return_value = ('em0\n', '')
|
||||
mocked_exists.side_effect = [False, True, True]
|
||||
mocked_open.return_value.__enter__ = lambda s: s
|
||||
mocked_open.return_value.__exit__ = mock.Mock()
|
||||
read_mock = mocked_open.return_value.read
|
||||
read_mock.side_effect = ['1']
|
||||
mocked_lldp_info.return_value = {'eth0': [
|
||||
(0, b''),
|
||||
(127, b'\x00\x80\xc2\x03\x00d\x08vlan-100'),
|
||||
(127, b'\x00\x80\xc2\x03\x00e\x08vlan-101')],
|
||||
'eth1': [
|
||||
(0, b''),
|
||||
(127, b'\x00\x80\xc2\x03\x00f\x08vlan-102'),
|
||||
(127, b'\x00\x80\xc2\x03\x00g\x08vlan-103')]
|
||||
}
|
||||
|
||||
interfaces = self.hardware.list_network_interfaces()
|
||||
self.assertEqual(6, len(interfaces))
|
||||
self.assertEqual('eth0', interfaces[0].name)
|
||||
self.assertEqual('eth1', interfaces[1].name)
|
||||
self.assertEqual('eth0.100', interfaces[2].name)
|
||||
self.assertEqual('eth0.101', interfaces[3].name)
|
||||
self.assertEqual('eth1.102', interfaces[4].name)
|
||||
self.assertEqual('eth1.103', interfaces[5].name)
|
||||
|
||||
@mock.patch.object(os, 'readlink', autospec=True)
|
||||
@mock.patch.object(os, 'listdir', autospec=True)
|
||||
@mock.patch.object(hardware, 'get_cached_node', autospec=True)
|
||||
|
12
releasenotes/notes/add-vlan-interfaces-cdfeb39d0f3d444d.yaml
Normal file
12
releasenotes/notes/add-vlan-interfaces-cdfeb39d0f3d444d.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds the ability to bring up VLAN interfaces and include them in the
|
||||
introspection report. A new kernel params field is added -
|
||||
``ipa-enable-vlan-interfaces``, which defines either the VLAN interface
|
||||
to enable, the interface to use, or 'all' - which indicates all
|
||||
interfaces. If the particular VLAN is not provided, IPA will
|
||||
use the LLDP information for the interface to determine which VLANs should
|
||||
be enabled. See
|
||||
`story 2008298 <https://storyboard.openstack.org/#!/story/2008298>`_.
|
||||
|
Loading…
Reference in New Issue
Block a user