blob: 7f989636b8f6c514ade83ee44742150e03926e88 [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 Walsh0e7f3012018-10-11 17:05:46 -050013import utils as utils
Michael Walshb973f9b2018-09-19 16:00:06 -050014from robot.libraries.BuiltIn import BuiltIn
15import re
Michael Walsh1793aeb2018-09-27 16:32:53 -050016import tempfile
Michael Walshaaded2a2019-08-14 17:25:27 -050017import collections
18import json
Michael Walshb973f9b2018-09-19 16:00:06 -050019
20
21def openbmctool_execute_command(command_string,
22 *args,
23 **kwargs):
24 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")
Michael Walshb973f9b2018-09-19 16:00:06 -050067 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}",
68 default="")
69 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}",
70 default="")
71 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).
Michael Walsh3343b5e2019-08-07 14:08:19 -050084 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.
Michael Walsh1793aeb2018-09-27 16:32:53 -050089 pipeline.insert(1, "| tail -n +1 | egrep -v 'Attempting login|User [^ ]+"
90 " has been logged out'")
Michael Walshb973f9b2018-09-19 16:00:06 -050091
Michael Walsh7384b9a2018-09-26 17:22:54 -050092 command_string = "set -o pipefail ; python3 $(which openbmctool.py) -H "\
Michael Walsh046fe222019-11-08 14:33:53 -060093 + openbmc_host + ":" + https_port + " -U " + openbmc_username + " -P " + openbmc_password\
Michael Walsh1793aeb2018-09-27 16:32:53 -050094 + " " + " ".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
Michael Walsh0e7f3012018-10-11 17:05:46 -050099def openbmctool_execute_command_json(command_string,
100 *args,
101 **kwargs):
102 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
115 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
Michael Walsh0e7f3012018-10-11 17:05:46 -0500123 if json_object['status'] != "ok":
124 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
129 return json_object['data']
130
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 """
158 rc, output = openbmctool_execute_command("fru status", print_output=False,
159 ignore_err=False)
160 # 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
237 rc, output = openbmctool_execute_command("fru print", print_output=False,
238 ignore_err=False)
239 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
308 rc, output = openbmctool_execute_command("fru list", print_output=False,
309 ignore_err=False)
310 if parse_json:
311 return gm.json_loads_multiple(output)
312 else:
313 return output
314
315
316def get_sensors_print():
317
318 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 """
344 rc, output = openbmctool_execute_command("sensors print",
345 print_output=False,
346 ignore_err=False)
347 # 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():
356
357 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 """
383 rc, output = openbmctool_execute_command("sensors list",
384 print_output=False,
385 ignore_err=False)
386 # 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 """
405 rc, output = openbmctool_execute_command("-V | cut -f 2 -d ' '",
406 print_output=False,
407 ignore_err=False)
408 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
417 return\
418 [
419 "inventory.txt",
420 "sensorReadings.txt",
421 "ledStatus.txt",
422 "SELshortlist.txt",
423 "parsedSELs.txt",
424 "bmcFullRaw.txt"
425 ]
426
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
442 openbmctool_execute_command("collect_service_data > " + temp_file_path,
443 ignore_err=False)
444 # 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
447 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)
451 # 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.
456 collect_service_data_file_paths =\
457 list(filter(None, file_paths.split("\n")))
458 if int(verify):
459 # Create a list of files by stripping the dir names from the elements
460 # of collect_service_data_file_paths.
461 files_obtained = [re.sub(r".*/", "", file_path)
462 for file_path in collect_service_data_file_paths]
463 files_expected = service_data_files()
464 files_missing = list(set(files_expected) - set(files_obtained))
465 if len(files_missing) > 0:
466 gp.printn("collect_service_data output:\n"
467 + gm.file_to_str(temp_file_path))
468 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
482 return\
483 [
484 "hardware_status",
485 "performance"
486 ]
487
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
510 rc, output = openbmctool_execute_command("health_check",
511 print_output=False,
512 ignore_err=False)
513 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
528 return\
529 [
530 "Address",
Michael Walsh0e7f3012018-10-11 17:05:46 -0500531 "Port"
532 ]
533
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
557 remote_logging_view =\
558 openbmctool_execute_command_json("logging remote_logging view",
559 print_output=False,
560 ignore_err=False)
561
562 if int(verify):
Michael Walshec01a6f2019-08-01 12:43:20 -0500563 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
607 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)