blob: a6c94e9bbbbdc4d099a5b07f0982f81dbde72378 [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 gen_print as gp
9import gen_cmd as gc
10import gen_valid as gv
11import gen_misc as gm
12import var_funcs as vf
13import utils as utils
14from robot.libraries.BuiltIn import BuiltIn
Patrick Williams57318182022-12-08 06:18:26 -060015import re
16import tempfile
George Keishinge635ddc2022-12-08 07:38:02 -060017import collections
18import json
Michael Walshb973f9b2018-09-19 16:00:06 -050019
20
George Keishinge635ddc2022-12-08 07:38:02 -060021def openbmctool_execute_command(command_string,
22 *args,
23 **kwargs):
Michael Walshb973f9b2018-09-19 16:00:06 -050024 r"""
25 Run the command string as an argument to the openbmctool.py program and
26 return the stdout and the return code.
27
28 This function provides several benefits versus calling shell_cmd directly:
29 - This function will obtain the global values for OPENBMC_HOST,
30 OPENBMC_USERNAME, etc.
31 - This function will compose the openbmctool.py command string which
32 includes the caller's command_string.
33 - The openbmctool.py produces additional text that clutters the output.
34 This function will remove such text. Example:
35 Attempting login...
36 <actual output>
37 User root has been logged out
38
39 NOTE: If you have pipe symbols in your command_string, they must be
40 surrounded by a single space on each side (see example below).
41
42 Example code:
43 ${rc} ${output}= Openbmctool Execute Command fru status | head -n 2
44
45 Example output:
46 #(CDT) 2018/09/19 15:16:58 - Issuing: set -o pipefail ; openbmctool.py -H hostname -U root -P ********
47 ... fru status | tail -n +1 | egrep -v 'Attempting login|User [^ ]+ hasbeen logged out' | head -n 2
48 Component | Is a FRU | Present | Functional | Has Logs
49 cpu0 | Yes | Yes | Yes | No
50
51 Description of arguments:
52 command_string The command string to be passed to the
53 openbmctool.py program.
Michael Walsh58b11ac2018-09-20 15:24:37 -050054 All remaining arguments are passed directly to shell_cmd. See the
55 shell_cmd prolog for details on allowable arguments. The caller may code
56 them directly as in this example:
57 openbmctool_execute_command("my command", quiet=1, max_attempts=2).
58 Python will do the work of putting these values into args/kwargs.
Michael Walshb973f9b2018-09-19 16:00:06 -050059 """
60
61 if not gv.valid_value(command_string):
62 return "", "", 1
63
64 # Get global BMC variable values.
65 openbmc_host = BuiltIn().get_variable_value("${OPENBMC_HOST}", default="")
Michael Walsh046fe222019-11-08 14:33:53 -060066 https_port = BuiltIn().get_variable_value("${HTTPS_PORT}", default="443")
George Keishinge635ddc2022-12-08 07:38:02 -060067 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}",
68 default="")
69 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}",
70 default="")
Michael Walshb973f9b2018-09-19 16:00:06 -050071 if not gv.valid_value(openbmc_host):
72 return "", "", 1
73 if not gv.valid_value(openbmc_username):
74 return "", "", 1
75 if not gv.valid_value(openbmc_password):
76 return "", "", 1
Michael Walsh046fe222019-11-08 14:33:53 -060077 if not gv.valid_value(https_port):
78 return "", "", 1
Michael Walshb973f9b2018-09-19 16:00:06 -050079
80 # Break the caller's command up into separate piped commands. For
81 # example, the user may have specified "fru status | head -n 2" which
Michael Walsh1793aeb2018-09-27 16:32:53 -050082 # would be broken into 2 list elements. We will also break on ">"
83 # (re-direct).
George Keishinge635ddc2022-12-08 07:38:02 -060084 pipeline = list(map(str.strip, re.split(r' ([\|>]) ',
85 str(command_string))))
Michael Walshb973f9b2018-09-19 16:00:06 -050086 # The "tail" command below prevents a "egrep: write error: Broken pipe"
87 # error if the user is piping the output to a sub-process.
88 # Use "egrep -v" to get rid of editorial output from openbmctool.py.
George Keishinge635ddc2022-12-08 07:38:02 -060089 pipeline.insert(1, "| tail -n +1 | egrep -v 'Attempting login|User [^ ]+"
90 " has been logged out'")
Michael Walshb973f9b2018-09-19 16:00:06 -050091
George Keishinge635ddc2022-12-08 07:38:02 -060092 command_string = "set -o pipefail ; python3 $(which openbmctool.py) -H "\
93 + openbmc_host + ":" + https_port + " -U " + openbmc_username + " -P " + openbmc_password\
94 + " " + " ".join(pipeline)
Michael Walshb973f9b2018-09-19 16:00:06 -050095
96 return gc.shell_cmd(command_string, *args, **kwargs)
Michael Walshd064ac92018-09-21 16:42:03 -050097
98
George Keishinge635ddc2022-12-08 07:38:02 -060099def openbmctool_execute_command_json(command_string,
100 *args,
101 **kwargs):
Michael Walsh0e7f3012018-10-11 17:05:46 -0500102 r"""
103 Run the command string as an argument to the openbmctool.py program, parse
104 the JSON output into a dictionary and return the dictionary.
105
106 This function is a wrapper for openbmctool_execute_command (defined
107 above). The caller may provide any command string where the output will
108 be JSON data. This function will convert the JSON data to a python
109 object, verify that the 'status' field = "ok" and return the 'data'
110 sub-field to the caller.
111
112 See openbmctool_execute_command (above) for all field descriptions.
113 """
114
George Keishinge635ddc2022-12-08 07:38:02 -0600115 rc, output = openbmctool_execute_command(command_string,
116 *args,
117 **kwargs)
Michael Walshaaded2a2019-08-14 17:25:27 -0500118 try:
119 json_object = utils.to_json_ordered(output)
120 except json.JSONDecodeError:
121 BuiltIn().fail(gp.sprint_error(output))
122
George Keishinge635ddc2022-12-08 07:38:02 -0600123 if json_object['status'] != "ok":
Michael Walsh0e7f3012018-10-11 17:05:46 -0500124 err_msg = "Error found in JSON data returned by the openbmctool.py "
125 err_msg += "command. Expected a 'status' field value of \"ok\":\n"
126 err_msg += gp.sprint_var(json_object, 1)
127 BuiltIn().fail(gp.sprint_error(err_msg))
128
George Keishinge635ddc2022-12-08 07:38:02 -0600129 return json_object['data']
Michael Walsh0e7f3012018-10-11 17:05:46 -0500130
131
Michael Walshd064ac92018-09-21 16:42:03 -0500132def get_fru_status():
133 r"""
134 Get the fru status and return as a list of dictionaries.
135
136 Example robot code:
137
138 ${fru_status}= Get Fru Status
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500139 Rprint Vars fru_status fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500140
141 Example result (excerpt):
142
143 fru_status:
144 fru_status[0]:
145 [component]: cpu0
146 [is_a]: Yes
147 [fru]: Yes
148 [present]: Yes
149 [functional]: No
150 fru_status[1]:
151 [component]: cpu0-core0
152 [is_a]: No
153 [fru]: Yes
154 [present]: Yes
155 [functional]: No
156 ...
157 """
George Keishinge635ddc2022-12-08 07:38:02 -0600158 rc, output = openbmctool_execute_command("fru status", print_output=False,
159 ignore_err=False)
Michael Walshd064ac92018-09-21 16:42:03 -0500160 # Example value for output (partial):
161 # Component | Is a FRU | Present | Functional | Has Logs
162 # cpu0 | Yes | Yes | Yes | No
163 # cpu0-core0 | No | Yes | Yes | No
164 # ...
165
166 # Replace spaces with underscores in field names (e.g. "Is a FRU" becomes
167 # "Is_a_FRU").
168 output = re.sub("([^ \\|])[ ]([^ ])", "\\1_\\2", output)
169 output = re.sub("([^ \\|])[ ]([^ ])", "\\1_\\2", output)
170
171 return vf.outbuf_to_report(output, field_delim="|")
172
173
174def get_fru_print(parse_json=True):
175 r"""
176 Get the output of the fru print command and return it either as raw JSON
177 data or as a list of dictionaries.
178
179 Example robot code:
180
181 ${fru_print}= Get Fru Print parse_json=${False}
182 Log to Console ${fru_print}
183
184 Example result (excerpt):
185
186 {
187 "data": {
188 "/xyz/openbmc_project/inventory/system": {
189 "AssetTag": "",
190 "BuildDate": "",
191 "Cached": false,
192 "FieldReplaceable": false,
193 "Manufacturer": "",
194 "Model": "xxxxxxxx",
195 "PartNumber": "",
196 "Present": true,
197 "PrettyName": "",
198 "SerialNumber": "13183FA"
199 },
200 "/xyz/openbmc_project/inventory/system/chassis": {
201 "AirCooled": true,
202 "WaterCooled": false
203 },
204 ...
205
206 Example robot code:
207
208 ${fru_print}= Get Fru Print
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500209 Rprint Vars fru_print fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500210
211 Example result (excerpt):
212
213 fru_print:
214 fru_print[0]:
215 [data]:
216 [/xyz/openbmc_project/inventory/system]:
217 [AssetTag]: <blank>
218 [BuildDate]: <blank>
219 [Cached]: False
220 [FieldReplaceable]: False
221 [Manufacturer]: <blank>
222 [Model]: xxxxxxxx
223 [PartNumber]: <blank>
224 [Present]: True
225 [PrettyName]: <blank>
226 [SerialNumber]: 13183FA
227 [/xyz/openbmc_project/inventory/system/chassis]:
228 [AirCooled]: True
229 [WaterCooled]: False
230 ...
231
232 Description of argument(s):
233 parse_json Indicates that the raw JSON data should
234 parsed into a list of dictionaries.
235 """
236
George Keishinge635ddc2022-12-08 07:38:02 -0600237 rc, output = openbmctool_execute_command("fru print", print_output=False,
238 ignore_err=False)
Michael Walshd064ac92018-09-21 16:42:03 -0500239 if parse_json:
240 return gm.json_loads_multiple(output)
241 else:
242 return output
243
244
245def get_fru_list(parse_json=True):
246 r"""
247 Get the output of the fru list command and return it either as raw JSON
248 data or as a list of dictionaries.
249
250 Example robot code:
251
252 ${fru_list}= Get Fru List parse_json=${False}
253 Log to Console ${fru_list}
254
255 Example result (excerpt):
256
257 {
258 "data": {
259 "/xyz/openbmc_project/inventory/system": {
260 "AssetTag": "",
261 "BuildDate": "",
262 "Cached": false,
263 "FieldReplaceable": false,
264 "Manufacturer": "",
265 "Model": "xxxxxxxx",
266 "PartNumber": "",
267 "Present": true,
268 "PrettyName": "",
269 "SerialNumber": "13183FA"
270 },
271 "/xyz/openbmc_project/inventory/system/chassis": {
272 "AirCooled": true,
273 "WaterCooled": false
274 },
275 ...
276
277 Example robot code:
278
279 ${fru_list}= Get Fru List
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500280 Rprint Vars fru_list fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500281
282 Example result (excerpt):
283
284 fru_list:
285 fru_list[0]:
286 [data]:
287 [/xyz/openbmc_project/inventory/system]:
288 [AssetTag]: <blank>
289 [BuildDate]: <blank>
290 [Cached]: False
291 [FieldReplaceable]: False
292 [Manufacturer]: <blank>
293 [Model]: xxxxxxxx
294 [PartNumber]: <blank>
295 [Present]: True
296 [PrettyName]: <blank>
297 [SerialNumber]: 13183FA
298 [/xyz/openbmc_project/inventory/system/chassis]:
299 [AirCooled]: True
300 [WaterCooled]: False
301 ...
302
303 Description of argument(s):
304 parse_json Indicates that the raw JSON data should
305 parsed into a list of dictionaries.
306 """
307
George Keishinge635ddc2022-12-08 07:38:02 -0600308 rc, output = openbmctool_execute_command("fru list", print_output=False,
309 ignore_err=False)
Michael Walshd064ac92018-09-21 16:42:03 -0500310 if parse_json:
311 return gm.json_loads_multiple(output)
312 else:
313 return output
314
315
316def get_sensors_print():
George Keishinge635ddc2022-12-08 07:38:02 -0600317
Michael Walshd064ac92018-09-21 16:42:03 -0500318 r"""
319 Get the output of the sensors print command and return as a list of
320 dictionaries.
321
322 Example robot code:
323
324 ${sensors_print}= Get Sensors Print
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500325 Rprint Vars sensors_print fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500326
327 Example result (excerpt):
328
329 sensors_print:
330 sensors_print[0]:
331 [sensor]: OCC0
332 [type]: Discrete
333 [units]: N/A
334 [value]: Active
335 [target]: Active
336 sensors_print[1]:
337 [sensor]: OCC1
338 [type]: Discrete
339 [units]: N/A
340 [value]: Active
341 [target]: Active
342 ...
343 """
George Keishinge635ddc2022-12-08 07:38:02 -0600344 rc, output = openbmctool_execute_command("sensors print",
345 print_output=False,
346 ignore_err=False)
Michael Walshd064ac92018-09-21 16:42:03 -0500347 # Example value for output (partial):
348 # sensor | type | units | value | target
349 # OCC0 | Discrete | N/A | Active | Active
350 # OCC1 | Discrete | N/A | Active | Active
351
352 return vf.outbuf_to_report(output, field_delim="|")
353
354
355def get_sensors_list():
George Keishinge635ddc2022-12-08 07:38:02 -0600356
Michael Walshd064ac92018-09-21 16:42:03 -0500357 r"""
358 Get the output of the sensors list command and return as a list of
359 dictionaries.
360
361 Example robot code:
362
363 ${sensors_list}= Get Sensors List
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500364 Rprint Vars sensors_list fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500365
366 Example result (excerpt):
367
368 sensors_list:
369 sensors_list[0]:
370 [sensor]: OCC0
371 [type]: Discrete
372 [units]: N/A
373 [value]: Active
374 [target]: Active
375 sensors_list[1]:
376 [sensor]: OCC1
377 [type]: Discrete
378 [units]: N/A
379 [value]: Active
380 [target]: Active
381 ...
382 """
George Keishinge635ddc2022-12-08 07:38:02 -0600383 rc, output = openbmctool_execute_command("sensors list",
384 print_output=False,
385 ignore_err=False)
Michael Walshd064ac92018-09-21 16:42:03 -0500386 # Example value for output (partial):
387 # sensor | type | units | value | target
388 # OCC0 | Discrete | N/A | Active | Active
389 # OCC1 | Discrete | N/A | Active | Active
390
391 return vf.outbuf_to_report(output, field_delim="|")
392
393
394def get_openbmctool_version():
395 r"""
396 Get the openbmctool.py version and return it.
397
398 Example robot code:
399 ${openbmctool_version}= Get Openbmctool Version
400 Rprint Vars openbmctool_version
401
402 Example result (excerpt):
403 openbmctool_version: 1.06
404 """
George Keishinge635ddc2022-12-08 07:38:02 -0600405 rc, output = openbmctool_execute_command("-V | cut -f 2 -d ' '",
406 print_output=False,
407 ignore_err=False)
Michael Walshd064ac92018-09-21 16:42:03 -0500408 return output
Michael Walsh1793aeb2018-09-27 16:32:53 -0500409
410
411def service_data_files():
412 r"""
413 Return a complete list of file names that are expected to be created by
414 the collect_service_data command.
415 """
416
George Keishinge635ddc2022-12-08 07:38:02 -0600417 return\
418 [
419 "inventory.txt",
420 "sensorReadings.txt",
421 "ledStatus.txt",
422 "SELshortlist.txt",
423 "parsedSELs.txt",
424 "bmcFullRaw.txt"
425 ]
Michael Walsh1793aeb2018-09-27 16:32:53 -0500426
427
428def collect_service_data(verify=False):
429 r"""
430 Run the collect_service_data command and return a list of files generated
431 by the command.
432
433 Description of argument(s):
434 verify If set, verify that all files which can be
435 created by collect_service_data did, in
436 fact, get created.
437 """
438
439 # Route the output of collect_service_data to a file for easier parsing.
440 temp = tempfile.NamedTemporaryFile()
441 temp_file_path = temp.name
George Keishinge635ddc2022-12-08 07:38:02 -0600442 openbmctool_execute_command("collect_service_data > " + temp_file_path,
443 ignore_err=False)
Michael Walsh1793aeb2018-09-27 16:32:53 -0500444 # Isolate the file paths in the collect_service_data output. We're
445 # looking for output lines like this from which to extract the file paths:
446 # Inventory collected and stored in /tmp/dummy--2018-09-26_17.59.18/inventory.txt
George Keishinge635ddc2022-12-08 07:38:02 -0600447 rc, file_paths = gc.shell_cmd("egrep 'collected and' " + temp_file_path
448 # + " | sed -re 's#.*/tmp#/tmp#g'",
449 + " | sed -re 's#[^/]*/#/#'",
450 quiet=1, print_output=0)
Michael Walsh1793aeb2018-09-27 16:32:53 -0500451 # Example file_paths value:
452 # /tmp/dummy--2018-09-26_17.59.18/inventory.txt
453 # /tmp/dummy--2018-09-26_17.59.18/sensorReadings.txt
454 # etc.
455 # Convert from output to list.
George Keishinge635ddc2022-12-08 07:38:02 -0600456 collect_service_data_file_paths =\
457 list(filter(None, file_paths.split("\n")))
Michael Walsh1793aeb2018-09-27 16:32:53 -0500458 if int(verify):
459 # Create a list of files by stripping the dir names from the elements
460 # of collect_service_data_file_paths.
George Keishinge635ddc2022-12-08 07:38:02 -0600461 files_obtained = [re.sub(r".*/", "", file_path)
462 for file_path in collect_service_data_file_paths]
Michael Walsh1793aeb2018-09-27 16:32:53 -0500463 files_expected = service_data_files()
464 files_missing = list(set(files_expected) - set(files_obtained))
465 if len(files_missing) > 0:
George Keishinge635ddc2022-12-08 07:38:02 -0600466 gp.printn("collect_service_data output:\n"
467 + gm.file_to_str(temp_file_path))
Michael Walsh1793aeb2018-09-27 16:32:53 -0500468 err_msg = "The following files are missing from the list of files"
469 err_msg += " returned by collect_service_data:\n"
470 err_msg += gp.sprint_var(files_missing)
471 err_msg += gp.sprint_var(collect_service_data_file_paths)
472 BuiltIn().fail(gp.sprint_error(err_msg))
473
474 return collect_service_data_file_paths
475
476
477def health_check_fields():
478 r"""
479 Return a complete list of field names returned by the health_check command.
480 """
481
George Keishinge635ddc2022-12-08 07:38:02 -0600482 return\
483 [
484 "hardware_status",
485 "performance"
486 ]
Michael Walsh1793aeb2018-09-27 16:32:53 -0500487
488
489def get_health_check(verify=False):
490 r"""
491 Get the health_check information and return as a dictionary.
492
493 Example robot code:
494
495 ${health_check}= Get Health Check
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500496 Rprint Vars health_check fmt=1
Michael Walsh1793aeb2018-09-27 16:32:53 -0500497
498 Example result:
499
500 health_check:
501 [hardware_status]: OK
502 [performance]: OK
503
504 Description of argument(s):
505 verify If set, verify that all all expected
506 field_names are generated by the
507 health_check command.
508 """
509
George Keishinge635ddc2022-12-08 07:38:02 -0600510 rc, output = openbmctool_execute_command("health_check",
511 print_output=False,
512 ignore_err=False)
Michael Walsh1793aeb2018-09-27 16:32:53 -0500513 health_check = vf.key_value_outbuf_to_dict(output, delim=":")
514 if int(verify):
Michael Walshec01a6f2019-08-01 12:43:20 -0500515 err_msg = gv.valid_dict(health_check, health_check_fields())
Michael Walsh0e7f3012018-10-11 17:05:46 -0500516 if err_msg != "":
Michael Walsh1793aeb2018-09-27 16:32:53 -0500517 BuiltIn().fail(gp.sprint_error(err_msg))
518
519 return health_check
Michael Walsh0e7f3012018-10-11 17:05:46 -0500520
521
522def remote_logging_view_fields():
523 r"""
524 Return a complete list of field names returned by the logging
525 remote_logging view command.
526 """
527
George Keishinge635ddc2022-12-08 07:38:02 -0600528 return\
529 [
530 "Address",
531 "Port"
532 ]
Michael Walsh0e7f3012018-10-11 17:05:46 -0500533
534
535def get_remote_logging_view(verify=False):
536 r"""
537 Get the remote_logging view information and return as a dictionary.
538
539 Example robot code:
540
541 ${remote_logging_view}= Get Remote Logging View
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500542 Rprint Vars remote_logging_view fmt=1
Michael Walsh0e7f3012018-10-11 17:05:46 -0500543
544 Example result:
545
546 remote_logging_view:
547 [Address]: <blank>
548 [AddressFamily]: xyz.openbmc_project.Network.Client.IPProtocol.IPv4
549 [Port]: 0
550
551 Description of argument(s):
552 verify If set, verify that all all expected field
553 names are generated by the 'logging
554 remote_logging view' command.
555 """
556
George Keishinge635ddc2022-12-08 07:38:02 -0600557 remote_logging_view =\
558 openbmctool_execute_command_json("logging remote_logging view",
559 print_output=False,
560 ignore_err=False)
Michael Walsh0e7f3012018-10-11 17:05:46 -0500561
562 if int(verify):
George Keishinge635ddc2022-12-08 07:38:02 -0600563 err_msg = gv.valid_dict(remote_logging_view,
564 remote_logging_view_fields())
Michael Walsh0e7f3012018-10-11 17:05:46 -0500565 if err_msg != "":
566 BuiltIn().fail(gp.sprint_error(err_msg))
567
568 return remote_logging_view
Michael Walshaaded2a2019-08-14 17:25:27 -0500569
570
571def network(sub_command, **options):
572 r"""
573 Run an openbmctool.py network command and return the results as a dictionary.
574
575 Note that any valid network argument may be specified as a function argument.
576
577 Example robot code:
578
579 ${ip_records}= Network getIP I=eth0
580 Rprint Vars ip_records
581
582 Resulting output:
583
584 ip_records:
585 [/xyz/openbmc_project/network/eth0/ipv4/23d41d48]:
586 [Address]: n.n.n.n
587 [Gateway]:
588 [Origin]: xyz.openbmc_project.Network.IP.AddressOrigin.Static
589 [PrefixLength]: 24
590 [Type]: xyz.openbmc_project.Network.IP.Protocol.IPv4
591 [/xyz/openbmc_project/network/eth0/ipv4/24ba5feb]:
592 [Address]: n.n.n.n
593 (etc.)
594
595 Description of argument(s):
596 sub_command The sub-command accepted by the network
597 command (e.g. "view-config", "getIP",
598 etc.).
599 options Zero or more options accepted by the network command.
600 """
601
602 if gm.python_version < gm.ordered_dict_version:
603 new_options = collections.OrderedDict(options)
604 else:
605 new_options = options
606
George Keishinge635ddc2022-12-08 07:38:02 -0600607 command_string = gc.create_command_string('network ' + sub_command,
608 new_options)
609 return openbmctool_execute_command_json(command_string,
610 print_output=False,
611 ignore_err=False)