blob: dfe84e7520d54e2a271b2e0094852decf315a3e2 [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,
93 "| tail -n +1 | egrep -v 'Attempting login|User [^ ]+"
94 " has been logged out'",
95 )
Michael Walshb973f9b2018-09-19 16:00:06 -050096
Patrick Williams20f38712022-12-08 06:18:26 -060097 command_string = (
98 "set -o pipefail ; python3 $(which openbmctool.py) -H "
99 + openbmc_host
100 + ":"
101 + https_port
102 + " -U "
103 + openbmc_username
104 + " -P "
105 + openbmc_password
106 + " "
107 + " ".join(pipeline)
108 )
Michael Walshb973f9b2018-09-19 16:00:06 -0500109
110 return gc.shell_cmd(command_string, *args, **kwargs)
Michael Walshd064ac92018-09-21 16:42:03 -0500111
112
Patrick Williams20f38712022-12-08 06:18:26 -0600113def openbmctool_execute_command_json(command_string, *args, **kwargs):
Michael Walsh0e7f3012018-10-11 17:05:46 -0500114 r"""
115 Run the command string as an argument to the openbmctool.py program, parse
116 the JSON output into a dictionary and return the dictionary.
117
118 This function is a wrapper for openbmctool_execute_command (defined
119 above). The caller may provide any command string where the output will
120 be JSON data. This function will convert the JSON data to a python
121 object, verify that the 'status' field = "ok" and return the 'data'
122 sub-field to the caller.
123
124 See openbmctool_execute_command (above) for all field descriptions.
125 """
126
Patrick Williams20f38712022-12-08 06:18:26 -0600127 rc, output = openbmctool_execute_command(command_string, *args, **kwargs)
Michael Walshaaded2a2019-08-14 17:25:27 -0500128 try:
129 json_object = utils.to_json_ordered(output)
130 except json.JSONDecodeError:
131 BuiltIn().fail(gp.sprint_error(output))
132
Patrick Williams20f38712022-12-08 06:18:26 -0600133 if json_object["status"] != "ok":
Michael Walsh0e7f3012018-10-11 17:05:46 -0500134 err_msg = "Error found in JSON data returned by the openbmctool.py "
135 err_msg += "command. Expected a 'status' field value of \"ok\":\n"
136 err_msg += gp.sprint_var(json_object, 1)
137 BuiltIn().fail(gp.sprint_error(err_msg))
138
Patrick Williams20f38712022-12-08 06:18:26 -0600139 return json_object["data"]
Michael Walsh0e7f3012018-10-11 17:05:46 -0500140
141
Michael Walshd064ac92018-09-21 16:42:03 -0500142def get_fru_status():
143 r"""
144 Get the fru status and return as a list of dictionaries.
145
146 Example robot code:
147
148 ${fru_status}= Get Fru Status
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500149 Rprint Vars fru_status fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500150
151 Example result (excerpt):
152
153 fru_status:
154 fru_status[0]:
155 [component]: cpu0
156 [is_a]: Yes
157 [fru]: Yes
158 [present]: Yes
159 [functional]: No
160 fru_status[1]:
161 [component]: cpu0-core0
162 [is_a]: No
163 [fru]: Yes
164 [present]: Yes
165 [functional]: No
166 ...
167 """
Patrick Williams20f38712022-12-08 06:18:26 -0600168 rc, output = openbmctool_execute_command(
169 "fru status", print_output=False, ignore_err=False
170 )
Michael Walshd064ac92018-09-21 16:42:03 -0500171 # Example value for output (partial):
172 # Component | Is a FRU | Present | Functional | Has Logs
173 # cpu0 | Yes | Yes | Yes | No
174 # cpu0-core0 | No | Yes | Yes | No
175 # ...
176
177 # Replace spaces with underscores in field names (e.g. "Is a FRU" becomes
178 # "Is_a_FRU").
179 output = re.sub("([^ \\|])[ ]([^ ])", "\\1_\\2", output)
180 output = re.sub("([^ \\|])[ ]([^ ])", "\\1_\\2", output)
181
182 return vf.outbuf_to_report(output, field_delim="|")
183
184
185def get_fru_print(parse_json=True):
186 r"""
187 Get the output of the fru print command and return it either as raw JSON
188 data or as a list of dictionaries.
189
190 Example robot code:
191
192 ${fru_print}= Get Fru Print parse_json=${False}
193 Log to Console ${fru_print}
194
195 Example result (excerpt):
196
197 {
198 "data": {
199 "/xyz/openbmc_project/inventory/system": {
200 "AssetTag": "",
201 "BuildDate": "",
202 "Cached": false,
203 "FieldReplaceable": false,
204 "Manufacturer": "",
205 "Model": "xxxxxxxx",
206 "PartNumber": "",
207 "Present": true,
208 "PrettyName": "",
209 "SerialNumber": "13183FA"
210 },
211 "/xyz/openbmc_project/inventory/system/chassis": {
212 "AirCooled": true,
213 "WaterCooled": false
214 },
215 ...
216
217 Example robot code:
218
219 ${fru_print}= Get Fru Print
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500220 Rprint Vars fru_print fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500221
222 Example result (excerpt):
223
224 fru_print:
225 fru_print[0]:
226 [data]:
227 [/xyz/openbmc_project/inventory/system]:
228 [AssetTag]: <blank>
229 [BuildDate]: <blank>
230 [Cached]: False
231 [FieldReplaceable]: False
232 [Manufacturer]: <blank>
233 [Model]: xxxxxxxx
234 [PartNumber]: <blank>
235 [Present]: True
236 [PrettyName]: <blank>
237 [SerialNumber]: 13183FA
238 [/xyz/openbmc_project/inventory/system/chassis]:
239 [AirCooled]: True
240 [WaterCooled]: False
241 ...
242
243 Description of argument(s):
244 parse_json Indicates that the raw JSON data should
245 parsed into a list of dictionaries.
246 """
247
Patrick Williams20f38712022-12-08 06:18:26 -0600248 rc, output = openbmctool_execute_command(
249 "fru print", print_output=False, ignore_err=False
250 )
Michael Walshd064ac92018-09-21 16:42:03 -0500251 if parse_json:
252 return gm.json_loads_multiple(output)
253 else:
254 return output
255
256
257def get_fru_list(parse_json=True):
258 r"""
259 Get the output of the fru list command and return it either as raw JSON
260 data or as a list of dictionaries.
261
262 Example robot code:
263
264 ${fru_list}= Get Fru List parse_json=${False}
265 Log to Console ${fru_list}
266
267 Example result (excerpt):
268
269 {
270 "data": {
271 "/xyz/openbmc_project/inventory/system": {
272 "AssetTag": "",
273 "BuildDate": "",
274 "Cached": false,
275 "FieldReplaceable": false,
276 "Manufacturer": "",
277 "Model": "xxxxxxxx",
278 "PartNumber": "",
279 "Present": true,
280 "PrettyName": "",
281 "SerialNumber": "13183FA"
282 },
283 "/xyz/openbmc_project/inventory/system/chassis": {
284 "AirCooled": true,
285 "WaterCooled": false
286 },
287 ...
288
289 Example robot code:
290
291 ${fru_list}= Get Fru List
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500292 Rprint Vars fru_list fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500293
294 Example result (excerpt):
295
296 fru_list:
297 fru_list[0]:
298 [data]:
299 [/xyz/openbmc_project/inventory/system]:
300 [AssetTag]: <blank>
301 [BuildDate]: <blank>
302 [Cached]: False
303 [FieldReplaceable]: False
304 [Manufacturer]: <blank>
305 [Model]: xxxxxxxx
306 [PartNumber]: <blank>
307 [Present]: True
308 [PrettyName]: <blank>
309 [SerialNumber]: 13183FA
310 [/xyz/openbmc_project/inventory/system/chassis]:
311 [AirCooled]: True
312 [WaterCooled]: False
313 ...
314
315 Description of argument(s):
316 parse_json Indicates that the raw JSON data should
317 parsed into a list of dictionaries.
318 """
319
Patrick Williams20f38712022-12-08 06:18:26 -0600320 rc, output = openbmctool_execute_command(
321 "fru list", print_output=False, ignore_err=False
322 )
Michael Walshd064ac92018-09-21 16:42:03 -0500323 if parse_json:
324 return gm.json_loads_multiple(output)
325 else:
326 return output
327
328
329def get_sensors_print():
Michael Walshd064ac92018-09-21 16:42:03 -0500330 r"""
331 Get the output of the sensors print command and return as a list of
332 dictionaries.
333
334 Example robot code:
335
336 ${sensors_print}= Get Sensors Print
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500337 Rprint Vars sensors_print fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500338
339 Example result (excerpt):
340
341 sensors_print:
342 sensors_print[0]:
343 [sensor]: OCC0
344 [type]: Discrete
345 [units]: N/A
346 [value]: Active
347 [target]: Active
348 sensors_print[1]:
349 [sensor]: OCC1
350 [type]: Discrete
351 [units]: N/A
352 [value]: Active
353 [target]: Active
354 ...
355 """
Patrick Williams20f38712022-12-08 06:18:26 -0600356 rc, output = openbmctool_execute_command(
357 "sensors print", print_output=False, ignore_err=False
358 )
Michael Walshd064ac92018-09-21 16:42:03 -0500359 # Example value for output (partial):
360 # sensor | type | units | value | target
361 # OCC0 | Discrete | N/A | Active | Active
362 # OCC1 | Discrete | N/A | Active | Active
363
364 return vf.outbuf_to_report(output, field_delim="|")
365
366
367def get_sensors_list():
Michael Walshd064ac92018-09-21 16:42:03 -0500368 r"""
369 Get the output of the sensors list command and return as a list of
370 dictionaries.
371
372 Example robot code:
373
374 ${sensors_list}= Get Sensors List
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500375 Rprint Vars sensors_list fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500376
377 Example result (excerpt):
378
379 sensors_list:
380 sensors_list[0]:
381 [sensor]: OCC0
382 [type]: Discrete
383 [units]: N/A
384 [value]: Active
385 [target]: Active
386 sensors_list[1]:
387 [sensor]: OCC1
388 [type]: Discrete
389 [units]: N/A
390 [value]: Active
391 [target]: Active
392 ...
393 """
Patrick Williams20f38712022-12-08 06:18:26 -0600394 rc, output = openbmctool_execute_command(
395 "sensors list", print_output=False, ignore_err=False
396 )
Michael Walshd064ac92018-09-21 16:42:03 -0500397 # Example value for output (partial):
398 # sensor | type | units | value | target
399 # OCC0 | Discrete | N/A | Active | Active
400 # OCC1 | Discrete | N/A | Active | Active
401
402 return vf.outbuf_to_report(output, field_delim="|")
403
404
405def get_openbmctool_version():
406 r"""
407 Get the openbmctool.py version and return it.
408
409 Example robot code:
410 ${openbmctool_version}= Get Openbmctool Version
411 Rprint Vars openbmctool_version
412
413 Example result (excerpt):
414 openbmctool_version: 1.06
415 """
Patrick Williams20f38712022-12-08 06:18:26 -0600416 rc, output = openbmctool_execute_command(
417 "-V | cut -f 2 -d ' '", print_output=False, ignore_err=False
418 )
Michael Walshd064ac92018-09-21 16:42:03 -0500419 return output
Michael Walsh1793aeb2018-09-27 16:32:53 -0500420
421
422def service_data_files():
423 r"""
424 Return a complete list of file names that are expected to be created by
425 the collect_service_data command.
426 """
427
Patrick Williams20f38712022-12-08 06:18:26 -0600428 return [
429 "inventory.txt",
430 "sensorReadings.txt",
431 "ledStatus.txt",
432 "SELshortlist.txt",
433 "parsedSELs.txt",
434 "bmcFullRaw.txt",
435 ]
Michael Walsh1793aeb2018-09-27 16:32:53 -0500436
437
438def collect_service_data(verify=False):
439 r"""
440 Run the collect_service_data command and return a list of files generated
441 by the command.
442
443 Description of argument(s):
444 verify If set, verify that all files which can be
445 created by collect_service_data did, in
446 fact, get created.
447 """
448
449 # Route the output of collect_service_data to a file for easier parsing.
450 temp = tempfile.NamedTemporaryFile()
451 temp_file_path = temp.name
Patrick Williams20f38712022-12-08 06:18:26 -0600452 openbmctool_execute_command(
453 "collect_service_data > " + temp_file_path, ignore_err=False
454 )
Michael Walsh1793aeb2018-09-27 16:32:53 -0500455 # Isolate the file paths in the collect_service_data output. We're
456 # looking for output lines like this from which to extract the file paths:
457 # Inventory collected and stored in /tmp/dummy--2018-09-26_17.59.18/inventory.txt
Patrick Williams20f38712022-12-08 06:18:26 -0600458 rc, file_paths = gc.shell_cmd(
459 "egrep 'collected and' " + temp_file_path
460 # + " | sed -re 's#.*/tmp#/tmp#g'",
461 + " | sed -re 's#[^/]*/#/#'",
462 quiet=1,
463 print_output=0,
464 )
Michael Walsh1793aeb2018-09-27 16:32:53 -0500465 # Example file_paths value:
466 # /tmp/dummy--2018-09-26_17.59.18/inventory.txt
467 # /tmp/dummy--2018-09-26_17.59.18/sensorReadings.txt
468 # etc.
469 # Convert from output to list.
Patrick Williams20f38712022-12-08 06:18:26 -0600470 collect_service_data_file_paths = list(
471 filter(None, file_paths.split("\n"))
472 )
Michael Walsh1793aeb2018-09-27 16:32:53 -0500473 if int(verify):
474 # Create a list of files by stripping the dir names from the elements
475 # of collect_service_data_file_paths.
Patrick Williams20f38712022-12-08 06:18:26 -0600476 files_obtained = [
477 re.sub(r".*/", "", file_path)
478 for file_path in collect_service_data_file_paths
479 ]
Michael Walsh1793aeb2018-09-27 16:32:53 -0500480 files_expected = service_data_files()
481 files_missing = list(set(files_expected) - set(files_obtained))
482 if len(files_missing) > 0:
Patrick Williams20f38712022-12-08 06:18:26 -0600483 gp.printn(
484 "collect_service_data output:\n"
485 + gm.file_to_str(temp_file_path)
486 )
Michael Walsh1793aeb2018-09-27 16:32:53 -0500487 err_msg = "The following files are missing from the list of files"
488 err_msg += " returned by collect_service_data:\n"
489 err_msg += gp.sprint_var(files_missing)
490 err_msg += gp.sprint_var(collect_service_data_file_paths)
491 BuiltIn().fail(gp.sprint_error(err_msg))
492
493 return collect_service_data_file_paths
494
495
496def health_check_fields():
497 r"""
498 Return a complete list of field names returned by the health_check command.
499 """
500
Patrick Williams20f38712022-12-08 06:18:26 -0600501 return ["hardware_status", "performance"]
Michael Walsh1793aeb2018-09-27 16:32:53 -0500502
503
504def get_health_check(verify=False):
505 r"""
506 Get the health_check information and return as a dictionary.
507
508 Example robot code:
509
510 ${health_check}= Get Health Check
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500511 Rprint Vars health_check fmt=1
Michael Walsh1793aeb2018-09-27 16:32:53 -0500512
513 Example result:
514
515 health_check:
516 [hardware_status]: OK
517 [performance]: OK
518
519 Description of argument(s):
520 verify If set, verify that all all expected
521 field_names are generated by the
522 health_check command.
523 """
524
Patrick Williams20f38712022-12-08 06:18:26 -0600525 rc, output = openbmctool_execute_command(
526 "health_check", print_output=False, ignore_err=False
527 )
Michael Walsh1793aeb2018-09-27 16:32:53 -0500528 health_check = vf.key_value_outbuf_to_dict(output, delim=":")
529 if int(verify):
Michael Walshec01a6f2019-08-01 12:43:20 -0500530 err_msg = gv.valid_dict(health_check, health_check_fields())
Michael Walsh0e7f3012018-10-11 17:05:46 -0500531 if err_msg != "":
Michael Walsh1793aeb2018-09-27 16:32:53 -0500532 BuiltIn().fail(gp.sprint_error(err_msg))
533
534 return health_check
Michael Walsh0e7f3012018-10-11 17:05:46 -0500535
536
537def remote_logging_view_fields():
538 r"""
539 Return a complete list of field names returned by the logging
540 remote_logging view command.
541 """
542
Patrick Williams20f38712022-12-08 06:18:26 -0600543 return ["Address", "Port"]
Michael Walsh0e7f3012018-10-11 17:05:46 -0500544
545
546def get_remote_logging_view(verify=False):
547 r"""
548 Get the remote_logging view information and return as a dictionary.
549
550 Example robot code:
551
552 ${remote_logging_view}= Get Remote Logging View
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500553 Rprint Vars remote_logging_view fmt=1
Michael Walsh0e7f3012018-10-11 17:05:46 -0500554
555 Example result:
556
557 remote_logging_view:
558 [Address]: <blank>
559 [AddressFamily]: xyz.openbmc_project.Network.Client.IPProtocol.IPv4
560 [Port]: 0
561
562 Description of argument(s):
563 verify If set, verify that all all expected field
564 names are generated by the 'logging
565 remote_logging view' command.
566 """
567
Patrick Williams20f38712022-12-08 06:18:26 -0600568 remote_logging_view = openbmctool_execute_command_json(
569 "logging remote_logging view", print_output=False, ignore_err=False
570 )
Michael Walsh0e7f3012018-10-11 17:05:46 -0500571
572 if int(verify):
Patrick Williams20f38712022-12-08 06:18:26 -0600573 err_msg = gv.valid_dict(
574 remote_logging_view, remote_logging_view_fields()
575 )
Michael Walsh0e7f3012018-10-11 17:05:46 -0500576 if err_msg != "":
577 BuiltIn().fail(gp.sprint_error(err_msg))
578
579 return remote_logging_view
Michael Walshaaded2a2019-08-14 17:25:27 -0500580
581
582def network(sub_command, **options):
583 r"""
584 Run an openbmctool.py network command and return the results as a dictionary.
585
586 Note that any valid network argument may be specified as a function argument.
587
588 Example robot code:
589
590 ${ip_records}= Network getIP I=eth0
591 Rprint Vars ip_records
592
593 Resulting output:
594
595 ip_records:
596 [/xyz/openbmc_project/network/eth0/ipv4/23d41d48]:
597 [Address]: n.n.n.n
598 [Gateway]:
599 [Origin]: xyz.openbmc_project.Network.IP.AddressOrigin.Static
600 [PrefixLength]: 24
601 [Type]: xyz.openbmc_project.Network.IP.Protocol.IPv4
602 [/xyz/openbmc_project/network/eth0/ipv4/24ba5feb]:
603 [Address]: n.n.n.n
604 (etc.)
605
606 Description of argument(s):
607 sub_command The sub-command accepted by the network
608 command (e.g. "view-config", "getIP",
609 etc.).
610 options Zero or more options accepted by the network command.
611 """
612
613 if gm.python_version < gm.ordered_dict_version:
614 new_options = collections.OrderedDict(options)
615 else:
616 new_options = options
617
Patrick Williams20f38712022-12-08 06:18:26 -0600618 command_string = gc.create_command_string(
619 "network " + sub_command, new_options
620 )
621 return openbmctool_execute_command_json(
622 command_string, print_output=False, ignore_err=False
623 )