From b0c871d0fd51af31a55d6a33841477eca9d99538 Mon Sep 17 00:00:00 2001 From: Steven Webster Date: Wed, 19 Sep 2018 20:45:42 -0400 Subject: [PATCH] Enable OVS LLDP inventory This commit introduces a pluggable lldp driver mechanism to enable LLDP discovery and reporting. An lldpd driver and OVS extension driver are included. By default, the lldpd driver is enabled, which operates over dedicated ports created in OVS, as well as standard linux interfaces. The OVS extension ensures that LLDP flows are present on periodic agent / neighbour polling by the sysinv agent. To add new drivers, a user would modify the [lldp] drivers value in sysinv.conf and add to the driver namespace (sysinv.agent.lldp.driver) Story: 2002946 Task: 22941 Signed-off-by: Steven Webster Change-Id: I9b7024b39ca93f46a8814e60d540f234e7f8eef2 --- sysinv/sysinv/centos/build_srpm.data | 2 +- sysinv/sysinv/sysinv/etc/sysinv/sysinv.conf | 3 + sysinv/sysinv/sysinv/setup.cfg | 4 + sysinv/sysinv/sysinv/sysinv/agent/lldp.py | 661 ------------------ .../sysinv/sysinv/agent/lldp/__init__.py | 0 .../sysinv/sysinv/sysinv/agent/lldp/config.py | 23 + .../sysinv/agent/lldp/drivers/__init__.py | 0 .../sysinv/sysinv/agent/lldp/drivers/base.py | 47 ++ .../agent/lldp/drivers/lldpd/__init__.py | 0 .../sysinv/agent/lldp/drivers/lldpd/driver.py | 321 +++++++++ .../sysinv/agent/lldp/drivers/ovs/__init__.py | 0 .../sysinv/agent/lldp/drivers/ovs/driver.py | 166 +++++ .../sysinv/sysinv/agent/lldp/manager.py | 176 +++++ .../sysinv/sysinv/sysinv/agent/lldp/plugin.py | 245 +++++++ sysinv/sysinv/sysinv/sysinv/agent/manager.py | 20 +- .../sysinv/sysinv/sysinv/common/exception.py | 4 + 16 files changed, 999 insertions(+), 673 deletions(-) delete mode 100644 sysinv/sysinv/sysinv/sysinv/agent/lldp.py create mode 100644 sysinv/sysinv/sysinv/sysinv/agent/lldp/__init__.py create mode 100644 sysinv/sysinv/sysinv/sysinv/agent/lldp/config.py create mode 100644 sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/__init__.py create mode 100644 sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/base.py create mode 100644 sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/lldpd/__init__.py create mode 100644 sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/lldpd/driver.py create mode 100644 sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/ovs/__init__.py create mode 100644 sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/ovs/driver.py create mode 100644 sysinv/sysinv/sysinv/sysinv/agent/lldp/manager.py create mode 100644 sysinv/sysinv/sysinv/sysinv/agent/lldp/plugin.py diff --git a/sysinv/sysinv/centos/build_srpm.data b/sysinv/sysinv/centos/build_srpm.data index e6a2d4a1d7..1c539eb7e1 100644 --- a/sysinv/sysinv/centos/build_srpm.data +++ b/sysinv/sysinv/centos/build_srpm.data @@ -1,2 +1,2 @@ SRC_DIR="sysinv" -TIS_PATCH_VER=281 +TIS_PATCH_VER=282 diff --git a/sysinv/sysinv/sysinv/etc/sysinv/sysinv.conf b/sysinv/sysinv/sysinv/etc/sysinv/sysinv.conf index b509eba512..97e5b4aa76 100644 --- a/sysinv/sysinv/sysinv/etc/sysinv/sysinv.conf +++ b/sysinv/sysinv/sysinv/etc/sysinv/sysinv.conf @@ -17,3 +17,6 @@ connection=postgresql://cgts:cgtspwd@localhost/cgtsdb: rpc_backend = sysinv.openstack.common.rpc.impl_kombu rabbit_host = 192.168.204.3 rabbit_port = 5672 + +[lldp] +drivers=lldpd diff --git a/sysinv/sysinv/sysinv/setup.cfg b/sysinv/sysinv/sysinv/setup.cfg index 74790cb4c6..7901bdf9e1 100644 --- a/sysinv/sysinv/sysinv/setup.cfg +++ b/sysinv/sysinv/sysinv/setup.cfg @@ -72,6 +72,10 @@ systemconfig.puppet_plugins = 032_swift = sysinv.puppet.swift:SwiftPuppet 033_service_parameter = sysinv.puppet.service_parameter:ServiceParamPuppet +sysinv.agent.lldp.drivers = + lldpd = sysinv.agent.lldp.drivers.lldpd.driver:SysinvLldpdAgentDriver + ovs = sysinv.agent.lldp.drivers.ovs.driver:SysinvOVSAgentDriver + [pbr] autodoc_index_modules = True diff --git a/sysinv/sysinv/sysinv/sysinv/agent/lldp.py b/sysinv/sysinv/sysinv/sysinv/agent/lldp.py deleted file mode 100644 index 1aeb4c3197..0000000000 --- a/sysinv/sysinv/sysinv/sysinv/agent/lldp.py +++ /dev/null @@ -1,661 +0,0 @@ -# -# Copyright (c) 2016 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# All Rights Reserved. -# - -""" inventory lldp Utilities and helper functions.""" - -import simplejson as json -import subprocess - -import threading - -from operator import attrgetter - -from sysinv.common import constants -from sysinv.openstack.common import log as logging - -LOG = logging.getLogger(__name__) - - -class Key(object): - def __init__(self, chassisid, portid, portname): - self.chassisid = chassisid - self.portid = portid - self.portname = portname - - def __hash__(self): - return hash((self.chassisid, self.portid, self.portname)) - - def __cmp__(self, rhs): - return (cmp(self.chassisid, rhs.chassisid) or - cmp(self.portid, rhs.portid) or - cmp(self.portname, rhs.portname)) - - def __eq__(self, rhs): - return (self.chassisid == rhs.chassisid and - self.portid == rhs.portid and - self.portname == rhs.portname) - - def __ne__(self, rhs): - return (self.chassisid != rhs.chassisid or - self.portid != rhs.portid or - self.portname != rhs.portname) - - def __str__(self): - return "%s [%s] [%s]" % (self.portname, self.chassisid, self.portid) - - def __repr__(self): - return "" % str(self) - - -class Agent(object): - '''Class to encapsulate LLDP agent data for System Inventory''' - - def __init__(self, **kwargs): - '''Construct an Agent object with the given values.''' - self.key = Key(kwargs.get(constants.LLDP_TLV_TYPE_CHASSIS_ID), - kwargs.get(constants.LLDP_TLV_TYPE_PORT_ID), - kwargs.get("name_or_uuid")) - self.status = kwargs.get('status') - self.ttl = kwargs.get(constants.LLDP_TLV_TYPE_TTL) - self.system_name = kwargs.get(constants.LLDP_TLV_TYPE_SYSTEM_NAME) - self.system_desc = kwargs.get(constants.LLDP_TLV_TYPE_SYSTEM_DESC) - self.port_desc = kwargs.get(constants.LLDP_TLV_TYPE_PORT_DESC) - self.capabilities = kwargs.get(constants.LLDP_TLV_TYPE_SYSTEM_CAP) - self.mgmt_addr = kwargs.get(constants.LLDP_TLV_TYPE_MGMT_ADDR) - self.dot1_lag = kwargs.get(constants.LLDP_TLV_TYPE_DOT1_LAG) - self.dot1_vlan_names = kwargs.get( - constants.LLDP_TLV_TYPE_DOT1_VLAN_NAMES) - self.dot3_max_frame = kwargs.get( - constants.LLDP_TLV_TYPE_DOT3_MAX_FRAME) - self.state = None - - def __hash__(self): - return self.key.__hash__() - - def __eq__(self, rhs): - return (self.key == rhs.key) - - def __ne__(self, rhs): - return (self.key != rhs.key or - self.status != rhs.status or - self.ttl != rhs.ttl or - self.system_name != rhs.system_name or - self.system_desc != rhs.system_desc or - self.port_desc != rhs.port_desc or - self.capabilities != rhs.capabilities or - self.mgmt_addr != rhs.mgmt_addr or - self.dot1_lag != rhs.dot1_lag or - self.dot1_vlan_names != rhs.dot1_vlan_names or - self.dot3_max_frame != rhs.dot3_max_frame or - self.state != rhs.state) - - def __str__(self): - return "%s: [%s] [%s] [%s], [%s], [%s], [%s], [%s], [%s]" % ( - self.key, self.status, self.system_name, self.system_desc, - self.port_desc, self.capabilities, - self.mgmt_addr, self.dot1_lag, - self.dot3_max_frame) - - def __repr__(self): - return "" % str(self) - - -class Neighbour(object): - '''Class to encapsulate LLDP neighbour data for System Inventory''' - - def __init__(self, **kwargs): - '''Construct an Neighbour object with the given values.''' - self.key = Key(kwargs.get(constants.LLDP_TLV_TYPE_CHASSIS_ID), - kwargs.get(constants.LLDP_TLV_TYPE_PORT_ID), - kwargs.get("name_or_uuid")) - self.msap = kwargs.get('msap') - self.ttl = kwargs.get(constants.LLDP_TLV_TYPE_TTL) - self.system_name = kwargs.get(constants.LLDP_TLV_TYPE_SYSTEM_NAME) - self.system_desc = kwargs.get(constants.LLDP_TLV_TYPE_SYSTEM_DESC) - self.port_desc = kwargs.get(constants.LLDP_TLV_TYPE_PORT_DESC) - self.capabilities = kwargs.get(constants.LLDP_TLV_TYPE_SYSTEM_CAP) - self.mgmt_addr = kwargs.get(constants.LLDP_TLV_TYPE_MGMT_ADDR) - self.dot1_port_vid = kwargs.get(constants.LLDP_TLV_TYPE_DOT1_PORT_VID) - self.dot1_vid_digest = kwargs.get( - constants.LLDP_TLV_TYPE_DOT1_VID_DIGEST) - self.dot1_mgmt_vid = kwargs.get(constants.LLDP_TLV_TYPE_DOT1_MGMT_VID) - self.dot1_vid_digest = kwargs.get( - constants.LLDP_TLV_TYPE_DOT1_VID_DIGEST) - self.dot1_mgmt_vid = kwargs.get(constants.LLDP_TLV_TYPE_DOT1_MGMT_VID) - self.dot1_lag = kwargs.get(constants.LLDP_TLV_TYPE_DOT1_LAG) - self.dot1_vlan_names = kwargs.get( - constants.LLDP_TLV_TYPE_DOT1_VLAN_NAMES) - self.dot1_proto_vids = kwargs.get( - constants.LLDP_TLV_TYPE_DOT1_PROTO_VIDS) - self.dot1_proto_ids = kwargs.get( - constants.LLDP_TLV_TYPE_DOT1_PROTO_IDS) - self.dot3_mac_status = kwargs.get( - constants.LLDP_TLV_TYPE_DOT3_MAC_STATUS) - self.dot3_max_frame = kwargs.get( - constants.LLDP_TLV_TYPE_DOT3_MAX_FRAME) - self.dot3_power_mdi = kwargs.get( - constants.LLDP_TLV_TYPE_DOT3_POWER_MDI) - - self.state = None - - def __hash__(self): - return self.key.__hash__() - - def __eq__(self, rhs): - return (self.key == rhs.key) - - def __ne__(self, rhs): - return (self.key != rhs.key or - self.msap != rhs.msap or - self.system_name != rhs.system_name or - self.system_desc != rhs.system_desc or - self.port_desc != rhs.port_desc or - self.capabilities != rhs.capabilities or - self.mgmt_addr != rhs.mgmt_addr or - self.dot1_port_vid != rhs.dot1_port_vid or - self.dot1_vid_digest != rhs.dot1_vid_digest or - self.dot1_mgmt_vid != rhs.dot1_mgmt_vid or - self.dot1_vid_digest != rhs.dot1_vid_digest or - self.dot1_mgmt_vid != rhs.dot1_mgmt_vid or - self.dot1_lag != rhs.dot1_lag or - self.dot1_vlan_names != rhs.dot1_vlan_names or - self.dot1_proto_vids != rhs.dot1_proto_vids or - self.dot1_proto_ids != rhs.dot1_proto_ids or - self.dot3_mac_status != rhs.dot3_mac_status or - self.dot3_max_frame != rhs.dot3_max_frame or - self.dot3_power_mdi != rhs.dot3_power_mdi) - - def __str__(self): - return "%s [%s] [%s] [%s], [%s]" % ( - self.key, self.system_name, self.system_desc, - self.port_desc, self.capabilities) - - def __repr__(self): - return "" % str(self) - - -class LLDPOperator(object): - '''Class to encapsulate LLDP operations for System Inventory''' - - def __init__(self, **kwargs): - self._lock = threading.Lock() - self.client = "" - self.agents = [] - self.neighbours = [] - self.current_neighbours = [] - self.previous_neighbours = [] - self.current_agents = [] - self.previous_agents = [] - self.agent_audit_count = 0 - self.neighbour_audit_count = 0 - - def lldpd_get_agent_status(self): - json_obj = json - p = subprocess.Popen(["lldpcli", "-f", "json", "show", - "configuration"], - stdout=subprocess.PIPE) - data = json_obj.loads(p.communicate()[0]) - - configuration = data['configuration'][0] - config = configuration['config'][0] - rx_only = config['rx-only'][0] - - if rx_only.get("value") == "no": - return "rx=enabled,tx=enabled" - else: - return "rx=enabled,tx=disabled" - - def lldpd_get_attrs(self, iface): - name_or_uuid = None - chassis_id = None - system_name = None - system_desc = None - capability = None - management_address = None - port_desc = None - dot1_lag = None - dot1_port_vid = None - dot1_vid_digest = None - dot1_mgmt_vid = None - dot1_vlan_names = None - dot1_proto_vids = None - dot1_proto_ids = None - dot3_mac_status = None - dot3_max_frame = None - dot3_power_mdi = None - ttl = None - attrs = {} - - # Note: dot1_vid_digest, dot1_mgmt_vid are not currently supported - # by the lldpd daemon - - name_or_uuid = iface.get("name") - chassis = iface.get("chassis")[0] - port = iface.get("port")[0] - - if not chassis.get('id'): - return attrs - chassis_id = chassis['id'][0].get("value") - - if not port.get('id'): - return attrs - port_id = port["id"][0].get("value") - - if not port.get('ttl'): - return attrs - ttl = port['ttl'][0].get("value") - - if chassis.get("name"): - system_name = chassis['name'][0].get("value") - - if chassis.get("descr"): - system_desc = chassis['descr'][0].get("value") - - if chassis.get("capability"): - capability = "" - for cap in chassis["capability"]: - if cap.get("enabled"): - if capability: - capability += ", " - capability += cap.get("type").lower() - - if chassis.get("mgmt-ip"): - management_address = "" - for addr in chassis["mgmt-ip"]: - if management_address: - management_address += ", " - management_address += addr.get("value").lower() - - if port.get("descr"): - port_desc = port["descr"][0].get("value") - - if port.get("link-aggregation"): - dot1_lag_supported = port["link-aggregation"][0].get("supported") - dot1_lag_enabled = port["link-aggregation"][0].get("enabled") - dot1_lag = "capable=" - if dot1_lag_supported: - dot1_lag += "y," - else: - dot1_lag += "n," - dot1_lag += "enabled=" - if dot1_lag_enabled: - dot1_lag += "y" - else: - dot1_lag += "n" - - if port.get("auto-negotiation"): - port_auto_neg_support = port["auto-negotiation"][0].get( - "supported") - port_auto_neg_enabled = port["auto-negotiation"][0].get("enabled") - dot3_mac_status = "auto-negotiation-capable=" - if port_auto_neg_support: - dot3_mac_status += "y," - else: - dot3_mac_status += "n," - dot3_mac_status += "auto-negotiation-enabled=" - if port_auto_neg_enabled: - dot3_mac_status += "y," - else: - dot3_mac_status += "n," - advertised = "" - if port.get("auto-negotiation")[0].get("advertised"): - for adv in port["auto-negotiation"][0].get("advertised"): - if advertised: - advertised += ", " - type = adv.get("type").lower() - if adv.get("hd") and not adv.get("fd"): - type += "hd" - elif adv.get("fd"): - type += "fd" - advertised += type - dot3_mac_status += advertised - - if port.get("mfs"): - dot3_max_frame = port["mfs"][0].get("value") - - if port.get("power"): - power_mdi_support = port["power"][0].get("supported") - power_mdi_enabled = port["power"][0].get("enabled") - power_mdi_devicetype = port["power"][0].get("device-type")[0].get( - "value") - power_mdi_pairs = port["power"][0].get("pairs")[0].get("value") - power_mdi_class = port["power"][0].get("class")[0].get("value") - dot3_power_mdi = "power-mdi-supported=" - if power_mdi_support: - dot3_power_mdi += "y," - else: - dot3_power_mdi += "n," - dot3_power_mdi += "power-mdi-enabled=" - if power_mdi_enabled: - dot3_power_mdi += "y," - else: - dot3_power_mdi += "n," - if power_mdi_support and power_mdi_enabled: - dot3_power_mdi += "device-type=" + power_mdi_devicetype - dot3_power_mdi += ",pairs=" + power_mdi_pairs - dot3_power_mdi += ",class=" + power_mdi_class - - vlans = None - if iface.get("vlan"): - vlans = iface.get("vlan") - - if vlans: - dot1_vlan_names = "" - for vlan in vlans: - if vlan.get("pvid"): - dot1_port_vid = vlan.get("vlan-id") - continue - if dot1_vlan_names: - dot1_vlan_names += ", " - dot1_vlan_names += vlan.get("value") - - ppvids = None - if iface.get("ppvids"): - ppvids = iface.get("ppvid") - - if ppvids: - dot1_proto_vids = "" - for ppvid in ppvids: - if dot1_proto_vids: - dot1_proto_vids += ", " - dot1_proto_vids += ppvid.get("value") - - pids = None - if iface.get("pi"): - pids = iface.get('pi') - dot1_proto_ids = "" - for id in pids: - if dot1_proto_ids: - dot1_proto_ids += ", " - dot1_proto_ids += id.get("value") - - msap = chassis_id + "," + port_id - - attrs = {"name_or_uuid": name_or_uuid, - constants.LLDP_TLV_TYPE_CHASSIS_ID: chassis_id, - constants.LLDP_TLV_TYPE_PORT_ID: port_id, - constants.LLDP_TLV_TYPE_TTL: ttl, - "msap": msap, - constants.LLDP_TLV_TYPE_SYSTEM_NAME: system_name, - constants.LLDP_TLV_TYPE_SYSTEM_DESC: system_desc, - constants.LLDP_TLV_TYPE_SYSTEM_CAP: capability, - constants.LLDP_TLV_TYPE_MGMT_ADDR: management_address, - constants.LLDP_TLV_TYPE_PORT_DESC: port_desc, - constants.LLDP_TLV_TYPE_DOT1_LAG: dot1_lag, - constants.LLDP_TLV_TYPE_DOT1_PORT_VID: dot1_port_vid, - constants.LLDP_TLV_TYPE_DOT1_VID_DIGEST: dot1_vid_digest, - constants.LLDP_TLV_TYPE_DOT1_MGMT_VID: dot1_mgmt_vid, - constants.LLDP_TLV_TYPE_DOT1_VLAN_NAMES: dot1_vlan_names, - constants.LLDP_TLV_TYPE_DOT1_PROTO_VIDS: dot1_proto_vids, - constants.LLDP_TLV_TYPE_DOT1_PROTO_IDS: dot1_proto_ids, - constants.LLDP_TLV_TYPE_DOT3_MAC_STATUS: dot3_mac_status, - constants.LLDP_TLV_TYPE_DOT3_MAX_FRAME: dot3_max_frame, - constants.LLDP_TLV_TYPE_DOT3_POWER_MDI: dot3_power_mdi} - - return attrs - - def lldpd_has_neighbour(self, name): - '''check if the interface has LLDP neighbours''' - p = subprocess.check_output(["lldpcli", "-f", "keyvalue", "show", - "neighbors", "summary", "ports", name]) - return len(p) > 0 - - def lldpd_agent_list(self): - json_obj = json - lldp_agents = [] - - p = subprocess.Popen(["lldpcli", "-f", "json", "show", "interface", - "detail"], stdout=subprocess.PIPE) - data = json_obj.loads(p.communicate()[0]) - - lldp = data['lldp'][0] - - if not lldp.get('interface'): - return lldp_agents - - for iface in lldp['interface']: - agent_attrs = self.lldpd_get_attrs(iface) - status = self.lldpd_get_agent_status() - agent_attrs.update({"status": status}) - agent = Agent(**agent_attrs) - lldp_agents.append(agent) - - return lldp_agents - - def lldpd_neighbour_list(self): - json_obj = json - lldp_neighbours = [] - p = subprocess.Popen(["lldpcli", "-f", "json", "show", "neighbor", - "detail"], stdout=subprocess.PIPE) - data = json_obj.loads(p.communicate()[0]) - - lldp = data['lldp'][0] - - if not lldp.get('interface'): - return lldp_neighbours - - for iface in lldp['interface']: - neighbour_attrs = self.lldpd_get_attrs(iface) - neighbour = Neighbour(**neighbour_attrs) - lldp_neighbours.append(neighbour) - - return lldp_neighbours - - def _do_request(self, callable): - """Thread safe wrapper for executing client requests. - - """ - - with self._lock: - return callable() - - def _execute_lldp_request(self, callable, snat=None): - try: - return self._do_request(callable) - except Exception as e: - LOG.error("Failed to execute LLDP request: %s", str(e)) - - def vswitch_lldp_get_status(self, admin_status): - if admin_status == "enabled": - status = "rx=enabled,tx=enabled" - elif admin_status == "tx-only": - status = "rx=disabled,tx=enabled" - elif admin_status == "rx-only": - status = "rx=enabled,tx=disabled" - else: - status = "rx=disabled,tx=disabled" - return status - - def vswitch_lldp_get_attrs(self, agent_neighbour_dict): - attrs = {} - - vswitch_to_db_dict = {'local-chassis': - constants.LLDP_TLV_TYPE_CHASSIS_ID, - 'local-port': constants.LLDP_TLV_TYPE_PORT_ID, - 'remote-chassis': - constants.LLDP_TLV_TYPE_CHASSIS_ID, - 'remote-port': constants.LLDP_TLV_TYPE_PORT_ID, - 'tx-ttl': constants.LLDP_TLV_TYPE_TTL, - 'rx-ttl': constants.LLDP_TLV_TYPE_TTL, - 'system-name': - constants.LLDP_TLV_TYPE_SYSTEM_NAME, - 'system-description': - constants.LLDP_TLV_TYPE_SYSTEM_DESC, - 'port-description': - constants.LLDP_TLV_TYPE_PORT_DESC, - 'system-capabilities': - constants.LLDP_TLV_TYPE_SYSTEM_CAP, - 'management-address': - constants.LLDP_TLV_TYPE_MGMT_ADDR, - 'dot1-lag': constants.LLDP_TLV_TYPE_DOT1_LAG, - 'dot1-management-vid': - constants.LLDP_TLV_TYPE_DOT1_MGMT_VID, - 'dot1-port-vid': - constants.LLDP_TLV_TYPE_DOT1_PORT_VID, - 'dot1-proto-ids': - constants.LLDP_TLV_TYPE_DOT1_PROTO_IDS, - 'dot1-proto-vids': - constants.LLDP_TLV_TYPE_DOT1_PROTO_VIDS, - 'dot1-vid-digest': - constants.LLDP_TLV_TYPE_DOT1_VID_DIGEST, - 'dot1-vlan-names': - constants.LLDP_TLV_TYPE_DOT1_VLAN_NAMES, - 'dot3-lag': - constants.LLDP_TLV_TYPE_DOT1_LAG, - 'dot3-mac-status': - constants.LLDP_TLV_TYPE_DOT3_MAC_STATUS, - 'dot3-max-frame': - constants.LLDP_TLV_TYPE_DOT3_MAX_FRAME, - 'dot3-power-mdi': - constants.LLDP_TLV_TYPE_DOT3_POWER_MDI} - - for k, v in vswitch_to_db_dict.iteritems(): - if k in agent_neighbour_dict: - if agent_neighbour_dict[k]: - attr = {v: agent_neighbour_dict[k]} - else: - attr = {v: None} - attrs.update(attr) - - msap = attrs[constants.LLDP_TLV_TYPE_CHASSIS_ID] \ - + "," + attrs[constants.LLDP_TLV_TYPE_PORT_ID] - - attr = {"name_or_uuid": agent_neighbour_dict["port-uuid"], - "msap": msap} - attrs.update(attr) - - return attrs - - def vswitch_lldp_agent_list(self): - """Sends a request to the vswitch requesting the full list of LLDP agent - - entries. - """ - - LOG.error("vswitch_lldp_agent_list is not implemented.") - return [] - - def vswitch_lldp_neighbour_list(self): - """Sends a request to the vswitch requesting the full list of LLDP - - neighbour entries. - """ - - LOG.error("vswitch_lldp_neighbour_ist s not implemented.") - return [] - - def lldp_agents_list(self, do_compute=False): - self.agent_audit_count += 1 - if self.agent_audit_count > constants.LLDP_FULL_AUDIT_COUNT: - LOG.debug("LLDP agent audit: triggering full sync") - self.agent_audit_count = 0 - self.lldp_agents_clear() - - self.previous_agents = self.current_agents - self.current_agents = self.lldpd_agent_list() - - if do_compute: - self.current_agents += self.vswitch_lldp_agent_list() - - current = set(self.current_agents) - previous = set(self.previous_agents) - removed = previous - current - - agent_array = [] - for a in self.current_agents: - agent_array.append(a) - - if removed: - for r in removed: - LOG.debug("LLDP agent audit: detected removed agent") - r.state = constants.LLDP_AGENT_STATE_REMOVED - agent_array.append(r) - return agent_array - - # Check that there is actual state changes and return an empty list if - # nothing changed. - if self.previous_agents: - pairs = zip(sorted(current, key=attrgetter('key')), - sorted(previous, key=attrgetter('key'))) - if not any(x != y for x, y in pairs): - LOG.debug("LLDP agent audit: No changes") - return [] - - return agent_array - - def lldp_agents_clear(self): - self.current_agents = [] - self.previous_agents = [] - - def lldp_neighbours_list(self, do_compute=False): - self.neighbour_audit_count += 1 - if self.neighbour_audit_count > constants.LLDP_FULL_AUDIT_COUNT: - LOG.debug("LLDP neighbour audit: triggering full sync") - self.neighbour_audit_count = 0 - self.lldp_neighbours_clear() - - self.previous_neighbours = self.current_neighbours - self.current_neighbours = self.lldpd_neighbour_list() - - if do_compute: - self.current_neighbours += self.vswitch_lldp_neighbour_list() - - current = set(self.current_neighbours) - previous = set(self.previous_neighbours) - removed = previous - current - - neighbour_array = [] - for n in self.current_neighbours: - neighbour_array.append(n) - - if removed: - for r in removed: - LOG.debug("LLDP neighbour audit: detected removed neighbour") - r.state = constants.LLDP_NEIGHBOUR_STATE_REMOVED - neighbour_array.append(r) - return neighbour_array - - # Check that there is actual state changes and return an empty list if - # nothing changed. - if self.previous_neighbours: - pairs = zip(sorted(current, key=attrgetter('key')), - sorted(previous, key=attrgetter('key'))) - if not any(x != y for x, y in pairs): - LOG.debug("LLDP neighbour audit: No changes") - return [] - - return neighbour_array - - def lldp_neighbours_clear(self): - self.current_neighbours = [] - self.previous_neighbours = [] - - def lldp_update_systemname(self, context, systemname, do_compute=False): - p = subprocess.Popen(["lldpcli", "-f", "json", "show", "chassis"], - stdout=subprocess.PIPE) - data = json.loads(p.communicate()[0]) - - local_chassis = data['local-chassis'][0] - chassis = local_chassis['chassis'][0] - name = chassis.get('name', None) - if name is None or not name[0].get("value"): - return - name = name[0] - - hostname = name.get("value").partition(':')[0] - - newname = hostname + ":" + systemname - - p = subprocess.Popen(["lldpcli", "configure", "system", "hostname", - newname], stdout=subprocess.PIPE) - - if do_compute: - LOG.error("lldp_update_systemname failed due to lack of vswitch") diff --git a/sysinv/sysinv/sysinv/sysinv/agent/lldp/__init__.py b/sysinv/sysinv/sysinv/sysinv/agent/lldp/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sysinv/sysinv/sysinv/sysinv/agent/lldp/config.py b/sysinv/sysinv/sysinv/sysinv/agent/lldp/config.py new file mode 100644 index 0000000000..b08b6db77c --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/agent/lldp/config.py @@ -0,0 +1,23 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# All Rights Reserved. +# + +from oslo_config import cfg +from oslo_utils._i18n import _ + +SYSINV_LLDP_OPTS = [ + cfg.ListOpt('drivers', + default=['lldpd'], + help=_("An ordered list of sysinv LLDP driver " + "entrypoints to be loaded from the " + "sysinv.agent namespace.")), +] + +cfg.CONF.register_opts(SYSINV_LLDP_OPTS, group="lldp") diff --git a/sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/__init__.py b/sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/base.py b/sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/base.py new file mode 100644 index 0000000000..fcc9a9c024 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/base.py @@ -0,0 +1,47 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# All Rights Reserved. +# + +import abc + +import six + + +@six.add_metaclass(abc.ABCMeta) +class SysinvLldpDriverBase(object): + """Sysinv LLDP Driver Base Class.""" + + @abc.abstractmethod + def lldp_has_neighbour(self, name): + pass + + @abc.abstractmethod + def lldp_update(self): + pass + + @abc.abstractmethod + def lldp_agents_list(self): + pass + + @abc.abstractmethod + def lldp_neighbours_list(self): + pass + + @abc.abstractmethod + def lldp_agents_clear(self): + pass + + @abc.abstractmethod + def lldp_neighbours_clear(self): + pass + + @abc.abstractmethod + def lldp_update_systemname(self, systemname): + pass diff --git a/sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/lldpd/__init__.py b/sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/lldpd/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/lldpd/driver.py b/sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/lldpd/driver.py new file mode 100644 index 0000000000..08891e2e9c --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/lldpd/driver.py @@ -0,0 +1,321 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# All Rights Reserved. +# + +from oslo_log import log as logging + +import simplejson as json +import subprocess + +from sysinv.agent.lldp.drivers import base +from sysinv.agent.lldp import plugin +from sysinv.common import constants + +LOG = logging.getLogger(__name__) + + +class SysinvLldpdAgentDriver(base.SysinvLldpDriverBase): + + def __init__(self, **kwargs): + self.client = "" + self.agents = [] + self.neighbours = [] + self.current_neighbours = [] + self.previous_neighbours = [] + self.current_agents = [] + self.previous_agents = [] + self.agent_audit_count = 0 + self.neighbour_audit_count = 0 + + def initialize(self): + self.__init__() + + @staticmethod + def _lldpd_get_agent_status(): + json_obj = json + p = subprocess.Popen(["lldpcli", "-f", "json", "show", + "configuration"], + stdout=subprocess.PIPE) + data = json_obj.loads(p.communicate()[0]) + + configuration = data['configuration'][0] + config = configuration['config'][0] + rx_only = config['rx-only'][0] + + if rx_only.get("value") == "no": + return "rx=enabled,tx=enabled" + else: + return "rx=enabled,tx=disabled" + + @staticmethod + def _lldpd_get_attrs(iface): + name_or_uuid = None + chassis_id = None + system_name = None + system_desc = None + capability = None + management_address = None + port_desc = None + dot1_lag = None + dot1_port_vid = None + dot1_vid_digest = None + dot1_mgmt_vid = None + dot1_vlan_names = None + dot1_proto_vids = None + dot1_proto_ids = None + dot3_mac_status = None + dot3_max_frame = None + dot3_power_mdi = None + ttl = None + attrs = {} + + # Note: dot1_vid_digest, dot1_mgmt_vid are not currently supported + # by the lldpd daemon + + name_or_uuid = iface.get("name") + chassis = iface.get("chassis")[0] + port = iface.get("port")[0] + + if not chassis.get('id'): + return attrs + chassis_id = chassis['id'][0].get("value") + + if not port.get('id'): + return attrs + port_id = port["id"][0].get("value") + + if not port.get('ttl'): + return attrs + ttl = port['ttl'][0].get("value") + + if chassis.get("name"): + system_name = chassis['name'][0].get("value") + + if chassis.get("descr"): + system_desc = chassis['descr'][0].get("value") + + if chassis.get("capability"): + capability = "" + for cap in chassis["capability"]: + if cap.get("enabled"): + if capability: + capability += ", " + capability += cap.get("type").lower() + + if chassis.get("mgmt-ip"): + management_address = "" + for addr in chassis["mgmt-ip"]: + if management_address: + management_address += ", " + management_address += addr.get("value").lower() + + if port.get("descr"): + port_desc = port["descr"][0].get("value") + + if port.get("link-aggregation"): + dot1_lag_supported = port["link-aggregation"][0].get("supported") + dot1_lag_enabled = port["link-aggregation"][0].get("enabled") + dot1_lag = "capable=" + if dot1_lag_supported: + dot1_lag += "y," + else: + dot1_lag += "n," + dot1_lag += "enabled=" + if dot1_lag_enabled: + dot1_lag += "y" + else: + dot1_lag += "n" + + if port.get("auto-negotiation"): + port_auto_neg_support = port["auto-negotiation"][0].get( + "supported") + port_auto_neg_enabled = port["auto-negotiation"][0].get("enabled") + dot3_mac_status = "auto-negotiation-capable=" + if port_auto_neg_support: + dot3_mac_status += "y," + else: + dot3_mac_status += "n," + dot3_mac_status += "auto-negotiation-enabled=" + if port_auto_neg_enabled: + dot3_mac_status += "y," + else: + dot3_mac_status += "n," + advertised = "" + if port.get("auto-negotiation")[0].get("advertised"): + for adv in port["auto-negotiation"][0].get("advertised"): + if advertised: + advertised += ", " + type = adv.get("type").lower() + if adv.get("hd") and not adv.get("fd"): + type += "hd" + elif adv.get("fd"): + type += "fd" + advertised += type + dot3_mac_status += advertised + + if port.get("mfs"): + dot3_max_frame = port["mfs"][0].get("value") + + if port.get("power"): + power_mdi_support = port["power"][0].get("supported") + power_mdi_enabled = port["power"][0].get("enabled") + power_mdi_devicetype = port["power"][0].get("device-type")[0].get( + "value") + power_mdi_pairs = port["power"][0].get("pairs")[0].get("value") + power_mdi_class = port["power"][0].get("class")[0].get("value") + dot3_power_mdi = "power-mdi-supported=" + if power_mdi_support: + dot3_power_mdi += "y," + else: + dot3_power_mdi += "n," + dot3_power_mdi += "power-mdi-enabled=" + if power_mdi_enabled: + dot3_power_mdi += "y," + else: + dot3_power_mdi += "n," + if power_mdi_support and power_mdi_enabled: + dot3_power_mdi += "device-type=" + power_mdi_devicetype + dot3_power_mdi += ",pairs=" + power_mdi_pairs + dot3_power_mdi += ",class=" + power_mdi_class + + vlans = None + if iface.get("vlan"): + vlans = iface.get("vlan") + + if vlans: + dot1_vlan_names = "" + for vlan in vlans: + if vlan.get("pvid"): + dot1_port_vid = vlan.get("vlan-id") + continue + if dot1_vlan_names: + dot1_vlan_names += ", " + dot1_vlan_names += vlan.get("value") + + ppvids = None + if iface.get("ppvids"): + ppvids = iface.get("ppvid") + + if ppvids: + dot1_proto_vids = "" + for ppvid in ppvids: + if dot1_proto_vids: + dot1_proto_vids += ", " + dot1_proto_vids += ppvid.get("value") + + pids = None + if iface.get("pi"): + pids = iface.get('pi') + dot1_proto_ids = "" + for id in pids: + if dot1_proto_ids: + dot1_proto_ids += ", " + dot1_proto_ids += id.get("value") + + msap = chassis_id + "," + port_id + + attrs = {"name_or_uuid": name_or_uuid, + constants.LLDP_TLV_TYPE_CHASSIS_ID: chassis_id, + constants.LLDP_TLV_TYPE_PORT_ID: port_id, + constants.LLDP_TLV_TYPE_TTL: ttl, + "msap": msap, + constants.LLDP_TLV_TYPE_SYSTEM_NAME: system_name, + constants.LLDP_TLV_TYPE_SYSTEM_DESC: system_desc, + constants.LLDP_TLV_TYPE_SYSTEM_CAP: capability, + constants.LLDP_TLV_TYPE_MGMT_ADDR: management_address, + constants.LLDP_TLV_TYPE_PORT_DESC: port_desc, + constants.LLDP_TLV_TYPE_DOT1_LAG: dot1_lag, + constants.LLDP_TLV_TYPE_DOT1_PORT_VID: dot1_port_vid, + constants.LLDP_TLV_TYPE_DOT1_VID_DIGEST: dot1_vid_digest, + constants.LLDP_TLV_TYPE_DOT1_MGMT_VID: dot1_mgmt_vid, + constants.LLDP_TLV_TYPE_DOT1_VLAN_NAMES: dot1_vlan_names, + constants.LLDP_TLV_TYPE_DOT1_PROTO_VIDS: dot1_proto_vids, + constants.LLDP_TLV_TYPE_DOT1_PROTO_IDS: dot1_proto_ids, + constants.LLDP_TLV_TYPE_DOT3_MAC_STATUS: dot3_mac_status, + constants.LLDP_TLV_TYPE_DOT3_MAX_FRAME: dot3_max_frame, + constants.LLDP_TLV_TYPE_DOT3_POWER_MDI: dot3_power_mdi} + + return attrs + + def lldp_has_neighbour(self, name): + p = subprocess.check_output(["lldpcli", "-f", "keyvalue", "show", + "neighbors", "summary", "ports", name]) + return len(p) > 0 + + def lldp_update(self): + subprocess.call(['lldpcli', 'update']) + + def lldp_agents_list(self): + json_obj = json + lldp_agents = [] + + p = subprocess.Popen(["lldpcli", "-f", "json", "show", "interface", + "detail"], stdout=subprocess.PIPE) + data = json_obj.loads(p.communicate()[0]) + + lldp = data['lldp'][0] + + if not lldp.get('interface'): + return lldp_agents + + for iface in lldp['interface']: + agent_attrs = self._lldpd_get_attrs(iface) + status = self._lldpd_get_agent_status() + agent_attrs.update({"status": status}) + agent = plugin.Agent(**agent_attrs) + lldp_agents.append(agent) + + return lldp_agents + + def lldp_agents_clear(self): + self.current_agents = [] + self.previous_agents = [] + + def lldp_neighbours_list(self): + json_obj = json + lldp_neighbours = [] + p = subprocess.Popen(["lldpcli", "-f", "json", "show", "neighbor", + "detail"], stdout=subprocess.PIPE) + data = json_obj.loads(p.communicate()[0]) + + lldp = data['lldp'][0] + + if not lldp.get('interface'): + return lldp_neighbours + + for iface in lldp['interface']: + neighbour_attrs = self._lldpd_get_attrs(iface) + neighbour = plugin.Neighbour(**neighbour_attrs) + lldp_neighbours.append(neighbour) + + return lldp_neighbours + + def lldp_neighbours_clear(self): + self.current_neighbours = [] + self.previous_neighbours = [] + + def lldp_update_systemname(self, systemname): + p = subprocess.Popen(["lldpcli", "-f", "json", "show", "chassis"], + stdout=subprocess.PIPE) + data = json.loads(p.communicate()[0]) + + local_chassis = data['local-chassis'][0] + chassis = local_chassis['chassis'][0] + name = chassis.get('name', None) + if name is None or not name[0].get("value"): + return + name = name[0] + + hostname = name.get("value").partition(':')[0] + + newname = hostname + ":" + systemname + + p = subprocess.Popen(["lldpcli", "configure", "system", "hostname", + newname], stdout=subprocess.PIPE) diff --git a/sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/ovs/__init__.py b/sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/ovs/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/ovs/driver.py b/sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/ovs/driver.py new file mode 100644 index 0000000000..8245867d3c --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/agent/lldp/drivers/ovs/driver.py @@ -0,0 +1,166 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# All Rights Reserved. +# + +import simplejson as json +import subprocess + +from oslo_log import log as logging + +from sysinv.agent.lldp.drivers.lldpd import driver as lldpd_driver +from sysinv.common import constants + +LOG = logging.getLogger(__name__) + + +class SysinvOVSAgentDriver(lldpd_driver.SysinvLldpdAgentDriver): + + def run_cmd(self, cmd): + p = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + p.wait() + output, error = p.communicate() + if p.returncode != 0: + LOG.error("Failed to run command %s: error: %s", cmd, error) + return None + return output + + def lldp_ovs_get_interface_port_map(self): + interface_port_map = {} + + cmd = "ovs-vsctl --timeout 10 --format json "\ + "--columns name,_uuid,interfaces list Port" + + output = self.run_cmd(cmd) + if not output: + return + + ports = json.loads(output) + ports = ports['data'] + + for port in ports: + port_uuid = port[1][1] + interfaces = port[2][1] + + if isinstance(interfaces, list): + for interface in interfaces: + interface_uuid = interface[1] + interface_port_map[interface_uuid] = port_uuid + else: + interface_uuid = interfaces + interface_port_map[interface_uuid] = port_uuid + + return interface_port_map + + def lldp_ovs_get_port_bridge_map(self): + port_bridge_map = {} + + cmd = "ovs-vsctl --timeout 10 --format json "\ + "--columns name,ports list Bridge" + output = self.run_cmd(cmd) + if not output: + return + + bridges = json.loads(output) + bridges = bridges['data'] + + for bridge in bridges: + bridge_name = bridge[0] + port_set = bridge[1][1] + for port in port_set: + value = port[1] + port_bridge_map[value] = bridge_name + + return port_bridge_map + + def lldp_ovs_lldp_flow_exists(self, brname, in_port): + + cmd = "ovs-ofctl dump-flows {} in_port={},dl_dst={},dl_type={}".format( + brname, in_port, constants.LLDP_MULTICAST_ADDRESS, + constants.LLDP_ETHER_TYPE) + output = self.run_cmd(cmd) + if not output: + return None + + return (output.count("\n") > 1) + + def lldp_ovs_add_flows(self, brname, in_port, out_port): + + cmd = ("ovs-ofctl add-flow {} in_port={},dl_dst={},dl_type={}," + "actions=output:{}".format( + brname, in_port, constants.LLDP_MULTICAST_ADDRESS, + constants.LLDP_ETHER_TYPE, out_port)) + output = self.run_cmd(cmd) + if not output: + return + + cmd = ("ovs-ofctl add-flow {} in_port={},dl_dst={},dl_type={}," + "actions=output:{}".format( + brname, out_port, constants.LLDP_MULTICAST_ADDRESS, + constants.LLDP_ETHER_TYPE, in_port)) + output = self.run_cmd(cmd) + if not output: + return + + def lldp_ovs_update_flows(self): + + port_bridge_map = self.lldp_ovs_get_port_bridge_map() + if not port_bridge_map: + return + + interface_port_map = self.lldp_ovs_get_interface_port_map() + if not interface_port_map: + return + + cmd = "ovs-vsctl --timeout 10 --format json "\ + "--columns name,_uuid,type,other_config list Interface" + + output = self.run_cmd(cmd) + if not output: + return + + data = json.loads(output) + data = data['data'] + + for interface in data: + name = interface[0] + uuid = interface[1][1] + type = interface[2] + other_config = interface[3] + + if type != 'internal': + continue + + config_map = other_config[1] + for config in config_map: + key = config[0] + value = config[1] + if key != 'lldp_phy_peer': + continue + + phy_peer = value + brname = port_bridge_map[interface_port_map[uuid]] + if not self.lldp_ovs_lldp_flow_exists(brname, name): + LOG.info("Adding missing LLDP flow from %s to %s", + name, phy_peer) + self.lldp_ovs_add_flows(brname, name, phy_peer) + + if not self.lldp_ovs_lldp_flow_exists(brname, value): + LOG.info("Adding missing LLDP flow from %s to %s", + phy_peer, name) + self.lldp_ovs_add_flows(brname, phy_peer, name) + + def lldp_agents_list(self): + self.lldp_ovs_update_flows() + return lldpd_driver.SysinvLldpdAgentDriver.lldp_agents_list(self) + + def lldp_neighbours_list(self): + self.lldp_ovs_update_flows() + return lldpd_driver.SysinvLldpdAgentDriver.lldp_neighbours_list(self) diff --git a/sysinv/sysinv/sysinv/sysinv/agent/lldp/manager.py b/sysinv/sysinv/sysinv/sysinv/agent/lldp/manager.py new file mode 100644 index 0000000000..e04a4e888e --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/agent/lldp/manager.py @@ -0,0 +1,176 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# All Rights Reserved. +# + +from oslo_config import cfg +from oslo_log import log +from stevedore.named import NamedExtensionManager +from sysinv.common import exception + +LOG = log.getLogger(__name__) +cfg.CONF.import_opt('drivers', + 'sysinv.agent.lldp.config', + group='lldp') + + +class SysinvLldpDriverManager(NamedExtensionManager): + """Implementation of Sysinv LLDP drivers.""" + + def __init__(self, namespace='sysinv.agent.lldp.drivers'): + + # Registered sysinv lldp agent drivers, keyed by name. + self.drivers = {} + + # Ordered list of sysinv lldp agent drivers, defining + # the order in which the drivers are called. + self.ordered_drivers = [] + + names = cfg.CONF.lldp.drivers + LOG.info("Configured sysinv LLDP agent drivers: %s", names) + + super(SysinvLldpDriverManager, self).__init__( + namespace, + names, + invoke_on_load=True, + name_order=True) + + LOG.info("Loaded sysinv LLDP agent drivers: %s", self.names()) + self._register_drivers() + + def _register_drivers(self): + """Register all sysinv LLDP agent drivers. + + This method should only be called once in the + SysinvLldpDriverManager constructor. + """ + for ext in self: + self.drivers[ext.name] = ext + self.ordered_drivers.append(ext) + LOG.info("Registered sysinv LLDP agent drivers: %s", + [driver.name for driver in self.ordered_drivers]) + + def _call_drivers_and_return_array(self, method_name, attr=None, + raise_orig_exc=False): + """Helper method for calling a method across all drivers. + + :param method_name: name of the method to call + :param attr: an optional attribute to provide to the drivers + :param raise_orig_exc: whether or not to raise the original + driver exception, or use a general one + """ + ret = [] + for driver in self.ordered_drivers: + try: + method = getattr(driver.obj, method_name) + if attr: + ret = ret + method(attr) + else: + ret = ret + method() + except Exception as e: + LOG.exception(e) + LOG.error( + "Sysinv LLDP agent driver '%(name)s' " + "failed in %(method)s", + {'name': driver.name, 'method': method_name} + ) + if raise_orig_exc: + raise + else: + raise exception.LLDPDriverError( + method=method_name + ) + return list(set(ret)) + + def _call_drivers(self, method_name, attr=None, raise_orig_exc=False): + """Helper method for calling a method across all drivers. + + :param method_name: name of the method to call + :param attr: an optional attribute to provide to the drivers + :param raise_orig_exc: whether or not to raise the original + driver exception, or use a general one + """ + for driver in self.ordered_drivers: + try: + method = getattr(driver.obj, method_name) + if attr: + return method(attr) + else: + return method() + except Exception as e: + LOG.exception(e) + LOG.error( + "Sysinv LLDP agent driver '%(name)s' " + "failed in %(method)s", + {'name': driver.name, 'method': method_name} + ) + if raise_orig_exc: + raise + else: + raise exception.LLDPDriverError( + method=method_name + ) + + def lldp_has_neighbour(self, name): + try: + return self._call_drivers("lldp_has_neighbour", + attr=name, + raise_orig_exc=True) + except Exception as e: + LOG.exception(e) + return [] + + def lldp_update(self): + try: + return self._call_drivers("lldp_update", + raise_orig_exc=True) + except Exception as e: + LOG.exception(e) + return [] + + def lldp_agents_list(self): + try: + return self._call_drivers_and_return_array("lldp_agents_list", + raise_orig_exc=True) + except Exception as e: + LOG.exception(e) + return [] + + def lldp_neighbours_list(self): + try: + return self._call_drivers_and_return_array("lldp_neighbours_list", + raise_orig_exc=True) + except Exception as e: + LOG.exception(e) + return [] + + def lldp_agents_clear(self): + try: + return self._call_drivers("lldp_agents_clear", + raise_orig_exc=True) + except Exception as e: + LOG.exception(e) + return + + def lldp_neighbours_clear(self): + try: + return self._call_drivers("lldp_neighbours_clear", + raise_orig_exc=True) + except Exception as e: + LOG.exception(e) + return + + def lldp_update_systemname(self, systemname): + try: + return self._call_drivers("lldp_update_systemname", + attr=systemname, + raise_orig_exc=True) + except Exception as e: + LOG.exception(e) + return diff --git a/sysinv/sysinv/sysinv/sysinv/agent/lldp/plugin.py b/sysinv/sysinv/sysinv/sysinv/agent/lldp/plugin.py new file mode 100644 index 0000000000..aab8335570 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/agent/lldp/plugin.py @@ -0,0 +1,245 @@ +# +# Copyright (c) 2018 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# All Rights Reserved. +# + +from oslo_log import log as logging + +from sysinv.agent.lldp import manager +from sysinv.common import exception +from sysinv.common import constants +from sysinv.openstack.common import excutils + +LOG = logging.getLogger(__name__) + + +class Key(object): + def __init__(self, chassisid, portid, portname): + self.chassisid = chassisid + self.portid = portid + self.portname = portname + + def __hash__(self): + return hash((self.chassisid, self.portid, self.portname)) + + def __cmp__(self, rhs): + return (cmp(self.chassisid, rhs.chassisid) or + cmp(self.portid, rhs.portid) or + cmp(self.portname, rhs.portname)) + + def __eq__(self, rhs): + return (self.chassisid == rhs.chassisid and + self.portid == rhs.portid and + self.portname == rhs.portname) + + def __ne__(self, rhs): + return (self.chassisid != rhs.chassisid or + self.portid != rhs.portid or + self.portname != rhs.portname) + + def __str__(self): + return "%s [%s] [%s]" % (self.portname, self.chassisid, self.portid) + + def __repr__(self): + return "" % str(self) + + +class Agent(object): + '''Class to encapsulate LLDP agent data for System Inventory''' + + def __init__(self, **kwargs): + '''Construct an Agent object with the given values.''' + self.key = Key(kwargs.get(constants.LLDP_TLV_TYPE_CHASSIS_ID), + kwargs.get(constants.LLDP_TLV_TYPE_PORT_ID), + kwargs.get("name_or_uuid")) + self.status = kwargs.get('status') + self.ttl = kwargs.get(constants.LLDP_TLV_TYPE_TTL) + self.system_name = kwargs.get(constants.LLDP_TLV_TYPE_SYSTEM_NAME) + self.system_desc = kwargs.get(constants.LLDP_TLV_TYPE_SYSTEM_DESC) + self.port_desc = kwargs.get(constants.LLDP_TLV_TYPE_PORT_DESC) + self.capabilities = kwargs.get(constants.LLDP_TLV_TYPE_SYSTEM_CAP) + self.mgmt_addr = kwargs.get(constants.LLDP_TLV_TYPE_MGMT_ADDR) + self.dot1_lag = kwargs.get(constants.LLDP_TLV_TYPE_DOT1_LAG) + self.dot1_vlan_names = kwargs.get( + constants.LLDP_TLV_TYPE_DOT1_VLAN_NAMES) + self.dot3_max_frame = kwargs.get( + constants.LLDP_TLV_TYPE_DOT3_MAX_FRAME) + self.state = None + + def __hash__(self): + return self.key.__hash__() + + def __eq__(self, rhs): + return (self.key == rhs.key) + + def __ne__(self, rhs): + return (self.key != rhs.key or + self.status != rhs.status or + self.ttl != rhs.ttl or + self.system_name != rhs.system_name or + self.system_desc != rhs.system_desc or + self.port_desc != rhs.port_desc or + self.capabilities != rhs.capabilities or + self.mgmt_addr != rhs.mgmt_addr or + self.dot1_lag != rhs.dot1_lag or + self.dot1_vlan_names != rhs.dot1_vlan_names or + self.dot3_max_frame != rhs.dot3_max_frame or + self.state != rhs.state) + + def __str__(self): + return "%s: [%s] [%s] [%s], [%s], [%s], [%s], [%s], [%s]" % ( + self.key, self.status, self.system_name, self.system_desc, + self.port_desc, self.capabilities, + self.mgmt_addr, self.dot1_lag, + self.dot3_max_frame) + + def __repr__(self): + return "" % str(self) + + +class Neighbour(object): + '''Class to encapsulate LLDP neighbour data for System Inventory''' + + def __init__(self, **kwargs): + '''Construct an Neighbour object with the given values.''' + self.key = Key(kwargs.get(constants.LLDP_TLV_TYPE_CHASSIS_ID), + kwargs.get(constants.LLDP_TLV_TYPE_PORT_ID), + kwargs.get("name_or_uuid")) + self.msap = kwargs.get('msap') + self.ttl = kwargs.get(constants.LLDP_TLV_TYPE_TTL) + self.system_name = kwargs.get(constants.LLDP_TLV_TYPE_SYSTEM_NAME) + self.system_desc = kwargs.get(constants.LLDP_TLV_TYPE_SYSTEM_DESC) + self.port_desc = kwargs.get(constants.LLDP_TLV_TYPE_PORT_DESC) + self.capabilities = kwargs.get(constants.LLDP_TLV_TYPE_SYSTEM_CAP) + self.mgmt_addr = kwargs.get(constants.LLDP_TLV_TYPE_MGMT_ADDR) + self.dot1_port_vid = kwargs.get(constants.LLDP_TLV_TYPE_DOT1_PORT_VID) + self.dot1_vid_digest = kwargs.get( + constants.LLDP_TLV_TYPE_DOT1_VID_DIGEST) + self.dot1_mgmt_vid = kwargs.get(constants.LLDP_TLV_TYPE_DOT1_MGMT_VID) + self.dot1_vid_digest = kwargs.get( + constants.LLDP_TLV_TYPE_DOT1_VID_DIGEST) + self.dot1_mgmt_vid = kwargs.get(constants.LLDP_TLV_TYPE_DOT1_MGMT_VID) + self.dot1_lag = kwargs.get(constants.LLDP_TLV_TYPE_DOT1_LAG) + self.dot1_vlan_names = kwargs.get( + constants.LLDP_TLV_TYPE_DOT1_VLAN_NAMES) + self.dot1_proto_vids = kwargs.get( + constants.LLDP_TLV_TYPE_DOT1_PROTO_VIDS) + self.dot1_proto_ids = kwargs.get( + constants.LLDP_TLV_TYPE_DOT1_PROTO_IDS) + self.dot3_mac_status = kwargs.get( + constants.LLDP_TLV_TYPE_DOT3_MAC_STATUS) + self.dot3_max_frame = kwargs.get( + constants.LLDP_TLV_TYPE_DOT3_MAX_FRAME) + self.dot3_power_mdi = kwargs.get( + constants.LLDP_TLV_TYPE_DOT3_POWER_MDI) + + self.state = None + + def __hash__(self): + return self.key.__hash__() + + def __eq__(self, rhs): + return (self.key == rhs.key) + + def __ne__(self, rhs): + return (self.key != rhs.key or + self.msap != rhs.msap or + self.system_name != rhs.system_name or + self.system_desc != rhs.system_desc or + self.port_desc != rhs.port_desc or + self.capabilities != rhs.capabilities or + self.mgmt_addr != rhs.mgmt_addr or + self.dot1_port_vid != rhs.dot1_port_vid or + self.dot1_vid_digest != rhs.dot1_vid_digest or + self.dot1_mgmt_vid != rhs.dot1_mgmt_vid or + self.dot1_vid_digest != rhs.dot1_vid_digest or + self.dot1_mgmt_vid != rhs.dot1_mgmt_vid or + self.dot1_lag != rhs.dot1_lag or + self.dot1_vlan_names != rhs.dot1_vlan_names or + self.dot1_proto_vids != rhs.dot1_proto_vids or + self.dot1_proto_ids != rhs.dot1_proto_ids or + self.dot3_mac_status != rhs.dot3_mac_status or + self.dot3_max_frame != rhs.dot3_max_frame or + self.dot3_power_mdi != rhs.dot3_power_mdi) + + def __str__(self): + return "%s [%s] [%s] [%s], [%s]" % ( + self.key, self.system_name, self.system_desc, + self.port_desc, self.capabilities) + + def __repr__(self): + return "" % str(self) + + +class SysinvLldpPlugin(): + + """Implementation of the Plugin.""" + + def __init__(self): + self.manager = manager.SysinvLldpDriverManager() + + def lldp_has_neighbour(self, name): + try: + return self.manager.lldp_has_neighbour(name) + except exception.LLDPDriverError as e: + LOG.exception(e) + with excutils.save_and_reraise_exception(): + LOG.error("LLDP has neighbour failed") + + def lldp_update(self): + try: + self.manager.lldp_update() + except exception.LLDPDriverError as e: + LOG.exception(e) + with excutils.save_and_reraise_exception(): + LOG.error("LLDP update failed") + + def lldp_agents_list(self): + try: + agents = self.manager.lldp_agents_list() + except exception.LLDPDriverError as e: + LOG.exception(e) + with excutils.save_and_reraise_exception(): + LOG.error("LLDP agents list failed") + + return agents + + def lldp_agents_clear(self): + try: + self.manager.lldp_agents_clear() + except exception.LLDPDriverError as e: + LOG.exception(e) + with excutils.save_and_reraise_exception(): + LOG.error("LLDP agents clear failed") + + def lldp_neighbours_list(self): + try: + neighbours = self.manager.lldp_neighbours_list() + except exception.LLDPDriverError as e: + LOG.exception(e) + with excutils.save_and_reraise_exception(): + LOG.error("LLDP neighbours list failed") + + return neighbours + + def lldp_neighbours_clear(self): + try: + self.manager.lldp_neighbours_clear() + except exception.LLDPDriverError as e: + LOG.exception(e) + with excutils.save_and_reraise_exception(): + LOG.error("LLDP neighbours clear failed") + + def lldp_update_systemname(self, systemname): + try: + self.manager.lldp_update_systemname(systemname) + except exception.LLDPDriverError as e: + LOG.exception(e) + with excutils.save_and_reraise_exception(): + LOG.error("LLDP update systemname failed") diff --git a/sysinv/sysinv/sysinv/sysinv/agent/manager.py b/sysinv/sysinv/sysinv/sysinv/agent/manager.py index 576c7add5a..f69384f6c5 100644 --- a/sysinv/sysinv/sysinv/sysinv/agent/manager.py +++ b/sysinv/sysinv/sysinv/sysinv/agent/manager.py @@ -53,7 +53,7 @@ from sysinv.agent import pv from sysinv.agent import lvg from sysinv.agent import pci from sysinv.agent import node -from sysinv.agent import lldp +from sysinv.agent.lldp import plugin as lldp_plugin from sysinv.common import constants from sysinv.common import exception from sysinv.common import service @@ -138,7 +138,7 @@ class AgentManager(service.PeriodicService): self._ipv_operator = pv.PVOperator() self._ipartition_operator = partition.PartitionOperator() self._ilvg_operator = lvg.LVGOperator() - self._lldp_operator = lldp.LLDPOperator() + self._lldp_operator = lldp_plugin.SysinvLldpPlugin() self._iconfig_read_config_reported = None self._ihost_personality = None self._ihost_uuid = "" @@ -363,10 +363,8 @@ class AgentManager(service.PeriodicService): neighbours = [] agents = [] - do_compute = constants.COMPUTE in self.subfunctions_list_get() - try: - neighbours = self._lldp_operator.lldp_neighbours_list(do_compute) + neighbours = self._lldp_operator.lldp_neighbours_list() except Exception as e: LOG.error("Failed to get LLDP neighbours: %s", str(e)) @@ -408,7 +406,7 @@ class AgentManager(service.PeriodicService): pass try: - agents = self._lldp_operator.lldp_agents_list(do_compute) + agents = self._lldp_operator.lldp_agents_list() except Exception as e: LOG.error("Failed to get LLDP agents: %s", str(e)) @@ -470,7 +468,8 @@ class AgentManager(service.PeriodicService): subprocess.call(['ip', 'link', 'set', interface, 'up']) links_down.append(interface) LOG.info('interface %s enabled to receive LLDP PDUs' % interface) - subprocess.call(['lldpcli', 'update']) + self._lldp_operator.lldp_update() + # delay maximum 30 seconds for lldpd to receive LLDP PDU timeout = 0 link_wait_for_lldp = True @@ -478,8 +477,9 @@ class AgentManager(service.PeriodicService): time.sleep(5) timeout = timeout + 5 link_wait_for_lldp = False + for link in links_down: - if not self._lldp_operator.lldpd_has_neighbour(link): + if not self._lldp_operator.lldp_has_neighbour(link): link_wait_for_lldp = True break self.host_lldp_get_and_report(context, rpcapi, host_uuid) @@ -1261,12 +1261,10 @@ class AgentManager(service.PeriodicService): :param systemname: the systemname """ - do_compute = constants.COMPUTE in self.subfunctions_list_get() rpcapi = conductor_rpcapi.ConductorAPI( topic=conductor_rpcapi.MANAGER_TOPIC) # Update the lldp agent - self._lldp_operator.lldp_update_systemname(context, systemname, - do_compute) + self._lldp_operator.lldp_update_systemname(systemname) # Trigger an audit to ensure the db is up to date self.host_lldp_get_and_report(context, rpcapi, self._ihost_uuid) diff --git a/sysinv/sysinv/sysinv/sysinv/common/exception.py b/sysinv/sysinv/sysinv/sysinv/common/exception.py index 4b036cd075..797f06c96c 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/exception.py +++ b/sysinv/sysinv/sysinv/sysinv/common/exception.py @@ -566,6 +566,10 @@ class LLDPTlvExists(Conflict): message = _("An LLDP TLV with type %(type) already exists.") +class LLDPDriverError(Conflict): + message = _("An LLDP driver error has occurred. method=%(method)") + + class SDNControllerAlreadyExists(Conflict): message = _("An SDN Controller with uuid %(uuid)s already exists.")