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

PSUd changes to compute power-budget for Modular chassis #104

Merged
merged 4 commits into from
Nov 11, 2020
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
# Compiled code which doesn't end in '.pyc'
sonic-thermalctld/scripts/thermalctldc
sonic-chassisd/scripts/chassisdc
sonic-psud/scripts/psudc

# Unit test / coverage reports
coverage.xml
.coverage
htmlcov/
htmlcov/
2 changes: 2 additions & 0 deletions sonic-psud/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml
154 changes: 151 additions & 3 deletions sonic-psud/scripts/psud
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,21 @@ try:
import sys
import threading

from sonic_py_common import daemon_base
from swsscommon import swsscommon
from sonic_py_common import daemon_base, logger

except ImportError as e:
raise ImportError (str(e) + " - required module not found")

try:
from swsscommon import swsscommon
except ImportError as e:
from tests import mock_swsscommon as swsscommon

try:
from sonic_platform_base.device_base import DeviceBase
except ImportError as e:
from tests.mock_device_base import DeviceBase

#
# Constants ====================================================================
#
Expand All @@ -32,6 +42,12 @@ CHASSIS_INFO_TABLE = 'CHASSIS_INFO'
CHASSIS_INFO_KEY_TEMPLATE = 'chassis {}'
CHASSIS_INFO_PSU_NUM_FIELD = 'psu_num'

CHASSIS_INFO_POWER_CONSUMER_FIELD = 'Consumed Power {}'
CHASSIS_INFO_POWER_SUPPLIER_FIELD = 'Supplied Power {}'
CHASSIS_INFO_TOTAL_POWER_CONSUMED_FIELD = 'Total Consumed Power'
CHASSIS_INFO_TOTAL_POWER_SUPPLIED_FIELD = 'Total Supplied Power'
CHASSIS_INFO_POWER_KEY_TEMPLATE = 'chassis_power_budget {}'

PSU_INFO_TABLE = 'PSU_INFO'
PSU_INFO_KEY_TEMPLATE = 'PSU {}'
PSU_INFO_PRESENCE_FIELD = 'presence'
Expand Down Expand Up @@ -133,8 +149,111 @@ def log_on_status_changed(logger, normal_status, normal_log, abnormal_log):
else:
logger.log_warning(abnormal_log)


#
# PSU Chassis Info ==========================================================
#
class PsuChassisInfo(logger.Logger):

def __init__(self, log_identifier, chassis):
"""
Constructor for PsuChassisInfo
:param chassis: Object representing a platform chassis
"""
super(PsuChassisInfo, self).__init__(log_identifier)

self.chassis = chassis
self.master_status_good = True
self.total_consumed_power = 0.0
self.total_supplied_power = 0.0

def run_power_budget(self, chassis_tbl):
self.total_supplied_power = 0.0
self.total_consumed_power = 0.0
total_supplied_power = 0.0
total_fan_consumed_power = 0.0
total_module_consumed_power = 0.0

dict_index = 0
total_entries_len = 2 #For total supplied and consumed
dict_len = self.chassis.get_num_psus() +\
self.chassis.get_num_fan_drawers() +\
self.chassis.get_num_modules() + \
total_entries_len

fvs = swsscommon.FieldValuePairs(dict_len)

for index, psu in enumerate(self.chassis.get_all_psus()):
mprabhu-nokia marked this conversation as resolved.
Show resolved Hide resolved
presence = try_get(psu.get_presence)
if not presence:
continue

power_good = try_get(psu.get_powergood_status)
if not power_good:
continue

name = try_get(psu.get_name, 'PSU {}'.format(index + 1))
supplied_power = try_get(psu.get_maximum_supplied_power, 0.0)
total_supplied_power = total_supplied_power + supplied_power
fvs[dict_index] = (CHASSIS_INFO_POWER_SUPPLIER_FIELD.format(name), str(supplied_power))
dict_index += 1

for index, power_consumer in enumerate(self.chassis.get_all_fan_drawers()):
presence = try_get(power_consumer.get_presence)
if not presence:
continue

name = try_get(power_consumer.get_name, 'FAN-DRAWER {}'.format(index))
fan_drawer_power = try_get(power_consumer.get_maximum_consumed_power, 0.0)
total_fan_consumed_power = total_fan_consumed_power + fan_drawer_power
fvs[dict_index] = (CHASSIS_INFO_POWER_CONSUMER_FIELD.format(name), str(fan_drawer_power))
dict_index += 1

for index, power_consumer in enumerate(self.chassis.get_all_modules()):
presence = try_get(power_consumer.get_presence)
if not presence:
continue

name = try_get(power_consumer.get_name, 'MODULE {}'.format(index))
module_power = try_get(power_consumer.get_maximum_consumed_power, 0.0)
total_module_consumed_power = total_module_consumed_power + module_power
fvs[dict_index] = (CHASSIS_INFO_POWER_CONSUMER_FIELD.format(name), str(module_power))
dict_index += 1

#Record total supplied and consumed power
self.total_supplied_power = total_supplied_power
self.total_consumed_power = total_fan_consumed_power + total_module_consumed_power

#Record in state DB in chassis table
fvs[dict_index] = (CHASSIS_INFO_TOTAL_POWER_SUPPLIED_FIELD, str(self.total_supplied_power))
fvs[dict_index + 1] = (CHASSIS_INFO_TOTAL_POWER_CONSUMED_FIELD, str(self.total_consumed_power))
chassis_tbl.set(CHASSIS_INFO_POWER_KEY_TEMPLATE.format(1), fvs)

def update_master_status(self):
if not self.total_supplied_power or not self.total_consumed_power:
if self.master_status_good is not True:
self.master_status_good = True
return False

master_status_good = (self.total_consumed_power < self.total_supplied_power)
if master_status_good == self.master_status_good:
return False

self.master_status_good = master_status_good

return True

def _set_psu_master_led(self, master_status):
try:
try:
from sonic_platform.psu import Psu
except ImportError as e:
from tests.mock_platform import MockPsu as Psu

color = DeviceBase.STATUS_LED_COLOR_GREEN if master_status else DeviceBase.STATUS_LED_COLOR_RED
Psu.set_status_master_led(color)
except NotImplementedError as e:
pass

# PSU status ===================================================================
#

Expand Down Expand Up @@ -216,6 +335,7 @@ class DaemonPsud(daemon_base.DaemonBase):
self.stop = threading.Event()
self.psu_status_dict = {}
self.fan_tbl = None
self.psu_chassis_info = None

# Signal handler
def signal_handler(self, sig, frame):
Expand Down Expand Up @@ -263,6 +383,7 @@ class DaemonPsud(daemon_base.DaemonBase):
fvs = swsscommon.FieldValuePairs([(CHASSIS_INFO_PSU_NUM_FIELD, str(psu_num))])
chassis_tbl.set(CHASSIS_INFO_KEY_TEMPLATE.format(1), fvs)


# Start main loop
self.log_info("Start daemon main loop")

Expand All @@ -271,13 +392,18 @@ class DaemonPsud(daemon_base.DaemonBase):
self.update_psu_data(psu_tbl)
self._update_led_color(psu_tbl)

if platform_chassis is not None and platform_chassis.is_modular_chassis():
self.update_psu_chassis_info(chassis_tbl)
self.update_master_led_color(chassis_tbl)

self.log_info("Stop daemon main loop")

# Delete all the information from DB and then exit
for psu_index in range(1, psu_num + 1):
psu_tbl._del(PSU_INFO_KEY_TEMPLATE.format(psu_index))

chassis_tbl._del(CHASSIS_INFO_KEY_TEMPLATE.format(1))
chassis_tbl._del(CHASSIS_INFO_POWER_KEY_TEMPLATE.format(1))

self.log_info("Shutting down...")

Expand Down Expand Up @@ -427,6 +553,28 @@ class DaemonPsud(daemon_base.DaemonBase):
])
self.fan_tbl.set(fan_name, fvs)

def update_psu_chassis_info(self, chassis_tbl):
if not platform_chassis:
return

if not self.psu_chassis_info:
self.psu_chassis_info = PsuChassisInfo(SYSLOG_IDENTIFIER, platform_chassis)

self.psu_chassis_info.run_power_budget(chassis_tbl)

def update_master_led_color(self, chassis_tbl):
if not platform_chassis or not self.psu_chassis_info:
return

psu_chassis_info = self.psu_chassis_info
if psu_chassis_info.update_master_status():
log_on_status_changed(self, psu_chassis_info.master_status_good,
'PSU supplied power warning cleared: supplied power is back to normal.',
'PSU supplied power warning: {}W supplied-power less than {}W consumed-power'.format(
psu_chassis_info.total_supplied_power, psu_chassis_info.total_consumed_power)
)
psu_chassis_info._set_psu_master_led(psu_chassis_info.master_status_good)


#
# Main =========================================================================
Expand Down
2 changes: 2 additions & 0 deletions sonic-psud/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[aliases]
test=pytest
10 changes: 10 additions & 0 deletions sonic-psud/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,21 @@
url='https://github.com/Azure/sonic-platform-daemons',
maintainer='Kevin Wang',
maintainer_email='kevinw@mellanox.com',
packages=[
'tests'
],
scripts=[
'scripts/psud',
],
setup_requires= [
'pytest-runner',
'wheel'
],
tests_require = [
'pytest',
'mock>=2.0.0',
'pytest-cov'
],
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: No Input/Output (Daemon)',
Expand All @@ -29,4 +38,5 @@
'Topic :: System :: Hardware',
],
keywords='sonic SONiC psu PSU daemon psud PSUD',
test_suite='setup.get_test_suite'
)
Empty file added sonic-psud/tests/__init__.py
Empty file.
11 changes: 11 additions & 0 deletions sonic-psud/tests/mock_device_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class DeviceBase():
#Device-types
DEVICE_TYPE_PSU = "PSU"
DEVICE_TYPE_FAN = "FAN"
DEVICE_TYPE_FANDRAWER = "FAN-DRAWER"

#LED colors
STATUS_LED_COLOR_GREEN = "green"
STATUS_LED_COLOR_AMBER = "amber"
STATUS_LED_COLOR_RED = "red"
STATUS_LED_COLOR_OFF = "off"
116 changes: 116 additions & 0 deletions sonic-psud/tests/mock_platform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from .mock_device_base import DeviceBase

class MockDevice:
def __init__(self):
self.name = None
self.presence = True
self.model = 'Module Model'
self.serial = 'Module Serial'

def get_name(self):
return self.name

def set_presence(self, presence):
self.presence = presence

def get_presence(self):
return self.presence

def get_model(self):
return self.model

def get_serial(self):
return self.serial

class MockPsu(MockDevice):

psu_master_led_color = DeviceBase.STATUS_LED_COLOR_OFF

def __init__(self, psu_presence, psu_status, psu_name):
self.name = psu_name
self.presence = True
self.psu_status = psu_status

def get_powergood_status(self):
return self.psu_status

def set_status(self, status):
self.psu_status = status

def set_maximum_supplied_power(self, supplied_power):
self.max_supplied_power = supplied_power

def get_maximum_supplied_power(self):
return self.max_supplied_power

@classmethod
def set_status_master_led(cls, color):
cls.psu_master_led_color = color

@classmethod
def get_status_master_led(cls):
return cls.psu_master_led_color

class MockFanDrawer(MockDevice):
def __init__(self, fan_drawer_presence, fan_drawer_status, fan_drawer_name):
self.name = fan_drawer_name
self.presence = True
self.fan_drawer_status = fan_drawer_status

def get_status(self):
return self.fan_drawer_status

def set_status(self, status):
self.fan_drawer_status = status

def set_maximum_consumed_power(self, consumed_power):
self.max_consumed_power = consumed_power

def get_maximum_consumed_power(self):
return self.max_consumed_power

class MockModule(MockDevice):
def __init__(self, module_presence, module_status, module_name):
self.name = module_name
self.presence = True
self.module_status = module_status

def get_status(self):
return self.module_status

def set_status(self, status):
self.module_status = status

def set_maximum_consumed_power(self, consumed_power):
self.max_consumed_power = consumed_power

def get_maximum_consumed_power(self):
return self.max_consumed_power

class MockChassis:

def __init__(self):
self.psu_list = []
self.fan_drawer_list = []
self.module_list = []

def get_num_psus(self):
return len(self.psu_list)

def get_all_psus(self):
return self.psu_list

def get_psu(self, index):
return self.psu_list[index]

def get_num_fan_drawers(self):
return len(self.fan_drawer_list)

def get_all_fan_drawers(self):
return self.fan_drawer_list

def get_num_modules(self):
return len(self.module_list)

def get_all_modules(self):
return self.module_list
Loading