From 314d29df7d364fdf390105d205199f6302856418 Mon Sep 17 00:00:00 2001 From: Junchao-Mellanox <57339448+Junchao-Mellanox@users.noreply.github.com> Date: Fri, 29 Nov 2019 11:30:49 +0800 Subject: [PATCH 01/13] DeviceBase.get_name should raise NotImplementedError like other member method --- sonic_platform_base/device_base.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sonic_platform_base/device_base.py b/sonic_platform_base/device_base.py index a18764eb1..73511d311 100644 --- a/sonic_platform_base/device_base.py +++ b/sonic_platform_base/device_base.py @@ -18,6 +18,8 @@ def get_name(self): Returns: string: The name of the device """ + raise NotImplementedError + def get_presence(self): """ From 265a8d9b6d5e8bd14f21ee0c178ff3686aefbec2 Mon Sep 17 00:00:00 2001 From: Junchao-Mellanox <57339448+Junchao-Mellanox@users.noreply.github.com> Date: Thu, 12 Dec 2019 10:04:01 +0800 Subject: [PATCH 02/13] Add thermal policy control --- setup.py | 1 + sonic_platform_base/chassis_base.py | 9 ++ .../sonic_thermal_control/__init__.py | 0 .../thermal_action_base.py | 72 +++++++++ .../thermal_condition_base.py | 70 ++++++++ .../thermal_info_base.py | 68 ++++++++ .../thermal_manager_base.py | 153 ++++++++++++++++++ .../sonic_thermal_control/thermal_policy.py | 74 +++++++++ 8 files changed, 447 insertions(+) create mode 100644 sonic_platform_base/sonic_thermal_control/__init__.py create mode 100644 sonic_platform_base/sonic_thermal_control/thermal_action_base.py create mode 100644 sonic_platform_base/sonic_thermal_control/thermal_condition_base.py create mode 100644 sonic_platform_base/sonic_thermal_control/thermal_info_base.py create mode 100644 sonic_platform_base/sonic_thermal_control/thermal_manager_base.py create mode 100644 sonic_platform_base/sonic_thermal_control/thermal_policy.py diff --git a/setup.py b/setup.py index 22e8a2dd8..92a09a45b 100644 --- a/setup.py +++ b/setup.py @@ -19,6 +19,7 @@ 'sonic_platform_base.sonic_ssd', 'sonic_psu', 'sonic_sfp', + 'sonic_thermal_control' ], classifiers=[ 'Development Status :: 3 - Alpha', diff --git a/sonic_platform_base/chassis_base.py b/sonic_platform_base/chassis_base.py index eb1e089b5..17fba3b05 100644 --- a/sonic_platform_base/chassis_base.py +++ b/sonic_platform_base/chassis_base.py @@ -333,6 +333,15 @@ def get_thermal(self, index): return thermal + def get_thermal_manager(self): + """ + Retrieves thermal manager class on this chassis + :return: A class derived from ThermalManagerBase representing the + specified thermal manager. ThermalManagerBase is returned as default + """ + from .sonic_thermal_control.thermal_manager_base import ThermalManagerBase + return ThermalManagerBase + ############################################## # SFP methods ############################################## diff --git a/sonic_platform_base/sonic_thermal_control/__init__.py b/sonic_platform_base/sonic_thermal_control/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sonic_platform_base/sonic_thermal_control/thermal_action_base.py b/sonic_platform_base/sonic_thermal_control/thermal_action_base.py new file mode 100644 index 000000000..89c684dd2 --- /dev/null +++ b/sonic_platform_base/sonic_thermal_control/thermal_action_base.py @@ -0,0 +1,72 @@ +class ThermalActionBase(object): + """ + Base class for thermal action. Once all thermal conditions in a thermal policy are matched, + all predefined thermal action would be executed. + """ + # JSON field definition + JSON_FIELD_ACTION_TYPE = 'type' + + # Dictionary of ThermalActionBase-derived class representing all thermal action types + _action_type_dict = {} + + def execute(self, thermal_info_dict): + """ + Take action when thermal condition matches. For example, adjust speed of fan or shut + down the switch. + :param thermal_info_dict: A dictionary stores all thermal information. + :return: + """ + raise NotImplementedError + + def load_from_json(self, json_obj): + """ + Initialize this object by a json object. The json object is read from policy.json section 'actions'. + Derived class can define any field in policy.json and interpret them in this function. + :param json_obj: A json object representing an action. + :return: + """ + pass + + @classmethod + def register_type(cls, type_name, action_type): + """ + Register a concrete action class by type name. The concrete action class must derive from + ThermalActionBase or have exactly the same member function 'execute' and 'load_from_json'. + For any concrete action class, it must be registered explicitly. + :param type_name: Type name of the action class which corresponding to the 'type' field of + an action in policy.json. + :param action_type: A concrete action class. + :return: + """ + if type_name not in cls._action_type_dict: + cls._action_type_dict[type_name] = action_type + else: + raise KeyError('ThermalAction type {} already exists'.format(type_name)) + + @classmethod + def get_type(cls, json_obj): + """ + Get a concrete action class by json object. The json object represents a action object and must + have a 'type' field. This function returns a pre-registered concrete action class if the specific + 'type' is found. + :param json_obj: A json object representing an action. + :return: A concrete action class if requested type exists; Otherwise None. + """ + if ThermalActionBase.JSON_FIELD_ACTION_TYPE in json_obj: + type_str = json_obj[ThermalActionBase.JSON_FIELD_ACTION_TYPE] + return cls._action_type_dict[type_str] if type_str in cls._action_type_dict else None + + return None + + +def thermal_action(type_name): + """ + Decorator to auto register a ThermalActionBase-derived class + :param type_name: Type name of the action class which corresponding to the 'type' field of + a action in policy.json. + :return: Wrapper function + """ + def wrapper(action_type): + ThermalActionBase.register_type(type_name, action_type) + return action_type + return wrapper diff --git a/sonic_platform_base/sonic_thermal_control/thermal_condition_base.py b/sonic_platform_base/sonic_thermal_control/thermal_condition_base.py new file mode 100644 index 000000000..a4f5f1361 --- /dev/null +++ b/sonic_platform_base/sonic_thermal_control/thermal_condition_base.py @@ -0,0 +1,70 @@ +class ThermalConditionBase(object): + """ + Base class for thermal condition + """ + # JSON field definition + JSON_FIELD_CONDITION_TYPE = 'type' + + # Dictionary of ThermalConditionBase-derived class representing all thermal condition types. + _condition_type_dict = {} + + def is_match(self, thermal_info_dict): + """ + Indicate if this condition is matched. + :param thermal_info_dict: A dictionary stores all thermal information. + :return: True if condition matched else False. + """ + raise NotImplementedError + + def load_from_json(self, json_obj): + """ + Initialize this object by a json object. The json object is read from policy.json section 'conditions'. + Derived class can define any field in policy.json and interpret them in this function. + :param json_obj: A json object representing a condition. + :return: + """ + pass + + @classmethod + def get_type(cls, json_obj): + """ + Get a concrete condition class by json object. The json object represents a condition object and must + have a 'type' field. This function returns a pre-registered concrete condition class if the specific + 'type' is found. + :param json_obj: A json object representing a condition. + :return: A concrete condition class if requested type exists; Otherwise None. + """ + if ThermalConditionBase.JSON_FIELD_CONDITION_TYPE in json_obj: + type_name = json_obj[ThermalConditionBase.JSON_FIELD_CONDITION_TYPE] + return cls._condition_type_dict[type_name] if type_name in cls._condition_type_dict else None + + return None + + @classmethod + def register_type(cls, type_name, condition_type): + """ + Register a concrete condition class by type name. The concrete condition class must derive from + ThermalConditionBase or have exactly the same member function 'is_match' and 'load_from_json'. + For any concrete condition class, it must be registered explicitly. + :param type_name: Type name of the condition class which corresponding to the 'type' field of + a condition in policy.json. + :param condition_type: A concrete condition class. + :return: + """ + if type_name not in cls._condition_type_dict: + cls._condition_type_dict[type_name] = condition_type + else: + raise KeyError('ThermalCondition type {} already exists'.format(type_name)) + + +def thermal_condition(type_name): + """ + Decorator to auto register a ThermalConditionBase-derived class + :param type_name: Type name of the condition class which corresponding to the 'type' field of + a condition in policy.json. + :return: Wrapper function + """ + def wrapper(condition_type): + ThermalConditionBase.register_type(type_name, condition_type) + return condition_type + return wrapper diff --git a/sonic_platform_base/sonic_thermal_control/thermal_info_base.py b/sonic_platform_base/sonic_thermal_control/thermal_info_base.py new file mode 100644 index 000000000..257fe02d6 --- /dev/null +++ b/sonic_platform_base/sonic_thermal_control/thermal_info_base.py @@ -0,0 +1,68 @@ +class ThermalInfoBase(object): + """ + Base class for thermal information + """ + # JSON field definition + JSON_FIELD_INFO_TYPE = 'type' + + # Dictionary of ThermalActionBase-derived class representing all thermal action types + _info_type_dict = {} + + def collect(self, chassis): + """ + Collect thermal information for thermal policy. + :param chassis: The chassis object. + :return: + """ + raise NotImplementedError + + def load_from_json(self, json_obj): + """ + Initialize this object by a json object. The json object is read from policy.json section 'info_types'. + Derived class can define any field in policy.json and interpret them in this function. + :param json_obj: A json object representing an thermal information. + :return: + """ + pass + + @classmethod + def register_type(cls, type_name, info_type): + """ + Register a concrete information class by type name. The concrete information class must derive from + ThermalInfoBase or have exactly the same member function 'collect' + For any concrete information class, it must be registered explicitly. + :param type_name: Type name of the information class. + :param info_type: A concrete information class. + :return: + """ + if type_name not in cls._info_type_dict: + cls._info_type_dict[type_name] = info_type + else: + raise KeyError('ThermalInfo type {} already exists'.format(type_name)) + + @classmethod + def get_type(cls, json_obj): + """ + Get a concrete information class by json object. The json object represents an information object and must + have a 'type' field. This function returns a pre-registered concrete information class if the specific + 'type' is found. + :param json_obj: A json object representing an information. + :return: A concrete information class if requested type exists; Otherwise None. + """ + if ThermalInfoBase.JSON_FIELD_INFO_TYPE in json_obj: + type_str = json_obj[ThermalInfoBase.JSON_FIELD_INFO_TYPE] + return cls._info_type_dict[type_str] if type_str in cls._info_type_dict else None + + return None + + +def thermal_info(type_name): + """ + Decorator to auto register a ThermalInfoBase-derived class + :param type_name: Type name of the information class + :return: Wrapper function + """ + def wrapper(info_type): + ThermalInfoBase.register_type(type_name, info_type) + return info_type + return wrapper diff --git a/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py b/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py new file mode 100644 index 000000000..9bab86ac3 --- /dev/null +++ b/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py @@ -0,0 +1,153 @@ +import json +from .thermal_policy import ThermalPolicy +from .thermal_info_base import ThermalInfoBase + + +class ThermalManagerBase(object): + """ + Base class of ThermalManager representing a manager to control all thermal policies. + """ + # JSON field definition. + JSON_FIELD_POLICIES = 'policies' + JSON_FIELD_INFO_TYPES = 'info_types' + JSON_FIELD_POLICY_NAME = 'name' + + # Dictionary of ThermalPolicy objects. + _policy_dict = {} + + # Dictionary of thermal information objects. A thermal information object is used by Thermal Policy + _thermal_info_dict = {} + + @classmethod + def initialize(cls): + """ + Initialize thermal manager, including register thermal condition types and thermal action types + and any other vendor specific initialization. + :return: + """ + pass + + @classmethod + def start_thermal_control_algorithm(cls): + """ + Start vendor specific thermal control algorithm. The default behavior of this function is a no-op. + :return: + """ + pass + + @classmethod + def stop_thermal_control_algorithm(cls): + """ + Stop vendor specific thermal control algorithm. The default behavior of this function is a no-op. + :return: + """ + pass + + @classmethod + def load(cls, policy_file_name): + """ + Load all thermal policies from policy.json file. An example looks like: + { + "info_types": [ + { + "type": "fan_info" # collect fan information for each iteration + }, + { + "type": "psu_info" # collect psu information for each iteration + } + ], + "policies": [ + { + "name": "any fan absence", # if any fan absence, set all fan speed to 100% and disable thermal control algorithm + "conditions": [ + { + "type": "fan.any.absence" # see sonic-platform-daemons.sonic-thermalctld.thermal_policy.thermal_conditions + } + ], + "actions": [ + { + "type": "fan.all.set_speed", # see sonic-platform-daemons.sonic-thermalctld.thermal_policy.thermal_actions + "speed": "100" + }, + { + "type": "thermal_control.control", + "status": "false" + } + ] + }, + { + "name": "all fan absence", # if all fan absence, shutdown the switch + "conditions": [ + { + "type": "fan.all.absence" + } + ], + "actions": [ + { + "type": "switch.shutdown" + } + ] + } + ] + } + :param policy_file_name: Path of policy.json. + :return: + """ + with open(policy_file_name, 'r') as policy_file: + json_obj = json.load(policy_file) + if cls.JSON_FIELD_POLICIES in json_obj: + json_policies = json_obj[cls.JSON_FIELD_POLICIES] + for json_policy in json_policies: + cls.load_policy(json_policy) + + if cls.JSON_FIELD_INFO_TYPES in json_obj: + for json_info in json_obj[cls.JSON_FIELD_INFO_TYPES]: + info_type = ThermalInfoBase.get_type(json_info) + if info_type: + cond_obj = info_type() + cls._thermal_info_dict[json_info[ThermalInfoBase.JSON_FIELD_INFO_TYPE]] = cond_obj + else: + raise KeyError('Invalid thermal information defined in policy file') + + @classmethod + def load_policy(cls, json_policy): + """ + Load a policy object from a JSON object. + :param json_policy: A JSON object representing a thermal policy. + :return: + """ + if cls.JSON_FIELD_POLICY_NAME in json_policy: + name = json_policy[cls.JSON_FIELD_POLICY_NAME] + if name in cls._policy_dict: + raise KeyError('Policy {} already exists'.format(name)) + + policy = ThermalPolicy() + policy.load_from_json(json_policy) + cls._policy_dict[name] = policy + else: + raise KeyError('{} not found in policy'.format(cls.JSON_FIELD_POLICY_NAME)) + + @classmethod + def run_policy(cls): + """ + Collect thermal information, run each policy, if one policy matches, execute the policy's action. + :return: + """ + if not cls._policy_dict: + return + + cls.collect_thermal_information() + + for policy in cls._policy_dict.values(): + if policy.is_match(cls._thermal_info_dict): + policy.do_action(cls._thermal_info_dict) + + @classmethod + def collect_thermal_information(cls, chassis): + """ + Collect thermal information. This function will be called before run_policy. + :param chassis: The chassis object. + :return: + """ + for thermal_info in cls._thermal_info_dict.values(): + thermal_info.collect(chassis) diff --git a/sonic_platform_base/sonic_thermal_control/thermal_policy.py b/sonic_platform_base/sonic_thermal_control/thermal_policy.py new file mode 100644 index 000000000..a641566aa --- /dev/null +++ b/sonic_platform_base/sonic_thermal_control/thermal_policy.py @@ -0,0 +1,74 @@ +from .thermal_action_base import ThermalActionBase +from .thermal_condition_base import ThermalConditionBase + + +class ThermalPolicy(object): + """ + Class representing a thermal policy. A thermal policy object is initialized by policy.json. + """ + # JSON field definition. + JSON_FIELD_NAME = 'name' + JSON_FIELD_CONDITIONS = 'conditions' + JSON_FIELD_ACTIONS = 'actions' + + def __init__(self): + # Name of the policy + self.name = None + + # Conditions load from policy.json + self.conditions = [] + + # Actions load from policy.json + self.actions = [] + + def load_from_json(self, json_obj): + """ + Load thermal policy from policy.json. + :param json_obj: A json object representing a thermal policy. + :return: + """ + if self.JSON_FIELD_NAME in json_obj: + self.name = json_obj[self.JSON_FIELD_NAME] + + if self.JSON_FIELD_CONDITIONS in json_obj: + for json_condition in json_obj[self.JSON_FIELD_CONDITIONS]: + cond_type = ThermalConditionBase.get_type(json_condition) + if cond_type: + cond_obj = cond_type() + cond_obj.from_json(json_condition) + self.conditions.append(cond_obj) + else: + raise KeyError('Invalid thermal condition defined in policy file') + + if self.JSON_FIELD_ACTIONS in json_obj: + for json_action in json_obj[self.JSON_FIELD_ACTIONS]: + action_type = ThermalActionBase.get_type(json_action) + if action_type: + action_obj = action_type() + action_obj.from_json(json_action) + self.actions.append(action_obj) + else: + raise KeyError('Invalid thermal action defined in policy file') + else: + raise KeyError('name field not found in policy') + + def is_match(self, thermal_info_dict): + """ + Indicate if this policy is match. + :param thermal_info_dict: A dictionary stores all thermal information. + :return: True if all conditions matches else False. + """ + for condition in self.conditions: + if not condition.is_match(thermal_info_dict): + return False + + return True + + def do_action(self, thermal_info_dict): + """ + Execute all actions if is_match returns True. + :param thermal_info_dict: A dictionary stores all thermal information. + :return: + """ + for action in self.actions: + action.execute(thermal_info_dict) From c3b9a7329cea7b3755421bd167473c47e97d1e56 Mon Sep 17 00:00:00 2001 From: Junchao-Mellanox <57339448+Junchao-Mellanox@users.noreply.github.com> Date: Fri, 13 Dec 2019 17:52:04 +0800 Subject: [PATCH 03/13] add thermal information to thermal manager --- setup.py | 2 +- .../thermal_manager_base.py | 23 +++++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/setup.py b/setup.py index 92a09a45b..5c4e1350e 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ 'sonic_platform_base.sonic_ssd', 'sonic_psu', 'sonic_sfp', - 'sonic_thermal_control' + 'sonic_platform_base.sonic_thermal_control' ], classifiers=[ 'Development Status :: 3 - Alpha', diff --git a/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py b/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py index 9bab86ac3..3d9431d9e 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py @@ -22,7 +22,16 @@ class ThermalManagerBase(object): def initialize(cls): """ Initialize thermal manager, including register thermal condition types and thermal action types - and any other vendor specific initialization. + and any other vendor specific initialization. The default behavior of this function is a no-op. + :return: + """ + pass + + @classmethod + def destroy(cls): + """ + Destroy thermal manager, including any vendor specific cleanup. The default behavior of this function + is a no-op. :return: """ pass @@ -98,19 +107,19 @@ def load(cls, policy_file_name): if cls.JSON_FIELD_POLICIES in json_obj: json_policies = json_obj[cls.JSON_FIELD_POLICIES] for json_policy in json_policies: - cls.load_policy(json_policy) + cls._load_policy(json_policy) if cls.JSON_FIELD_INFO_TYPES in json_obj: for json_info in json_obj[cls.JSON_FIELD_INFO_TYPES]: info_type = ThermalInfoBase.get_type(json_info) if info_type: - cond_obj = info_type() - cls._thermal_info_dict[json_info[ThermalInfoBase.JSON_FIELD_INFO_TYPE]] = cond_obj + info_obj = info_type() + cls._thermal_info_dict[json_info[ThermalInfoBase.JSON_FIELD_INFO_TYPE]] = info_obj else: raise KeyError('Invalid thermal information defined in policy file') @classmethod - def load_policy(cls, json_policy): + def _load_policy(cls, json_policy): """ Load a policy object from a JSON object. :param json_policy: A JSON object representing a thermal policy. @@ -136,14 +145,14 @@ def run_policy(cls): if not cls._policy_dict: return - cls.collect_thermal_information() + cls._collect_thermal_information() for policy in cls._policy_dict.values(): if policy.is_match(cls._thermal_info_dict): policy.do_action(cls._thermal_info_dict) @classmethod - def collect_thermal_information(cls, chassis): + def _collect_thermal_information(cls, chassis): """ Collect thermal information. This function will be called before run_policy. :param chassis: The chassis object. From 3314a1235ca65c9e5ded8d74af7c51b8a979f9db Mon Sep 17 00:00:00 2001 From: junchao Date: Mon, 16 Dec 2019 16:30:29 +0800 Subject: [PATCH 04/13] Fix issues found in manual test --- .../sonic_thermal_control/thermal_actions.py | 101 ++++++++++++++++ .../thermal_conditions.py | 73 ++++++++++++ .../sonic_thermal_control/thermal_infos.py | 109 ++++++++++++++++++ .../thermal_manager_base.py | 10 +- .../sonic_thermal_control/thermal_policy.py | 4 +- 5 files changed, 292 insertions(+), 5 deletions(-) create mode 100644 sonic_platform_base/sonic_thermal_control/thermal_actions.py create mode 100644 sonic_platform_base/sonic_thermal_control/thermal_conditions.py create mode 100644 sonic_platform_base/sonic_thermal_control/thermal_infos.py diff --git a/sonic_platform_base/sonic_thermal_control/thermal_actions.py b/sonic_platform_base/sonic_thermal_control/thermal_actions.py new file mode 100644 index 000000000..4c68d8970 --- /dev/null +++ b/sonic_platform_base/sonic_thermal_control/thermal_actions.py @@ -0,0 +1,101 @@ +from .thermal_action_base import ThermalActionBase, thermal_action + + +class SetFanSpeedAction(ThermalActionBase): + """ + Base thermal action class to set speed for fans + """ + # JSON field definition + JSON_FIELD_SPEED = 'speed' + + def __init__(self): + """ + Constructor of SetFanSpeedAction which actually do nothing. + """ + self.speed = None + + def load_from_json(self, json_obj): + """ + Construct ControlThermalControlAlgoAction via JSON. JSON example: + { + "type": "fan.all.set_speed" + "speed": "100" + } + :param json_obj: A JSON object representing a SetFanSpeedAction action. + :return: + """ + if SetFanSpeedAction.JSON_FIELD_SPEED in json_obj: + self.speed = float(json_obj[SetFanSpeedAction.JSON_FIELD_SPEED]) + else: + raise ValueError('SetFanSpeedAction missing mandatory field {} in policy.json'. + format(SetFanSpeedAction.JSON_FIELD_SPEED)) + + +@thermal_action('fan.all.set_speed') +class SetAllFanSpeedAction(SetFanSpeedAction): + """ + Action to set speed for all fans + """ + def execute(self, thermal_info_dict): + """ + Set speed for all fans + :param thermal_info_dict: A dictionary stores all thermal information. + :return: + """ + from .thermal_infos import FanInfo + if FanInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[FanInfo.INFO_NAME], FanInfo): + fan_info_obj = thermal_info_dict[FanInfo.INFO_NAME] + for fan in fan_info_obj.get_present_fans(): + fan.set_speed(self.speed) + + +@thermal_action('thermal_control.control') +class ControlThermalControlAlgoAction(ThermalActionBase): + """ + Action to control the thermal control algorithm + """ + # JSON field definition + JSON_FIELD_STATUS = 'status' + + def __init__(self): + self.status = True + + def load_from_json(self, json_obj): + """ + Construct ControlThermalControlAlgoAction via JSON. JSON example: + { + "type": "thermal_control.control" + "status": "true" + } + :param json_obj: A JSON object representing a ControlThermalControlAlgoAction action. + :return: + """ + if ControlThermalControlAlgoAction.JSON_FIELD_STATUS in json_obj: + status_str = json_obj[ControlThermalControlAlgoAction.JSON_FIELD_STATUS].lower() + if status_str == 'true': + self.status = True + elif status_str == 'false': + self.status = False + else: + raise ValueError('Invalid {} field value, please specify true of false'. + format(ControlThermalControlAlgoAction.JSON_FIELD_STATUS)) + else: + raise ValueError('ControlThermalControlAlgoAction ' + 'missing mandatory field {} in policy.json'. + format(ControlThermalControlAlgoAction.JSON_FIELD_STATUS)) + + def execute(self, thermal_info_dict): + """ + Disable thermal control algorithm + :param thermal_info_dict: A dictionary stores all thermal information. + :return: + """ + import sonic_platform.platform + chassis = sonic_platform.platform.Platform().get_chassis() + thermal_manager = chassis.get_thermal_manager() + if self.status: + thermal_manager.start_thermal_control_algorithm() + else: + thermal_manager.stop_thermal_control_algorithm() + + diff --git a/sonic_platform_base/sonic_thermal_control/thermal_conditions.py b/sonic_platform_base/sonic_thermal_control/thermal_conditions.py new file mode 100644 index 000000000..cec6855a1 --- /dev/null +++ b/sonic_platform_base/sonic_thermal_control/thermal_conditions.py @@ -0,0 +1,73 @@ +from .thermal_condition_base import ThermalConditionBase, thermal_condition + + +@thermal_condition('fan.any.absence') +class AnyFanAbsenceCondition(ThermalConditionBase): + def is_match(self, thermal_info_dict): + from .thermal_infos import FanInfo + if FanInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[FanInfo.INFO_NAME], FanInfo): + fan_info_obj = thermal_info_dict[FanInfo.INFO_NAME] + if not fan_info_obj.is_status_changed(): + return False + return len(fan_info_obj.get_absence_fans()) > 0 + return False + + +@thermal_condition('fan.all.absence') +class AllFanAbsenceCondition(ThermalConditionBase): + def is_match(self, thermal_info_dict): + from .thermal_infos import FanInfo + if FanInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[FanInfo.INFO_NAME], FanInfo): + fan_info_obj = thermal_info_dict[FanInfo.INFO_NAME] + if not fan_info_obj.is_status_changed(): + return False + return len(fan_info_obj.get_presence_fans()) == 0 + return False + + +@thermal_condition('fan.all.presence') +class AllFanPresenceCondition(ThermalConditionBase): + def is_match(self, thermal_info_dict): + from .thermal_infos import FanInfo + if FanInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[FanInfo.INFO_NAME], FanInfo): + fan_info_obj = thermal_info_dict[FanInfo.INFO_NAME] + if not fan_info_obj.is_status_changed(): + return False + return len(fan_info_obj.get_absence_fans()) == 0 + return False + + +@thermal_condition('psu.any.absence') +class AnyPsuAbsenceCondition(ThermalConditionBase): + def is_match(self, thermal_info_dict): + from .thermal_infos import PsuInfo + if PsuInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[PsuInfo.INFO_NAME], PsuInfo): + psu_info_obj = thermal_info_dict[PsuInfo.INFO_NAME] + if not psu_info_obj.is_status_changed(): + return False + return len(psu_info_obj.get_absence_psus()) > 0 + return False + + +@thermal_condition('psu.all.absence') +class AllPsuAbsenceCondition(ThermalConditionBase): + def is_match(self, thermal_info_dict): + from .thermal_infos import PsuInfo + if PsuInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[PsuInfo.INFO_NAME], PsuInfo): + psu_info_obj = thermal_info_dict[PsuInfo.INFO_NAME] + if not psu_info_obj.is_status_changed(): + return False + return len(psu_info_obj.get_presence_psus()) == 0 + return False + + +@thermal_condition('psu.all.presence') +class AllFanPresenceCondition(ThermalConditionBase): + def is_match(self, thermal_info_dict): + from .thermal_infos import PsuInfo + if PsuInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[PsuInfo.INFO_NAME], PsuInfo): + psu_info_obj = thermal_info_dict[PsuInfo.INFO_NAME] + if not psu_info_obj.is_status_changed(): + return False + return len(psu_info_obj.get_absence_psus()) == 0 + return False diff --git a/sonic_platform_base/sonic_thermal_control/thermal_infos.py b/sonic_platform_base/sonic_thermal_control/thermal_infos.py new file mode 100644 index 000000000..72352c3ef --- /dev/null +++ b/sonic_platform_base/sonic_thermal_control/thermal_infos.py @@ -0,0 +1,109 @@ +from .thermal_info_base import ThermalInfoBase, thermal_info + + +@thermal_info('fan_info') +class FanInfo(ThermalInfoBase): + """ + Fan information needed by thermal policy + """ + + # Fan information name + INFO_NAME = 'fan_info' + + def __init__(self): + self._absence_fans = set() + self._presence_fans = set() + self._status_changed = False + + def collect(self, chassis): + """ + Collect absence and presence fans. + :param chassis: The chassis object + :return: + """ + self._status_changed = False + for fan in chassis.get_all_fans(): + if fan.get_presence() and fan not in self._presence_fans: + self._presence_fans.add(fan) + self._status_changed = True + if fan in self._absence_fans: + self._absence_fans.remove(fan) + elif not fan.get_presence() and fan not in self._absence_fans: + self._absence_fans.add(fan) + self._status_changed = True + if fan in self._presence_fans: + self._presence_fans.remove(fan) + + def get_absence_fans(self): + """ + Retrieves absence fans + :return: A set of absence fans + """ + return self._absence_fans + + def get_presence_fans(self): + """ + Retrieves presence fans + :return: A set of presence fans + """ + return self._presence_fans + + def is_status_changed(self): + """ + Retrieves if the status of fan information changed + :return: True if status changed else False + """ + return self._status_changed + + +@thermal_info('psu_info') +class PsuInfo(ThermalInfoBase): + """ + PSU information needed by thermal policy + """ + INFO_NAME = 'psu_info' + + def __init__(self): + self._absence_psus = set() + self._presence_psus = set() + self._status_changed = False + + def collect(self, chassis): + """ + Collect absence and presence PSUs. + :param chassis: The chassis object + :return: + """ + self._status_changed = False + for psu in chassis.get_all_psus(): + if psu.get_presence() and psu not in self._presence_psus: + self._presence_psus.add(psu) + self._status_changed = True + if psu in self._absence_psus: + self._absence_psus.remove(psu) + elif not psu.get_presence() and psu not in self._absence_psus: + self._absence_psus.add(psu) + self._status_changed = True + if psu in self._presence_psus: + self._presence_psus.remove(psu) + + def get_absence_psus(self): + """ + Retrieves presence PSUs + :return: A set of absence PSUs + """ + return self._absence_psus + + def get_presence_psus(self): + """ + Retrieves presence PSUs + :return: A set of presence fans + """ + return self._presence_psus + + def is_status_changed(self): + """ + Retrieves if the status of PSU information changed + :return: True if status changed else False + """ + return self._status_changed diff --git a/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py b/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py index 3d9431d9e..733343c6c 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py @@ -1,6 +1,9 @@ import json from .thermal_policy import ThermalPolicy from .thermal_info_base import ThermalInfoBase +from .thermal_actions import * +from .thermal_conditions import * +from .thermal_infos import * class ThermalManagerBase(object): @@ -22,7 +25,7 @@ class ThermalManagerBase(object): def initialize(cls): """ Initialize thermal manager, including register thermal condition types and thermal action types - and any other vendor specific initialization. The default behavior of this function is a no-op. + and any other vendor specific initialization. :return: """ pass @@ -137,15 +140,16 @@ def _load_policy(cls, json_policy): raise KeyError('{} not found in policy'.format(cls.JSON_FIELD_POLICY_NAME)) @classmethod - def run_policy(cls): + def run_policy(cls, chassis): """ Collect thermal information, run each policy, if one policy matches, execute the policy's action. + :param chassis: The chassis object. :return: """ if not cls._policy_dict: return - cls._collect_thermal_information() + cls._collect_thermal_information(chassis) for policy in cls._policy_dict.values(): if policy.is_match(cls._thermal_info_dict): diff --git a/sonic_platform_base/sonic_thermal_control/thermal_policy.py b/sonic_platform_base/sonic_thermal_control/thermal_policy.py index a641566aa..975b3fd1d 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_policy.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_policy.py @@ -35,7 +35,7 @@ def load_from_json(self, json_obj): cond_type = ThermalConditionBase.get_type(json_condition) if cond_type: cond_obj = cond_type() - cond_obj.from_json(json_condition) + cond_obj.load_from_json(json_condition) self.conditions.append(cond_obj) else: raise KeyError('Invalid thermal condition defined in policy file') @@ -45,7 +45,7 @@ def load_from_json(self, json_obj): action_type = ThermalActionBase.get_type(json_action) if action_type: action_obj = action_type() - action_obj.from_json(json_action) + action_obj.load_from_json(json_action) self.actions.append(action_obj) else: raise KeyError('Invalid thermal action defined in policy file') From 1825ea9eabcca5d35d69476e831099d4b1b26831 Mon Sep 17 00:00:00 2001 From: junchao Date: Wed, 18 Dec 2019 09:52:43 +0800 Subject: [PATCH 05/13] 1.move default thermal policy impl here due to dependency reason; 2.add unit test for thermal policy --- pytest.ini | 3 + setup.cfg | 2 + setup.py | 11 +- .../sonic_thermal_control/.gitignore | 2 + .../sonic_thermal_control/thermal_actions.py | 18 +-- .../thermal_conditions.py | 12 -- .../sonic_thermal_control/thermal_infos.py | 26 +++++ tests/.gitignore | 2 + tests/__init__.py | 0 tests/mock_platform.py | 44 ++++++++ tests/test_thermal_policy.py | 104 ++++++++++++++++++ tests/thermal_policy.json | 68 ++++++++++++ 12 files changed, 271 insertions(+), 21 deletions(-) create mode 100644 pytest.ini create mode 100644 setup.cfg create mode 100644 sonic_platform_base/sonic_thermal_control/.gitignore create mode 100644 tests/.gitignore create mode 100644 tests/__init__.py create mode 100644 tests/mock_platform.py create mode 100644 tests/test_thermal_policy.py create mode 100644 tests/thermal_policy.json diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 000000000..c24fe5bb9 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +filterwarnings = + ignore::DeprecationWarning diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..9af7e6f11 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[aliases] +test=pytest \ No newline at end of file diff --git a/setup.py b/setup.py index 5c4e1350e..35ef8c36b 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,15 @@ 'sonic_platform_base.sonic_ssd', 'sonic_psu', 'sonic_sfp', - 'sonic_platform_base.sonic_thermal_control' + 'sonic_platform_base.sonic_thermal_control', + 'tests' + ], + setup_requires= [ + 'pytest-runner' + ], + tests_require = [ + 'pytest', + 'mock>=2.0.0' ], classifiers=[ 'Development Status :: 3 - Alpha', @@ -35,4 +43,5 @@ 'Topic :: Utilities', ], keywords='sonic SONiC platform hardware interface api API', + test_suite='setup.get_test_suite' ) diff --git a/sonic_platform_base/sonic_thermal_control/.gitignore b/sonic_platform_base/sonic_thermal_control/.gitignore new file mode 100644 index 000000000..4ec183879 --- /dev/null +++ b/sonic_platform_base/sonic_thermal_control/.gitignore @@ -0,0 +1,2 @@ +*.pyc +*/__pycache__/ \ No newline at end of file diff --git a/sonic_platform_base/sonic_thermal_control/thermal_actions.py b/sonic_platform_base/sonic_thermal_control/thermal_actions.py index 4c68d8970..c4ec8a1f0 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_actions.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_actions.py @@ -45,7 +45,7 @@ def execute(self, thermal_info_dict): from .thermal_infos import FanInfo if FanInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[FanInfo.INFO_NAME], FanInfo): fan_info_obj = thermal_info_dict[FanInfo.INFO_NAME] - for fan in fan_info_obj.get_present_fans(): + for fan in fan_info_obj.get_presence_fans(): fan.set_speed(self.speed) @@ -90,12 +90,14 @@ def execute(self, thermal_info_dict): :param thermal_info_dict: A dictionary stores all thermal information. :return: """ - import sonic_platform.platform - chassis = sonic_platform.platform.Platform().get_chassis() - thermal_manager = chassis.get_thermal_manager() - if self.status: - thermal_manager.start_thermal_control_algorithm() - else: - thermal_manager.stop_thermal_control_algorithm() + from .thermal_infos import ChassisInfo + if ChassisInfo.INFO_NAME in thermal_info_dict: + chassis_info_obj = thermal_info_dict[ChassisInfo.INFO_NAME] + chassis = chassis_info_obj.get_chassis() + thermal_manager = chassis.get_thermal_manager() + if self.status: + thermal_manager.start_thermal_control_algorithm() + else: + thermal_manager.stop_thermal_control_algorithm() diff --git a/sonic_platform_base/sonic_thermal_control/thermal_conditions.py b/sonic_platform_base/sonic_thermal_control/thermal_conditions.py index cec6855a1..826bd39fb 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_conditions.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_conditions.py @@ -7,8 +7,6 @@ def is_match(self, thermal_info_dict): from .thermal_infos import FanInfo if FanInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[FanInfo.INFO_NAME], FanInfo): fan_info_obj = thermal_info_dict[FanInfo.INFO_NAME] - if not fan_info_obj.is_status_changed(): - return False return len(fan_info_obj.get_absence_fans()) > 0 return False @@ -19,8 +17,6 @@ def is_match(self, thermal_info_dict): from .thermal_infos import FanInfo if FanInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[FanInfo.INFO_NAME], FanInfo): fan_info_obj = thermal_info_dict[FanInfo.INFO_NAME] - if not fan_info_obj.is_status_changed(): - return False return len(fan_info_obj.get_presence_fans()) == 0 return False @@ -31,8 +27,6 @@ def is_match(self, thermal_info_dict): from .thermal_infos import FanInfo if FanInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[FanInfo.INFO_NAME], FanInfo): fan_info_obj = thermal_info_dict[FanInfo.INFO_NAME] - if not fan_info_obj.is_status_changed(): - return False return len(fan_info_obj.get_absence_fans()) == 0 return False @@ -43,8 +37,6 @@ def is_match(self, thermal_info_dict): from .thermal_infos import PsuInfo if PsuInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[PsuInfo.INFO_NAME], PsuInfo): psu_info_obj = thermal_info_dict[PsuInfo.INFO_NAME] - if not psu_info_obj.is_status_changed(): - return False return len(psu_info_obj.get_absence_psus()) > 0 return False @@ -55,8 +47,6 @@ def is_match(self, thermal_info_dict): from .thermal_infos import PsuInfo if PsuInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[PsuInfo.INFO_NAME], PsuInfo): psu_info_obj = thermal_info_dict[PsuInfo.INFO_NAME] - if not psu_info_obj.is_status_changed(): - return False return len(psu_info_obj.get_presence_psus()) == 0 return False @@ -67,7 +57,5 @@ def is_match(self, thermal_info_dict): from .thermal_infos import PsuInfo if PsuInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[PsuInfo.INFO_NAME], PsuInfo): psu_info_obj = thermal_info_dict[PsuInfo.INFO_NAME] - if not psu_info_obj.is_status_changed(): - return False return len(psu_info_obj.get_absence_psus()) == 0 return False diff --git a/sonic_platform_base/sonic_thermal_control/thermal_infos.py b/sonic_platform_base/sonic_thermal_control/thermal_infos.py index 72352c3ef..86c23a7a7 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_infos.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_infos.py @@ -107,3 +107,29 @@ def is_status_changed(self): :return: True if status changed else False """ return self._status_changed + + +@thermal_info('chassis_info') +class ChassisInfo(ThermalInfoBase): + """ + Chassis information needed by thermal policy + """ + INFO_NAME = 'chassis_info' + + def __init__(self): + self._chassis = None + + def collect(self, chassis): + """ + Collect platform chassis. + :param chassis: The chassis object + :return: + """ + self._chassis = chassis + + def get_chassis(self): + """ + Retrieves platform chassis object + :return: A platform chassis object. + """ + return self._chassis diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 000000000..4ec183879 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,2 @@ +*.pyc +*/__pycache__/ \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/mock_platform.py b/tests/mock_platform.py new file mode 100644 index 000000000..b7ce49993 --- /dev/null +++ b/tests/mock_platform.py @@ -0,0 +1,44 @@ +class MockFan: + def __init__(self): + self.presence = True + self.speed = 60 + + def get_presence(self): + return self.presence + + def set_speed(self, speed): + self.speed = speed + + +class MockPsu: + def __init__(self): + self.presence = True + + def get_presence(self): + return self.presence + + +class MockChassis: + def __init__(self): + self.fan_list = [] + self.psu_list = [] + + def get_all_psus(self): + return self.psu_list + + def get_all_fans(self): + return self.fan_list + + def get_thermal_manager(self): + from sonic_platform_base.sonic_thermal_control.thermal_manager_base import ThermalManagerBase + return ThermalManagerBase + + def make_fan_absence(self): + fan = MockFan() + fan.presence = False + self.fan_list.append(fan) + + def make_psu_absence(self): + psu = MockPsu() + psu.presence = False + self.psu_list.append(psu) diff --git a/tests/test_thermal_policy.py b/tests/test_thermal_policy.py new file mode 100644 index 000000000..8724eb0ee --- /dev/null +++ b/tests/test_thermal_policy.py @@ -0,0 +1,104 @@ +import os +import sys +import pytest +from mock import MagicMock +from .mock_platform import MockChassis, MockFan + +test_path = os.path.dirname(os.path.abspath(__file__)) +modules_path = os.path.dirname(test_path) +sys.path.insert(0, modules_path) + +from sonic_platform_base.sonic_thermal_control.thermal_manager_base import ThermalManagerBase +from sonic_platform_base.sonic_thermal_control.thermal_infos import FanInfo, PsuInfo + + +@pytest.fixture(scope='session', autouse=True) +def thermal_manager(): + policy_file = os.path.join(test_path, 'thermal_policy.json') + ThermalManagerBase.load(policy_file) + + return ThermalManagerBase + + +def test_load_policy(thermal_manager): + assert 'psu_info' in thermal_manager._thermal_info_dict + assert 'fan_info' in thermal_manager._thermal_info_dict + assert 'chassis_info' in thermal_manager._thermal_info_dict + + assert 'any fan absence' in thermal_manager._policy_dict + assert 'any psu absence' in thermal_manager._policy_dict + assert 'all fan and psu presence' in thermal_manager._policy_dict + + +def test_fan_info(): + chassis = MockChassis() + chassis.make_fan_absence() + fan_info = FanInfo() + fan_info.collect(chassis) + assert len(fan_info.get_absence_fans()) == 1 + assert len(fan_info.get_presence_fans()) == 0 + assert fan_info.is_status_changed() + + fan_list = chassis.get_all_fans() + fan_list[0].presence = True + fan_info.collect(chassis) + assert len(fan_info.get_absence_fans()) == 0 + assert len(fan_info.get_presence_fans()) == 1 + assert fan_info.is_status_changed() + + +def test_psu_info(): + chassis = MockChassis() + chassis.make_psu_absence() + psu_info = PsuInfo() + psu_info.collect(chassis) + assert len(psu_info.get_absence_psus()) == 1 + assert len(psu_info.get_presence_psus()) == 0 + assert psu_info.is_status_changed() + + psu_list = chassis.get_all_psus() + psu_list[0].presence = True + psu_info.collect(chassis) + assert len(psu_info.get_absence_psus()) == 0 + assert len(psu_info.get_presence_psus()) == 1 + assert psu_info.is_status_changed() + + +def test_fan_policy(thermal_manager): + chassis = MockChassis() + chassis.make_fan_absence() + chassis.fan_list.append(MockFan()) + thermal_manager.start_thermal_control_algorithm = MagicMock() + thermal_manager.stop_thermal_control_algorithm = MagicMock() + thermal_manager.run_policy(chassis) + + fan_list = chassis.get_all_fans() + assert fan_list[1].speed == 100 + thermal_manager.stop_thermal_control_algorithm.assert_called_once() + + fan_list[0].presence = True + thermal_manager.run_policy(chassis) + thermal_manager.start_thermal_control_algorithm.assert_called_once() + + +def test_psu_policy(thermal_manager): + chassis = MockChassis() + chassis.make_psu_absence() + chassis.fan_list.append(MockFan()) + thermal_manager.start_thermal_control_algorithm = MagicMock() + thermal_manager.stop_thermal_control_algorithm = MagicMock() + thermal_manager.run_policy(chassis) + + fan_list = chassis.get_all_fans() + assert fan_list[0].speed == 100 + thermal_manager.stop_thermal_control_algorithm.assert_called_once() + + psu_list = chassis.get_all_psus() + psu_list[0].presence = True + thermal_manager.run_policy(chassis) + thermal_manager.start_thermal_control_algorithm.assert_called_once() + + + + + diff --git a/tests/thermal_policy.json b/tests/thermal_policy.json new file mode 100644 index 000000000..f485a8876 --- /dev/null +++ b/tests/thermal_policy.json @@ -0,0 +1,68 @@ +{ + "info_types": [ + { + "type": "fan_info" + }, + { + "type": "psu_info" + }, + { + "type": "chassis_info" + } + ], + "policies": [ + { + "name": "any fan absence", + "conditions": [ + { + "type": "fan.any.absence" + } + ], + "actions": [ + { + "type": "thermal_control.control", + "status": "false" + }, + { + "type": "fan.all.set_speed", + "speed": "100" + } + ] + }, + { + "name": "any psu absence", + "conditions": [ + { + "type": "psu.any.absence" + } + ], + "actions": [ + { + "type": "thermal_control.control", + "status": "false" + }, + { + "type": "fan.all.set_speed", + "speed": "100" + } + ] + }, + { + "name": "all fan and psu presence", + "conditions": [ + { + "type": "fan.all.presence" + }, + { + "type": "psu.all.presence" + } + ], + "actions": [ + { + "type": "thermal_control.control", + "status": "true" + } + ] + } + ] +} \ No newline at end of file From 49ebc6aea9606e38c465cadbd205d3a4eef10468 Mon Sep 17 00:00:00 2001 From: junchao Date: Wed, 18 Dec 2019 10:03:48 +0800 Subject: [PATCH 06/13] adjust gitignore file to ignore pyc and pycache --- .gitignore | 4 +++- sonic_platform_base/sonic_thermal_control/.gitignore | 2 -- tests/.gitignore | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) delete mode 100644 sonic_platform_base/sonic_thermal_control/.gitignore delete mode 100644 tests/.gitignore diff --git a/.gitignore b/.gitignore index 2b1a01ab0..810be15f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ -build/ +*.pyc */__pycache__/ +build/ sonic_platform_common.egg-info/ +.cache \ No newline at end of file diff --git a/sonic_platform_base/sonic_thermal_control/.gitignore b/sonic_platform_base/sonic_thermal_control/.gitignore deleted file mode 100644 index 4ec183879..000000000 --- a/sonic_platform_base/sonic_thermal_control/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.pyc -*/__pycache__/ \ No newline at end of file diff --git a/tests/.gitignore b/tests/.gitignore deleted file mode 100644 index 4ec183879..000000000 --- a/tests/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.pyc -*/__pycache__/ \ No newline at end of file From 6fd963451feee3f2bc2db118cb098cf6dd9b732b Mon Sep 17 00:00:00 2001 From: junchao Date: Wed, 18 Dec 2019 18:17:32 +0800 Subject: [PATCH 07/13] move default thermal policy to platform API and remove useless unit test --- pytest.ini | 3 - setup.cfg | 2 - setup.py | 13 +- .../sonic_thermal_control/thermal_actions.py | 103 ------------- .../thermal_conditions.py | 61 -------- .../sonic_thermal_control/thermal_infos.py | 135 ------------------ tests/__init__.py | 0 tests/mock_platform.py | 44 ------ tests/test_thermal_policy.py | 104 -------------- tests/thermal_policy.json | 68 --------- 10 files changed, 2 insertions(+), 531 deletions(-) delete mode 100644 pytest.ini delete mode 100644 setup.cfg delete mode 100644 sonic_platform_base/sonic_thermal_control/thermal_actions.py delete mode 100644 sonic_platform_base/sonic_thermal_control/thermal_conditions.py delete mode 100644 sonic_platform_base/sonic_thermal_control/thermal_infos.py delete mode 100644 tests/__init__.py delete mode 100644 tests/mock_platform.py delete mode 100644 tests/test_thermal_policy.py delete mode 100644 tests/thermal_policy.json diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index c24fe5bb9..000000000 --- a/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -[pytest] -filterwarnings = - ignore::DeprecationWarning diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 9af7e6f11..000000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[aliases] -test=pytest \ No newline at end of file diff --git a/setup.py b/setup.py index 35ef8c36b..9dd5b6ec1 100644 --- a/setup.py +++ b/setup.py @@ -19,15 +19,7 @@ 'sonic_platform_base.sonic_ssd', 'sonic_psu', 'sonic_sfp', - 'sonic_platform_base.sonic_thermal_control', - 'tests' - ], - setup_requires= [ - 'pytest-runner' - ], - tests_require = [ - 'pytest', - 'mock>=2.0.0' + 'sonic_platform_base.sonic_thermal_control' ], classifiers=[ 'Development Status :: 3 - Alpha', @@ -42,6 +34,5 @@ 'Programming Language :: Python :: 3.6', 'Topic :: Utilities', ], - keywords='sonic SONiC platform hardware interface api API', - test_suite='setup.get_test_suite' + keywords='sonic SONiC platform hardware interface api API' ) diff --git a/sonic_platform_base/sonic_thermal_control/thermal_actions.py b/sonic_platform_base/sonic_thermal_control/thermal_actions.py deleted file mode 100644 index c4ec8a1f0..000000000 --- a/sonic_platform_base/sonic_thermal_control/thermal_actions.py +++ /dev/null @@ -1,103 +0,0 @@ -from .thermal_action_base import ThermalActionBase, thermal_action - - -class SetFanSpeedAction(ThermalActionBase): - """ - Base thermal action class to set speed for fans - """ - # JSON field definition - JSON_FIELD_SPEED = 'speed' - - def __init__(self): - """ - Constructor of SetFanSpeedAction which actually do nothing. - """ - self.speed = None - - def load_from_json(self, json_obj): - """ - Construct ControlThermalControlAlgoAction via JSON. JSON example: - { - "type": "fan.all.set_speed" - "speed": "100" - } - :param json_obj: A JSON object representing a SetFanSpeedAction action. - :return: - """ - if SetFanSpeedAction.JSON_FIELD_SPEED in json_obj: - self.speed = float(json_obj[SetFanSpeedAction.JSON_FIELD_SPEED]) - else: - raise ValueError('SetFanSpeedAction missing mandatory field {} in policy.json'. - format(SetFanSpeedAction.JSON_FIELD_SPEED)) - - -@thermal_action('fan.all.set_speed') -class SetAllFanSpeedAction(SetFanSpeedAction): - """ - Action to set speed for all fans - """ - def execute(self, thermal_info_dict): - """ - Set speed for all fans - :param thermal_info_dict: A dictionary stores all thermal information. - :return: - """ - from .thermal_infos import FanInfo - if FanInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[FanInfo.INFO_NAME], FanInfo): - fan_info_obj = thermal_info_dict[FanInfo.INFO_NAME] - for fan in fan_info_obj.get_presence_fans(): - fan.set_speed(self.speed) - - -@thermal_action('thermal_control.control') -class ControlThermalControlAlgoAction(ThermalActionBase): - """ - Action to control the thermal control algorithm - """ - # JSON field definition - JSON_FIELD_STATUS = 'status' - - def __init__(self): - self.status = True - - def load_from_json(self, json_obj): - """ - Construct ControlThermalControlAlgoAction via JSON. JSON example: - { - "type": "thermal_control.control" - "status": "true" - } - :param json_obj: A JSON object representing a ControlThermalControlAlgoAction action. - :return: - """ - if ControlThermalControlAlgoAction.JSON_FIELD_STATUS in json_obj: - status_str = json_obj[ControlThermalControlAlgoAction.JSON_FIELD_STATUS].lower() - if status_str == 'true': - self.status = True - elif status_str == 'false': - self.status = False - else: - raise ValueError('Invalid {} field value, please specify true of false'. - format(ControlThermalControlAlgoAction.JSON_FIELD_STATUS)) - else: - raise ValueError('ControlThermalControlAlgoAction ' - 'missing mandatory field {} in policy.json'. - format(ControlThermalControlAlgoAction.JSON_FIELD_STATUS)) - - def execute(self, thermal_info_dict): - """ - Disable thermal control algorithm - :param thermal_info_dict: A dictionary stores all thermal information. - :return: - """ - from .thermal_infos import ChassisInfo - if ChassisInfo.INFO_NAME in thermal_info_dict: - chassis_info_obj = thermal_info_dict[ChassisInfo.INFO_NAME] - chassis = chassis_info_obj.get_chassis() - thermal_manager = chassis.get_thermal_manager() - if self.status: - thermal_manager.start_thermal_control_algorithm() - else: - thermal_manager.stop_thermal_control_algorithm() - - diff --git a/sonic_platform_base/sonic_thermal_control/thermal_conditions.py b/sonic_platform_base/sonic_thermal_control/thermal_conditions.py deleted file mode 100644 index 826bd39fb..000000000 --- a/sonic_platform_base/sonic_thermal_control/thermal_conditions.py +++ /dev/null @@ -1,61 +0,0 @@ -from .thermal_condition_base import ThermalConditionBase, thermal_condition - - -@thermal_condition('fan.any.absence') -class AnyFanAbsenceCondition(ThermalConditionBase): - def is_match(self, thermal_info_dict): - from .thermal_infos import FanInfo - if FanInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[FanInfo.INFO_NAME], FanInfo): - fan_info_obj = thermal_info_dict[FanInfo.INFO_NAME] - return len(fan_info_obj.get_absence_fans()) > 0 - return False - - -@thermal_condition('fan.all.absence') -class AllFanAbsenceCondition(ThermalConditionBase): - def is_match(self, thermal_info_dict): - from .thermal_infos import FanInfo - if FanInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[FanInfo.INFO_NAME], FanInfo): - fan_info_obj = thermal_info_dict[FanInfo.INFO_NAME] - return len(fan_info_obj.get_presence_fans()) == 0 - return False - - -@thermal_condition('fan.all.presence') -class AllFanPresenceCondition(ThermalConditionBase): - def is_match(self, thermal_info_dict): - from .thermal_infos import FanInfo - if FanInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[FanInfo.INFO_NAME], FanInfo): - fan_info_obj = thermal_info_dict[FanInfo.INFO_NAME] - return len(fan_info_obj.get_absence_fans()) == 0 - return False - - -@thermal_condition('psu.any.absence') -class AnyPsuAbsenceCondition(ThermalConditionBase): - def is_match(self, thermal_info_dict): - from .thermal_infos import PsuInfo - if PsuInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[PsuInfo.INFO_NAME], PsuInfo): - psu_info_obj = thermal_info_dict[PsuInfo.INFO_NAME] - return len(psu_info_obj.get_absence_psus()) > 0 - return False - - -@thermal_condition('psu.all.absence') -class AllPsuAbsenceCondition(ThermalConditionBase): - def is_match(self, thermal_info_dict): - from .thermal_infos import PsuInfo - if PsuInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[PsuInfo.INFO_NAME], PsuInfo): - psu_info_obj = thermal_info_dict[PsuInfo.INFO_NAME] - return len(psu_info_obj.get_presence_psus()) == 0 - return False - - -@thermal_condition('psu.all.presence') -class AllFanPresenceCondition(ThermalConditionBase): - def is_match(self, thermal_info_dict): - from .thermal_infos import PsuInfo - if PsuInfo.INFO_NAME in thermal_info_dict and isinstance(thermal_info_dict[PsuInfo.INFO_NAME], PsuInfo): - psu_info_obj = thermal_info_dict[PsuInfo.INFO_NAME] - return len(psu_info_obj.get_absence_psus()) == 0 - return False diff --git a/sonic_platform_base/sonic_thermal_control/thermal_infos.py b/sonic_platform_base/sonic_thermal_control/thermal_infos.py deleted file mode 100644 index 86c23a7a7..000000000 --- a/sonic_platform_base/sonic_thermal_control/thermal_infos.py +++ /dev/null @@ -1,135 +0,0 @@ -from .thermal_info_base import ThermalInfoBase, thermal_info - - -@thermal_info('fan_info') -class FanInfo(ThermalInfoBase): - """ - Fan information needed by thermal policy - """ - - # Fan information name - INFO_NAME = 'fan_info' - - def __init__(self): - self._absence_fans = set() - self._presence_fans = set() - self._status_changed = False - - def collect(self, chassis): - """ - Collect absence and presence fans. - :param chassis: The chassis object - :return: - """ - self._status_changed = False - for fan in chassis.get_all_fans(): - if fan.get_presence() and fan not in self._presence_fans: - self._presence_fans.add(fan) - self._status_changed = True - if fan in self._absence_fans: - self._absence_fans.remove(fan) - elif not fan.get_presence() and fan not in self._absence_fans: - self._absence_fans.add(fan) - self._status_changed = True - if fan in self._presence_fans: - self._presence_fans.remove(fan) - - def get_absence_fans(self): - """ - Retrieves absence fans - :return: A set of absence fans - """ - return self._absence_fans - - def get_presence_fans(self): - """ - Retrieves presence fans - :return: A set of presence fans - """ - return self._presence_fans - - def is_status_changed(self): - """ - Retrieves if the status of fan information changed - :return: True if status changed else False - """ - return self._status_changed - - -@thermal_info('psu_info') -class PsuInfo(ThermalInfoBase): - """ - PSU information needed by thermal policy - """ - INFO_NAME = 'psu_info' - - def __init__(self): - self._absence_psus = set() - self._presence_psus = set() - self._status_changed = False - - def collect(self, chassis): - """ - Collect absence and presence PSUs. - :param chassis: The chassis object - :return: - """ - self._status_changed = False - for psu in chassis.get_all_psus(): - if psu.get_presence() and psu not in self._presence_psus: - self._presence_psus.add(psu) - self._status_changed = True - if psu in self._absence_psus: - self._absence_psus.remove(psu) - elif not psu.get_presence() and psu not in self._absence_psus: - self._absence_psus.add(psu) - self._status_changed = True - if psu in self._presence_psus: - self._presence_psus.remove(psu) - - def get_absence_psus(self): - """ - Retrieves presence PSUs - :return: A set of absence PSUs - """ - return self._absence_psus - - def get_presence_psus(self): - """ - Retrieves presence PSUs - :return: A set of presence fans - """ - return self._presence_psus - - def is_status_changed(self): - """ - Retrieves if the status of PSU information changed - :return: True if status changed else False - """ - return self._status_changed - - -@thermal_info('chassis_info') -class ChassisInfo(ThermalInfoBase): - """ - Chassis information needed by thermal policy - """ - INFO_NAME = 'chassis_info' - - def __init__(self): - self._chassis = None - - def collect(self, chassis): - """ - Collect platform chassis. - :param chassis: The chassis object - :return: - """ - self._chassis = chassis - - def get_chassis(self): - """ - Retrieves platform chassis object - :return: A platform chassis object. - """ - return self._chassis diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/mock_platform.py b/tests/mock_platform.py deleted file mode 100644 index b7ce49993..000000000 --- a/tests/mock_platform.py +++ /dev/null @@ -1,44 +0,0 @@ -class MockFan: - def __init__(self): - self.presence = True - self.speed = 60 - - def get_presence(self): - return self.presence - - def set_speed(self, speed): - self.speed = speed - - -class MockPsu: - def __init__(self): - self.presence = True - - def get_presence(self): - return self.presence - - -class MockChassis: - def __init__(self): - self.fan_list = [] - self.psu_list = [] - - def get_all_psus(self): - return self.psu_list - - def get_all_fans(self): - return self.fan_list - - def get_thermal_manager(self): - from sonic_platform_base.sonic_thermal_control.thermal_manager_base import ThermalManagerBase - return ThermalManagerBase - - def make_fan_absence(self): - fan = MockFan() - fan.presence = False - self.fan_list.append(fan) - - def make_psu_absence(self): - psu = MockPsu() - psu.presence = False - self.psu_list.append(psu) diff --git a/tests/test_thermal_policy.py b/tests/test_thermal_policy.py deleted file mode 100644 index 8724eb0ee..000000000 --- a/tests/test_thermal_policy.py +++ /dev/null @@ -1,104 +0,0 @@ -import os -import sys -import pytest -from mock import MagicMock -from .mock_platform import MockChassis, MockFan - -test_path = os.path.dirname(os.path.abspath(__file__)) -modules_path = os.path.dirname(test_path) -sys.path.insert(0, modules_path) - -from sonic_platform_base.sonic_thermal_control.thermal_manager_base import ThermalManagerBase -from sonic_platform_base.sonic_thermal_control.thermal_infos import FanInfo, PsuInfo - - -@pytest.fixture(scope='session', autouse=True) -def thermal_manager(): - policy_file = os.path.join(test_path, 'thermal_policy.json') - ThermalManagerBase.load(policy_file) - - return ThermalManagerBase - - -def test_load_policy(thermal_manager): - assert 'psu_info' in thermal_manager._thermal_info_dict - assert 'fan_info' in thermal_manager._thermal_info_dict - assert 'chassis_info' in thermal_manager._thermal_info_dict - - assert 'any fan absence' in thermal_manager._policy_dict - assert 'any psu absence' in thermal_manager._policy_dict - assert 'all fan and psu presence' in thermal_manager._policy_dict - - -def test_fan_info(): - chassis = MockChassis() - chassis.make_fan_absence() - fan_info = FanInfo() - fan_info.collect(chassis) - assert len(fan_info.get_absence_fans()) == 1 - assert len(fan_info.get_presence_fans()) == 0 - assert fan_info.is_status_changed() - - fan_list = chassis.get_all_fans() - fan_list[0].presence = True - fan_info.collect(chassis) - assert len(fan_info.get_absence_fans()) == 0 - assert len(fan_info.get_presence_fans()) == 1 - assert fan_info.is_status_changed() - - -def test_psu_info(): - chassis = MockChassis() - chassis.make_psu_absence() - psu_info = PsuInfo() - psu_info.collect(chassis) - assert len(psu_info.get_absence_psus()) == 1 - assert len(psu_info.get_presence_psus()) == 0 - assert psu_info.is_status_changed() - - psu_list = chassis.get_all_psus() - psu_list[0].presence = True - psu_info.collect(chassis) - assert len(psu_info.get_absence_psus()) == 0 - assert len(psu_info.get_presence_psus()) == 1 - assert psu_info.is_status_changed() - - -def test_fan_policy(thermal_manager): - chassis = MockChassis() - chassis.make_fan_absence() - chassis.fan_list.append(MockFan()) - thermal_manager.start_thermal_control_algorithm = MagicMock() - thermal_manager.stop_thermal_control_algorithm = MagicMock() - thermal_manager.run_policy(chassis) - - fan_list = chassis.get_all_fans() - assert fan_list[1].speed == 100 - thermal_manager.stop_thermal_control_algorithm.assert_called_once() - - fan_list[0].presence = True - thermal_manager.run_policy(chassis) - thermal_manager.start_thermal_control_algorithm.assert_called_once() - - -def test_psu_policy(thermal_manager): - chassis = MockChassis() - chassis.make_psu_absence() - chassis.fan_list.append(MockFan()) - thermal_manager.start_thermal_control_algorithm = MagicMock() - thermal_manager.stop_thermal_control_algorithm = MagicMock() - thermal_manager.run_policy(chassis) - - fan_list = chassis.get_all_fans() - assert fan_list[0].speed == 100 - thermal_manager.stop_thermal_control_algorithm.assert_called_once() - - psu_list = chassis.get_all_psus() - psu_list[0].presence = True - thermal_manager.run_policy(chassis) - thermal_manager.start_thermal_control_algorithm.assert_called_once() - - - - - diff --git a/tests/thermal_policy.json b/tests/thermal_policy.json deleted file mode 100644 index f485a8876..000000000 --- a/tests/thermal_policy.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "info_types": [ - { - "type": "fan_info" - }, - { - "type": "psu_info" - }, - { - "type": "chassis_info" - } - ], - "policies": [ - { - "name": "any fan absence", - "conditions": [ - { - "type": "fan.any.absence" - } - ], - "actions": [ - { - "type": "thermal_control.control", - "status": "false" - }, - { - "type": "fan.all.set_speed", - "speed": "100" - } - ] - }, - { - "name": "any psu absence", - "conditions": [ - { - "type": "psu.any.absence" - } - ], - "actions": [ - { - "type": "thermal_control.control", - "status": "false" - }, - { - "type": "fan.all.set_speed", - "speed": "100" - } - ] - }, - { - "name": "all fan and psu presence", - "conditions": [ - { - "type": "fan.all.presence" - }, - { - "type": "psu.all.presence" - } - ], - "actions": [ - { - "type": "thermal_control.control", - "status": "true" - } - ] - } - ] -} \ No newline at end of file From a9bfb29366dd97caf02308d29613fc4fb4d361b5 Mon Sep 17 00:00:00 2001 From: junchao Date: Wed, 18 Dec 2019 18:21:15 +0800 Subject: [PATCH 08/13] fix issues found by unit test --- .../sonic_thermal_control/thermal_manager_base.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py b/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py index 733343c6c..630fc4e24 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py @@ -1,9 +1,6 @@ import json from .thermal_policy import ThermalPolicy from .thermal_info_base import ThermalInfoBase -from .thermal_actions import * -from .thermal_conditions import * -from .thermal_infos import * class ThermalManagerBase(object): From 56e746f108f1613e062f2deb8734aa0edd0a8e26 Mon Sep 17 00:00:00 2001 From: junchao Date: Thu, 19 Dec 2019 10:55:42 +0800 Subject: [PATCH 09/13] rename some functions to make it more readable according to Kebo comments --- .../sonic_thermal_control/thermal_action_base.py | 16 ++++++++-------- .../thermal_condition_base.py | 16 ++++++++-------- .../sonic_thermal_control/thermal_info_base.py | 16 ++++++++-------- .../thermal_manager_base.py | 6 +++--- .../sonic_thermal_control/thermal_policy.py | 8 ++++---- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/sonic_platform_base/sonic_thermal_control/thermal_action_base.py b/sonic_platform_base/sonic_thermal_control/thermal_action_base.py index 89c684dd2..d3190d544 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_action_base.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_action_base.py @@ -1,4 +1,4 @@ -class ThermalActionBase(object): +class ThermalPolicyActionBase(object): """ Base class for thermal action. Once all thermal conditions in a thermal policy are matched, all predefined thermal action would be executed. @@ -6,7 +6,7 @@ class ThermalActionBase(object): # JSON field definition JSON_FIELD_ACTION_TYPE = 'type' - # Dictionary of ThermalActionBase-derived class representing all thermal action types + # Dictionary of ThermalPolicyActionBase-derived class representing all thermal action types _action_type_dict = {} def execute(self, thermal_info_dict): @@ -28,10 +28,10 @@ def load_from_json(self, json_obj): pass @classmethod - def register_type(cls, type_name, action_type): + def register_concrete_action_type(cls, type_name, action_type): """ Register a concrete action class by type name. The concrete action class must derive from - ThermalActionBase or have exactly the same member function 'execute' and 'load_from_json'. + ThermalPolicyActionBase or have exactly the same member function 'execute' and 'load_from_json'. For any concrete action class, it must be registered explicitly. :param type_name: Type name of the action class which corresponding to the 'type' field of an action in policy.json. @@ -52,8 +52,8 @@ def get_type(cls, json_obj): :param json_obj: A json object representing an action. :return: A concrete action class if requested type exists; Otherwise None. """ - if ThermalActionBase.JSON_FIELD_ACTION_TYPE in json_obj: - type_str = json_obj[ThermalActionBase.JSON_FIELD_ACTION_TYPE] + if ThermalPolicyActionBase.JSON_FIELD_ACTION_TYPE in json_obj: + type_str = json_obj[ThermalPolicyActionBase.JSON_FIELD_ACTION_TYPE] return cls._action_type_dict[type_str] if type_str in cls._action_type_dict else None return None @@ -61,12 +61,12 @@ def get_type(cls, json_obj): def thermal_action(type_name): """ - Decorator to auto register a ThermalActionBase-derived class + Decorator to auto register a ThermalPolicyActionBase-derived class :param type_name: Type name of the action class which corresponding to the 'type' field of a action in policy.json. :return: Wrapper function """ def wrapper(action_type): - ThermalActionBase.register_type(type_name, action_type) + ThermalPolicyActionBase.register_concrete_action_type(type_name, action_type) return action_type return wrapper diff --git a/sonic_platform_base/sonic_thermal_control/thermal_condition_base.py b/sonic_platform_base/sonic_thermal_control/thermal_condition_base.py index a4f5f1361..8c5d998c0 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_condition_base.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_condition_base.py @@ -1,11 +1,11 @@ -class ThermalConditionBase(object): +class ThermalPolicyConditionBase(object): """ Base class for thermal condition """ # JSON field definition JSON_FIELD_CONDITION_TYPE = 'type' - # Dictionary of ThermalConditionBase-derived class representing all thermal condition types. + # Dictionary of ThermalPolicyConditionBase-derived class representing all thermal condition types. _condition_type_dict = {} def is_match(self, thermal_info_dict): @@ -34,17 +34,17 @@ def get_type(cls, json_obj): :param json_obj: A json object representing a condition. :return: A concrete condition class if requested type exists; Otherwise None. """ - if ThermalConditionBase.JSON_FIELD_CONDITION_TYPE in json_obj: - type_name = json_obj[ThermalConditionBase.JSON_FIELD_CONDITION_TYPE] + if ThermalPolicyConditionBase.JSON_FIELD_CONDITION_TYPE in json_obj: + type_name = json_obj[ThermalPolicyConditionBase.JSON_FIELD_CONDITION_TYPE] return cls._condition_type_dict[type_name] if type_name in cls._condition_type_dict else None return None @classmethod - def register_type(cls, type_name, condition_type): + def register_concrete_condition_type(cls, type_name, condition_type): """ Register a concrete condition class by type name. The concrete condition class must derive from - ThermalConditionBase or have exactly the same member function 'is_match' and 'load_from_json'. + ThermalPolicyConditionBase or have exactly the same member function 'is_match' and 'load_from_json'. For any concrete condition class, it must be registered explicitly. :param type_name: Type name of the condition class which corresponding to the 'type' field of a condition in policy.json. @@ -59,12 +59,12 @@ def register_type(cls, type_name, condition_type): def thermal_condition(type_name): """ - Decorator to auto register a ThermalConditionBase-derived class + Decorator to auto register a ThermalPolicyConditionBase-derived class :param type_name: Type name of the condition class which corresponding to the 'type' field of a condition in policy.json. :return: Wrapper function """ def wrapper(condition_type): - ThermalConditionBase.register_type(type_name, condition_type) + ThermalPolicyConditionBase.register_concrete_condition_type(type_name, condition_type) return condition_type return wrapper diff --git a/sonic_platform_base/sonic_thermal_control/thermal_info_base.py b/sonic_platform_base/sonic_thermal_control/thermal_info_base.py index 257fe02d6..cd60cef79 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_info_base.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_info_base.py @@ -1,11 +1,11 @@ -class ThermalInfoBase(object): +class ThermalPolicyInfoBase(object): """ Base class for thermal information """ # JSON field definition JSON_FIELD_INFO_TYPE = 'type' - # Dictionary of ThermalActionBase-derived class representing all thermal action types + # Dictionary of ThermalPolicyInfoBase-derived class representing all thermal action types _info_type_dict = {} def collect(self, chassis): @@ -26,10 +26,10 @@ def load_from_json(self, json_obj): pass @classmethod - def register_type(cls, type_name, info_type): + def register_concrete_info_type(cls, type_name, info_type): """ Register a concrete information class by type name. The concrete information class must derive from - ThermalInfoBase or have exactly the same member function 'collect' + ThermalPolicyInfoBase or have exactly the same member function 'collect' For any concrete information class, it must be registered explicitly. :param type_name: Type name of the information class. :param info_type: A concrete information class. @@ -49,8 +49,8 @@ def get_type(cls, json_obj): :param json_obj: A json object representing an information. :return: A concrete information class if requested type exists; Otherwise None. """ - if ThermalInfoBase.JSON_FIELD_INFO_TYPE in json_obj: - type_str = json_obj[ThermalInfoBase.JSON_FIELD_INFO_TYPE] + if ThermalPolicyInfoBase.JSON_FIELD_INFO_TYPE in json_obj: + type_str = json_obj[ThermalPolicyInfoBase.JSON_FIELD_INFO_TYPE] return cls._info_type_dict[type_str] if type_str in cls._info_type_dict else None return None @@ -58,11 +58,11 @@ def get_type(cls, json_obj): def thermal_info(type_name): """ - Decorator to auto register a ThermalInfoBase-derived class + Decorator to auto register a ThermalPolicyInfoBase-derived class :param type_name: Type name of the information class :return: Wrapper function """ def wrapper(info_type): - ThermalInfoBase.register_type(type_name, info_type) + ThermalPolicyInfoBase.register_concrete_info_type(type_name, info_type) return info_type return wrapper diff --git a/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py b/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py index 630fc4e24..21d367150 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py @@ -1,6 +1,6 @@ import json from .thermal_policy import ThermalPolicy -from .thermal_info_base import ThermalInfoBase +from .thermal_info_base import ThermalPolicyInfoBase class ThermalManagerBase(object): @@ -111,10 +111,10 @@ def load(cls, policy_file_name): if cls.JSON_FIELD_INFO_TYPES in json_obj: for json_info in json_obj[cls.JSON_FIELD_INFO_TYPES]: - info_type = ThermalInfoBase.get_type(json_info) + info_type = ThermalPolicyInfoBase.get_type(json_info) if info_type: info_obj = info_type() - cls._thermal_info_dict[json_info[ThermalInfoBase.JSON_FIELD_INFO_TYPE]] = info_obj + cls._thermal_info_dict[json_info[ThermalPolicyInfoBase.JSON_FIELD_INFO_TYPE]] = info_obj else: raise KeyError('Invalid thermal information defined in policy file') diff --git a/sonic_platform_base/sonic_thermal_control/thermal_policy.py b/sonic_platform_base/sonic_thermal_control/thermal_policy.py index 975b3fd1d..d1a883cfc 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_policy.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_policy.py @@ -1,5 +1,5 @@ -from .thermal_action_base import ThermalActionBase -from .thermal_condition_base import ThermalConditionBase +from .thermal_action_base import ThermalPolicyActionBase +from .thermal_condition_base import ThermalPolicyConditionBase class ThermalPolicy(object): @@ -32,7 +32,7 @@ def load_from_json(self, json_obj): if self.JSON_FIELD_CONDITIONS in json_obj: for json_condition in json_obj[self.JSON_FIELD_CONDITIONS]: - cond_type = ThermalConditionBase.get_type(json_condition) + cond_type = ThermalPolicyConditionBase.get_type(json_condition) if cond_type: cond_obj = cond_type() cond_obj.load_from_json(json_condition) @@ -42,7 +42,7 @@ def load_from_json(self, json_obj): if self.JSON_FIELD_ACTIONS in json_obj: for json_action in json_obj[self.JSON_FIELD_ACTIONS]: - action_type = ThermalActionBase.get_type(json_action) + action_type = ThermalPolicyActionBase.get_type(json_action) if action_type: action_obj = action_type() action_obj.load_from_json(json_action) From 5430513fcefbe853b2ac86fd2605bb812b9423d9 Mon Sep 17 00:00:00 2001 From: junchao Date: Mon, 23 Dec 2019 17:45:01 +0800 Subject: [PATCH 10/13] 1. add a common base class for info, action and condition; 2. raise Exception instead of KeyError --- sonic_platform_base/chassis_base.py | 3 +- .../thermal_action_base.py | 63 ++---------------- .../thermal_condition_base.py | 64 ++----------------- .../thermal_info_base.py | 59 +---------------- .../thermal_json_object.py | 63 ++++++++++++++++++ .../thermal_manager_base.py | 17 ++--- .../sonic_thermal_control/thermal_policy.py | 27 +++----- 7 files changed, 92 insertions(+), 204 deletions(-) create mode 100644 sonic_platform_base/sonic_thermal_control/thermal_json_object.py diff --git a/sonic_platform_base/chassis_base.py b/sonic_platform_base/chassis_base.py index 17fba3b05..051dc2dcd 100644 --- a/sonic_platform_base/chassis_base.py +++ b/sonic_platform_base/chassis_base.py @@ -339,8 +339,7 @@ def get_thermal_manager(self): :return: A class derived from ThermalManagerBase representing the specified thermal manager. ThermalManagerBase is returned as default """ - from .sonic_thermal_control.thermal_manager_base import ThermalManagerBase - return ThermalManagerBase + raise NotImplementedError ############################################## # SFP methods diff --git a/sonic_platform_base/sonic_thermal_control/thermal_action_base.py b/sonic_platform_base/sonic_thermal_control/thermal_action_base.py index d3190d544..86b8faff9 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_action_base.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_action_base.py @@ -1,14 +1,11 @@ -class ThermalPolicyActionBase(object): +from .thermal_json_object import ThermalJsonObject + + +class ThermalPolicyActionBase(ThermalJsonObject): """ Base class for thermal action. Once all thermal conditions in a thermal policy are matched, all predefined thermal action would be executed. """ - # JSON field definition - JSON_FIELD_ACTION_TYPE = 'type' - - # Dictionary of ThermalPolicyActionBase-derived class representing all thermal action types - _action_type_dict = {} - def execute(self, thermal_info_dict): """ Take action when thermal condition matches. For example, adjust speed of fan or shut @@ -18,55 +15,3 @@ def execute(self, thermal_info_dict): """ raise NotImplementedError - def load_from_json(self, json_obj): - """ - Initialize this object by a json object. The json object is read from policy.json section 'actions'. - Derived class can define any field in policy.json and interpret them in this function. - :param json_obj: A json object representing an action. - :return: - """ - pass - - @classmethod - def register_concrete_action_type(cls, type_name, action_type): - """ - Register a concrete action class by type name. The concrete action class must derive from - ThermalPolicyActionBase or have exactly the same member function 'execute' and 'load_from_json'. - For any concrete action class, it must be registered explicitly. - :param type_name: Type name of the action class which corresponding to the 'type' field of - an action in policy.json. - :param action_type: A concrete action class. - :return: - """ - if type_name not in cls._action_type_dict: - cls._action_type_dict[type_name] = action_type - else: - raise KeyError('ThermalAction type {} already exists'.format(type_name)) - - @classmethod - def get_type(cls, json_obj): - """ - Get a concrete action class by json object. The json object represents a action object and must - have a 'type' field. This function returns a pre-registered concrete action class if the specific - 'type' is found. - :param json_obj: A json object representing an action. - :return: A concrete action class if requested type exists; Otherwise None. - """ - if ThermalPolicyActionBase.JSON_FIELD_ACTION_TYPE in json_obj: - type_str = json_obj[ThermalPolicyActionBase.JSON_FIELD_ACTION_TYPE] - return cls._action_type_dict[type_str] if type_str in cls._action_type_dict else None - - return None - - -def thermal_action(type_name): - """ - Decorator to auto register a ThermalPolicyActionBase-derived class - :param type_name: Type name of the action class which corresponding to the 'type' field of - a action in policy.json. - :return: Wrapper function - """ - def wrapper(action_type): - ThermalPolicyActionBase.register_concrete_action_type(type_name, action_type) - return action_type - return wrapper diff --git a/sonic_platform_base/sonic_thermal_control/thermal_condition_base.py b/sonic_platform_base/sonic_thermal_control/thermal_condition_base.py index 8c5d998c0..2196efad1 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_condition_base.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_condition_base.py @@ -1,13 +1,10 @@ -class ThermalPolicyConditionBase(object): +from .thermal_json_object import ThermalJsonObject + + +class ThermalPolicyConditionBase(ThermalJsonObject): """ Base class for thermal condition """ - # JSON field definition - JSON_FIELD_CONDITION_TYPE = 'type' - - # Dictionary of ThermalPolicyConditionBase-derived class representing all thermal condition types. - _condition_type_dict = {} - def is_match(self, thermal_info_dict): """ Indicate if this condition is matched. @@ -15,56 +12,3 @@ def is_match(self, thermal_info_dict): :return: True if condition matched else False. """ raise NotImplementedError - - def load_from_json(self, json_obj): - """ - Initialize this object by a json object. The json object is read from policy.json section 'conditions'. - Derived class can define any field in policy.json and interpret them in this function. - :param json_obj: A json object representing a condition. - :return: - """ - pass - - @classmethod - def get_type(cls, json_obj): - """ - Get a concrete condition class by json object. The json object represents a condition object and must - have a 'type' field. This function returns a pre-registered concrete condition class if the specific - 'type' is found. - :param json_obj: A json object representing a condition. - :return: A concrete condition class if requested type exists; Otherwise None. - """ - if ThermalPolicyConditionBase.JSON_FIELD_CONDITION_TYPE in json_obj: - type_name = json_obj[ThermalPolicyConditionBase.JSON_FIELD_CONDITION_TYPE] - return cls._condition_type_dict[type_name] if type_name in cls._condition_type_dict else None - - return None - - @classmethod - def register_concrete_condition_type(cls, type_name, condition_type): - """ - Register a concrete condition class by type name. The concrete condition class must derive from - ThermalPolicyConditionBase or have exactly the same member function 'is_match' and 'load_from_json'. - For any concrete condition class, it must be registered explicitly. - :param type_name: Type name of the condition class which corresponding to the 'type' field of - a condition in policy.json. - :param condition_type: A concrete condition class. - :return: - """ - if type_name not in cls._condition_type_dict: - cls._condition_type_dict[type_name] = condition_type - else: - raise KeyError('ThermalCondition type {} already exists'.format(type_name)) - - -def thermal_condition(type_name): - """ - Decorator to auto register a ThermalPolicyConditionBase-derived class - :param type_name: Type name of the condition class which corresponding to the 'type' field of - a condition in policy.json. - :return: Wrapper function - """ - def wrapper(condition_type): - ThermalPolicyConditionBase.register_concrete_condition_type(type_name, condition_type) - return condition_type - return wrapper diff --git a/sonic_platform_base/sonic_thermal_control/thermal_info_base.py b/sonic_platform_base/sonic_thermal_control/thermal_info_base.py index cd60cef79..32aa06ee0 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_info_base.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_info_base.py @@ -1,13 +1,10 @@ +from .thermal_json_object import ThermalJsonObject + + class ThermalPolicyInfoBase(object): """ Base class for thermal information """ - # JSON field definition - JSON_FIELD_INFO_TYPE = 'type' - - # Dictionary of ThermalPolicyInfoBase-derived class representing all thermal action types - _info_type_dict = {} - def collect(self, chassis): """ Collect thermal information for thermal policy. @@ -16,53 +13,3 @@ def collect(self, chassis): """ raise NotImplementedError - def load_from_json(self, json_obj): - """ - Initialize this object by a json object. The json object is read from policy.json section 'info_types'. - Derived class can define any field in policy.json and interpret them in this function. - :param json_obj: A json object representing an thermal information. - :return: - """ - pass - - @classmethod - def register_concrete_info_type(cls, type_name, info_type): - """ - Register a concrete information class by type name. The concrete information class must derive from - ThermalPolicyInfoBase or have exactly the same member function 'collect' - For any concrete information class, it must be registered explicitly. - :param type_name: Type name of the information class. - :param info_type: A concrete information class. - :return: - """ - if type_name not in cls._info_type_dict: - cls._info_type_dict[type_name] = info_type - else: - raise KeyError('ThermalInfo type {} already exists'.format(type_name)) - - @classmethod - def get_type(cls, json_obj): - """ - Get a concrete information class by json object. The json object represents an information object and must - have a 'type' field. This function returns a pre-registered concrete information class if the specific - 'type' is found. - :param json_obj: A json object representing an information. - :return: A concrete information class if requested type exists; Otherwise None. - """ - if ThermalPolicyInfoBase.JSON_FIELD_INFO_TYPE in json_obj: - type_str = json_obj[ThermalPolicyInfoBase.JSON_FIELD_INFO_TYPE] - return cls._info_type_dict[type_str] if type_str in cls._info_type_dict else None - - return None - - -def thermal_info(type_name): - """ - Decorator to auto register a ThermalPolicyInfoBase-derived class - :param type_name: Type name of the information class - :return: Wrapper function - """ - def wrapper(info_type): - ThermalPolicyInfoBase.register_concrete_info_type(type_name, info_type) - return info_type - return wrapper diff --git a/sonic_platform_base/sonic_thermal_control/thermal_json_object.py b/sonic_platform_base/sonic_thermal_control/thermal_json_object.py new file mode 100644 index 000000000..3060d8665 --- /dev/null +++ b/sonic_platform_base/sonic_thermal_control/thermal_json_object.py @@ -0,0 +1,63 @@ +class ThermalJsonObject(object): + """ + Base class for thermal json object. + """ + # JSON field definition + JSON_FIELD_TYPE = 'type' + + # Dictionary of ThermalJsonObject-derived class representing all thermal json types + _object_type_dict = {} + + def load_from_json(self, json_obj): + """ + Initialize this object by a json object. The json object is read from policy json file. + Derived class can define any field in policy json file and interpret them in this function. + :param json_obj: A json object representing an object. + :return: + """ + pass + + @classmethod + def register_concrete_type(cls, type_name, object_type): + """ + Register a concrete class by type name. The concrete class must derive from + ThermalJsonObject. + :param type_name: Name of the class. + :param object_type: A concrete class. + :return: + """ + if type_name not in cls._object_type_dict: + cls._object_type_dict[type_name] = object_type + else: + raise Exception('ThermalJsonObject type {} already exists'.format(type_name)) + + @classmethod + def get_type(cls, json_obj): + """ + Get a concrete class by json object. The json object represents an object and must + have a 'type' field. This function returns a pre-registered concrete class if the specific + 'type' is found. + :param json_obj: A json object representing an action. + :return: A concrete class if requested type exists; Otherwise None. + """ + if ThermalJsonObject.JSON_FIELD_TYPE in json_obj: + type_str = json_obj[ThermalJsonObject.JSON_FIELD_TYPE] + if type_str in cls._object_type_dict: + return cls._object_type_dict[type_str] + else: + raise Exception('ThermalJsonObject type {} not found'.format(type_str) ) + + raise Exception('Invalid policy file, {} field must be presented'.format(ThermalJsonObject.JSON_FIELD_TYPE)) + + +def thermal_json_object(type_name): + """ + Decorator to auto register a ThermalJsonObject-derived class + :param type_name: Type name of the concrete class which corresponding to the 'type' field of + a condition, action or info. + :return: Wrapper function + """ + def wrapper(object_type): + ThermalJsonObject.register_concrete_type(type_name, object_type) + return object_type + return wrapper diff --git a/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py b/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py index 21d367150..bac38f47e 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py @@ -1,6 +1,6 @@ import json from .thermal_policy import ThermalPolicy -from .thermal_info_base import ThermalPolicyInfoBase +from .thermal_json_object import ThermalJsonObject class ThermalManagerBase(object): @@ -28,7 +28,7 @@ def initialize(cls): pass @classmethod - def destroy(cls): + def deinitialize(cls): """ Destroy thermal manager, including any vendor specific cleanup. The default behavior of this function is a no-op. @@ -111,12 +111,9 @@ def load(cls, policy_file_name): if cls.JSON_FIELD_INFO_TYPES in json_obj: for json_info in json_obj[cls.JSON_FIELD_INFO_TYPES]: - info_type = ThermalPolicyInfoBase.get_type(json_info) - if info_type: - info_obj = info_type() - cls._thermal_info_dict[json_info[ThermalPolicyInfoBase.JSON_FIELD_INFO_TYPE]] = info_obj - else: - raise KeyError('Invalid thermal information defined in policy file') + info_type = ThermalJsonObject.get_type(json_info) + info_obj = info_type() + cls._thermal_info_dict[json_info[ThermalJsonObject.JSON_FIELD_TYPE]] = info_obj @classmethod def _load_policy(cls, json_policy): @@ -128,13 +125,13 @@ def _load_policy(cls, json_policy): if cls.JSON_FIELD_POLICY_NAME in json_policy: name = json_policy[cls.JSON_FIELD_POLICY_NAME] if name in cls._policy_dict: - raise KeyError('Policy {} already exists'.format(name)) + raise Exception('Policy {} already exists'.format(name)) policy = ThermalPolicy() policy.load_from_json(json_policy) cls._policy_dict[name] = policy else: - raise KeyError('{} not found in policy'.format(cls.JSON_FIELD_POLICY_NAME)) + raise Exception('{} not found in policy'.format(cls.JSON_FIELD_POLICY_NAME)) @classmethod def run_policy(cls, chassis): diff --git a/sonic_platform_base/sonic_thermal_control/thermal_policy.py b/sonic_platform_base/sonic_thermal_control/thermal_policy.py index d1a883cfc..93edb9e84 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_policy.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_policy.py @@ -1,5 +1,4 @@ -from .thermal_action_base import ThermalPolicyActionBase -from .thermal_condition_base import ThermalPolicyConditionBase +from .thermal_json_object import ThermalJsonObject class ThermalPolicy(object): @@ -32,25 +31,19 @@ def load_from_json(self, json_obj): if self.JSON_FIELD_CONDITIONS in json_obj: for json_condition in json_obj[self.JSON_FIELD_CONDITIONS]: - cond_type = ThermalPolicyConditionBase.get_type(json_condition) - if cond_type: - cond_obj = cond_type() - cond_obj.load_from_json(json_condition) - self.conditions.append(cond_obj) - else: - raise KeyError('Invalid thermal condition defined in policy file') + cond_type = ThermalJsonObject.get_type(json_condition) + cond_obj = cond_type() + cond_obj.load_from_json(json_condition) + self.conditions.append(cond_obj) if self.JSON_FIELD_ACTIONS in json_obj: for json_action in json_obj[self.JSON_FIELD_ACTIONS]: - action_type = ThermalPolicyActionBase.get_type(json_action) - if action_type: - action_obj = action_type() - action_obj.load_from_json(json_action) - self.actions.append(action_obj) - else: - raise KeyError('Invalid thermal action defined in policy file') + action_type = ThermalJsonObject.get_type(json_action) + action_obj = action_type() + action_obj.load_from_json(json_action) + self.actions.append(action_obj) else: - raise KeyError('name field not found in policy') + raise Exception('name field not found in policy') def is_match(self, thermal_info_dict): """ From 2db821541cde1c47b024f7ab60ea61a62e5f83f5 Mon Sep 17 00:00:00 2001 From: junchao Date: Wed, 25 Dec 2019 17:32:10 +0800 Subject: [PATCH 11/13] add get_high_critical_threshold and get_low_critical_threshold to thermal_base --- sonic_platform_base/thermal_base.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sonic_platform_base/thermal_base.py b/sonic_platform_base/thermal_base.py index 6dc8dc1f3..0388d4f60 100644 --- a/sonic_platform_base/thermal_base.py +++ b/sonic_platform_base/thermal_base.py @@ -72,3 +72,23 @@ def set_low_threshold(self, temperature): A boolean, True if threshold is set successfully, False if not """ raise NotImplementedError + + def get_high_critical_threshold(self): + """ + Retrieves the high critical threshold temperature of thermal + + Returns: + A float number, the high critical threshold temperature of thermal in Celsius + up to nearest thousandth of one degree Celsius, e.g. 30.125 + """ + raise NotImplementedError + + def get_low_critical_threshold(self): + """ + Retrieves the low critical threshold temperature of thermal + + Returns: + A float number, the low critical threshold temperature of thermal in Celsius + up to nearest thousandth of one degree Celsius, e.g. 30.125 + """ + raise NotImplementedError From fc80aa10269d260197d1b7a906832db106c622db Mon Sep 17 00:00:00 2001 From: junchao Date: Thu, 26 Dec 2019 10:17:32 +0800 Subject: [PATCH 12/13] fix typo when load thermal algorithm JSON config --- .../thermal_manager_base.py | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py b/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py index bac38f47e..85aee20a0 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py @@ -11,6 +11,9 @@ class ThermalManagerBase(object): JSON_FIELD_POLICIES = 'policies' JSON_FIELD_INFO_TYPES = 'info_types' JSON_FIELD_POLICY_NAME = 'name' + JSON_FIELD_THERMAL_ALGORITHM = "thermal_control_algorithm" + JSON_FIELD_FAN_SPEED_WHEN_SUSPEND = "fan_speed_when_suspend" + JSON_FIELD_RUN_AT_BOOT_UP = "run_at_boot_up" # Dictionary of ThermalPolicy objects. _policy_dict = {} @@ -18,11 +21,15 @@ class ThermalManagerBase(object): # Dictionary of thermal information objects. A thermal information object is used by Thermal Policy _thermal_info_dict = {} + _fan_speed_when_suspend = None + + _run_thermal_algorithm_at_boot_up = None + @classmethod def initialize(cls): """ Initialize thermal manager, including register thermal condition types and thermal action types - and any other vendor specific initialization. + and any other vendor specific initialization. :return: """ pass @@ -30,7 +37,7 @@ def initialize(cls): @classmethod def deinitialize(cls): """ - Destroy thermal manager, including any vendor specific cleanup. The default behavior of this function + Destroy thermal manager, including any vendor specific cleanup. The default behavior of this function is a no-op. :return: """ @@ -57,6 +64,10 @@ def load(cls, policy_file_name): """ Load all thermal policies from policy.json file. An example looks like: { + "thermal_control_algorithm": { + "run_at_boot_up": "false", + "fan_speed_when_suspend": "60" + }, "info_types": [ { "type": "fan_info" # collect fan information for each iteration @@ -115,6 +126,17 @@ def load(cls, policy_file_name): info_obj = info_type() cls._thermal_info_dict[json_info[ThermalJsonObject.JSON_FIELD_TYPE]] = info_obj + if cls.JSON_FIELD_THERMAL_ALGORITHM in json_obj: + json_thermal_algorithm_config = json_obj[cls.JSON_FIELD_THERMAL_ALGORITHM] + if cls.JSON_FIELD_RUN_AT_BOOT_UP in json_thermal_algorithm_config: + cls._run_thermal_algorithm_at_boot_up = \ + True if json_thermal_algorithm_config[cls.JSON_FIELD_RUN_AT_BOOT_UP].lower() == 'true' else False + + if cls.JSON_FIELD_FAN_SPEED_WHEN_SUSPEND in json_thermal_algorithm_config: + # if the string is not a valid int, let it raise + cls._fan_speed_when_suspend = \ + int(json_thermal_algorithm_config[cls.JSON_FIELD_FAN_SPEED_WHEN_SUSPEND]) + @classmethod def _load_policy(cls, json_policy): """ @@ -158,3 +180,23 @@ def _collect_thermal_information(cls, chassis): """ for thermal_info in cls._thermal_info_dict.values(): thermal_info.collect(chassis) + + @classmethod + def init_thermal_algorithm(cls, chassis): + """ + Initialize thermal algorithm according to policy file. + :param chassis: The chassis object. + :return: + """ + if cls._run_thermal_algorithm_at_boot_up is not None: + if cls._run_thermal_algorithm_at_boot_up: + cls.start_thermal_control_algorithm() + else: + cls.stop_thermal_control_algorithm() + if cls._fan_speed_when_suspend is not None: + for fan in chassis.get_all_fans(): + fan.set_speed(cls._fan_speed_when_suspend) + + for psu in chassis.get_all_psus(): + for fan in psu.get_all_fans(): + fan.set_speed(cls._fan_speed_when_suspend) From b8e2ab71d9cd0911192d1a6f20aa261e98f76e8a Mon Sep 17 00:00:00 2001 From: junchao Date: Fri, 17 Jan 2020 11:36:24 +0800 Subject: [PATCH 13/13] Fix review comment by Joe --- setup.py | 4 ++-- .../sonic_thermal_control/thermal_action_base.py | 2 +- .../sonic_thermal_control/thermal_manager_base.py | 4 ++-- .../sonic_thermal_control/thermal_policy.py | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index 9dd5b6ec1..c718aeaf8 100644 --- a/setup.py +++ b/setup.py @@ -17,9 +17,9 @@ 'sonic_platform_base.sonic_eeprom', 'sonic_platform_base.sonic_sfp', 'sonic_platform_base.sonic_ssd', + 'sonic_platform_base.sonic_thermal_control', 'sonic_psu', - 'sonic_sfp', - 'sonic_platform_base.sonic_thermal_control' + 'sonic_sfp' ], classifiers=[ 'Development Status :: 3 - Alpha', diff --git a/sonic_platform_base/sonic_thermal_control/thermal_action_base.py b/sonic_platform_base/sonic_thermal_control/thermal_action_base.py index 86b8faff9..ee5373794 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_action_base.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_action_base.py @@ -4,7 +4,7 @@ class ThermalPolicyActionBase(ThermalJsonObject): """ Base class for thermal action. Once all thermal conditions in a thermal policy are matched, - all predefined thermal action would be executed. + all predefined thermal action will be executed. """ def execute(self, thermal_info_dict): """ diff --git a/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py b/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py index 85aee20a0..8ffe5d6ed 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_manager_base.py @@ -62,7 +62,7 @@ def stop_thermal_control_algorithm(cls): @classmethod def load(cls, policy_file_name): """ - Load all thermal policies from policy.json file. An example looks like: + Load all thermal policies from JSON policy file. An example looks like: { "thermal_control_algorithm": { "run_at_boot_up": "false", @@ -110,7 +110,7 @@ def load(cls, policy_file_name): } ] } - :param policy_file_name: Path of policy.json. + :param policy_file_name: Path of JSON policy file. :return: """ with open(policy_file_name, 'r') as policy_file: diff --git a/sonic_platform_base/sonic_thermal_control/thermal_policy.py b/sonic_platform_base/sonic_thermal_control/thermal_policy.py index 93edb9e84..56e619bff 100644 --- a/sonic_platform_base/sonic_thermal_control/thermal_policy.py +++ b/sonic_platform_base/sonic_thermal_control/thermal_policy.py @@ -3,7 +3,7 @@ class ThermalPolicy(object): """ - Class representing a thermal policy. A thermal policy object is initialized by policy.json. + Class representing a thermal policy. A thermal policy object is initialized by JSON policy file. """ # JSON field definition. JSON_FIELD_NAME = 'name' @@ -14,15 +14,15 @@ def __init__(self): # Name of the policy self.name = None - # Conditions load from policy.json + # Conditions load from JSON policy file self.conditions = [] - # Actions load from policy.json + # Actions load from JSON policy file self.actions = [] def load_from_json(self, json_obj): """ - Load thermal policy from policy.json. + Load thermal policy from JSON policy file. :param json_obj: A json object representing a thermal policy. :return: """