blob: 4d5f54bb04e39a0d92f5c989df51ba4e8e79d1bd [file] [log] [blame]
Michael Walshb973f9b2018-09-19 16:00:06 -05001#!/usr/bin/env python
2
3r"""
4This module provides many valuable openbmctool.py functions such as
5openbmctool_execute_command.
6"""
7
Michael Walshd064ac92018-09-21 16:42:03 -05008import gen_print as gp
Michael Walshb973f9b2018-09-19 16:00:06 -05009import gen_cmd as gc
10import gen_valid as gv
Michael Walshd064ac92018-09-21 16:42:03 -050011import gen_misc as gm
12import var_funcs as vf
Michael Walshb973f9b2018-09-19 16:00:06 -050013from robot.libraries.BuiltIn import BuiltIn
14import re
Michael Walsh1793aeb2018-09-27 16:32:53 -050015import tempfile
Michael Walshb973f9b2018-09-19 16:00:06 -050016
17
18def openbmctool_execute_command(command_string,
19 *args,
20 **kwargs):
21 r"""
22 Run the command string as an argument to the openbmctool.py program and
23 return the stdout and the return code.
24
25 This function provides several benefits versus calling shell_cmd directly:
26 - This function will obtain the global values for OPENBMC_HOST,
27 OPENBMC_USERNAME, etc.
28 - This function will compose the openbmctool.py command string which
29 includes the caller's command_string.
30 - The openbmctool.py produces additional text that clutters the output.
31 This function will remove such text. Example:
32 Attempting login...
33 <actual output>
34 User root has been logged out
35
36 NOTE: If you have pipe symbols in your command_string, they must be
37 surrounded by a single space on each side (see example below).
38
39 Example code:
40 ${rc} ${output}= Openbmctool Execute Command fru status | head -n 2
41
42 Example output:
43 #(CDT) 2018/09/19 15:16:58 - Issuing: set -o pipefail ; openbmctool.py -H hostname -U root -P ********
44 ... fru status | tail -n +1 | egrep -v 'Attempting login|User [^ ]+ hasbeen logged out' | head -n 2
45 Component | Is a FRU | Present | Functional | Has Logs
46 cpu0 | Yes | Yes | Yes | No
47
48 Description of arguments:
49 command_string The command string to be passed to the
50 openbmctool.py program.
Michael Walsh58b11ac2018-09-20 15:24:37 -050051 All remaining arguments are passed directly to shell_cmd. See the
52 shell_cmd prolog for details on allowable arguments. The caller may code
53 them directly as in this example:
54 openbmctool_execute_command("my command", quiet=1, max_attempts=2).
55 Python will do the work of putting these values into args/kwargs.
Michael Walshb973f9b2018-09-19 16:00:06 -050056 """
57
58 if not gv.valid_value(command_string):
59 return "", "", 1
60
61 # Get global BMC variable values.
62 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}", default="")
63 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}",
64 default="")
65 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}",
66 default="")
67 if not gv.valid_value(openbmc_host):
68 return "", "", 1
69 if not gv.valid_value(openbmc_username):
70 return "", "", 1
71 if not gv.valid_value(openbmc_password):
72 return "", "", 1
73
74 # Break the caller's command up into separate piped commands. For
75 # example, the user may have specified "fru status | head -n 2" which
Michael Walsh1793aeb2018-09-27 16:32:53 -050076 # would be broken into 2 list elements. We will also break on ">"
77 # (re-direct).
78 pipeline = map(str.strip, re.split(r' ([\|>]) ', str(command_string)))
Michael Walshb973f9b2018-09-19 16:00:06 -050079 # The "tail" command below prevents a "egrep: write error: Broken pipe"
80 # error if the user is piping the output to a sub-process.
81 # Use "egrep -v" to get rid of editorial output from openbmctool.py.
Michael Walsh1793aeb2018-09-27 16:32:53 -050082 pipeline.insert(1, "| tail -n +1 | egrep -v 'Attempting login|User [^ ]+"
83 " has been logged out'")
Michael Walshb973f9b2018-09-19 16:00:06 -050084
Michael Walsh7384b9a2018-09-26 17:22:54 -050085 command_string = "set -o pipefail ; python3 $(which openbmctool.py) -H "\
86 + openbmc_host + " -U " + openbmc_username + " -P " + openbmc_password\
Michael Walsh1793aeb2018-09-27 16:32:53 -050087 + " " + " ".join(pipeline)
Michael Walshb973f9b2018-09-19 16:00:06 -050088
89 return gc.shell_cmd(command_string, *args, **kwargs)
Michael Walshd064ac92018-09-21 16:42:03 -050090
91
92def get_fru_status():
93 r"""
94 Get the fru status and return as a list of dictionaries.
95
96 Example robot code:
97
98 ${fru_status}= Get Fru Status
99 Rprint Vars 1 fru_status
100
101 Example result (excerpt):
102
103 fru_status:
104 fru_status[0]:
105 [component]: cpu0
106 [is_a]: Yes
107 [fru]: Yes
108 [present]: Yes
109 [functional]: No
110 fru_status[1]:
111 [component]: cpu0-core0
112 [is_a]: No
113 [fru]: Yes
114 [present]: Yes
115 [functional]: No
116 ...
117 """
118 rc, output = openbmctool_execute_command("fru status", print_output=False,
119 ignore_err=False)
120 # Example value for output (partial):
121 # Component | Is a FRU | Present | Functional | Has Logs
122 # cpu0 | Yes | Yes | Yes | No
123 # cpu0-core0 | No | Yes | Yes | No
124 # ...
125
126 # Replace spaces with underscores in field names (e.g. "Is a FRU" becomes
127 # "Is_a_FRU").
128 output = re.sub("([^ \\|])[ ]([^ ])", "\\1_\\2", output)
129 output = re.sub("([^ \\|])[ ]([^ ])", "\\1_\\2", output)
130
131 return vf.outbuf_to_report(output, field_delim="|")
132
133
134def get_fru_print(parse_json=True):
135 r"""
136 Get the output of the fru print command and return it either as raw JSON
137 data or as a list of dictionaries.
138
139 Example robot code:
140
141 ${fru_print}= Get Fru Print parse_json=${False}
142 Log to Console ${fru_print}
143
144 Example result (excerpt):
145
146 {
147 "data": {
148 "/xyz/openbmc_project/inventory/system": {
149 "AssetTag": "",
150 "BuildDate": "",
151 "Cached": false,
152 "FieldReplaceable": false,
153 "Manufacturer": "",
154 "Model": "xxxxxxxx",
155 "PartNumber": "",
156 "Present": true,
157 "PrettyName": "",
158 "SerialNumber": "13183FA"
159 },
160 "/xyz/openbmc_project/inventory/system/chassis": {
161 "AirCooled": true,
162 "WaterCooled": false
163 },
164 ...
165
166 Example robot code:
167
168 ${fru_print}= Get Fru Print
169 Rprint Vars 1 fru_print
170
171 Example result (excerpt):
172
173 fru_print:
174 fru_print[0]:
175 [data]:
176 [/xyz/openbmc_project/inventory/system]:
177 [AssetTag]: <blank>
178 [BuildDate]: <blank>
179 [Cached]: False
180 [FieldReplaceable]: False
181 [Manufacturer]: <blank>
182 [Model]: xxxxxxxx
183 [PartNumber]: <blank>
184 [Present]: True
185 [PrettyName]: <blank>
186 [SerialNumber]: 13183FA
187 [/xyz/openbmc_project/inventory/system/chassis]:
188 [AirCooled]: True
189 [WaterCooled]: False
190 ...
191
192 Description of argument(s):
193 parse_json Indicates that the raw JSON data should
194 parsed into a list of dictionaries.
195 """
196
197 rc, output = openbmctool_execute_command("fru print", print_output=False,
198 ignore_err=False)
199 if parse_json:
200 return gm.json_loads_multiple(output)
201 else:
202 return output
203
204
205def get_fru_list(parse_json=True):
206 r"""
207 Get the output of the fru list command and return it either as raw JSON
208 data or as a list of dictionaries.
209
210 Example robot code:
211
212 ${fru_list}= Get Fru List parse_json=${False}
213 Log to Console ${fru_list}
214
215 Example result (excerpt):
216
217 {
218 "data": {
219 "/xyz/openbmc_project/inventory/system": {
220 "AssetTag": "",
221 "BuildDate": "",
222 "Cached": false,
223 "FieldReplaceable": false,
224 "Manufacturer": "",
225 "Model": "xxxxxxxx",
226 "PartNumber": "",
227 "Present": true,
228 "PrettyName": "",
229 "SerialNumber": "13183FA"
230 },
231 "/xyz/openbmc_project/inventory/system/chassis": {
232 "AirCooled": true,
233 "WaterCooled": false
234 },
235 ...
236
237 Example robot code:
238
239 ${fru_list}= Get Fru List
240 Rprint Vars 1 fru_list
241
242 Example result (excerpt):
243
244 fru_list:
245 fru_list[0]:
246 [data]:
247 [/xyz/openbmc_project/inventory/system]:
248 [AssetTag]: <blank>
249 [BuildDate]: <blank>
250 [Cached]: False
251 [FieldReplaceable]: False
252 [Manufacturer]: <blank>
253 [Model]: xxxxxxxx
254 [PartNumber]: <blank>
255 [Present]: True
256 [PrettyName]: <blank>
257 [SerialNumber]: 13183FA
258 [/xyz/openbmc_project/inventory/system/chassis]:
259 [AirCooled]: True
260 [WaterCooled]: False
261 ...
262
263 Description of argument(s):
264 parse_json Indicates that the raw JSON data should
265 parsed into a list of dictionaries.
266 """
267
268 rc, output = openbmctool_execute_command("fru list", print_output=False,
269 ignore_err=False)
270 if parse_json:
271 return gm.json_loads_multiple(output)
272 else:
273 return output
274
275
276def get_sensors_print():
277
278 r"""
279 Get the output of the sensors print command and return as a list of
280 dictionaries.
281
282 Example robot code:
283
284 ${sensors_print}= Get Sensors Print
285 Rprint Vars 1 sensors_print
286
287 Example result (excerpt):
288
289 sensors_print:
290 sensors_print[0]:
291 [sensor]: OCC0
292 [type]: Discrete
293 [units]: N/A
294 [value]: Active
295 [target]: Active
296 sensors_print[1]:
297 [sensor]: OCC1
298 [type]: Discrete
299 [units]: N/A
300 [value]: Active
301 [target]: Active
302 ...
303 """
304 rc, output = openbmctool_execute_command("sensors print",
305 print_output=False,
306 ignore_err=False)
307 # Example value for output (partial):
308 # sensor | type | units | value | target
309 # OCC0 | Discrete | N/A | Active | Active
310 # OCC1 | Discrete | N/A | Active | Active
311
312 return vf.outbuf_to_report(output, field_delim="|")
313
314
315def get_sensors_list():
316
317 r"""
318 Get the output of the sensors list command and return as a list of
319 dictionaries.
320
321 Example robot code:
322
323 ${sensors_list}= Get Sensors List
324 Rprint Vars 1 sensors_list
325
326 Example result (excerpt):
327
328 sensors_list:
329 sensors_list[0]:
330 [sensor]: OCC0
331 [type]: Discrete
332 [units]: N/A
333 [value]: Active
334 [target]: Active
335 sensors_list[1]:
336 [sensor]: OCC1
337 [type]: Discrete
338 [units]: N/A
339 [value]: Active
340 [target]: Active
341 ...
342 """
343 rc, output = openbmctool_execute_command("sensors list",
344 print_output=False,
345 ignore_err=False)
346 # Example value for output (partial):
347 # sensor | type | units | value | target
348 # OCC0 | Discrete | N/A | Active | Active
349 # OCC1 | Discrete | N/A | Active | Active
350
351 return vf.outbuf_to_report(output, field_delim="|")
352
353
354def get_openbmctool_version():
355 r"""
356 Get the openbmctool.py version and return it.
357
358 Example robot code:
359 ${openbmctool_version}= Get Openbmctool Version
360 Rprint Vars openbmctool_version
361
362 Example result (excerpt):
363 openbmctool_version: 1.06
364 """
365 rc, output = openbmctool_execute_command("-V | cut -f 2 -d ' '",
366 print_output=False,
367 ignore_err=False)
368 return output
Michael Walsh1793aeb2018-09-27 16:32:53 -0500369
370
371def service_data_files():
372 r"""
373 Return a complete list of file names that are expected to be created by
374 the collect_service_data command.
375 """
376
377 return\
378 [
379 "inventory.txt",
380 "sensorReadings.txt",
381 "ledStatus.txt",
382 "SELshortlist.txt",
383 "parsedSELs.txt",
384 "bmcFullRaw.txt"
385 ]
386
387
388def collect_service_data(verify=False):
389 r"""
390 Run the collect_service_data command and return a list of files generated
391 by the command.
392
393 Description of argument(s):
394 verify If set, verify that all files which can be
395 created by collect_service_data did, in
396 fact, get created.
397 """
398
399 # Route the output of collect_service_data to a file for easier parsing.
400 temp = tempfile.NamedTemporaryFile()
401 temp_file_path = temp.name
402 openbmctool_execute_command("collect_service_data > " + temp_file_path,
403 ignore_err=False)
404 # Isolate the file paths in the collect_service_data output. We're
405 # looking for output lines like this from which to extract the file paths:
406 # Inventory collected and stored in /tmp/dummy--2018-09-26_17.59.18/inventory.txt
407 rc, file_paths = gc.shell_cmd("egrep 'collected and' " + temp_file_path
408 # + " | sed -re 's#.*/tmp#/tmp#g'",
409 + " | sed -re 's#[^/]*/#/#'",
410 quiet=1, print_output=0)
411 # Example file_paths value:
412 # /tmp/dummy--2018-09-26_17.59.18/inventory.txt
413 # /tmp/dummy--2018-09-26_17.59.18/sensorReadings.txt
414 # etc.
415 # Convert from output to list.
416 collect_service_data_file_paths =\
417 list(filter(None, file_paths.split("\n")))
418 if int(verify):
419 # Create a list of files by stripping the dir names from the elements
420 # of collect_service_data_file_paths.
421 files_obtained = [re.sub(r".*/", "", file_path)
422 for file_path in collect_service_data_file_paths]
423 files_expected = service_data_files()
424 files_missing = list(set(files_expected) - set(files_obtained))
425 if len(files_missing) > 0:
426 gp.printn("collect_service_data output:\n"
427 + gm.file_to_str(temp_file_path))
428 err_msg = "The following files are missing from the list of files"
429 err_msg += " returned by collect_service_data:\n"
430 err_msg += gp.sprint_var(files_missing)
431 err_msg += gp.sprint_var(collect_service_data_file_paths)
432 BuiltIn().fail(gp.sprint_error(err_msg))
433
434 return collect_service_data_file_paths
435
436
437def health_check_fields():
438 r"""
439 Return a complete list of field names returned by the health_check command.
440 """
441
442 return\
443 [
444 "hardware_status",
445 "performance"
446 ]
447
448
449def get_health_check(verify=False):
450 r"""
451 Get the health_check information and return as a dictionary.
452
453 Example robot code:
454
455 ${health_check}= Get Health Check
456 Rpvars 1 health_check
457
458 Example result:
459
460 health_check:
461 [hardware_status]: OK
462 [performance]: OK
463
464 Description of argument(s):
465 verify If set, verify that all all expected
466 field_names are generated by the
467 health_check command.
468 """
469
470 rc, output = openbmctool_execute_command("health_check",
471 print_output=False,
472 ignore_err=False)
473 health_check = vf.key_value_outbuf_to_dict(output, delim=":")
474 if int(verify):
475 # Create a list of files by stripping the dir names from the elements
476 # of collect_service_data_file_paths.
477 fields_obtained = health_check.keys()
478 fields_expected = health_check_fields()
479 fields_missing = list(set(fields_expected) - set(fields_obtained))
480 if len(fields_missing) > 0:
481 err_msg = "The following fields are missing from the output of"
482 err_msg += " health_check:\n"
483 err_msg += gp.sprint_var(fields_missing)
484 err_msg += gp.sprint_var(health_check)
485 BuiltIn().fail(gp.sprint_error(err_msg))
486
487 return health_check