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 += " -P " + BuiltIn().get_variable_value("${IPMI_PASSWORD}")
117        cmd_buf += " " + BuiltIn().get_variable_value("${HOST}")
118        cmd_buf += " " + BuiltIn().get_variable_value("${OPENBMC_HOST}")
119        cmd_buf += " " + cmd_string
120        rc, stdout, stderr = gc.shell_cmd(cmd_buf,
121                                          print_output=print_output,
122                                          ignore_err=ignore_err,
123                                          return_stderr=1)
124        return stdout, stderr, rc
125
126
127def get_lan_print_dict(ipmi_cmd_type='external'):
128    r"""
129    Get IPMI 'lan print' output and return it as a dictionary.
130
131    Here is an example of the IPMI lan print output:
132
133    Set in Progress         : Set Complete
134    Auth Type Support       : MD5
135    Auth Type Enable        : Callback : MD5
136                            : User     : MD5
137                            : Operator : MD5
138                            : Admin    : MD5
139                            : OEM      : MD5
140    IP Address Source       : Static Address
141    IP Address              : x.x.x.x
142    Subnet Mask             : x.x.x.x
143    MAC Address             : xx:xx:xx:xx:xx:xx
144    Default Gateway IP      : x.x.x.x
145    802.1q VLAN ID          : Disabled
146    Cipher Suite Priv Max   : Not Available
147    Bad Password Threshold  : Not Available
148
149    Given that data, this function will return the following dictionary.
150
151    lan_print_dict:
152      [Set in Progress]:                              Set Complete
153      [Auth Type Support]:                            MD5
154      [Auth Type Enable]:
155        [Callback]:                                   MD5
156        [User]:                                       MD5
157        [Operator]:                                   MD5
158        [Admin]:                                      MD5
159        [OEM]:                                        MD5
160      [IP Address Source]:                            Static Address
161      [IP Address]:                                   x.x.x.x
162      [Subnet Mask]:                                  x.x.x.x
163      [MAC Address]:                                  xx:xx:xx:xx:xx:xx
164      [Default Gateway IP]:                           x.x.x.x
165      [802.1q VLAN ID]:                               Disabled
166      [Cipher Suite Priv Max]:                        Not Available
167      [Bad Password Threshold]:                       Not Available
168
169    Description of argument(s):
170    ipmi_cmd_type                   The type of ipmi command to use (e.g.
171                                    'inband', 'external').
172    """
173
174    # Notice in the example of data above that 'Auth Type Enable' needs some
175    # special processing.  We essentially want to isolate its data and remove
176    # the 'Auth Type Enable' string so that key_value_outbuf_to_dict can
177    # process it as a sub-dictionary.
178    cmd_buf = "lan print | grep -E '^(Auth Type Enable)" +\
179        "?[ ]+: ' | sed -re 's/^(Auth Type Enable)?[ ]+: //g'"
180    stdout1, stderr, rc = execute_ipmi_cmd(cmd_buf, ipmi_cmd_type,
181                                           print_output=0)
182
183    # Now get the remainder of the data and exclude the lines with no field
184    # names (i.e. the 'Auth Type Enable' sub-fields).
185    cmd_buf = "lan print | grep -E -v '^[ ]+: '"
186    stdout2, stderr, rc = execute_ipmi_cmd(cmd_buf, ipmi_cmd_type,
187                                           print_output=0)
188
189    # Make auth_type_enable_dict sub-dictionary...
190    auth_type_enable_dict = vf.key_value_outbuf_to_dict(stdout1, to_lower=0,
191                                                        underscores=0)
192
193    # Create the lan_print_dict...
194    lan_print_dict = vf.key_value_outbuf_to_dict(stdout2, to_lower=0,
195                                                 underscores=0)
196    # Re-assign 'Auth Type Enable' to contain the auth_type_enable_dict.
197    lan_print_dict['Auth Type Enable'] = auth_type_enable_dict
198
199    return lan_print_dict
200
201
202def get_ipmi_power_reading(strip_watts=1):
203    r"""
204    Get IPMI power reading data and return it as a dictionary.
205
206    The data is obtained by issuing the IPMI "power reading" command.  An
207    example is shown below:
208
209    Instantaneous power reading:                   234 Watts
210    Minimum during sampling period:                234 Watts
211    Maximum during sampling period:                234 Watts
212    Average power reading over sample period:      234 Watts
213    IPMI timestamp:                           Thu Jan  1 00:00:00 1970
214    Sampling period:                          00000000 Seconds.
215    Power reading state is:                   deactivated
216
217    For the data shown above, the following dictionary will be returned.
218
219    result:
220      [instantaneous_power_reading]:              238 Watts
221      [minimum_during_sampling_period]:           238 Watts
222      [maximum_during_sampling_period]:           238 Watts
223      [average_power_reading_over_sample_period]: 238 Watts
224      [ipmi_timestamp]:                           Thu Jan  1 00:00:00 1970
225      [sampling_period]:                          00000000 Seconds.
226      [power_reading_state_is]:                   deactivated
227
228    Description of argument(s):
229    strip_watts                     Strip all dictionary values of the
230                                    trailing " Watts" substring.
231    """
232
233    status, ret_values = \
234        grk.run_key_u("Run IPMI Standard Command  dcmi power reading")
235    result = vf.key_value_outbuf_to_dict(ret_values)
236
237    if strip_watts:
238        result.update((k, re.sub(' Watts$', '', v)) for k, v in result.items())
239
240    return result
241
242
243def get_mc_info():
244    r"""
245    Get IPMI mc info data and return it as a dictionary.
246
247    The data is obtained by issuing the IPMI "mc info" command.  An
248    example is shown below:
249
250    Device ID                 : 0
251    Device Revision           : 0
252    Firmware Revision         : 2.01
253    IPMI Version              : 2.0
254    Manufacturer ID           : 42817
255    Manufacturer Name         : Unknown (0xA741)
256    Product ID                : 16975 (0x424f)
257    Product Name              : Unknown (0x424F)
258    Device Available          : yes
259    Provides Device SDRs      : yes
260    Additional Device Support :
261        Sensor Device
262        SEL Device
263        FRU Inventory Device
264        Chassis Device
265    Aux Firmware Rev Info     :
266        0x00
267        0x00
268        0x00
269        0x00
270
271    For the data shown above, the following dictionary will be returned.
272    mc_info:
273      [device_id]:                       0
274      [device_revision]:                 0
275      [firmware_revision]:               2.01
276      [ipmi_version]:                    2.0
277      [manufacturer_id]:                 42817
278      [manufacturer_name]:               Unknown (0xA741)
279      [product_id]:                      16975 (0x424f)
280      [product_name]:                    Unknown (0x424F)
281      [device_available]:                yes
282      [provides_device_sdrs]:            yes
283      [additional_device_support]:
284        [additional_device_support][0]:  Sensor Device
285        [additional_device_support][1]:  SEL Device
286        [additional_device_support][2]:  FRU Inventory Device
287        [additional_device_support][3]:  Chassis Device
288      [aux_firmware_rev_info]:
289        [aux_firmware_rev_info][0]:      0x00
290        [aux_firmware_rev_info][1]:      0x00
291        [aux_firmware_rev_info][2]:      0x00
292        [aux_firmware_rev_info][3]:      0x00
293    """
294
295    status, ret_values = \
296        grk.run_key_u("Run IPMI Standard Command  mc info")
297    result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1)
298
299    return result
300
301
302def get_sdr_info():
303    r"""
304    Get IPMI sdr info data and return it as a dictionary.
305
306    The data is obtained by issuing the IPMI "sdr info" command.  An
307    example is shown below:
308
309    SDR Version                         : 0x51
310    Record Count                        : 216
311    Free Space                          : unspecified
312    Most recent Addition                :
313    Most recent Erase                   :
314    SDR overflow                        : no
315    SDR Repository Update Support       : unspecified
316    Delete SDR supported                : no
317    Partial Add SDR supported           : no
318    Reserve SDR repository supported    : no
319    SDR Repository Alloc info supported : no
320
321    For the data shown above, the following dictionary will be returned.
322    mc_info:
323
324      [sdr_version]:                         0x51
325      [record_Count]:                        216
326      [free_space]:                          unspecified
327      [most_recent_addition]:
328      [most_recent_erase]:
329      [sdr_overflow]:                        no
330      [sdr_repository_update_support]:       unspecified
331      [delete_sdr_supported]:                no
332      [partial_add_sdr_supported]:           no
333      [reserve_sdr_repository_supported]:    no
334      [sdr_repository_alloc_info_supported]: no
335    """
336
337    status, ret_values = \
338        grk.run_key_u("Run IPMI Standard Command  sdr info")
339    result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1)
340
341    return result
342
343
344def get_aux_version(version_id):
345    r"""
346    Get IPMI Aux version info data and return it.
347
348    Description of argument(s):
349    version_id                      The data is obtained by from BMC
350                                    /etc/os-release
351                                    (e.g. "xxx-v2.1-438-g0030304-r3-gfea8585").
352
353    In the prior example, the 3rd field is "438" is the commit version and
354    the 5th field is "r3" and value "3" is the release version.
355
356    Aux version return from this function 4380003.
357    """
358
359    # Commit version.
360    count = re.findall("-(\\d{1,4})-", version_id)
361
362    # Release version.
363    release = re.findall("-r(\\d{1,4})", version_id)
364    if release:
365        aux_version = count[0] + "{0:0>4}".format(release[0])
366    else:
367        aux_version = count[0] + "0000"
368
369    return aux_version
370
371
372def get_fru_info():
373    r"""
374    Get fru info and return it as a list of dictionaries.
375
376    The data is obtained by issuing the IPMI "fru print -N 50" command.  An
377    example is shown below:
378
379    FRU Device Description : Builtin FRU Device (ID 0)
380     Device not present (Unspecified error)
381
382    FRU Device Description : cpu0 (ID 1)
383     Board Mfg Date        : Sun Dec 31 18:00:00 1995
384     Board Mfg             : <Manufacturer Name>
385     Board Product         : PROCESSOR MODULE
386     Board Serial          : YA1934315964
387     Board Part Number     : 02CY209
388
389    FRU Device Description : cpu1 (ID 2)
390     Board Mfg Date        : Sun Dec 31 18:00:00 1995
391     Board Mfg             : <Manufacturer Name>
392     Board Product         : PROCESSOR MODULE
393     Board Serial          : YA1934315965
394     Board Part Number     : 02CY209
395
396    For the data shown above, the following list of dictionaries will be
397    returned.
398
399    fru_obj:
400      fru_obj[0]:
401        [fru_device_description]:  Builtin FRU Device (ID 0)
402        [state]:                   Device not present (Unspecified error)
403      fru_obj[1]:
404        [fru_device_description]:  cpu0 (ID 1)
405        [board_mfg_date]:          Sun Dec 31 18:00:00 1995
406        [board_mfg]:               <Manufacturer Name>
407        [board_product]:           PROCESSOR MODULE
408        [board_serial]:            YA1934315964
409        [board_part_number]:       02CY209
410      fru_obj[2]:
411        [fru_device_description]:  cpu1 (ID 2)
412        [board_mfg_date]:          Sun Dec 31 18:00:00 1995
413        [board_mfg]:               <Manufacturer Name>
414        [board_product]:           PROCESSOR MODULE
415        [board_serial]:            YA1934315965
416        [board_part_number]:       02CY209
417    """
418
419    status, ret_values = \
420        grk.run_key_u("Run IPMI Standard Command  fru print -N 50")
421
422    # Manipulate the "Device not present" line to create a "state" key.
423    ret_values = re.sub("Device not present", "state : Device not present",
424                        ret_values)
425
426    return [vf.key_value_outbuf_to_dict(x) for x in re.split("\n\n",
427                                                             ret_values)]
428
429
430def get_component_fru_info(component='cpu',
431                           fru_objs=None):
432    r"""
433    Get fru info for the given component and return it as a list of
434    dictionaries.
435
436    This function calls upon get_fru_info and then filters out the unwanted
437    entries.  See get_fru_info's prolog for a layout of the data.
438
439    Description of argument(s):
440    component                       The component (e.g. "cpu", "dimm", etc.).
441    fru_objs                        A fru_objs list such as the one returned
442                                    by get_fru_info.  If this is None, then
443                                    this function will call get_fru_info to
444                                    obtain such a list.
445                                    Supplying this argument may improve
446                                    performance if this function is to be
447                                    called multiple times.
448    """
449
450    if fru_objs is None:
451        fru_objs = get_fru_info()
452    return\
453        [x for x in fru_objs
454         if re.match(component + '([0-9]+)? ', x['fru_device_description'])]
455