1#!/usr/bin/env python
2
3r"""
4Provide useful ipmi functions.
5"""
6
7import re
8import gen_print as gp
9import gen_misc as gm
10import gen_cmd as gc
11import gen_robot_keyword as grk
12import gen_robot_utils as gru
13import bmc_ssh_utils as bsu
14import var_funcs as vf
15import tempfile
16gru.my_import_resource("ipmi_client.robot")
17from robot.libraries.BuiltIn import BuiltIn
18
19
20def get_sol_info():
21    r"""
22    Get all SOL info and return it as a dictionary.
23
24    Example use:
25
26    Robot code:
27    ${sol_info}=  get_sol_info
28    Rpvars  sol_info
29
30    Output:
31    sol_info:
32      sol_info[Info]:                                SOL parameter 'Payload Channel (7)'
33                                                     not supported - defaulting to 0x0e
34      sol_info[Character Send Threshold]:            1
35      sol_info[Force Authentication]:                true
36      sol_info[Privilege Level]:                     USER
37      sol_info[Set in progress]:                     set-complete
38      sol_info[Retry Interval (ms)]:                 100
39      sol_info[Non-Volatile Bit Rate (kbps)]:        IPMI-Over-Serial-Setting
40      sol_info[Character Accumulate Level (ms)]:     100
41      sol_info[Enabled]:                             true
42      sol_info[Volatile Bit Rate (kbps)]:            IPMI-Over-Serial-Setting
43      sol_info[Payload Channel]:                     14 (0x0e)
44      sol_info[Payload Port]:                        623
45      sol_info[Force Encryption]:                    true
46      sol_info[Retry Count]:                         7
47    """
48
49    status, ret_values = grk.run_key_u("Run IPMI Standard Command  sol info")
50
51    # Create temp file path.
52    temp = tempfile.NamedTemporaryFile()
53    temp_file_path = temp.name
54
55    # Write sol info to temp file path.
56    text_file = open(temp_file_path, "w")
57    text_file.write(ret_values)
58    text_file.close()
59
60    # Use my_parm_file to interpret data.
61    sol_info = gm.my_parm_file(temp_file_path)
62
63    return sol_info
64
65
66def set_sol_setting(setting_name, setting_value):
67    r"""
68    Set SOL setting with given value.
69
70    # Description of argument(s):
71    # setting_name                  SOL setting which needs to be set (e.g.
72    #                               "retry-count").
73    # setting_value                 Value which needs to be set (e.g. "7").
74    """
75
76    status, ret_values = grk.run_key_u("Run IPMI Standard Command  sol set "
77                                       + setting_name + " " + setting_value)
78
79    return status
80
81
82def execute_ipmi_cmd(cmd_string,
83                     ipmi_cmd_type='inband',
84                     print_output=1,
85                     ignore_err=0):
86    r"""
87    Run the given command string as an IPMI command and return the stdout,
88    stderr and the return code.
89
90    Description of argument(s):
91    cmd_string                      The command string to be run as an IPMI
92                                    command.
93    ipmi_cmd_type                   'inband' or 'external'.
94    print_output                    If this is set, this function will print
95                                    the stdout/stderr generated by
96                                    the IPMI command.
97    ignore_err                      Ignore error means that a failure
98                                    encountered by running the command
99                                    string will not be raised as a python
100                                    exception.
101    """
102
103    if ipmi_cmd_type == 'inband':
104        IPMI_INBAND_CMD = BuiltIn().get_variable_value("${IPMI_INBAND_CMD}")
105        cmd_buf = IPMI_INBAND_CMD + " " + cmd_string
106        return bsu.os_execute_command(cmd_buf,
107                                      print_out=print_output,
108                                      ignore_err=ignore_err)
109
110    if ipmi_cmd_type == 'external':
111        cmd_buf = BuiltIn().get_variable_value("${IPMI_EXT_CMD}")
112        IPMI_USER_OPTIONS =\
113            BuiltIn().get_variable_value("${IPMI_USER_OPTIONS}")
114        if IPMI_USER_OPTIONS != "":
115            cmd_buf += " " + IPMI_USER_OPTIONS
116        cmd_buf += " " + BuiltIn().get_variable_value("${HOST}")
117        cmd_buf += " " + BuiltIn().get_variable_value("${OPENBMC_HOST}")
118        cmd_buf += " " + cmd_string
119        rc, stdout, stderr = gc.shell_cmd(cmd_buf,
120                                          print_output=print_output,
121                                          ignore_err=ignore_err,
122                                          return_stderr=1)
123        return stdout, stderr, rc
124
125
126def get_lan_print_dict(ipmi_cmd_type='external'):
127    r"""
128    Get IPMI 'lan print' output and return it as a dictionary.
129
130    Here is an example of the IPMI lan print output:
131
132    Set in Progress         : Set Complete
133    Auth Type Support       : MD5
134    Auth Type Enable        : Callback : MD5
135                            : User     : MD5
136                            : Operator : MD5
137                            : Admin    : MD5
138                            : OEM      : MD5
139    IP Address Source       : Static Address
140    IP Address              : x.x.x.x
141    Subnet Mask             : x.x.x.x
142    MAC Address             : xx:xx:xx:xx:xx:xx
143    Default Gateway IP      : x.x.x.x
144    802.1q VLAN ID          : Disabled
145    Cipher Suite Priv Max   : Not Available
146    Bad Password Threshold  : Not Available
147
148    Given that data, this function will return the following dictionary.
149
150    lan_print_dict:
151      [Set in Progress]:                              Set Complete
152      [Auth Type Support]:                            MD5
153      [Auth Type Enable]:
154        [Callback]:                                   MD5
155        [User]:                                       MD5
156        [Operator]:                                   MD5
157        [Admin]:                                      MD5
158        [OEM]:                                        MD5
159      [IP Address Source]:                            Static Address
160      [IP Address]:                                   x.x.x.x
161      [Subnet Mask]:                                  x.x.x.x
162      [MAC Address]:                                  xx:xx:xx:xx:xx:xx
163      [Default Gateway IP]:                           x.x.x.x
164      [802.1q VLAN ID]:                               Disabled
165      [Cipher Suite Priv Max]:                        Not Available
166      [Bad Password Threshold]:                       Not Available
167
168    Description of argument(s):
169    ipmi_cmd_type                   The type of ipmi command to use (e.g.
170                                    'inband', 'external').
171    """
172
173    # Notice in the example of data above that 'Auth Type Enable' needs some
174    # special processing.  We essentially want to isolate its data and remove
175    # the 'Auth Type Enable' string so that key_value_outbuf_to_dict can
176    # process it as a sub-dictionary.
177    cmd_buf = "lan print | grep -E '^(Auth Type Enable)" +\
178        "?[ ]+: ' | sed -re 's/^(Auth Type Enable)?[ ]+: //g'"
179    stdout1, stderr, rc = execute_ipmi_cmd(cmd_buf, ipmi_cmd_type,
180                                           print_output=0)
181
182    # Now get the remainder of the data and exclude the lines with no field
183    # names (i.e. the 'Auth Type Enable' sub-fields).
184    cmd_buf = "lan print | grep -E -v '^[ ]+: '"
185    stdout2, stderr, rc = execute_ipmi_cmd(cmd_buf, ipmi_cmd_type,
186                                           print_output=0)
187
188    # Make auth_type_enable_dict sub-dictionary...
189    auth_type_enable_dict = vf.key_value_outbuf_to_dict(stdout1, to_lower=0,
190                                                        underscores=0)
191
192    # Create the lan_print_dict...
193    lan_print_dict = vf.key_value_outbuf_to_dict(stdout2, to_lower=0,
194                                                 underscores=0)
195    # Re-assign 'Auth Type Enable' to contain the auth_type_enable_dict.
196    lan_print_dict['Auth Type Enable'] = auth_type_enable_dict
197
198    return lan_print_dict
199
200
201def get_ipmi_power_reading(strip_watts=1):
202    r"""
203    Get IPMI power reading data and return it as a dictionary.
204
205    The data is obtained by issuing the IPMI "power reading" command.  An
206    example is shown below:
207
208    Instantaneous power reading:                   234 Watts
209    Minimum during sampling period:                234 Watts
210    Maximum during sampling period:                234 Watts
211    Average power reading over sample period:      234 Watts
212    IPMI timestamp:                           Thu Jan  1 00:00:00 1970
213    Sampling period:                          00000000 Seconds.
214    Power reading state is:                   deactivated
215
216    For the data shown above, the following dictionary will be returned.
217
218    result:
219      [instantaneous_power_reading]:              238 Watts
220      [minimum_during_sampling_period]:           238 Watts
221      [maximum_during_sampling_period]:           238 Watts
222      [average_power_reading_over_sample_period]: 238 Watts
223      [ipmi_timestamp]:                           Thu Jan  1 00:00:00 1970
224      [sampling_period]:                          00000000 Seconds.
225      [power_reading_state_is]:                   deactivated
226
227    Description of argument(s):
228    strip_watts                     Strip all dictionary values of the
229                                    trailing " Watts" substring.
230    """
231
232    status, ret_values = \
233        grk.run_key_u("Run IPMI Standard Command  dcmi power reading")
234    result = vf.key_value_outbuf_to_dict(ret_values)
235
236    if strip_watts:
237        result.update((k, re.sub(' Watts$', '', v)) for k, v in result.items())
238
239    return result
240
241
242def get_mc_info():
243    r"""
244    Get IPMI mc info data and return it as a dictionary.
245
246    The data is obtained by issuing the IPMI "mc info" command.  An
247    example is shown below:
248
249    Device ID                 : 0
250    Device Revision           : 0
251    Firmware Revision         : 2.01
252    IPMI Version              : 2.0
253    Manufacturer ID           : 42817
254    Manufacturer Name         : Unknown (0xA741)
255    Product ID                : 16975 (0x424f)
256    Product Name              : Unknown (0x424F)
257    Device Available          : yes
258    Provides Device SDRs      : yes
259    Additional Device Support :
260        Sensor Device
261        SEL Device
262        FRU Inventory Device
263        Chassis Device
264    Aux Firmware Rev Info     :
265        0x00
266        0x00
267        0x00
268        0x00
269
270    For the data shown above, the following dictionary will be returned.
271    mc_info:
272      [device_id]:                       0
273      [device_revision]:                 0
274      [firmware_revision]:               2.01
275      [ipmi_version]:                    2.0
276      [manufacturer_id]:                 42817
277      [manufacturer_name]:               Unknown (0xA741)
278      [product_id]:                      16975 (0x424f)
279      [product_name]:                    Unknown (0x424F)
280      [device_available]:                yes
281      [provides_device_sdrs]:            yes
282      [additional_device_support]:
283        [additional_device_support][0]:  Sensor Device
284        [additional_device_support][1]:  SEL Device
285        [additional_device_support][2]:  FRU Inventory Device
286        [additional_device_support][3]:  Chassis Device
287      [aux_firmware_rev_info]:
288        [aux_firmware_rev_info][0]:      0x00
289        [aux_firmware_rev_info][1]:      0x00
290        [aux_firmware_rev_info][2]:      0x00
291        [aux_firmware_rev_info][3]:      0x00
292    """
293
294    status, ret_values = \
295        grk.run_key_u("Run IPMI Standard Command  mc info")
296    result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1)
297
298    return result
299
300
301def get_sdr_info():
302    r"""
303    Get IPMI sdr info data and return it as a dictionary.
304
305    The data is obtained by issuing the IPMI "sdr info" command.  An
306    example is shown below:
307
308    SDR Version                         : 0x51
309    Record Count                        : 216
310    Free Space                          : unspecified
311    Most recent Addition                :
312    Most recent Erase                   :
313    SDR overflow                        : no
314    SDR Repository Update Support       : unspecified
315    Delete SDR supported                : no
316    Partial Add SDR supported           : no
317    Reserve SDR repository supported    : no
318    SDR Repository Alloc info supported : no
319
320    For the data shown above, the following dictionary will be returned.
321    mc_info:
322
323      [sdr_version]:                         0x51
324      [record_Count]:                        216
325      [free_space]:                          unspecified
326      [most_recent_addition]:
327      [most_recent_erase]:
328      [sdr_overflow]:                        no
329      [sdr_repository_update_support]:       unspecified
330      [delete_sdr_supported]:                no
331      [partial_add_sdr_supported]:           no
332      [reserve_sdr_repository_supported]:    no
333      [sdr_repository_alloc_info_supported]: no
334    """
335
336    status, ret_values = \
337        grk.run_key_u("Run IPMI Standard Command  sdr info")
338    result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1)
339
340    return result
341
342
343def get_aux_version(version_id):
344    r"""
345    Get IPMI Aux version info data and return it.
346
347    Description of argument(s):
348    version_id                      The data is obtained by from BMC
349                                    /etc/os-release
350                                    (e.g. "xxx-v2.1-438-g0030304-r3-gfea8585").
351
352    In the prior example, the 3rd field is "438" is the commit version and
353    the 5th field is "r3" and value "3" is the release version.
354
355    Aux version return from this function 4380003.
356    """
357
358    # Commit version.
359    count = re.findall("-(\\d{1,4})-", version_id)
360
361    # Release version.
362    release = re.findall("-r(\\d{1,4})", version_id)
363    if release:
364        aux_version = count[0] + "{0:0>4}".format(release[0])
365    else:
366        aux_version = count[0] + "0000"
367
368    return aux_version
369
370
371def get_fru_info():
372    r"""
373    Get fru info and return it as a list of dictionaries.
374
375    The data is obtained by issuing the IPMI "fru print -N 50" command.  An
376    example is shown below:
377
378    FRU Device Description : Builtin FRU Device (ID 0)
379     Device not present (Unspecified error)
380
381    FRU Device Description : cpu0 (ID 1)
382     Board Mfg Date        : Sun Dec 31 18:00:00 1995
383     Board Mfg             : <Manufacturer Name>
384     Board Product         : PROCESSOR MODULE
385     Board Serial          : YA1934315964
386     Board Part Number     : 02CY209
387
388    FRU Device Description : cpu1 (ID 2)
389     Board Mfg Date        : Sun Dec 31 18:00:00 1995
390     Board Mfg             : <Manufacturer Name>
391     Board Product         : PROCESSOR MODULE
392     Board Serial          : YA1934315965
393     Board Part Number     : 02CY209
394
395    For the data shown above, the following list of dictionaries will be
396    returned.
397
398    fru_obj:
399      fru_obj[0]:
400        [fru_device_description]:  Builtin FRU Device (ID 0)
401        [state]:                   Device not present (Unspecified error)
402      fru_obj[1]:
403        [fru_device_description]:  cpu0 (ID 1)
404        [board_mfg_date]:          Sun Dec 31 18:00:00 1995
405        [board_mfg]:               <Manufacturer Name>
406        [board_product]:           PROCESSOR MODULE
407        [board_serial]:            YA1934315964
408        [board_part_number]:       02CY209
409      fru_obj[2]:
410        [fru_device_description]:  cpu1 (ID 2)
411        [board_mfg_date]:          Sun Dec 31 18:00:00 1995
412        [board_mfg]:               <Manufacturer Name>
413        [board_product]:           PROCESSOR MODULE
414        [board_serial]:            YA1934315965
415        [board_part_number]:       02CY209
416    """
417
418    status, ret_values = \
419        grk.run_key_u("Run IPMI Standard Command  fru print -N 50")
420
421    # Manipulate the "Device not present" line to create a "state" key.
422    ret_values = re.sub("Device not present", "state : Device not present",
423                        ret_values)
424
425    return [vf.key_value_outbuf_to_dict(x) for x in re.split("\n\n",
426                                                             ret_values)]
427
428
429def get_component_fru_info(component='cpu',
430                           fru_objs=None):
431    r"""
432    Get fru info for the given component and return it as a list of
433    dictionaries.
434
435    This function calls upon get_fru_info and then filters out the unwanted
436    entries.  See get_fru_info's prolog for a layout of the data.
437
438    Description of argument(s):
439    component                       The component (e.g. "cpu", "dimm", etc.).
440    fru_objs                        A fru_objs list such as the one returned
441                                    by get_fru_info.  If this is None, then
442                                    this function will call get_fru_info to
443                                    obtain such a list.
444                                    Supplying this argument may improve
445                                    performance if this function is to be
446                                    called multiple times.
447    """
448
449    if fru_objs is None:
450        fru_objs = get_fru_info()
451    return\
452        [x for x in fru_objs
453         if re.match(component + '([0-9]+)? ', x['fru_device_description'])]
454