blob: 17595c4a521ce74cd49f22264f6f1814bd91263a [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="")
66 openbmc_username = BuiltIn().get_variable_value("${OPENBMC_USERNAME}",
67 default="")
68 openbmc_password = BuiltIn().get_variable_value("${OPENBMC_PASSWORD}",
69 default="")
70 if not gv.valid_value(openbmc_host):
71 return "", "", 1
72 if not gv.valid_value(openbmc_username):
73 return "", "", 1
74 if not gv.valid_value(openbmc_password):
75 return "", "", 1
76
77 # Break the caller's command up into separate piped commands. For
78 # example, the user may have specified "fru status | head -n 2" which
Michael Walsh1793aeb2018-09-27 16:32:53 -050079 # would be broken into 2 list elements. We will also break on ">"
80 # (re-direct).
Michael Walsh3343b5e2019-08-07 14:08:19 -050081 pipeline = list(map(str.strip, re.split(r' ([\|>]) ',
82 str(command_string))))
Michael Walshb973f9b2018-09-19 16:00:06 -050083 # The "tail" command below prevents a "egrep: write error: Broken pipe"
84 # error if the user is piping the output to a sub-process.
85 # Use "egrep -v" to get rid of editorial output from openbmctool.py.
Michael Walsh1793aeb2018-09-27 16:32:53 -050086 pipeline.insert(1, "| tail -n +1 | egrep -v 'Attempting login|User [^ ]+"
87 " has been logged out'")
Michael Walshb973f9b2018-09-19 16:00:06 -050088
Michael Walsh7384b9a2018-09-26 17:22:54 -050089 command_string = "set -o pipefail ; python3 $(which openbmctool.py) -H "\
90 + openbmc_host + " -U " + openbmc_username + " -P " + openbmc_password\
Michael Walsh1793aeb2018-09-27 16:32:53 -050091 + " " + " ".join(pipeline)
Michael Walshb973f9b2018-09-19 16:00:06 -050092
93 return gc.shell_cmd(command_string, *args, **kwargs)
Michael Walshd064ac92018-09-21 16:42:03 -050094
95
Michael Walsh0e7f3012018-10-11 17:05:46 -050096def openbmctool_execute_command_json(command_string,
97 *args,
98 **kwargs):
99 r"""
100 Run the command string as an argument to the openbmctool.py program, parse
101 the JSON output into a dictionary and return the dictionary.
102
103 This function is a wrapper for openbmctool_execute_command (defined
104 above). The caller may provide any command string where the output will
105 be JSON data. This function will convert the JSON data to a python
106 object, verify that the 'status' field = "ok" and return the 'data'
107 sub-field to the caller.
108
109 See openbmctool_execute_command (above) for all field descriptions.
110 """
111
112 rc, output = openbmctool_execute_command(command_string,
113 *args,
114 **kwargs)
Michael Walshaaded2a2019-08-14 17:25:27 -0500115 try:
116 json_object = utils.to_json_ordered(output)
117 except json.JSONDecodeError:
118 BuiltIn().fail(gp.sprint_error(output))
119
Michael Walsh0e7f3012018-10-11 17:05:46 -0500120 if json_object['status'] != "ok":
121 err_msg = "Error found in JSON data returned by the openbmctool.py "
122 err_msg += "command. Expected a 'status' field value of \"ok\":\n"
123 err_msg += gp.sprint_var(json_object, 1)
124 BuiltIn().fail(gp.sprint_error(err_msg))
125
126 return json_object['data']
127
128
Michael Walshd064ac92018-09-21 16:42:03 -0500129def get_fru_status():
130 r"""
131 Get the fru status and return as a list of dictionaries.
132
133 Example robot code:
134
135 ${fru_status}= Get Fru Status
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500136 Rprint Vars fru_status fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500137
138 Example result (excerpt):
139
140 fru_status:
141 fru_status[0]:
142 [component]: cpu0
143 [is_a]: Yes
144 [fru]: Yes
145 [present]: Yes
146 [functional]: No
147 fru_status[1]:
148 [component]: cpu0-core0
149 [is_a]: No
150 [fru]: Yes
151 [present]: Yes
152 [functional]: No
153 ...
154 """
155 rc, output = openbmctool_execute_command("fru status", print_output=False,
156 ignore_err=False)
157 # Example value for output (partial):
158 # Component | Is a FRU | Present | Functional | Has Logs
159 # cpu0 | Yes | Yes | Yes | No
160 # cpu0-core0 | No | Yes | Yes | No
161 # ...
162
163 # Replace spaces with underscores in field names (e.g. "Is a FRU" becomes
164 # "Is_a_FRU").
165 output = re.sub("([^ \\|])[ ]([^ ])", "\\1_\\2", output)
166 output = re.sub("([^ \\|])[ ]([^ ])", "\\1_\\2", output)
167
168 return vf.outbuf_to_report(output, field_delim="|")
169
170
171def get_fru_print(parse_json=True):
172 r"""
173 Get the output of the fru print command and return it either as raw JSON
174 data or as a list of dictionaries.
175
176 Example robot code:
177
178 ${fru_print}= Get Fru Print parse_json=${False}
179 Log to Console ${fru_print}
180
181 Example result (excerpt):
182
183 {
184 "data": {
185 "/xyz/openbmc_project/inventory/system": {
186 "AssetTag": "",
187 "BuildDate": "",
188 "Cached": false,
189 "FieldReplaceable": false,
190 "Manufacturer": "",
191 "Model": "xxxxxxxx",
192 "PartNumber": "",
193 "Present": true,
194 "PrettyName": "",
195 "SerialNumber": "13183FA"
196 },
197 "/xyz/openbmc_project/inventory/system/chassis": {
198 "AirCooled": true,
199 "WaterCooled": false
200 },
201 ...
202
203 Example robot code:
204
205 ${fru_print}= Get Fru Print
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500206 Rprint Vars fru_print fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500207
208 Example result (excerpt):
209
210 fru_print:
211 fru_print[0]:
212 [data]:
213 [/xyz/openbmc_project/inventory/system]:
214 [AssetTag]: <blank>
215 [BuildDate]: <blank>
216 [Cached]: False
217 [FieldReplaceable]: False
218 [Manufacturer]: <blank>
219 [Model]: xxxxxxxx
220 [PartNumber]: <blank>
221 [Present]: True
222 [PrettyName]: <blank>
223 [SerialNumber]: 13183FA
224 [/xyz/openbmc_project/inventory/system/chassis]:
225 [AirCooled]: True
226 [WaterCooled]: False
227 ...
228
229 Description of argument(s):
230 parse_json Indicates that the raw JSON data should
231 parsed into a list of dictionaries.
232 """
233
234 rc, output = openbmctool_execute_command("fru print", print_output=False,
235 ignore_err=False)
236 if parse_json:
237 return gm.json_loads_multiple(output)
238 else:
239 return output
240
241
242def get_fru_list(parse_json=True):
243 r"""
244 Get the output of the fru list command and return it either as raw JSON
245 data or as a list of dictionaries.
246
247 Example robot code:
248
249 ${fru_list}= Get Fru List parse_json=${False}
250 Log to Console ${fru_list}
251
252 Example result (excerpt):
253
254 {
255 "data": {
256 "/xyz/openbmc_project/inventory/system": {
257 "AssetTag": "",
258 "BuildDate": "",
259 "Cached": false,
260 "FieldReplaceable": false,
261 "Manufacturer": "",
262 "Model": "xxxxxxxx",
263 "PartNumber": "",
264 "Present": true,
265 "PrettyName": "",
266 "SerialNumber": "13183FA"
267 },
268 "/xyz/openbmc_project/inventory/system/chassis": {
269 "AirCooled": true,
270 "WaterCooled": false
271 },
272 ...
273
274 Example robot code:
275
276 ${fru_list}= Get Fru List
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500277 Rprint Vars fru_list fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500278
279 Example result (excerpt):
280
281 fru_list:
282 fru_list[0]:
283 [data]:
284 [/xyz/openbmc_project/inventory/system]:
285 [AssetTag]: <blank>
286 [BuildDate]: <blank>
287 [Cached]: False
288 [FieldReplaceable]: False
289 [Manufacturer]: <blank>
290 [Model]: xxxxxxxx
291 [PartNumber]: <blank>
292 [Present]: True
293 [PrettyName]: <blank>
294 [SerialNumber]: 13183FA
295 [/xyz/openbmc_project/inventory/system/chassis]:
296 [AirCooled]: True
297 [WaterCooled]: False
298 ...
299
300 Description of argument(s):
301 parse_json Indicates that the raw JSON data should
302 parsed into a list of dictionaries.
303 """
304
305 rc, output = openbmctool_execute_command("fru list", print_output=False,
306 ignore_err=False)
307 if parse_json:
308 return gm.json_loads_multiple(output)
309 else:
310 return output
311
312
313def get_sensors_print():
314
315 r"""
316 Get the output of the sensors print command and return as a list of
317 dictionaries.
318
319 Example robot code:
320
321 ${sensors_print}= Get Sensors Print
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500322 Rprint Vars sensors_print fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500323
324 Example result (excerpt):
325
326 sensors_print:
327 sensors_print[0]:
328 [sensor]: OCC0
329 [type]: Discrete
330 [units]: N/A
331 [value]: Active
332 [target]: Active
333 sensors_print[1]:
334 [sensor]: OCC1
335 [type]: Discrete
336 [units]: N/A
337 [value]: Active
338 [target]: Active
339 ...
340 """
341 rc, output = openbmctool_execute_command("sensors print",
342 print_output=False,
343 ignore_err=False)
344 # Example value for output (partial):
345 # sensor | type | units | value | target
346 # OCC0 | Discrete | N/A | Active | Active
347 # OCC1 | Discrete | N/A | Active | Active
348
349 return vf.outbuf_to_report(output, field_delim="|")
350
351
352def get_sensors_list():
353
354 r"""
355 Get the output of the sensors list command and return as a list of
356 dictionaries.
357
358 Example robot code:
359
360 ${sensors_list}= Get Sensors List
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500361 Rprint Vars sensors_list fmt=1
Michael Walshd064ac92018-09-21 16:42:03 -0500362
363 Example result (excerpt):
364
365 sensors_list:
366 sensors_list[0]:
367 [sensor]: OCC0
368 [type]: Discrete
369 [units]: N/A
370 [value]: Active
371 [target]: Active
372 sensors_list[1]:
373 [sensor]: OCC1
374 [type]: Discrete
375 [units]: N/A
376 [value]: Active
377 [target]: Active
378 ...
379 """
380 rc, output = openbmctool_execute_command("sensors list",
381 print_output=False,
382 ignore_err=False)
383 # Example value for output (partial):
384 # sensor | type | units | value | target
385 # OCC0 | Discrete | N/A | Active | Active
386 # OCC1 | Discrete | N/A | Active | Active
387
388 return vf.outbuf_to_report(output, field_delim="|")
389
390
391def get_openbmctool_version():
392 r"""
393 Get the openbmctool.py version and return it.
394
395 Example robot code:
396 ${openbmctool_version}= Get Openbmctool Version
397 Rprint Vars openbmctool_version
398
399 Example result (excerpt):
400 openbmctool_version: 1.06
401 """
402 rc, output = openbmctool_execute_command("-V | cut -f 2 -d ' '",
403 print_output=False,
404 ignore_err=False)
405 return output
Michael Walsh1793aeb2018-09-27 16:32:53 -0500406
407
408def service_data_files():
409 r"""
410 Return a complete list of file names that are expected to be created by
411 the collect_service_data command.
412 """
413
414 return\
415 [
416 "inventory.txt",
417 "sensorReadings.txt",
418 "ledStatus.txt",
419 "SELshortlist.txt",
420 "parsedSELs.txt",
421 "bmcFullRaw.txt"
422 ]
423
424
425def collect_service_data(verify=False):
426 r"""
427 Run the collect_service_data command and return a list of files generated
428 by the command.
429
430 Description of argument(s):
431 verify If set, verify that all files which can be
432 created by collect_service_data did, in
433 fact, get created.
434 """
435
436 # Route the output of collect_service_data to a file for easier parsing.
437 temp = tempfile.NamedTemporaryFile()
438 temp_file_path = temp.name
439 openbmctool_execute_command("collect_service_data > " + temp_file_path,
440 ignore_err=False)
441 # Isolate the file paths in the collect_service_data output. We're
442 # looking for output lines like this from which to extract the file paths:
443 # Inventory collected and stored in /tmp/dummy--2018-09-26_17.59.18/inventory.txt
444 rc, file_paths = gc.shell_cmd("egrep 'collected and' " + temp_file_path
445 # + " | sed -re 's#.*/tmp#/tmp#g'",
446 + " | sed -re 's#[^/]*/#/#'",
447 quiet=1, print_output=0)
448 # Example file_paths value:
449 # /tmp/dummy--2018-09-26_17.59.18/inventory.txt
450 # /tmp/dummy--2018-09-26_17.59.18/sensorReadings.txt
451 # etc.
452 # Convert from output to list.
453 collect_service_data_file_paths =\
454 list(filter(None, file_paths.split("\n")))
455 if int(verify):
456 # Create a list of files by stripping the dir names from the elements
457 # of collect_service_data_file_paths.
458 files_obtained = [re.sub(r".*/", "", file_path)
459 for file_path in collect_service_data_file_paths]
460 files_expected = service_data_files()
461 files_missing = list(set(files_expected) - set(files_obtained))
462 if len(files_missing) > 0:
463 gp.printn("collect_service_data output:\n"
464 + gm.file_to_str(temp_file_path))
465 err_msg = "The following files are missing from the list of files"
466 err_msg += " returned by collect_service_data:\n"
467 err_msg += gp.sprint_var(files_missing)
468 err_msg += gp.sprint_var(collect_service_data_file_paths)
469 BuiltIn().fail(gp.sprint_error(err_msg))
470
471 return collect_service_data_file_paths
472
473
474def health_check_fields():
475 r"""
476 Return a complete list of field names returned by the health_check command.
477 """
478
479 return\
480 [
481 "hardware_status",
482 "performance"
483 ]
484
485
486def get_health_check(verify=False):
487 r"""
488 Get the health_check information and return as a dictionary.
489
490 Example robot code:
491
492 ${health_check}= Get Health Check
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500493 Rprint Vars health_check fmt=1
Michael Walsh1793aeb2018-09-27 16:32:53 -0500494
495 Example result:
496
497 health_check:
498 [hardware_status]: OK
499 [performance]: OK
500
501 Description of argument(s):
502 verify If set, verify that all all expected
503 field_names are generated by the
504 health_check command.
505 """
506
507 rc, output = openbmctool_execute_command("health_check",
508 print_output=False,
509 ignore_err=False)
510 health_check = vf.key_value_outbuf_to_dict(output, delim=":")
511 if int(verify):
Michael Walshec01a6f2019-08-01 12:43:20 -0500512 err_msg = gv.valid_dict(health_check, health_check_fields())
Michael Walsh0e7f3012018-10-11 17:05:46 -0500513 if err_msg != "":
Michael Walsh1793aeb2018-09-27 16:32:53 -0500514 BuiltIn().fail(gp.sprint_error(err_msg))
515
516 return health_check
Michael Walsh0e7f3012018-10-11 17:05:46 -0500517
518
519def remote_logging_view_fields():
520 r"""
521 Return a complete list of field names returned by the logging
522 remote_logging view command.
523 """
524
525 return\
526 [
527 "Address",
Michael Walsh0e7f3012018-10-11 17:05:46 -0500528 "Port"
529 ]
530
531
532def get_remote_logging_view(verify=False):
533 r"""
534 Get the remote_logging view information and return as a dictionary.
535
536 Example robot code:
537
538 ${remote_logging_view}= Get Remote Logging View
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500539 Rprint Vars remote_logging_view fmt=1
Michael Walsh0e7f3012018-10-11 17:05:46 -0500540
541 Example result:
542
543 remote_logging_view:
544 [Address]: <blank>
545 [AddressFamily]: xyz.openbmc_project.Network.Client.IPProtocol.IPv4
546 [Port]: 0
547
548 Description of argument(s):
549 verify If set, verify that all all expected field
550 names are generated by the 'logging
551 remote_logging view' command.
552 """
553
554 remote_logging_view =\
555 openbmctool_execute_command_json("logging remote_logging view",
556 print_output=False,
557 ignore_err=False)
558
559 if int(verify):
Michael Walshec01a6f2019-08-01 12:43:20 -0500560 err_msg = gv.valid_dict(remote_logging_view,
561 remote_logging_view_fields())
Michael Walsh0e7f3012018-10-11 17:05:46 -0500562 if err_msg != "":
563 BuiltIn().fail(gp.sprint_error(err_msg))
564
565 return remote_logging_view
Michael Walshaaded2a2019-08-14 17:25:27 -0500566
567
568def network(sub_command, **options):
569 r"""
570 Run an openbmctool.py network command and return the results as a dictionary.
571
572 Note that any valid network argument may be specified as a function argument.
573
574 Example robot code:
575
576 ${ip_records}= Network getIP I=eth0
577 Rprint Vars ip_records
578
579 Resulting output:
580
581 ip_records:
582 [/xyz/openbmc_project/network/eth0/ipv4/23d41d48]:
583 [Address]: n.n.n.n
584 [Gateway]:
585 [Origin]: xyz.openbmc_project.Network.IP.AddressOrigin.Static
586 [PrefixLength]: 24
587 [Type]: xyz.openbmc_project.Network.IP.Protocol.IPv4
588 [/xyz/openbmc_project/network/eth0/ipv4/24ba5feb]:
589 [Address]: n.n.n.n
590 (etc.)
591
592 Description of argument(s):
593 sub_command The sub-command accepted by the network
594 command (e.g. "view-config", "getIP",
595 etc.).
596 options Zero or more options accepted by the network command.
597 """
598
599 if gm.python_version < gm.ordered_dict_version:
600 new_options = collections.OrderedDict(options)
601 else:
602 new_options = options
603
604 command_string = gc.create_command_string('network ' + sub_command,
605 new_options)
606 return openbmctool_execute_command_json(command_string,
607 print_output=False,
608 ignore_err=False)