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(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    # Notice in the example of data above that 'Auth Type Enable' needs some
172    # special processing.  We essentially want to isolate its data and remove
173    # the 'Auth Type Enable' string so that key_value_outbuf_to_dict can
174    # process it as a sub-dictionary.
175    cmd_buf = "lan print | grep -E '^(Auth Type Enable)" +\
176        "?[ ]+: ' | sed -re 's/^(Auth Type Enable)?[ ]+: //g'"
177    stdout1, stderr, rc = execute_ipmi_cmd(cmd_buf, ipmi_cmd_type,
178                                           print_output=0)
179
180    # Now get the remainder of the data and exclude the lines with no field
181    # names (i.e. the 'Auth Type Enable' sub-fields).
182    cmd_buf = "lan print | grep -E -v '^[ ]+: '"
183    stdout2, stderr, rc = execute_ipmi_cmd(cmd_buf, ipmi_cmd_type,
184                                           print_output=0)
185
186    # Make auth_type_enable_dict sub-dictionary...
187    auth_type_enable_dict = vf.key_value_outbuf_to_dict(stdout1, to_lower=0,
188                                                        underscores=0)
189
190    # Create the lan_print_dict...
191    lan_print_dict = vf.key_value_outbuf_to_dict(stdout2, to_lower=0,
192                                                 underscores=0)
193    # Re-assign 'Auth Type Enable' to contain the auth_type_enable_dict.
194    lan_print_dict['Auth Type Enable'] = auth_type_enable_dict
195
196    return lan_print_dict
197
198
199def get_ipmi_power_reading(strip_watts=1):
200    r"""
201    Get IPMI power reading data and return it as a dictionary.
202
203    The data is obtained by issuing the IPMI "power reading" command.  An
204    example is shown below:
205
206    Instantaneous power reading:                   234 Watts
207    Minimum during sampling period:                234 Watts
208    Maximum during sampling period:                234 Watts
209    Average power reading over sample period:      234 Watts
210    IPMI timestamp:                           Thu Jan  1 00:00:00 1970
211    Sampling period:                          00000000 Seconds.
212    Power reading state is:                   deactivated
213
214    For the data shown above, the following dictionary will be returned.
215
216    result:
217      [instantaneous_power_reading]:              238 Watts
218      [minimum_during_sampling_period]:           238 Watts
219      [maximum_during_sampling_period]:           238 Watts
220      [average_power_reading_over_sample_period]: 238 Watts
221      [ipmi_timestamp]:                           Thu Jan  1 00:00:00 1970
222      [sampling_period]:                          00000000 Seconds.
223      [power_reading_state_is]:                   deactivated
224
225    Description of argument(s):
226    strip_watts                     Strip all dictionary values of the
227                                    trailing " Watts" substring.
228    """
229
230    status, ret_values = \
231        grk.run_key_u("Run IPMI Standard Command  dcmi power reading")
232    result = vf.key_value_outbuf_to_dict(ret_values)
233
234    if strip_watts:
235        result.update((k, re.sub(' Watts$', '', v)) for k, v in result.items())
236
237    return result
238
239
240def get_mc_info():
241    r"""
242    Get IPMI mc info data and return it as a dictionary.
243
244    The data is obtained by issuing the IPMI "mc info" command.  An
245    example is shown below:
246
247    Device ID                 : 0
248    Device Revision           : 0
249    Firmware Revision         : 2.01
250    IPMI Version              : 2.0
251    Manufacturer ID           : 42817
252    Manufacturer Name         : Unknown (0xA741)
253    Product ID                : 16975 (0x424f)
254    Product Name              : Unknown (0x424F)
255    Device Available          : yes
256    Provides Device SDRs      : yes
257    Additional Device Support :
258        Sensor Device
259        SEL Device
260        FRU Inventory Device
261        Chassis Device
262    Aux Firmware Rev Info     :
263        0x00
264        0x00
265        0x00
266        0x00
267
268    For the data shown above, the following dictionary will be returned.
269    mc_info:
270      [device_id]:                       0
271      [device_revision]:                 0
272      [firmware_revision]:               2.01
273      [ipmi_version]:                    2.0
274      [manufacturer_id]:                 42817
275      [manufacturer_name]:               Unknown (0xA741)
276      [product_id]:                      16975 (0x424f)
277      [product_name]:                    Unknown (0x424F)
278      [device_available]:                yes
279      [provides_device_sdrs]:            yes
280      [additional_device_support]:
281        [additional_device_support][0]:  Sensor Device
282        [additional_device_support][1]:  SEL Device
283        [additional_device_support][2]:  FRU Inventory Device
284        [additional_device_support][3]:  Chassis Device
285      [aux_firmware_rev_info]:
286        [aux_firmware_rev_info][0]:      0x00
287        [aux_firmware_rev_info][1]:      0x00
288        [aux_firmware_rev_info][2]:      0x00
289        [aux_firmware_rev_info][3]:      0x00
290    """
291
292    status, ret_values = \
293        grk.run_key_u("Run IPMI Standard Command  mc info")
294    result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1)
295
296    return result
297
298
299def get_sdr_info():
300    r"""
301    Get IPMI sdr info data and return it as a dictionary.
302
303    The data is obtained by issuing the IPMI "sdr info" command.  An
304    example is shown below:
305
306    SDR Version                         : 0x51
307    Record Count                        : 216
308    Free Space                          : unspecified
309    Most recent Addition                :
310    Most recent Erase                   :
311    SDR overflow                        : no
312    SDR Repository Update Support       : unspecified
313    Delete SDR supported                : no
314    Partial Add SDR supported           : no
315    Reserve SDR repository supported    : no
316    SDR Repository Alloc info supported : no
317
318    For the data shown above, the following dictionary will be returned.
319    mc_info:
320
321      [sdr_version]:                         0x51
322      [record_Count]:                        216
323      [free_space]:                          unspecified
324      [most_recent_addition]:
325      [most_recent_erase]:
326      [sdr_overflow]:                        no
327      [sdr_repository_update_support]:       unspecified
328      [delete_sdr_supported]:                no
329      [partial_add_sdr_supported]:           no
330      [reserve_sdr_repository_supported]:    no
331      [sdr_repository_alloc_info_supported]: no
332    """
333
334    status, ret_values = \
335        grk.run_key_u("Run IPMI Standard Command  sdr info")
336    result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1)
337
338    return result
339
340
341def get_aux_version(version_id):
342    r"""
343    Get IPMI Aux version info data and return it.
344
345    Description of argument(s):
346    version_id                      The data is obtained by from BMC
347                                    /etc/os-release
348                                    (e.g. "xxx-v2.1-438-g0030304-r3-gfea8585").
349
350    In the prior example, the 3rd field is "438" is the commit version and
351    the 5th field is "r3" and value "3" is the release version.
352
353    Aux version return from this function 4380003.
354    """
355
356    # Commit version.
357    count = re.findall("-(\\d{1,4})-", version_id)
358
359    # Release version.
360    release = re.findall("-r(\\d{1,4})", version_id)
361    if release:
362        aux_version = count[0] + "{0:0>4}".format(release[0])
363    else:
364        aux_version = count[0] + "0000"
365
366    return aux_version
367
368
369def get_fru_info():
370    r"""
371    Get fru info and return it as a list of dictionaries.
372
373    The data is obtained by issuing the IPMI "fru print -N 50" command.  An
374    example is shown below:
375
376    FRU Device Description : Builtin FRU Device (ID 0)
377     Device not present (Unspecified error)
378
379    FRU Device Description : cpu0 (ID 1)
380     Board Mfg Date        : Sun Dec 31 18:00:00 1995
381     Board Mfg             : <Manufacturer Name>
382     Board Product         : PROCESSOR MODULE
383     Board Serial          : YA1934315964
384     Board Part Number     : 02CY209
385
386    FRU Device Description : cpu1 (ID 2)
387     Board Mfg Date        : Sun Dec 31 18:00:00 1995
388     Board Mfg             : <Manufacturer Name>
389     Board Product         : PROCESSOR MODULE
390     Board Serial          : YA1934315965
391     Board Part Number     : 02CY209
392
393    For the data shown above, the following list of dictionaries will be
394    returned.
395
396    fru_obj:
397      fru_obj[0]:
398        [fru_device_description]:  Builtin FRU Device (ID 0)
399        [state]:                   Device not present (Unspecified error)
400      fru_obj[1]:
401        [fru_device_description]:  cpu0 (ID 1)
402        [board_mfg_date]:          Sun Dec 31 18:00:00 1995
403        [board_mfg]:               <Manufacturer Name>
404        [board_product]:           PROCESSOR MODULE
405        [board_serial]:            YA1934315964
406        [board_part_number]:       02CY209
407      fru_obj[2]:
408        [fru_device_description]:  cpu1 (ID 2)
409        [board_mfg_date]:          Sun Dec 31 18:00:00 1995
410        [board_mfg]:               <Manufacturer Name>
411        [board_product]:           PROCESSOR MODULE
412        [board_serial]:            YA1934315965
413        [board_part_number]:       02CY209
414    """
415
416    status, ret_values = \
417        grk.run_key_u("Run IPMI Standard Command  fru print -N 50")
418
419    # Manipulate the "Device not present" line to create a "state" key.
420    ret_values = re.sub("Device not present", "state : Device not present",
421                        ret_values)
422
423    return [vf.key_value_outbuf_to_dict(x) for x in re.split("\n\n",
424                                                             ret_values)]
425
426
427def get_component_fru_info(component='cpu',
428                           fru_objs=None):
429    r"""
430    Get fru info for the given component and return it as a list of
431    dictionaries.
432
433    This function calls upon get_fru_info and then filters out the unwanted
434    entries.  See get_fru_info's prolog for a layout of the data.
435
436    Description of argument(s):
437    component                       The component (e.g. "cpu", "dimm", etc.).
438    fru_objs                        A fru_objs list such as the one returned
439                                    by get_fru_info.  If this is None, then
440                                    this function will call get_fru_info to
441                                    obtain such a list.
442                                    Supplying this argument may improve
443                                    performance if this function is to be
444                                    called multiple times.
445    """
446
447    if fru_objs is None:
448        fru_objs = get_fru_info()
449    return\
450        [x for x in fru_objs
451         if re.match(component + '([0-9]+)? ', x['fru_device_description'])]
452
453
454def get_user_info(userid, channel_number=1):
455    r"""
456    Get user info using channel command and return it as a dictionary.
457
458    Description of argument(s):
459    userid          The userid (e.g. "1", "2", etc.).
460    channel_number  The user's channel number (e.g. "1").
461
462    The data is obtained by issuing the IPMI "channel getaccess" command.  An
463    example is shown below for user id 1 and channel number 1.
464
465    Maximum User IDs     : 15
466    Enabled User IDs     : 1
467    User ID              : 1
468    User Name            : root
469    Fixed Name           : No
470    Access Available     : callback
471    Link Authentication  : enabled
472    IPMI Messaging       : enabled
473    Privilege Level      : ADMINISTRATOR
474    Enable Status        : enabled
475
476    For the data shown above, the following dictionary will be returned.
477
478    user_info:
479      [maximum_userids]:     15
480      [enabled_userids:      1
481      [userid]               1
482      [user_name]            root
483      [fixed_name]           No
484      [access_available]     callback
485      [link_authentication]  enabled
486      [ipmi_messaging]       enabled
487      [privilege_level]      ADMINISTRATOR
488      [enable_status]        enabled
489
490    """
491
492    status, ret_values = grk.run_key_u("Run IPMI Standard Command  channel getaccess "
493                                       + str(channel_number) + " " + str(userid))
494
495    result = vf.key_value_outbuf_to_dict(ret_values, process_indent=1)
496
497    return result
498
499
500def channel_getciphers_ipmi():
501
502    r"""
503    Run 'channel getciphers ipmi' command and return the result as a list of dictionaries.
504
505    Example robot code:
506    ${ipmi_channel_ciphers}=  Channel Getciphers IPMI
507    Rprint Vars  ipmi_channel_ciphers
508
509    Example output:
510    ipmi_channel_ciphers:
511      [0]:
512        [id]:                                         3
513        [iana]:                                       N/A
514        [auth_alg]:                                   hmac_sha1
515        [integrity_alg]:                              hmac_sha1_96
516        [confidentiality_alg]:                        aes_cbc_128
517      [1]:
518        [id]:                                         17
519        [iana]:                                       N/A
520        [auth_alg]:                                   hmac_sha256
521        [integrity_alg]:                              sha256_128
522        [confidentiality_alg]:                        aes_cbc_128
523    """
524
525    cmd_buf = "channel getciphers ipmi | sed -re 's/ Alg/_Alg/g'"
526    stdout, stderr, rc = execute_ipmi_cmd(cmd_buf, "external", print_output=0)
527    return vf.outbuf_to_report(stdout)
528