blob: f89683dde3cd7d1c65f823490a9145ce40673987 [file] [log] [blame]
George Keishinge7e91712021-09-03 11:28:44 -05001#!/usr/bin/env python3
Michael Walshb973f9b2018-09-19 16:00:06 -05002
3r"""
4This module provides many valuable openbmctool.py functions such as
5openbmctool_execute_command.
6"""
7
George Keishinge635ddc2022-12-08 07:38:02 -06008import collections
9import json
Patrick Williams20f38712022-12-08 06:18:26 -060010import re
11import tempfile
12
13import gen_cmd as gc
14import gen_misc as gm
15import gen_print as gp
16import gen_valid as gv
17import utils as utils
18import var_funcs as vf
19from robot.libraries.BuiltIn import BuiltIn
Michael Walshb973f9b2018-09-19 16:00:06 -050020
21
Patrick Williams20f38712022-12-08 06:18:26 -060022def openbmctool_execute_command(command_string, *args, **kwargs):
Michael Walshb973f9b2018-09-19 16:00:06 -050023 r"""
24 Run the command string as an argument to the openbmctool.py program and
25 return the stdout and the return code.
26
27 This function provides several benefits versus calling shell_cmd directly:
28 - This function will obtain the global values for OPENBMC_HOST,
29 OPENBMC_USERNAME, etc.
30 - This function will compose the openbmctool.py command string which
31 includes the caller's command_string.
32 - The openbmctool.py produces additional text that clutters the output.
33 This function will remove such text. Example:
34 Attempting login...
35 <actual output>
36 User root has been logged out
37
38 NOTE: If you have pipe symbols in your command_string, they must be
39 surrounded by a single space on each side (see example below).
40
41 Example code:
42 ${rc} ${output}= Openbmctool Execute Command fru status | head -n 2
43
44 Example output:
45 #(CDT) 2018/09/19 15:16:58 - Issuing: set -o pipefail ; openbmctool.py -H hostname -U root -P ********
46 ... fru status | tail -n +1 | egrep -v 'Attempting login|User [^ ]+ hasbeen logged out' | head -n 2
47 Component | Is a FRU | Present | Functional | Has Logs
48 cpu0 | Yes | Yes | Yes | No
49
50 Description of arguments:
51 command_string The command string to be passed to the
52 openbmctool.py program.
Michael Walsh58b11ac2018-09-20 15:24:37 -050053 All remaining arguments are passed directly to shell_cmd. See the
54 shell_cmd prolog for details on allowable arguments. The caller may code
55 them directly as in this example:
56 openbmctool_execute_command("my command", quiet=1, max_attempts=2).
57 Python will do the work of putting these values into args/kwargs.
Michael Walshb973f9b2018-09-19 16:00:06 -050058 """
59
60 if not gv.valid_value(command_string):
61 return "", "", 1
62
63 # Get global BMC variable values.
64 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}", default="")
Michael Walsh046fe222019-11-08 14:33:53 -060065 https_port = BuiltIn().get_variable_value("${HTTPS_PORT}", default="443")
Patrick Williams20f38712022-12-08 06:18:26 -060066 openbmc_username = BuiltIn().get_variable_value(
67 "${OPENBMC_USERNAME}", default=""
68 )
69 openbmc_password = BuiltIn().get_variable_value(
70 "${OPENBMC_PASSWORD}", default=""
71 )
Michael Walshb973f9b2018-09-19 16:00:06 -050072 if not gv.valid_value(openbmc_host):
73 return "", "", 1
74 if not gv.valid_value(openbmc_username):
75 return "", "", 1
76 if not gv.valid_value(openbmc_password):
77 return "", "", 1
Michael Walsh046fe222019-11-08 14:33:53 -060078 if not gv.valid_value(https_port):
79 return "", "", 1
Michael Walshb973f9b2018-09-19 16:00:06 -050080
81 # Break the caller's command up into separate piped commands. For
82 # example, the user may have specified "fru status | head -n 2" which
Michael Walsh1793aeb2018-09-27 16:32:53 -050083 # would be broken into 2 list elements. We will also break on ">"
84 # (re-direct).
Patrick Williams20f38712022-12-08 06:18:26 -060085 pipeline = list(
86 map(str.strip, re.split(r" ([\|>]) ", str(command_string)))
87 )
Michael Walshb973f9b2018-09-19 16:00:06 -050088 # The "tail" command below prevents a "egrep: write error: Broken pipe"
89 # error if the user is piping the output to a sub-process.
90 # Use "egrep -v" to get rid of editorial output from openbmctool.py.
Patrick Williams20f38712022-12-08 06:18:26 -060091 pipeline.insert(
92 1,
George Keishing4a27d692022-12-13 08:50:14 -060093 (
94 "| tail -n +1 | egrep -v 'Attempting login|User [^ ]+"
95 " has been logged out'"
96 ),
Patrick Williams20f38712022-12-08 06:18:26 -060097 )
Michael Walshb973f9b2018-09-19 16:00:06 -050098
Patrick Williams20f38712022-12-08 06:18:26 -060099 command_string = (
100 "set -o pipefail ; python3 $(which openbmctool.py) -H "
101 + openbmc_host
102 + ":"
103 + https_port
104 + " -U "
105 + openbmc_username
106 + " -P "
107 + openbmc_password
108 + " "
109 + " ".join(pipeline)
110 )
Michael Walshb973f9b2018-09-19 16:00:06 -0500111
112 return gc.shell_cmd(command_string, *args, **kwargs)
Michael Walshd064ac92018-09-21 16:42:03 -0500113
114
Patrick Williams20f38712022-12-08 06:18:26 -0600115def openbmctool_execute_command_json(command_string, *args, **kwargs):
Michael Walsh0e7f3012018-10-11 17:05:46 -0500116 r"""
117 Run the command string as an argument to the openbmctool.py program, parse
118 the JSON output into a dictionary and return the dictionary.
119
120 This function is a wrapper for openbmctool_execute_command (defined
121 above). The caller may provide any command string where the output will
122 be JSON data. This function will convert the JSON data to a python
123 object, verify that the 'status' field = "ok" and return the 'data'
124 sub-field to the caller.
125
126 See openbmctool_execute_command (above) for all field descriptions.
127 """
128
Patrick Williams20f38712022-12-08 06:18:26 -0600129 rc, output = openbmctool_execute_command(command_string, *args, **kwargs)
Michael Walshaaded2a2019-08-14 17:25:27 -0500130 try:
131 json_object = utils.to_json_ordered(output)
132 except json.JSONDecodeError:
133 BuiltIn().fail(gp.sprint_error(output))
134
Patrick Williams20f38712022-12-08 06:18:26 -0600135 if json_object["status"] != "ok":
Michael Walsh0e7f3012018-10-11 17:05:46 -0500136 err_msg = "Error found in JSON data returned by the openbmctool.py "
137 err_msg += "command. Expected a 'status' field value of \"ok\":\n"
138 err_msg += gp.sprint_var(json_object, 1)
139 BuiltIn().fail(gp.sprint_error(err_msg))
140
Patrick Williams20f38712022-12-08 06:18:26 -0600141 return json_object["data"]
Michael Walsh0e7f3012018-10-11 17:05:46 -0500142
143
Michael Walshd064ac92018-09-21 16:42:03 -0500144def get_fru_status():
145 r"""
146 Get the fru status and return as a list of dictionaries.
147
148 Example robot code:
149
150 ${fru_status}= Get Fru Status
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500151 Rprint Vars fru_status fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500152
153 Example result (excerpt):
154
155 fru_status:
156 fru_status[0]:
157 [component]: cpu0
158 [is_a]: Yes
159 [fru]: Yes
160 [present]: Yes
161 [functional]: No
162 fru_status[1]:
163 [component]: cpu0-core0
164 [is_a]: No
165 [fru]: Yes
166 [present]: Yes
167 [functional]: No
168 ...
169 """
Patrick Williams20f38712022-12-08 06:18:26 -0600170 rc, output = openbmctool_execute_command(
171 "fru status", print_output=False, ignore_err=False
172 )
Michael Walshd064ac92018-09-21 16:42:03 -0500173 # Example value for output (partial):
174 # Component | Is a FRU | Present | Functional | Has Logs
175 # cpu0 | Yes | Yes | Yes | No
176 # cpu0-core0 | No | Yes | Yes | No
177 # ...
178
179 # Replace spaces with underscores in field names (e.g. "Is a FRU" becomes
180 # "Is_a_FRU").
181 output = re.sub("([^ \\|])[ ]([^ ])", "\\1_\\2", output)
182 output = re.sub("([^ \\|])[ ]([^ ])", "\\1_\\2", output)
183
184 return vf.outbuf_to_report(output, field_delim="|")
185
186
187def get_fru_print(parse_json=True):
188 r"""
189 Get the output of the fru print command and return it either as raw JSON
190 data or as a list of dictionaries.
191
192 Example robot code:
193
194 ${fru_print}= Get Fru Print parse_json=${False}
195 Log to Console ${fru_print}
196
197 Example result (excerpt):
198
199 {
200 "data": {
201 "/xyz/openbmc_project/inventory/system": {
202 "AssetTag": "",
203 "BuildDate": "",
204 "Cached": false,
205 "FieldReplaceable": false,
206 "Manufacturer": "",
207 "Model": "xxxxxxxx",
208 "PartNumber": "",
209 "Present": true,
210 "PrettyName": "",
211 "SerialNumber": "13183FA"
212 },
213 "/xyz/openbmc_project/inventory/system/chassis": {
214 "AirCooled": true,
215 "WaterCooled": false
216 },
217 ...
218
219 Example robot code:
220
221 ${fru_print}= Get Fru Print
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500222 Rprint Vars fru_print fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500223
224 Example result (excerpt):
225
226 fru_print:
227 fru_print[0]:
228 [data]:
229 [/xyz/openbmc_project/inventory/system]:
230 [AssetTag]: <blank>
231 [BuildDate]: <blank>
232 [Cached]: False
233 [FieldReplaceable]: False
234 [Manufacturer]: <blank>
235 [Model]: xxxxxxxx
236 [PartNumber]: <blank>
237 [Present]: True
238 [PrettyName]: <blank>
239 [SerialNumber]: 13183FA
240 [/xyz/openbmc_project/inventory/system/chassis]:
241 [AirCooled]: True
242 [WaterCooled]: False
243 ...
244
245 Description of argument(s):
246 parse_json Indicates that the raw JSON data should
247 parsed into a list of dictionaries.
248 """
249
Patrick Williams20f38712022-12-08 06:18:26 -0600250 rc, output = openbmctool_execute_command(
251 "fru print", print_output=False, ignore_err=False
252 )
Michael Walshd064ac92018-09-21 16:42:03 -0500253 if parse_json:
254 return gm.json_loads_multiple(output)
255 else:
256 return output
257
258
259def get_fru_list(parse_json=True):
260 r"""
261 Get the output of the fru list command and return it either as raw JSON
262 data or as a list of dictionaries.
263
264 Example robot code:
265
266 ${fru_list}= Get Fru List parse_json=${False}
267 Log to Console ${fru_list}
268
269 Example result (excerpt):
270
271 {
272 "data": {
273 "/xyz/openbmc_project/inventory/system": {
274 "AssetTag": "",
275 "BuildDate": "",
276 "Cached": false,
277 "FieldReplaceable": false,
278 "Manufacturer": "",
279 "Model": "xxxxxxxx",
280 "PartNumber": "",
281 "Present": true,
282 "PrettyName": "",
283 "SerialNumber": "13183FA"
284 },
285 "/xyz/openbmc_project/inventory/system/chassis": {
286 "AirCooled": true,
287 "WaterCooled": false
288 },
289 ...
290
291 Example robot code:
292
293 ${fru_list}= Get Fru List
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500294 Rprint Vars fru_list fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500295
296 Example result (excerpt):
297
298 fru_list:
299 fru_list[0]:
300 [data]:
301 [/xyz/openbmc_project/inventory/system]:
302 [AssetTag]: <blank>
303 [BuildDate]: <blank>
304 [Cached]: False
305 [FieldReplaceable]: False
306 [Manufacturer]: <blank>
307 [Model]: xxxxxxxx
308 [PartNumber]: <blank>
309 [Present]: True
310 [PrettyName]: <blank>
311 [SerialNumber]: 13183FA
312 [/xyz/openbmc_project/inventory/system/chassis]:
313 [AirCooled]: True
314 [WaterCooled]: False
315 ...
316
317 Description of argument(s):
318 parse_json Indicates that the raw JSON data should
319 parsed into a list of dictionaries.
320 """
321
Patrick Williams20f38712022-12-08 06:18:26 -0600322 rc, output = openbmctool_execute_command(
323 "fru list", print_output=False, ignore_err=False
324 )
Michael Walshd064ac92018-09-21 16:42:03 -0500325 if parse_json:
326 return gm.json_loads_multiple(output)
327 else:
328 return output
329
330
331def get_sensors_print():
Michael Walshd064ac92018-09-21 16:42:03 -0500332 r"""
333 Get the output of the sensors print command and return as a list of
334 dictionaries.
335
336 Example robot code:
337
338 ${sensors_print}= Get Sensors Print
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500339 Rprint Vars sensors_print fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500340
341 Example result (excerpt):
342
343 sensors_print:
344 sensors_print[0]:
345 [sensor]: OCC0
346 [type]: Discrete
347 [units]: N/A
348 [value]: Active
349 [target]: Active
350 sensors_print[1]:
351 [sensor]: OCC1
352 [type]: Discrete
353 [units]: N/A
354 [value]: Active
355 [target]: Active
356 ...
357 """
Patrick Williams20f38712022-12-08 06:18:26 -0600358 rc, output = openbmctool_execute_command(
359 "sensors print", print_output=False, ignore_err=False
360 )
Michael Walshd064ac92018-09-21 16:42:03 -0500361 # Example value for output (partial):
362 # sensor | type | units | value | target
363 # OCC0 | Discrete | N/A | Active | Active
364 # OCC1 | Discrete | N/A | Active | Active
365
366 return vf.outbuf_to_report(output, field_delim="|")
367
368
369def get_sensors_list():
Michael Walshd064ac92018-09-21 16:42:03 -0500370 r"""
371 Get the output of the sensors list command and return as a list of
372 dictionaries.
373
374 Example robot code:
375
376 ${sensors_list}= Get Sensors List
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500377 Rprint Vars sensors_list fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500378
379 Example result (excerpt):
380
381 sensors_list:
382 sensors_list[0]:
383 [sensor]: OCC0
384 [type]: Discrete
385 [units]: N/A
386 [value]: Active
387 [target]: Active
388 sensors_list[1]:
389 [sensor]: OCC1
390 [type]: Discrete
391 [units]: N/A
392 [value]: Active
393 [target]: Active
394 ...
395 """
Patrick Williams20f38712022-12-08 06:18:26 -0600396 rc, output = openbmctool_execute_command(
397 "sensors list", print_output=False, ignore_err=False
398 )
Michael Walshd064ac92018-09-21 16:42:03 -0500399 # Example value for output (partial):
400 # sensor | type | units | value | target
401 # OCC0 | Discrete | N/A | Active | Active
402 # OCC1 | Discrete | N/A | Active | Active
403
404 return vf.outbuf_to_report(output, field_delim="|")
405
406
407def get_openbmctool_version():
408 r"""
409 Get the openbmctool.py version and return it.
410
411 Example robot code:
412 ${openbmctool_version}= Get Openbmctool Version
413 Rprint Vars openbmctool_version
414
415 Example result (excerpt):
416 openbmctool_version: 1.06
417 """
Patrick Williams20f38712022-12-08 06:18:26 -0600418 rc, output = openbmctool_execute_command(
419 "-V | cut -f 2 -d ' '", print_output=False, ignore_err=False
420 )
Michael Walshd064ac92018-09-21 16:42:03 -0500421 return output
Michael Walsh1793aeb2018-09-27 16:32:53 -0500422
423
424def service_data_files():
425 r"""
426 Return a complete list of file names that are expected to be created by
427 the collect_service_data command.
428 """
429
Patrick Williams20f38712022-12-08 06:18:26 -0600430 return [
431 "inventory.txt",
432 "sensorReadings.txt",
433 "ledStatus.txt",
434 "SELshortlist.txt",
435 "parsedSELs.txt",
436 "bmcFullRaw.txt",
437 ]
Michael Walsh1793aeb2018-09-27 16:32:53 -0500438
439
440def collect_service_data(verify=False):
441 r"""
442 Run the collect_service_data command and return a list of files generated
443 by the command.
444
445 Description of argument(s):
446 verify If set, verify that all files which can be
447 created by collect_service_data did, in
448 fact, get created.
449 """
450
451 # Route the output of collect_service_data to a file for easier parsing.
452 temp = tempfile.NamedTemporaryFile()
453 temp_file_path = temp.name
Patrick Williams20f38712022-12-08 06:18:26 -0600454 openbmctool_execute_command(
455 "collect_service_data > " + temp_file_path, ignore_err=False
456 )
Michael Walsh1793aeb2018-09-27 16:32:53 -0500457 # Isolate the file paths in the collect_service_data output. We're
458 # looking for output lines like this from which to extract the file paths:
459 # Inventory collected and stored in /tmp/dummy--2018-09-26_17.59.18/inventory.txt
Patrick Williams20f38712022-12-08 06:18:26 -0600460 rc, file_paths = gc.shell_cmd(
461 "egrep 'collected and' " + temp_file_path
462 # + " | sed -re 's#.*/tmp#/tmp#g'",
463 + " | sed -re 's#[^/]*/#/#'",
464 quiet=1,
465 print_output=0,
466 )
Michael Walsh1793aeb2018-09-27 16:32:53 -0500467 # Example file_paths value:
468 # /tmp/dummy--2018-09-26_17.59.18/inventory.txt
469 # /tmp/dummy--2018-09-26_17.59.18/sensorReadings.txt
470 # etc.
471 # Convert from output to list.
Patrick Williams20f38712022-12-08 06:18:26 -0600472 collect_service_data_file_paths = list(
473 filter(None, file_paths.split("\n"))
474 )
Michael Walsh1793aeb2018-09-27 16:32:53 -0500475 if int(verify):
476 # Create a list of files by stripping the dir names from the elements
477 # of collect_service_data_file_paths.
Patrick Williams20f38712022-12-08 06:18:26 -0600478 files_obtained = [
479 re.sub(r".*/", "", file_path)
480 for file_path in collect_service_data_file_paths
481 ]
Michael Walsh1793aeb2018-09-27 16:32:53 -0500482 files_expected = service_data_files()
483 files_missing = list(set(files_expected) - set(files_obtained))
484 if len(files_missing) > 0:
Patrick Williams20f38712022-12-08 06:18:26 -0600485 gp.printn(
486 "collect_service_data output:\n"
487 + gm.file_to_str(temp_file_path)
488 )
Michael Walsh1793aeb2018-09-27 16:32:53 -0500489 err_msg = "The following files are missing from the list of files"
490 err_msg += " returned by collect_service_data:\n"
491 err_msg += gp.sprint_var(files_missing)
492 err_msg += gp.sprint_var(collect_service_data_file_paths)
493 BuiltIn().fail(gp.sprint_error(err_msg))
494
495 return collect_service_data_file_paths
496
497
498def health_check_fields():
499 r"""
500 Return a complete list of field names returned by the health_check command.
501 """
502
Patrick Williams20f38712022-12-08 06:18:26 -0600503 return ["hardware_status", "performance"]
Michael Walsh1793aeb2018-09-27 16:32:53 -0500504
505
506def get_health_check(verify=False):
507 r"""
508 Get the health_check information and return as a dictionary.
509
510 Example robot code:
511
512 ${health_check}= Get Health Check
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500513 Rprint Vars health_check fmt=1
Michael Walsh1793aeb2018-09-27 16:32:53 -0500514
515 Example result:
516
517 health_check:
518 [hardware_status]: OK
519 [performance]: OK
520
521 Description of argument(s):
522 verify If set, verify that all all expected
523 field_names are generated by the
524 health_check command.
525 """
526
Patrick Williams20f38712022-12-08 06:18:26 -0600527 rc, output = openbmctool_execute_command(
528 "health_check", print_output=False, ignore_err=False
529 )
Michael Walsh1793aeb2018-09-27 16:32:53 -0500530 health_check = vf.key_value_outbuf_to_dict(output, delim=":")
531 if int(verify):
Michael Walshec01a6f2019-08-01 12:43:20 -0500532 err_msg = gv.valid_dict(health_check, health_check_fields())
Michael Walsh0e7f3012018-10-11 17:05:46 -0500533 if err_msg != "":
Michael Walsh1793aeb2018-09-27 16:32:53 -0500534 BuiltIn().fail(gp.sprint_error(err_msg))
535
536 return health_check
Michael Walsh0e7f3012018-10-11 17:05:46 -0500537
538
539def remote_logging_view_fields():
540 r"""
541 Return a complete list of field names returned by the logging
542 remote_logging view command.
543 """
544
Patrick Williams20f38712022-12-08 06:18:26 -0600545 return ["Address", "Port"]
Michael Walsh0e7f3012018-10-11 17:05:46 -0500546
547
548def get_remote_logging_view(verify=False):
549 r"""
550 Get the remote_logging view information and return as a dictionary.
551
552 Example robot code:
553
554 ${remote_logging_view}= Get Remote Logging View
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500555 Rprint Vars remote_logging_view fmt=1
Michael Walsh0e7f3012018-10-11 17:05:46 -0500556
557 Example result:
558
559 remote_logging_view:
560 [Address]: <blank>
561 [AddressFamily]: xyz.openbmc_project.Network.Client.IPProtocol.IPv4
562 [Port]: 0
563
564 Description of argument(s):
565 verify If set, verify that all all expected field
566 names are generated by the 'logging
567 remote_logging view' command.
568 """
569
Patrick Williams20f38712022-12-08 06:18:26 -0600570 remote_logging_view = openbmctool_execute_command_json(
571 "logging remote_logging view", print_output=False, ignore_err=False
572 )
Michael Walsh0e7f3012018-10-11 17:05:46 -0500573
574 if int(verify):
Patrick Williams20f38712022-12-08 06:18:26 -0600575 err_msg = gv.valid_dict(
576 remote_logging_view, remote_logging_view_fields()
577 )
Michael Walsh0e7f3012018-10-11 17:05:46 -0500578 if err_msg != "":
579 BuiltIn().fail(gp.sprint_error(err_msg))
580
581 return remote_logging_view
Michael Walshaaded2a2019-08-14 17:25:27 -0500582
583
584def network(sub_command, **options):
585 r"""
586 Run an openbmctool.py network command and return the results as a dictionary.
587
588 Note that any valid network argument may be specified as a function argument.
589
590 Example robot code:
591
592 ${ip_records}= Network getIP I=eth0
593 Rprint Vars ip_records
594
595 Resulting output:
596
597 ip_records:
598 [/xyz/openbmc_project/network/eth0/ipv4/23d41d48]:
599 [Address]: n.n.n.n
600 [Gateway]:
601 [Origin]: xyz.openbmc_project.Network.IP.AddressOrigin.Static
602 [PrefixLength]: 24
603 [Type]: xyz.openbmc_project.Network.IP.Protocol.IPv4
604 [/xyz/openbmc_project/network/eth0/ipv4/24ba5feb]:
605 [Address]: n.n.n.n
606 (etc.)
607
608 Description of argument(s):
609 sub_command The sub-command accepted by the network
610 command (e.g. "view-config", "getIP",
611 etc.).
612 options Zero or more options accepted by the network command.
613 """
614
615 if gm.python_version < gm.ordered_dict_version:
616 new_options = collections.OrderedDict(options)
617 else:
618 new_options = options
619
Patrick Williams20f38712022-12-08 06:18:26 -0600620 command_string = gc.create_command_string(
621 "network " + sub_command, new_options
622 )
623 return openbmctool_execute_command_json(
624 command_string, print_output=False, ignore_err=False
625 )