xref: /openbmc/openbmc-test-automation/lib/utils.py (revision a618f67c922c4bd42a1f00820eca15a590fae412)
1#!/usr/bin/env python3
2
3r"""
4Companion file to utils.robot.
5"""
6
7import collections
8import json
9import os
10
11import bmc_ssh_utils as bsu
12import gen_print as gp
13import gen_robot_keyword as grk
14import var_funcs as vf
15from robot.libraries import DateTime
16from robot.libraries.BuiltIn import BuiltIn
17
18try:
19    from robot.utils import DotDict
20except ImportError:
21    pass
22import re
23
24# The code base directory will be one level up from the directory containing this module.
25code_base_dir_path = os.path.dirname(os.path.dirname(__file__)) + os.sep
26
27
28def get_code_base_dir_path():
29    r"""
30    Return the dir path of our code base.
31    """
32
33    return code_base_dir_path
34
35
36def set_power_policy_method():
37    r"""
38    Set the global bmc_power_policy_method to either 'Old' or 'New'.
39
40    The power policy data has moved from an 'org' location to an 'xyz'
41    location.  This keyword will determine whether the new method of getting
42    the power policy is valid and will set the global bmc_power_policy_method
43    variable accordingly.  If power_policy_setup is already set (by a prior
44    call to this function), this keyword will simply return.
45
46    If bmc_power_policy_method is "Old", this function will adjust the global
47    policy variables from data/variables.py: RESTORE_LAST_STATE,
48    ALWAYS_POWER_ON, ALWAYS_POWER_OFF.
49    """
50
51    # Retrieve global variables.
52    power_policy_setup = int(
53        BuiltIn().get_variable_value("${power_policy_setup}", default=0)
54    )
55    bmc_power_policy_method = BuiltIn().get_variable_value(
56        "${bmc_power_policy_method}", default=0
57    )
58    gp.dpvar(power_policy_setup)
59
60    # If this function has already been run once, we need not continue.
61    if power_policy_setup:
62        return
63
64    gp.dpvar(bmc_power_policy_method, 1)
65
66    # The user has not set bmc_power_policy_method via a -v parm so we will
67    # determine what it should be.
68    if bmc_power_policy_method == "":
69        status, ret_values = grk.run_key_u("New Get Power Policy", ignore=1)
70        if status == "PASS":
71            bmc_power_policy_method = "New"
72        else:
73            bmc_power_policy_method = "Old"
74
75    gp.qpvar(bmc_power_policy_method)
76    # For old style, we will rewrite these global variable settings to old
77    # values.
78    if bmc_power_policy_method == "Old":
79        BuiltIn().set_global_variable(
80            "${RESTORE_LAST_STATE}", "RESTORE_LAST_STATE"
81        )
82        BuiltIn().set_global_variable("${ALWAYS_POWER_ON}", "ALWAYS_POWER_ON")
83        BuiltIn().set_global_variable(
84            "${ALWAYS_POWER_OFF}", "ALWAYS_POWER_OFF"
85        )
86
87    # Set global variables to control subsequent calls to this function.
88    BuiltIn().set_global_variable(
89        "${bmc_power_policy_method}", bmc_power_policy_method
90    )
91    BuiltIn().set_global_variable("${power_policy_setup}", 1)
92
93
94def translate_power_policy_value(policy):
95    r"""
96    Translate the policy value and return the result.
97
98    Using old style functions, callers might call like this with a hard-
99    code value for policy:
100
101    Set BMC Power Policy  ALWAYS_POWER_OFF
102
103    This function will get the value of the corresponding global variable (if
104    it exists) and return it.
105
106    This will allow the old style call to still work on systems using the new
107    method of storing the policy value.
108    """
109
110    valid_power_policy_vars = BuiltIn().get_variable_value(
111        "${valid_power_policy_vars}"
112    )
113
114    if policy not in valid_power_policy_vars:
115        return policy
116
117    status, ret_values = grk.run_key_u(
118        "Get Variable Value  ${" + policy + "}", quiet=1
119    )
120    return ret_values
121
122
123def get_bmc_date_time():
124    r"""
125    Get date/time info from BMC and return as a dictionary.
126
127    Example of dictionary data returned by this keyword.
128    time_dict:
129      [local_time]:               Fri 2017-11-03 152756 UTC
130      [local_time_seconds]:       1509740876
131      [universal_time]:           Fri 2017-11-03 152756 UTC
132      [universal_time_seconds]:   1509740876
133      [rtc_time]:                 Fri 2016-05-20 163403
134      [rtc_time_seconds]:         1463780043
135      [time_zone]:                n/a (UTC, +0000)
136      [network_time_on]:          yes
137      [ntp_synchronized]:         no
138      [rtc_in_local_tz]:          no
139    """
140
141    out_buf, stderr, rc = bsu.bmc_execute_command("timedatectl")
142    # Example of output returned by call to timedatectl:
143    #       Local time: Fri 2017-11-03 15:27:56 UTC
144    #   Universal time: Fri 2017-11-03 15:27:56 UTC
145    #         RTC time: Fri 2016-05-20 16:34:03
146    #        Time zone: n/a (UTC, +0000)
147    #  Network time on: yes
148    # NTP synchronized: no
149    #  RTC in local TZ: no
150
151    # Convert the out_buf to a dictionary.
152    initial_time_dict = vf.key_value_outbuf_to_dict(out_buf)
153
154    # For each "_time" entry in the dictionary, we will create a corresponding
155    # "_time_seconds" entry.  We create a new dictionary so that the entries
156    # are kept in a nice order for printing.
157    try:
158        result_time_dict = collections.OrderedDict()
159    except AttributeError:
160        result_time_dict = DotDict()
161
162    for key, value in initial_time_dict.items():
163        result_time_dict[key] = value
164        if not key.endswith("_time"):
165            continue
166        result_time_dict[key + "_seconds"] = int(
167            DateTime.convert_date(value, result_format="epoch")
168        )
169
170    return result_time_dict
171
172
173def get_bmc_df(df_parm_string=""):
174    r"""
175        Get df report from BMC and return as a report "object".
176
177        A df report object is a list where each entry is a dictionary whose keys
178        are the field names from the first entry in report_list.
179
180        Example df report object:
181
182        df_report:
183          df_report[0]:
184            [filesystem]:    dev
185            [1k-blocks]:     247120
186            [used]:          0
187            [available]:     247120
188            [use%]:          0%
189            [mounted]:       /dev
190          df_report[1]:
191            [filesystem]:    dev
192            [1k-blocks]:     247120
193            [used]:          0
194            [available]:     247120
195            [use%]:          0%
196            [mounted]:       /dev
197
198    .   Description of argument(s):
199        df_parm_string  A string containing valid df command parms (e.g.
200                        "-h /var").
201    """
202
203    out_buf, stderr, rc = bsu.bmc_execute_command("df " + df_parm_string)
204    return vf.outbuf_to_report(out_buf)
205
206
207def get_sbe(cfam_addrs):
208    r"""
209    Return CFAM value which contains such things as SBE side bit.
210
211    Description of argument(s):
212    cfam_addrs      Provide the CFAM address
213    """
214
215    cmd_buf = (
216        "pdbg -d p9w -p0 getcfam " + cfam_addrs + " | sed -re 's/.* = //g'"
217    )
218    out_buf, stderr, rc = bsu.bmc_execute_command(cmd_buf)
219
220    return int(out_buf, 16)
221
222
223def compare_mac_address(sys_mac_addr, user_mac_addr):
224    r"""
225        Return 1 if the MAC value matched, otherwise 0.
226
227    .   Description of argument(s):
228        sys_mac_addr   A valid system MAC string (e.g. "70:e2:84:14:2a:08")
229        user_mac_addr  A user provided MAC string (e.g. "70:e2:84:14:2a:08")
230    """
231
232    index = 0
233    # Example: ['70', 'e2', '84', '14', '2a', '08']
234    mac_list = user_mac_addr.split(":")
235    for item in sys_mac_addr.split(":"):
236        if int(item, 16) == int(mac_list[index], 16):
237            index = index + 1
238            continue
239        return 0
240
241    return 1
242
243
244def get_os_ethtool(interface_name):
245    r"""
246    Get OS 'ethtool' output for the given interface_name and return it as a
247    dictionary.
248
249    Settings for enP52p1s0f0:
250          Supported ports: [ TP ]
251          Supported link modes:   10baseT/Half 10baseT/Full
252                                  100baseT/Half 100baseT/Full
253                                  1000baseT/Half 1000baseT/Full
254          Supported pause frame use: No
255          Supports auto-negotiation: Yes
256          Supported FEC modes: Not reported
257          Advertised link modes:  10baseT/Half 10baseT/Full
258                                  100baseT/Half 100baseT/Full
259                                  1000baseT/Half 1000baseT/Full
260          Advertised pause frame use: Symmetric
261          Advertised auto-negotiation: Yes
262          Advertised FEC modes: Not reported
263          Speed: Unknown!
264          Duplex: Unknown! (255)
265          Port: Twisted Pair
266          PHYAD: 1
267          Transceiver: internal
268          Auto-negotiation: on
269          MDI-X: Unknown
270          Supports Wake-on: g
271          Wake-on: g
272          Current message level: 0x000000ff (255)
273                                 drv probe link timer ifdown ifup rx_err tx_err
274          Link detected: no
275
276    Given that data, this function will return the following dictionary.
277
278    ethtool_dict:
279      [supported_ports]:             [ TP ]
280      [supported_link_modes]:
281        [supported_link_modes][0]:   10baseT/Half 10baseT/Full
282        [supported_link_modes][1]:   100baseT/Half 100baseT/Full
283        [supported_link_modes][2]:   1000baseT/Half 1000baseT/Full
284      [supported_pause_frame_use]:   No
285      [supports_auto-negotiation]:   Yes
286      [supported_fec_modes]:         Not reported
287      [advertised_link_modes]:
288        [advertised_link_modes][0]:  10baseT/Half 10baseT/Full
289        [advertised_link_modes][1]:  100baseT/Half 100baseT/Full
290        [advertised_link_modes][2]:  1000baseT/Half 1000baseT/Full
291      [advertised_pause_frame_use]:  Symmetric
292      [advertised_auto-negotiation]: Yes
293      [advertised_fec_modes]:        Not reported
294      [speed]:                       Unknown!
295      [duplex]:                      Unknown! (255)
296      [port]:                        Twisted Pair
297      [phyad]:                       1
298      [transceiver]:                 internal
299      [auto-negotiation]:            on
300      [mdi-x]:                       Unknown
301      [supports_wake-on]:            g
302      [wake-on]:                     g
303      [current_message_level]:       0x000000ff (255)
304      [drv_probe_link_timer_ifdown_ifup_rx_err_tx_err]:<blank>
305      [link_detected]:               no
306    """
307
308    # Using sed and tail to massage the data a bit before running
309    # key_value_outbuf_to_dict.
310    cmd_buf = (
311        "ethtool "
312        + interface_name
313        + " | sed -re 's/(.* link modes:)(.*)/\\1\\n\\2/g' | tail -n +2"
314    )
315    stdout, stderr, rc = bsu.os_execute_command(cmd_buf)
316    result = vf.key_value_outbuf_to_dict(stdout, process_indent=1, strip=" \t")
317
318    return result
319
320
321def to_json_ordered(json_str):
322    r"""
323    Parse the JSON string data and return an ordered JSON dictionary object.
324
325    Description of argument(s):
326    json_str                        The string containing the JSON data.
327    """
328
329    try:
330        return json.loads(json_str, object_pairs_hook=DotDict)
331    except TypeError:
332        return json.loads(json_str.decode("utf-8"), object_pairs_hook=DotDict)
333
334
335def get_bmc_release_info():
336    r"""
337    Get release info from the BMC and return as a dictionary.
338
339    Example:
340
341    ${release_info}=  Get BMC Release Info
342    Rprint Vars  release_info
343
344    Output:
345
346    release_info:
347      [id]:                           openbmc-phosphor
348      [name]:                         Phosphor OpenBMC (Phosphor OpenBMC Project Reference...
349      [version]:                      2.8.0-dev
350      [version_id]:                   2.8.0-dev-1083-g8954c3505
351      [pretty_name]:                  Phosphor OpenBMC (Phosphor OpenBMC Project Reference...
352      [build_id]:                     2.8.0-dev
353      [openbmc_target_machine]:       witherspoon
354    """
355
356    out_buf, stderr, rc = bsu.bmc_execute_command("cat /etc/os-release")
357    return vf.key_value_outbuf_to_dict(out_buf, delim="=", strip='"')
358
359
360def get_os_release_info():
361    r"""
362    Get release info from the OS and return as a dictionary.
363
364    Example:
365
366    ${release_info}=  Get OS Release Info
367    Rprint Vars  release_info
368
369    Output:
370    release_info:
371      [name]:                                         Red Hat Enterprise Linux Server
372      [version]:                                      7.6 (Maipo)
373      [id]:                                           rhel
374      [id_like]:                                      fedora
375      [variant]:                                      Server
376      [variant_id]:                                   server
377      [version_id]:                                   7.6
378      [pretty_name]:                                  Red Hat Enterprise Linux Server 7.6 (Maipo)
379      [ansi_color]:                                   0;31
380      [cpe_name]:                                     cpe:/o:redhat:enterprise_linux:7.6:GA:server
381      [home_url]:                                     https://www.redhat.com/
382      [bug_report_url]:                               https://bugzilla.redhat.com/
383      [redhat_bugzilla_product]:                      Red Hat Enterprise Linux 7
384      [redhat_bugzilla_product_version]:              7.6
385      [redhat_support_product]:                       Red Hat Enterprise Linux
386      [redhat_support_product_version]:               7.6
387    """
388
389    out_buf, stderr, rc = bsu.os_execute_command("cat /etc/os-release")
390    return vf.key_value_outbuf_to_dict(out_buf, delim="=", strip='"')
391
392
393def pdbg(option_string, **bsu_options):
394    r"""
395    Run pdbg on the BMC with the caller's option string and return the output.
396
397    Description of argument(s):
398    option_string    A string of options which are to be processed by the pdbg command.
399    bsu_options      Options to be passed directly to bmc_execute_command.  See its prolog for
400                     details.
401    """
402
403    # Default print_out to 1.
404    if "print_out" not in bsu_options:
405        bsu_options["print_out"] = 1
406
407    stdout, stderr, rc = bsu.bmc_execute_command(
408        "pdbg " + option_string, **bsu_options
409    )
410    return stdout
411
412
413def ecmd(option_string, **bsu_options):
414    r"""
415    Run ecmd command on the BMC with the caller's option string and return the output.
416
417    Description of argument(s):
418    option_string    A string of options which are to be executed on BMC.
419                     (e.g. getscom pu 20010a40 -all,
420                     putscom pu 20010a40 4000000000000000 -p0).
421    bsu_options      Options to be passed directly to bmc_execute_command.  See its prolog for
422                     details.
423    """
424
425    # Default print_out to 1.
426    if "print_out" not in bsu_options:
427        bsu_options["print_out"] = 1
428
429    stdout, stderr, rc = bsu.bmc_execute_command(option_string, **bsu_options)
430    return stdout
431
432
433def split_string_with_index(stri, n):
434    r"""
435    To split every n characters and forms an element for every nth index
436
437    Example : Given ${stri} = "abcdef", then the function call,
438    ${data}=  Split List With Index  ${stri}  2
439    then, result will be data = ['ab', 'cd', 'ef']
440    """
441
442    n = int(n)
443    data = [stri[index : index + n] for index in range(0, len(stri), n)]
444    return data
445
446
447def remove_whitespace(instring):
448    r"""
449    Removes the white spaces around the string
450
451    Example: instring = "  xxx  ", then returns instring = "xxx"
452    """
453
454    return instring.strip()
455
456
457def zfill_data(data, num):
458    r"""
459    zfill() method adds zeros (0) at the beginning of the string, until it
460    reaches the specified length.
461
462    Usage : ${anystr}=  Zfill Data  ${data}  num
463
464    Example : Binary of one Byte has 8 bits - xxxx xxxx
465
466    Consider ${binary} has only 3 bits after converting from Hexadecimal/decimal to Binary
467    Say ${binary} = 110 then,
468    ${binary}=  Zfill Data  ${binary}  8
469    Now ${binary} will be 0000 0110
470    """
471
472    return data.zfill(int(num))
473
474
475def get_subsequent_value_from_list(list, value):
476    r"""
477    returns first index of the element occurrence.
478    """
479
480    index = [list.index(i) for i in list if value in i]
481    return index
482
483
484def return_decoded_string(input):
485    r"""
486    returns decoded string of encoded byte.
487    """
488
489    encoded_string = input.encode("ascii", "ignore")
490    decoded_string = encoded_string.decode()
491    return decoded_string
492
493
494def remove_unicode_from_uri(uri):
495    r"""
496    returns dbus uri without unicode in prefix
497    """
498
499    return re.sub("`-|\\|-", "", uri)
500
501
502def get_bmc_major_minor_version(version):
503    r"""
504    returns major version and minor version
505    from cat /etc/os-release command.
506    For example,
507    xyz23.01 --> [23, 01]
508    xyz.0-112 --> [0, 112]
509    ZERzzYY-23.04-1-xx3 --> [23, 04, 1, 3]
510    """
511
512    return re.findall(r"\d+", re.sub("[A-Z]|[a-z]", "", version))
513
514
515def convert_name_into_bytes_with_prefix(name):
516    r"""
517    Convert name into bytes with prefix 0x
518    """
519
520    bytes_list = []
521
522    for letter in name:
523        bytes_list.append(hex(ord(letter)))
524
525    return bytes_list
526
527
528def convert_name_into_bytes_without_prefix(name):
529    r"""
530    Convert name into bytes
531    """
532
533    tmp_lst = []
534
535    for letter in name:
536        value = convert_to_hex_value_without_prefix(letter)
537        tmp_lst.append(value)
538
539    return tmp_lst
540
541
542def convert_to_hex_value_without_prefix(letter):
543    r"""
544    Convert into hex
545    """
546
547    value = hex(ord(letter))
548    if value[:2] == "0x":
549        value = value[2:]
550
551    return value
552
553
554def convert_prefix_hex_list_to_non_prefix_hex_list(list):
555    r"""
556    Convert into list of hex with prefix to list of hex without prefix.
557    """
558
559    tmp_list = []
560
561    for value in list:
562        if value[:2] == "0x":
563            tmp_list.append(value[2:])
564
565    return tmp_list
566