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