From b8f19eef5528ebf6c9069e38dcbc0cfec4498d49 Mon Sep 17 00:00:00 2001 From: Junchao-Mellanox <57339448+Junchao-Mellanox@users.noreply.github.com> Date: Thu, 12 Nov 2020 12:53:18 +0800 Subject: [PATCH] [sonic-snmpagent] SONiC physical entity mib extension (#168) Implement all mib objects defined in EntPhysicalEntry of RFC 2737. **- How I did it** Refactor rfc2737.py to have different mib updater for different physical entity: XcvrCacheUpdater, FanCacheUpdater, FanDrawerCacheUpdater, PsuCacheUpdater, ThermalCacheUpdater. **- How to verify it** Manual test on MSN2410 --- src/sonic_ax_impl/mibs/__init__.py | 89 +- .../ietf/physical_entity_sub_oid_generator.py | 188 ++++ src/sonic_ax_impl/mibs/ietf/rfc2737.py | 973 +++++++++++++++--- src/sonic_ax_impl/mibs/ietf/rfc3433.py | 4 +- tests/mock_tables/state_db.json | 65 +- tests/namespace/test_sensor.py | 33 +- tests/test_sensor.py | 26 +- tests/test_sn.py | 193 +++- 8 files changed, 1311 insertions(+), 260 deletions(-) create mode 100644 src/sonic_ax_impl/mibs/ietf/physical_entity_sub_oid_generator.py diff --git a/src/sonic_ax_impl/mibs/__init__.py b/src/sonic_ax_impl/mibs/__init__.py index 0b0f8b48bbd..dbe4d6f4f76 100644 --- a/src/sonic_ax_impl/mibs/__init__.py +++ b/src/sonic_ax_impl/mibs/__init__.py @@ -25,27 +25,6 @@ TABLE_NAME_SEPARATOR_COLON = ':' TABLE_NAME_SEPARATOR_VBAR = '|' -# This is used in both rfc2737 and rfc3433 -SENSOR_PART_ID_MAP = { - "temperature": 1, - "voltage": 2, - "rx1power": 11, - "rx2power": 21, - "rx3power": 31, - "rx4power": 41, - "tx1bias": 12, - "tx2bias": 22, - "tx3bias": 32, - "tx4bias": 42, - "tx1power": 13, - "tx2power": 23, - "tx3power": 33, - "tx4power": 43, -} - -# IfIndex to OID multiplier for transceiver -IFINDEX_SUB_ID_MULTIPLIER = 1000 - redis_kwargs = {'unix_socket_path': '/var/run/redis/redis.sock'} @@ -65,6 +44,22 @@ def chassis_info_table(chassis_name): return "CHASSIS_INFO" + TABLE_NAME_SEPARATOR_VBAR + chassis_name +def fan_info_table(fan_name): + """ + :param: fan_name: fan name + :return: fan info entry for this fan + """ + return 'FAN_INFO' + TABLE_NAME_SEPARATOR_VBAR + fan_name + + +def fan_drawer_info_table(drawer_name): + """ + :param: drawer_name: fan drawer name + :return: fan drawer info entry for this fan + """ + return 'FAN_DRAWER_INFO' + TABLE_NAME_SEPARATOR_VBAR + drawer_name + + def psu_info_table(psu_name): """ :param: psu_name: psu name @@ -73,6 +68,15 @@ def psu_info_table(psu_name): return "PSU_INFO" + TABLE_NAME_SEPARATOR_VBAR + psu_name + +def physical_entity_info_table(name): + """ + :param: name: object name + :return: entity info entry for this object + """ + return 'PHYSICAL_ENTITY_INFO' + TABLE_NAME_SEPARATOR_VBAR + name + + def counter_table(sai_id): """ :param if_name: given sai_id to cast. @@ -106,6 +110,14 @@ def transceiver_dom_table(port_name): return "TRANSCEIVER_DOM_SENSOR" + TABLE_NAME_SEPARATOR_VBAR + port_name +def thermal_info_table(thermal_name): + """ + :param: port_name: port name + :return: transceiver dom entry for this port + """ + + return "TEMPERATURE_INFO" + TABLE_NAME_SEPARATOR_VBAR + thermal_name + def lldp_entry_table(if_name): """ :param if_name: given interface to cast. @@ -386,40 +398,6 @@ def get_device_metadata(db_conn): device_metadata = db_conn.get_all(db_conn.STATE_DB, DEVICE_METADATA) return device_metadata -def get_transceiver_sub_id(ifindex): - """ - Returns sub OID for transceiver. Sub OID is calculated as folows: - +------------+------------+ - |Interface |Index | - +------------+------------+ - |Ethernet[X] |X * 1000 | - +------------+------------+ - () - :param ifindex: interface index - :return: sub OID of a port calculated as sub OID = {{index}} * 1000 - """ - - return (ifindex * IFINDEX_SUB_ID_MULTIPLIER, ) - -def get_transceiver_sensor_sub_id(ifindex, sensor): - """ - Returns sub OID for transceiver sensor. Sub OID is calculated as folows: - +-------------------------------------+------------------------------+ - |Sensor |Index | - +-------------------------------------+------------------------------+ - |RX Power for Ethernet[X]/[LANEID] |X * 1000 + LANEID * 10 + 1 | - |TX Bias for Ethernet[X]/[LANEID] |X * 1000 + LANEID * 10 + 2 | - |Temperature for Ethernet[X] |X * 1000 + 1 | - |Voltage for Ethernet[X]/[LANEID] |X * 1000 + 2 | - +-------------------------------------+------------------------------+ - () - :param ifindex: interface index - :param sensor: sensor key - :return: sub OID = {{index}} * 1000 + {{lane}} * 10 + sensor id - """ - - transceiver_oid, = get_transceiver_sub_id(ifindex) - return (transceiver_oid + SENSOR_PART_ID_MAP[sensor], ) def get_redis_pubsub(db_conn, db_name, pattern): redis_client = db_conn.get_redis_client(db_name) @@ -428,6 +406,7 @@ def get_redis_pubsub(db_conn, db_name, pattern): pubsub.psubscribe("__keyspace@{}__:{}".format(db, pattern)) return pubsub + class RedisOidTreeUpdater(MIBUpdater): def __init__(self, prefix_str): super().__init__() diff --git a/src/sonic_ax_impl/mibs/ietf/physical_entity_sub_oid_generator.py b/src/sonic_ax_impl/mibs/ietf/physical_entity_sub_oid_generator.py new file mode 100644 index 00000000000..1b368dda341 --- /dev/null +++ b/src/sonic_ax_impl/mibs/ietf/physical_entity_sub_oid_generator.py @@ -0,0 +1,188 @@ +""" +For non-port entity, the rule to generate entPhysicalIndex describes below: +The entPhysicalIndex is divided into 3 layers: + 1. Module layer which includes modules located on system (e.g. fan drawer, PSU) + 2. Device layer which includes system devices (e.g. fan ) + 3. Sensor layer which includes system sensors (e.g. temperature sensor, fan sensor) +The entPhysicalIndex is a 9 digits number, and each digit describes below: +Digit 1: Module Type +Digit 2~3: Module Index +Digit 4~5: Device Type +Digit 6~7: Device Index +Digit 8: Sensor Type +Digit 9: Sensor Index + +Module Type describes below: +2 - Management +5 - Fan Drawer +6 - PSU +Device Type describes below: +01 - PS +02 - Fan +24 - Power Monitor (temperature, power, current, voltage...) +99 - Chassis Thermals +Sensor Type describes below: +1 - Temperature +2 - Fan Tachometers +3 - Power +4 - Current +5 - Voltage + +e.g. 501000000 means the first fan drawer, 502020100 means the first fan of the second fan drawer + +As we are using ifindex to generate port entPhysicalIndex and ifindex might be a valur larger +than 99, we uses a different way to generate port entPhysicalIndex. + +For port entity, the entPhysicalIndex is a 10 digits number, and each digit describes below: +Digit 1: 1 +Digit 2~8: ifindex +Digit 9: Sensor Type +Digit 10: Sensor Index + +Port Sensor Type describes below: +1 - Temperature +2 - TX Power +3 - RX Power +4 - TX BIAS +5 - Voltage +""" + +# Moduel Type Definition +MODULE_TYPE_MULTIPLE = 100000000 +MODULE_INDEX_MULTIPLE = 1000000 +MODULE_TYPE_MGMT = 2 * MODULE_TYPE_MULTIPLE +MODULE_TYPE_FAN_DRAWER = 5 * MODULE_TYPE_MULTIPLE +MODULE_TYPE_PSU = 6 * MODULE_TYPE_MULTIPLE +MODULE_TYPE_PORT = 1000000000 + +# Device Type Definition +DEVICE_TYPE_MULTIPLE = 10000 +DEVICE_INDEX_MULTIPLE = 100 +DEVICE_TYPE_PS = 1 * DEVICE_TYPE_MULTIPLE +DEVICE_TYPE_FAN = 2 * DEVICE_TYPE_MULTIPLE +DEVICE_TYPE_CHASSIS_THERMAL = 99 * DEVICE_TYPE_MULTIPLE +DEVICE_TYPE_POWER_MONITOR = 24 * DEVICE_TYPE_MULTIPLE + +# Sensor Type Definition +SENSOR_TYPE_MULTIPLE = 10 +SENSOR_TYPE_TEMP = 1 * SENSOR_TYPE_MULTIPLE +SENSOR_TYPE_FAN = 2 * SENSOR_TYPE_MULTIPLE +SENSOR_TYPE_POWER = 3 * SENSOR_TYPE_MULTIPLE +SENSOR_TYPE_CURRENT = 4 * SENSOR_TYPE_MULTIPLE +SENSOR_TYPE_VOLTAGE = 5 * SENSOR_TYPE_MULTIPLE + +# Port entPhysicalIndex Definition +PORT_IFINDEX_MULTIPLE = 100 +SENSOR_TYPE_PORT_TX_POWER = 2 * SENSOR_TYPE_MULTIPLE +SENSOR_TYPE_PORT_RX_POWER = 3 * SENSOR_TYPE_MULTIPLE +SENSOR_TYPE_PORT_TX_BIAS = 4 * SENSOR_TYPE_MULTIPLE + +CHASSIS_SUB_ID = 1 +CHASSIS_MGMT_SUB_ID = MODULE_TYPE_MGMT + +# This is used in both rfc2737 and rfc3433 +XCVR_SENSOR_PART_ID_MAP = { + "temperature": SENSOR_TYPE_TEMP, + "tx1power": SENSOR_TYPE_PORT_TX_POWER + 1, + "tx2power": SENSOR_TYPE_PORT_TX_POWER + 2, + "tx3power": SENSOR_TYPE_PORT_TX_POWER + 3, + "tx4power": SENSOR_TYPE_PORT_TX_POWER + 4, + "rx1power": SENSOR_TYPE_PORT_RX_POWER + 1, + "rx2power": SENSOR_TYPE_PORT_RX_POWER + 2, + "rx3power": SENSOR_TYPE_PORT_RX_POWER + 3, + "rx4power": SENSOR_TYPE_PORT_RX_POWER + 4, + "tx1bias": SENSOR_TYPE_PORT_TX_BIAS + 1, + "tx2bias": SENSOR_TYPE_PORT_TX_BIAS + 2, + "tx3bias": SENSOR_TYPE_PORT_TX_BIAS + 3, + "tx4bias": SENSOR_TYPE_PORT_TX_BIAS + 4, + "voltage": SENSOR_TYPE_VOLTAGE, +} + +PSU_SENSOR_PART_ID_MAP = { + 'temperature': SENSOR_TYPE_TEMP, + 'power': SENSOR_TYPE_POWER, + 'current': SENSOR_TYPE_CURRENT, + 'voltage': SENSOR_TYPE_VOLTAGE +} + +def get_chassis_thermal_sub_id(position): + """ + Returns sub OID for thermals that belong to chassis. Sub OID is calculated as follows: + sub OID = CHASSIS_MGMT_SUB_ID + DEVICE_TYPE_CHASSIS_THERMAL + position * DEVICE_INDEX_MULTIPLE + SENSOR_TYPE_TEMP, + :param position: thermal position + :return: sub OID of the thermal + """ + return (CHASSIS_MGMT_SUB_ID + DEVICE_TYPE_CHASSIS_THERMAL + position * DEVICE_INDEX_MULTIPLE + SENSOR_TYPE_TEMP, ) + +def get_fan_sub_id(parent_id, position): + """ + Returns sub OID for fan. Sub OID is calculated as follows: + sub OID = parent_id[0] + DEVICE_TYPE_FAN + position * DEVICE_INDEX_MULTIPLE + If parent_id is chassis OID, will use a "virtual" fan drawer OID as its parent_id + :param parent_id: parent device sub OID + :param position: fan position + :return: sub OID of the fan + """ + if parent_id[0] == CHASSIS_SUB_ID: + parent_id = MODULE_TYPE_FAN_DRAWER + position * MODULE_INDEX_MULTIPLE + else: + parent_id = parent_id[0] + return (parent_id + DEVICE_TYPE_FAN + position * DEVICE_INDEX_MULTIPLE, ) + +def get_fan_drawer_sub_id(position): + """ + Returns sub OID for fan drawer. Sub OID is calculated as follows: + sub OID = MODULE_TYPE_FAN_DRAWER + position * MODULE_INDEX_MULTIPLE + :param position: fan drawer position + :return: sub OID of the fan drawer + """ + return (MODULE_TYPE_FAN_DRAWER + position * MODULE_INDEX_MULTIPLE, ) + +def get_fan_tachometers_sub_id(parent_id): + """ + Returns sub OID for fan tachometers. Sub OID is calculated as follows: + sub OID = parent_id[0] + SENSOR_TYPE_FAN + :param parent_id: parent device sub OID + :return: sub OID of the fan tachometers + """ + return (parent_id[0] + SENSOR_TYPE_FAN, ) + +def get_psu_sub_id(position): + """ + Returns sub OID for PSU. Sub OID is calculated as follows: + sub OID = MODULE_TYPE_PSU + position * MODULE_INDEX_MULTIPLE + :param position: PSU position + :return: sub OID of PSU + """ + return (MODULE_TYPE_PSU + position * MODULE_INDEX_MULTIPLE, ) + +def get_psu_sensor_sub_id(parent_id, sensor): + """ + Returns sub OID for PSU sensor. Sub OID is calculated as follows: + sub OID = parent_id[0] + DEVICE_TYPE_POWER_MONITOR + PSU_SENSOR_PART_ID_MAP[sensor] + :param parent_id: PSU oid + :param sensor: PSU sensor name + :return: sub OID of PSU sensor + """ + return (parent_id[0] + DEVICE_TYPE_POWER_MONITOR + PSU_SENSOR_PART_ID_MAP[sensor], ) + +def get_transceiver_sub_id(ifindex): + """ + Returns sub OID for transceiver. Sub OID is calculated as folows: + sub OID = MODULE_TYPE_PORT + ifindex * PORT_IFINDEX_MULTIPLE + :param ifindex: interface index + :return: sub OID of a port + """ + return (MODULE_TYPE_PORT + ifindex * PORT_IFINDEX_MULTIPLE, ) + +def get_transceiver_sensor_sub_id(ifindex, sensor): + """ + Returns sub OID for transceiver sensor. Sub OID is calculated as folows: + sub OID = transceiver_oid + XCVR_SENSOR_PART_ID_MAP[sensor] + :param ifindex: interface index + :param sensor: sensor key + :return: sub OID = {{index}} * 1000 + {{lane}} * 10 + sensor id + """ + + transceiver_oid, = get_transceiver_sub_id(ifindex) + return (transceiver_oid + XCVR_SENSOR_PART_ID_MAP[sensor],) diff --git a/src/sonic_ax_impl/mibs/ietf/rfc2737.py b/src/sonic_ax_impl/mibs/ietf/rfc2737.py index 9601858a064..e734de39120 100644 --- a/src/sonic_ax_impl/mibs/ietf/rfc2737.py +++ b/src/sonic_ax_impl/mibs/ietf/rfc2737.py @@ -11,6 +11,18 @@ from sonic_ax_impl import mibs from sonic_ax_impl.mibs import Namespace +from .physical_entity_sub_oid_generator import CHASSIS_SUB_ID +from .physical_entity_sub_oid_generator import CHASSIS_MGMT_SUB_ID +from .physical_entity_sub_oid_generator import get_chassis_thermal_sub_id +from .physical_entity_sub_oid_generator import get_fan_sub_id +from .physical_entity_sub_oid_generator import get_fan_drawer_sub_id +from .physical_entity_sub_oid_generator import get_fan_tachometers_sub_id +from .physical_entity_sub_oid_generator import get_psu_sub_id +from .physical_entity_sub_oid_generator import get_psu_sensor_sub_id +from .physical_entity_sub_oid_generator import get_transceiver_sub_id +from .physical_entity_sub_oid_generator import get_transceiver_sensor_sub_id + + @unique class PhysicalClass(int, Enum): """ @@ -28,6 +40,54 @@ class PhysicalClass(int, Enum): MODULE = 9 PORT = 10 STACK = 11 + CPU = 12 # Added in RFC 6933 + + +@unique +class FanInfoDB(str, Enum): + """ + FAN info keys + """ + MODEL = 'model' + PRESENCE = 'presence' + SERIAL = 'serial' + SPEED = 'speed' + REPLACEABLE = 'is_replaceable' + + +@unique +class FanDrawerInfoDB(str, Enum): + """ + FAN drawer info keys + """ + MODEL = 'model' + PRESENCE = 'presence' + SERIAL = 'serial' + REPLACEABLE = 'is_replaceable' + + +@unique +class PhysicalRelationInfoDB(str, Enum): + """ + Physical relation info keys + """ + POSITION_IN_PARENT = 'position_in_parent' + PARENT_NAME = 'parent_name' + + +@unique +class PsuInfoDB(str, Enum): + """ + PSU info keys + """ + MODEL = 'model' + SERIAL = 'serial' + CURRENT = 'current' + POWER = 'power' + PRESENCE = 'presence' + VOLTAGE = 'voltage' + TEMPERATURE = 'temp' + REPLACEABLE = 'is_replaceable' @unique @@ -35,16 +95,39 @@ class XcvrInfoDB(str, Enum): """ Transceiver info keys """ - TYPE = "type" HARDWARE_REVISION = "hardware_rev" SERIAL_NUMBER = "serial" MANUFACTURE_NAME = "manufacturer" MODEL_NAME = "model" + REPLACEABLE = 'is_replaceable' + +@unique +class ThermalInfoDB(str, Enum): + """ + FAN drawer info keys + """ + TEMPERATURE = 'temperature' + REPLACEABLE = 'is_replaceable' + +# Map used to generate PSU sensor description +PSU_SENSOR_NAME_MAP = { + 'temperature': 'Temperature', + 'power' : 'Power', + 'current' : 'Current', + 'voltage' : 'Voltage' +} + +PSU_SENSOR_POSITION_MAP = { + 'temperature': 1, + 'power' : 2, + 'current' : 3, + 'voltage' : 4 +} -# Map used to generate sensor description -SENSOR_NAME_MAP = { +# Map used to generate transceiver sensor description +XCVR_SENSOR_NAME_MAP = { "temperature" : "Temperature", "voltage" : "Voltage", "rx1power" : "RX Power", @@ -61,18 +144,46 @@ class XcvrInfoDB(str, Enum): "tx4power" : "TX Power", } -QSFP_LANES = (1, 2, 3, 4) +XCVR_SENSOR_INDEX_MAP = { + "temperature" : 1, + "tx1power" : 2, + "tx2power" : 3, + "tx3power" : 4, + "tx4power" : 5, + "rx1power" : 6, + "rx2power" : 7, + "rx3power" : 8, + "rx4power" : 9, + "tx1bias" : 10, + "tx2bias" : 11, + "tx3bias" : 12, + "tx4bias" : 13, + "voltage" : 14, +} +NOT_AVAILABLE = 'N/A' +QSFP_LANES = (1, 2, 3, 4) -def get_transceiver_data(xcvr_info): +def is_null_str(value): """ - :param xcvr_info: transceiver info dict - :return: tuple (type, hw_version, mfg_name, model_name) of transceiver; - Empty string if field not in xcvr_info + Indicate if a string value is null + :param value: input string value + :return: True is string value is empty or equal to 'N/A' or 'None' """ + if not isinstance(value, str) or value == NOT_AVAILABLE or value == 'None': + return True + return False - return (xcvr_info.get(xcvr_field.value, "") - for xcvr_field in XcvrInfoDB) + +def get_db_data(info_dict, enum_type): + """ + :param info_dict: db info dict + :param enum_type: db field enum + :return: tuple of fields values defined in enum_type; + Empty string if field not in info_dict + """ + return (info_dict.get(field.value, "") + for field in enum_type) def get_transceiver_description(sfp_type, if_alias): @@ -106,7 +217,25 @@ def get_transceiver_sensor_description(sensor, if_alias): # continue as with non per channel sensor port_name = if_alias - return "DOM {} Sensor for {}".format(SENSOR_NAME_MAP[sensor], port_name) + return "DOM {} Sensor for {}".format(XCVR_SENSOR_NAME_MAP[sensor], port_name) + + +class Callback(object): + """ + Utility class to store a callable and its arguments for future invoke + """ + def __init__(self, function, args): + # A callable + self.function = function + + # Arguments for the given callable + self.args = args + + def invoke(self): + """ + Invoke the callable + """ + self.function(*self.args) class PhysicalTableMIBUpdater(MIBUpdater): @@ -114,8 +243,18 @@ class PhysicalTableMIBUpdater(MIBUpdater): Updater class for physical table MIB """ - CHASSIS_ID = 1 - TRANSCEIVER_KEY_PATTERN = mibs.transceiver_info_table("*") + # Chassis key name in CHASSIS_INFO table + CHASSIS_NAME = 'chassis 1' + + # Indicate that an entity is replaceable, according to RFC2737 + REPLACEABLE = 1 + + # Indicate that an entity is not replaceable, according to RFC2737 + NOT_REPLACEABLE = 2 + + # A list of physical entity updater, @decorator physical_entity_updater can register physical entity updater types + # to this list, and these types will be used for create instance for each type + physical_entity_updater_types = [] def __init__(self): super().__init__() @@ -123,20 +262,50 @@ def __init__(self): self.statedb = Namespace.init_namespace_dbs() Namespace.connect_all_dbs(self.statedb, mibs.STATE_DB) - self.if_alias_map = {} - # List of available sub OIDs. self.physical_entities = [] # Map sub ID to its data. self.physical_classes_map = {} self.physical_description_map = {} + self.physical_name_map = {} self.physical_hw_version_map = {} self.physical_serial_number_map = {} self.physical_mfg_name_map = {} self.physical_model_name_map = {} + self.physical_contained_in_map = {} + self.physical_parent_relative_pos_map = {} + self.physical_fru_map = {} + + # Map physical entity name and oid. According to RFC2737, entPhysicalContainedIn is indicates the value of + # entPhysicalIndex for the physical entity which 'contains' this physical entity. However, there is + # only parent entity name in database, so need a way to get physical entity oid by name. + self.physical_name_to_oid_map = {} + + # Map physical entity name that need resolve. The key is the entity name, value is a list of Callback objects + # that will be called when the entity name is added to self.physical_name_to_oid_map. + # It's possible the parent name and parent oid are still not in self.physical_name_to_oid_map when child entity + # update cache. In that case, the child entity might not be able to calculate its sub id and cannot update its + # cache or do future operation. So this dictionary provides a way to store such operations for future executes. + self.pending_resolve_parent_name_map = {} + + # physical entity updaters + self.physical_entity_updaters = self.create_physical_entity_updaters() + + @classmethod + def register_entity_updater_type(cls, object_type): + """ + Register physical entity updater + :param object_type: entity updater type + """ + cls.physical_entity_updater_types.append(object_type) - self.pubsub = [None] * len(self.statedb) + def create_physical_entity_updaters(self): + """ + Create all physical entity updater instances + :return: a list of physical entity updater instance + """ + return [creator(self) for creator in PhysicalTableMIBUpdater.physical_entity_updater_types] def reinit_data(self): """ @@ -146,18 +315,22 @@ def reinit_data(self): # reinit cache self.physical_classes_map = {} self.physical_description_map = {} + self.physical_name_map = {} self.physical_hw_version_map = {} self.physical_serial_number_map = {} self.physical_mfg_name_map = {} self.physical_model_name_map = {} + self.physical_contained_in_map = {} + self.physical_parent_relative_pos_map = {} + self.physical_fru_map = {} - # update interface maps - _, self.if_alias_map, _, _ = \ - Namespace.get_sync_d_from_all_namespace(mibs.init_sync_d_interface_tables, Namespace.init_namespace_dbs()) + self.physical_name_to_oid_map = {} + self.pending_resolve_parent_name_map = {} device_metadata = mibs.get_device_metadata(self.statedb[0]) - chassis_sub_id = (self.CHASSIS_ID, ) + chassis_sub_id = (CHASSIS_SUB_ID, ) self.physical_entities = [chassis_sub_id] + self.physical_name_to_oid_map[self.CHASSIS_NAME] = chassis_sub_id if not device_metadata or not device_metadata.get("chassis_serial_number"): chassis_serial_number = "" @@ -166,144 +339,177 @@ def reinit_data(self): self.physical_classes_map[chassis_sub_id] = PhysicalClass.CHASSIS self.physical_serial_number_map[chassis_sub_id] = chassis_serial_number - - # retrieve the initial list of transceivers that are present in the system - transceiver_info = Namespace.dbs_keys(self.statedb, mibs.STATE_DB, self.TRANSCEIVER_KEY_PATTERN) - if transceiver_info: - self.transceiver_entries = [entry \ - for entry in transceiver_info] - else: - self.transceiver_entries = [] - - # update cache with initial data - for transceiver_entry in self.transceiver_entries: - # extract interface name - interface = transceiver_entry.split(mibs.TABLE_NAME_SEPARATOR_VBAR)[-1] - self._update_transceiver_cache(interface) - - def _update_per_namespace_data(self, pubsub): - """ - Update cache. - Here we listen to changes in STATE_DB TRANSCEIVER_INFO table - and update data only when there is a change (SET, DELETE) - """ - - # This code is not executed in unit test, since mockredis - # does not support pubsub - while True: - msg = pubsub.get_message() - - if not msg: - break - - transceiver_entry = msg["channel"].split(":")[-1] - data = msg['data'] # event data - - # extract interface name - interface = transceiver_entry.split(mibs.TABLE_NAME_SEPARATOR_VBAR)[-1] - - # get interface from interface name - ifindex = port_util.get_index_from_str(interface) - - if ifindex is None: - # interface name invalid, skip this entry - mibs.logger.warning( - "Invalid interface name in {} \ - in STATE_DB, skipping".format(transceiver_entry)) - continue - - if "set" in data: - self._update_transceiver_cache(interface) - elif "del" in data: - # remove deleted transceiver - remove_sub_ids = [mibs.get_transceiver_sub_id(ifindex)] - - # remove all sensor OIDs associated with removed transceiver - for sensor in SENSOR_NAME_MAP: - remove_sub_ids.append(mibs.get_transceiver_sensor_sub_id(ifindex, sensor)) - - for sub_id in remove_sub_ids: - if sub_id and sub_id in self.physical_entities: - self.physical_entities.remove(sub_id) + self.physical_name_map[chassis_sub_id] = self.CHASSIS_NAME + self.physical_description_map[chassis_sub_id] = self.CHASSIS_NAME + self.physical_contained_in_map[chassis_sub_id] = 0 + self.physical_fru_map[chassis_sub_id] = self.NOT_REPLACEABLE + + # Add a chassis mgmt node + chassis_mgmt_sub_id = (CHASSIS_MGMT_SUB_ID,) + self.add_sub_id(chassis_mgmt_sub_id) + self.physical_classes_map[chassis_mgmt_sub_id] = PhysicalClass.CPU + self.physical_contained_in_map[chassis_mgmt_sub_id] = CHASSIS_SUB_ID + self.physical_parent_relative_pos_map[chassis_mgmt_sub_id] = 1 + name = 'MGMT' + self.physical_description_map[chassis_mgmt_sub_id] = name + self.physical_name_map[chassis_mgmt_sub_id] = name + self.physical_fru_map[chassis_mgmt_sub_id] = self.NOT_REPLACEABLE + + for updater in self.physical_entity_updaters: + updater.reinit_data() def update_data(self): # This code is not executed in unit test, since mockredis # does not support pubsub for i in range(len(self.statedb)): - if not self.pubsub[i]: - pattern = self.TRANSCEIVER_KEY_PATTERN - self.pubsub[i] = mibs.get_redis_pubsub(self.statedb[i], self.statedb[i].STATE_DB, pattern) - self._update_per_namespace_data(self.pubsub[i]) + for updater in self.physical_entity_updaters: + updater.update_data(i, self.statedb[i]) - def _update_transceiver_cache(self, interface): - """ - Update data for single transceiver - :param: interface: Interface name associated with transceiver - """ - - # get interface from interface name - ifindex = port_util.get_index_from_str(interface) - - # update xcvr info from DB - # use port's name as key for transceiver info entries - sub_id = mibs.get_transceiver_sub_id(ifindex) - - # add interface to available OID list + def add_sub_id(self, sub_id): insort_right(self.physical_entities, sub_id) - # get transceiver information from transceiver info entry in STATE DB - transceiver_info = Namespace.dbs_get_all(self.statedb, mibs.STATE_DB, - mibs.transceiver_info_table(interface)) - - if not transceiver_info: - return + def remove_sub_ids(self, remove_sub_ids): + """ + Remove all data related to given sub id list + :param remove_sub_ids: a list of sub ids that will be removed + """ + for sub_id in remove_sub_ids: + if not sub_id: + continue + if sub_id in self.physical_entities: + self.physical_entities.remove(sub_id) + if sub_id in self.physical_classes_map: + self.physical_classes_map.pop(sub_id) + if sub_id in self.physical_name_map: + name = self.physical_name_map[sub_id] + if name in self.physical_name_to_oid_map: + self.physical_name_to_oid_map.pop(name) + if name in self.pending_resolve_parent_name_map: + self.pending_resolve_parent_name_map.pop(name) + self.physical_name_map.pop(sub_id) + if sub_id in self.physical_hw_version_map: + self.physical_hw_version_map.pop(sub_id) + if sub_id in self.physical_serial_number_map: + self.physical_serial_number_map.pop(sub_id) + if sub_id in self.physical_mfg_name_map: + self.physical_mfg_name_map.pop(sub_id) + if sub_id in self.physical_model_name_map: + self.physical_model_name_map.pop(sub_id) + if sub_id in self.physical_contained_in_map: + self.physical_contained_in_map.pop(sub_id) + if sub_id in self.physical_parent_relative_pos_map: + self.physical_parent_relative_pos_map.pop(sub_id) + if sub_id in self.physical_fru_map: + self.physical_fru_map.pop(sub_id) + + def add_pending_entity_name_callback(self, name, function, args): + """ + Store a callback for those entity whose parent entity name has not been resolved yet + :param name: parent entity name + :param function: a callable + :param args: arguments for the callable + """ + if name in self.pending_resolve_parent_name_map: + self.pending_resolve_parent_name_map[name].append(Callback(function, args)) + else: + self.pending_resolve_parent_name_map[name] = [Callback(function, args)] - # physical class - network port - self.physical_classes_map[sub_id] = PhysicalClass.PORT + def update_name_to_oid_map(self, name, oid): + """ + Update entity name to oid map. If the given name is in self.pending_resolve_parent_name_map, update physical + contained in information accordingly. + :param name: entity name + :param oid: entity oid + """ + self.physical_name_to_oid_map[name] = oid - # save values into cache - sfp_type, \ - self.physical_hw_version_map[sub_id],\ - self.physical_serial_number_map[sub_id], \ - self.physical_mfg_name_map[sub_id], \ - self.physical_model_name_map[sub_id] = get_transceiver_data(transceiver_info) + if name in self.pending_resolve_parent_name_map: + for callback in self.pending_resolve_parent_name_map[name]: + callback.invoke() + self.pending_resolve_parent_name_map.pop(name) - ifalias = self.if_alias_map.get(interface, "") + def set_phy_class(self, sub_id, phy_class): + """ + :param sub_id: sub OID + :param phy_class: physical entity class + """ + self.physical_classes_map[sub_id] = phy_class - # generate a description for this transceiver - self.physical_description_map[sub_id] = get_transceiver_description(sfp_type, ifalias) + def set_phy_parent_relative_pos(self, sub_id, pos): + """ + :param sub_id: sub OID + :param pos: 1-based relative position + """ + self.physical_parent_relative_pos_map[sub_id] = pos - # update transceiver sensor cache - self._update_transceiver_sensor_cache(interface) + def set_phy_descr(self, sub_id, phy_desc): + """ + :param sub_id: sub OID + :param phy_desc: physical entity description + """ + self.physical_description_map[sub_id] = phy_desc - def _update_transceiver_sensor_cache(self, interface): + def set_phy_name(self, sub_id, name): """ - Update sensor data for single transceiver - :param: interface: Interface name associated with transceiver + :param sub_id: sub OID + :param name: physical entity name """ + self.physical_name_map[sub_id] = name - ifalias = self.if_alias_map.get(interface, "") - ifindex = port_util.get_index_from_str(interface) + def set_phy_contained_in(self, sub_id, parent): + """ + :param sub_id: sub OID + :param parent: parent entity name or parent oid + """ - # get transceiver sensors from transceiver dom entry in STATE DB - transceiver_dom_entry = Namespace.dbs_get_all(self.statedb, mibs.STATE_DB, - mibs.transceiver_dom_table(interface)) + if isinstance(parent, str): + if parent in self.physical_name_to_oid_map: + self.physical_contained_in_map[sub_id] = self.physical_name_to_oid_map[parent][0] + else: + self._add_pending_entity_name_callback(parent, self.set_phy_contained_in, [sub_id, parent]) + elif isinstance(parent, int): + self.physical_contained_in_map[sub_id] = parent + elif isinstance(parent, tuple): + self.physical_contained_in_map[sub_id] = parent[0] + + def set_phy_hw_ver(self, sub_id, phy_hw_ver): + """ + :param sub_id: sub OID + :param phy_hw_ver: physical entity hardware version + """ + self.physical_hw_version_map[sub_id] = phy_hw_ver - if not transceiver_dom_entry: - return + def set_phy_serial_num(self, sub_id, phy_serial_num): + """ + :param sub_id: sub OID + :param phy_serial_num: physical entity serial number + """ + self.physical_serial_number_map[sub_id] = phy_serial_num - # go over transceiver sensors - for sensor in transceiver_dom_entry: - if sensor not in SENSOR_NAME_MAP: - continue - sensor_sub_id = mibs.get_transceiver_sensor_sub_id(ifindex, sensor) - sensor_description = get_transceiver_sensor_description(sensor, ifalias) + def set_phy_mfg_name(self, sub_id, phy_mfg_name): + """ + :param sub_id: sub OID + :param phy_mfg_name: physical entity manufacturer name + """ + self.physical_mfg_name_map[sub_id] = phy_mfg_name - self.physical_classes_map[sensor_sub_id] = PhysicalClass.SENSOR - self.physical_description_map[sensor_sub_id] = sensor_description + def set_phy_model_name(self, sub_id, phy_model_name): + """ + :param sub_id: sub OID + :param phy_model_name: physical entity model name + """ + self.physical_model_name_map[sub_id] = phy_model_name - # add to available OIDs list - insort_right(self.physical_entities, sensor_sub_id) + def set_phy_fru(self, sub_id, replaceable): + """ + :param sub_id: sub OID + :param replaceable: physical entity FRU + """ + if isinstance(replaceable, str): + replaceable = True if replaceable.lower() == 'true' else False + self.physical_fru_map[sub_id] = self.REPLACEABLE if replaceable else self.NOT_REPLACEABLE + elif isinstance(replaceable, bool): + self.physical_fru_map[sub_id] = self.REPLACEABLE if replaceable else self.NOT_REPLACEABLE def get_next(self, sub_id): """ @@ -326,6 +532,15 @@ def get_phy_class(self, sub_id): return self.physical_classes_map.get(sub_id, PhysicalClass.UNKNOWN) return None + def get_phy_parent_relative_pos(self, sub_id): + """ + :param sub_id: sub OID + :return: relative position in parent device for this OID + """ + if sub_id in self.physical_entities: + return self.physical_parent_relative_pos_map.get(sub_id, PhysicalClass.UNKNOWN) + return None + def get_phy_descr(self, sub_id): """ :param sub_id: sub OID @@ -336,14 +551,34 @@ def get_phy_descr(self, sub_id): return self.physical_description_map.get(sub_id, "") return None - def get_phy_name(self, sub_id): + def get_phy_vendor_type(self, sub_id): """ :param sub_id: sub OID - :return: name string for this OID + :return: vendor type for this OID """ return "" if sub_id in self.physical_entities else None + def get_phy_contained_in(self, sub_id): + """ + :param sub_id: sub OID + :return: physical contained in device OID for this OID + """ + + if sub_id in self.physical_entities: + return self.physical_contained_in_map.get(sub_id, -1) + #return sub_id_tuple[0] if isinstance(sub_id_tuple, tuple) else None + return None + + def get_phy_name(self, sub_id): + """ + :param sub_id: sub OID + :return: name string for this OID + """ + if sub_id in self.physical_entities: + return self.physical_name_map.get(sub_id, "") + return None + def get_phy_hw_ver(self, sub_id): """ :param sub_id: sub OID @@ -400,6 +635,450 @@ def get_phy_model_name(self, sub_id): return self.physical_model_name_map.get(sub_id, "") return None + def get_phy_alias(self, sub_id): + """ + :param sub_id: sub OID + :return: alias for this OID + """ + + return "" if sub_id in self.physical_entities else None + + def get_phy_assert_id(self, sub_id): + """ + :param sub_id: sub OID + :return: assert ID for this OID + """ + + return "" if sub_id in self.physical_entities else None + + def is_fru(self, sub_id): + """ + :param sub_id: sub OID + :return: if it is FRU for this OID + """ + if sub_id in self.physical_entities: + return self.physical_fru_map.get(sub_id, self.NOT_REPLACEABLE) + return None + + +def physical_entity_updater(): + """ + Decorator for auto registering physical entity types + """ + def wrapper(object_type): + PhysicalTableMIBUpdater.register_entity_updater_type(object_type) + return object_type + + return wrapper + + +class PhysicalEntityCacheUpdater(object): + """ + Base class for physical entity cache updater + """ + def __init__(self, mib_updater): + self.mib_updater = mib_updater + self.pub_sub_dict = {} + + # Map to store fan to its related oid. The key is the db key in FAN_INFO table, the value is a list of oid that + # relates to this fan entry. The map is used for removing fan mib objects when a fan removing from the system. + self.entity_to_oid_map = {} + + def reinit_data(self): + self.entity_to_oid_map.clear() + # retrieve the initial list of entity in db + key_info = Namespace.dbs_keys(self.mib_updater.statedb, mibs.STATE_DB, self.get_key_pattern()) + if key_info: + keys = [entry for entry in key_info] + else: + keys = [] + + # update cache with initial data + for key in keys: + # extract entity name + name = key.split(mibs.TABLE_NAME_SEPARATOR_VBAR)[-1] + self._update_entity_cache(name) + + def update_data(self, db_index, db): + if db_index not in self.pub_sub_dict: + self.pub_sub_dict[db_index] = mibs.get_redis_pubsub(db, db.STATE_DB, self.get_key_pattern()) + + self._update_per_namespace_data(self.pub_sub_dict[db_index]) + + def _update_per_namespace_data(self, pubsub): + """ + Update cache. + Here we listen to changes in STATE_DB table + and update data only when there is a change (SET, DELETE) + """ + while True: + msg = pubsub.get_message() + + if not msg: + break + + db_entry = msg["channel"].split(":")[-1] + data = msg['data'] # event data + if not isinstance(data, bytes): + continue + + # extract interface name + name = db_entry.split(mibs.TABLE_NAME_SEPARATOR_VBAR)[-1] + + if "set" in data: + self._update_entity_cache(name) + elif "del" in data: + self._remove_entity_cache(name) + + def get_key_pattern(self): + pass + + def _update_entity_cache(self, name): + pass + + def get_physical_relation_info(self, name): + return Namespace.dbs_get_all(self.mib_updater.statedb, mibs.STATE_DB, + mibs.physical_entity_info_table(name)) + + def _add_entity_related_oid(self, entity_name, oid): + if entity_name not in self.entity_to_oid_map: + self.entity_to_oid_map[entity_name] = [oid] + else: + self.entity_to_oid_map[entity_name].append(oid) + + def _remove_entity_cache(self, entity_name): + if entity_name in self.entity_to_oid_map: + self.mib_updater.remove_sub_ids(self.entity_to_oid_map[entity_name]) + self.entity_to_oid_map.pop(entity_name) + + +@physical_entity_updater() +class XcvrCacheUpdater(PhysicalEntityCacheUpdater): + KEY_PATTERN = mibs.transceiver_info_table("*") + + def __init__(self, mib_updater): + super(XcvrCacheUpdater, self).__init__(mib_updater) + self.if_alias_map = {} + + def get_key_pattern(self): + return XcvrCacheUpdater.KEY_PATTERN + + def reinit_data(self): + # update interface maps + _, self.if_alias_map, _, _ = \ + Namespace.get_sync_d_from_all_namespace(mibs.init_sync_d_interface_tables, Namespace.init_namespace_dbs()) + PhysicalEntityCacheUpdater.reinit_data(self) + + def _update_entity_cache(self, interface): + """ + Update data for single transceiver + :param: interface: Interface name associated with transceiver + """ + + # get interface from interface name + ifindex = port_util.get_index_from_str(interface) + + if ifindex is None: + # interface name invalid, skip this entry + mibs.logger.warning( + "Invalid interface name in {} \ + in STATE_DB, skipping".format(interface)) + return + + # get transceiver information from transceiver info entry in STATE DB + transceiver_info = Namespace.dbs_get_all(self.mib_updater.statedb, mibs.STATE_DB, + mibs.transceiver_info_table(interface)) + + if not transceiver_info: + return + + # update xcvr info from DB + # use port's name as key for transceiver info entries + sub_id = get_transceiver_sub_id(ifindex) + + # add interface to available OID list + self.mib_updater.add_sub_id(sub_id) + + self._add_entity_related_oid(interface, sub_id) + + # physical class - network port + self.mib_updater.set_phy_class(sub_id, PhysicalClass.PORT) + + # save values into cache + sfp_type, hw_version, serial_number, mfg_name, model_name, replaceable = get_db_data(transceiver_info, XcvrInfoDB) + self.mib_updater.set_phy_hw_ver(sub_id, hw_version) + self.mib_updater.set_phy_serial_num(sub_id, serial_number) + self.mib_updater.set_phy_mfg_name(sub_id, mfg_name) + self.mib_updater.set_phy_model_name(sub_id, model_name) + self.mib_updater.set_phy_contained_in(sub_id, CHASSIS_SUB_ID) + self.mib_updater.set_phy_fru(sub_id, replaceable) + # Relative position of SFP can be changed at run time. For example, plug out a normal cable SFP3 and plug in + # a 1 split 4 SFP, the relative position of SFPs after SPF3 will change. In this case, it is hard to determine + # the relative position for other SFP. According to RFC 2737, 'If the agent cannot determine the parent-relative position + # for some reason, or if the associated value of entPhysicalContainedIn is '0', then the value '-1' is returned'. + # See https://tools.ietf.org/html/rfc2737. + self.mib_updater.set_phy_parent_relative_pos(sub_id, -1) + + ifalias = self.if_alias_map.get(interface, "") + + # generate a description for this transceiver + self.mib_updater.set_phy_descr(sub_id, get_transceiver_description(sfp_type, ifalias)) + self.mib_updater.set_phy_name(sub_id, interface) + + # update transceiver sensor cache + self._update_transceiver_sensor_cache(interface, sub_id) + + def _update_transceiver_sensor_cache(self, interface, sub_id): + """ + Update sensor data for single transceiver + :param: interface: Interface name associated with transceiver + :param: sub_id: OID of transceiver + """ + + ifalias = self.if_alias_map.get(interface, "") + ifindex = port_util.get_index_from_str(interface) + + # get transceiver sensors from transceiver dom entry in STATE DB + transceiver_dom_entry = Namespace.dbs_get_all(self.mib_updater.statedb, mibs.STATE_DB, + mibs.transceiver_dom_table(interface)) + + if not transceiver_dom_entry: + return + + # go over transceiver sensors + for sensor in transceiver_dom_entry: + if sensor not in XCVR_SENSOR_NAME_MAP: + continue + sensor_sub_id = get_transceiver_sensor_sub_id(ifindex, sensor) + self._add_entity_related_oid(interface, sensor_sub_id) + sensor_description = get_transceiver_sensor_description(sensor, ifalias) + + self.mib_updater.set_phy_class(sensor_sub_id, PhysicalClass.SENSOR) + self.mib_updater.set_phy_descr(sensor_sub_id, sensor_description) + self.mib_updater.set_phy_name(sensor_sub_id, sensor_description) + self.mib_updater.set_phy_contained_in(sensor_sub_id, sub_id) + self.mib_updater.set_phy_parent_relative_pos(sensor_sub_id, XCVR_SENSOR_INDEX_MAP[sensor]) + self.mib_updater.set_phy_fru(sensor_sub_id, False) + # add to available OIDs list + self.mib_updater.add_sub_id(sensor_sub_id) + + +@physical_entity_updater() +class PsuCacheUpdater(PhysicalEntityCacheUpdater): + KEY_PATTERN = mibs.psu_info_table("*") + + def __init__(self, mib_updater): + super(PsuCacheUpdater, self).__init__(mib_updater) + + def get_key_pattern(self): + return PsuCacheUpdater.KEY_PATTERN + + def _update_entity_cache(self, psu_name): + psu_info = Namespace.dbs_get_all(self.mib_updater.statedb, mibs.STATE_DB, + mibs.psu_info_table(psu_name)) + + if not psu_info: + return + + model, serial, current, power, presence, voltage, temperature, replaceable = get_db_data(psu_info, PsuInfoDB) + if presence.lower() != 'true': + self._remove_entity_cache(psu_name) + return + + psu_relation_info = self.get_physical_relation_info(psu_name) + psu_position, psu_parent_name = get_db_data(psu_relation_info, PhysicalRelationInfoDB) + psu_position = int(psu_position) + psu_sub_id = get_psu_sub_id(psu_position) + self._add_entity_related_oid(psu_name, psu_sub_id) + self.mib_updater.update_name_to_oid_map(psu_name, psu_sub_id) + + # add PSU to available OID list + self.mib_updater.add_sub_id(psu_sub_id) + self.mib_updater.set_phy_class(psu_sub_id, PhysicalClass.POWERSUPPLY) + self.mib_updater.set_phy_descr(psu_sub_id, psu_name) + self.mib_updater.set_phy_name(psu_sub_id, psu_name) + if not is_null_str(model): + self.mib_updater.set_phy_model_name(psu_sub_id, model) + if not is_null_str(serial): + self.mib_updater.set_phy_serial_num(psu_sub_id, serial) + self.mib_updater.set_phy_parent_relative_pos(psu_sub_id, psu_position) + self.mib_updater.set_phy_contained_in(psu_sub_id, psu_parent_name) + self.mib_updater.set_phy_fru(psu_sub_id, replaceable) + + # add psu current sensor as a physical entity + if current and not is_null_str(current): + self._update_psu_sensor_cache(psu_name, psu_sub_id, 'current') + if power and not is_null_str(power): + self._update_psu_sensor_cache(psu_name, psu_sub_id, 'power') + if temperature and not is_null_str(temperature): + self._update_psu_sensor_cache(psu_name, psu_sub_id, 'temperature') + if voltage and not is_null_str(voltage): + self._update_psu_sensor_cache(psu_name, psu_sub_id, 'voltage') + + def _update_psu_sensor_cache(self, psu_name, psu_sub_id, sensor_name): + psu_current_sub_id = get_psu_sensor_sub_id(psu_sub_id, sensor_name) + self._add_entity_related_oid(psu_name, psu_current_sub_id) + self.mib_updater.add_sub_id(psu_current_sub_id) + self.mib_updater.set_phy_class(psu_current_sub_id, PhysicalClass.SENSOR) + desc = '{} for {}'.format(PSU_SENSOR_NAME_MAP[sensor_name], psu_name) + self.mib_updater.set_phy_descr(psu_current_sub_id, desc) + self.mib_updater.set_phy_name(psu_current_sub_id, desc) + self.mib_updater.set_phy_parent_relative_pos(psu_current_sub_id, PSU_SENSOR_POSITION_MAP[sensor_name]) + self.mib_updater.set_phy_contained_in(psu_current_sub_id, psu_sub_id) + self.mib_updater.set_phy_fru(psu_current_sub_id, False) + + +@physical_entity_updater() +class FanDrawerCacheUpdater(PhysicalEntityCacheUpdater): + KEY_PATTERN = mibs.fan_drawer_info_table("*") + + def __init__(self, mib_updater): + super(FanDrawerCacheUpdater, self).__init__(mib_updater) + + def get_key_pattern(self): + return FanDrawerCacheUpdater.KEY_PATTERN + + def _update_entity_cache(self, drawer_name): + drawer_info = Namespace.dbs_get_all(self.mib_updater.statedb, mibs.STATE_DB, + mibs.fan_drawer_info_table(drawer_name)) + + if not drawer_info: + return + + model, presence, serial, replaceable = get_db_data(drawer_info, FanDrawerInfoDB) + if presence.lower() != 'true': + self._remove_entity_cache(drawer_name) + return + + drawer_relation_info = self.get_physical_relation_info(drawer_name) + if drawer_relation_info: + drawer_position, drawer_parent_name = get_db_data(drawer_relation_info, PhysicalRelationInfoDB) + drawer_position = int(drawer_position) + drawer_sub_id = get_fan_drawer_sub_id(drawer_position) + self._add_entity_related_oid(drawer_name, drawer_sub_id) + self.mib_updater.update_name_to_oid_map(drawer_name, drawer_sub_id) + + # add fan drawer to available OID list + self.mib_updater.add_sub_id(drawer_sub_id) + self.mib_updater.set_phy_class(drawer_sub_id, PhysicalClass.CONTAINER) + self.mib_updater.set_phy_descr(drawer_sub_id, drawer_name) + self.mib_updater.set_phy_name(drawer_sub_id, drawer_name) + self.mib_updater.set_phy_parent_relative_pos(drawer_sub_id, drawer_position) + self.mib_updater.set_phy_contained_in(drawer_sub_id, drawer_parent_name) + if model and not is_null_str(model): + self.mib_updater.set_phy_model_name(drawer_sub_id, model) + if serial and not is_null_str(serial): + self.mib_updater.set_phy_serial_num(drawer_sub_id, serial) + self.mib_updater.set_phy_fru(drawer_sub_id, replaceable) + + +@physical_entity_updater() +class FanCacheUpdater(PhysicalEntityCacheUpdater): + KEY_PATTERN = mibs.fan_info_table("*") + + def __init__(self, mib_updater): + super(FanCacheUpdater, self).__init__(mib_updater) + + def get_key_pattern(self): + return FanCacheUpdater.KEY_PATTERN + + def _update_entity_cache(self, fan_name): + fan_info = Namespace.dbs_get_all(self.mib_updater.statedb, mibs.STATE_DB, + mibs.fan_info_table(fan_name)) + + if not fan_info: + return + + model, presence, serial, speed, replaceable = get_db_data(fan_info, FanInfoDB) + if presence.lower() != 'true': + self._remove_entity_cache(fan_name) + return + + fan_relation_info = self.get_physical_relation_info(fan_name) + fan_position, fan_parent_name = get_db_data(fan_relation_info, PhysicalRelationInfoDB) + fan_position = int(fan_position) + if fan_parent_name in self.mib_updater.physical_name_to_oid_map: + self._update_fan_mib_info(fan_parent_name, fan_position, fan_name, serial, model, speed, replaceable) + else: + args = [fan_parent_name, fan_position, fan_name, serial, model, speed, replaceable] + self.mib_updater.add_pending_entity_name_callback(fan_parent_name, self._update_fan_mib_info, args) + + def _update_fan_mib_info(self, fan_parent_name, fan_position, fan_name, serial, model, speed, replaceable): + fan_parent_sub_id = self.mib_updater.physical_name_to_oid_map[fan_parent_name] + fan_sub_id = get_fan_sub_id(fan_parent_sub_id, fan_position) + self._add_entity_related_oid(fan_name, fan_sub_id) + #self.mib_updater.update_name_to_oid_map(fan_name, fan_sub_id) + + # add fan to available OID list + self.mib_updater.add_sub_id(fan_sub_id) + self.mib_updater.set_phy_class(fan_sub_id, PhysicalClass.FAN) + self.mib_updater.set_phy_descr(fan_sub_id, fan_name) + self.mib_updater.set_phy_name(fan_sub_id, fan_name) + self.mib_updater.set_phy_parent_relative_pos(fan_sub_id, fan_position) + self.mib_updater.set_phy_contained_in(fan_sub_id, fan_parent_name) + if serial and not is_null_str(serial): + self.mib_updater.set_phy_serial_num(fan_sub_id, serial) + if model and not is_null_str(model): + self.mib_updater.set_phy_model_name(fan_sub_id, model) + self.mib_updater.set_phy_fru(fan_sub_id, replaceable) + + # add fan tachometers as a physical entity + if speed and not is_null_str(speed): + fan_tachometers_sub_id = get_fan_tachometers_sub_id(fan_sub_id) + self._add_entity_related_oid(fan_name, fan_tachometers_sub_id) + self.mib_updater.add_sub_id(fan_tachometers_sub_id) + self.mib_updater.set_phy_class(fan_tachometers_sub_id, PhysicalClass.SENSOR) + desc = 'Tachometers for {}'.format(fan_name) + self.mib_updater.set_phy_descr(fan_tachometers_sub_id, desc) + self.mib_updater.set_phy_name(fan_tachometers_sub_id, desc) + self.mib_updater.set_phy_parent_relative_pos(fan_tachometers_sub_id, 1) + self.mib_updater.set_phy_contained_in(fan_tachometers_sub_id, fan_sub_id) + self.mib_updater.set_phy_fru(fan_tachometers_sub_id, False) + + +@physical_entity_updater() +class ThermalCacheUpdater(PhysicalEntityCacheUpdater): + KEY_PATTERN = mibs.thermal_info_table("*") + + def __init__(self, mib_updater): + super(ThermalCacheUpdater, self).__init__(mib_updater) + + def get_key_pattern(self): + return ThermalCacheUpdater.KEY_PATTERN + + def _update_entity_cache(self, thermal_name): + thermal_info = Namespace.dbs_get_all(self.mib_updater.statedb, mibs.STATE_DB, + mibs.thermal_info_table(thermal_name)) + if not thermal_info: + return + + temperature, replaceable = get_db_data(thermal_info, ThermalInfoDB) + if temperature and not is_null_str(temperature): + thermal_relation_info = self.get_physical_relation_info(thermal_name) + if not thermal_relation_info: + return + thermal_position, thermal_parent_name = get_db_data(thermal_relation_info, PhysicalRelationInfoDB) + thermal_position = int(thermal_position) + + # only process thermals belong to chassis here, thermals belong to other + # physical entity will be processed in other entity updater, for example + # PSU thermal will be processed by PsuCacheUpdater + if thermal_parent_name in self.mib_updater.physical_name_to_oid_map and \ + self.mib_updater.physical_name_to_oid_map[thermal_parent_name] == (CHASSIS_SUB_ID,): + thermal_sub_id = get_chassis_thermal_sub_id(thermal_position) + self._add_entity_related_oid(thermal_name, thermal_sub_id) + + # add thermal to available OID list + self.mib_updater.add_sub_id(thermal_sub_id) + self.mib_updater.set_phy_class(thermal_sub_id, PhysicalClass.SENSOR) + self.mib_updater.set_phy_descr(thermal_sub_id, thermal_name) + self.mib_updater.set_phy_name(thermal_sub_id, thermal_name) + self.mib_updater.set_phy_parent_relative_pos(thermal_sub_id, thermal_position) + self.mib_updater.set_phy_contained_in(thermal_sub_id, CHASSIS_MGMT_SUB_ID) + self.mib_updater.set_phy_fru(thermal_sub_id, replaceable) + else: + self._remove_entity_cache(thermal_name) + class PhysicalTableMIB(metaclass=MIBMeta, prefix='.1.3.6.1.2.1.47.1.1.1'): """ @@ -411,9 +1090,18 @@ class PhysicalTableMIB(metaclass=MIBMeta, prefix='.1.3.6.1.2.1.47.1.1.1'): entPhysicalDescr = \ SubtreeMIBEntry('1.2', updater, ValueType.OCTET_STRING, updater.get_phy_descr) + entPhysicalVendorType = \ + SubtreeMIBEntry('1.3', updater, ValueType.OCTET_STRING, updater.get_phy_vendor_type) + + entPhysicalContainedIn = \ + SubtreeMIBEntry('1.4', updater, ValueType.INTEGER, updater.get_phy_contained_in) + entPhysicalClass = \ SubtreeMIBEntry('1.5', updater, ValueType.INTEGER, updater.get_phy_class) + entPhysicalParentRelPos = \ + SubtreeMIBEntry('1.6', updater, ValueType.INTEGER, updater.get_phy_parent_relative_pos) + entPhysicalName = \ SubtreeMIBEntry('1.7', updater, ValueType.OCTET_STRING, updater.get_phy_name) @@ -434,3 +1122,12 @@ class PhysicalTableMIB(metaclass=MIBMeta, prefix='.1.3.6.1.2.1.47.1.1.1'): entPhysicalModelName = \ SubtreeMIBEntry('1.13', updater, ValueType.OCTET_STRING, updater.get_phy_model_name) + + entPhysicalAlias = \ + SubtreeMIBEntry('1.14', updater, ValueType.OCTET_STRING, updater.get_phy_alias) + + entPhysicalAssetID = \ + SubtreeMIBEntry('1.15', updater, ValueType.OCTET_STRING, updater.get_phy_assert_id) + + entPhysicalIsFRU = \ + SubtreeMIBEntry('1.16', updater, ValueType.INTEGER, updater.is_fru) diff --git a/src/sonic_ax_impl/mibs/ietf/rfc3433.py b/src/sonic_ax_impl/mibs/ietf/rfc3433.py index 3cdc6ed5f63..610e7f04996 100644 --- a/src/sonic_ax_impl/mibs/ietf/rfc3433.py +++ b/src/sonic_ax_impl/mibs/ietf/rfc3433.py @@ -10,6 +10,8 @@ from sonic_ax_impl import mibs from sonic_ax_impl.mibs import Namespace +from .physical_entity_sub_oid_generator import get_transceiver_sensor_sub_id + @unique class EntitySensorDataType(int, Enum): """ @@ -318,7 +320,7 @@ def update_data(self): raw_sensor_value = transceiver_dom_entry_data.get(sensor_key) sensor = get_transceiver_sensor(sensor_key) - sub_id = mibs.get_transceiver_sensor_sub_id(ifindex, sensor_key) + sub_id = get_transceiver_sensor_sub_id(ifindex, sensor_key) try: mib_values = sensor.mib_values(raw_sensor_value) diff --git a/tests/mock_tables/state_db.json b/tests/mock_tables/state_db.json index 28eb4a0bc55..6b186c16b07 100644 --- a/tests/mock_tables/state_db.json +++ b/tests/mock_tables/state_db.json @@ -4,15 +4,30 @@ }, "PSU_INFO|PSU 1": { "presence": "false", - "status": "false" + "status": "false", + "current": "15.4", + "power": "302.6", + "voltage": "15.1", + "temp": "30.1" }, "PSU_INFO|PSU 2": { "presence": "true", - "status": "true" + "status": "true", + "current": "13.4", + "power": "312.6", + "voltage": "13.1", + "temp": "31.1", + "model": "PSU_MODEL", + "serial": "PSU_SERIAL", + "is_replaceable": "True" }, "PSU_INFO|PSU 3": { "presence": "true", - "status": "false" + "status": "false", + "current": "15.5", + "power": "302.8", + "voltage": "15.5", + "temp": "30.8" }, "CHASSIS_INFO|chassis 1": { "psu_num": "3" @@ -22,7 +37,8 @@ "hardware_rev": "A1", "serial": "SERIAL_NUM", "manufacturer": "VENDOR_NAME", - "model": "MODEL_NAME" + "model": "MODEL_NAME", + "is_replaceable": "True" }, "TRANSCEIVER_DOM_SENSOR|Ethernet0": { "temperature": 25.39, @@ -46,6 +62,47 @@ "MGMT_PORT_TABLE|eth1": { "oper_status": "up" }, + "PHYSICAL_ENTITY_INFO|PSU 1": { + "position_in_parent": 1, + "parent_name": "chassis 1" + }, + "PHYSICAL_ENTITY_INFO|PSU 2": { + "position_in_parent": 2, + "parent_name": "chassis 1" + }, + "PHYSICAL_ENTITY_INFO|PSU 3": { + "position_in_parent": 3, + "parent_name": "chassis 1" + }, + "PHYSICAL_ENTITY_INFO|drawer1": { + "position_in_parent": 1, + "parent_name": "chassis 1" + }, + "PHYSICAL_ENTITY_INFO|fan1": { + "position_in_parent": 1, + "parent_name": "drawer1" + }, + "PHYSICAL_ENTITY_INFO|thermal1": { + "position_in_parent": 1, + "parent_name": "chassis 1" + }, + "FAN_DRAWER_INFO|drawer1": { + "model": "DRAWERMODEL", + "serial": "DRAWERSERIAL", + "presence": "True", + "is_replaceable": "True" + }, + "FAN_INFO|fan1": { + "model": "FANMODEL", + "serial": "FANSERIAL", + "speed": "50", + "presence": "True", + "is_replaceable": "True" + }, + "TEMPERATURE_INFO|thermal1": { + "temperature": "20.5", + "is_replaceable": "False" + }, "NEIGH_STATE_TABLE|10.0.0.57": { "state" : "6402" }, diff --git a/tests/namespace/test_sensor.py b/tests/namespace/test_sensor.py index 5ecaaae84e3..ade3d586470 100644 --- a/tests/namespace/test_sensor.py +++ b/tests/namespace/test_sensor.py @@ -16,6 +16,7 @@ from ax_interface import ValueType from ax_interface.encodings import ObjectIdentifier from ax_interface.constants import PduTypes +from sonic_ax_impl.mibs.ietf.physical_entity_sub_oid_generator import get_transceiver_sub_id, get_transceiver_sensor_sub_id from sonic_ax_impl.mibs.ietf import rfc3433 from sonic_ax_impl.main import SonicMIB @@ -25,8 +26,10 @@ def setUpClass(cls): tests.mock_tables.dbconnector.load_namespace_config() importlib.reload(rfc3433) cls.lut = MIBTable(rfc3433.PhysicalSensorTableMIB) - cls.XCVR_SUB_ID = 1 * 1000 - cls.XCVR_SUB_ID_ASIC1 = 1 * 9000 + cls.IFINDEX = 1 + cls.IFINDEX_ASIC1 = 9 + cls.XCVR_SUB_ID = get_transceiver_sub_id(cls.IFINDEX) + cls.XCVR_SUB_ID_ASIC1 = get_transceiver_sub_id(cls.IFINDEX_ASIC1) cls.XCVR_CHANNELS = (1, 2, 3, 4) # Update MIBs @@ -77,7 +80,7 @@ def test_getpdu_xcvr_temperature_sensor(self): rfc3433.EntitySensorStatus.OK ] - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID + 1, expected_values) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX, 'temperature')[0], expected_values) def test_getpdu_xcvr_temperature_sensor_asic1(self): @@ -93,7 +96,7 @@ def test_getpdu_xcvr_temperature_sensor_asic1(self): rfc3433.EntitySensorStatus.OK ] - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID_ASIC1 + 1, expected_values) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX_ASIC1, 'temperature')[0], expected_values) def test_getpdu_xcvr_voltage_sensor(self): """ @@ -108,7 +111,7 @@ def test_getpdu_xcvr_voltage_sensor(self): rfc3433.EntitySensorStatus.OK ] - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID + 2, expected_values) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX, 'voltage')[0], expected_values) def test_getpdu_xcvr_voltage_sensor_asic1(self): @@ -124,7 +127,7 @@ def test_getpdu_xcvr_voltage_sensor_asic1(self): rfc3433.EntitySensorStatus.OK ] - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID_ASIC1 + 2, expected_values) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX_ASIC1, 'voltage')[0], expected_values) def test_getpdu_xcvr_rx_power_sensor_minus_infinity(self): """ @@ -140,8 +143,7 @@ def test_getpdu_xcvr_rx_power_sensor_minus_infinity(self): rfc3433.EntitySensorStatus.OK ] - channel = 1 - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID + 10 * channel + 1, expected_values) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX, 'rx1power')[0], expected_values) def test_getpdu_xcvr_rx_power_sensor(self): """ @@ -158,7 +160,8 @@ def test_getpdu_xcvr_rx_power_sensor(self): # test for each channel except first, we already test above for channel in (2, 3, 4): - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID + 10 * channel + 1, expected_values) + sensor = 'rx{}power'.format(channel) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX, sensor)[0], expected_values) def test_getpdu_xcvr_tx_power_sensor(self): """ @@ -175,7 +178,8 @@ def test_getpdu_xcvr_tx_power_sensor(self): # test for each channel except first, we already test above for channel in (1, 2, 3, 4): - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID + 10 * channel + 3, expected_values) + sensor = 'tx{}power'.format(channel) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX, sensor)[0], expected_values) def test_getpdu_xcvr_tx_bias_sensor_unknown(self): """ @@ -191,8 +195,7 @@ def test_getpdu_xcvr_tx_bias_sensor_unknown(self): rfc3433.EntitySensorStatus.UNAVAILABLE ] - channel = 1 - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID + 10 * channel + 2, expected_values) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX, 'tx1bias')[0], expected_values) def test_getpdu_xcvr_tx_bias_sensor_overflow(self): """ @@ -208,8 +211,7 @@ def test_getpdu_xcvr_tx_bias_sensor_overflow(self): rfc3433.EntitySensorStatus.OK ] - channel = 3 - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID + 10 * channel + 2, expected_values) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX, 'tx3bias')[0], expected_values) def test_getpdu_xcvr_tx_bias_sensor(self): """ @@ -226,5 +228,6 @@ def test_getpdu_xcvr_tx_bias_sensor(self): # test for each channel for channel in (2, 4): - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID + 10 * channel + 2, expected_values) + sensor = 'tx{}bias'.format(channel) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX, sensor)[0], expected_values) diff --git a/tests/test_sensor.py b/tests/test_sensor.py index 3a694d095c5..e3b649d0b00 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -15,6 +15,7 @@ from ax_interface import ValueType from ax_interface.encodings import ObjectIdentifier from ax_interface.constants import PduTypes +from sonic_ax_impl.mibs.ietf.physical_entity_sub_oid_generator import get_transceiver_sub_id, get_transceiver_sensor_sub_id from sonic_ax_impl.mibs.ietf import rfc3433 from sonic_ax_impl.main import SonicMIB @@ -22,7 +23,8 @@ class TestSonicMIB(TestCase): @classmethod def setUpClass(cls): cls.lut = MIBTable(rfc3433.PhysicalSensorTableMIB) - cls.XCVR_SUB_ID = 1 * 1000 + cls.IFINDEX = 1 + cls.XCVR_SUB_ID = get_transceiver_sub_id(cls.IFINDEX) cls.XCVR_CHANNELS = (1, 2, 3, 4) # Update MIBs @@ -74,7 +76,7 @@ def test_getpdu_xcvr_temperature_sensor(self): rfc3433.EntitySensorStatus.OK ] - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID + 1, expected_values) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX, 'temperature')[0], expected_values) def test_getpdu_xcvr_voltage_sensor(self): """ @@ -89,7 +91,7 @@ def test_getpdu_xcvr_voltage_sensor(self): rfc3433.EntitySensorStatus.OK ] - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID + 2, expected_values) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX, 'voltage')[0], expected_values) def test_getpdu_xcvr_rx_power_sensor_minus_infinity(self): """ @@ -105,8 +107,7 @@ def test_getpdu_xcvr_rx_power_sensor_minus_infinity(self): rfc3433.EntitySensorStatus.OK ] - channel = 1 - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID + 10 * channel + 1, expected_values) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX, 'rx1power')[0], expected_values) def test_getpdu_xcvr_rx_power_sensor(self): """ @@ -123,7 +124,8 @@ def test_getpdu_xcvr_rx_power_sensor(self): # test for each channel except first, we already test above for channel in (2, 3, 4): - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID + 10 * channel + 1, expected_values) + sensor = 'rx{}power'.format(channel) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX, sensor)[0], expected_values) def test_getpdu_xcvr_tx_power_sensor(self): """ @@ -140,7 +142,8 @@ def test_getpdu_xcvr_tx_power_sensor(self): # test for each channel except first, we already test above for channel in (1, 2, 3, 4): - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID + 10 * channel + 3, expected_values) + sensor = 'tx{}power'.format(channel) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX, sensor)[0], expected_values) def test_getpdu_xcvr_tx_bias_sensor_unknown(self): """ @@ -156,8 +159,7 @@ def test_getpdu_xcvr_tx_bias_sensor_unknown(self): rfc3433.EntitySensorStatus.UNAVAILABLE ] - channel = 1 - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID + 10 * channel + 2, expected_values) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX, 'tx1bias')[0], expected_values) def test_getpdu_xcvr_tx_bias_sensor_overflow(self): """ @@ -173,8 +175,7 @@ def test_getpdu_xcvr_tx_bias_sensor_overflow(self): rfc3433.EntitySensorStatus.OK ] - channel = 3 - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID + 10 * channel + 2, expected_values) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX, 'tx3bias')[0], expected_values) def test_getpdu_xcvr_tx_bias_sensor(self): """ @@ -191,5 +192,6 @@ def test_getpdu_xcvr_tx_bias_sensor(self): # test for each channel for channel in (2, 4): - self._test_getpdu_xcvr_sensor(self.XCVR_SUB_ID + 10 * channel + 2, expected_values) + sensor = 'tx{}bias'.format(channel) + self._test_getpdu_xcvr_sensor(get_transceiver_sensor_sub_id(self.IFINDEX, sensor)[0], expected_values) diff --git a/tests/test_sn.py b/tests/test_sn.py index d62df9bcc5b..a30de39aa3e 100644 --- a/tests/test_sn.py +++ b/tests/test_sn.py @@ -15,7 +15,12 @@ from ax_interface import ValueType from ax_interface.encodings import ObjectIdentifier from ax_interface.constants import PduTypes -from sonic_ax_impl.mibs.ietf.rfc2737 import PhysicalClass +from sonic_ax_impl.mibs.ietf.rfc2737 import PhysicalClass, PSU_SENSOR_NAME_MAP, PSU_SENSOR_POSITION_MAP +from sonic_ax_impl.mibs.ietf.physical_entity_sub_oid_generator import CHASSIS_SUB_ID, CHASSIS_MGMT_SUB_ID, PSU_SENSOR_PART_ID_MAP +from sonic_ax_impl.mibs.ietf.physical_entity_sub_oid_generator import get_psu_sensor_sub_id, get_psu_sub_id, get_fan_drawer_sub_id +from sonic_ax_impl.mibs.ietf.physical_entity_sub_oid_generator import get_fan_sub_id, get_fan_tachometers_sub_id +from sonic_ax_impl.mibs.ietf.physical_entity_sub_oid_generator import get_chassis_thermal_sub_id, get_transceiver_sub_id +from sonic_ax_impl.mibs.ietf.physical_entity_sub_oid_generator import get_transceiver_sensor_sub_id from sonic_ax_impl.main import SonicMIB class TestSonicMIB(TestCase): @@ -62,55 +67,154 @@ def test_getnextpdu_chassis_serial_number(self): self.assertEqual(value0.type_, ValueType.OCTET_STRING) self.assertEqual(str(value0.data), "SAMPLETESTSN") + def test_getpdu_chassis_mgmt_info(self): + sub_id = CHASSIS_MGMT_SUB_ID + expected_mib = { + 2: (ValueType.OCTET_STRING, "MGMT"), + 4: (ValueType.INTEGER, 1), + 5: (ValueType.INTEGER, PhysicalClass.CPU), + 6: (ValueType.INTEGER, 1), + 16: (ValueType.INTEGER, 2) + } + + self._check_getpdu(sub_id, expected_mib) + + def test_getpdu_psu_info(self): + sub_id = get_psu_sub_id(2)[0] + expected_mib = { + 2: (ValueType.OCTET_STRING, "PSU 2"), + 4: (ValueType.INTEGER, 1), + 5: (ValueType.INTEGER, PhysicalClass.POWERSUPPLY), + 6: (ValueType.INTEGER, 2), + 7: (ValueType.OCTET_STRING, "PSU 2"), + 8: (ValueType.OCTET_STRING, ""), + 9: (ValueType.OCTET_STRING, ""), + 10: (ValueType.OCTET_STRING, ""), + 11: (ValueType.OCTET_STRING, "PSU_SERIAL"), + 12: (ValueType.OCTET_STRING, ""), + 13: (ValueType.OCTET_STRING, "PSU_MODEL"), + 16: (ValueType.INTEGER, 1) + } + + self._check_getpdu(sub_id, expected_mib) + + def test_getpdu_psu_sensor_info(self): + for sensor_name, oid_offset in PSU_SENSOR_PART_ID_MAP.items(): + self._check_psu_sensor_info(sensor_name, oid_offset) + + def _check_psu_sensor_info(self, sensor_name, oid_offset): + psu_sub_id = get_psu_sub_id(2)[0] + sub_id = get_psu_sensor_sub_id((psu_sub_id, ), sensor_name)[0] + expected_mib = { + 2: (ValueType.OCTET_STRING, "{} for PSU 2".format(PSU_SENSOR_NAME_MAP[sensor_name])), + 4: (ValueType.INTEGER, psu_sub_id), + 5: (ValueType.INTEGER, PhysicalClass.SENSOR), + 6: (ValueType.INTEGER, PSU_SENSOR_POSITION_MAP[sensor_name]), + 7: (ValueType.OCTET_STRING, "{} for PSU 2".format(PSU_SENSOR_NAME_MAP[sensor_name])), + 16: (ValueType.INTEGER, 2) + } + + self._check_getpdu(sub_id, expected_mib) + + def test_getpdu_fan_drawer_info(self): + sub_id = get_fan_drawer_sub_id(1)[0] + expected_mib = { + 2: (ValueType.OCTET_STRING, "drawer1"), + 4: (ValueType.INTEGER, 1), + 5: (ValueType.INTEGER, PhysicalClass.CONTAINER), + 6: (ValueType.INTEGER, 1), + 7: (ValueType.OCTET_STRING, "drawer1"), + 8: (ValueType.OCTET_STRING, ""), + 9: (ValueType.OCTET_STRING, ""), + 10: (ValueType.OCTET_STRING, ""), + 11: (ValueType.OCTET_STRING, "DRAWERSERIAL"), + 12: (ValueType.OCTET_STRING, ""), + 13: (ValueType.OCTET_STRING, "DRAWERMODEL"), + 16: (ValueType.INTEGER, 1) + } + self._check_getpdu(sub_id, expected_mib) + + def test_getpdu_fan_info(self): + drawer_sub_id = get_fan_drawer_sub_id(1) + sub_id = get_fan_sub_id(drawer_sub_id, 1)[0] + expected_mib = { + 2: (ValueType.OCTET_STRING, "fan1"), + 4: (ValueType.INTEGER, drawer_sub_id[0]), + 5: (ValueType.INTEGER, PhysicalClass.FAN), + 6: (ValueType.INTEGER, 1), + 7: (ValueType.OCTET_STRING, "fan1"), + 8: (ValueType.OCTET_STRING, ""), + 9: (ValueType.OCTET_STRING, ""), + 10: (ValueType.OCTET_STRING, ""), + 11: (ValueType.OCTET_STRING, "FANSERIAL"), + 12: (ValueType.OCTET_STRING, ""), + 13: (ValueType.OCTET_STRING, "FANMODEL"), + 16: (ValueType.INTEGER, 1) + } + self._check_getpdu(sub_id, expected_mib) + + def test_getpdu_fan_tachometers_info(self): + drawer_sub_id = get_fan_drawer_sub_id(1) + fan_sub_id = get_fan_sub_id(drawer_sub_id, 1) + sub_id = get_fan_tachometers_sub_id(fan_sub_id)[0] + expected_mib = { + 2: (ValueType.OCTET_STRING, "Tachometers for fan1"), + 4: (ValueType.INTEGER, fan_sub_id[0]), + 5: (ValueType.INTEGER, PhysicalClass.SENSOR), + 6: (ValueType.INTEGER, 1), + 7: (ValueType.OCTET_STRING, "Tachometers for fan1"), + 16: (ValueType.INTEGER, 2) + } + self._check_getpdu(sub_id, expected_mib) + + def test_getpdu_thermal_info(self): + sub_id = get_chassis_thermal_sub_id(1)[0] + expected_mib = { + 2: (ValueType.OCTET_STRING, "thermal1"), + 4: (ValueType.INTEGER, CHASSIS_MGMT_SUB_ID), + 5: (ValueType.INTEGER, PhysicalClass.SENSOR), + 6: (ValueType.INTEGER, 1), + 7: (ValueType.OCTET_STRING, "thermal1"), + 16: (ValueType.INTEGER, 2) + } + self._check_getpdu(sub_id, expected_mib) + def test_getpdu_xcvr_info(self): - sub_id = 1000 * 1 # sub id for Ethernet100 + sub_id = get_transceiver_sub_id(1)[0] expected_mib = { 2: (ValueType.OCTET_STRING, "QSFP+ for etp1"), + 4: (ValueType.INTEGER, CHASSIS_SUB_ID), 5: (ValueType.INTEGER, PhysicalClass.PORT), - 7: (ValueType.OCTET_STRING, ""), # skip + 6: (ValueType.INTEGER, -1), + 7: (ValueType.OCTET_STRING, "Ethernet0"), 8: (ValueType.OCTET_STRING, "A1"), 9: (ValueType.OCTET_STRING, ""), # skip 10: (ValueType.OCTET_STRING, ""), # skip 11: (ValueType.OCTET_STRING, "SERIAL_NUM"), 12: (ValueType.OCTET_STRING, "VENDOR_NAME"), - 13: (ValueType.OCTET_STRING, "MODEL_NAME") + 13: (ValueType.OCTET_STRING, "MODEL_NAME"), + 16: (ValueType.INTEGER, 1) } - oids = [ObjectIdentifier(12, 0, 1, 0, (1, 3, 6, 1, 2, 1, 47, 1, 1, 1, 1, field_sub_id, sub_id)) - for field_sub_id in expected_mib] - - get_pdu = GetNextPDU( - header=PDUHeader(1, PduTypes.GET, 16, 0, 42, 0, 0, 0), - oids=oids - ) - - encoded = get_pdu.encode() - response = get_pdu.make_response(self.lut) - - for mib_key, value in zip(expected_mib, response.values): - expected_oid = ObjectIdentifier(12, 0, 1, 0, (1, 3, 6, 1, 2, 1, 47, 1, 1, 1, 1, mib_key, sub_id)) - expected_type, expected_value = expected_mib[mib_key] - self.assertEqual(str(value.name), str(expected_oid)) - self.assertEqual(value.type_, expected_type) - self.assertEqual(str(value.data), str(expected_value)) + self._check_getpdu(sub_id, expected_mib) def test_getpdu_xcvr_dom(self): expected_mib = { - 1000 * 1 + 1: "DOM Temperature Sensor for etp1", - 1000 * 1 + 2: "DOM Voltage Sensor for etp1", - 1000 * 1 + 11: "DOM RX Power Sensor for etp1/1", - 1000 * 1 + 21: "DOM RX Power Sensor for etp1/2", - 1000 * 1 + 31: "DOM RX Power Sensor for etp1/3", - 1000 * 1 + 41: "DOM RX Power Sensor for etp1/4", - 1000 * 1 + 12: "DOM TX Bias Sensor for etp1/1", - 1000 * 1 + 22: "DOM TX Bias Sensor for etp1/2", - 1000 * 1 + 32: "DOM TX Bias Sensor for etp1/3", - 1000 * 1 + 42: "DOM TX Bias Sensor for etp1/4", - 1000 * 1 + 13: "DOM TX Power Sensor for etp1/1", - 1000 * 1 + 23: "DOM TX Power Sensor for etp1/2", - 1000 * 1 + 33: "DOM TX Power Sensor for etp1/3", - 1000 * 1 + 43: "DOM TX Power Sensor for etp1/4", + get_transceiver_sensor_sub_id(1, 'temperature')[0]: "DOM Temperature Sensor for etp1", + get_transceiver_sensor_sub_id(1, 'voltage')[0]: "DOM Voltage Sensor for etp1", + get_transceiver_sensor_sub_id(1, 'rx1power')[0]: "DOM RX Power Sensor for etp1/1", + get_transceiver_sensor_sub_id(1, 'rx2power')[0]: "DOM RX Power Sensor for etp1/2", + get_transceiver_sensor_sub_id(1, 'rx3power')[0]: "DOM RX Power Sensor for etp1/3", + get_transceiver_sensor_sub_id(1, 'rx4power')[0]: "DOM RX Power Sensor for etp1/4", + get_transceiver_sensor_sub_id(1, 'tx1bias')[0]: "DOM TX Bias Sensor for etp1/1", + get_transceiver_sensor_sub_id(1, 'tx2bias')[0]: "DOM TX Bias Sensor for etp1/2", + get_transceiver_sensor_sub_id(1, 'tx3bias')[0]: "DOM TX Bias Sensor for etp1/3", + get_transceiver_sensor_sub_id(1, 'tx4bias')[0]: "DOM TX Bias Sensor for etp1/4", + get_transceiver_sensor_sub_id(1, 'tx1power')[0]: "DOM TX Power Sensor for etp1/1", + get_transceiver_sensor_sub_id(1, 'tx2power')[0]: "DOM TX Power Sensor for etp1/2", + get_transceiver_sensor_sub_id(1, 'tx3power')[0]: "DOM TX Power Sensor for etp1/3", + get_transceiver_sensor_sub_id(1, 'tx4power')[0]: "DOM TX Power Sensor for etp1/4", } phyDescr, phyClass = 2, 5 @@ -144,3 +248,22 @@ def test_getpdu_xcvr_dom(self): self.assertEqual(value.type_, expected_type) self.assertEqual(str(value.data), str(expected_value)) + def _check_getpdu(self, sub_id, expected_mib): + oids = [ObjectIdentifier(12, 0, 1, 0, (1, 3, 6, 1, 2, 1, 47, 1, 1, 1, 1, field_sub_id, sub_id)) + for field_sub_id in expected_mib] + + get_pdu = GetNextPDU( + header=PDUHeader(1, PduTypes.GET, 16, 0, 42, 0, 0, 0), + oids=oids + ) + + encoded = get_pdu.encode() + response = get_pdu.make_response(self.lut) + + for mib_key, value in zip(expected_mib, response.values): + expected_oid = ObjectIdentifier(12, 0, 1, 0, (1, 3, 6, 1, 2, 1, 47, 1, 1, 1, 1, mib_key, sub_id)) + expected_type, expected_value = expected_mib[mib_key] + self.assertEqual(str(value.name), str(expected_oid)) + self.assertEqual(value.type_, expected_type) + self.assertEqual(str(value.data), str(expected_value)) +