-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
Copy pathipmihelper.py
269 lines (224 loc) · 8.83 KB
/
ipmihelper.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#!/usr/bin/python3
########################################################################
# DellEMC
#
# Module contains implementation of IpmiSensor and IpmiFru classes that
# provide Sensor's and FRU's information respectively.
#
########################################################################
import subprocess
import re
# IPMI Request Network Function Codes
NetFn_SensorEvent = 0x04
NetFn_Storage = 0x0A
# IPMI Sensor Device Commands
Cmd_GetSensorReadingFactors = 0x23
Cmd_GetSensorThreshold = 0x27
Cmd_GetSensorReading = 0x2D
# IPMI FRU Device Commands
Cmd_ReadFRUData = 0x11
def get_ipmitool_raw_output(args):
"""
Returns a list the elements of which are the individual bytes of
ipmitool raw <cmd> command output.
"""
result_bytes = list()
result = ""
command = "ipmitool raw {}".format(args)
try:
proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE,
universal_newlines=True, stderr=subprocess.STDOUT)
stdout = proc.communicate()[0]
proc.wait()
if not proc.returncode:
result = stdout.rstrip('\n')
except EnvironmentError:
pass
for i in result.split():
result_bytes.append(int(i, 16))
return result_bytes
class IpmiSensor(object):
# Sensor Threshold types and their respective bit masks
THRESHOLD_BIT_MASK = {
"LowerNonCritical" : 0,
"LowerCritical" : 1,
"LowerNonRecoverable" : 2,
"UpperNonCritical" : 3,
"UpperCritical" : 4,
"UpperNonRecoverable" : 5
}
def __init__(self, sensor_id, is_discrete=False):
self.id = sensor_id
self.is_discrete = is_discrete
def _get_converted_sensor_reading(self, raw_value):
"""
Returns a 2 element tuple(bool, int) in which first element
provides the validity of the reading and the second element is
the converted sensor reading
"""
# Get Sensor Reading Factors
cmd_args = "{} {} {} {}".format(NetFn_SensorEvent,
Cmd_GetSensorReadingFactors,
self.id, raw_value)
factors = get_ipmitool_raw_output(cmd_args)
if len(factors) != 7:
return False, 0
# Compute Twos complement
def get_twos_complement(val, bits):
if val & (1 << (bits - 1)):
val = val - (1 << bits)
return val
# Calculate actual sensor value from the raw sensor value
# using the sensor reading factors.
M = get_twos_complement(((factors[2] & 0xC0) << 8) | factors[1], 10)
B = get_twos_complement(((factors[4] & 0xC0) << 8) | factors[3], 10)
R_exp = get_twos_complement((factors[6] & 0xF0) >> 4, 4)
B_exp = get_twos_complement(factors[6] & 0x0F, 4)
converted_reading = ((M * raw_value) + (B * 10**B_exp)) * 10**R_exp
return True, converted_reading
def get_reading(self):
"""
For Threshold sensors, returns the sensor reading.
For Discrete sensors, returns the state value.
Returns:
A tuple (bool, int) where the first element provides the
validity of the reading and the second element provides the
sensor reading/state value.
"""
# Get Sensor Reading
cmd_args = "{} {} {}".format(NetFn_SensorEvent, Cmd_GetSensorReading,
self.id)
output = get_ipmitool_raw_output(cmd_args)
if len(output) != 4:
return False, 0
# Check reading/state unavailable
if output[1] & 0x20:
return False, 0
if self.is_discrete:
state = ((output[3] & 0x7F) << 8) | output[2]
return True, state
else:
return self._get_converted_sensor_reading(output[0])
def get_threshold(self, threshold_type):
"""
Returns the sensor's threshold value for a given threshold type.
Args:
threshold_type (str) - one of the below mentioned
threshold type strings
"LowerNonCritical"
"LowerCritical"
"LowerNonRecoverable"
"UpperNonCritical"
"UpperCritical"
"UpperNonRecoverable"
Returns:
A tuple (bool, int) where the first element provides the
validity of that threshold and second element provides the
threshold value.
"""
# Thresholds are not valid for discrete sensors
if self.is_discrete:
raise TypeError("Threshold is not applicable for Discrete Sensor")
if threshold_type not in list(self.THRESHOLD_BIT_MASK.keys()):
raise ValueError("Invalid threshold type {} provided. Valid types "
"are {}".format(threshold_type,
list(self.THRESHOLD_BIT_MASK.keys())))
bit_mask = self.THRESHOLD_BIT_MASK[threshold_type]
# Get Sensor Threshold
cmd_args = "{} {} {}".format(NetFn_SensorEvent, Cmd_GetSensorThreshold,
self.id)
thresholds = get_ipmitool_raw_output(cmd_args)
if len(thresholds) != 7:
return False, 0
valid_thresholds = thresholds.pop(0)
# Check whether particular threshold is readable
if valid_thresholds & (1 << bit_mask):
return self._get_converted_sensor_reading(thresholds[bit_mask])
else:
return False, 0
class IpmiFru(object):
def __init__(self, fru_id):
self.id = fru_id
def _get_ipmitool_fru_print(self):
result = ""
command = "ipmitool fru print {}".format(self.id)
try:
proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE,
universal_newlines=True, stderr=subprocess.STDOUT)
stdout = proc.communicate()[0]
proc.wait()
if not proc.returncode:
result = stdout.rstrip('\n')
except EnvironmentError:
pass
return result
def _get_from_fru(self, info):
"""
Returns a string containing the info from FRU
"""
fru_output = self._get_ipmitool_fru_print()
if not fru_output:
return "NA"
info_req = re.search(r"%s\s*:(.*)" % info, fru_output)
if not info_req:
return "NA"
return info_req.group(1).strip()
def get_board_serial(self):
"""
Returns a string containing the Serial Number of the device.
"""
return self._get_from_fru('Board Serial')
def get_board_part_number(self):
"""
Returns a string containing the Part Number of the device.
"""
return self._get_from_fru('Board Part Number')
def get_board_mfr_id(self):
"""
Returns a string containing the manufacturer id of the FRU.
"""
return self._get_from_fru('Board Mfg')
def get_board_product(self):
"""
Returns a string containing the manufacturer id of the FRU.
"""
return self._get_from_fru('Board Product')
def get_fru_data(self, offset, count=1):
"""
Reads and returns the FRU data at the provided offset.
Args:
offset (int) - FRU offset to read
count (int) - Number of bytes to read [optional, default = 1]
Returns:
A tuple (bool, list(int)) where the first element provides
the validity of the data read and the second element is a
list, the elements of which are the individual bytes of the
FRU data read.
"""
result_bytes = list()
is_valid = True
result = ""
offset_LSB = offset & 0xFF
offset_MSB = offset & 0xFF00
command = "ipmitool raw {} {} {} {} {} {}".format(NetFn_Storage,
Cmd_ReadFRUData,
self.id, offset_LSB,
offset_MSB, count)
try:
proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE,
universal_newlines=True, stderr=subprocess.STDOUT)
stdout = proc.communicate()[0]
proc.wait()
if not proc.returncode:
result = stdout.rstrip('\n')
except EnvironmentError:
is_valid = False
if (not result) or (not is_valid):
return False, result_bytes
for i in result.split():
result_bytes.append(int(i, 16))
read_count = result_bytes.pop(0)
if read_count != count:
return False, result_bytes
else:
return True, result_bytes