Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added logic to get PSU info directly from the state DB #101

Merged
merged 3 commits into from
Apr 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/sonic_ax_impl/mibs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@

redis_kwargs = {'unix_socket_path': '/var/run/redis/redis.sock'}

def chassis_info_table(chassis_name):
"""
:param: chassis_name: chassis name
:return: chassis info entry for this chassis
"""

return "CHASSIS_INFO" + TABLE_NAME_SEPARATOR_VBAR + chassis_name

def psu_info_table(psu_name):
"""
:param: psu_name: psu name
:return: psu info entry for this psu
"""

return "PSU_INFO" + TABLE_NAME_SEPARATOR_VBAR + psu_name

def counter_table(sai_id):
"""
:param if_name: given sai_id to cast.
Expand Down
132 changes: 90 additions & 42 deletions src/sonic_ax_impl/mibs/vendor/cisco/ciscoEntityFruControlMIB.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,48 @@
import imp
import re
import sys

from enum import Enum, unique
from sonic_ax_impl import mibs
from ax_interface import MIBMeta, ValueType, MIBUpdater, MIBEntry, SubtreeMIBEntry
from ax_interface.encodings import ObjectIdentifier
from ax_interface import MIBMeta, ValueType, SubtreeMIBEntry
from swsssdk import SonicV2Connector

CHASSIS_INFO_KEY_TEMPLATE = 'chassis {}'
PSU_INFO_KEY_TEMPLATE = 'PSU {}'

PSU_PRESENCE_OK = 'true'
PSU_STATUS_OK = 'true'

@unique
class CHASSISInfoDB(bytes, Enum):
"""
CHASSIS info keys
"""

PSU_NUM = b"psu_num"

@unique
class PSUInfoDB(bytes, Enum):
"""
PSU info keys
"""

PRESENCE = b"presence"
STATUS = b"status"

def get_chassis_data(chassis_info):
"""
:param chassis_info: chassis info dict
:return: tuple (psu_num) of chassis;
Empty string if field not in chassis_info
"""

return tuple(chassis_info.get(chassis_field.value, b"").decode() for chassis_field in CHASSISInfoDB)

def get_psu_data(psu_info):
"""
:param psu_info: psu info dict
:return: tuple (presence, status) of psu;
Empty string if field not in psu_info
"""

PSU_PLUGIN_MODULE_NAME = 'psuutil'
PSU_PLUGIN_MODULE_PATH = "/usr/share/sonic/platform/plugins/{}.py".format(PSU_PLUGIN_MODULE_NAME)
PSU_PLUGIN_CLASS_NAME = 'PsuUtil'
return tuple(psu_info.get(psu_field.value, b"").decode() for psu_field in PSUInfoDB)

class PowerStatusHandler:
"""
Expand All @@ -18,38 +52,57 @@ def __init__(self):
"""
init the handler
"""
self.psuutil = None
self.statedb = SonicV2Connector()
self.statedb.connect(self.statedb.STATE_DB)

try:
module = imp.load_source(PSU_PLUGIN_MODULE_NAME, PSU_PLUGIN_MODULE_PATH)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load_source [](start = 25, length = 11)

Since we don't need the module 'psutil', can we remove it from setup.py?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@qiluo-msft Unfortunately we can't remove it from setup.py

Summary:
From psutil:

psutil (process and system utilities) is a cross-platform library for retrieving information on running processes and system utilization (CPU, memory, disks, network, sensors) in Python.

Usage:
From sonic_ax_impl:

# From the psutil documentation https://pythonhosted.org/psutil/#psutil.cpu_percent:
#
#    Warning the first time this function is called
#    with interval = 0.0 or None it will return a
#    meaningless 0.0 value which you are supposed
#    to ignore.
psutil.cpu_percent()
# '...is recommended for accuracy that this function be called with at least 0.1 seconds between calls.'
time.sleep(0.1)
# a sliding window of 60 contiguous 5 sec utilization (up to five minutes)
self.cpuutils = collections.deque([psutil.cpu_percent()], maxlen=60)
self.system_virtual_memory = psutil.virtual_memory()

except ImportError as e:
mibs.logger.error("Failed to load PSU module '%s': %s" % (PSU_PLUGIN_MODULE_NAME, str(e)), True)
return
except FileNotFoundError as e:
mibs.logger.error("Failed to get platform specific PSU module '%s': %s" % (PSU_PLUGIN_MODULE_NAME, str(e)), True)
return
def _get_num_psus(self):
"""
Get PSU number
:return: the number of supported PSU
"""
chassis_name = CHASSIS_INFO_KEY_TEMPLATE.format(1)
chassis_info = self.statedb.get_all(self.statedb.STATE_DB, mibs.chassis_info_table(chassis_name))
num_psus = get_chassis_data(chassis_info)

try:
platform_psuutil_class = getattr(module, PSU_PLUGIN_CLASS_NAME)
self.psuutil = platform_psuutil_class()
except AttributeError as e:
mibs.logger.error("Failed to instantiate '%s' class: %s" % (PLATFORM_SPECIFIC_CLASS_NAME, str(e)), True)
return int(num_psus[0])

def _get_psu_presence(self, psu_index):
"""
Get PSU presence
:return: the presence of particular PSU
"""
psu_name = PSU_INFO_KEY_TEMPLATE.format(psu_index)
psu_info = self.statedb.get_all(self.statedb.STATE_DB, mibs.psu_info_table(psu_name))
presence, status = get_psu_data(psu_info)

return presence == PSU_PRESENCE_OK

def _getPsuIndex(self, sub_id):
def _get_psu_status(self, psu_index):
"""
Get PSU status
:return: the status of particular PSU
"""
psu_name = PSU_INFO_KEY_TEMPLATE.format(psu_index)
psu_info = self.statedb.get_all(self.statedb.STATE_DB, mibs.psu_info_table(psu_name))
presence, status = get_psu_data(psu_info)

return status == PSU_STATUS_OK

def _get_psu_index(self, sub_id):
"""
Get the PSU index from sub_id
:return: the index of supported PSU
"""
if not self.psuutil or not sub_id or len(sub_id) > 1:
if not sub_id or len(sub_id) > 1:
return None

psu_index = int(sub_id[0])

try:
num_psus = self.psuutil.get_num_psus()
num_psus = self._get_num_psus()
except Exception:
# Any unexpected exception or error, log it and keep running
mibs.logger.exception("PowerStatusHandler._getPsuIndex() caught an unexpected exception during get_num_psus()")
mibs.logger.exception("PowerStatusHandler._get_psu_index() caught an unexpected exception during _get_num_psus()")
return None

if psu_index < 1 or psu_index > num_psus:
Expand All @@ -62,27 +115,23 @@ def get_next(self, sub_id):
:param sub_id: The 1-based snmp sub-identifier query.
:return: the next sub id.
"""
if not self.psuutil:
return None

if not sub_id:
return (1,)

psu_index = self._getPsuIndex(sub_id)
psu_index = self._get_psu_index(sub_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_get_psu_index [](start = 25, length = 14)

get_next is nothrow function. However, if the StateDB is not as good as you expect, there will be exception.

This is a sample:

  File "/usr/local/lib/python3.6/dist-packages/sonic_ax_impl/mibs/vendor/cisco/ciscoEntityFruControlMIB.py", line 102, in _get_psu_index
    num_psus = self._get_num_psus()
  File "/usr/local/lib/python3.6/dist-packages/sonic_ax_impl/mibs/vendor/cisco/ciscoEntityFruControlMIB.py", line 65, in _get_num_psus
    num_psus = get_chassis_data(chassis_info)
  File "/usr/local/lib/python3.6/dist-packages/sonic_ax_impl/mibs/vendor/cisco/ciscoEntityFruControlMIB.py", line 36, in get_chassis_data
    return tuple(chassis_info.get(chassis_field.value, b"").decode() for chassis_field in CHASSISInfoDB)
  File "/usr/local/lib/python3.6/dist-packages/sonic_ax_impl/mibs/vendor/cisco/ciscoEntityFruControlMIB.py", line 36, in <genexpr>
    return tuple(chassis_info.get(chassis_field.value, b"").decode() for chassis_field in CHASSISInfoDB)
AttributeError: 'NoneType' object has no attribute 'get'

try:
num_psus = self.psuutil.get_num_psus()
num_psus = self._get_num_psus()
except Exception:
# Any unexpected exception or error, log it and keep running
mibs.logger.exception("PowerStatusHandler.get_next() caught an unexpected exception during get_num_psus()")
mibs.logger.exception("PowerStatusHandler.get_next() caught an unexpected exception during _get_num_psus()")
return None


if psu_index and psu_index + 1 <= num_psus:
return (psu_index + 1,)

return None

def getPsuStatus(self, sub_id):
def get_psu_status(self, sub_id):
"""
:param sub_id: The 1-based sub-identifier query.
:return: the status of requested PSU according to cefcModuleOperStatus ModuleOperType
Expand All @@ -91,24 +140,24 @@ def getPsuStatus(self, sub_id):
8 - the module is provisioned, but it is missing. This is a failure state.
:ref: https://www.cisco.com/c/en/us/td/docs/switches/wan/mgx/mgx_8850/software/mgx_r2-0-10/pxm/reference/guide/pxm/cscoent.html
"""
psu_index = self._getPsuIndex(sub_id)
psu_index = self._get_psu_index(sub_id)

if not psu_index:
return None

try:
psu_presence = self.psuutil.get_psu_presence(psu_index)
psu_presence = self._get_psu_presence(psu_index)
except Exception:
# Any unexpected exception or error, log it and keep running
mibs.logger.exception("PowerStatusHandler.getPsuStatus() caught an unexpected exception during get_psu_presence()")
mibs.logger.exception("PowerStatusHandler.get_psu_status() caught an unexpected exception during _get_psu_presence()")
return None

if psu_presence:
try:
psu_status = self.psuutil.get_psu_status(psu_index)
psu_status = self._get_psu_status(psu_index)
except Exception:
# Any unexpected exception or error, log it and keep running
mibs.logger.exception("PowerStatusHandler.getPsuStatus() caught an unexpected exception during get_psu_status()")
mibs.logger.exception("PowerStatusHandler.get_psu_status() caught an unexpected exception during _get_psu_status()")
return None

if psu_status:
Expand All @@ -118,7 +167,6 @@ def getPsuStatus(self, sub_id):
else:
return 8


class cefcFruPowerStatusTable(metaclass=MIBMeta, prefix='.1.3.6.1.4.1.9.9.117.1.1.2'):
"""
'cefcFruPowerStatusTable' http://oidref.com/1.3.6.1.4.1.9.9.117.1.1.2
Expand All @@ -129,4 +177,4 @@ class cefcFruPowerStatusTable(metaclass=MIBMeta, prefix='.1.3.6.1.4.1.9.9.117.1.
# cefcFruPowerStatusTable = '1.3.6.1.4.1.9.9.117.1.1.2'
# csqIfQosGroupStatsEntry = '1.3.6.1.4.1.9.9.117.1.1.2.1'

psu_status = SubtreeMIBEntry('1.2', power_status_handler, ValueType.INTEGER, power_status_handler.getPsuStatus)
psu_status = SubtreeMIBEntry('1.2', power_status_handler, ValueType.INTEGER, power_status_handler.get_psu_status)
1 change: 0 additions & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
import tests.mock_tables.imp
15 changes: 0 additions & 15 deletions tests/mock_tables/imp.py

This file was deleted.

53 changes: 34 additions & 19 deletions tests/mock_tables/state_db.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,43 @@
"DEVICE_METADATA|localhost": {
"chassis_serial_number": "SAMPLETESTSN"
},
"PSU_INFO|PSU 1": {
"presence": "false",
"status": "false"
},
"PSU_INFO|PSU 2": {
"presence": "true",
"status": "true"
},
"PSU_INFO|PSU 3": {
"presence": "true",
"status": "false"
},
"CHASSIS_INFO|chassis 1": {
"psu_num": "3"
},
"TRANSCEIVER_INFO|Ethernet0": {
"type": "QSFP+",
"hardwarerev" : "A1",
"serialnum": "SERIAL_NUM",
"manufacturename": "VENDOR_NAME",
"modelname": "MODEL_NAME"
"type": "QSFP+",
"hardwarerev" : "A1",
"serialnum": "SERIAL_NUM",
"manufacturename": "VENDOR_NAME",
"modelname": "MODEL_NAME"
},
"TRANSCEIVER_DOM_SENSOR|Ethernet0": {
"temperature": 25.39,
"voltage": 3.37,
"tx1bias": "N/A",
"tx2bias": 4.44,
"tx3bias": "inf",
"tx4bias": 4.44,
"rx1power": "-inf",
"rx2power": -0.97,
"rx3power": -0.97,
"rx4power": -0.97,
"tx1power": -5.4,
"tx2power": -5.4,
"tx3power": -5.4,
"tx4power": -5.4
"temperature": 25.39,
"voltage": 3.37,
"tx1bias": "N/A",
"tx2bias": 4.44,
"tx3bias": "inf",
"tx4bias": 4.44,
"rx1power": "-inf",
"rx2power": -0.97,
"rx3power": -0.97,
"rx4power": -0.97,
"tx1power": -5.4,
"tx2power": -5.4,
"tx3power": -5.4,
"tx4power": -5.4
},
"MGMT_PORT_TABLE|eth0": {
"oper_status": "down"
Expand Down
62 changes: 0 additions & 62 deletions tests/plugins/psuutil.py

This file was deleted.