4af672b849
For Ironic multi-tenant networking support, we need to be able to discover the nodes local connectivity. To do this IPA can try to pull LLDP packets for every NIC. This patch adds a processing hook to handle the data from these packets in ironic-inspector so that we can populate the correct fields fields in Ironic. The generic lldp hook only handles the mandatory fields port id and chassis id, set on port_id and switch_id in local_link_connection. Further LLDP fields should be handled by additional vendor specific LLDP processing hooks, that populate the switch_info field in a non-generic way. Change-Id: I884eaaa9cc54cd08c21147da438b1dabc10d3a40 Related-Bug: #1526403 Depends-On: Ie655fd59b06de7b84fba3b438d5e4c2ecd8075c3 Depends-On: Idae9b1ede1797029da1bd521501b121957ca1f1a
123 lines
4.7 KiB
Python
123 lines
4.7 KiB
Python
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
# implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Generic LLDP Processing Hook"""
|
|
|
|
import binascii
|
|
|
|
from ironicclient import exc as client_exc
|
|
import netaddr
|
|
from oslo_config import cfg
|
|
|
|
from ironic_inspector.common.i18n import _LW, _LE
|
|
from ironic_inspector.common import ironic
|
|
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'
|
|
|
|
|
|
class GenericLocalLinkConnectionHook(base.ProcessingHook):
|
|
"""Process mandatory LLDP packet fields
|
|
|
|
Non-vendor specific LLDP packet fields processed for each NIC found for a
|
|
baremetal node, port ID and chassis ID. These fields if found and if valid
|
|
will be saved into the local link connection info port id and switch id
|
|
fields on the Ironic port that represents that NIC.
|
|
"""
|
|
|
|
def _get_local_link_patch(self, tlv_type, tlv_value, port):
|
|
try:
|
|
data = bytearray(binascii.unhexlify(tlv_value))
|
|
except TypeError:
|
|
LOG.warning(_LW("TLV value for TLV type %d not in correct"
|
|
"format, ensure TLV value is in "
|
|
"hexidecimal format when sent to "
|
|
"inspector"), tlv_type)
|
|
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()))
|
|
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()))
|
|
|
|
if item and 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)
|
|
|
|
ironic_ports = node_info.ports()
|
|
|
|
for iface in inventory['interfaces']:
|
|
if iface['name'] not in introspection_data['all_interfaces']:
|
|
continue
|
|
port = ironic_ports[iface['mac_address']]
|
|
|
|
lldp_data = iface.get('lldp')
|
|
if lldp_data is None:
|
|
LOG.warning(_LW("No LLDP Data found for interface %s"), iface)
|
|
continue
|
|
|
|
patches = []
|
|
for tlv_type, tlv_value in lldp_data:
|
|
patch = self._get_local_link_patch(tlv_type, tlv_value, port)
|
|
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
|
|
# pass it into the function.
|
|
cli = ironic.get_client(api_version=REQUIRED_IRONIC_VERSION)
|
|
node_info.patch_port(iface['mac_address'], patches, ironic=cli)
|
|
except client_exc.NotAcceptable:
|
|
LOG.error(_LE("Unable to set Ironic port local link "
|
|
"connection information because Ironic does not "
|
|
"support the required version"))
|
|
# NOTE(sambetts) May as well break out out of the loop here
|
|
# because Ironic version is not going to change for the other
|
|
# interfaces.
|
|
break
|