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
19import json
20
21
22def get_sol_info():
23    r"""
24    Get all SOL info and return it as a dictionary.
25
26    Example use:
27
28    Robot code:
29    ${sol_info}=  get_sol_info
30    Rpvars  sol_info
31
32    Output:
33    sol_info:
34      sol_info[Info]:                                SOL parameter 'Payload Channel (7)'
35                                                     not supported - defaulting to 0x0e
36      sol_info[Character Send Threshold]:            1
37      sol_info[Force Authentication]:                true
38      sol_info[Privilege Level]:                     USER
39      sol_info[Set in progress]:                     set-complete
40      sol_info[Retry Interval (ms)]:                 100
41      sol_info[Non-Volatile Bit Rate (kbps)]:        IPMI-Over-Serial-Setting
42      sol_info[Character Accumulate Level (ms)]:     100
43      sol_info[Enabled]:                             true
44      sol_info[Volatile Bit Rate (kbps)]:            IPMI-Over-Serial-Setting
45      sol_info[Payload Channel]:                     14 (0x0e)
46      sol_info[Payload Port]:                        623
47      sol_info[Force Encryption]:                    true
48      sol_info[Retry Count]:                         7
49    """
50
51    status, ret_values = grk.run_key_u("Run IPMI Standard Command  sol info")
52
53    # Create temp file path.
54    temp = tempfile.NamedTemporaryFile()
55    temp_file_path = temp.name
56
57    # Write sol info to temp file path.
58    text_file = open(temp_file_path, "w")
59    text_file.write(ret_values)
60    text_file.close()
61
62    # Use my_parm_file to interpret data.
63    sol_info = gm.my_parm_file(temp_file_path)
64
65    return sol_info
66
67
68def set_sol_setting(setting_name, setting_value):
69    r"""
70    Set SOL setting with given value.
71
72    # Description of argument(s):
73    # setting_name                  SOL setting which needs to be set (e.g.
74    #                               "retry-count").
75    # setting_value                 Value which needs to be set (e.g. "7").
76    """
77
78    status, ret_values = grk.run_key_u("Run IPMI Standard Command  sol set "
79                                       + setting_name + " " + setting_value)
80
81    return status
82
83
84def execute_ipmi_cmd(cmd_string,
85                     ipmi_cmd_type='inband',
86                     print_output=1,
87                     ignore_err=0,
88                     **options):
89    r"""
90    Run the given command string as an IPMI command and return the stdout,
91    stderr and the return code.
92
93    Description of argument(s):
94    cmd_string                      The command string to be run as an IPMI
95                                    command.
96    ipmi_cmd_type                   'inband' or 'external'.
97    print_output                    If this is set, this function will print
98                                    the stdout/stderr generated by
99                                    the IPMI command.
100    ignore_err                      Ignore error means that a failure
101                                    encountered by running the command
102                                    string will not be raised as a python
103                                    exception.
104    options                         These are passed directly to the
105                                    create_ipmi_ext_command_string function.
106                                    See that function's prolog for details.
107    """
108
109    if ipmi_cmd_type == 'inband':
110        IPMI_INBAND_CMD = BuiltIn().get_variable_value("${IPMI_INBAND_CMD}")
111        cmd_buf = IPMI_INBAND_CMD + " " + cmd_string
112        return bsu.os_execute_command(cmd_buf,
113                                      print_out=print_output,
114                                      ignore_err=ignore_err)
115
116    if ipmi_cmd_type == 'external':
117        cmd_buf = ic.create_ipmi_ext_command_string(cmd_string, **options)
118        rc, stdout, stderr = gc.shell_cmd(cmd_buf,
119                                          print_output=print_output,
120                                          ignore_err=ignore_err,
121                                          return_stderr=1)
122        return stdout, stderr, rc
123
124
125def get_lan_print_dict(channel_number='', ipmi_cmd_type='external'):
126    r"""
127    Get IPMI 'lan print' output and return it as a dictionary.
128
129    Here is an example of the IPMI lan print output:
130
131    Set in Progress         : Set Complete
132    Auth Type Support       : MD5
133    Auth Type Enable        : Callback : MD5
134                            : User     : MD5
135                            : Operator : MD5
136                            : Admin    : MD5
137                            : OEM      : MD5
138    IP Address Source       : Static Address
139    IP Address              : x.x.x.x
140    Subnet Mask             : x.x.x.x
141    MAC Address             : xx:xx:xx:xx:xx:xx
142    Default Gateway IP      : x.x.x.x
143    802.1q VLAN ID          : Disabled
144    Cipher Suite Priv Max   : Not Available
145    Bad Password Threshold  : Not Available
146
147    Given that data, this function will return the following dictionary.
148
149    lan_print_dict:
150      [Set in Progress]:                              Set Complete
151      [Auth Type Support]:                            MD5
152      [Auth Type Enable]:
153        [Callback]:                                   MD5
154        [User]:                                       MD5
155        [Operator]:                                   MD5
156        [Admin]:                                      MD5
157        [OEM]:                                        MD5
158      [IP Address Source]:                            Static Address
159      [IP Address]:                                   x.x.x.x
160      [Subnet Mask]:                                  x.x.x.x
161      [MAC Address]:                                  xx:xx:xx:xx:xx:xx
162      [Default Gateway IP]:                           x.x.x.x
163      [802.1q VLAN ID]:                               Disabled
164      [Cipher Suite Priv Max]:                        Not Available
165      [Bad Password Threshold]:                       Not Available
166
167    Description of argument(s):
168    ipmi_cmd_type                   The type of ipmi command to use (e.g.
169                                    'inband', 'external').
170    """
171
172    channel_number = str(channel_number)
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 " + channel_number + " | 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 " + channel_number + " | 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
455
456def get_user_info(userid, channel_number=1):
457    r"""
458    Get user info using channel command and return it as a dictionary.
459
460    Description of argument(s):
461    userid          The userid (e.g. "1", "2", etc.).
462    channel_number  The user's channel number (e.g. "1").
463
464    Note: If userid is blank, this function will return a list of dictionaries.  Each list entry represents
465    one userid record.
466
467    The data is obtained by issuing the IPMI "channel getaccess" command.  An
468    example is shown below for user id 1 and channel number 1.
469
470    Maximum User IDs     : 15
471    Enabled User IDs     : 1
472    User ID              : 1
473    User Name            : root
474    Fixed Name           : No
475    Access Available     : callback
476    Link Authentication  : enabled
477    IPMI Messaging       : enabled
478    Privilege Level      : ADMINISTRATOR
479    Enable Status        : enabled
480
481    For the data shown above, the following dictionary will be returned.
482
483    user_info:
484      [maximum_userids]:     15
485      [enabled_userids:      1
486      [userid]               1
487      [user_name]            root
488      [fixed_name]           No
489      [access_available]     callback
490      [link_authentication]  enabled
491      [ipmi_messaging]       enabled
492      [privilege_level]      ADMINISTRATOR
493      [enable_status]        enabled
494    """
495
496    status, ret_values = grk.run_key_u("Run IPMI Standard Command  channel getaccess "
497                                       + str(channel_number) + " " + str(userid))
498
499    if userid == "":
500        return vf.key_value_outbuf_to_dicts(ret_values, process_indent=1)
501    else:
502        return vf.key_value_outbuf_to_dict(ret_values, process_indent=1)
503
504
505def channel_getciphers_ipmi():
506
507    r"""
508    Run 'channel getciphers ipmi' command and return the result as a list of dictionaries.
509
510    Example robot code:
511    ${ipmi_channel_ciphers}=  Channel Getciphers IPMI
512    Rprint Vars  ipmi_channel_ciphers
513
514    Example output:
515    ipmi_channel_ciphers:
516      [0]:
517        [id]:                                         3
518        [iana]:                                       N/A
519        [auth_alg]:                                   hmac_sha1
520        [integrity_alg]:                              hmac_sha1_96
521        [confidentiality_alg]:                        aes_cbc_128
522      [1]:
523        [id]:                                         17
524        [iana]:                                       N/A
525        [auth_alg]:                                   hmac_sha256
526        [integrity_alg]:                              sha256_128
527        [confidentiality_alg]:                        aes_cbc_128
528    """
529
530    cmd_buf = "channel getciphers ipmi | sed -re 's/ Alg/_Alg/g'"
531    stdout, stderr, rc = execute_ipmi_cmd(cmd_buf, "external", print_output=0)
532    return vf.outbuf_to_report(stdout)
533
534
535def get_device_id_config():
536    r"""
537    Get the device id config data and return as a dictionary.
538
539    Example:
540
541    dev_id_config =  get_device_id_config()
542    print_vars(dev_id_config)
543
544    dev_id_config:
545        [manuf_id]:            7244
546        [addn_dev_support]:     141
547        [prod_id]:            16976
548        [aux]:                    0
549        [id]:                    32
550        [revision]:             129
551        [device_revision]:        1
552    """
553    stdout, stderr, rc = bsu.bmc_execute_command("cat /usr/share/ipmi-providers/dev_id.json")
554
555    result = json.loads(stdout)
556
557    # Create device revision field for the user.
558    # Reference IPMI specification v2.0 "Get Device ID Command"
559    # [7]   1 = device provides Device SDRs
560    #       0 = device does not provide Device SDRs
561    # [6:4] reserved. Return as 0.
562    # [3:0] Device Revision, binary encoded.
563
564    result['device_revision'] = result['revision'] & 0x0F
565
566    return result
567