Skip to content

Commit

Permalink
[Thermalctld] Update thermal info to CHASSIS_STATE_DB (sonic-net#101)
Browse files Browse the repository at this point in the history
Enhance thermalctld to write to chassis state-DB on a modular chassis

HLD: sonic-net/SONiC#646

In a modular chassis, the thermal information from all line-cards
will be updated to the chassis state-DB in the control-card.

Additionally, minimum and maximum temperatures will be recorded.
The fan control algorithm used by certain vendors will require
this information.
  • Loading branch information
mprabhu-nokia authored Nov 11, 2020
1 parent 05c79de commit 8c2a5cc
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 3 deletions.
20 changes: 20 additions & 0 deletions sonic-thermalctld/scripts/thermalctld
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ except ImportError as e:

SYSLOG_IDENTIFIER = 'thermalctld'
NOT_AVAILABLE = 'N/A'
INVALID_SLOT = -1

# utility functions

Expand Down Expand Up @@ -462,6 +463,15 @@ class TemperatureUpdater(logger.Logger):
self.temperature_status_dict = {}
state_db = daemon_base.db_connect("STATE_DB")
self.table = swsscommon.Table(state_db, TemperatureUpdater.TEMPER_INFO_TABLE_NAME)
self.chassis_table = None

self.is_chassis_system = chassis.is_modular_chassis()
if self.is_chassis_system:
my_slot = try_get(chassis.get_my_slot, INVALID_SLOT)
if my_slot != INVALID_SLOT:
table_name = TemperatureUpdater.TEMPER_INFO_TABLE_NAME+'_'+str(my_slot)
chassis_state_db = daemon_base.db_connect("CHASSIS_STATE_DB")
self.chassis_table = swsscommon.Table(chassis_state_db, table_name)

def deinit(self):
"""
Expand All @@ -470,6 +480,8 @@ class TemperatureUpdater(logger.Logger):
"""
for name in self.temperature_status_dict.keys():
self.table._del(name)
if self.is_chassis_system and self.chassis_table is not None:
self.chassis_table._del(name)

def _log_on_status_changed(self, normal_status, normal_log, abnormal_log):
"""
Expand Down Expand Up @@ -515,9 +527,13 @@ class TemperatureUpdater(logger.Logger):
low_threshold = NOT_AVAILABLE
high_critical_threshold = NOT_AVAILABLE
low_critical_threshold = NOT_AVAILABLE
maximum_temperature = NOT_AVAILABLE
minimum_temperature = NOT_AVAILABLE
temperature = try_get(thermal.get_temperature)
if temperature != NOT_AVAILABLE:
temperature_status.set_temperature(name, temperature)
minimum_temperature = try_get(thermal.get_minimum_recorded)
maximum_temperature = try_get(thermal.get_maximum_recorded)
high_threshold = try_get(thermal.get_high_threshold)
low_threshold = try_get(thermal.get_low_threshold)
high_critical_threshold = try_get(thermal.get_high_critical_threshold)
Expand All @@ -544,6 +560,8 @@ class TemperatureUpdater(logger.Logger):

fvs = swsscommon.FieldValuePairs(
[('temperature', str(temperature)),
('minimum_temperature', str(minimum_temperature)),
('maximum_temperature', str(maximum_temperature)),
('high_threshold', str(high_threshold)),
('low_threshold', str(low_threshold)),
('warning_status', str(warning)),
Expand All @@ -553,6 +571,8 @@ class TemperatureUpdater(logger.Logger):
])

self.table.set(name, fvs)
if self.is_chassis_system and self.chassis_table is not None:
self.chassis_table.set(name, fvs)


class ThermalMonitor(ProcessTaskBase):
Expand Down
23 changes: 23 additions & 0 deletions sonic-thermalctld/tests/mock_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ class MockThermal:
def __init__(self):
self.name = None
self.temperature = 2
self.minimum_temperature = 1
self.maximum_temperature = 5
self.high_threshold = 3
self.low_threshold = 1
self.high_critical_threshold = 4
Expand All @@ -115,6 +117,12 @@ def get_name(self):
def get_temperature(self):
return self.temperature

def get_minimum_recorded(self):
return self.minimum_temperature

def get_maximum_recorded(self):
return self.maximum_temperature

def get_high_threshold(self):
return self.high_threshold

Expand Down Expand Up @@ -154,6 +162,7 @@ def __init__(self):
self.psu_list = []
self.thermal_list = []
self.fan_drawer_list = []
self.is_chassis_system = False

def get_all_fans(self):
return self.fan_list
Expand All @@ -167,6 +176,9 @@ def get_all_thermals(self):
def get_all_fan_drawers(self):
return self.fan_drawer_list

def get_num_thermals(self):
return len(self.thermal_list)

def make_absence_fan(self):
fan = MockFan()
fan.presence = False
Expand Down Expand Up @@ -220,3 +232,14 @@ def make_error_thermal(self):
thermal = MockErrorThermal()
self.thermal_list.append(thermal)

def is_modular_chassis(self):
return self.is_chassis_system

def set_modular_chassis(self, is_true):
self.is_chassis_system = is_true

def set_my_slot(self, my_slot):
self.my_slot = my_slot

def get_my_slot(self):
return self.my_slot
17 changes: 14 additions & 3 deletions sonic-thermalctld/tests/mock_swsscommon.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
STATE_DB = ''

CHASSIS_STATE_DB = ''

class Table:
def __init__(self, db, table_name):
self.table_name = table_name
self.mock_dict = {}

def _del(self, key):
del self.mock_dict[key]
pass

def set(self, key, fvs):
self.mock_dict[key] = fvs.fv_dict
pass

def get(self, key):
if key in self.mock_dict:
return self.mock_dict[key]
return None

def get_size(self):
return (len(self.mock_dict))

class FieldValuePairs:
def __init__(self, fvs):
pass
def __init__(self, fvs):
self.fv_dict = dict(fvs)
pass
56 changes: 56 additions & 0 deletions sonic-thermalctld/tests/test_thermalctld.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
load_source('thermalctld', scripts_path + '/thermalctld')
from thermalctld import *

TEMPER_INFO_TABLE_NAME = 'TEMPERATURE_INFO'

def setup_function():
FanStatus.log_notice = MagicMock()
Expand Down Expand Up @@ -283,3 +284,58 @@ def test_update_thermal_with_exception():
temperature_updater = TemperatureUpdater(SYSLOG_IDENTIFIER, chassis)
temperature_updater.update()
temperature_updater.log_warning.assert_called()

#Modular chassis related tests
def test_updater_thermal_check_modular_chassis():
chassis = MockChassis()
assert chassis.is_modular_chassis() == False

temperature_updater = TemperatureUpdater(SYSLOG_IDENTIFIER, chassis)
assert temperature_updater.chassis_table == None

chassis.set_modular_chassis(True)
chassis.set_my_slot(-1)
temperature_updater = TemperatureUpdater(SYSLOG_IDENTIFIER, chassis)
assert temperature_updater.chassis_table == None

my_slot = 1
chassis.set_my_slot(my_slot)
temperature_updater = TemperatureUpdater(SYSLOG_IDENTIFIER, chassis)
assert temperature_updater.chassis_table != None
assert temperature_updater.chassis_table.table_name == TEMPER_INFO_TABLE_NAME+'_'+str(my_slot)

def test_updater_thermal_check_chassis_table():
chassis = MockChassis()

thermal1 = MockThermal()
chassis.get_all_thermals().append(thermal1)

chassis.set_modular_chassis(True)
chassis.set_my_slot(1)
temperature_updater = TemperatureUpdater(SYSLOG_IDENTIFIER, chassis)

temperature_updater.update()
assert temperature_updater.chassis_table.get_size() == chassis.get_num_thermals()

thermal2 = MockThermal()
chassis.get_all_thermals().append(thermal2)
temperature_updater.update()
assert temperature_updater.chassis_table.get_size() == chassis.get_num_thermals()

temperature_updater.deinit()
assert temperature_updater.chassis_table.get_size() == 0

def test_updater_thermal_check_min_max():
chassis = MockChassis()

thermal = MockThermal()
chassis.get_all_thermals().append(thermal)

chassis.set_modular_chassis(True)
chassis.set_my_slot(1)
temperature_updater = TemperatureUpdater(SYSLOG_IDENTIFIER, chassis)

temperature_updater.update()
slot_dict = temperature_updater.chassis_table.get('Thermal 1')
assert slot_dict['minimum_temperature'] == str(thermal.get_minimum_recorded())
assert slot_dict['maximum_temperature'] == str(thermal.get_maximum_recorded())

0 comments on commit 8c2a5cc

Please sign in to comment.