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():
208    r"""
209    Return CFAM value which contains such things as SBE side bit.
210    """
211
212    cmd_buf = "pdbg -d p9w -p0 getcfam 0x2808 | sed -re 's/.* = //g'"
213    out_buf, stderr, rc = bsu.bmc_execute_command(cmd_buf)
214
215    return int(out_buf, 16)
216
217
218def compare_mac_address(sys_mac_addr, user_mac_addr):
219    r"""
220        Return 1 if the MAC value matched, otherwise 0.
221
222    .   Description of argument(s):
223        sys_mac_addr   A valid system MAC string (e.g. "70:e2:84:14:2a:08")
224        user_mac_addr  A user provided MAC string (e.g. "70:e2:84:14:2a:08")
225    """
226
227    index = 0
228    # Example: ['70', 'e2', '84', '14', '2a', '08']
229    mac_list = user_mac_addr.split(":")
230    for item in sys_mac_addr.split(":"):
231        if int(item, 16) == int(mac_list[index], 16):
232            index = index + 1
233            continue
234        return 0
235
236    return 1
237
238
239def get_os_ethtool(interface_name):
240    r"""
241    Get OS 'ethtool' output for the given interface_name and return it as a
242    dictionary.
243
244    Settings for enP52p1s0f0:
245          Supported ports: [ TP ]
246          Supported link modes:   10baseT/Half 10baseT/Full
247                                  100baseT/Half 100baseT/Full
248                                  1000baseT/Half 1000baseT/Full
249          Supported pause frame use: No
250          Supports auto-negotiation: Yes
251          Supported FEC modes: Not reported
252          Advertised link modes:  10baseT/Half 10baseT/Full
253                                  100baseT/Half 100baseT/Full
254                                  1000baseT/Half 1000baseT/Full
255          Advertised pause frame use: Symmetric
256          Advertised auto-negotiation: Yes
257          Advertised FEC modes: Not reported
258          Speed: Unknown!
259          Duplex: Unknown! (255)
260          Port: Twisted Pair
261          PHYAD: 1
262          Transceiver: internal
263          Auto-negotiation: on
264          MDI-X: Unknown
265          Supports Wake-on: g
266          Wake-on: g
267          Current message level: 0x000000ff (255)
268                                 drv probe link timer ifdown ifup rx_err tx_err
269          Link detected: no
270
271    Given that data, this function will return the following dictionary.
272
273    ethtool_dict:
274      [supported_ports]:             [ TP ]
275      [supported_link_modes]:
276        [supported_link_modes][0]:   10baseT/Half 10baseT/Full
277        [supported_link_modes][1]:   100baseT/Half 100baseT/Full
278        [supported_link_modes][2]:   1000baseT/Half 1000baseT/Full
279      [supported_pause_frame_use]:   No
280      [supports_auto-negotiation]:   Yes
281      [supported_fec_modes]:         Not reported
282      [advertised_link_modes]:
283        [advertised_link_modes][0]:  10baseT/Half 10baseT/Full
284        [advertised_link_modes][1]:  100baseT/Half 100baseT/Full
285        [advertised_link_modes][2]:  1000baseT/Half 1000baseT/Full
286      [advertised_pause_frame_use]:  Symmetric
287      [advertised_auto-negotiation]: Yes
288      [advertised_fec_modes]:        Not reported
289      [speed]:                       Unknown!
290      [duplex]:                      Unknown! (255)
291      [port]:                        Twisted Pair
292      [phyad]:                       1
293      [transceiver]:                 internal
294      [auto-negotiation]:            on
295      [mdi-x]:                       Unknown
296      [supports_wake-on]:            g
297      [wake-on]:                     g
298      [current_message_level]:       0x000000ff (255)
299      [drv_probe_link_timer_ifdown_ifup_rx_err_tx_err]:<blank>
300      [link_detected]:               no
301    """
302
303    # Using sed and tail to massage the data a bit before running
304    # key_value_outbuf_to_dict.
305    cmd_buf = (
306        "ethtool "
307        + interface_name
308        + " | sed -re 's/(.* link modes:)(.*)/\\1\\n\\2/g' | tail -n +2"
309    )
310    stdout, stderr, rc = bsu.os_execute_command(cmd_buf)
311    result = vf.key_value_outbuf_to_dict(stdout, process_indent=1, strip=" \t")
312
313    return result
314
315
316def to_json_ordered(json_str):
317    r"""
318    Parse the JSON string data and return an ordered JSON dictionary object.
319
320    Description of argument(s):
321    json_str                        The string containing the JSON data.
322    """
323
324    try:
325        return json.loads(json_str, object_pairs_hook=DotDict)
326    except TypeError:
327        return json.loads(json_str.decode("utf-8"), object_pairs_hook=DotDict)
328
329
330def get_bmc_release_info():
331    r"""
332    Get release info from the BMC and return as a dictionary.
333
334    Example:
335
336    ${release_info}=  Get BMC Release Info
337    Rprint Vars  release_info
338
339    Output:
340
341    release_info:
342      [id]:                           openbmc-phosphor
343      [name]:                         Phosphor OpenBMC (Phosphor OpenBMC Project Reference...
344      [version]:                      2.8.0-dev
345      [version_id]:                   2.8.0-dev-1083-g8954c3505
346      [pretty_name]:                  Phosphor OpenBMC (Phosphor OpenBMC Project Reference...
347      [build_id]:                     2.8.0-dev
348      [openbmc_target_machine]:       witherspoon
349    """
350
351    out_buf, stderr, rc = bsu.bmc_execute_command("cat /etc/os-release")
352    return vf.key_value_outbuf_to_dict(out_buf, delim="=", strip='"')
353
354
355def get_os_release_info():
356    r"""
357    Get release info from the OS and return as a dictionary.
358
359    Example:
360
361    ${release_info}=  Get OS Release Info
362    Rprint Vars  release_info
363
364    Output:
365    release_info:
366      [name]:                                         Red Hat Enterprise Linux Server
367      [version]:                                      7.6 (Maipo)
368      [id]:                                           rhel
369      [id_like]:                                      fedora
370      [variant]:                                      Server
371      [variant_id]:                                   server
372      [version_id]:                                   7.6
373      [pretty_name]:                                  Red Hat Enterprise Linux Server 7.6 (Maipo)
374      [ansi_color]:                                   0;31
375      [cpe_name]:                                     cpe:/o:redhat:enterprise_linux:7.6:GA:server
376      [home_url]:                                     https://www.redhat.com/
377      [bug_report_url]:                               https://bugzilla.redhat.com/
378      [redhat_bugzilla_product]:                      Red Hat Enterprise Linux 7
379      [redhat_bugzilla_product_version]:              7.6
380      [redhat_support_product]:                       Red Hat Enterprise Linux
381      [redhat_support_product_version]:               7.6
382    """
383
384    out_buf, stderr, rc = bsu.os_execute_command("cat /etc/os-release")
385    return vf.key_value_outbuf_to_dict(out_buf, delim="=", strip='"')
386
387
388def pdbg(option_string, **bsu_options):
389    r"""
390    Run pdbg on the BMC with the caller's option string and return the output.
391
392    Description of argument(s):
393    option_string    A string of options which are to be processed by the pdbg command.
394    bsu_options      Options to be passed directly to bmc_execute_command.  See its prolog for
395                     details.
396    """
397
398    # Default print_out to 1.
399    if "print_out" not in bsu_options:
400        bsu_options["print_out"] = 1
401
402    stdout, stderr, rc = bsu.bmc_execute_command(
403        "pdbg " + option_string, **bsu_options
404    )
405    return stdout
406
407
408def ecmd(option_string, **bsu_options):
409    r"""
410    Run ecmd command on the BMC with the caller's option string and return the output.
411
412    Description of argument(s):
413    option_string    A string of options which are to be executed on BMC.
414                     (e.g. getscom pu 20010a40 -all,
415                     putscom pu 20010a40 4000000000000000 -p0).
416    bsu_options      Options to be passed directly to bmc_execute_command.  See its prolog for
417                     details.
418    """
419
420    # Default print_out to 1.
421    if "print_out" not in bsu_options:
422        bsu_options["print_out"] = 1
423
424    stdout, stderr, rc = bsu.bmc_execute_command(option_string, **bsu_options)
425    return stdout
426
427
428def split_string_with_index(stri, n):
429    r"""
430    To split every n characters and forms an element for every nth index
431
432    Example : Given ${stri} = "abcdef", then the function call,
433    ${data}=  Split List With Index  ${stri}  2
434    then, result will be data = ['ab', 'cd', 'ef']
435    """
436
437    n = int(n)
438    data = [stri[index : index + n] for index in range(0, len(stri), n)]
439    return data
440
441
442def remove_whitespace(instring):
443    r"""
444    Removes the white spaces around the string
445
446    Example: instring = "  xxx  ", then returns instring = "xxx"
447    """
448
449    return instring.strip()
450
451
452def zfill_data(data, num):
453    r"""
454    zfill() method adds zeros (0) at the beginning of the string, until it
455    reaches the specified length.
456
457    Usage : ${anystr}=  Zfill Data  ${data}  num
458
459    Example : Binary of one Byte has 8 bits - xxxx xxxx
460
461    Consider ${binary} has only 3 bits after converting from Hexadecimal/decimal to Binary
462    Say ${binary} = 110 then,
463    ${binary}=  Zfill Data  ${binary}  8
464    Now ${binary} will be 0000 0110
465    """
466
467    return data.zfill(int(num))
468
469
470def get_subsequent_value_from_list(list, value):
471    r"""
472    returns first index of the element occurrence.
473    """
474
475    index = [list.index(i) for i in list if value in i]
476    return index
477
478
479def return_decoded_string(input):
480    r"""
481    returns decoded string of encoded byte.
482    """
483
484    encoded_string = input.encode("ascii", "ignore")
485    decoded_string = encoded_string.decode()
486    return decoded_string
487
488
489def remove_unicode_from_uri(uri):
490    r"""
491    returns dbus uri without unicode in prefix
492    """
493
494    return re.sub("`-|\\|-", "", uri)
495
496
497def get_bmc_major_minor_version(version):
498    r"""
499    returns major version and minor version
500    from cat /etc/os-release command.
501    For example,
502    xyz23.01 --> [23, 01]
503    xyz.0-112 --> [0, 112]
504    ZERzzYY-23.04-1-xx3 --> [23, 04, 1, 3]
505    """
506
507    return re.findall(r"\d+", re.sub("[A-Z]|[a-z]", "", version))
508