Skip to content

Commit 9036e15

Browse files
[thermal control] Backport changes from master to 201911 branch (#90)
1 parent ee60f54 commit 9036e15

File tree

11 files changed

+443
-2
lines changed

11 files changed

+443
-2
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1-
build/
1+
*.pyc
22
*/__pycache__/
3+
build/
34
sonic_platform_common.egg-info/
5+
.cache

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
'sonic_platform_base.sonic_eeprom',
1818
'sonic_platform_base.sonic_sfp',
1919
'sonic_platform_base.sonic_ssd',
20+
'sonic_platform_base.sonic_thermal_control',
2021
'sonic_psu',
2122
'sonic_sfp',
2223
],
@@ -33,5 +34,5 @@
3334
'Programming Language :: Python :: 3.6',
3435
'Topic :: Utilities',
3536
],
36-
keywords='sonic SONiC platform hardware interface api API',
37+
keywords='sonic SONiC platform hardware interface api API'
3738
)

sonic_platform_base/chassis_base.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,14 @@ def get_thermal(self, index):
333333

334334
return thermal
335335

336+
def get_thermal_manager(self):
337+
"""
338+
Retrieves thermal manager class on this chassis
339+
:return: A class derived from ThermalManagerBase representing the
340+
specified thermal manager. ThermalManagerBase is returned as default
341+
"""
342+
raise NotImplementedError
343+
336344
##############################################
337345
# SFP methods
338346
##############################################

sonic_platform_base/sonic_thermal_control/__init__.py

Whitespace-only changes.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from .thermal_json_object import ThermalJsonObject
2+
3+
4+
class ThermalPolicyActionBase(ThermalJsonObject):
5+
"""
6+
Base class for thermal action. Once all thermal conditions in a thermal policy are matched,
7+
all predefined thermal action will be executed.
8+
"""
9+
def execute(self, thermal_info_dict):
10+
"""
11+
Take action when thermal condition matches. For example, adjust speed of fan or shut
12+
down the switch.
13+
:param thermal_info_dict: A dictionary stores all thermal information.
14+
:return:
15+
"""
16+
raise NotImplementedError
17+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from .thermal_json_object import ThermalJsonObject
2+
3+
4+
class ThermalPolicyConditionBase(ThermalJsonObject):
5+
"""
6+
Base class for thermal condition
7+
"""
8+
def is_match(self, thermal_info_dict):
9+
"""
10+
Indicate if this condition is matched.
11+
:param thermal_info_dict: A dictionary stores all thermal information.
12+
:return: True if condition matched else False.
13+
"""
14+
raise NotImplementedError
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from .thermal_json_object import ThermalJsonObject
2+
3+
4+
class ThermalPolicyInfoBase(object):
5+
"""
6+
Base class for thermal information
7+
"""
8+
def collect(self, chassis):
9+
"""
10+
Collect thermal information for thermal policy.
11+
:param chassis: The chassis object.
12+
:return:
13+
"""
14+
raise NotImplementedError
15+
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
class ThermalJsonObject(object):
2+
"""
3+
Base class for thermal json object.
4+
"""
5+
# JSON field definition
6+
JSON_FIELD_TYPE = 'type'
7+
8+
# Dictionary of ThermalJsonObject-derived class representing all thermal json types
9+
_object_type_dict = {}
10+
11+
def load_from_json(self, json_obj):
12+
"""
13+
Initialize this object by a json object. The json object is read from policy json file.
14+
Derived class can define any field in policy json file and interpret them in this function.
15+
:param json_obj: A json object representing an object.
16+
:return:
17+
"""
18+
pass
19+
20+
def __eq__(self, other):
21+
"""
22+
Compare input object with this object, return True if equal. Subclass should override this
23+
if necessary.
24+
:param other: Object to compare with.
25+
:return: True if equal else False
26+
"""
27+
return self.__class__ == other.__class__
28+
29+
@classmethod
30+
def register_concrete_type(cls, type_name, object_type):
31+
"""
32+
Register a concrete class by type name. The concrete class must derive from
33+
ThermalJsonObject.
34+
:param type_name: Name of the class.
35+
:param object_type: A concrete class.
36+
:return:
37+
"""
38+
if type_name not in cls._object_type_dict:
39+
cls._object_type_dict[type_name] = object_type
40+
else:
41+
raise Exception('ThermalJsonObject type {} already exists'.format(type_name))
42+
43+
@classmethod
44+
def get_type(cls, json_obj):
45+
"""
46+
Get a concrete class by json object. The json object represents an object and must
47+
have a 'type' field. This function returns a pre-registered concrete class if the specific
48+
'type' is found.
49+
:param json_obj: A json object representing an action.
50+
:return: A concrete class if requested type exists; Otherwise None.
51+
"""
52+
if ThermalJsonObject.JSON_FIELD_TYPE in json_obj:
53+
type_str = json_obj[ThermalJsonObject.JSON_FIELD_TYPE]
54+
if type_str in cls._object_type_dict:
55+
return cls._object_type_dict[type_str]
56+
else:
57+
raise Exception('ThermalJsonObject type {} not found'.format(type_str) )
58+
59+
raise Exception('Invalid policy file, {} field must be presented'.format(ThermalJsonObject.JSON_FIELD_TYPE))
60+
61+
62+
def thermal_json_object(type_name):
63+
"""
64+
Decorator to auto register a ThermalJsonObject-derived class
65+
:param type_name: Type name of the concrete class which corresponding to the 'type' field of
66+
a condition, action or info.
67+
:return: Wrapper function
68+
"""
69+
def wrapper(object_type):
70+
ThermalJsonObject.register_concrete_type(type_name, object_type)
71+
return object_type
72+
return wrapper
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import json
2+
from .thermal_policy import ThermalPolicy
3+
from .thermal_json_object import ThermalJsonObject
4+
5+
6+
class ThermalManagerBase(object):
7+
"""
8+
Base class of ThermalManager representing a manager to control all thermal policies.
9+
"""
10+
# JSON field definition.
11+
JSON_FIELD_POLICIES = 'policies'
12+
JSON_FIELD_INFO_TYPES = 'info_types'
13+
JSON_FIELD_POLICY_NAME = 'name'
14+
JSON_FIELD_THERMAL_ALGORITHM = "thermal_control_algorithm"
15+
JSON_FIELD_FAN_SPEED_WHEN_SUSPEND = "fan_speed_when_suspend"
16+
JSON_FIELD_RUN_AT_BOOT_UP = "run_at_boot_up"
17+
18+
# Dictionary of ThermalPolicy objects.
19+
_policy_dict = {}
20+
21+
# Dictionary of thermal information objects. A thermal information object is used by Thermal Policy
22+
_thermal_info_dict = {}
23+
24+
_fan_speed_when_suspend = None
25+
26+
_run_thermal_algorithm_at_boot_up = None
27+
28+
@classmethod
29+
def initialize(cls):
30+
"""
31+
Initialize thermal manager, including register thermal condition types and thermal action types
32+
and any other vendor specific initialization.
33+
:return:
34+
"""
35+
pass
36+
37+
@classmethod
38+
def deinitialize(cls):
39+
"""
40+
Destroy thermal manager, including any vendor specific cleanup. The default behavior of this function
41+
is a no-op.
42+
:return:
43+
"""
44+
pass
45+
46+
@classmethod
47+
def start_thermal_control_algorithm(cls):
48+
"""
49+
Start vendor specific thermal control algorithm. The default behavior of this function is a no-op.
50+
:return:
51+
"""
52+
pass
53+
54+
@classmethod
55+
def stop_thermal_control_algorithm(cls):
56+
"""
57+
Stop vendor specific thermal control algorithm. The default behavior of this function is a no-op.
58+
:return:
59+
"""
60+
pass
61+
62+
@classmethod
63+
def load(cls, policy_file_name):
64+
"""
65+
Load all thermal policies from JSON policy file. An example looks like:
66+
{
67+
"thermal_control_algorithm": {
68+
"run_at_boot_up": "false",
69+
"fan_speed_when_suspend": "60"
70+
},
71+
"info_types": [
72+
{
73+
"type": "fan_info" # collect fan information for each iteration
74+
},
75+
{
76+
"type": "psu_info" # collect psu information for each iteration
77+
}
78+
],
79+
"policies": [
80+
{
81+
"name": "any fan absence", # if any fan absence, set all fan speed to 100% and disable thermal control algorithm
82+
"conditions": [
83+
{
84+
"type": "fan.any.absence" # see sonic-platform-daemons.sonic-thermalctld.thermal_policy.thermal_conditions
85+
}
86+
],
87+
"actions": [
88+
{
89+
"type": "fan.all.set_speed", # see sonic-platform-daemons.sonic-thermalctld.thermal_policy.thermal_actions
90+
"speed": "100"
91+
},
92+
{
93+
"type": "thermal_control.control",
94+
"status": "false"
95+
}
96+
]
97+
},
98+
{
99+
"name": "all fan absence", # if all fan absence, shutdown the switch
100+
"conditions": [
101+
{
102+
"type": "fan.all.absence"
103+
}
104+
],
105+
"actions": [
106+
{
107+
"type": "switch.shutdown"
108+
}
109+
]
110+
}
111+
]
112+
}
113+
:param policy_file_name: Path of JSON policy file.
114+
:return:
115+
"""
116+
with open(policy_file_name, 'r') as policy_file:
117+
json_obj = json.load(policy_file)
118+
if cls.JSON_FIELD_POLICIES in json_obj:
119+
json_policies = json_obj[cls.JSON_FIELD_POLICIES]
120+
for json_policy in json_policies:
121+
cls._load_policy(json_policy)
122+
123+
if cls.JSON_FIELD_INFO_TYPES in json_obj:
124+
for json_info in json_obj[cls.JSON_FIELD_INFO_TYPES]:
125+
info_type = ThermalJsonObject.get_type(json_info)
126+
info_obj = info_type()
127+
cls._thermal_info_dict[json_info[ThermalJsonObject.JSON_FIELD_TYPE]] = info_obj
128+
129+
if cls.JSON_FIELD_THERMAL_ALGORITHM in json_obj:
130+
json_thermal_algorithm_config = json_obj[cls.JSON_FIELD_THERMAL_ALGORITHM]
131+
if cls.JSON_FIELD_RUN_AT_BOOT_UP in json_thermal_algorithm_config:
132+
cls._run_thermal_algorithm_at_boot_up = \
133+
True if json_thermal_algorithm_config[cls.JSON_FIELD_RUN_AT_BOOT_UP].lower() == 'true' else False
134+
135+
if cls.JSON_FIELD_FAN_SPEED_WHEN_SUSPEND in json_thermal_algorithm_config:
136+
# if the string is not a valid int, let it raise
137+
cls._fan_speed_when_suspend = \
138+
int(json_thermal_algorithm_config[cls.JSON_FIELD_FAN_SPEED_WHEN_SUSPEND])
139+
140+
@classmethod
141+
def _load_policy(cls, json_policy):
142+
"""
143+
Load a policy object from a JSON object.
144+
:param json_policy: A JSON object representing a thermal policy.
145+
:return:
146+
"""
147+
if cls.JSON_FIELD_POLICY_NAME in json_policy:
148+
name = json_policy[cls.JSON_FIELD_POLICY_NAME]
149+
if name in cls._policy_dict:
150+
raise Exception('Policy {} already exists'.format(name))
151+
152+
policy = ThermalPolicy()
153+
policy.load_from_json(json_policy)
154+
policy.validate_duplicate_policy(cls._policy_dict.values())
155+
cls._policy_dict[name] = policy
156+
else:
157+
raise Exception('{} not found in policy'.format(cls.JSON_FIELD_POLICY_NAME))
158+
159+
@classmethod
160+
def run_policy(cls, chassis):
161+
"""
162+
Collect thermal information, run each policy, if one policy matches, execute the policy's action.
163+
:param chassis: The chassis object.
164+
:return:
165+
"""
166+
if not cls._policy_dict:
167+
return
168+
169+
cls._collect_thermal_information(chassis)
170+
171+
for policy in cls._policy_dict.values():
172+
if policy.is_match(cls._thermal_info_dict):
173+
policy.do_action(cls._thermal_info_dict)
174+
175+
@classmethod
176+
def _collect_thermal_information(cls, chassis):
177+
"""
178+
Collect thermal information. This function will be called before run_policy.
179+
:param chassis: The chassis object.
180+
:return:
181+
"""
182+
for thermal_info in cls._thermal_info_dict.values():
183+
thermal_info.collect(chassis)
184+
185+
@classmethod
186+
def init_thermal_algorithm(cls, chassis):
187+
"""
188+
Initialize thermal algorithm according to policy file.
189+
:param chassis: The chassis object.
190+
:return:
191+
"""
192+
if cls._run_thermal_algorithm_at_boot_up is not None:
193+
if cls._run_thermal_algorithm_at_boot_up:
194+
cls.start_thermal_control_algorithm()
195+
else:
196+
cls.stop_thermal_control_algorithm()
197+
if cls._fan_speed_when_suspend is not None:
198+
for fan in chassis.get_all_fans():
199+
fan.set_speed(cls._fan_speed_when_suspend)
200+
201+
for psu in chassis.get_all_psus():
202+
for fan in psu.get_all_fans():
203+
fan.set_speed(cls._fan_speed_when_suspend)

0 commit comments

Comments
 (0)