Support LLDP data as part of interfaces in inventory

To support multi-tenant networking in Ironic we need to be able to
discover not just the NICs a baremetal machine has but also the physical
connectivity to switches in the network.

This patch collects LLDP (Link Layer Discovery Protocol) data as part of
the list interfaces stage of the generic hardware manager. This
information can then be processed by the ironic inspector to populate
the local link information on each ironic port.

The processing done on this data in ironic python agent is limited, this
is to allow for server side processing hooks to process as much or as
little of the data as they want. This is to allow for multi-vendor
environments that might use different parts of the LLDP packet to use a
generic ramdisk and configure the processing server side using inspector
plugins.

Reserved fields switch_port_descr and switch_chassis_descr have been
deprecated for removal in Ocata in favor of passing the whole packet.

Change-Id: Idae9b1ede1797029da1bd521501b121957ca1f1a
Partial-Bug: #1526403
This commit is contained in:
Sam Betts
2016-05-24 17:46:49 +01:00
parent 99a053f654
commit a7f0af722f
6 changed files with 145 additions and 10 deletions

@@ -13,6 +13,7 @@
# limitations under the License.
import abc
import binascii
import functools
import os
import shlex
@@ -31,6 +32,7 @@ import stevedore
from ironic_python_agent import encoding
from ironic_python_agent import errors
from ironic_python_agent import netutils
from ironic_python_agent import utils
_global_managers = None
@@ -181,14 +183,17 @@ class BlockDevice(encoding.SerializableComparable):
class NetworkInterface(encoding.SerializableComparable):
serializable_fields = ('name', 'mac_address', 'switch_port_descr',
'switch_chassis_descr', 'ipv4_address',
'has_carrier')
'has_carrier', 'lldp')
def __init__(self, name, mac_addr, ipv4_address=None, has_carrier=True):
def __init__(self, name, mac_addr, ipv4_address=None, has_carrier=True,
lldp=None):
self.name = name
self.mac_address = mac_addr
self.ipv4_address = ipv4_address
self.has_carrier = has_carrier
# TODO(russellhaering): Pull these from LLDP
self.lldp = lldp
# TODO(sambetts) Remove these fields in Ocata, they have been
# superseded by self.lldp
self.switch_port_descr = None
self.switch_chassis_descr = None
@@ -412,6 +417,7 @@ class GenericHardwareManager(HardwareManager):
def __init__(self):
self.sys_path = '/sys'
self.lldp_data = {}
def evaluate_hardware_support(self):
# Do some initialization before we declare ourself ready
@@ -441,6 +447,32 @@ class GenericHardwareManager(HardwareManager):
LOG.warning('No disks detected in %d seconds',
CONF.disk_wait_delay * CONF.disk_wait_attempts)
def _cache_lldp_data(self, interface_names):
interface_names = [name for name in interface_names if name != 'lo']
try:
raw_lldp_data = netutils.get_lldp_info(interface_names)
except Exception:
# NOTE(sambetts) The get_lldp_info function will log this exception
# and we don't invalidate any existing data in the cache if we fail
# to get data to replace it so just return.
return
for ifname, tlvs in raw_lldp_data.items():
# NOTE(sambetts) Convert each tlv value to hex so that it can be
# serialised safely
processed_tlvs = []
for typ, data in tlvs:
try:
processed_tlvs.append((typ,
binascii.hexlify(data).decode()))
except (binascii.Error, binascii.Incomplete) as e:
LOG.warning('An error occurred while processing TLV type '
'%s for interface %s: %s', (typ, ifname, e))
self.lldp_data[ifname] = processed_tlvs
def _get_lldp_data(self, interface_name):
if self.lldp_data:
return self.lldp_data.get(interface_name)
def _get_interface_info(self, interface_name):
addr_path = '{0}/class/net/{1}/address'.format(self.sys_path,
interface_name)
@@ -450,7 +482,8 @@ class GenericHardwareManager(HardwareManager):
return NetworkInterface(
interface_name, mac_addr,
ipv4_address=self.get_ipv4_addr(interface_name),
has_carrier=self._interface_has_carrier(interface_name))
has_carrier=self._interface_has_carrier(interface_name),
lldp=self._get_lldp_data(interface_name))
def get_ipv4_addr(self, interface_id):
try:
@@ -478,9 +511,12 @@ class GenericHardwareManager(HardwareManager):
def list_network_interfaces(self):
iface_names = os.listdir('{0}/class/net'.format(self.sys_path))
return [self._get_interface_info(name)
for name in iface_names
if self._is_device(name)]
iface_names = [name for name in iface_names if self._is_device(name)]
if CONF.collect_lldp:
self._cache_lldp_data(iface_names)
return [self._get_interface_info(name) for name in iface_names]
def get_cpus(self):
lines = utils.execute('lscpu')[0]