Use processed lldp data, if available, for local_link_connection plugin
This change uses the processed lldp stored by the lldp_basic plugin to populate the local_link_connection port_id and switch_id. If processed lldp data cannot be found, the raw lldp data is processed by this plugin as currently implemented. This patch changes the processing to use the construct lib as implemented in the common definitions in lldp_tlvs.py to parse the LLDP TLVs. A minor change is also made to pass node_info into warning log messages. Change-Id: Iae3fa41736898df2c73350d8dc61ed24f2963e32
This commit is contained in:
parent
328acd4d0a
commit
07c94392e2
|
@ -191,7 +191,9 @@ Here are some plugins that can be additionally enabled:
|
|||
port ID and chassis ID, if found it configures the local link connection
|
||||
information on the nodes Ironic ports with that data. To enable LLDP in the
|
||||
inventory from IPA ``ipa-collect-lldp=1`` should be passed as a kernel
|
||||
parameter to the IPA ramdisk.
|
||||
parameter to the IPA ramdisk. In order to avoid processing the raw LLDP
|
||||
data twice, the ``lldp_basic`` plugin should also be installed and run
|
||||
prior to this plugin.
|
||||
``lldp_basic``
|
||||
Processes LLDP data returned from inspection and parses TLVs from the
|
||||
Basic Management (802.1AB), 802.1Q, and 802.3 sets and stores the
|
||||
|
|
|
@ -15,30 +15,30 @@
|
|||
|
||||
import binascii
|
||||
|
||||
from construct import core
|
||||
from ironicclient import exc as client_exc
|
||||
import netaddr
|
||||
from oslo_config import cfg
|
||||
|
||||
from ironic_inspector.common import ironic
|
||||
from ironic_inspector.common import lldp_parsers
|
||||
from ironic_inspector.common import lldp_tlvs as tlv
|
||||
from ironic_inspector.plugins import base
|
||||
from ironic_inspector import utils
|
||||
|
||||
LOG = utils.getProcessingLogger(__name__)
|
||||
|
||||
# NOTE(sambetts) Constants defined according to IEEE standard for LLDP
|
||||
# http://standards.ieee.org/getieee802/download/802.1AB-2009.pdf
|
||||
LLDP_TLV_TYPE_CHASSIS_ID = 1
|
||||
LLDP_TLV_TYPE_PORT_ID = 2
|
||||
PORT_ID_SUBTYPE_MAC = 3
|
||||
PORT_ID_SUBTYPE_IFNAME = 5
|
||||
PORT_ID_SUBTYPE_LOCAL = 7
|
||||
STRING_PORT_SUBTYPES = [PORT_ID_SUBTYPE_IFNAME, PORT_ID_SUBTYPE_LOCAL]
|
||||
CHASSIS_ID_SUBTYPE_MAC = 4
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
REQUIRED_IRONIC_VERSION = '1.19'
|
||||
|
||||
PORT_ID_ITEM_NAME = "port_id"
|
||||
SWITCH_ID_ITEM_NAME = "switch_id"
|
||||
|
||||
LLDP_PROC_DATA_MAPPING =\
|
||||
{lldp_parsers.LLDP_CHASSIS_ID_NM: SWITCH_ID_ITEM_NAME,
|
||||
lldp_parsers.LLDP_PORT_ID_NM: PORT_ID_ITEM_NAME}
|
||||
|
||||
|
||||
class GenericLocalLinkConnectionHook(base.ProcessingHook):
|
||||
"""Process mandatory LLDP packet fields
|
||||
|
@ -49,33 +49,39 @@ class GenericLocalLinkConnectionHook(base.ProcessingHook):
|
|||
fields on the Ironic port that represents that NIC.
|
||||
"""
|
||||
|
||||
def _get_local_link_patch(self, tlv_type, tlv_value, port):
|
||||
def _get_local_link_patch(self, tlv_type, tlv_value, port, node_info):
|
||||
try:
|
||||
data = bytearray(binascii.unhexlify(tlv_value))
|
||||
except TypeError:
|
||||
LOG.warning("TLV value for TLV type %d not in correct"
|
||||
"format, ensure TLV value is in "
|
||||
"hexidecimal format when sent to "
|
||||
"inspector", tlv_type)
|
||||
"inspector", tlv_type, node_info=node_info)
|
||||
return
|
||||
|
||||
item = value = None
|
||||
if tlv_type == LLDP_TLV_TYPE_PORT_ID:
|
||||
# Check to ensure the port id is an allowed type
|
||||
item = "port_id"
|
||||
if data[0] in STRING_PORT_SUBTYPES:
|
||||
value = data[1:].decode()
|
||||
if data[0] == PORT_ID_SUBTYPE_MAC:
|
||||
value = str(netaddr.EUI(
|
||||
binascii.hexlify(data[1:]).decode(),
|
||||
dialect=netaddr.mac_unix_expanded))
|
||||
elif tlv_type == LLDP_TLV_TYPE_CHASSIS_ID:
|
||||
# Check to ensure the chassis id is the allowed type
|
||||
if data[0] == CHASSIS_ID_SUBTYPE_MAC:
|
||||
item = "switch_id"
|
||||
value = str(netaddr.EUI(
|
||||
binascii.hexlify(data[1:]).decode(),
|
||||
dialect=netaddr.mac_unix_expanded))
|
||||
if tlv_type == tlv.LLDP_TLV_PORT_ID:
|
||||
try:
|
||||
port_id = tlv.PortId.parse(data)
|
||||
except (core.MappingError, netaddr.AddrFormatError) as e:
|
||||
LOG.warning("TLV parse error for Port ID: %s", e,
|
||||
node_info=node_info)
|
||||
return
|
||||
|
||||
item = PORT_ID_ITEM_NAME
|
||||
value = port_id.value
|
||||
elif tlv_type == tlv.LLDP_TLV_CHASSIS_ID:
|
||||
try:
|
||||
chassis_id = tlv.ChassisId.parse(data)
|
||||
except (core.MappingError, netaddr.AddrFormatError) as e:
|
||||
LOG.warning("TLV parse error for Chassis ID: %s", e,
|
||||
node_info=node_info)
|
||||
return
|
||||
|
||||
# Only accept mac address for chassis ID
|
||||
if 'mac_address' in chassis_id.subtype:
|
||||
item = SWITCH_ID_ITEM_NAME
|
||||
value = chassis_id.value
|
||||
|
||||
if item and value:
|
||||
if (not CONF.processing.overwrite_existing and
|
||||
|
@ -85,6 +91,21 @@ class GenericLocalLinkConnectionHook(base.ProcessingHook):
|
|||
'path': '/local_link_connection/%s' % item,
|
||||
'value': value}
|
||||
|
||||
def _get_lldp_processed_patch(self, name, item, lldp_proc_data, port):
|
||||
|
||||
if 'lldp_processed' not in lldp_proc_data:
|
||||
return
|
||||
|
||||
value = lldp_proc_data['lldp_processed'].get(name)
|
||||
|
||||
if value:
|
||||
if (not CONF.processing.overwrite_existing and
|
||||
item in port.local_link_connection):
|
||||
return
|
||||
return {'op': 'add',
|
||||
'path': '/local_link_connection/%s' % item,
|
||||
'value': value}
|
||||
|
||||
def before_update(self, introspection_data, node_info, **kwargs):
|
||||
"""Process LLDP data and patch Ironic port local link connection"""
|
||||
inventory = utils.get_inventory(introspection_data)
|
||||
|
@ -111,11 +132,24 @@ class GenericLocalLinkConnectionHook(base.ProcessingHook):
|
|||
continue
|
||||
|
||||
patches = []
|
||||
for tlv_type, tlv_value in lldp_data:
|
||||
patch = self._get_local_link_patch(tlv_type, tlv_value, port)
|
||||
# First check if lldp data was already processed by lldp_basic
|
||||
# plugin which stores data in 'all_interfaces'
|
||||
proc_data = introspection_data['all_interfaces'][iface['name']]
|
||||
|
||||
for name, item in LLDP_PROC_DATA_MAPPING.items():
|
||||
patch = self._get_lldp_processed_patch(name, item,
|
||||
proc_data, port)
|
||||
if patch is not None:
|
||||
patches.append(patch)
|
||||
|
||||
# If no processed lldp data was available then parse raw lldp data
|
||||
if not patches:
|
||||
for tlv_type, tlv_value in lldp_data:
|
||||
patch = self._get_local_link_patch(tlv_type, tlv_value,
|
||||
port, node_info)
|
||||
if patch is not None:
|
||||
patches.append(patch)
|
||||
|
||||
try:
|
||||
# NOTE(sambetts) We need a newer version of Ironic API for this
|
||||
# transaction, so create a new ironic client and explicitly
|
||||
|
|
|
@ -143,3 +143,54 @@ class TestGenericLocalLinkConnectionHook(test_base.NodeTest):
|
|||
]
|
||||
self.hook.before_update(self.data, self.node_info)
|
||||
self.assertCalledWithPatch(patches, mock_patch)
|
||||
|
||||
@mock.patch.object(node_cache.NodeInfo, 'patch_port')
|
||||
def test_processed_data_available(self, mock_patch):
|
||||
self.data['all_interfaces'] = {
|
||||
'em1': {"ip": self.ips[0], "mac": self.macs[0],
|
||||
"lldp_processed": {
|
||||
"switch_chassis_id": "11:22:33:aa:bb:dd",
|
||||
"switch_port_id": "Ethernet2/66"}
|
||||
}
|
||||
}
|
||||
|
||||
patches = [
|
||||
{'path': '/local_link_connection/port_id',
|
||||
'value': 'Ethernet2/66', 'op': 'add'},
|
||||
{'path': '/local_link_connection/switch_id',
|
||||
'value': '11:22:33:aa:bb:dd', 'op': 'add'},
|
||||
]
|
||||
self.hook.before_update(self.data, self.node_info)
|
||||
self.assertCalledWithPatch(patches, mock_patch)
|
||||
|
||||
@mock.patch.object(node_cache.NodeInfo, 'patch_port')
|
||||
def test_processed_data_chassis_only(self, mock_patch):
|
||||
self.data['all_interfaces'] = {
|
||||
'em1': {"ip": self.ips[0], "mac": self.macs[0],
|
||||
"lldp_processed": {
|
||||
"switch_chassis_id": "11:22:33:aa:bb:dd"}
|
||||
}
|
||||
}
|
||||
|
||||
patches = [
|
||||
{'path': '/local_link_connection/switch_id',
|
||||
'value': '11:22:33:aa:bb:dd', 'op': 'add'}
|
||||
]
|
||||
self.hook.before_update(self.data, self.node_info)
|
||||
self.assertCalledWithPatch(patches, mock_patch)
|
||||
|
||||
@mock.patch.object(node_cache.NodeInfo, 'patch_port')
|
||||
def test_processed_data_port_only(self, mock_patch):
|
||||
self.data['all_interfaces'] = {
|
||||
'em1': {"ip": self.ips[0], "mac": self.macs[0],
|
||||
"lldp_processed": {
|
||||
"switch_port_id": "Ethernet2/66"}
|
||||
}
|
||||
}
|
||||
|
||||
patches = [
|
||||
{'path': '/local_link_connection/port_id',
|
||||
'value': 'Ethernet2/66', 'op': 'add'}
|
||||
]
|
||||
self.hook.before_update(self.data, self.node_info)
|
||||
self.assertCalledWithPatch(patches, mock_patch)
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Add a check from the link_local_connection plugin to use data stored by the
|
||||
lldp_basic plugin to avoid having to parse the LLDP packets twice.
|
||||
|
Loading…
Reference in New Issue