├── .coveragerc ├── .github └── workflows │ ├── check-dco.yml │ ├── lint.yml │ ├── publish.yml │ └── test.yml ├── .gitignore ├── AUTHORS ├── COPYING ├── README.rst ├── bin └── supported_cmds.py ├── docs ├── commands.rst └── source │ ├── bmcWatchdog_cmd.rst │ ├── chassis_cmd.rst │ ├── conf.py │ ├── index.rst │ ├── introduction.rst │ ├── ipmDevGlobal_cmd.rst │ ├── ipmiMsgSupport_cmd.rst │ └── quick_start.rst ├── examples ├── dcmi.py ├── interface_aardvark.py └── interface_rmcp.py ├── pyipmi ├── .gitignore ├── __init__.py ├── bmc.py ├── chassis.py ├── constants.py ├── dcmi.py ├── emulation.py ├── errors.py ├── event.py ├── fields.py ├── fru.py ├── helper.py ├── hpm.py ├── interfaces │ ├── __init__.py │ ├── aardvark.py │ ├── ipmb.py │ ├── ipmbdev.py │ ├── ipmitool.py │ ├── mock.py │ └── rmcp.py ├── ipmitool.py ├── lan.py ├── logger.py ├── messaging.py ├── msgs │ ├── __init__.py │ ├── bmc.py │ ├── chassis.py │ ├── constants.py │ ├── dcmi.py │ ├── device_messaging.py │ ├── event.py │ ├── fru.py │ ├── hpm.py │ ├── lan.py │ ├── message.py │ ├── picmg.py │ ├── registry.py │ ├── sdr.py │ ├── sel.py │ ├── sensor.py │ ├── session.py │ └── vita.py ├── picmg.py ├── sdr.py ├── sel.py ├── sensor.py ├── session.py ├── state.py └── utils.py ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── fru_bin ├── kontron_am4010.bin ├── kontron_am4904.bin └── vadatech_utc017.bin ├── hpm_bin └── firmware.hpm ├── interfaces ├── __init__.py ├── test_aardvark.py ├── test_ipmb.py ├── test_ipmitool.py └── test_rmcp.py ├── msgs ├── __init__.py ├── test_bmc.py ├── test_chassis.py ├── test_device_messaging.py ├── test_event.py ├── test_fru.py ├── test_hpm.py ├── test_message.py ├── test_picmg.py ├── test_registry.py ├── test_sdr.py ├── test_sel.py ├── test_sensor.py └── test_vita.py ├── test_bmc.py ├── test_chassis.py ├── test_device_messaging.py ├── test_errors.py ├── test_event.py ├── test_fields.py ├── test_fru.py ├── test_helper.py ├── test_hpm.py ├── test_ipmi.py ├── test_ipmitool.py ├── test_lan.py ├── test_picmg.py ├── test_sdr.py ├── test_sel.py ├── test_sensor.py └── test_utils.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | omit = 3 | */python?.?/* 4 | */site-packages/nose/* 5 | */tests/* 6 | -------------------------------------------------------------------------------- /.github/workflows/check-dco.yml: -------------------------------------------------------------------------------- 1 | name: Check DCO 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | check-dco: 7 | name: Check Developer's Certificate of Origin 8 | runs-on: ubuntu-22.04 9 | 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-python@v4 13 | with: 14 | python-version: ${{ matrix.python-version }} 15 | 16 | - name: Check DCO 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | run: | 20 | pip3 install -U dco-check 21 | dco-check 22 | 23 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint with flake8 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | lint: 7 | name: Lint with flake8 8 | runs-on: ubuntu-22.04 9 | 10 | steps: 11 | - uses: actions/checkout@v3 12 | 13 | - uses: actions/setup-python@v4 14 | with: 15 | python-version: '3.10' 16 | 17 | - name: Install dependencies 18 | run: pip install flake8 19 | 20 | - name: Lint with flake8 21 | run: | 22 | flake8 pyipmi/ tests/ --count --show-source --statistics 23 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python Package on PYPI 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | deploy: 10 | name: Publish to Pypi 11 | runs-on: ubuntu-22.04 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Set up Python 3.10 16 | uses: actions/setup-python@v4 17 | with: 18 | python-version: '3.10' 19 | - name: Install pypa/build 20 | run: >- 21 | python -m 22 | pip install 23 | build 24 | --user 25 | - name: Build a binary wheel and a source tarball 26 | run: >- 27 | python -m 28 | build 29 | --sdist 30 | --wheel 31 | --outdir dist/ 32 | . 33 | - name: Publish distribution to PyPI 34 | if: startsWith(github.ref, 'refs/tags') 35 | uses: pypa/gh-action-pypi-publish@master 36 | with: 37 | password: ${{ secrets.PYPI_API_TOKEN }} 38 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | tests: 7 | name: Python ${{ matrix.python-version }} 8 | runs-on: ubuntu-22.04 9 | 10 | strategy: 11 | matrix: 12 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "pypy3.8", "pypy3.9", "pypy3.10"] 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: actions/setup-python@v4 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | 21 | - name: Install dependencies 22 | run: pip install python-coveralls coverage pytest pytest-cov flake8 23 | 24 | # - name: Setup and run tests 25 | # run: python setup.py test 26 | 27 | - name: Test with pytest 28 | run: | 29 | pytest --cov=pyipmi --cov-report=lcov 30 | 31 | - name: Coveralls Parallel 32 | uses: coverallsapp/github-action@master 33 | with: 34 | github-token: ${{ secrets.github_token }} 35 | flag-name: run-${{ matrix.test_number }} 36 | parallel: true 37 | path-to-lcov: coverage.lcov 38 | 39 | finish: 40 | needs: tests 41 | runs-on: ubuntu-22.04 42 | steps: 43 | - name: Coveralls Finished 44 | uses: coverallsapp/github-action@master 45 | with: 46 | github-token: ${{ secrets.github_token }} 47 | parallel-finished: true 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyc 3 | *.swp 4 | build/ 5 | dist/ 6 | *.egg-info/ 7 | .eggs/ 8 | .venv/ -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Michael Walle 2 | Heiko Thiery 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Pure Python IPMI Library 2 | ======================== 3 | 4 | |BuildStatus| |PyPiVersion| |Documentation| |PyPiPythonVersions| |Coveralls| |CodeClimate| |Codacy| 5 | 6 | Features 7 | -------- 8 | * native RMCP interface 9 | * legacy RMCP interface (using ipmitool as backend) 10 | * RMCP+ interface (using ipmitool as backend) 11 | * system (KCS) interface (using ipmitool as backend) 12 | * IPMB interface using the `Total Phase`_ Aardvark 13 | * IPMB interface using ipmb-dev driver on Linux 14 | 15 | Tested Devices 16 | -------------- 17 | * Kontron mTCA Carrier Manager 18 | * Kontron CompactPCI boards 19 | * Pigeon Point Shelf Manager 20 | * HPE iLO3/iLO4 21 | * N.A.T. NAT-MCH 22 | * DESY MMC STAMP & related AMCs (DAMC-FMC2ZUP, DAMC-FMC1Z7IO) 23 | 24 | Requirements 25 | ------------ 26 | 27 | For IPMB interface a `Total Phase`_ Aardvark is needed. 28 | Another option is to use ipmb-dev driver on Linux with an I2C bus, driver of which supports slave mode: 29 | https://www.kernel.org/doc/html/latest/driver-api/ipmb.html 30 | 31 | For legacy RMCP, RMCP+ and system interface (KCS) the installtion of ipmitool 32 | is required. 33 | 34 | Installation 35 | ------------ 36 | 37 | Using ``pip`` 38 | ''''''''''''' 39 | 40 | The recommended installation method is using 41 | `pip `__:: 42 | 43 | pip install python-ipmi 44 | 45 | Manual installation 46 | ''''''''''''''''''' 47 | 48 | Download the source distribution package for the library. Extract the the package to 49 | a temporary location and install:: 50 | 51 | python setup.py install 52 | 53 | Documentation 54 | ------------- 55 | 56 | You can find the most up to date documentation at: 57 | http://python-ipmi.rtfd.org 58 | 59 | Example 60 | ------- 61 | 62 | Below is an example that shows how to setup the interface and the connection 63 | using the `ipmitool`_ as backend with both network and serial interfaces. 64 | 65 | Example with lan interface: 66 | 67 | .. code:: python 68 | 69 | import pyipmi 70 | import pyipmi.interfaces 71 | 72 | # Supported interface_types for ipmitool are: 'lan' , 'lanplus', and 'serial-terminal' 73 | interface = pyipmi.interfaces.create_interface('ipmitool', interface_type='lan') 74 | 75 | connection = pyipmi.create_connection(interface) 76 | 77 | connection.target = pyipmi.Target(0x82) 78 | connection.target.set_routing([(0x81,0x20,0),(0x20,0x82,7)]) 79 | 80 | connection.session.set_session_type_rmcp('10.0.0.1', port=623) 81 | connection.session.set_auth_type_user('admin', 'admin') 82 | connection.session.set_priv_level("ADMINISTRATOR") 83 | connection.session.establish() 84 | 85 | connection.get_device_id() 86 | 87 | ipmitool command: 88 | 89 | .. code:: shell 90 | 91 | ipmitool -I lan -H 10.0.0.1 -p 623 -L "ADMINISTRATOR" -U "admin" -P "admin" -t 0x82 -b 0 -l 0 raw 0x06 0x01 92 | 93 | 94 | Example with serial interface: 95 | 96 | .. code:: python 97 | 98 | import pyipmi 99 | import pyipmi.interfaces 100 | 101 | interface = pyipmi.interfaces.create_interface('ipmitool', interface_type='serial-terminal') 102 | 103 | connection = pyipmi.create_connection(interface) 104 | 105 | connection.target = pyipmi.Target(0xb2) 106 | 107 | # set_session_type_serial(port, baudrate) 108 | connection.session.set_session_type_serial('/dev/tty2', 115200) 109 | connection.session.establish() 110 | 111 | connection.get_device_id() 112 | 113 | ipmitool command: 114 | 115 | .. code:: shell 116 | 117 | ipmitool -I serial-terminal -D /dev/tty2:115200 -t 0xb2 -l 0 raw 0x06 0x01 118 | 119 | Compatibility 120 | ------------- 121 | 122 | Python > 3.6 is currently supported. Python 2.x is deprecated. 123 | 124 | Contributing 125 | ------------ 126 | 127 | Contributions are always welcome. You may send patches directly (eg. ``git 128 | send-email``), do a github pull request or just file an issue. 129 | 130 | * respect the coding style (eg. PEP8), 131 | * provide well-formed commit message (see `this blog post 132 | `_.) 133 | * add a Signed-off-by line (eg. ``git commit -s``) 134 | 135 | License 136 | ------- 137 | 138 | This library is free software; you can redistribute it and/or modify it 139 | under the terms of the GNU Lesser General Public License as published by 140 | the Free Software Foundation; either version 2.1 of the License, or (at 141 | your option) any later version. 142 | 143 | This library is distributed in the hope that it will be useful, but WITHOUT 144 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 145 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 146 | License for more details. 147 | 148 | You should have received a copy of the GNU Lesser General Public License 149 | along with this library; if not, write to the Free Software Foundation, 150 | Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 151 | 152 | .. _Total Phase: http://www.totalphase.com 153 | .. _ipmitool: http://sourceforge.net/projects/ipmitool/ 154 | .. |BuildStatus| image:: https://github.com/kontron/python-ipmi/actions/workflows/test.yml/badge.svg 155 | :target: https://github.com/kontron/python-ipmi/actions/workflows/test.yml 156 | .. |PyPiVersion| image:: https://badge.fury.io/py/python-ipmi.svg 157 | :target: http://badge.fury.io/py/python-ipmi 158 | .. |Documentation| image:: https://readthedocs.org/projects/python-ipmi/badge/?version=latest 159 | :target: https://python-ipmi.readthedocs.io/en/latest/?badge=latest 160 | :alt: Documentation Status 161 | .. |PyPiPythonVersions| image:: https://img.shields.io/pypi/pyversions/python-ipmi.svg 162 | :alt: Python versions 163 | :target: http://badge.fury.io/py/python-ipmi 164 | .. |CodeClimate| image:: https://codeclimate.com/github/kontron/python-ipmi/badges/gpa.svg 165 | :target: http://codeclimate.com/github/kontron/python-ipmi 166 | .. |Coveralls| image:: https://coveralls.io/repos/github/kontron/python-ipmi/badge.svg?branch=master 167 | :target: https://coveralls.io/github/kontron/python-ipmi?branch=master 168 | .. |Codacy| image:: https://app.codacy.com/project/badge/Grade/068eca4b1e784425aa46ae0b06aeaf37 169 | :alt: Codacy Badge 170 | :target: https://www.codacy.com/gh/kontron/python-ipmi/dashboard?utm_source=github.com&utm_medium=referral&utm_content=kontron/python-ipmi&utm_campaign=Badge_Grade 171 | -------------------------------------------------------------------------------- /bin/supported_cmds.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from collections import OrderedDict, namedtuple 4 | 5 | from pyipmi.msgs.registry import DEFAULT_REGISTRY 6 | 7 | 8 | def make_table(grid): 9 | col_length = map(list, zip(*[[len(item) for item in row] for row in grid])) 10 | max_cols = [max(out) for out in col_length] 11 | rst = table_div(max_cols, 1) 12 | 13 | for i, row in enumerate(grid): 14 | header_flag = False 15 | if i == 0 or i == len(grid)-1: 16 | header_flag = True 17 | rst += normalize_row(row, max_cols) 18 | rst += table_div(max_cols, header_flag) 19 | return rst 20 | 21 | 22 | def table_div(max_cols, header_flag=1): 23 | out = "" 24 | if header_flag == 1: 25 | style = "=" 26 | else: 27 | style = "-" 28 | 29 | for max_col in max_cols: 30 | out += max_col * style + " " 31 | 32 | out += "\n" 33 | return out 34 | 35 | 36 | def normalize_row(row, max_cols): 37 | r = "" 38 | for i, max_col in enumerate(max_cols): 39 | r += row[i] + (max_col - len(row[i]) + 1) * " " 40 | 41 | return r + "\n" 42 | 43 | 44 | def get_command_list(): 45 | data = list() 46 | Command = namedtuple('Command', ['netfn', 'cmdid', 'grpext', 'name']) 47 | 48 | od = OrderedDict(sorted(DEFAULT_REGISTRY.registry.items())) 49 | 50 | for key, val in od.items(): 51 | if isinstance(key, tuple): 52 | 53 | # skip response messages 54 | if key[0] & 1: 55 | continue 56 | 57 | data.append(Command(str(hex(key[0])), str(hex(key[1])), 58 | str(key[2]), val.__name__[:-3])) 59 | 60 | return data 61 | 62 | 63 | def main(): 64 | data = get_command_list() 65 | data.insert(0, ('Netfn', 'CMD', 'Group Extension', 'Name')) 66 | 67 | if len(sys.argv) > 1 and sys.argv[1].lower() == 'rst': 68 | rst = make_table(data) 69 | print(rst) 70 | else: 71 | print(data) 72 | 73 | 74 | if __name__ == '__main__': 75 | main() 76 | -------------------------------------------------------------------------------- /docs/source/bmcWatchdog_cmd.rst: -------------------------------------------------------------------------------- 1 | BMC Watchdog Timer Commands 2 | =========================== 3 | 4 | The :abbr:`BMC (Board Management Controller)` implements a standardized **'Watchdog Timer'** that can be used for a number of system timeout functions by system management software or by the :abbr:`BIOS (Basic Input Output System)`. Setting a timeout value of '0' allows the selected timeout action to occur immediately. This provides a standardized means for devices on the :abbr:`IPMB (Intelligent Platform Management Bus)` to performs emergency recovery actions. The `IPMI standard`_ defines the following BMC Watchdog Timer commands: 5 | 6 | +-------------------------------+-----+---------+-----+ 7 | | Command | O/M | Support | API | 8 | +===============================+=====+=========+=====+ 9 | | Reset Watchdog Timer | M | Yes | Yes | 10 | +-------------------------------+-----+---------+-----+ 11 | | Set Watchdog Timer | M | Yes | Yes | 12 | +-------------------------------+-----+---------+-----+ 13 | | Get Watchdog Timer | M | Yes | Yes | 14 | +-------------------------------+-----+---------+-----+ 15 | 16 | .. note:: 17 | 18 | - O/M - Optional/Mandatory command as stated by the IPMI standard 19 | - Support - Supported command by **send_message_with_name** method 20 | - API - High level API support implemented in this library 21 | 22 | Reset Watchdog Timer Command 23 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 24 | 25 | This command is used for starting and restarting the **Watchdog Timer** from the initial countdown value that was specified with ``set_watchdog_timer`` method (see next command). 26 | 27 | +------------------------------+ 28 | | **reset_watchdog_timer()** | 29 | +------------------------------+ 30 | 31 | For example: 32 | 33 | .. code:: python 34 | 35 | ipmi.reset_watchdog_timer() 36 | 37 | Set Watchdog Timer Command 38 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 39 | 40 | This command is used to initialize and configure the **Watchdog Timer**. This command is also used for stopping the timer. 41 | 42 | +----------------------------------------------+ 43 | | **set_watchdog_timer(watchdog_timer)** | 44 | +----------------------------------------------+ 45 | 46 | For example: 47 | 48 | .. code:: python 49 | 50 | ipmi.set_watchdog_timer(watchdog_timer) 51 | 52 | where the ``watchdog_timer`` has the attributes shown below for the next command. 53 | 54 | Get Watchdog Timer Command 55 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 56 | 57 | This command retrieves the current settings and present countdown of the watchdog timer. 58 | 59 | +------------------------------+ 60 | | **get_watchdog_timer()** | 61 | +------------------------------+ 62 | 63 | where the returned object has the following attributes shown in the order as they appear in the table of the `IPMI standard`_: 64 | 65 | * ``dont_log`` 66 | * ``is_running`` (``dont_stop``) 67 | * ``timer_use`` 68 | * ``pre_timeout_interrupt`` 69 | * ``timeout_action`` 70 | * ``pre_timeout_interval`` 71 | * ``timer_use_expiration_flags`` 72 | * ``initial_countdown`` 73 | * ``present_countdown`` 74 | 75 | The ``dont_stop`` attribute is not changed by the ``get_watchdog_timer`` method and used only by the ``set_watchdog_timer`` method. 76 | 77 | For example: 78 | 79 | .. code:: python 80 | 81 | watchdog_timer=ipmi.get_watchdog_timer() 82 | 83 | print("--- Printing Watchdog Timer ---") 84 | timer_use_const=['BIOS FRB2','BIOS/POST','OS Load','SMS/OS','OEM'] 85 | pretime_intr_const=['None','SMI','NMI','Msg intr'] 86 | timeout_act_const=['No action','Hard Reset','Power Down','Power Cycle'] 87 | print(""" 88 | Don't log: {0[dont_log]:} 89 | Timer is running: {0[is_running]:} 90 | Pre-timout interval: {0[pre_timeout_interval]:d} 91 | Initial countdown value: {0[initial_countdown]:d} 92 | Present countdown value: {0[present_countdown]:d} 93 | """[1:-1].format(wd_timer.__dict__),end='') 94 | print(" Timer use: ", 95 | timer_use_const[watchdog_timer.__dict__['timer_use']-1]) 96 | print(" Timer use expiration flag: ", 97 | timer_use_const[watchdog_timer.__dict__['timer_use_expiration_flags']-1]) 98 | print(" Pre-timeout interrupt: ", 99 | pretime_intr_const[watchdog_timer.__dict__['pre_timeout_interval']]) 100 | print(" Time out action: ", 101 | timeout_act_const[watchdog_timer.__dict__['timeout_action']]) 102 | 103 | 104 | .. _IPMI standard: https://www.intel.com/content/dam/www/public/us/en/documents/product-briefs/ipmi-second-gen-interface-spec-v2-rev1-1.pdf 105 | -------------------------------------------------------------------------------- /docs/source/chassis_cmd.rst: -------------------------------------------------------------------------------- 1 | Chassis Commands 2 | ================ 3 | 4 | These commands are primarily to provide standardized chassis status and control functions for Remote Management Cards and Remote Consoles that access the :abbr:`BMC (Board Management Controller)`. The `IPMI standard`_ defines the following Chassis commands: 5 | 6 | +-------------------------------+-----+---------+-----+ 7 | | Command | O/M | Support | API | 8 | +===============================+=====+=========+=====+ 9 | | Get Chassis Capabilities | M | Yes | No | 10 | +-------------------------------+-----+---------+-----+ 11 | | Get Chassis Status | M | Yes | Yes | 12 | +-------------------------------+-----+---------+-----+ 13 | | Chassis Control | M | Yes | Yes | 14 | +-------------------------------+-----+---------+-----+ 15 | | Chassis Reset | O | No | No | 16 | +-------------------------------+-----+---------+-----+ 17 | | Chassis Identify | O | No | No | 18 | +-------------------------------+-----+---------+-----+ 19 | | Set Front Panel Enables | O | No | No | 20 | +-------------------------------+-----+---------+-----+ 21 | | Set Chassis Capabilities | O | No | No | 22 | +-------------------------------+-----+---------+-----+ 23 | | Set Power Restore Policy | O | No | No | 24 | +-------------------------------+-----+---------+-----+ 25 | | Set Power Cycle Interval | O | No | No | 26 | +-------------------------------+-----+---------+-----+ 27 | | Get System Restart Cause | O | No | No | 28 | +-------------------------------+-----+---------+-----+ 29 | | Set System Boot Options | O | No | No | 30 | +-------------------------------+-----+---------+-----+ 31 | | Get System Boot Options | O | No | No | 32 | +-------------------------------+-----+---------+-----+ 33 | | Get POH Counter | O | Yes | No | 34 | +-------------------------------+-----+---------+-----+ 35 | 36 | .. note:: 37 | 38 | - O/M - Optional/Mandatory command as stated by the IPMI standard 39 | - Support - Supported command by **send_message_with_name** method 40 | - API - High level API support implemented in this library 41 | 42 | Get Chassis Capabilities Command 43 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 44 | 45 | This command returns information about which main chassis management functions are present on the :abbr:`IPMB (Intelligent Platform Management Bus)` and what addresses are used to access those functions. This command is used to find the devices that provide functions such as :abbr:`SEL (System Event Log)`, :abbr:`SDR (Snesor Data Record)`, and :abbr:`ICMB (Intelligent Chassis Management Bus)` Bridging so that theyt can be accessed via commands delivered via a physical or logical :abbr:`IPMB (Intelligent Platform Management Bus)`. 46 | 47 | +-------------------------------------+ 48 | | **get_chassis_capabilities()** | 49 | +-------------------------------------+ 50 | 51 | **NOT IMPLEMENTED YET!!!** 52 | 53 | Get Chassis Status Command 54 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 55 | 56 | This command returns information regarding the high-level status of the system chassis and main power subsystem. 57 | 58 | +--------------------------------------+ 59 | | **get_chassis_status()** | 60 | +--------------------------------------+ 61 | 62 | where the returned object has the following attributes shown in the order as they appear in the table of the `IPMI standard`_: 63 | 64 | * ``restore_policy`` 65 | * ``control_fault`` 66 | * ``fault`` 67 | * ``interlock`` 68 | * ``overload`` 69 | * ``power_on`` 70 | * ``last_event`` 71 | * ``chassis_state`` 72 | 73 | For example: 74 | 75 | .. code:: python 76 | 77 | chassis_status=ipmi.get_chassis_status() 78 | 79 | 80 | Chassis Control Command 81 | ~~~~~~~~~~~~~~~~~~~~~~~ 82 | 83 | This command provides a mechanism for providing power up, power down, and reset control. 84 | 85 | +-----------------------------------------+ 86 | | **chassis_control(option)** | 87 | +-----------------------------------------+ 88 | 89 | where the ``option`` argument can take the following integer values as defined in the standard: 90 | 91 | - CONTROL_POWER_DOWN = 0 92 | - CONTROL_POWER_UP = 1 93 | - CONTROL_POWER_CYCLE = 2 94 | - CONTROL_HARD_RESET = 3 95 | - CONTROL_DIAGNOSTIC_INTERRUPT = 4 96 | - CONTROL_SOFT_SHUTDOWN = 5 97 | 98 | 99 | For example: 100 | 101 | .. code:: python 102 | 103 | ipmi.chassis_control(option) 104 | 105 | There are methods defined for each of the above options: 106 | 107 | .. code:: python 108 | 109 | ipmi.chassis_control_power_down() 110 | ipmi.chassis_control_power_up() 111 | ipmi.chassis_control_power_cycle() 112 | ipmi.chassis_control_hard_reset() 113 | ipmi.chassis_control_diagnostic_interrupt() 114 | ipmi.chassis_control_soft_shutdown() 115 | 116 | 117 | .. _IPMI standard: https://www.intel.com/content/dam/www/public/us/en/documents/product-briefs/ipmi-second-gen-interface-spec-v2-rev1-1.pdf 118 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # python-ipmi documentation build configuration file, created by 5 | # sphinx-quickstart on Fri Mar 1 16:02:43 2019. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | # import os 21 | # import sys 22 | # sys.path.insert(0, os.path.abspath('.')) 23 | 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = ['sphinx.ext.autodoc', 35 | 'sphinx.ext.doctest', 36 | 'sphinx.ext.intersphinx', 37 | 'sphinx.ext.todo', 38 | 'sphinx.ext.coverage', 39 | 'sphinx.ext.ifconfig', 40 | 'sphinx.ext.viewcode', 41 | 'sphinx.ext.githubpages', 42 | 'sphinx.ext.graphviz'] 43 | 44 | # Add any paths that contain templates here, relative to this directory. 45 | templates_path = ['ytemplates'] 46 | 47 | # The suffix(es) of source filenames. 48 | # You can specify multiple suffix as a list of string: 49 | # 50 | # source_suffix = ['.rst', '.md'] 51 | source_suffix = '.rst' 52 | 53 | # The master toctree document. 54 | master_doc = 'index' 55 | 56 | # General information about the project. 57 | project = 'python-ipmi' 58 | copyright = '2019, FerencFarkasPhD' 59 | author = 'FerencFarkasPhD' 60 | 61 | # The version info for the project you're documenting, acts as replacement for 62 | # |version| and |release|, also used in various other places throughout the 63 | # built documents. 64 | # 65 | # The short X.Y version. 66 | version = '0.1' 67 | # The full version, including alpha/beta/rc tags. 68 | release = '0.1' 69 | 70 | # The language for content autogenerated by Sphinx. Refer to documentation 71 | # for a list of supported languages. 72 | # 73 | # This is also used if you do content translation via gettext catalogs. 74 | # Usually you set "language" from the command line for these cases. 75 | language = None 76 | 77 | # List of patterns, relative to source directory, that match files and 78 | # directories to ignore when looking for source files. 79 | # This patterns also effect to html_static_path and html_extra_path 80 | exclude_patterns = [] 81 | 82 | # The name of the Pygments (syntax highlighting) style to use. 83 | pygments_style = 'sphinx' 84 | 85 | # If true, `todo` and `todoList` produce output, else they produce nothing. 86 | todo_include_todos = True 87 | 88 | 89 | # -- Options for HTML output ---------------------------------------------- 90 | 91 | # The theme to use for HTML and HTML Help pages. See the documentation for 92 | # a list of builtin themes. 93 | # 94 | html_theme = 'alabaster' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | # 100 | # html_theme_options = {} 101 | 102 | # Add any paths that contain custom static files (such as style sheets) here, 103 | # relative to this directory. They are copied after the builtin static files, 104 | # so a file named "default.css" will overwrite the builtin "default.css". 105 | html_static_path = ['ystatic'] 106 | 107 | 108 | # -- Options for HTMLHelp output ------------------------------------------ 109 | 110 | # Output file base name for HTML help builder. 111 | htmlhelp_basename = 'python-ipmidoc' 112 | 113 | 114 | # -- Options for LaTeX output --------------------------------------------- 115 | 116 | latex_elements = { 117 | # The paper size ('letterpaper' or 'a4paper'). 118 | # 119 | # 'papersize': 'letterpaper', 120 | 121 | # The font size ('10pt', '11pt' or '12pt'). 122 | # 123 | # 'pointsize': '10pt', 124 | 125 | # Additional stuff for the LaTeX preamble. 126 | # 127 | # 'preamble': '', 128 | 129 | # Latex figure (float) alignment 130 | # 131 | # 'figure_align': 'htbp', 132 | } 133 | 134 | # Grouping the document tree into LaTeX files. List of tuples 135 | # (source start file, target name, title, 136 | # author, documentclass [howto, manual, or own class]). 137 | latex_documents = [ 138 | (master_doc, 'python-ipmi.tex', 'python-ipmi Documentation', 139 | 'FerencFarkasPhD', 'manual'), 140 | ] 141 | 142 | 143 | # -- Options for manual page output --------------------------------------- 144 | 145 | # One entry per manual page. List of tuples 146 | # (source start file, name, description, authors, manual section). 147 | man_pages = [ 148 | (master_doc, 'python-ipmi', 'python-ipmi Documentation', 149 | [author], 1) 150 | ] 151 | 152 | 153 | # -- Options for Texinfo output ------------------------------------------- 154 | 155 | # Grouping the document tree into Texinfo files. List of tuples 156 | # (source start file, target name, title, author, 157 | # dir menu entry, description, category) 158 | texinfo_documents = [ 159 | (master_doc, 'python-ipmi', 'python-ipmi Documentation', 160 | author, 'python-ipmi', 'One line description of project.', 161 | 'Miscellaneous'), 162 | ] 163 | 164 | 165 | # Example configuration for intersphinx: refer to the Python standard library. 166 | intersphinx_mapping = {'https://docs.python.org/': None} 167 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. python-ipmi documentation master file, created by 2 | sphinx-quickstart on Fri Mar 1 16:02:43 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to python-ipmi's documentation! 7 | ======================================= 8 | 9 | This documentation describes the usage of the python-ipmi library. 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | :caption: Contents: 14 | 15 | introduction 16 | quick_start 17 | ipmDevGlobal_cmd 18 | ipmiMsgSupport_cmd 19 | bmcWatchdog_cmd 20 | chassis_cmd 21 | 22 | 23 | Indices and tables 24 | ================== 25 | 26 | * :ref:`genindex` 27 | * :ref:`modindex` 28 | * :ref:`search` 29 | 30 | -------------------------------------------------------------------------------- /docs/source/introduction.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | The :abbr:`IPMI (Intelligent Platform Management Interface)` is a set of computer interface specifications for an autonomous computer subsystem that provides management and monitoring capabilities independently of the host system's :abbr:`CPU (Central Processor Unit)`, firmware (:abbr:`BIOS (Basic Input/Output System)` or :abbr:`UEFI (Unified Extensible Firmware Interface)`) and operating system. The python-ipmi library provides :abbr:`API (Application Programming Interface)` for using IPMI protocol within the python environment. This library supports :abbr:`IPMI (Intelligent Platform Management Interface)` version 2.0 as described in the `IPMI standard`_. 5 | 6 | There are two ways to communicate with a server using :abbr:`IPMI (Intelligent Platform Management Interface)` interface: 7 | 8 | 1. :abbr:`IPMI (Intelligent Platform Management Interface)` over :abbr:`LAN (Local Area Network)` using :abbr:`RMCP (Remote Management Control Protocol)` packet datagrams 9 | 2. :abbr:`IPMB (Intelligent Platform Management Bus)` is an |I2C| -based bus 10 | 11 | Features 12 | -------- 13 | * native :abbr:`RMCP (Remote Management Control Protocol)` interface (using python libraries only) 14 | * legacy :abbr:`RMCP (Remote Management Control Protocol)` interface (requires `ipmitool`_ to be installed) 15 | * :abbr:`IPMB (Intelligent Platform Management Bus)` interface (using the `Total Phase`_ Aardvark) 16 | 17 | Tested Devices 18 | -------------- 19 | * Kontron mTCA Carrier Manager 20 | * Kontron CompactPCI boards 21 | * Pigeon Point Shelf Manager 22 | * HPE iLO3/iLO4 and T5224DN 2U24 23 | 24 | Requirements 25 | ------------ 26 | 27 | For :abbr:`IPMB (Intelligent Platform Management Bus)` interface a `Total Phase`_ Aardvark is needed. 28 | 29 | Installation 30 | ------------ 31 | 32 | Using ``pip`` 33 | ''''''''''''' 34 | 35 | The recommended installation method is using 36 | `pip `__:: 37 | 38 | pip install python-ipmi 39 | 40 | .. warning:: 41 | 42 | If you are using Anaconda, still the above installation procedure shall be used as **conda install python-ipmi** will not find the installation package. 43 | 44 | Manual installation 45 | ''''''''''''''''''' 46 | 47 | Download the source distribution package for the library. Extract the package to 48 | a temporary location and install:: 49 | 50 | python setup.py install 51 | 52 | 53 | Compatibility 54 | ------------- 55 | 56 | Python 2.7 is currently supported. 57 | Python 3.x support is in beta 58 | 59 | Contributing 60 | ------------ 61 | 62 | Contributions are always welcome. You may send patches directly (eg. ``git 63 | send-email``), do a github pull request or just file an issue. 64 | 65 | * respect the coding style (eg. PEP8), 66 | * provide well-formed commit message (see `this blog post 67 | `_.) 68 | * add a Signed-off-by line (eg. ``git commit -s``) 69 | 70 | License 71 | ------- 72 | 73 | This library is free software; you can redistribute it and/or modify it 74 | under the terms of the GNU Lesser General Public License as published by 75 | the Free Software Foundation; either version 2.1 of the License, or (at 76 | your option) any later version. 77 | 78 | This library is distributed in the hope that it will be useful, but WITHOUT 79 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 80 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 81 | License for more details. 82 | 83 | You should have received a copy of the GNU Lesser General Public License 84 | along with this library; if not, write to the Free Software Foundation, 85 | Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 86 | 87 | .. _Total Phase: http://www.totalphase.com 88 | .. _ipmitool: http://sourceforge.net/projects/ipmitool/ 89 | .. _IPMI standard: https://www.intel.com/content/dam/www/public/us/en/documents/product-briefs/ipmi-second-gen-interface-spec-v2-rev1-1.pdf 90 | .. |I2C| replace:: I\ :sup:`2`\ C -------------------------------------------------------------------------------- /docs/source/ipmDevGlobal_cmd.rst: -------------------------------------------------------------------------------- 1 | IPM Device "Global" Commands 2 | ============================ 3 | 4 | This section describes the high level :abbr:`API (Application Programming Interface)` for the commands that are common to all :abbr:`IPM (Intelligent Platform Management)` devices that follow the :abbr:`IPMI (Intelligent Platform Management Interface)` standard. The `IPMI standard`_ defines the following IPM Device "Global" commands: 5 | 6 | +-------------------------------+-----+---------+-----+ 7 | | Command | O/M | Support | API | 8 | +===============================+=====+=========+=====+ 9 | | Get Device ID | M | Yes | Yes | 10 | +-------------------------------+-----+---------+-----+ 11 | | Cold Reset | O | Yes | Yes | 12 | +-------------------------------+-----+---------+-----+ 13 | | Warm Reset | O | Yes | Yes | 14 | +-------------------------------+-----+---------+-----+ 15 | | Get Self Test Results | M | Yes | No | 16 | +-------------------------------+-----+---------+-----+ 17 | | Manufacturing Test On | O | Yes | No | 18 | +-------------------------------+-----+---------+-----+ 19 | | Get ACPI Power State | O | No | No | 20 | +-------------------------------+-----+---------+-----+ 21 | | Set ACPI Power State | O | No | No | 22 | +-------------------------------+-----+---------+-----+ 23 | | Get Device GUID | O | No | No | 24 | +-------------------------------+-----+---------+-----+ 25 | 26 | .. note:: 27 | 28 | - O/M - Optional/Mandatory command as stated by the IPMI standard 29 | - Support - Supported command by **send_message_with_name** method 30 | - API - High level API support implemented in this library 31 | 32 | Get Device ID Command 33 | ~~~~~~~~~~~~~~~~~~~~~ 34 | 35 | You can retrieve the Intelligent Devices's HW revision, FW/SW revision, and information regarding additional logical device functionality with 36 | 37 | +------------------------------+ 38 | | **get_device_id()** | 39 | +------------------------------+ 40 | 41 | where the returned object has the following attributes shown in the order as appear in the table of the `IPMI standard`_: 42 | 43 | * ``device_id`` 44 | * ``provides_sdrs`` 45 | * ``revision`` 46 | * ``available`` 47 | * ``fw_revision (fw_revision.major, fw_revision.minor)`` 48 | * ``ipmi_version (ipmi_version.major, ipmi_version.minor)`` 49 | * ``supported_functions`` 50 | * ``manufacturer_id`` 51 | * ``product_id`` 52 | * ``aux`` 53 | 54 | The returned object also has a method ``supports_function(func_name)`` where the argument can be one of the following (not case sensitive): 55 | 56 | * **'sensor'** 57 | * **'sdr_repository'** 58 | * **'sel'** 59 | * **'fru_inventory'** 60 | * **'ipmb_event_receiver'** 61 | * **'ipmb_event_generator'** 62 | * **'bridge'** 63 | * **'chassis'** 64 | 65 | The method returns **'True'** if the given function is supported by the **Target**, otherwise is **'False'**. 66 | 67 | For example: 68 | 69 | .. code:: python 70 | 71 | device_id = ipmi.get_device_id() 72 | 73 | print("--- Printing Device ID ---") 74 | functions = ( 75 | ('SENSOR', 'Sensor Device', 11), 76 | ('SDR_REPOSITORY', 'SDR Repository Device', 3), 77 | ('SEL', 'SEL Device', 14), 78 | ('FRU_INVENTORY', 'FRU Inventory Device', 4), 79 | ('IPMB_EVENT_RECEIVER', 'IPMB Event Receiver', 5), 80 | ('IPMB_EVENT_GENERATOR', 'IPMB Event Generator', 4), 81 | ('BRIDGE', 'Bridge', 18), 82 | ('CHASSIS', 'Chassis Device', 10)) 83 | ChkBox=['[ ]','[X]'] 84 | print(''' 85 | Device ID: %(device_id)s 86 | Provides Device SDRs: %(provides_sdrs)s 87 | Device Revision: %(revision)s 88 | Device Available: %(available)d 89 | Firmware Revision: %(fw_revision)s 90 | IPMI Version: %(ipmi_version)s 91 | Manufacturer ID: %(manufacturer_id)d (0x%(manufacturer_id)04x) 92 | Product ID: %(product_id)d (0x%(product_id)04x) 93 | Aux Firmware Rev Info: %(aux)s 94 | Additional Device Support: '''[1:-1] % device_id.__dict__) 95 | for n, s, l in functions: 96 | if device_id.supports_function(n): 97 | print(' %s%s%s' % (s,l*' ',ChkBox[1])) 98 | else: 99 | print(' %s%s%s' % (s,l*' ',ChkBox[0])) 100 | 101 | 102 | Cold Reset Command 103 | ~~~~~~~~~~~~~~~~~~ 104 | 105 | This command directs the **Target** to perform a 'Cold Reset' of itself. The device reinitalizes its event, communication, and sensor funtioncs. Self Test, if implemented, will be also run. 106 | 107 | +------------------------------+ 108 | | **cold_reset()** | 109 | +------------------------------+ 110 | 111 | For example: 112 | 113 | .. code:: python 114 | 115 | ipmi.cold_reset() 116 | 117 | Warm Reset Command 118 | ~~~~~~~~~~~~~~~~~~ 119 | 120 | This command directs the **Target** to perform a 'Warm Reset' of itself. Communication interfaces are reset, but current configurations of interrupt enables, thresholds, etc. will be left alone, and no Self Test initiated. 121 | 122 | +------------------------------+ 123 | | **warm_reset()** | 124 | +------------------------------+ 125 | 126 | For example: 127 | 128 | .. code:: python 129 | 130 | ipmi.warm_reset() 131 | 132 | .. _IPMI standard: https://www.intel.com/content/dam/www/public/us/en/documents/product-briefs/ipmi-second-gen-interface-spec-v2-rev1-1.pdf 133 | -------------------------------------------------------------------------------- /examples/dcmi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | 5 | import pyipmi 6 | import pyipmi.interfaces 7 | 8 | 9 | def main(): 10 | 11 | if len(sys.argv) < 4: 12 | print(' ') 13 | sys.exit(1) 14 | 15 | host = sys.argv[1] 16 | user = sys.argv[2] 17 | password = sys.argv[3] 18 | 19 | interface = pyipmi.interfaces.create_interface('ipmitool', 20 | interface_type='lanplus') 21 | ipmi = pyipmi.create_connection(interface) 22 | ipmi.session.set_session_type_rmcp(host, 623) 23 | ipmi.session.set_auth_type_user(user, password) 24 | ipmi.target = pyipmi.Target(ipmb_address=0x20) 25 | ipmi.open() 26 | 27 | for selector in range(1, 6): 28 | caps = ipmi.get_dcmi_capabilities(selector) 29 | print('Selector: {} '.format(selector)) 30 | print(' version: {} '.format(caps.specification_conformence)) 31 | print(' revision: {}'.format(caps.parameter_revision)) 32 | print(' data: {}'.format(caps.parameter_data)) 33 | 34 | rsp = ipmi.get_power_reading(1) 35 | 36 | print('Power Reading') 37 | print(' current: {}'.format(rsp.current_power)) 38 | print(' minimum: {}'.format(rsp.minimum_power)) 39 | print(' maximum: {}'.format(rsp.maximum_power)) 40 | print(' average: {}'.format(rsp.average_power)) 41 | print(' timestamp: {}'.format(rsp.timestamp)) 42 | print(' period: {}'.format(rsp.period)) 43 | print(' state: {}'.format(rsp.reading_state)) 44 | 45 | ipmi.close() 46 | 47 | 48 | if __name__ == '__main__': 49 | main() 50 | -------------------------------------------------------------------------------- /examples/interface_aardvark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import pyipmi 4 | import pyipmi.interfaces 5 | 6 | 7 | interface = pyipmi.interfaces.create_interface('aardvark', 8 | slave_address=0x20, 9 | serial_number='2237-523145') 10 | target = pyipmi.Target(ipmb_address=0xb4) 11 | 12 | # (1) The device connection can either be opened and closed 13 | ipmi = pyipmi.Ipmi(interface=interface, target=target) 14 | ipmi.open() 15 | device_id = ipmi.get_device_id() 16 | ipmi.close() 17 | 18 | # (2) or the 'with' statement can be used 19 | with pyipmi.Ipmi(interface=interface, target=target) as ipmi: 20 | device_id = ipmi.get_device_id() 21 | 22 | print(''' 23 | Device ID: %(device_id)s 24 | Device Revision: %(revision)s 25 | Firmware Revision: %(fw_revision)s 26 | IPMI Version: %(ipmi_version)s 27 | Manufacturer ID: %(manufacturer_id)d (0x%(manufacturer_id)04x) 28 | Product ID: %(product_id)d (0x%(product_id)04x) 29 | Device Available: %(available)d 30 | Provides SDRs: %(provides_sdrs)d 31 | Additional Device Support: 32 | '''[1:-1] % device_id.__dict__) 33 | 34 | functions = ( 35 | ('SENSOR', 'Sensor Device'), 36 | ('SDR_REPOSITORY', 'SDR Repository Device'), 37 | ('SEL', 'SEL Device'), 38 | ('FRU_INVENTORY', 'FRU Inventory Device'), 39 | ('IPMB_EVENT_RECEIVER', 'IPMB Event Receiver'), 40 | ('IPMB_EVENT_GENERATOR', 'IPMB Event Generator'), 41 | ('BRIDGE', 'Bridge'), 42 | ('CHASSIS', 'Chassis Device') 43 | ) 44 | for n, s in functions: 45 | if device_id.supports_function(n): 46 | print(' %s' % s) 47 | 48 | if device_id.aux is not None: 49 | print('Aux Firmware Rev Info: [%s]' % ( 50 | ' '.join('0x%02x' % d for d in device_id.aux))) 51 | -------------------------------------------------------------------------------- /examples/interface_rmcp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import pyipmi 4 | import pyipmi.interfaces 5 | 6 | 7 | intf = pyipmi.interfaces.create_interface('rmcp', 8 | slave_address=0x81, 9 | host_target_address=0x20, 10 | keep_alive_interval=0) 11 | sess = pyipmi.Session() 12 | sess.set_session_type_rmcp('10.0.114.116', 623) 13 | sess.set_auth_type_user('admin', 'admin') 14 | sess.set_priv_level("ADMINISTRATOR") 15 | target = pyipmi.Target(ipmb_address=0x20) 16 | 17 | # (1) The device connection can either be opened and closed 18 | ipmi = pyipmi.Ipmi(interface=intf, session=sess, target=target) 19 | ipmi.open() 20 | device_id = ipmi.get_device_id() 21 | ipmi.close() 22 | 23 | # (2) or the 'with' statement can be used 24 | with pyipmi.Ipmi(interface=intf, session=sess, target=target) as ipmi: 25 | device_id = ipmi.get_device_id() 26 | 27 | print(''' 28 | Device ID: %(device_id)s 29 | Device Revision: %(revision)s 30 | Firmware Revision: %(fw_revision)s 31 | IPMI Version: %(ipmi_version)s 32 | Manufacturer ID: %(manufacturer_id)d (0x%(manufacturer_id)04x) 33 | Product ID: %(product_id)d (0x%(product_id)04x) 34 | Device Available: %(available)d 35 | Provides SDRs: %(provides_sdrs)d 36 | Additional Device Support: 37 | '''[1:-1] % device_id.__dict__) 38 | 39 | functions = ( 40 | ('SENSOR', 'Sensor Device'), 41 | ('SDR_REPOSITORY', 'SDR Repository Device'), 42 | ('SEL', 'SEL Device'), 43 | ('FRU_INVENTORY', 'FRU Inventory Device'), 44 | ('IPMB_EVENT_RECEIVER', 'IPMB Event Receiver'), 45 | ('IPMB_EVENT_GENERATOR', 'IPMB Event Generator'), 46 | ('BRIDGE', 'Bridge'), 47 | ('CHASSIS', 'Chassis Device') 48 | ) 49 | for n, s in functions: 50 | if device_id.supports_function(n): 51 | print(' %s' % s) 52 | 53 | if device_id.aux is not None: 54 | print('Aux Firmware Rev Info: [%s]' % ( 55 | ' '.join('0x%02x' % d for d in device_id.aux))) 56 | -------------------------------------------------------------------------------- /pyipmi/.gitignore: -------------------------------------------------------------------------------- 1 | version.py 2 | -------------------------------------------------------------------------------- /pyipmi/bmc.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Kontron Europe GmbH 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 | 17 | from .msgs import create_request_by_name 18 | from .utils import check_completion_code 19 | from .state import State 20 | from .fields import VersionField 21 | 22 | 23 | class Bmc(object): 24 | def get_device_id(self): 25 | return DeviceId(self.send_message_with_name('GetDeviceId')) 26 | 27 | def get_device_guid(self): 28 | return DeviceGuid(self.send_message_with_name('GetDeviceGuid')) 29 | 30 | def cold_reset(self): 31 | self.send_message_with_name('ColdReset') 32 | 33 | def warm_reset(self): 34 | self.send_message_with_name('WarmReset') 35 | 36 | def i2c_write_read(self, bus_type, bus_id, channel, address, count, 37 | data=None): 38 | req = create_request_by_name('MasterWriteRead') 39 | req.bus_id.type = bus_type 40 | req.bus_id.id = bus_id 41 | req.bus_id.channel = channel 42 | req.bus_id.slave_address = address 43 | req.read_count = count 44 | if data: 45 | req.data = data 46 | rsp = self.send_message(req) 47 | check_completion_code(rsp.completion_code) 48 | return rsp.data 49 | 50 | def i2c_write(self, bus_type, bus_id, channel, address, data): 51 | self.i2c_write_read(bus_type, bus_id, channel, address, 0, data) 52 | 53 | def i2c_read(self, bus_type, bus_id, channel, address, count): 54 | return self.i2c_write_read(bus_type, bus_id, channel, 55 | address, count, None) 56 | 57 | def set_watchdog_timer(self, config): 58 | req = create_request_by_name('SetWatchdogTimer') 59 | req.timer_use.timer_use = config.timer_use 60 | req.timer_use.dont_stop = config.dont_stop and 1 or 0 61 | req.timer_use.dont_log = config.dont_log and 1 or 0 62 | 63 | req.timer_actions.pre_timeout_interrupt = config.pre_timeout_interrupt 64 | req.timer_actions.timeout_action = config.timeout_action 65 | 66 | req.pre_timeout_interval = config.pre_timeout_interval 67 | req.timer_use_expiration_flags = config.timer_use_expiration_flags 68 | req.initial_countdown = config.initial_countdown 69 | rsp = self.send_message(req) 70 | check_completion_code(rsp.completion_code) 71 | 72 | def get_watchdog_timer(self): 73 | return Watchdog(self.send_message_with_name('GetWatchdogTimer')) 74 | 75 | def reset_watchdog_timer(self): 76 | self.send_message_with_name('ResetWatchdogTimer') 77 | 78 | 79 | class Watchdog(State): 80 | TIMER_USE_OEM = 5 81 | TIMER_USE_SMS_OS = 4 82 | TIMER_USE_OS_LOAD = 3 83 | TIMER_USE_BIOS_POST = 2 84 | TIMER_USE_BIOS_FRB2 = 1 85 | 86 | TIMEOUT_ACTION_NO_ACTION = 0 87 | TIMEOUT_ACTION_HARD_RESET = 1 88 | TIMEOUT_ACTION_POWER_DOWN = 2 89 | TIMEOUT_ACTION_POWER_CYCLE = 3 90 | 91 | __properties__ = [ 92 | # (property, description) 93 | ('timer_use', ''), 94 | ('dont_stop', ''), 95 | ('is_running', ''), 96 | ('dont_log', ''), 97 | ('pre_timeout_interrupt', ''), 98 | ('timeout_action', ''), 99 | ('pre_timeout_interval', ''), 100 | ('timer_use_expiration_flags', ''), 101 | ('initial_countdown', ''), 102 | ('present_countdown', ''), 103 | ] 104 | 105 | def _from_response(self, rsp): 106 | self.timer_use = rsp.timer_use.timer_use 107 | self.is_running = bool(rsp.timer_use.is_running) 108 | self.dont_log = bool(rsp.timer_use.dont_log) 109 | self.pre_timeout_interrupt = rsp.timer_actions.pre_timeout_interrupt 110 | self.timeout_action = rsp.timer_actions.timeout_action 111 | self.pre_timeout_interval = rsp.pre_timeout_interval 112 | self.timer_use_expiration_flags = rsp.timer_use_expiration_flags 113 | self.initial_countdown = rsp.initial_countdown 114 | self.present_countdown = rsp.present_countdown 115 | 116 | 117 | class DeviceId(State): 118 | 119 | def __str__(self): 120 | string = 'Device ID: %d' % self.device_id 121 | string += ' revision: %d' % self.revision 122 | string += ' available: %d' % self.available 123 | string += ' fw version: %s' % (self.fw_revision) 124 | string += ' ipmi: %s' % self.ipmi_version 125 | string += ' manufacturer: %d' % self.manufacturer_id 126 | string += ' product: %d' % self.product_id 127 | string += ' functions: %s' % ','.join(self.supported_functions) 128 | return string 129 | 130 | def supports_function(self, name): 131 | """Return if a function is supported. 132 | 133 | `name` is one of 'SENSOR', 'SDR_REPOSITORY', 'SEL', 'FRU_INVENTORY', 134 | 'IPMB_EVENT_RECEIVER', 'IPMB_EVENT_GENERATOR', 'BRIDGE', 'CHASSIS'. 135 | """ 136 | return name.lower() in self.supported_functions 137 | 138 | def _from_response(self, rsp): 139 | self.device_id = rsp.device_id 140 | self.revision = rsp.device_revision.device_revision 141 | self.provides_sdrs = bool(rsp.device_revision.provides_device_sdrs) 142 | self.available = bool(rsp.firmware_revision.device_available) 143 | 144 | self.fw_revision = VersionField( 145 | (rsp.firmware_revision.major, rsp.firmware_revision.minor)) 146 | 147 | self.ipmi_version = VersionField( 148 | (rsp.ipmi_version & 0xf, (rsp.ipmi_version >> 4) & 0xf)) 149 | 150 | self.manufacturer_id = rsp.manufacturer_id 151 | self.product_id = rsp.product_id 152 | 153 | self.supported_functions = [] 154 | functions = ('sensor', 155 | 'sdr_repository', 156 | 'sel', 157 | 'fru_inventory', 158 | 'ipmb_event_receiver', 159 | 'ipmb_event_generator', 160 | 'bridge', 161 | 'chassis') 162 | 163 | for function in functions: 164 | if hasattr(rsp.additional_support, function): 165 | if getattr(rsp.additional_support, function): 166 | self.supported_functions.append(function) 167 | 168 | self.aux = None 169 | if rsp.auxiliary is not None: 170 | self.aux = list(rsp.auxiliary) 171 | 172 | 173 | class DeviceGuid(State): 174 | def __str__(self): 175 | return 'Device GUID: %s' % self.device_guid_string 176 | 177 | def _from_response(self, rsp): 178 | self.device_guid = rsp.device_guid 179 | self.device_guid_string = \ 180 | '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-' \ 181 | '%02x%02x%02x%02x%02x%02x' % tuple(reversed(self.device_guid)) 182 | -------------------------------------------------------------------------------- /pyipmi/constants.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Kontron Europe GmbH 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 | 17 | # Entity ID assignments 18 | ENTITY_ID_CPU = 0x03 19 | ENTITY_ID_BASEBOARD = 0x07 20 | ENTITY_ID_POWER_MODULE = 0x0a 21 | ENTITY_ID_COOLING_UNIT = 0x1e 22 | ENTITY_ID_AIR_INLET = 0x37 23 | ENTITY_ID_DCMI_AIR_INLET = 0x40 24 | ENTITY_ID_DCMI_CPU = 0x41 25 | ENTITY_ID_DCMI_BASEBOARD = 0x42 26 | ENTITY_ID_PICMG_FRONT_BOARD = 0xa0 27 | ENTITY_ID_PICMG_REAR_TRANSITION_MODULE = 0xc0 28 | ENTITY_ID_PICMG_ADVANCED_MC = 0xc1 29 | ENTITY_ID_PICMG_MICROTCA_CARRIER_HUB = 0xc2 30 | ENTITY_ID_PICMG_SHELF_MANAGEMENT_CONTROLLER = 0xf0 31 | ENTITY_ID_PICMG_FILTRATION_UNIT = 0xf1 32 | ENTITY_ID_PICMG_SHELF_FRU_INFORMATION = 0xf2 33 | ENTITY_ID_VITA_FRONT_BOARD = 0xa0 34 | ENTITY_ID_VITA_REAR_TRANSITION_MODULE = 0xc0 35 | ENTITY_ID_VITA_XMC = 0xc1 36 | -------------------------------------------------------------------------------- /pyipmi/dcmi.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Kontron Europe GmbH 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 | 17 | from .constants import (ENTITY_ID_DCMI_AIR_INLET, ENTITY_ID_DCMI_CPU, 18 | ENTITY_ID_DCMI_BASEBOARD) 19 | 20 | 21 | PARAM_SUPPORTED_DCMI_CAPABILITIES = 1 22 | PARAM_MANDATORY_PLATFORM_ATTRIBUTES = 2 23 | PARAM_OPTIONAL_PLATFORM_ATTRIBUTES = 3 24 | PARAM_MANAGEABILITY_ACCESS_ATTRIBUTES = 4 25 | PARAM_ENHANCED_SYSTEM_POWER_STATISTICS_ATTRIBUTES = 5 26 | 27 | 28 | class Dcmi(object): 29 | def get_dcmi_capabilities(self, selector): 30 | rsp = self.send_message_with_name('GetDcmiCapabilities', 31 | parameter_selector=selector) 32 | return rsp 33 | 34 | def get_power_reading(self, mode, attributes=0): 35 | rsp = self.send_message_with_name('GetPowerReading', 36 | mode=mode, attributes=attributes) 37 | return rsp 38 | 39 | def get_dcmi_sensor_record_ids(self): 40 | 41 | record_ids = list() 42 | 43 | DCMI_ENTITIES = (ENTITY_ID_DCMI_AIR_INLET, ENTITY_ID_DCMI_CPU, 44 | ENTITY_ID_DCMI_BASEBOARD) 45 | 46 | for entity_id in DCMI_ENTITIES: 47 | rsp = self.send_message_with_name('GetDcmiSensorInfo', 48 | sensor_type=1, 49 | entity_id=entity_id, 50 | entity_instance=0, 51 | entity_instance_start=0) 52 | # convert the returned raw data in a list of SDR record IDs 53 | ids = [msb << 8 | lsb for (lsb, msb) in 54 | zip(rsp.record_ids, rsp.record_ids[1:])[::2]] 55 | record_ids.extend(ids) 56 | 57 | return record_ids 58 | -------------------------------------------------------------------------------- /pyipmi/errors.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Kontron Europe GmbH 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 | 17 | from .msgs.constants import COMPLETION_CODE_DESCR, CC_ERR_CMD_SPECIFIC_DESC 18 | 19 | 20 | class DecodingError(Exception): 21 | """Error on message decoding.""" 22 | pass 23 | 24 | 25 | class EncodingError(Exception): 26 | """Error on message encoding.""" 27 | pass 28 | 29 | 30 | class IpmiTimeoutError(Exception): 31 | """Timeout occurred.""" 32 | pass 33 | 34 | 35 | class CompletionCodeError(Exception): 36 | """IPMI completion code not OK.""" 37 | 38 | def __init__(self, cc, cmdid=None, netfn=None, group_extension=None): 39 | self.cc = cc 40 | self.cc_desc = self.find_cc_desc(cc, cmdid, netfn, group_extension) 41 | 42 | def __str__(self): 43 | return "%s cc=0x%02x desc=%s" \ 44 | % (self.__class__.__name__, self.cc, self.cc_desc) 45 | 46 | @staticmethod 47 | def find_cc_desc(error_cc, cmdid=None, netfn=None, group_extension=None): 48 | # Search in global completion codes first 49 | for cc in COMPLETION_CODE_DESCR: 50 | if error_cc == cc[0]: 51 | return cc[1] 52 | # Then search in command specific completion codes 53 | if cmdid is not None: 54 | command_cc = CC_ERR_CMD_SPECIFIC_DESC.get((netfn, cmdid, group_extension), {}) 55 | descr = command_cc.get(error_cc, "Unknown error description") 56 | return descr 57 | # Completion code description not found 58 | return "Unknown error description" 59 | 60 | 61 | class NotSupportedError(Exception): 62 | """Not supported yet.""" 63 | pass 64 | 65 | 66 | class DescriptionError(Exception): 67 | """Message description incorrect.""" 68 | pass 69 | 70 | 71 | class RetryError(Exception): 72 | """Maximum number of retries exceeded.""" 73 | pass 74 | 75 | 76 | class DataNotFound(Exception): 77 | """Requested data not found.""" 78 | pass 79 | 80 | 81 | class HpmError(Exception): 82 | """HPM.1 error.""" 83 | pass 84 | 85 | 86 | class IpmiConnectionError(Exception): 87 | """Connection error.""" 88 | def __init__(self, msg=None): 89 | self.msg = msg 90 | 91 | def __str__(self): 92 | return "{}".format(self.msg) 93 | 94 | 95 | class IpmiLongPasswordError(Exception): 96 | """Password longer than 20 bytes.""" 97 | def __init__(self, msg=None): 98 | self.msg = msg 99 | 100 | def __str__(self): 101 | return "{}".format(self.msg) 102 | -------------------------------------------------------------------------------- /pyipmi/event.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Kontron Europe GmbH 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 | 17 | from .utils import check_completion_code 18 | from .msgs import create_request_by_name 19 | 20 | EVENT_ASSERTION = 0 21 | EVENT_DEASSERTION = 1 22 | 23 | 24 | class Event(object): 25 | def set_event_receiver(self, ipmb_address, lun): 26 | req = create_request_by_name('SetEventReceiver') 27 | req.event_receiver.ipmb_i2c_slave_address = ipmb_address 28 | req.event_receiver.lun = lun 29 | rsp = self.send_message(req) 30 | check_completion_code(rsp.completion_code) 31 | 32 | def get_event_receiver(self): 33 | req = create_request_by_name('GetEventReceiver') 34 | rsp = self.send_message(req) 35 | check_completion_code(rsp.completion_code) 36 | ipmb_address = rsp.event_receiver.ipmb_i2c_slave_address 37 | lun = rsp.event_receiver.lun 38 | return (ipmb_address, lun) 39 | -------------------------------------------------------------------------------- /pyipmi/fields.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | import array 6 | 7 | from .errors import DecodingError 8 | from .utils import py3_array_tobytes 9 | 10 | 11 | class VersionField(object): 12 | """This class represent the Version fields defines by IPMI. 13 | 14 | Introduced with HPM the version field can hold additional auxiliary bytes. 15 | """ 16 | 17 | VERSION_FIELD_LEN = 2 18 | VERSION_WITH_AUX_FIELD_LEN = 6 19 | 20 | def __init__(self, data=None): 21 | self.major = None 22 | self.minor = None 23 | if data: 24 | self._from_data(data) 25 | 26 | def _from_data(self, data): 27 | if isinstance(data, str): 28 | data = [ord(c) for c in data] 29 | 30 | data = array.array('B', data) 31 | self.version = self._decode_data(data[0:2]) 32 | if len(data) == self.VERSION_WITH_AUX_FIELD_LEN: 33 | self.auxiliary = data[2:6] 34 | 35 | def __str__(self): 36 | return self.version_to_string() 37 | 38 | def _decode_data(self, data): 39 | """`data` is array.array.""" 40 | self.major = data[0] 41 | 42 | if data[1] == 0xff: 43 | self.minor = data[1] 44 | elif data[1] <= 0x99: 45 | self.minor = int(py3_array_tobytes(data[1:2]).decode('bcd+')) 46 | else: 47 | raise DecodingError() 48 | 49 | def version_to_string(self): 50 | return ''.join("%s.%s" % (self.major, self.minor)) 51 | 52 | 53 | def _unpack6bitascii(data): 54 | """Unpack the 6bit ascii encoded string.""" 55 | string = '' 56 | for i in range(0, len(data), 3): 57 | d = data[i:i+3] 58 | string += chr(0x20 + (d[0] & 0x3f)) 59 | string += chr(0x20 + (((d[0] & 0xc0) >> 6) | ((d[1] & 0xf) << 2))) 60 | string += chr(0x20 + (((d[1] & 0xf0) >> 4) | ((d[2] & 0x3) << 4))) 61 | string += chr(0x20 + ((d[2] & 0xfc) >> 2)) 62 | return string 63 | 64 | 65 | class TypeLengthString(object): 66 | """ 67 | This is the TYPE/LENGTH BYTE FORMAT field representation according the 68 | Platform Management FRU Information Storage Definition v1.0. 69 | 70 | In addition the difference to the 'FRU Information Storage Definition' to 71 | the variant used in Type/Length for the Device ID String used in the SDR. 72 | """ 73 | 74 | TYPE_FRU_BINARY = 0 75 | TYPE_SDR_UNICODE = 0 76 | TYPE_BCD_PLUS = 1 77 | TYPE_6BIT_ASCII = 2 78 | TYPE_ASCII_OR_UTF16 = 3 79 | 80 | def __init__(self, data=None, offset=0, force_lang_eng=False, sdr=False): 81 | if data: 82 | self._from_data(data, offset, force_lang_eng) 83 | 84 | def __str__(self): 85 | if self.field_type is self.TYPE_FRU_BINARY: 86 | return ' '.join('%02x' % b for b in self.raw) 87 | else: 88 | return self.string.replace('\x00', '') 89 | 90 | def _from_data(self, data, offset=0, force_lang_eng=False): 91 | self.offset = offset 92 | self.field_type = data[offset] >> 6 & 0x3 93 | self.length = data[offset] & 0x3f 94 | 95 | self.raw = data[offset+1:offset+1+self.length] 96 | 97 | if self.field_type == self.TYPE_BCD_PLUS: 98 | self.string = self.raw.decode('bcd+') 99 | elif self.field_type == self.TYPE_6BIT_ASCII: 100 | self.string = _unpack6bitascii(self.raw) 101 | else: 102 | chr_data = ''.join([chr(c) for c in self.raw]) 103 | self.string = chr_data 104 | 105 | 106 | class FruTypeLengthString(TypeLengthString): 107 | 108 | def __init__(self, data=None, offset=0, force_lang_eng=False): 109 | super(FruTypeLengthString, self).__init__(data, offset, 110 | force_lang_eng, 111 | sdr=False) 112 | 113 | 114 | class SdrTypeLengthString(TypeLengthString): 115 | 116 | def __init__(self, data=None, offset=0, force_lang_eng=False): 117 | super(SdrTypeLengthString, self).__init__(data, sdr=True) 118 | -------------------------------------------------------------------------------- /pyipmi/helper.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Kontron Europe GmbH 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 | 17 | import time 18 | 19 | from .errors import CompletionCodeError, RetryError 20 | from .utils import check_completion_code, ByteBuffer 21 | from .msgs import constants 22 | 23 | 24 | def get_sdr_chunk_helper(send_fn, req, reserve_fn, retry=5): 25 | 26 | while True: 27 | retry -= 1 28 | if retry == 0: 29 | raise RetryError() 30 | rsp = send_fn(req) 31 | if rsp.completion_code == constants.CC_OK: 32 | break 33 | elif rsp.completion_code == constants.CC_RES_CANCELED: 34 | time.sleep(1) 35 | req.reservation_id = reserve_fn() 36 | continue 37 | elif rsp.completion_code == constants.CC_TIMEOUT: 38 | time.sleep(0.1) 39 | continue 40 | elif rsp.completion_code == constants.CC_RESP_COULD_NOT_BE_PRV: 41 | time.sleep(0.1 * retry) 42 | continue 43 | else: 44 | check_completion_code(rsp.completion_code) 45 | 46 | return rsp 47 | 48 | 49 | def get_sdr_data_helper(reserve_fn, get_fn, record_id, reservation_id=None): 50 | """Helper function to retrieve the sdr data. 51 | 52 | A specified helper function is used to retrieve the chunks. 53 | 54 | This can be used for SDRs from the Sensor Device or form the SDR 55 | repository. 56 | """ 57 | if reservation_id is None: 58 | reservation_id = reserve_fn() 59 | 60 | (next_id, data) = get_fn(reservation_id, record_id, 0, 5) 61 | 62 | header = ByteBuffer(data) 63 | record_id = header.pop_unsigned_int(2) 64 | record_version = header.pop_unsigned_int(1) # noqa:F841 65 | record_type = header.pop_unsigned_int(1) # noqa:F841 66 | record_payload_length = header.pop_unsigned_int(1) 67 | record_length = record_payload_length + 5 68 | record_data = ByteBuffer(data) 69 | 70 | offset = len(record_data) 71 | max_req_len = 20 72 | retry = 20 73 | 74 | # now get the other record data 75 | while True: 76 | retry -= 1 77 | if retry == 0: 78 | raise RetryError() 79 | 80 | length = max_req_len 81 | if (offset + length) > record_length: 82 | length = record_length - offset 83 | 84 | try: 85 | (next_id, data) = get_fn(reservation_id, record_id, offset, length) 86 | except CompletionCodeError as e: 87 | if e.cc == constants.CC_CANT_RET_NUM_REQ_BYTES: 88 | # reduce max length 89 | max_req_len -= 4 90 | if max_req_len <= 0: 91 | retry = 0 92 | else: 93 | raise CompletionCodeError(e.cc) 94 | 95 | record_data.extend(data[:]) 96 | offset = len(record_data) 97 | if len(record_data) >= record_length: 98 | break 99 | 100 | return (next_id, record_data) 101 | 102 | 103 | def _clear_repository(reserve_fn, clear_fn, ctrl, retry, reservation): 104 | while True: 105 | retry -= 1 106 | if retry <= 0: 107 | raise RetryError() 108 | 109 | try: 110 | in_progress = clear_fn(ctrl, reservation) 111 | except CompletionCodeError as e: 112 | if e.cc == constants.CC_RES_CANCELED: 113 | time.sleep(0.2) 114 | reservation = reserve_fn() 115 | continue 116 | else: 117 | check_completion_code(e.cc) 118 | 119 | if in_progress == constants.REPOSITORY_ERASURE_IN_PROGRESS: 120 | time.sleep(0.5) 121 | continue 122 | 123 | break 124 | return reservation 125 | 126 | 127 | def clear_repository_helper(reserve_fn, clear_fn, retry=5, reservation=None): 128 | """Helper function to start repository erasure and wait until finish. 129 | 130 | This helper is used by clear_sel and clear_sdr_repository. 131 | """ 132 | if reservation is None: 133 | reservation = reserve_fn() 134 | 135 | # start erasure 136 | reservation = _clear_repository(reserve_fn, clear_fn, 137 | constants.REPOSITORY_INITIATE_ERASE, 138 | retry, reservation) 139 | 140 | # give some time to clear 141 | time.sleep(0.5) 142 | 143 | # wait until finish 144 | reservation = _clear_repository(reserve_fn, clear_fn, 145 | constants.REPOSITORY_GET_ERASE_STATUS, 146 | retry, reservation) 147 | -------------------------------------------------------------------------------- /pyipmi/interfaces/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Kontron Europe GmbH 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 | 17 | from .ipmitool import Ipmitool 18 | from .aardvark import Aardvark 19 | from .ipmbdev import IpmbDev 20 | from .mock import Mock 21 | from .rmcp import Rmcp 22 | 23 | INTERFACES = [ 24 | Ipmitool, 25 | Aardvark, 26 | IpmbDev, 27 | Mock, 28 | Rmcp, 29 | ] 30 | 31 | 32 | def create_interface(interface, *args, **kwargs): 33 | for intf in INTERFACES: 34 | if intf.NAME == interface: 35 | intf = intf(*args, **kwargs) 36 | return intf 37 | 38 | raise RuntimeError('unknown interface with name %s' % interface) 39 | -------------------------------------------------------------------------------- /pyipmi/interfaces/ipmbdev.py: -------------------------------------------------------------------------------- 1 | import os 2 | import select 3 | import time 4 | from array import array 5 | 6 | from ..msgs import create_message, encode_message, decode_message 7 | from ..errors import IpmiTimeoutError 8 | from ..logger import log 9 | from ..interfaces.ipmb import IpmbHeaderReq, checksum, rx_filter, encode_ipmb_msg 10 | 11 | 12 | class IpmbDev(object): 13 | """This interface uses ipmb-dev-int linux driver.""" 14 | 15 | NAME = 'ipmbdev' 16 | 17 | def __init__(self, slave_address=0x20, port='/dev/ipmb-0'): 18 | # TODO: slave address is currently not defined here 19 | self.slave_address = slave_address 20 | self.port = port 21 | self.timeout = 0.25 22 | self.max_retries = 3 23 | self.next_sequence_number = 0 24 | 25 | def open(self): 26 | self._dev = os.open(self.port, os.O_RDWR) 27 | 28 | def close(self): 29 | os.close(self._dev) 30 | 31 | def establish_session(self, session): 32 | pass 33 | 34 | def close_session(self): 35 | pass 36 | 37 | def is_ipmc_accessible(self, target): 38 | header = IpmbHeaderReq() 39 | header.netfn = 6 40 | header.rs_lun = 0 41 | header.rs_sa = target.ipmb_address 42 | header.rq_seq = self.next_sequence_number 43 | header.rq_lun = 0 44 | header.rq_sa = self.slave_address 45 | header.cmdid = 1 46 | self._send_raw(header, None) 47 | self._receive_raw(header) 48 | return True 49 | 50 | def _inc_sequence_number(self): 51 | self.next_sequence_number = (self.next_sequence_number + 1) % 64 52 | 53 | @staticmethod 54 | def _encode_ipmb_msg_req(header, cmd_data): 55 | data = header.encode() 56 | data.extend(cmd_data) 57 | data.append(checksum(data[2:])) 58 | 59 | return data 60 | 61 | def _send_raw(self, header, raw_bytes): 62 | raw_bytes = encode_ipmb_msg(header, raw_bytes) 63 | i2c_addr = header.rs_sa >> 1 64 | 65 | log().debug('I2C TX to %02Xh [%s]', i2c_addr, 66 | ' '.join(['%02x' % b for b in raw_bytes])) 67 | os.write(self._dev, bytes([len(raw_bytes)]) + raw_bytes) 68 | 69 | def _receive_raw(self, header): 70 | start_time = time.time() 71 | rsp_received = False 72 | poll_returned_no_data = False 73 | while not rsp_received: 74 | timeout = self.timeout - (time.time() - start_time) 75 | 76 | if timeout <= 0 or poll_returned_no_data: 77 | raise IpmiTimeoutError() 78 | 79 | r, w, e = select.select([self._dev], [], [], timeout) 80 | if self._dev not in r: 81 | poll_returned_no_data = True 82 | continue 83 | 84 | rx_data = os.read(self._dev, 256) 85 | # ipmb-dev-int puts message length into first byte 86 | assert rx_data[0] == len(rx_data) - 1 87 | rx_data = rx_data[1:] 88 | 89 | rx_data = array('B', rx_data) 90 | log().debug('I2C RX from %02Xh [%s]', rx_data[3], 91 | ' '.join(['%02x' % c for c in rx_data])) 92 | 93 | rsp_received = rx_filter(header, rx_data) 94 | rx_data = rx_data[1:] 95 | 96 | return rx_data 97 | 98 | def _send_and_receive(self, target, lun, netfn, cmdid, payload): 99 | """Send and receive data using ipmb-dev-int interface. 100 | 101 | target: 102 | lun: 103 | netfn: 104 | cmdid: 105 | payload: IPMI message payload as bytestring 106 | 107 | Returns the received data as bytestring 108 | """ 109 | self._inc_sequence_number() 110 | 111 | # assemble IPMB header 112 | header = IpmbHeaderReq() 113 | header.netfn = netfn 114 | header.rs_lun = lun 115 | header.rs_sa = target.ipmb_address 116 | header.rq_seq = self.next_sequence_number 117 | header.rq_lun = 0 118 | header.rq_sa = self.slave_address 119 | header.cmdid = cmdid 120 | 121 | retries = 0 122 | while retries < self.max_retries: 123 | try: 124 | self._send_raw(header, payload) 125 | rx_data = self._receive_raw(header) 126 | break 127 | except IpmiTimeoutError: 128 | pass 129 | except IOError: 130 | pass 131 | 132 | retries += 1 133 | time.sleep(retries * 0.2) 134 | 135 | else: 136 | raise IpmiTimeoutError() 137 | 138 | return rx_data[5:-1] 139 | 140 | def send_and_receive_raw(self, target, lun, netfn, raw_bytes): 141 | """Interface function to send and receive raw message. 142 | 143 | target: IPMI target 144 | lun: logical unit number 145 | netfn: network function 146 | raw_bytes: RAW bytes as bytestring 147 | 148 | Returns the IPMI message response bytestring. 149 | """ 150 | return self._send_and_receive(target=target, 151 | lun=lun, 152 | netfn=netfn, 153 | cmdid=array('B', raw_bytes)[0], 154 | payload=raw_bytes[1:]) 155 | 156 | def send_and_receive(self, req): 157 | """Interface function to send and receive an IPMI message. 158 | 159 | target: IPMI target 160 | req: IPMI message request 161 | 162 | Returns the IPMI message response. 163 | """ 164 | log().debug('IPMI Request [%s]', req) 165 | 166 | rx_data = self._send_and_receive(target=req.target, 167 | lun=req.lun, 168 | netfn=req.netfn, 169 | cmdid=req.cmdid, 170 | payload=encode_message(req)) 171 | rsp = create_message(req.netfn + 1, req.cmdid, req.group_extension) 172 | decode_message(rsp, rx_data) 173 | 174 | log().debug('IPMI Response [%s])', rsp) 175 | 176 | return rsp 177 | -------------------------------------------------------------------------------- /pyipmi/interfaces/mock.py: -------------------------------------------------------------------------------- 1 | class Mock(object): 2 | """This interface is used as mock.""" 3 | 4 | NAME = 'mock' 5 | 6 | def __init__(self): 7 | pass 8 | 9 | def open(self): 10 | pass 11 | 12 | def close(self): 13 | pass 14 | 15 | def establish_session(self, session): 16 | pass 17 | 18 | def is_ipmc_accessible(self, target): 19 | pass 20 | 21 | def send_and_receive_raw(self, target, lun, netfn, raw_bytes): 22 | pass 23 | 24 | def send_and_receive(self, req): 25 | pass 26 | -------------------------------------------------------------------------------- /pyipmi/logger.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Kontron Europe GmbH 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 | 17 | import logging 18 | 19 | 20 | def log(): 21 | return logging.getLogger('pyipmi') 22 | 23 | 24 | def add_log_handler(handler): 25 | log().addHandler(handler) 26 | 27 | 28 | def set_log_level(level): 29 | log().setLevel(level) 30 | 31 | 32 | class NullHandler(logging.Handler): 33 | def emit(self, record): 34 | pass 35 | 36 | 37 | add_log_handler(NullHandler()) 38 | -------------------------------------------------------------------------------- /pyipmi/msgs/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | # Copyright (c) 2014 Kontron Europe GmbH 3 | # 4 | # This library is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 2.1 of the License, or (at your option) any later version. 8 | # 9 | # This library is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public 15 | # License along with this library; if not, write to the Free Software 16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | 18 | from .registry import register_message_class # noqa:F401 19 | from .registry import create_request_by_name # noqa:F401 20 | from .registry import create_response_by_name # noqa:F401 21 | from .registry import create_message # noqa:F401 22 | from .registry import create_response_message # noqa:F401 23 | 24 | from .message import Message # noqa:F401 25 | from .message import ByteArray # noqa:F401 26 | from .message import VariableByteArray # noqa:F401 27 | from .message import UnsignedInt # noqa:F401 28 | from .message import UnsignedIntMask # noqa:F401 29 | from .message import Timestamp # noqa:F401 30 | from .message import Bitfield # noqa:F401 31 | from .message import CompletionCode # noqa:F401 32 | from .message import Conditional # noqa:F401 33 | from .message import Optional # noqa:F401 34 | from .message import RemainingBytes # noqa:F401 35 | from .message import String # noqa:F401 36 | from .message import EventMessageRevision # noqa:F401 37 | from .message import GroupExtensionIdentifier # noqa:F401 38 | from .message import encode_message # noqa:F401 39 | from .message import decode_message # noqa:F401 40 | from .message import pack_message # noqa:F401 41 | 42 | from . import bmc # noqa:F401 43 | from . import chassis # noqa:F401 44 | from . import dcmi # noqa:F401 45 | from . import device_messaging # noqa:F401 46 | from . import fru # noqa:F401 47 | from . import hpm # noqa:F401 48 | from . import picmg # noqa:F401 49 | from . import sdr # noqa:F401 50 | from . import sel # noqa:F401 51 | from . import sensor # noqa:F401 52 | from . import event # noqa:F401 53 | from . import lan # noqa:F401 54 | from . import vita # noqa:F401 55 | -------------------------------------------------------------------------------- /pyipmi/msgs/chassis.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Kontron Europe GmbH 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 | 17 | from . import constants 18 | 19 | from . import register_message_class 20 | from . import Message 21 | from . import UnsignedInt 22 | from . import Bitfield 23 | from . import CompletionCode 24 | from . import Optional 25 | from . import RemainingBytes 26 | 27 | CONTROL_POWER_DOWN = 0 28 | CONTROL_POWER_UP = 1 29 | CONTROL_POWER_CYCLE = 2 30 | CONTROL_HARD_RESET = 3 31 | CONTROL_DIAGNOSTIC_INTERRUPT = 4 32 | CONTROL_SOFT_SHUTDOWN = 5 33 | 34 | 35 | @register_message_class 36 | class GetChassisCapabilitiesReq(Message): 37 | __cmdid__ = constants.CMDID_GET_CHASSIS_CAPABILITIES 38 | __netfn__ = constants.NETFN_CHASSIS 39 | 40 | 41 | @register_message_class 42 | class GetChassisCapabilitiesRsp(Message): 43 | __cmdid__ = constants.CMDID_GET_CHASSIS_CAPABILITIES 44 | __netfn__ = constants.NETFN_CHASSIS | 1 45 | __fields__ = ( 46 | CompletionCode(), 47 | Bitfield('capabilities_flags', 1, 48 | Bitfield.Bit('intrusion_sensor', 1), 49 | Bitfield.Bit('frontpanel_lockout', 1), 50 | Bitfield.Bit('diagnostic_interrupt', 1), 51 | Bitfield.Bit('power_interlock', 1), 52 | Bitfield.ReservedBit(4, 0)), 53 | UnsignedInt('fru_info_device_address', 1), 54 | UnsignedInt('sdr_device_address', 1), 55 | UnsignedInt('sel_device_address', 1), 56 | UnsignedInt('system_management_device_address', 1), 57 | Optional( 58 | UnsignedInt('bridge_device_address', 1) 59 | ), 60 | ) 61 | 62 | 63 | @register_message_class 64 | class GetChassisStatusReq(Message): 65 | __cmdid__ = constants.CMDID_GET_CHASSIS_STATUS 66 | __netfn__ = constants.NETFN_CHASSIS 67 | 68 | 69 | @register_message_class 70 | class GetChassisStatusRsp(Message): 71 | __cmdid__ = constants.CMDID_GET_CHASSIS_STATUS 72 | __netfn__ = constants.NETFN_CHASSIS | 1 73 | __fields__ = ( 74 | CompletionCode(), 75 | Bitfield('current_power_state', 1, 76 | Bitfield.Bit('power_on', 1), 77 | Bitfield.Bit('power_overload', 1), 78 | Bitfield.Bit('interlock', 1), 79 | Bitfield.Bit('power_fault', 1), 80 | Bitfield.Bit('power_control_fault', 1), 81 | Bitfield.Bit('power_restore_policy', 2), 82 | Bitfield.ReservedBit(1, 0),), 83 | Bitfield('last_power_event', 1, 84 | Bitfield.Bit('ac_failed', 1), 85 | Bitfield.Bit('power_overload', 1), 86 | Bitfield.Bit('power_interlock', 1), 87 | Bitfield.Bit('power_fault', 1), 88 | Bitfield.Bit('power_is_on_via_ipmi_command', 1), 89 | Bitfield.ReservedBit(3, 0),), 90 | Bitfield('misc_chassis_state', 1, 91 | Bitfield.Bit('chassis_intrusion_active', 1), 92 | Bitfield.Bit('front_panel_lockout_active', 1), 93 | Bitfield.Bit('drive_fault', 1), 94 | Bitfield.Bit('cooling_fault_detected', 1), 95 | Bitfield.Bit('chassis_id_state', 2), 96 | Bitfield.Bit('id_cmd_state_info_support', 1), 97 | Bitfield.ReservedBit(1, 0),), 98 | Optional( 99 | UnsignedInt('front_panel_button_capabilities', 1), 100 | ), 101 | ) 102 | 103 | 104 | @register_message_class 105 | class ChassisControlReq(Message): 106 | __cmdid__ = constants.CMDID_CHASSIS_CONTROL 107 | __netfn__ = constants.NETFN_CHASSIS 108 | __fields__ = ( 109 | Bitfield('control', 1, 110 | Bitfield.Bit('option', 4), 111 | Bitfield.ReservedBit(4, 0)), 112 | ) 113 | 114 | 115 | @register_message_class 116 | class ChassisControlRsp(Message): 117 | __cmdid__ = constants.CMDID_CHASSIS_CONTROL 118 | __netfn__ = constants.NETFN_CHASSIS | 1 119 | __fields__ = ( 120 | CompletionCode(), 121 | ) 122 | 123 | 124 | @register_message_class 125 | class GetPohCounterReq(Message): 126 | __cmdid__ = constants.CMDID_GET_POH_COUNTER 127 | __netfn__ = constants.NETFN_CHASSIS 128 | 129 | 130 | @register_message_class 131 | class GetPohCounterRsp(Message): 132 | __cmdid__ = constants.CMDID_GET_POH_COUNTER 133 | __netfn__ = constants.NETFN_CHASSIS | 1 134 | __fields__ = ( 135 | CompletionCode(), 136 | UnsignedInt('minutes_per_count', 1), 137 | UnsignedInt('counter_reading', 4), 138 | ) 139 | 140 | 141 | @register_message_class 142 | class GetSystemBootOptionsReq(Message): 143 | __cmdid__ = constants.CMDID_GET_SYSTEM_BOOT_OPTIONS 144 | __netfn__ = constants.NETFN_CHASSIS 145 | __fields__ = ( 146 | Bitfield('parameter_selector', 1, 147 | Bitfield.Bit('boot_option_parameter_selector', 7), 148 | Bitfield.ReservedBit(1)), 149 | UnsignedInt('set_selector', 1, 0), 150 | UnsignedInt('block_selector', 1, 0) 151 | ) 152 | 153 | 154 | @register_message_class 155 | class GetSystemBootOptionsRsp(Message): 156 | __cmdid__ = constants.CMDID_GET_SYSTEM_BOOT_OPTIONS 157 | __netfn__ = constants.NETFN_CHASSIS | 1 158 | __fields__ = ( 159 | CompletionCode(), 160 | Bitfield('parameter_version', 1, 161 | Bitfield.Bit('parameter_version', 4, default=1), 162 | Bitfield.ReservedBit(4)), 163 | Bitfield('parameter_valid', 1, 164 | Bitfield.Bit('boot_option_parameter_selector', 7), 165 | Bitfield.Bit('parameter_validity', 1)), 166 | RemainingBytes('data'), 167 | ) 168 | 169 | 170 | @register_message_class 171 | class SetSystemBootOptionsReq(Message): 172 | __cmdid__ = constants.CMDID_SET_SYSTEM_BOOT_OPTIONS 173 | __netfn__ = constants.NETFN_CHASSIS 174 | __fields__ = ( 175 | Bitfield('parameter_selector', 1, 176 | Bitfield.Bit('boot_option_parameter_selector', 7), 177 | Bitfield.Bit('parameter_validity', 1, default=0)), 178 | RemainingBytes('data') 179 | ) 180 | 181 | 182 | @register_message_class 183 | class SetSystemBootOptionsRsp(Message): 184 | __cmdid__ = constants.CMDID_SET_SYSTEM_BOOT_OPTIONS 185 | __netfn__ = constants.NETFN_CHASSIS | 1 186 | __fields__ = ( 187 | CompletionCode(), 188 | ) 189 | -------------------------------------------------------------------------------- /pyipmi/msgs/event.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Kontron Europe GmbH 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 | 17 | from . import constants 18 | from . import register_message_class 19 | from . import Message 20 | from . import Bitfield 21 | from . import CompletionCode 22 | 23 | 24 | @register_message_class 25 | class SetEventReceiverReq(Message): 26 | __cmdid__ = constants.CMDID_SET_EVENT_RECEIVER 27 | __netfn__ = constants.NETFN_SENSOR_EVENT 28 | __fields__ = ( 29 | Bitfield('event_receiver', 2, 30 | Bitfield.ReservedBit(1, 0), 31 | Bitfield.Bit('ipmb_i2c_slave_address', 7, 0), 32 | Bitfield.Bit('lun', 2, 0), 33 | Bitfield.ReservedBit(6, 0),), 34 | ) 35 | 36 | 37 | @register_message_class 38 | class SetEventReceiverRsp(Message): 39 | __cmdid__ = constants.CMDID_SET_EVENT_RECEIVER 40 | __netfn__ = constants.NETFN_SENSOR_EVENT | 1 41 | __fields__ = ( 42 | CompletionCode(), 43 | ) 44 | 45 | 46 | @register_message_class 47 | class GetEventReceiverReq(Message): 48 | __cmdid__ = constants.CMDID_GET_EVENT_RECEIVER 49 | __netfn__ = constants.NETFN_SENSOR_EVENT 50 | 51 | 52 | @register_message_class 53 | class GetEventReceiverRsp(Message): 54 | __cmdid__ = constants.CMDID_GET_EVENT_RECEIVER 55 | __netfn__ = constants.NETFN_SENSOR_EVENT | 1 56 | __fields__ = ( 57 | CompletionCode(), 58 | Bitfield('event_receiver', 2, 59 | Bitfield.ReservedBit(1, 0), 60 | Bitfield.Bit('ipmb_i2c_slave_address', 7, 0), 61 | Bitfield.Bit('lun', 2, 0), 62 | Bitfield.ReservedBit(6, 0),), 63 | ) 64 | -------------------------------------------------------------------------------- /pyipmi/msgs/fru.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Kontron Europe GmbH 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 | 17 | from . import constants 18 | 19 | from . import register_message_class 20 | from . import Message 21 | from . import UnsignedInt 22 | from . import Bitfield 23 | from . import CompletionCode 24 | from . import RemainingBytes 25 | from . import VariableByteArray 26 | 27 | 28 | @register_message_class 29 | class GetFruInventoryAreaInfoReq(Message): 30 | __cmdid__ = constants.CMDID_GET_FRU_INVENTORY_AREA_INFO 31 | __netfn__ = constants.NETFN_STORAGE 32 | __fields__ = ( 33 | UnsignedInt('fru_id', 1, 0), 34 | ) 35 | 36 | 37 | @register_message_class 38 | class GetFruInventoryAreaInfoRsp(Message): 39 | __cmdid__ = constants.CMDID_GET_FRU_INVENTORY_AREA_INFO 40 | __netfn__ = constants.NETFN_STORAGE | 1 41 | __fields__ = ( 42 | CompletionCode(), 43 | UnsignedInt('area_size', 2), 44 | Bitfield('area_info', 1, 45 | Bitfield.Bit('access', 1), 46 | Bitfield.ReservedBit(7, 0)), 47 | ) 48 | 49 | 50 | @register_message_class 51 | class ReadFruDataReq(Message): 52 | __cmdid__ = constants.CMDID_READ_FRU_DATA 53 | __netfn__ = constants.NETFN_STORAGE 54 | __fields__ = ( 55 | UnsignedInt('fru_id', 1), 56 | UnsignedInt('offset', 2), 57 | UnsignedInt('count', 1), 58 | ) 59 | 60 | 61 | @register_message_class 62 | class ReadFruDataRsp(Message): 63 | __cmdid__ = constants.CMDID_READ_FRU_DATA 64 | __netfn__ = constants.NETFN_STORAGE | 1 65 | 66 | def _length_count(obj): 67 | return obj.count 68 | 69 | __fields__ = ( 70 | CompletionCode(), 71 | UnsignedInt('count', 1), 72 | VariableByteArray('data', _length_count), 73 | ) 74 | 75 | 76 | @register_message_class 77 | class WriteFruDataReq(Message): 78 | __cmdid__ = constants.CMDID_WRITE_FRU_DATA 79 | __netfn__ = constants.NETFN_STORAGE 80 | __fields__ = ( 81 | UnsignedInt('fru_id', 1), 82 | UnsignedInt('offset', 2), 83 | RemainingBytes('data'), 84 | ) 85 | 86 | 87 | @register_message_class 88 | class WriteFruDataRsp(Message): 89 | __cmdid__ = constants.CMDID_WRITE_FRU_DATA 90 | __netfn__ = constants.NETFN_STORAGE | 1 91 | __fields__ = ( 92 | CompletionCode(), 93 | UnsignedInt('count_written', 1) 94 | ) 95 | -------------------------------------------------------------------------------- /pyipmi/msgs/lan.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Kontron Europe GmbH 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 | 17 | from . import constants 18 | from . import register_message_class 19 | from . import Message 20 | from . import ByteArray 21 | from . import UnsignedInt 22 | from . import Bitfield 23 | from . import CompletionCode 24 | from . import Optional 25 | from . import RemainingBytes 26 | 27 | 28 | @register_message_class 29 | class SetLanConfigurationParametersReq(Message): 30 | __cmdid__ = constants.CMDID_SET_LAN_CONFIGURATION_PARAMETERS 31 | __netfn__ = constants.NETFN_TRANSPORT 32 | __fields__ = ( 33 | Bitfield('command', 1, 34 | Bitfield.Bit('channel_number', 4, 0), 35 | Bitfield.ReservedBit(4, 0),), 36 | UnsignedInt('parameter_selector', 1), 37 | RemainingBytes('data'), 38 | ) 39 | 40 | 41 | @register_message_class 42 | class SetLanConfigurationParametersRsp(Message): 43 | __cmdid__ = constants.CMDID_SET_LAN_CONFIGURATION_PARAMETERS 44 | __netfn__ = constants.NETFN_TRANSPORT | 1 45 | __fields__ = ( 46 | CompletionCode(), 47 | Optional(ByteArray('auxiliary', 4)) 48 | ) 49 | 50 | 51 | @register_message_class 52 | class GetLanConfigurationParametersReq(Message): 53 | __cmdid__ = constants.CMDID_GET_LAN_CONFIGURATION_PARAMETERS 54 | __netfn__ = constants.NETFN_TRANSPORT 55 | __fields__ = ( 56 | Bitfield('command', 1, 57 | Bitfield.Bit('channel_number', 4), 58 | Bitfield.ReservedBit(3, 0), 59 | Bitfield.Bit('get_parameter_revision_only', 1, 0),), 60 | UnsignedInt('parameter_selector', 1, 0), 61 | UnsignedInt('set_selector', 1, 0), 62 | UnsignedInt('block_selector', 1, 0), 63 | ) 64 | 65 | 66 | @register_message_class 67 | class GetLanConfigurationParametersRsp(Message): 68 | __cmdid__ = constants.CMDID_GET_LAN_CONFIGURATION_PARAMETERS 69 | __netfn__ = constants.NETFN_TRANSPORT | 1 70 | __fields__ = ( 71 | CompletionCode(), 72 | UnsignedInt('parameter_revision', 1, 0), 73 | RemainingBytes('data'), 74 | ) 75 | -------------------------------------------------------------------------------- /pyipmi/msgs/registry.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Kontron Europe GmbH 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 | 17 | from functools import partial 18 | from ..errors import DescriptionError 19 | 20 | 21 | class MessageRegistry(object): 22 | def __init__(self): 23 | self.registry = dict() 24 | 25 | def register_class(self, cls): 26 | # some sanity checks 27 | # (1) class name has to end in Req or Rsp 28 | if cls.__name__[-3:] not in ('Req', 'Rsp'): 29 | raise DescriptionError('Class name has to end in Req or Rsp') 30 | # (2) mandantory fields 31 | for attr in ('__netfn__', '__cmdid__', '__default_lun__', 32 | '__group_extension__'): 33 | if not hasattr(cls, attr): 34 | raise DescriptionError('Class has to have attribute "%s"' % 35 | attr) 36 | # (3) netfn lsb has to be 0 for Req and 1 for Rsp 37 | if cls.__name__.endswith('Req') and cls.__netfn__ & 1 != 0: 38 | raise DescriptionError('LSB of NetFN of a Request must be 0') 39 | 40 | if cls.__name__.endswith('Rsp') and cls.__netfn__ & 1 != 1: 41 | raise DescriptionError('LSB of NetFN of a Request must be 1') 42 | 43 | # (4) must not be registered before 44 | if cls.__name__ in self.registry: 45 | raise DescriptionError('Message %s already registered' % 46 | cls.__name__) 47 | msg_id = (cls.__netfn__, cls.__cmdid__, cls.__group_extension__) 48 | if msg_id in self.registry: 49 | raise DescriptionError('Message (%d,%d,%d) already registered (%s)' 50 | % (msg_id[0], msg_id[1], msg_id[2], 51 | self.registry[msg_id])) 52 | 53 | # register name 54 | self.registry[cls.__name__] = cls 55 | # register (netfn, cmdid, group_extension) tuple 56 | self.registry[msg_id] = cls 57 | 58 | # register 59 | return cls 60 | 61 | def create(self, netfn, cmdid, group_extension, *args, **kwargs): 62 | return self.registry[(netfn, cmdid, group_extension)](*args, **kwargs) 63 | 64 | def create_response(self, req): 65 | return self.create(req.netfn + 1, req.cmdid, req.group_extension) 66 | 67 | def create_request_by_name(self, name, *args, **kwargs): 68 | return self.registry[name + "Req"](*args, **kwargs) 69 | 70 | def create_response_by_name(self, name, *args, **kwargs): 71 | return self.registry[name + "Rsp"](*args, **kwargs) 72 | 73 | 74 | DEFAULT_REGISTRY = MessageRegistry() 75 | register_message_class = partial(MessageRegistry.register_class, 76 | DEFAULT_REGISTRY) 77 | create_message = partial(MessageRegistry.create, DEFAULT_REGISTRY) 78 | create_response_message = partial(MessageRegistry.create_response, 79 | DEFAULT_REGISTRY) 80 | create_request_by_name = partial(MessageRegistry.create_request_by_name, 81 | DEFAULT_REGISTRY) 82 | create_response_by_name = partial(MessageRegistry.create_response_by_name, 83 | DEFAULT_REGISTRY) 84 | -------------------------------------------------------------------------------- /pyipmi/msgs/sel.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Kontron Europe GmbH 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 | 17 | from . import constants 18 | from . import register_message_class 19 | from . import Message 20 | from . import ByteArray 21 | from . import UnsignedInt 22 | from . import Timestamp 23 | from . import Bitfield 24 | from . import CompletionCode 25 | from . import RemainingBytes 26 | 27 | 28 | @register_message_class 29 | class GetSelInfoReq(Message): 30 | __cmdid__ = constants.CMDID_GET_SEL_INFO 31 | __netfn__ = constants.NETFN_STORAGE 32 | 33 | 34 | @register_message_class 35 | class GetSelInfoRsp(Message): 36 | __cmdid__ = constants.CMDID_GET_SEL_INFO 37 | __netfn__ = constants.NETFN_STORAGE | 1 38 | __fields__ = ( 39 | CompletionCode(), 40 | UnsignedInt('version', 1, default=0x51), 41 | UnsignedInt('entries', 2), 42 | UnsignedInt('free_bytes', 2), 43 | Timestamp('most_recent_addition'), 44 | Timestamp('most_recent_erase'), 45 | Bitfield('operation_support', 1, 46 | Bitfield.Bit('get_sel_allocation_info', 1), 47 | Bitfield.Bit('reserve_sel', 1), 48 | Bitfield.Bit('partial_add_sel_entry', 1), 49 | Bitfield.Bit('delete_sel', 1), 50 | Bitfield.ReservedBit(3), 51 | Bitfield.Bit('overflow_flag', 1)), 52 | ) 53 | 54 | 55 | @register_message_class 56 | class GetSelAllocationInfoReq(Message): 57 | __cmdid__ = constants.CMDID_GET_SEL_ALLOCATION_INFO 58 | __netfn__ = constants.NETFN_STORAGE 59 | 60 | 61 | @register_message_class 62 | class GetSelAllocationInfoRsp(Message): 63 | __cmdid__ = constants.CMDID_GET_SEL_ALLOCATION_INFO 64 | __netfn__ = constants.NETFN_STORAGE | 1 65 | __fields__ = ( 66 | CompletionCode(), 67 | UnsignedInt('possible_alloc_units', 2), 68 | UnsignedInt('alloc_unit_size', 2), 69 | UnsignedInt('free_alloc_units', 2), 70 | UnsignedInt('largest_free_block', 2), 71 | UnsignedInt('max_record_size', 1) 72 | ) 73 | 74 | 75 | @register_message_class 76 | class ReserveSelReq(Message): 77 | __cmdid__ = constants.CMDID_RESERVE_SEL 78 | __netfn__ = constants.NETFN_STORAGE 79 | 80 | 81 | @register_message_class 82 | class ReserveSelRsp(Message): 83 | __cmdid__ = constants.CMDID_RESERVE_SEL 84 | __netfn__ = constants.NETFN_STORAGE | 1 85 | __fields__ = ( 86 | CompletionCode(), 87 | UnsignedInt('reservation_id', 2) 88 | ) 89 | 90 | 91 | @register_message_class 92 | class GetSelEntryReq(Message): 93 | __cmdid__ = constants.CMDID_GET_SEL_ENTRY 94 | __netfn__ = constants.NETFN_STORAGE 95 | __fields__ = ( 96 | UnsignedInt('reservation_id', 2), 97 | UnsignedInt('record_id', 2), 98 | UnsignedInt('offset', 1), 99 | UnsignedInt('length', 1), 100 | ) 101 | 102 | 103 | @register_message_class 104 | class GetSelEntryRsp(Message): 105 | __cmdid__ = constants.CMDID_GET_SEL_ENTRY 106 | __netfn__ = constants.NETFN_STORAGE | 1 107 | __fields__ = ( 108 | CompletionCode(), 109 | UnsignedInt('next_record_id', 2), 110 | RemainingBytes('record_data'), 111 | ) 112 | 113 | 114 | @register_message_class 115 | class AddSelEntryReq(Message): 116 | __cmdid__ = constants.CMDID_ADD_SEL_ENTRY 117 | __netfn__ = constants.NETFN_STORAGE 118 | __fields__ = ( 119 | ByteArray('record_data', 16) 120 | ) 121 | 122 | 123 | @register_message_class 124 | class AddSelEntryRsp(Message): 125 | __cmdid__ = constants.CMDID_ADD_SEL_ENTRY 126 | __netfn__ = constants.NETFN_STORAGE | 1 127 | __fields__ = ( 128 | CompletionCode(), 129 | UnsignedInt('record_id', 2) 130 | ) 131 | 132 | 133 | @register_message_class 134 | class DeleteSelEntryReq(Message): 135 | __cmdid__ = constants.CMDID_DELETE_SEL_ENTRY 136 | __netfn__ = constants.NETFN_STORAGE 137 | __fields__ = ( 138 | UnsignedInt('reservation_id', 2), 139 | UnsignedInt('record_id', 2) 140 | ) 141 | 142 | 143 | @register_message_class 144 | class DeleteSelEntryRsp(Message): 145 | __cmdid__ = constants.CMDID_DELETE_SEL_ENTRY 146 | __netfn__ = constants.NETFN_STORAGE | 1 147 | __fields__ = ( 148 | CompletionCode(), 149 | UnsignedInt('record_id', 2) 150 | ) 151 | 152 | 153 | @register_message_class 154 | class ClearSelReq(Message): 155 | __cmdid__ = constants.CMDID_CLEAR_SEL 156 | __netfn__ = constants.NETFN_STORAGE 157 | __fields__ = ( 158 | UnsignedInt('reservation_id', 2), 159 | ByteArray('key', 3, default=b'CLR'), 160 | UnsignedInt('cmd', 1) 161 | ) 162 | 163 | 164 | @register_message_class 165 | class ClearSelRsp(Message): 166 | __cmdid__ = constants.CMDID_CLEAR_SEL 167 | __netfn__ = constants.NETFN_STORAGE | 1 168 | __fields__ = ( 169 | CompletionCode(), 170 | Bitfield('status', 1, 171 | Bitfield.Bit('erase_in_progress', 4), 172 | Bitfield.ReservedBit(4),), 173 | ) 174 | 175 | 176 | @register_message_class 177 | class GetSelTimeReq(Message): 178 | __cmdid__ = constants.CMDID_GET_SEL_TIME 179 | __netfn__ = constants.NETFN_STORAGE 180 | 181 | 182 | @register_message_class 183 | class GetSelTimeRsp(Message): 184 | __cmdid__ = constants.CMDID_GET_SEL_TIME 185 | __netfn__ = constants.NETFN_STORAGE | 1 186 | __fields__ = ( 187 | CompletionCode(), 188 | Timestamp('timestamp') 189 | ) 190 | 191 | 192 | @register_message_class 193 | class SetSelTimeReq(Message): 194 | __cmdid__ = constants.CMDID_SET_SEL_TIME 195 | __netfn__ = constants.NETFN_STORAGE 196 | __fields__ = ( 197 | Timestamp('timestamp') 198 | ) 199 | 200 | 201 | @register_message_class 202 | class SetSelTimeRsp(Message): 203 | __cmdid__ = constants.CMDID_SET_SEL_TIME 204 | __netfn__ = constants.NETFN_STORAGE | 1 205 | __fields__ = ( 206 | CompletionCode() 207 | ) 208 | -------------------------------------------------------------------------------- /pyipmi/msgs/session.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontron/python-ipmi/5566c7b9d91cd527068de0bcc4fd2b370d68336b/pyipmi/msgs/session.py -------------------------------------------------------------------------------- /pyipmi/session.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Kontron Europe GmbH 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 | 17 | 18 | class Session(object): 19 | AUTH_TYPE_NONE = 0x00 20 | AUTH_TYPE_MD2 = 0x01 21 | AUTH_TYPE_MD5 = 0x02 22 | AUTH_TYPE_PASSWORD = 0x04 23 | AUTH_TYPE_OEM = 0x05 24 | 25 | PRIV_LEVEL_USER = 2 26 | PRIV_LEVEL_OPERATOR = 3 27 | PRIV_LEVEL_ADMINISTRATOR = 4 28 | PRIV_LEVEL_OEM = 5 29 | 30 | session_id = None 31 | _interface = None 32 | _priv_level = PRIV_LEVEL_ADMINISTRATOR 33 | _auth_type = AUTH_TYPE_NONE 34 | _auth_username = None 35 | _auth_password = None 36 | _rmcp_host = None 37 | _rmcp_port = None 38 | _serial_port = None 39 | _serial_baudrate = None 40 | 41 | def __init__(self): 42 | self.established = False 43 | self.sid = 0 44 | self.sequence_number = 0 45 | self.activated = False 46 | 47 | def _get_interface(self): 48 | try: 49 | return self._interface 50 | except AttributeError: 51 | raise RuntimeError('No interface has been set') 52 | 53 | def _set_interface(self, interface): 54 | self._interface = interface 55 | 56 | def increment_sequence_number(self): 57 | self.sequence_number += 1 58 | if self.sequence_number > 0xffffffff: 59 | self.sequence_number = 1 60 | 61 | def set_session_type_rmcp(self, host, port=623): 62 | self._rmcp_host = host 63 | self._rmcp_port = port 64 | 65 | @property 66 | def rmcp_host(self): 67 | return self._rmcp_host 68 | 69 | @property 70 | def rmcp_port(self): 71 | return self._rmcp_port 72 | 73 | def set_session_type_serial(self, port, baudrate): 74 | self._serial_port = port 75 | self._serial_baudrate = baudrate 76 | 77 | @property 78 | def serial_port(self): 79 | return self._serial_port 80 | 81 | @property 82 | def serial_baudrate(self): 83 | return self._serial_baudrate 84 | 85 | @property 86 | def priv_level(self): 87 | return self._priv_level 88 | 89 | def set_priv_level(self, level): 90 | LEVELS = { 91 | 'user': self.PRIV_LEVEL_USER, 92 | 'operator': self.PRIV_LEVEL_OPERATOR, 93 | 'administrator': self.PRIV_LEVEL_ADMINISTRATOR, 94 | } 95 | self._priv_level = LEVELS[level.lower()] 96 | 97 | def _set_auth_type(self, auth_type): 98 | self._auth_type = auth_type 99 | 100 | def _get_auth_type(self): 101 | return self._auth_type 102 | 103 | def set_auth_type_user(self, username, password): 104 | self._auth_type = self.AUTH_TYPE_PASSWORD 105 | self._auth_username = username 106 | self._auth_password = password 107 | 108 | @property 109 | def auth_username(self): 110 | return self._auth_username 111 | 112 | @property 113 | def auth_password(self): 114 | return self._auth_password 115 | 116 | def establish(self): 117 | if hasattr(self.interface, 'establish_session'): 118 | self.interface.establish_session(self) 119 | 120 | def close(self): 121 | if hasattr(self.interface, 'close_session'): 122 | self.interface.close_session() 123 | 124 | def rmcp_ping(self): 125 | if hasattr(self.interface, 'rmcp_ping'): 126 | self.interface.rmcp_ping() 127 | 128 | def __str__(self): 129 | string = 'Session:\n' 130 | string += ' ID: 0x%08x\n' % self.sid 131 | string += ' Seq: 0x%08x\n' % self.sequence_number 132 | string += ' Host: %s:%s\n' % (self._rmcp_host, self._rmcp_port) 133 | string += ' Auth.: %s\n' % self.auth_type 134 | string += ' User: %s\n' % self._auth_username 135 | string += ' Password: %s\n' % self._auth_password 136 | string += '\n' 137 | return string 138 | 139 | interface = property(_get_interface, _set_interface) 140 | auth_type = property(_get_auth_type, _set_auth_type) 141 | -------------------------------------------------------------------------------- /pyipmi/state.py: -------------------------------------------------------------------------------- 1 | class DefaultProperties(object): 2 | def __init__(self): 3 | if hasattr(self, '__properties__'): 4 | for prop in self.__properties__: 5 | setattr(self, prop[0], None) 6 | 7 | 8 | class ResponseDecoder(object): 9 | def __init__(self, rsp=None): 10 | if rsp: 11 | self._from_response(rsp) 12 | 13 | 14 | class State(DefaultProperties, ResponseDecoder): 15 | """This is a container that represents a state. 16 | 17 | The state can have default 18 | properties that are created by init. 19 | """ 20 | 21 | def __init__(self, rsp=None): 22 | DefaultProperties.__init__(self) 23 | ResponseDecoder.__init__(self, rsp) 24 | -------------------------------------------------------------------------------- /pyipmi/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Kontron Europe GmbH 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 16 | 17 | import sys 18 | import codecs 19 | from array import array 20 | from .msgs import constants 21 | from .errors import DecodingError, CompletionCodeError 22 | 23 | 24 | _PY3 = (sys.version_info >= (3,)) 25 | 26 | 27 | def py3enc_unic_bytes_fix(dat): 28 | # python 3 unicode fix 29 | if isinstance(dat, str) and _PY3: 30 | dat = dat.encode('raw_unicode_escape') 31 | return dat 32 | 33 | 34 | def py3dec_unic_bytes_fix(dat): 35 | # python 3 unicode fix 36 | if _PY3: 37 | return dat.decode('raw_unicode_escape') 38 | return dat 39 | 40 | 41 | def py3_array_frombytes(msg, data): 42 | if _PY3: 43 | return msg.frombytes(data) 44 | else: 45 | return msg.fromstring(data) 46 | 47 | 48 | def py3_array_tobytes(msg): 49 | if _PY3: 50 | return msg.tobytes() 51 | else: 52 | return msg.tostring() 53 | 54 | 55 | def check_completion_code(cc): 56 | if cc != constants.CC_OK: 57 | raise CompletionCodeError(cc) 58 | 59 | 60 | def check_rsp_completion_code(rsp): 61 | """ 62 | Check the completion code of a specific response and raise 63 | CompletionCodeError in case there's an error. 64 | 65 | This method allows to pass more metadata than the `check_completion_code` 66 | method to try to interpret command-specific completion codes description in 67 | case there is an error. 68 | 69 | `rsp` should be a subclass of `Message` here. 70 | """ 71 | if rsp.completion_code != constants.CC_OK: 72 | raise CompletionCodeError( 73 | rsp.completion_code, 74 | cmdid=rsp.cmdid, 75 | netfn=rsp.netfn & 0xfe, # Get the request NetFn from response NetFn 76 | group_extension=rsp.group_extension) 77 | 78 | 79 | def chunks(data, count): 80 | for i in range(0, len(data), count): 81 | yield data[i:i+count] 82 | 83 | 84 | class ByteBuffer(object): 85 | def __init__(self, data=None): 86 | 87 | if data is not None: 88 | self.array = array('B', data) 89 | else: 90 | self.array = array('B') 91 | 92 | def push_unsigned_int(self, value, length): 93 | for i in range(length): 94 | self.array.append((value >> (8*i) & 0xff)) 95 | 96 | def pop_unsigned_int(self, length): 97 | value = 0 98 | for i in range(length): 99 | try: 100 | value |= self.array.pop(0) << (8*i) 101 | except IndexError: 102 | raise DecodingError('Data too short for message') 103 | return value 104 | 105 | def push_string(self, value): 106 | if _PY3 and isinstance(value, str): 107 | # Encode Unicode to UTF-8 108 | value = value.encode() 109 | py3_array_frombytes(self.array, value) 110 | 111 | def pop_string(self, length): 112 | string = self.array[0:length] 113 | del self.array[0:length] 114 | return py3_array_tobytes(string) 115 | # return py3dec_unic_bytes_fix(string.tostring()) 116 | 117 | def pop_slice(self, length): 118 | if len(self.array) < length: 119 | raise DecodingError('Data too short for message') 120 | 121 | c = ByteBuffer(self.array[0:length]) 122 | self.__delslice__(0, length) 123 | return c 124 | 125 | def tobytes(self): 126 | return self.array.tobytes() 127 | 128 | def tostring(self): 129 | return py3_array_tobytes(self.array) 130 | 131 | def extend(self, data): 132 | self.array.extend(data) 133 | 134 | def append_array(self, a): 135 | self.array.extend(a) 136 | 137 | def __getslice__(self, a, b): 138 | return self.array[a:b] 139 | 140 | def __delslice__(self, a, b): 141 | del self.array[a:b] 142 | 143 | def __len__(self): 144 | return len(self.array) 145 | 146 | def __getitem__(self, idx): 147 | return self.array[idx] 148 | 149 | 150 | BCD_MAP = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ', '-', '.'] 151 | 152 | 153 | def bcd_encode(input, errors='strict'): 154 | raise NotImplementedError() 155 | 156 | 157 | def bcd_decode(encoded_input): 158 | chars = list() 159 | try: 160 | for data in encoded_input: 161 | if not _PY3: 162 | data = ord(data) 163 | chars.append(BCD_MAP[data >> 4 & 0xf] + BCD_MAP[data & 0xf]) 164 | return (''.join(chars), len(encoded_input) * 2) 165 | except IndexError: 166 | raise ValueError() 167 | 168 | 169 | def bcd_search(name): 170 | # Python 3.9 normalizes 'bcd+' as 'bcd_' 171 | if name not in ('bcd+', 'bcd'): 172 | return None 173 | return codecs.CodecInfo(name='bcd+', encode=bcd_encode, decode=bcd_decode) 174 | 175 | 176 | def is_string(string): 177 | if _PY3: 178 | return isinstance(string, str) 179 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | 4 | [nosetests] 5 | with-coverage = 1 6 | cover-xml = 1 7 | cover-branches = 1 8 | cover-package = pyipmi 9 | 10 | [pep257] 11 | ignore = D100,D101,D102,D103,D104,D105,D203,D204 12 | 13 | [flake8] 14 | extend-ignore = E501 15 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from setuptools import setup, find_packages 5 | import os 6 | import subprocess 7 | 8 | name = 'python-ipmi' 9 | version_py = os.path.join(os.path.dirname(__file__), 'pyipmi', 'version.py') 10 | 11 | 12 | def git_pep440_version(): 13 | full = subprocess.check_output( 14 | ['git', 'describe', '--tags', '--always', '--dirty=.dirty'], 15 | stderr=subprocess.STDOUT).decode().strip() 16 | tag = subprocess.check_output( 17 | ['git', 'describe', '--tags', '--always', '--abbrev=0'], 18 | stderr=subprocess.STDOUT).decode().strip() 19 | tail = full[len(tag):] 20 | return tag + tail.replace('-', '.dev', 1).replace('-', '+', 1) 21 | 22 | 23 | try: 24 | version = git_pep440_version() 25 | with open(version_py, 'w') as f: 26 | f.write('# This file was autogenerated by setup.py\n') 27 | f.write('__version__ = \'%s\'\n' % (version,)) 28 | except (OSError, subprocess.CalledProcessError, IOError): 29 | try: 30 | with open(version_py, 'r') as f: 31 | d = dict() 32 | exec(f.read(), d) 33 | version = d['__version__'] 34 | except IOError: 35 | version = 'unknown' 36 | 37 | 38 | with open('README.rst') as f: 39 | readme = f.read() 40 | 41 | setup(name=name, 42 | version=version, 43 | description='Pure python IPMI library', 44 | long_description=readme, 45 | url='https://github.com/kontron/python-ipmi', 46 | download_url='https://github.com/kontron/python-ipmi/tarball/' + version, 47 | author='Michael Walle, Heiko Thiery', 48 | author_email='michael.walle@kontron.com, heiko.thiery@kontron.com', 49 | packages=find_packages(exclude=['tests*']), 50 | license='LGPLv2+', 51 | platforms=["any"], 52 | classifiers=[ 53 | 'Development Status :: 3 - Alpha', 54 | 'Environment :: Console', 55 | 'License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)', 56 | 'Natural Language :: English', 57 | 'Operating System :: OS Independent', 58 | 'Programming Language :: Python :: 3', 59 | 'Programming Language :: Python :: 3.7', 60 | 'Programming Language :: Python :: 3.8', 61 | 'Programming Language :: Python :: 3.9', 62 | 'Programming Language :: Python :: 3.10', 63 | 'Programming Language :: Python :: 3.11', 64 | 'Programming Language :: Python :: 3.12', 65 | 'Programming Language :: Python :: 3.13', 66 | 'Topic :: Software Development :: Libraries :: Python Modules', 67 | ], 68 | entry_points={ 69 | 'console_scripts': [ 70 | 'ipmitool.py = pyipmi.ipmitool:main', 71 | ] 72 | }, 73 | test_suite='tests', 74 | tests_require=[ 75 | 'pytest', 76 | ], 77 | install_requires=[ 78 | ], 79 | ) 80 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontron/python-ipmi/5566c7b9d91cd527068de0bcc4fd2b370d68336b/tests/__init__.py -------------------------------------------------------------------------------- /tests/fru_bin/kontron_am4010.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontron/python-ipmi/5566c7b9d91cd527068de0bcc4fd2b370d68336b/tests/fru_bin/kontron_am4010.bin -------------------------------------------------------------------------------- /tests/fru_bin/kontron_am4904.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontron/python-ipmi/5566c7b9d91cd527068de0bcc4fd2b370d68336b/tests/fru_bin/kontron_am4904.bin -------------------------------------------------------------------------------- /tests/fru_bin/vadatech_utc017.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontron/python-ipmi/5566c7b9d91cd527068de0bcc4fd2b370d68336b/tests/fru_bin/vadatech_utc017.bin -------------------------------------------------------------------------------- /tests/hpm_bin/firmware.hpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontron/python-ipmi/5566c7b9d91cd527068de0bcc4fd2b370d68336b/tests/hpm_bin/firmware.hpm -------------------------------------------------------------------------------- /tests/interfaces/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontron/python-ipmi/5566c7b9d91cd527068de0bcc4fd2b370d68336b/tests/interfaces/__init__.py -------------------------------------------------------------------------------- /tests/interfaces/test_aardvark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | class MockPyaardvark: 6 | def enable_i2c_slave(self, d): 7 | pass 8 | 9 | 10 | class TestAardvark: 11 | pass 12 | 13 | # @classmethod 14 | # def setup_class(self): 15 | # """Mock pyaardvark import 16 | # http://erikzaadi.com/2012/07/03/mocking-python-imports/ 17 | # """ 18 | # self.pyaardvark_mock = MagicMock() 19 | # self.pyaardvark_mock.open.return_value = MockPyaardvark() 20 | 21 | # modules = { 22 | # 'pyaardvark': self.pyaardvark_mock, 23 | # 'pyaardvark.open': self.pyaardvark_mock.open, 24 | # } 25 | 26 | # self.module_patcher = patch.dict('sys.modules', modules) 27 | # self.module_patcher.start() 28 | # ok_('pyaardvark' in sys.modules.keys()) 29 | # ok_('pyaardvark.open' in sys.modules.keys()) 30 | 31 | # from pyipmi.interfaces.aardvark import Aardvark 32 | # self.my_aardvark = Aardvark() 33 | 34 | # @classmethod 35 | # def teardown_class(self): 36 | # """Let's clean up""" 37 | # self.module_patcher.stop() 38 | 39 | # def test_rx_filter(self): 40 | # header = IpmbHeader() 41 | # header.rs_lun = 0 42 | # header.rs_sa = 0x72 43 | # header.rq_seq = 2 44 | # header.rq_lun = 0 45 | # header.rq_sa = 0x20 46 | # header.netfn = 6 47 | # header.cmdid = 1 48 | 49 | # rx_data = (0x1c, 0xc4, 0x72, 0x08, 0x1, 0x85) 50 | 51 | # ok_(self.my_aardvark._rx_filter(0x20, header, rx_data)) 52 | 53 | # def test_inc_sequence_number(self): 54 | # self.my_aardvark.next_sequence_number = 0 55 | # self.my_aardvark._inc_sequence_number() 56 | # eq_(self.my_aardvark.next_sequence_number, 1) 57 | 58 | # self.my_aardvark.next_sequence_number = 63 59 | # self.my_aardvark._inc_sequence_number() 60 | # eq_(self.my_aardvark.next_sequence_number, 0) 61 | -------------------------------------------------------------------------------- /tests/interfaces/test_ipmb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from array import array 5 | 6 | from pyipmi import Target 7 | from pyipmi.interfaces.ipmb import (checksum, IpmbHeaderReq, IpmbHeaderRsp, 8 | encode_send_message, encode_bridged_message, 9 | encode_ipmb_msg, decode_bridged_message, 10 | rx_filter) 11 | 12 | 13 | def test_checksum(): 14 | assert checksum([1, 2, 3, 4, 5]) == 256-15 15 | 16 | 17 | def test_header_req_encode(): 18 | header = IpmbHeaderReq() 19 | header.rs_lun = 0 20 | header.rs_sa = 0x72 21 | header.rq_seq = 2 22 | header.rq_lun = 1 23 | header.rq_sa = 0x20 24 | header.netfn = 6 25 | header.cmdid = 1 26 | data = header.encode() 27 | assert data == b'\x72\x18\x76\x20\x09\x01' 28 | 29 | 30 | def test_header_req_decode(): 31 | header = IpmbHeaderReq() 32 | header.decode(b'\x72\x19\x76\x20\x08\x01') 33 | assert header.rs_sa == 0x72 34 | assert header.rs_lun == 1 35 | assert header.rq_sa == 0x20 36 | assert header.rq_lun == 0 37 | assert header.netfn == 6 38 | 39 | header = IpmbHeaderReq(data=b'\x72\x19\x76\x20\x08\x01') 40 | assert header.rs_sa == 0x72 41 | assert header.rs_lun == 1 42 | assert header.rq_sa == 0x20 43 | assert header.rq_lun == 0 44 | assert header.netfn == 6 45 | 46 | 47 | def test_header_rsp_encode(): 48 | header = IpmbHeaderRsp() 49 | header.rs_lun = 0 50 | header.rs_sa = 0x72 51 | header.rq_seq = 2 52 | header.rq_lun = 1 53 | header.rq_sa = 0x20 54 | header.netfn = 6 55 | header.cmdid = 1 56 | data = header.encode() 57 | assert data == b'\x20\x19\xc7\x72\x08\x01' 58 | 59 | 60 | def test_header_rsp_decode(): 61 | header = IpmbHeaderRsp() 62 | header.decode(b'\x72\x19\x76\x20\x08\x01') 63 | assert header.rq_sa == 0x72 64 | assert header.rq_lun == 1 65 | assert header.rs_sa == 0x20 66 | assert header.rs_lun == 0 67 | assert header.netfn == 6 68 | 69 | header = IpmbHeaderRsp(data=b'\x72\x19\x76\x20\x08\x01') 70 | assert header.rq_sa == 0x72 71 | assert header.rq_lun == 1 72 | assert header.rs_sa == 0x20 73 | assert header.rs_lun == 0 74 | assert header.netfn == 6 75 | 76 | 77 | def test_encode_ipmb_msg(): 78 | header = IpmbHeaderReq() 79 | header.rs_lun = 0 80 | header.rs_sa = 0x72 81 | header.rq_seq = 2 82 | header.rq_lun = 0 83 | header.rq_sa = 0x20 84 | header.netfn = 6 85 | header.cmdid = 1 86 | 87 | assert encode_ipmb_msg(header, b'\xaa\xbb\xcc') == \ 88 | b'\x72\x18\x76\x20\x08\x01\xaa\xbb\xcc\xa6' 89 | 90 | 91 | def test_encode_send_message(): 92 | data = encode_send_message(b'\xaa\xbb', 0x12, 0x20, 7, 0x22) 93 | assert data == b'\x20\x18\xc8\x12\x88\x34\x47\xaa\xbb\x86' 94 | 95 | 96 | def test_encode_bridged_message(): 97 | payload = array('B', b'\xaa\xbb') 98 | t = Target(0) 99 | t.set_routing([(0x81, 0x20, 7), (0x20, 0x72, None)]) 100 | header = IpmbHeaderReq() 101 | header.netfn = 6 102 | header.rs_lun = 0 103 | header.rq_seq = 0x11 104 | header.rq_lun = 0 105 | header.cmdid = 0xaa 106 | data = encode_bridged_message(t.routing, header, payload, seq=0x22) 107 | assert data == \ 108 | b'\x20\x18\xc8\x81\x88\x34\x47\x72\x18\x76\x20\x44\xaa\xaa\xbb\x8d\x7c' 109 | 110 | 111 | def test_decode_bridged_message(): 112 | # 81 1c 63 20 14 34 00 20 1c c4 82 14 34 00 20 14 cc 74 14 22 00 ed ff 6a 36 113 | data = b'\x81\x1c\x63\x20\x14\x34\x00\x20\x1c\xc4\x82\x14\x34\x00\x20\x14\xcc\x74\x14\x22\x00\xed\xff\x6a\x36' 114 | data = decode_bridged_message(data) 115 | assert len(data) == 9 116 | assert data == b'\x20\x14\xcc\x74\x14\x22\x00\xed\xff' 117 | 118 | 119 | def test_rx_filter(): 120 | header_req = IpmbHeaderReq() 121 | header_req.rs_lun = 1 122 | header_req.rs_sa = 0x72 123 | header_req.rq_seq = 2 124 | header_req.rq_lun = 0 125 | header_req.rq_sa = 0x20 126 | header_req.netfn = 6 127 | header_req.cmdid = 1 128 | 129 | # requester and responder fields are twisted ... (sa and lun) 130 | header_rsp = IpmbHeaderReq() 131 | header_rsp.rs_lun = 0 132 | header_rsp.rs_sa = 0x20 133 | header_rsp.rq_seq = 2 134 | header_rsp.rq_lun = 1 135 | header_rsp.rq_sa = 0x72 136 | header_rsp.netfn = 6 + 1 137 | header_rsp.cmdid = 1 138 | 139 | rx_data = encode_ipmb_msg(header_rsp, b'\xaa\xbb\xcc') 140 | 141 | assert rx_filter(header_req, rx_data) 142 | 143 | 144 | def test_rx_filter_config_filter(): 145 | header_req = IpmbHeaderReq() 146 | header_req.rs_lun = 1 147 | header_req.rs_sa = 0x72 148 | header_req.rq_seq = 2 149 | header_req.rq_lun = 0 150 | header_req.rq_sa = 0x20 151 | header_req.netfn = 6 152 | header_req.cmdid = 1 153 | 154 | # requester and responder fields are twisted ... (sa and lun) 155 | header_rsp = IpmbHeaderReq() 156 | header_rsp.rs_lun = 0 157 | header_rsp.rs_sa = 0x20 158 | header_rsp.rq_seq = 3 159 | header_rsp.rq_lun = 1 160 | header_rsp.rq_sa = 0x72 161 | header_rsp.netfn = 6 + 1 162 | header_rsp.cmdid = 1 163 | 164 | rx_data = encode_ipmb_msg(header_rsp, b'\xaa\xbb\xcc') 165 | 166 | assert rx_filter(header_req, rx_data, rq_seq=False) 167 | 168 | header_req = IpmbHeaderReq() 169 | header_req.rs_lun = 1 170 | header_req.rs_sa = 0x72 171 | header_req.rq_seq = 2 172 | header_req.rq_lun = 0 173 | header_req.rq_sa = 0x20 174 | header_req.netfn = 6 175 | header_req.cmdid = 1 176 | 177 | # requester and responder fields are twisted ... (sa and lun) 178 | header_rsp = IpmbHeaderReq() 179 | header_rsp.rs_lun = 0 180 | header_rsp.rs_sa = 0x20 181 | header_rsp.rq_seq = 2 182 | header_rsp.rq_lun = 0 183 | header_rsp.rq_sa = 0x72 184 | header_rsp.netfn = 6 + 1 185 | header_rsp.cmdid = 1 186 | 187 | rx_data = encode_ipmb_msg(header_rsp, b'\xaa\xbb\xcc') 188 | 189 | assert not rx_filter(header_req, rx_data, rq_lun=True) 190 | 191 | header_req = IpmbHeaderReq() 192 | header_req.rs_lun = 1 193 | header_req.rs_sa = 0x70 194 | header_req.rq_seq = 2 195 | header_req.rq_lun = 0 196 | header_req.rq_sa = 0x20 197 | header_req.netfn = 6 198 | header_req.cmdid = 1 199 | 200 | # requester and responder fields are twisted ... (sa and lun) 201 | header_rsp = IpmbHeaderReq() 202 | header_rsp.rs_lun = 0 203 | header_rsp.rs_sa = 0x20 204 | header_rsp.rq_seq = 2 205 | header_rsp.rq_lun = 1 206 | header_rsp.rq_sa = 0x72 207 | header_rsp.netfn = 6 + 1 208 | header_rsp.cmdid = 1 209 | 210 | rx_data = encode_ipmb_msg(header_rsp, b'\xaa\xbb\xcc') 211 | 212 | assert not rx_filter(header_req, rx_data, rs_sa=True) 213 | -------------------------------------------------------------------------------- /tests/interfaces/test_rmcp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import array 5 | import pytest 6 | 7 | from pyipmi.session import Session 8 | from pyipmi.interfaces.rmcp import (AsfMsg, AsfPing, AsfPong, IpmiMsg, RmcpMsg) 9 | from pyipmi.utils import py3_array_tobytes 10 | from pyipmi.errors import DecodingError 11 | 12 | 13 | class TestRmcpMsg: 14 | def test_rmcpmsg_pack(self): 15 | m = RmcpMsg(0x7) 16 | pdu = m.pack(None, 0xff) 17 | assert pdu == b'\x06\x00\xff\x07' 18 | 19 | m = RmcpMsg(0x7) 20 | pdu = m.pack(b'\x11\x22\x33\x44', 0xff) 21 | assert pdu == b'\x06\x00\xff\x07\x11\x22\x33\x44' 22 | 23 | def test_rmcpmsg_unpack(self): 24 | pdu = b'\x06\x00\xee\x07\x44\x33\x22\x11' 25 | m = RmcpMsg() 26 | sdu = m.unpack(pdu) 27 | assert m.version == 6 28 | assert m.seq_number == 0xee 29 | assert m.class_of_msg == 0x7 30 | assert sdu == b'\x44\x33\x22\x11' 31 | 32 | 33 | class TestAsfMsg: 34 | def test_pack(self): 35 | m = AsfMsg() 36 | pdu = m.pack() 37 | assert pdu == b'\x00\x00\x11\xbe\x00\x00\x00\x00' 38 | 39 | def test_unpack(self): 40 | pdu = b'\x00\x00\x11\xbe\x00\x00\x00\x00' 41 | msg = AsfMsg() 42 | msg.unpack(pdu) 43 | 44 | def test_tostr(self): 45 | m = AsfMsg() 46 | m.data = b'\xaa\xbb\xcc' 47 | assert str(m) == 'aa bb cc' 48 | 49 | 50 | class TestAsfPing(): 51 | def test_pack(self): 52 | m = AsfPing() 53 | pdu = m.pack() 54 | assert pdu == b'\x00\x00\x11\xbe\x80\x00\x00\x00' 55 | 56 | 57 | class TestAsfPong(): 58 | def test_unpack(self): 59 | pdu = b'\x00\x00\x11\xbe\x40\x00\x00\x10\x00\x00\x11\xbe\x00\x00\x00\x00\x81\x00\x00\x00\x00\x00\x00\x00' 60 | m = AsfPong() 61 | m.unpack(pdu) 62 | 63 | 64 | class TestIpmiMsg: 65 | def test_ipmimsg_pack(self): 66 | m = IpmiMsg() 67 | pdu = m.pack(None) 68 | assert pdu == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 69 | 70 | def test_ipmimsg_pack_password(self): 71 | s = Session() 72 | s.set_auth_type_user('admin', 'admin') 73 | m = IpmiMsg(session=s) 74 | psw = m._padd_password() 75 | assert psw == b'admin\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 76 | 77 | s = Session() 78 | s.set_auth_type_user(b'admin', b'admin') 79 | m = IpmiMsg(session=s) 80 | psw = m._padd_password() 81 | assert psw == b'admin\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 82 | 83 | def test_ipmimsg_pack_with_data(self): 84 | data = py3_array_tobytes(array.array('B', (1, 2, 3, 4))) 85 | m = IpmiMsg() 86 | pdu = m.pack(data) 87 | assert pdu == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x01\x02\x03\x04' 88 | 89 | def test_ipmimsg_pack_with_session(self): 90 | s = Session() 91 | s.set_auth_type_user('admin', 'admin') 92 | s.sequence_number = 0x14131211 93 | s.sid = 0x18171615 94 | m = IpmiMsg(session=s) 95 | pdu = m.pack(None) 96 | assert pdu == b'\x04\x11\x12\x13\x14\x15\x16\x17\x18admin\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 97 | 98 | def test_ipmimsg_pack_auth_md5(self): 99 | s = Session() 100 | s.set_auth_type_user('admin', 'admin') 101 | s.sid = 0x02f99b85 102 | m = IpmiMsg(session=s) 103 | sdu = b'\x20\x18\xc8\x81\x0c\x3a\x02\x04\xe1\x2c\xb4\xd3\x17\xdc\x40\xdf\xe9\x78\x1e\x6d\x8e\x10\xad\xeb\x2c\xe8\x5c\xa0\x5b' 104 | auth = m._pack_auth_code_md5(sdu) 105 | assert auth == b'\x40\x46\xb1\x51\x4c\x89\x7f\x73\xc2\xfb\xa7\x4d\xf8\x03\x73\x8c' 106 | 107 | def test_ipmimsg_unpack(self): 108 | pdu = b'\x00\x11\x22\x33\x44\x55\x66\x77\x88\x00' 109 | m = IpmiMsg() 110 | m.unpack(pdu) 111 | 112 | assert m.auth_type == 0 113 | assert m.sequence_number == 0x11223344 114 | assert m.session_id == 0x55667788 115 | 116 | def test_ipmimsg_unpack_auth(self): 117 | pdu = b'\x01\x11\x22\x33\x44\x55\x66\x77\x88\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x00' 118 | m = IpmiMsg() 119 | m.unpack(pdu) 120 | 121 | assert m.auth_type == 1 122 | assert m.sequence_number == 0x11223344 123 | assert m.session_id == 0x55667788 124 | assert m.auth_code == [1, 2, 3, 4, 5, 6, 7, 8, 125 | 9, 10, 11, 12, 13, 14, 15, 16] 126 | 127 | def test_ipmimsg_unpack_check_sdu_length(self): 128 | pdu = b'\x01\x11\x22\x33\x44\x55\x66\x77\x88\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x02\x00\x00\x00' 129 | m = IpmiMsg() 130 | with pytest.raises(DecodingError): 131 | # data len is 2 ( byte 25 = \x02) but actual payload length is 3 132 | # (\x00\x00\x00) so we have len(pdu) != header_len + data_len 133 | m.unpack(pdu) 134 | 135 | def test_ipmimsg_unpack_no_check_sdu_length(self): 136 | pdu = b'\x01\x11\x22\x33\x44\x55\x66\x77\x88\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x02\x00\x00\x00' 137 | m = IpmiMsg(ignore_sdu_length=True) 138 | # Same PDU here, we have len(pdu) != header_len + data_len 139 | sdu = m.unpack(pdu) 140 | 141 | assert m.auth_type == 1 142 | assert m.sequence_number == 0x11223344 143 | assert m.session_id == 0x55667788 144 | assert m.auth_code == [1, 2, 3, 4, 5, 6, 7, 8, 145 | 9, 10, 11, 12, 13, 14, 15, 16] 146 | assert sdu == b'\x00\x00\x00' 147 | 148 | def tests_ipmimsg_unpack_no_check_sdu_length_empty_sdu(self): 149 | pdu = b'\x01\x11\x22\x33\x44\x55\x66\x77\x88\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x02' 150 | m = IpmiMsg(ignore_sdu_length=True) 151 | # We have len(pdu) != header_len + data_len 152 | sdu = m.unpack(pdu) 153 | 154 | assert m.auth_type == 1 155 | assert m.sequence_number == 0x11223344 156 | assert m.session_id == 0x55667788 157 | assert m.auth_code == [1, 2, 3, 4, 5, 6, 7, 8, 158 | 9, 10, 11, 12, 13, 14, 15, 16] 159 | assert sdu is None 160 | 161 | 162 | class TestRmcp: 163 | # def test_send_and_receive_raw(self): 164 | # mock_send = MagicMock() 165 | # mock_recv = MagicMock() 166 | # mock_recv.return_value = (b'\x06\x00\xee\x07\x00\x00\x00\x00\x00\x00' 167 | # b'\x00\x00\x00\x06' 168 | # b'\x01\x02\x03\x04\x05\x06', 0) 169 | 170 | # target = Target() 171 | # target.ipmb_address = 0x20 172 | # rmcp = Rmcp() 173 | # rmcp.host = '10.10.10.10' 174 | # rmcp.port = 637 175 | 176 | # rmcp._sock.sendto = mock_send 177 | # rmcp._sock.recvfrom = mock_recv 178 | 179 | # rmcp.send_and_receive_raw(target, 0, 0, b'\x00') 180 | # rmcp._send_ipmi_msg.assert_called_with(1) 181 | 182 | def test_send_and_receive(self): 183 | pass 184 | -------------------------------------------------------------------------------- /tests/msgs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kontron/python-ipmi/5566c7b9d91cd527068de0bcc4fd2b370d68336b/tests/msgs/__init__.py -------------------------------------------------------------------------------- /tests/msgs/test_bmc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from array import array 4 | 5 | import pyipmi.msgs.bmc 6 | 7 | from pyipmi.msgs import decode_message 8 | 9 | 10 | def test_getdeviceid_decode_req(): 11 | m = pyipmi.msgs.bmc.GetDeviceIdReq() 12 | decode_message(m, b'') 13 | 14 | 15 | def test_getdeviceid_decode_rsp_with_cc(): 16 | m = pyipmi.msgs.bmc.GetDeviceIdRsp() 17 | decode_message(m, b'\xc0') 18 | assert m.completion_code == 0xc0 19 | 20 | 21 | def test_getdeviceid_decode_valid_rsp(): 22 | m = pyipmi.msgs.bmc.GetDeviceIdRsp() 23 | decode_message(m, b'\x00\x0c\x89\x00\x00\x02\x3d\x98' 24 | b'\x3a\x00\xbe\x14\x04\x00\x02\x00') 25 | assert m.completion_code == 0 26 | assert m.device_id == 0x0c 27 | assert m.device_revision.device_revision == 9 28 | assert m.device_revision.provides_device_sdrs == 1 29 | assert m.firmware_revision.major == 0 30 | assert m.firmware_revision.device_available == 0 31 | assert m.firmware_revision.minor == 0 32 | assert m.ipmi_version == 2 33 | assert m.additional_support.sensor == 1 34 | assert m.additional_support.sdr_repository == 0 35 | assert m.additional_support.sel == 1 36 | assert m.additional_support.fru_inventory == 1 37 | assert m.additional_support.ipmb_event_receiver == 1 38 | assert m.additional_support.ipmb_event_generator == 1 39 | assert m.additional_support.bridge == 0 40 | assert m.additional_support.chassis == 0 41 | assert m.manufacturer_id == 15000 42 | assert m.product_id == 5310 43 | assert m.auxiliary == array('B', [4, 0, 2, 0]) 44 | 45 | 46 | def test_getdeviceid_decode_valid_rsp_wo_aux(): 47 | m = pyipmi.msgs.bmc.GetDeviceIdRsp() 48 | decode_message(m, b'\x00\x0c\x89\x00\x00\x02\x3d\x98' 49 | b'\x3a\x00\xbe\x14') 50 | assert m.completion_code == 0 51 | assert m.device_id == 0x0c 52 | assert m.device_revision.device_revision == 9 53 | assert m.device_revision.provides_device_sdrs == 1 54 | assert m.firmware_revision.major == 0 55 | assert m.firmware_revision.device_available == 0 56 | assert m.firmware_revision.minor == 0 57 | assert m.ipmi_version == 2 58 | assert m.additional_support.sensor == 1 59 | assert m.additional_support.sdr_repository == 0 60 | assert m.additional_support.sel == 1 61 | assert m.additional_support.fru_inventory == 1 62 | assert m.additional_support.ipmb_event_receiver == 1 63 | assert m.additional_support.ipmb_event_generator == 1 64 | assert m.additional_support.bridge == 0 65 | assert m.additional_support.chassis == 0 66 | assert m.manufacturer_id == 15000 67 | assert m.product_id == 5310 68 | 69 | 70 | def test_getdeviceguid_decode_req(): 71 | m = pyipmi.msgs.bmc.GetDeviceGuidReq() 72 | decode_message(m, b'') 73 | 74 | 75 | def test_getdeviceguid_decode_valid_rsp(): 76 | m = pyipmi.msgs.bmc.GetDeviceGuidRsp() 77 | decode_message(m, b'\x00\xff\xee\xdd\xcc\xbb\xaa' 78 | b'\x99\x88\x77\x66\x55\x44\x33\x22\x11\x00') 79 | assert m.completion_code == 0x00 80 | assert m.device_guid == array('B', [0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 81 | 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 82 | 0x33, 0x22, 0x11, 0x00]) 83 | 84 | 85 | def test_getselftestresults_decode_test_passed_rsp(): 86 | m = pyipmi.msgs.bmc.GetSelftestResultsRsp() 87 | decode_message(m, b'\x00\x55\x00') 88 | assert m.completion_code == 0x00 89 | assert m.result == 0x55 90 | assert int(m.status) == 0x00 91 | 92 | 93 | def test_getselftestresults_decode_test_fail_not_implemented_rsp(): 94 | m = pyipmi.msgs.bmc.GetSelftestResultsRsp() 95 | decode_message(m, b'\x00\x56\x00') 96 | assert m.completion_code == 0x00 97 | assert m.result == 0x56 98 | assert int(m.status) == 0x00 99 | 100 | 101 | def test_getselftestresults_decode_test_fail_corrupted_sel_rsp(): 102 | m = pyipmi.msgs.bmc.GetSelftestResultsRsp() 103 | decode_message(m, b'\x00\x57\x80') 104 | assert m.completion_code == 0x00 105 | assert m.result == 0x57 106 | assert int(m.status) == 0x80 107 | assert m.status.cannot_access_sel_device == 1 108 | assert m.status.cannot_access_sdr_device == 0 109 | assert m.status.cannot_access_bmc_fru_device == 0 110 | assert m.status.ipmb_signal_lines_do_not_respond == 0 111 | assert m.status.sdr_repository_empty == 0 112 | assert m.status.internal_use_area_corrupted == 0 113 | assert m.status.controller_bootblock_corrupted == 0 114 | assert m.status.controller_firmware_corrupted == 0 115 | 116 | 117 | def test_getselftestresults_decode_test_fail_corrupted_sdr_rsp(): 118 | m = pyipmi.msgs.bmc.GetSelftestResultsRsp() 119 | decode_message(m, b'\x00\x57\x40') 120 | assert m.completion_code == 0x00 121 | assert m.result == 0x57 122 | assert int(m.status) == 0x40 123 | assert m.status.cannot_access_sel_device == 0 124 | assert m.status.cannot_access_sdr_device == 1 125 | assert m.status.cannot_access_bmc_fru_device == 0 126 | assert m.status.ipmb_signal_lines_do_not_respond == 0 127 | assert m.status.sdr_repository_empty == 0 128 | assert m.status.internal_use_area_corrupted == 0 129 | assert m.status.controller_bootblock_corrupted == 0 130 | assert m.status.controller_firmware_corrupted == 0 131 | 132 | 133 | def test_getselftestresults_decode_test_fail_corrupted_fru_rsp(): 134 | m = pyipmi.msgs.bmc.GetSelftestResultsRsp() 135 | decode_message(m, b'\x00\x57\x20') 136 | assert m.completion_code == 0x00 137 | assert m.result == 0x57 138 | assert int(m.status) == 0x20 139 | assert m.status.cannot_access_sel_device == 0 140 | assert m.status.cannot_access_sdr_device == 0 141 | assert m.status.cannot_access_bmc_fru_device == 1 142 | assert m.status.ipmb_signal_lines_do_not_respond == 0 143 | assert m.status.sdr_repository_empty == 0 144 | assert m.status.internal_use_area_corrupted == 0 145 | assert m.status.controller_bootblock_corrupted == 0 146 | assert m.status.controller_firmware_corrupted == 0 147 | -------------------------------------------------------------------------------- /tests/msgs/test_chassis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from array import array 4 | 5 | import pyipmi.msgs.chassis 6 | 7 | from pyipmi.msgs import encode_message, decode_message 8 | 9 | 10 | def test_getchassisstatus_encode_valid_req(): 11 | m = pyipmi.msgs.chassis.GetChassisStatusReq() 12 | data = encode_message(m) 13 | assert m.__netfn__ == 0 14 | assert m.__cmdid__ == 1 15 | assert data == b'' 16 | 17 | 18 | def test_getchassisstatus_decode_valid_rsp(): 19 | m = pyipmi.msgs.chassis.GetChassisStatusRsp() 20 | decode_message(m, b'\x00\xea\xaa\xaa') 21 | assert m.completion_code == 0x00 22 | assert m.current_power_state.power_on == 0 23 | assert m.current_power_state.power_overload == 1 24 | assert m.current_power_state.interlock == 0 25 | assert m.current_power_state.power_fault == 1 26 | assert m.current_power_state.power_control_fault == 0 27 | assert m.current_power_state.power_restore_policy == 3 28 | 29 | assert m.last_power_event.ac_failed == 0 30 | assert m.last_power_event.power_overload == 1 31 | assert m.last_power_event.power_interlock == 0 32 | assert m.last_power_event.power_fault == 1 33 | assert m.last_power_event.power_is_on_via_ipmi_command == 0 34 | 35 | assert m.misc_chassis_state.chassis_intrusion_active == 0 36 | assert m.misc_chassis_state.front_panel_lockout_active == 1 37 | assert m.misc_chassis_state.drive_fault == 0 38 | assert m.misc_chassis_state.cooling_fault_detected == 1 39 | 40 | 41 | def test_getchassisstatus_decode_valid_optional_byte_rsp(): 42 | m = pyipmi.msgs.chassis.GetChassisStatusRsp() 43 | decode_message(m, b'\x00\x00\x00\00\xaa') 44 | assert m.completion_code == 0x00 45 | assert m.front_panel_button_capabilities == 0xaa 46 | 47 | 48 | def test_chassiscontrol_encode_valid_req(): 49 | m = pyipmi.msgs.chassis.ChassisControlReq() 50 | m.control.option = 1 51 | data = encode_message(m) 52 | assert m.__netfn__ == 0 53 | assert m.__cmdid__ == 2 54 | assert data == b'\x01' 55 | 56 | 57 | def test_getsystembootoptions_encode_valid_req(): 58 | m = pyipmi.msgs.chassis.GetSystemBootOptionsReq() 59 | m.parameter_selector.boot_option_parameter_selector = 5 60 | data = encode_message(m) 61 | assert m.__netfn__ == 0 62 | assert m.__cmdid__ == 9 63 | assert data == b'\x05\x00\x00' 64 | 65 | 66 | def test_getsystembootoptions_decode_valid_rsp(): 67 | m = pyipmi.msgs.chassis.GetSystemBootOptionsRsp() 68 | decode_message(m, b'\x00\x01\x85\x00\x08\x00\x00\x00') 69 | 70 | assert m.completion_code == 0x00 71 | assert m.parameter_version.parameter_version == 1 72 | assert m.parameter_valid.boot_option_parameter_selector == 5 73 | assert m.parameter_valid.parameter_validity == 1 74 | assert m.data == array('B', b'\x00\x08\x00\x00\x00') 75 | 76 | 77 | def test_setsystembootoptions_encode_valid_req(): 78 | m = pyipmi.msgs.chassis.SetSystemBootOptionsReq() 79 | m.parameter_selector.boot_option_parameter_selector = 5 80 | m.parameter_selector.parameter_validity = 1 81 | m.data = array('B', b'\x70\x08\x00\x00\x00') 82 | data = encode_message(m) 83 | 84 | assert m.__netfn__ == 0 85 | assert m.__cmdid__ == 8 86 | assert data == b'\x85\x70\x08\x00\x00\x00' 87 | 88 | 89 | def test_setsystembootoptions_decode_valid_rsp(): 90 | m = pyipmi.msgs.chassis.SetSystemBootOptionsRsp() 91 | decode_message(m, b'\x00') 92 | 93 | assert m.completion_code == 0x00 94 | -------------------------------------------------------------------------------- /tests/msgs/test_event.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import pyipmi.msgs.event 4 | 5 | from pyipmi.msgs import encode_message 6 | from pyipmi.msgs import decode_message 7 | 8 | 9 | def test_seteventreceiver_encode_lun0_req(): 10 | m = pyipmi.msgs.event.SetEventReceiverReq() 11 | m.event_receiver.ipmb_i2c_slave_address = 0x10 12 | m.event_receiver.lun = 0 13 | data = encode_message(m) 14 | assert data == b'\x20\x00' 15 | 16 | 17 | def test_seteventreceiver_encode_lun3_req(): 18 | m = pyipmi.msgs.event.SetEventReceiverReq() 19 | m.event_receiver.ipmb_i2c_slave_address = 0x10 20 | m.event_receiver.lun = 3 21 | data = encode_message(m) 22 | assert data, b'\x20\x03' 23 | 24 | 25 | def test_geteventreceiver_decode_lun0_rsp(): 26 | m = pyipmi.msgs.event.GetEventReceiverRsp() 27 | decode_message(m, b'\x00\x20\x00') 28 | assert m.completion_code == 0x00 29 | assert m.event_receiver.ipmb_i2c_slave_address == 0x10 30 | assert m.event_receiver.lun == 0 31 | 32 | 33 | def test_geteventreceiver_decode_lun3_rsp(): 34 | m = pyipmi.msgs.event.GetEventReceiverRsp() 35 | decode_message(m, b'\x00\x20\x03') 36 | assert m.completion_code == 0x00 37 | assert m.event_receiver.ipmb_i2c_slave_address == 0x10 38 | assert m.event_receiver.lun == 3 39 | -------------------------------------------------------------------------------- /tests/msgs/test_fru.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import pytest 4 | 5 | from array import array 6 | 7 | import pyipmi 8 | from pyipmi.errors import DecodingError, EncodingError 9 | from pyipmi.msgs import encode_message, decode_message 10 | 11 | 12 | def test_fruinventoryareainfo_decode_valid_rsp(): 13 | m = pyipmi.msgs.fru.GetFruInventoryAreaInfoRsp() 14 | decode_message(m, b'\x00\x01\x02\x01') 15 | assert m.completion_code == 0x00 16 | assert m.area_size == 0x0201 17 | assert m.area_info.access == 1 18 | 19 | 20 | def test_writefrudatareq_decode_valid_req(): 21 | m = pyipmi.msgs.fru.WriteFruDataReq() 22 | decode_message(m, b'\x01\x02\x03\x04\x05') 23 | assert m.fru_id == 1 24 | assert m.offset == 0x302 25 | assert m.data == array('B', b'\x04\x05') 26 | 27 | 28 | def test_writefrudatareq_encode_valid_req(): 29 | m = pyipmi.msgs.fru.WriteFruDataReq() 30 | m.fru_id = 1 31 | m.offset = 0x302 32 | m.data = array('B', b'\x04\x05') 33 | data = encode_message(m) 34 | assert data == b'\x01\x02\x03\x04\x05' 35 | 36 | 37 | def test_writefrudatareq_decode_valid_req_wo_data(): 38 | m = pyipmi.msgs.fru.WriteFruDataReq() 39 | decode_message(m, b'\x01\x02\x03') 40 | assert m.fru_id == 1 41 | assert m.offset == 0x302 42 | assert m.data == array('B') 43 | 44 | 45 | def test_writefrudatareq_encode_valid_req_wo_data(): 46 | m = pyipmi.msgs.fru.WriteFruDataReq() 47 | m.fru_id = 1 48 | m.offset = 0x302 49 | m.data = array('B') 50 | data = encode_message(m) 51 | assert data == b'\x01\x02\x03' 52 | 53 | 54 | def test_writefrudatareq_decode_invalid_req(): 55 | m = pyipmi.msgs.fru.WriteFruDataReq() 56 | with pytest.raises(DecodingError): 57 | decode_message(m, b'\x01\x02') 58 | 59 | 60 | def test_readfrudatareq_decode_valid_req(): 61 | m = pyipmi.msgs.fru.ReadFruDataReq() 62 | decode_message(m, b'\x01\x02\x03\x04') 63 | assert m.fru_id == 1 64 | assert m.offset == 0x302 65 | assert m.count == 4 66 | 67 | 68 | def test_readfrudatareq_decode_short_req(): 69 | m = pyipmi.msgs.fru.ReadFruDataReq() 70 | with pytest.raises(DecodingError): 71 | decode_message(m, b'\x01\x02\x03') 72 | 73 | 74 | def test_readfrudatareq_decode_long_req(): 75 | m = pyipmi.msgs.fru.ReadFruDataReq() 76 | with pytest.raises(DecodingError): 77 | decode_message(m, b'\x01\x02\x03\04\x05') 78 | 79 | 80 | def test_readfrudatareq_encode_valid_req(): 81 | m = pyipmi.msgs.fru.ReadFruDataReq() 82 | m.fru_id = 1 83 | m.offset = 0x302 84 | m.count = 4 85 | data = encode_message(m) 86 | assert data == b'\x01\x02\x03\x04' 87 | 88 | 89 | def test_readfrudatarsp_decode_valid_rsp(): 90 | m = pyipmi.msgs.fru.ReadFruDataRsp() 91 | decode_message(m, b'\x00\x05\x01\x02\x03\x04\x05') 92 | assert m.completion_code == 0 93 | assert m.count == 5 94 | assert m.data == array('B', b'\x01\x02\x03\x04\x05') 95 | 96 | 97 | def test_readfrudatarsp_decode_rsp_with_cc(): 98 | m = pyipmi.msgs.fru.ReadFruDataRsp() 99 | decode_message(m, b'\xc0') 100 | assert m.completion_code == 0xc0 101 | 102 | 103 | def test_readfrudatarsp_decode_invalid_rsp(): 104 | m = pyipmi.msgs.fru.ReadFruDataRsp() 105 | with pytest.raises(DecodingError): 106 | decode_message(m, b'\x00\x01\x01\x02') 107 | 108 | 109 | def test_readfrudatarsp_encode_valid_rsp(): 110 | m = pyipmi.msgs.fru.ReadFruDataRsp() 111 | m.completion_code = 0 112 | m.count = 5 113 | m.data = array('B', b'\x01\x02\x03\x04\x05') 114 | data = encode_message(m) 115 | assert data == b'\x00\x05\x01\x02\x03\x04\x05' 116 | 117 | 118 | def test_readfrudatarsp_encode_invalid_rsp(): 119 | m = pyipmi.msgs.fru.ReadFruDataRsp() 120 | m.completion_code = 0 121 | m.count = 1 122 | m.data = array('B', b'\x01\x02') 123 | with pytest.raises(EncodingError): 124 | encode_message(m) 125 | -------------------------------------------------------------------------------- /tests/msgs/test_hpm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import pyipmi.msgs.hpm 4 | 5 | from pyipmi.msgs import encode_message 6 | from pyipmi.msgs import decode_message 7 | 8 | 9 | def test_uploadfirmwareblockreq_encode(): 10 | m = pyipmi.msgs.hpm.UploadFirmwareBlockReq() 11 | m.number = 1 12 | m.data = [0, 1, 2, 3] 13 | data = encode_message(m) 14 | assert data == b'\x00\x01\x00\x01\x02\x03' 15 | 16 | 17 | def test_activatefirmwarereq_decode_valid_req(): 18 | m = pyipmi.msgs.hpm.ActivateFirmwareReq() 19 | decode_message(m, b'\x00\x01') 20 | assert m.picmg_identifier == 0 21 | assert m.rollback_override_policy == 1 22 | 23 | 24 | def test_activatefirmwarereq_encode_valid_req(): 25 | m = pyipmi.msgs.hpm.ActivateFirmwareReq() 26 | m.picmg_identifier = 0 27 | m.rollback_override_policy = 0x1 28 | data = encode_message(m) 29 | assert data == b'\x00\x01' 30 | 31 | 32 | def test_activatefirmwarereq_decode_valid_req_wo_optional(): 33 | m = pyipmi.msgs.hpm.ActivateFirmwareReq() 34 | decode_message(m, b'\x00') 35 | assert m.picmg_identifier == 0 36 | assert m.rollback_override_policy is None 37 | 38 | 39 | def test_activatefirmwarereq_encode_valid_req_wo_optional(): 40 | m = pyipmi.msgs.hpm.ActivateFirmwareReq() 41 | m.picmg_identifier = 0 42 | m.rollback_override_policy = None 43 | data = encode_message(m) 44 | assert data == b'\x00' 45 | -------------------------------------------------------------------------------- /tests/msgs/test_message.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from array import array 5 | from pyipmi.utils import ByteBuffer 6 | from pyipmi.msgs.message import (Bitfield, Message, UnsignedInt, 7 | RemainingBytes, String) 8 | 9 | 10 | class TMessage(object): 11 | def __init__(self, field): 12 | setattr(self, field.name, field.create()) 13 | self.field = field 14 | 15 | def encode(self): 16 | data = ByteBuffer() 17 | self.field.encode(self, data) 18 | return data 19 | 20 | def decode(self, data): 21 | data = ByteBuffer(data) 22 | self.field.decode(self, data) 23 | 24 | 25 | def test_bitfield_encode(): 26 | t = TMessage(Bitfield('status', 1, 27 | Bitfield.Bit('erase_in_progress', 4), 28 | Bitfield.ReservedBit(4, 0),)) 29 | t.status.erase_in_progress = 1 30 | byte_buffer = t.encode() 31 | assert byte_buffer.array == array('B', [0x1]) 32 | 33 | 34 | def test_unsignedint_encode(): 35 | t = TMessage(UnsignedInt('test', 4)) 36 | t.test = 0x12345678 37 | byte_buffer = t.encode() 38 | assert byte_buffer.array == array('B', [0x78, 0x56, 0x34, 0x12]) 39 | 40 | t = TMessage(UnsignedInt('test', 8)) 41 | t.test = 0x12345678 42 | byte_buffer = t.encode() 43 | assert byte_buffer.array == array('B', [0x78, 0x56, 0x34, 0x12, 0, 0, 0, 0]) 44 | 45 | 46 | def test_unsignedint_decode(): 47 | t = TMessage(UnsignedInt('test', 1)) 48 | t.decode(b'\x12') 49 | assert t.test == 0x12 50 | 51 | t.decode(b'\xd7') 52 | assert t.test == 0xd7 53 | 54 | 55 | def test_string_encode(): 56 | t = TMessage(String('test', 10)) 57 | t.test = '1234' 58 | byte_buffer = t.encode() 59 | assert byte_buffer.array == array('B', [0x31, 0x32, 0x33, 0x34]) 60 | 61 | 62 | def test_string_decode(): 63 | t = TMessage(String('test', 10)) 64 | t.decode(b'abcdef') 65 | assert t.test == b'abcdef' 66 | 67 | 68 | def test_remainingbytes_encode(): 69 | t = TMessage(RemainingBytes('test')) 70 | t.test = [0xb4, 1] 71 | byte_buffer = t.encode() 72 | assert byte_buffer.array == array('B', [0xb4, 0x01]) 73 | 74 | 75 | def test_message(): 76 | msg = Message() 77 | msg.__netfn__ = 0x1 78 | msg.__cmdid__ = 0x2 79 | 80 | assert msg.lun == 0 81 | assert msg.netfn == 1 82 | assert msg.cmdid == 2 83 | -------------------------------------------------------------------------------- /tests/msgs/test_picmg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import pyipmi.msgs.bmc 4 | import pyipmi.msgs.sel 5 | import pyipmi.msgs.event 6 | import pyipmi.msgs.hpm 7 | import pyipmi.msgs.sensor 8 | 9 | from pyipmi.msgs import constants, decode_message, encode_message 10 | from pyipmi.msgs.picmg import PICMG_IDENTIFIER 11 | 12 | 13 | def test_get_picmg_properties_req(): 14 | msg = pyipmi.msgs.picmg.GetPicmgPropertiesReq() 15 | assert msg.cmdid == constants.CMDID_GET_PICMG_PROPERTIES 16 | assert msg.netfn == constants.NETFN_GROUP_EXTENSION 17 | assert msg.group_extension == PICMG_IDENTIFIER 18 | 19 | 20 | def test_get_address_info_picmg_2_9_rsp(): 21 | m = pyipmi.msgs.picmg.GetAddressInfoRsp() 22 | decode_message(m, b'\x00\x00\x01\x02\x03') 23 | assert m.completion_code == 0x00 24 | assert m.picmg_identifier == 0x00 25 | assert m.hardware_address == 0x01 26 | assert m.ipmb_0_address == 0x02 27 | assert m.ipmb_1_address == 0x03 28 | assert m.fru_id is None 29 | assert m.site_id is None 30 | assert m.site_type is None 31 | assert m.carrier_number is None 32 | 33 | 34 | def test_get_address_info_picmg_3_0_rsp(): 35 | m = pyipmi.msgs.picmg.GetAddressInfoRsp() 36 | decode_message(m, b'\x00\x00\x01\x02\xff\x04\x05\x06') 37 | assert m.completion_code == 0x00 38 | assert m.picmg_identifier == 0x00 39 | assert m.hardware_address == 0x01 40 | assert m.ipmb_0_address == 0x02 41 | assert m.ipmb_1_address == 0xff 42 | assert m.fru_id == 0x04 43 | assert m.site_id == 0x05 44 | assert m.site_type == 0x06 45 | assert m.carrier_number is None 46 | 47 | 48 | def test_get_address_info_mtca_rsp(): 49 | m = pyipmi.msgs.picmg.GetAddressInfoRsp() 50 | decode_message(m, b'\x00\x00\x01\x02\x03\x04\x05\x06\x07') 51 | assert m.completion_code == 0x00 52 | assert m.picmg_identifier == 0x00 53 | assert m.hardware_address == 0x01 54 | assert m.ipmb_0_address == 0x02 55 | assert m.ipmb_1_address == 0x03 56 | assert m.fru_id == 0x04 57 | assert m.site_id == 0x05 58 | assert m.site_type == 0x06 59 | assert m.carrier_number == 0x07 60 | 61 | 62 | def test_get_shelf_address_info_rsp(): 63 | m = pyipmi.msgs.picmg.GetShelfAddressInfoRsp() 64 | decode_message(m, b'\x00\x00\x01\x02') 65 | assert m.completion_code == 0x00 66 | assert m.picmg_identifier == 0x00 67 | assert m.shelf_address[0] == 0x01 68 | assert m.shelf_address[1] == 0x02 69 | 70 | 71 | def test_encode_fru_control_req(): 72 | m = pyipmi.msgs.picmg.FruControlReq() 73 | m.fru_id = 1 74 | m.option = 2 75 | data = encode_message(m) 76 | assert data == b'\x00\x01\x02' 77 | 78 | 79 | def test_decode_fru_control_rsp(): 80 | m = pyipmi.msgs.picmg.FruControlRsp() 81 | decode_message(m, b'\x00\x00\xaa') 82 | assert m.rsp_data[0] == 0xaa 83 | 84 | 85 | def test_clear_activation_lock_req(): 86 | m = pyipmi.msgs.picmg.SetFruActivationPolicyReq() 87 | m.fru_id = 1 88 | m.mask.activation_locked = 1 89 | m.set.activation_locked = 0 90 | data = encode_message(m) 91 | assert data == b'\x00\x01\x01\x00' 92 | 93 | 94 | def test_set_activation_lock_req(): 95 | m = pyipmi.msgs.picmg.SetFruActivationPolicyReq() 96 | m.fru_id = 1 97 | m.mask.activation_locked = 1 98 | m.set.activation_locked = 1 99 | data = encode_message(m) 100 | assert data == b'\x00\x01\x01\x01' 101 | 102 | 103 | def test_clear_deactivation_lock_req(): 104 | m = pyipmi.msgs.picmg.SetFruActivationPolicyReq() 105 | m.fru_id = 1 106 | m.mask.deactivation_locked = 1 107 | m.set.deactivation_locked = 0 108 | data = encode_message(m) 109 | assert data == b'\x00\x01\x02\x00' 110 | 111 | 112 | def test_set_deactivation_lock_req(): 113 | m = pyipmi.msgs.picmg.SetFruActivationPolicyReq() 114 | m.fru_id = 1 115 | m.mask.deactivation_locked = 1 116 | m.set.deactivation_locked = 1 117 | data = encode_message(m) 118 | assert data == b'\x00\x01\x02\x02' 119 | 120 | 121 | def test_decode_rsp_local_control_state(): 122 | m = pyipmi.msgs.picmg.GetFruLedStateRsp() 123 | decode_message(m, b'\x00\x00\x01\xff\x00\x02') 124 | assert m.completion_code == 0x00 125 | assert m.led_states.local_avail == 1 126 | assert m.local_function == 0xff 127 | assert m.local_on_duration == 0 128 | assert m.local_color == 2 129 | 130 | 131 | def test_decode_rsp_override_mode(): 132 | m = pyipmi.msgs.picmg.GetFruLedStateRsp() 133 | decode_message(m, b'\x00\x00\x03\xff\x00\x03\xff\x00\x03') 134 | assert m.completion_code == 0x00 135 | assert m.led_states.local_avail == 1 136 | assert m.local_function == 0xff 137 | assert m.local_on_duration == 0 138 | assert m.local_color == 3 139 | assert m.led_states.override_en == 1 140 | assert m.override_function == 0xff 141 | assert m.override_on_duration == 0 142 | assert m.override_color == 3 143 | assert m.led_states.lamp_test_en == 0 144 | 145 | 146 | def test_decode_rsp_lamp_test_and_override_mode(): 147 | m = pyipmi.msgs.picmg.GetFruLedStateRsp() 148 | decode_message(m, b'\x00\x00\x07\xff\x00\x02\xff\x00\x02\x7f') 149 | assert m.completion_code == 0x00 150 | assert m.led_states.local_avail == 1 151 | assert m.local_function == 0xff 152 | assert m.local_on_duration == 0 153 | assert m.local_color == 2 154 | assert m.led_states.override_en == 1 155 | assert m.override_function == 0xff 156 | assert m.override_on_duration == 0 157 | assert m.override_color == 2 158 | assert m.led_states.lamp_test_en == 1 159 | assert m.lamp_test_duration == 0x7f 160 | 161 | 162 | def test_decode_rsp_only_lamp_test_mode(): 163 | m = pyipmi.msgs.picmg.GetFruLedStateRsp() 164 | decode_message(m, b'\x00\x00\x04\xff\x00\x02\xff\x00\x02\x7f') 165 | assert m.completion_code == 0x00 166 | assert m.led_states.local_avail == 0 167 | assert m.local_function == 0xff 168 | assert m.local_on_duration == 0 169 | assert m.local_color == 2 170 | assert m.led_states.override_en == 0 171 | assert m.override_function == 0xff 172 | assert m.override_on_duration == 0 173 | assert m.override_color == 2 174 | assert m.led_states.lamp_test_en == 1 175 | assert m.lamp_test_duration == 0x7f 176 | 177 | 178 | def test_encode_req_pm_heartbeat(): 179 | m = pyipmi.msgs.picmg.SendPmHeartbeatReq() 180 | m.timeout = 10 181 | m.ps1.mch_1 = 1 182 | data = encode_message(m) 183 | assert data == b'\x00\n\x01' 184 | 185 | m = pyipmi.msgs.picmg.SendPmHeartbeatReq() 186 | m.timeout = 10 187 | m.ps1.mch_2 = 1 188 | data = encode_message(m) 189 | assert data == b'\x00\n\x02' 190 | 191 | 192 | def test_decode_rsp_pm_heartbeat(): 193 | m = pyipmi.msgs.picmg.SendPmHeartbeatRsp() 194 | decode_message(m, b'\x00\x00') 195 | assert m.completion_code == 0x00 196 | assert m.picmg_identifier == 0x00 197 | -------------------------------------------------------------------------------- /tests/msgs/test_registry.py: -------------------------------------------------------------------------------- 1 | 2 | import pyipmi.msgs 3 | from pyipmi.msgs import create_message, create_response_message, create_response_by_name, create_request_by_name 4 | 5 | 6 | def test_create_message(): 7 | req = create_message(6, 1, None) 8 | assert type(req) is pyipmi.msgs.bmc.GetDeviceIdReq 9 | req = create_message(7, 1, None) 10 | assert type(req) is pyipmi.msgs.bmc.GetDeviceIdRsp 11 | 12 | 13 | def test_create_response(): 14 | req = pyipmi.msgs.bmc.GetDeviceIdReq() 15 | rsp = create_response_message(req) 16 | assert type(rsp) is pyipmi.msgs.bmc.GetDeviceIdRsp 17 | 18 | 19 | def test_create_request_by_name(): 20 | req = create_request_by_name('GetDeviceId') 21 | assert type(req) is pyipmi.msgs.bmc.GetDeviceIdReq 22 | 23 | 24 | def test_create_response_by_name(): 25 | rsp = create_response_by_name('GetDeviceId') 26 | assert type(rsp) is pyipmi.msgs.bmc.GetDeviceIdRsp 27 | -------------------------------------------------------------------------------- /tests/msgs/test_sdr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from array import array 4 | 5 | import pyipmi.msgs.sdr 6 | 7 | from pyipmi.msgs import encode_message 8 | from pyipmi.msgs import decode_message 9 | 10 | 11 | def test_getsdrrepositoryinfo_encode_req(): 12 | m = pyipmi.msgs.sdr.GetSdrRepositoryInfoReq() 13 | data = encode_message(m) 14 | assert data == b'' 15 | 16 | 17 | def test_getsdrrepositoryinfo_decode_rsp(): 18 | m = pyipmi.msgs.sdr.GetSdrRepositoryInfoRsp() 19 | decode_message(m, b'\x00\x51\x00\x11\x55\xaa\x11\x22\x33\x44\x55\x66\x77\x88\xaa') 20 | assert m.completion_code == 0x00 21 | assert m.sdr_version == 0x51 22 | assert m.record_count == 0x1100 23 | assert m.free_space == 0xaa55 24 | assert m.most_recent_addition == 0x44332211 25 | assert m.most_recent_erase == 0x88776655 26 | assert m.support.get_allocation_info == 0 27 | assert m.support.reserve == 1 28 | assert m.support.partial_add == 0 29 | assert m.support.delete == 1 30 | assert m.support.update_type == 1 31 | assert m.support.overflow_flag == 1 32 | 33 | 34 | def test_getsdrrepositoryallocationinfo_encode_req(): 35 | m = pyipmi.msgs.sdr.GetSdrRepositoryAllocationInfoReq() 36 | data = encode_message(m) 37 | assert data == b'' 38 | 39 | 40 | def test_getsdrrepositoryallocationinfo_decode_rsp(): 41 | m = pyipmi.msgs.sdr.GetSdrRepositoryAllocationInfoRsp() 42 | decode_message(m, b'\x00\x11\x22\x33\x44\x55\x66\x77\x88\xaa') 43 | assert m.completion_code == 0x00 44 | assert m.number_of_units == 0x2211 45 | assert m.unit_size == 0x4433 46 | assert m.free_units == 0x6655 47 | assert m.largest_free_block == 0x8877 48 | assert m.maximum_record_size == 0xaa 49 | 50 | 51 | def test_reservesdrrepository_encode_req(): 52 | m = pyipmi.msgs.sdr.ReserveSdrRepositoryReq() 53 | data = encode_message(m) 54 | assert data == b'' 55 | 56 | 57 | def test_reservesdrrepository_decode_rsp(): 58 | m = pyipmi.msgs.sdr.ReserveSdrRepositoryRsp() 59 | decode_message(m, b'\x00\x11\x22') 60 | assert m.completion_code == 0x00 61 | assert m.reservation_id == 0x2211 62 | 63 | 64 | def test_getsdr_encode_req(): 65 | m = pyipmi.msgs.sdr.GetSdrReq() 66 | m.reservation_id = 0x1122 67 | m.record_id = 0x3344 68 | m.offset = 0xaa 69 | m.bytes_to_read = 0x55 70 | data = encode_message(m) 71 | assert data == b'\x22\x11\x44\x33\xaa\x55' 72 | 73 | 74 | def test_getsdr_decode_rsp(): 75 | m = pyipmi.msgs.sdr.GetSdrRsp() 76 | decode_message(m, b'\x00\x11\x22\x33\x44\x55\x66') 77 | assert m.completion_code == 0x00 78 | assert m.next_record_id == 0x2211 79 | assert m.record_data == array('B', [0x33, 0x44, 0x55, 0x66]) 80 | 81 | 82 | def test_addsdr_encode_req(): 83 | m = pyipmi.msgs.sdr.AddSdrReq() 84 | m.record_data = array('B', [0x55, 0x44]) 85 | data = encode_message(m) 86 | assert data == b'\x55\x44' 87 | 88 | 89 | def test_addsdr_decode_rsp(): 90 | m = pyipmi.msgs.sdr.AddSdrRsp() 91 | decode_message(m, b'\x00\x11\x22') 92 | assert m.completion_code == 0x00 93 | assert m.record_id == 0x2211 94 | 95 | 96 | def test_partialaddsdr_encode_req(): 97 | m = pyipmi.msgs.sdr.PartialAddSdrReq() 98 | m.reservation_id = 0x2211 99 | m.record_id = 0x4433 100 | m.offset = 0xaa 101 | m.status.in_progress = 0xaa 102 | m.record_data = array('B', [0x55, 0x44]) 103 | data = encode_message(m) 104 | assert data == b'\x11\x22\x33\x44\xaa\x0a\x55\x44' 105 | 106 | 107 | def test_partialaddsdr_decode_rsp(): 108 | m = pyipmi.msgs.sdr.PartialAddSdrRsp() 109 | decode_message(m, b'\x00\x11\x22') 110 | assert m.completion_code == 0x00 111 | assert m.record_id == 0x2211 112 | 113 | 114 | def test_deletesdr_encode_req(): 115 | m = pyipmi.msgs.sdr.DeleteSdrReq() 116 | m.reservation_id = 0x2211 117 | m.record_id = 0x4433 118 | data = encode_message(m) 119 | assert data == b'\x11\x22\x33\x44' 120 | 121 | 122 | def test_deletesdr_decode_rsp(): 123 | m = pyipmi.msgs.sdr.DeleteSdrRsp() 124 | decode_message(m, b'\x00\x11\x22') 125 | assert m.completion_code == 0x00 126 | assert m.record_id == 0x2211 127 | 128 | 129 | def test_clearsdrrepository_encode_req(): 130 | m = pyipmi.msgs.sdr.ClearSdrRepositoryReq() 131 | m.reservation_id = 0x2211 132 | m.cmd = 1 133 | data = encode_message(m) 134 | assert data == b'\x11"CLR\x01' 135 | 136 | 137 | def test_clearsdrrepository_decode_rsp(): 138 | m = pyipmi.msgs.sdr.ClearSdrRepositoryRsp() 139 | decode_message(m, b'\x00\x11') 140 | assert m.completion_code == 0x00 141 | assert m.status.erase_in_progress == 0x1 142 | -------------------------------------------------------------------------------- /tests/msgs/test_sel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import pytest 4 | 5 | from array import array 6 | 7 | import pyipmi.msgs.sel 8 | 9 | from pyipmi.errors import DecodingError 10 | from pyipmi.msgs import encode_message 11 | from pyipmi.msgs import decode_message 12 | 13 | 14 | def test_getselentry_decode_rsp_with_cc(): 15 | m = pyipmi.msgs.sel.GetSelEntryRsp() 16 | decode_message(m, b'\xc0') 17 | assert m.completion_code == 0xc0 18 | 19 | 20 | def test_getselentry_decode_invalid_rsp(): 21 | m = pyipmi.msgs.sel.GetSelEntryRsp() 22 | with pytest.raises(DecodingError): 23 | decode_message(m, b'\x00\x01') 24 | 25 | 26 | def test_getselentry_decode_valid_rsp(): 27 | m = pyipmi.msgs.sel.GetSelEntryRsp() 28 | decode_message(m, b'\x00\x02\x01\x01\x02\x03\x04') 29 | assert m.completion_code == 0x00 30 | assert m.next_record_id == 0x0102 31 | assert m.record_data == array('B', [1, 2, 3, 4]) 32 | 33 | 34 | def test_getselentry_encode_valid_rsp(): 35 | m = pyipmi.msgs.sel.GetSelEntryRsp() 36 | m.completion_code = 0 37 | m.next_record_id = 0x0102 38 | m.record_data = array('B', b'\x01\x02\x03\x04') 39 | data = encode_message(m) 40 | assert data == b'\x00\x02\x01\x01\x02\x03\x04' 41 | -------------------------------------------------------------------------------- /tests/test_bmc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pyipmi.bmc import DeviceId, DeviceGuid, Watchdog 5 | import pyipmi.msgs.bmc 6 | from pyipmi.msgs import decode_message 7 | 8 | 9 | def test_watchdog_object(): 10 | msg = pyipmi.msgs.bmc.GetWatchdogTimerRsp() 11 | decode_message(msg, b'\x00\x41\x42\x33\x44\x55\x66\x77\x88') 12 | 13 | wdt = Watchdog(msg) 14 | assert wdt.timer_use == 1 15 | assert wdt.is_running == 1 16 | assert wdt.dont_log == 0 17 | assert wdt.timeout_action == 2 18 | assert wdt.pre_timeout_interrupt == 4 19 | assert wdt.pre_timeout_interval == 0x33 20 | 21 | assert wdt.timer_use_expiration_flags == 0x44 22 | assert wdt.initial_countdown == 0x6655 23 | assert wdt.present_countdown == 0x8877 24 | 25 | 26 | def test_deviceid_object(): 27 | rsp = pyipmi.msgs.bmc.GetDeviceIdRsp() 28 | decode_message(rsp, b'\x00\x12\x84\x05\x67\x51\x55\x12\x34\x56\x44\x55') 29 | 30 | dev = DeviceId(rsp) 31 | assert dev.device_id == 18 32 | assert dev.revision == 4 33 | assert dev.provides_sdrs 34 | assert str(dev.fw_revision) == '5.67' 35 | assert str(dev.ipmi_version) == '1.5' 36 | assert dev.manufacturer_id == 5649426 37 | assert dev.product_id == 21828 38 | 39 | assert dev.aux is None 40 | 41 | 42 | def test_deviceid_object_with_aux(): 43 | msg = pyipmi.msgs.bmc.GetDeviceIdRsp() 44 | decode_message(msg, 45 | b'\x00\x00\x00\x00\x00\x00\x00\x00' 46 | b'\x00\x00\x00\x00\x01\x02\x03\x04') 47 | 48 | device_id = DeviceId(msg) 49 | assert device_id.aux == [1, 2, 3, 4] 50 | 51 | 52 | def test_deviceguid_object(): 53 | m = pyipmi.msgs.bmc.GetDeviceGuidRsp() 54 | decode_message(m, b'\x00\xff\xee\xdd\xcc\xbb\xaa' 55 | b'\x99\x88\x77\x66\x55\x44\x33\x22\x11\x00') 56 | guid = DeviceGuid(m) 57 | assert guid.device_guid_string == '00112233-4455-6677-8899-aabbccddeeff' 58 | -------------------------------------------------------------------------------- /tests/test_chassis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import pytest 5 | 6 | from array import array 7 | 8 | from pyipmi.chassis import (ChassisStatus, data_to_boot_device, 9 | data_to_boot_mode, data_to_boot_persistency, 10 | boot_options_to_data, BootDevice) 11 | import pyipmi.msgs.chassis 12 | from pyipmi.msgs import decode_message 13 | 14 | 15 | def test_chassisstatus_object(): 16 | msg = pyipmi.msgs.chassis.GetChassisStatusRsp() 17 | decode_message(msg, b'\x00\xff\xff\xff') 18 | 19 | status = ChassisStatus(msg) 20 | 21 | assert status.power_on 22 | assert status.overload 23 | assert status.interlock 24 | assert status.fault 25 | assert status.control_fault 26 | assert status.restore_policy == 3 27 | 28 | assert 'ac_failed' in status.last_event 29 | assert 'overload' in status.last_event 30 | assert 'interlock' in status.last_event 31 | assert 'fault' in status.last_event 32 | assert 'power_on_via_ipmi' in status.last_event 33 | 34 | assert 'intrusion', status.chassis_state 35 | assert 'front_panel_lockout', status.chassis_state 36 | assert 'drive_fault', status.chassis_state 37 | assert 'cooling_fault', status.chassis_state 38 | 39 | 40 | def test_datatobootmode(): 41 | assert data_to_boot_mode(array('B', [0, 0, 0, 0, 0])) == "legacy" 42 | assert data_to_boot_mode(array('B', [0b10100000, 0, 0, 0, 0])) == "efi" 43 | 44 | 45 | def test_datatobootpersistency(): 46 | assert data_to_boot_persistency(array('B', [0b11000000, 0, 0, 0, 0])) 47 | assert not data_to_boot_persistency(array('B', [0b10000000, 0, 0, 0, 0])) 48 | 49 | 50 | def test_datatobootdevice(): 51 | assert data_to_boot_device(array('B', [0b11000000, 0b00001000, 0, 0, 0])) == BootDevice.DEFAULT_HDD 52 | assert data_to_boot_device(array('B', [0b11000000, 0b00000100, 0, 0, 0])) == BootDevice.PXE 53 | 54 | 55 | def test_bootoptionstodata(): 56 | assert boot_options_to_data("bios setup", "efi", True).array == array('B', [0b11100000, 0b00011000, 0, 0, 0]) 57 | 58 | 59 | def test_bootoptionstodata_raise_typeerror(): 60 | with pytest.raises(TypeError): 61 | boot_options_to_data("pxe", "efi", 1) 62 | 63 | 64 | def test_bootoptionstodata_raise_valueerror_bootmode(): 65 | with pytest.raises(ValueError): 66 | boot_options_to_data("pxe", "wrong boot mode", True) 67 | 68 | 69 | def test_bootoptionstodata_raise_valueerror_bootdevice(): 70 | with pytest.raises(ValueError): 71 | boot_options_to_data("wrong boot device", "efi", True) 72 | -------------------------------------------------------------------------------- /tests/test_device_messaging.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pyipmi.messaging import UserAccess, UserPrivilegeLevel 5 | import pyipmi.msgs.device_messaging 6 | from pyipmi.msgs import decode_message 7 | 8 | 9 | def test_useraccess_object(): 10 | msg = pyipmi.msgs.device_messaging.GetUserAccessRsp() 11 | decode_message(msg, b'\x00\x0a\x42\x01\x13') 12 | 13 | user_access = UserAccess(msg) 14 | 15 | assert user_access.user_count == 10 16 | assert user_access.enabled_user_count == 2 17 | assert user_access.enabled_status == 1 18 | assert user_access.fixed_name_user_count == 1 19 | assert user_access.privilege_level == UserPrivilegeLevel.OPERATOR 20 | assert user_access.ipmi_messaging == 1 21 | assert user_access.link_auth == 0 22 | assert user_access.callback_only == 0 23 | -------------------------------------------------------------------------------- /tests/test_errors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import pytest 5 | 6 | from pyipmi.errors import (DecodingError, EncodingError, CompletionCodeError, 7 | NotSupportedError, DescriptionError, RetryError, 8 | DataNotFound, HpmError) 9 | 10 | 11 | def test_DecodingError(): 12 | with pytest.raises(DecodingError): 13 | raise DecodingError() 14 | 15 | 16 | def test_EncodingError(): 17 | with pytest.raises(EncodingError): 18 | raise EncodingError() 19 | 20 | 21 | def test_CompletionCodeError(): 22 | with pytest.raises(CompletionCodeError): 23 | raise CompletionCodeError(cc=0x09) 24 | 25 | 26 | def test_NotSupportedError(): 27 | with pytest.raises(NotSupportedError): 28 | raise NotSupportedError() 29 | 30 | 31 | def test_DescriptionError(): 32 | with pytest.raises(DescriptionError): 33 | raise DescriptionError() 34 | 35 | 36 | def test_RetryError(): 37 | with pytest.raises(RetryError): 38 | raise RetryError() 39 | 40 | 41 | def test_DataNotFound(): 42 | with pytest.raises(DataNotFound): 43 | raise DataNotFound() 44 | 45 | 46 | def test_HpmError_no_msg(): 47 | with pytest.raises(HpmError): 48 | raise HpmError() 49 | -------------------------------------------------------------------------------- /tests/test_event.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | from pyipmi import interfaces, create_connection 4 | from pyipmi.msgs.event import (SetEventReceiverRsp, GetEventReceiverRsp) 5 | 6 | 7 | class TestEvent(object): 8 | 9 | def setup_method(self): 10 | self.mock_send_recv = MagicMock() 11 | 12 | interface = interfaces.create_interface('mock') 13 | self.ipmi = create_connection(interface) 14 | self.ipmi.send_message = self.mock_send_recv 15 | 16 | def test_set_event_receiver(self): 17 | 18 | rsp = SetEventReceiverRsp() 19 | rsp.completion_code = 0 20 | self.mock_send_recv.return_value = rsp 21 | 22 | self.ipmi.set_event_receiver(ipmb_address=0xb0, lun=1) 23 | args, _ = self.mock_send_recv.call_args 24 | req = args[0] 25 | assert req.event_receiver.ipmb_i2c_slave_address == 0xb0 26 | assert req.event_receiver.lun == 1 27 | 28 | def test_get_event_receiver(self): 29 | 30 | rsp = GetEventReceiverRsp() 31 | rsp.completion_code = 0 32 | rsp.event_receiver.ipmb_i2c_slave_address = 0xc0 33 | rsp.event_receiver.lun = 2 34 | self.mock_send_recv.return_value = rsp 35 | 36 | (addr, lun) = self.ipmi.get_event_receiver() 37 | assert addr == 0xc0 38 | assert lun == 2 39 | -------------------------------------------------------------------------------- /tests/test_fields.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import pytest 5 | 6 | from pyipmi.fields import (VersionField, FruTypeLengthString) 7 | from pyipmi.errors import DecodingError 8 | 9 | 10 | def test_versionfield_object(): 11 | version = VersionField([1, 0x99]) 12 | assert version.major == 1 13 | assert version.minor == 99 14 | 15 | version = VersionField('\x00\x99') 16 | assert version.major == 0 17 | assert version.minor == 99 18 | 19 | 20 | def test_versionfield_invalid(): 21 | version = VersionField('\x00\xff') 22 | assert version.major == 0 23 | assert version.minor == 255 24 | 25 | 26 | def test_versionfield_decoding_error(): 27 | with pytest.raises(DecodingError): 28 | version = VersionField('\x00\x9a') # noqa:F841 29 | 30 | 31 | def test_FruTypeLengthString_6bitascii(): 32 | f = FruTypeLengthString(b'\x83d\xc9\xb2\xde', 0) 33 | assert f.string == 'DELL' 34 | -------------------------------------------------------------------------------- /tests/test_fru.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import pytest 5 | 6 | from pyipmi.errors import (DecodingError) 7 | 8 | from pyipmi.fru import (FruData, FruInventory, 9 | FruPicmgPowerModuleCapabilityRecord, 10 | InventoryCommonHeader, InventoryBoardInfoArea, 11 | get_fru_inventory_from_file) 12 | 13 | 14 | this_file_path = os.path.dirname(os.path.abspath(__file__)) 15 | 16 | 17 | def test_frudata_object(): 18 | fru_field = FruData((0, 1, 2, 3)) 19 | assert fru_field.data[0] == 0 20 | assert fru_field.data[1] == 1 21 | assert fru_field.data[2] == 2 22 | assert fru_field.data[3] == 3 23 | 24 | fru_field = FruData('\x00\x01\x02\x03') 25 | assert fru_field.data[0] == 0 26 | assert fru_field.data[1] == 1 27 | assert fru_field.data[2] == 2 28 | assert fru_field.data[3] == 3 29 | 30 | 31 | def test_commonheader_object(): 32 | InventoryCommonHeader((0, 1, 2, 3, 4, 5, 6, 235)) 33 | 34 | 35 | def test_commonheader_object_invalid_checksum(): 36 | with pytest.raises(DecodingError): 37 | InventoryCommonHeader((0, 1, 2, 3, 4, 5, 6, 0)) 38 | 39 | InventoryCommonHeader((0, 1, 2, 3, 4, 5, 6, 0), ignore_checksum=True) 40 | 41 | 42 | def test_fru_inventory_from_file(): 43 | fru_file = os.path.join(this_file_path, 'fru_bin/kontron_am4010.bin') 44 | fru = get_fru_inventory_from_file(fru_file) 45 | assert fru.chassis_info_area is None 46 | 47 | 48 | def test_board_area(): 49 | fru_file = os.path.join(this_file_path, 'fru_bin/kontron_am4010.bin') 50 | fru = get_fru_inventory_from_file(fru_file) 51 | 52 | board_area = fru.board_info_area 53 | assert board_area.manufacturer.string == 'Kontron' 54 | assert board_area.product_name.string == 'AM4010' 55 | assert board_area.serial_number.string == '0023721003' 56 | assert board_area.part_number.string == '35943' 57 | 58 | 59 | def test_product_area(): 60 | fru_file = os.path.join(this_file_path, 'fru_bin/kontron_am4010.bin') 61 | fru = get_fru_inventory_from_file(fru_file) 62 | 63 | product_area = fru.product_info_area 64 | assert product_area.manufacturer.string == 'Kontron' 65 | assert product_area.name.string == 'AM4010' 66 | assert product_area.serial_number.string == '0000000000000000000000000' 67 | assert product_area.part_number.string == '0012' 68 | 69 | 70 | def test_multirecord_with_power_module_capability_record(): 71 | fru_file = os.path.join(this_file_path, 'fru_bin/vadatech_utc017.bin') 72 | fru = get_fru_inventory_from_file(fru_file) 73 | assert len(fru.multirecord_area.records) == 1 74 | record = fru.multirecord_area.records[0] 75 | assert isinstance(record, FruPicmgPowerModuleCapabilityRecord) 76 | assert record.maximum_current_output == 42.0 77 | 78 | 79 | def test_BoardInfoArea(): 80 | area = InventoryBoardInfoArea(b'\x01\t\x00\x00\x00\x00\x83d\xc9\xb2\xdePowerEdge R515 \xceCN717033AI0058\xc90RMRF7A05A\x03\xc1\x00\x00*') 81 | assert area.manufacturer.string == 'DELL' 82 | assert area.product_name.string == 'PowerEdge R515 ' 83 | assert area.serial_number.string == 'CN717033AI0058' 84 | assert area.part_number.string == '0RMRF7A05' 85 | 86 | 87 | def test_FruInventory_ignore_checksum_error(): 88 | data = b'\x01\x00\x00\x01\x04\x00\x00\xfa\x01\x03\x00vq\xb4\xcaASRockRack\xc0\xc0\xc0\xc0\xc1\x00\x1b\x01\x03\x00\xcaASRockRack\xc0\xc0\xc0\xc0\xc0\xc0\xc1\x00\x00M\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 89 | 90 | with pytest.raises(DecodingError): 91 | FruInventory(data, ignore_checksum=False) 92 | 93 | inv = FruInventory(data, ignore_checksum=True) 94 | 95 | assert inv.board_info_area.manufacturer.string == 'ASRockRack' 96 | assert inv.product_info_area.manufacturer.string == 'ASRockRack' 97 | -------------------------------------------------------------------------------- /tests/test_helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from unittest.mock import MagicMock, call 5 | 6 | from pyipmi.helper import clear_repository_helper 7 | from pyipmi.msgs.constants import (REPOSITORY_ERASURE_COMPLETED, 8 | REPOSITORY_ERASURE_IN_PROGRESS, 9 | REPOSITORY_INITIATE_ERASE, 10 | REPOSITORY_GET_ERASE_STATUS) 11 | 12 | 13 | def test_clear_repository_helper(): 14 | reserve_fn = MagicMock() 15 | reserve_fn.return_value = (0x1234) 16 | 17 | clear_fn = MagicMock() 18 | clear_fn.side_effect = [ 19 | REPOSITORY_ERASURE_COMPLETED, 20 | REPOSITORY_ERASURE_IN_PROGRESS, 21 | REPOSITORY_ERASURE_COMPLETED, 22 | ] 23 | 24 | clear_repository_helper(reserve_fn, clear_fn) 25 | 26 | clear_calls = [ 27 | call(REPOSITORY_INITIATE_ERASE, 0x1234), 28 | call(REPOSITORY_GET_ERASE_STATUS, 0x1234), 29 | call(REPOSITORY_GET_ERASE_STATUS, 0x1234), 30 | ] 31 | clear_fn.assert_has_calls(clear_calls) 32 | assert clear_fn.call_count == 3 33 | -------------------------------------------------------------------------------- /tests/test_hpm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | 6 | from pyipmi.hpm import (ComponentProperty, ComponentPropertyDescriptionString, 7 | ComponentPropertyGeneral, 8 | ComponentPropertyCurrentVersion, 9 | ComponentPropertyDeferredVersion, 10 | ComponentPropertyRollbackVersion, 11 | UpgradeActionRecord, UpgradeActionRecordBackup, 12 | UpgradeActionRecordPrepare, 13 | UpgradeActionRecordUploadForUpgrade, 14 | UpgradeActionRecordUploadForCompare, UpgradeImage, 15 | PROPERTY_GENERAL_PROPERTIES, PROPERTY_CURRENT_VERSION, 16 | PROPERTY_DESCRIPTION_STRING, PROPERTY_ROLLBACK_VERSION, 17 | PROPERTY_DEFERRED_VERSION) 18 | 19 | 20 | class TestComponentProperty: 21 | def test_general(self): 22 | prop = ComponentProperty().from_data(PROPERTY_GENERAL_PROPERTIES, b'\xaa') 23 | assert type(prop) is ComponentPropertyGeneral 24 | 25 | prop = ComponentProperty().from_data(PROPERTY_GENERAL_PROPERTIES, (0xaa,)) 26 | assert type(prop) is ComponentPropertyGeneral 27 | 28 | def test_currentversion(self): 29 | prop = ComponentProperty().from_data(PROPERTY_CURRENT_VERSION, b'\x01\x99') 30 | assert type(prop) is ComponentPropertyCurrentVersion 31 | 32 | prop = ComponentProperty().from_data( 33 | PROPERTY_CURRENT_VERSION, (0x01, 0x99)) 34 | assert type(prop) is ComponentPropertyCurrentVersion 35 | 36 | def test_descriptionstring(self): 37 | prop = ComponentProperty().from_data(PROPERTY_DESCRIPTION_STRING, 38 | b'\x30\x31\x32') 39 | assert type(prop) is ComponentPropertyDescriptionString 40 | assert prop.description == '012' 41 | 42 | prop = ComponentProperty().from_data( 43 | PROPERTY_DESCRIPTION_STRING, (0x33, 0x34, 0x35)) 44 | assert type(prop) is ComponentPropertyDescriptionString 45 | assert prop.description == '345' 46 | 47 | def test_descriptionstring_with_trailinge_zeros(self): 48 | prop = ComponentProperty().from_data(PROPERTY_DESCRIPTION_STRING, 49 | b'\x36\x37\x38\x00\x00') 50 | assert type(prop) is ComponentPropertyDescriptionString 51 | assert prop.description == '678' 52 | 53 | def test_rollbackversion(self): 54 | prop = ComponentProperty().from_data( 55 | PROPERTY_ROLLBACK_VERSION, (0x2, 0x88)) 56 | assert type(prop) is ComponentPropertyRollbackVersion 57 | 58 | def test_deferredversion(self): 59 | prop = ComponentProperty().from_data( 60 | PROPERTY_DEFERRED_VERSION, (0x3, 0x77)) 61 | assert type(prop) is ComponentPropertyDeferredVersion 62 | 63 | 64 | def test_upgradeactionrecord_create_from_data(): 65 | record = UpgradeActionRecord.create_from_data(b'\x00\x08\x02') 66 | assert record.action == 0 67 | assert type(record) is UpgradeActionRecordBackup 68 | 69 | record = UpgradeActionRecord.create_from_data(b'\x01\x08\x02') 70 | assert record.action == 1 71 | assert type(record) is UpgradeActionRecordPrepare 72 | 73 | record = \ 74 | UpgradeActionRecord.create_from_data( 75 | b'\x02\x08\x02' 76 | b'\x01\x99\xaa\xbb\xcc\xdd' 77 | b'\x30\x31\x32\x33\x34\x35\x36\x37' 78 | b'\x38\x39\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x30' 79 | b'\x04\x00\x00\x00\x11\x22\x33\x44') 80 | assert record.firmware_version.version_to_string() == '1.99' 81 | assert record.firmware_description_string == '012345678901234567890' 82 | assert record.firmware_length == 4 83 | 84 | record = UpgradeActionRecord.create_from_data(b'\x03\x08\x02') 85 | assert record.action == 3 86 | assert type(record) is UpgradeActionRecordUploadForCompare 87 | 88 | 89 | def test_upgrade_image(): 90 | path = os.path.dirname(os.path.abspath(__file__)) 91 | hpm_file = os.path.join(path, 'hpm_bin/firmware.hpm') 92 | image = UpgradeImage(hpm_file) 93 | assert isinstance(image.actions[0], UpgradeActionRecordPrepare) 94 | assert isinstance(image.actions[1], UpgradeActionRecordUploadForUpgrade) 95 | -------------------------------------------------------------------------------- /tests/test_ipmi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from unittest.mock import MagicMock 5 | 6 | import pytest 7 | 8 | from pyipmi import (Ipmi, interfaces, create_connection, NullRequester, Routing, 9 | Session, Target) 10 | from pyipmi.errors import CompletionCodeError, RetryError 11 | from pyipmi.msgs.bmc import GetDeviceIdReq, GetDeviceIdRsp 12 | from pyipmi.msgs.sensor import GetSensorReadingReq, GetSensorReadingRsp 13 | from pyipmi.msgs.constants import CC_NODE_BUSY 14 | 15 | 16 | def test_target(): 17 | target = Target() 18 | assert target.ipmb_address is None 19 | assert target.routing is None 20 | 21 | target = Target(0xa0) 22 | assert target.ipmb_address == 0xa0 23 | assert target.routing is None 24 | 25 | target = Target(ipmb_address=0xb0) 26 | assert target.ipmb_address == 0xb0 27 | assert target.routing is None 28 | 29 | 30 | def test_target_routing(): 31 | target = Target(routing=[(0x82, 0x20, 0)]) 32 | assert len(target.routing) == 1 33 | assert target.routing[0].rq_sa == 0x82 34 | assert target.routing[0].rs_sa == 0x20 35 | assert target.routing[0].channel == 0 36 | 37 | target = Target(routing=[(0x82, 0x20, 0), (0x20, 0x82, 7)]) 38 | assert len(target.routing) == 2 39 | assert target.routing[0].rq_sa == 0x82 40 | assert target.routing[0].rs_sa == 0x20 41 | assert target.routing[0].channel == 0 42 | assert target.routing[1].rq_sa == 0x20 43 | assert target.routing[1].rs_sa == 0x82 44 | assert target.routing[1].channel == 7 45 | 46 | 47 | def test_routing(): 48 | routing = Routing(0x82, 0x20, 7) 49 | assert routing.rq_sa == 0x82 50 | assert routing.rs_sa == 0x20 51 | assert routing.channel == 7 52 | 53 | 54 | def test_target_set_routing(): 55 | target = Target() 56 | 57 | target.set_routing([(0x11, 0x12, 0x13)]) 58 | assert len(target.routing) == 1 59 | assert target.routing[0].rq_sa == 0x11 60 | assert target.routing[0].rs_sa == 0x12 61 | assert target.routing[0].channel == 0x13 62 | 63 | target.set_routing([(0x11, 0x12, 0x13), (0x21, 0x22, 0x23)]) 64 | assert len(target.routing) == 2 65 | assert target.routing[0].rq_sa == 0x11 66 | assert target.routing[0].rs_sa == 0x12 67 | assert target.routing[0].channel == 0x13 68 | assert target.routing[1].rq_sa == 0x21 69 | assert target.routing[1].rs_sa == 0x22 70 | assert target.routing[1].channel == 0x23 71 | 72 | 73 | def test_target_set_routing_from_string(): 74 | target = Target() 75 | 76 | target.set_routing('[(0x11, 0x12, 0x13)]') 77 | assert len(target.routing) == 1 78 | assert target.routing[0].rq_sa == 0x11 79 | assert target.routing[0].rs_sa == 0x12 80 | assert target.routing[0].channel == 0x13 81 | 82 | target.set_routing('[(0x11, 0x12, 0x13), (0x21, 0x22, 0x23)]') 83 | assert len(target.routing) == 2 84 | assert target.routing[0].rq_sa == 0x11 85 | assert target.routing[0].rs_sa == 0x12 86 | assert target.routing[0].channel == 0x13 87 | assert target.routing[1].rq_sa == 0x21 88 | assert target.routing[1].rs_sa == 0x22 89 | assert target.routing[1].channel == 0x23 90 | 91 | 92 | def test_create_connection(): 93 | interface = interfaces.create_interface('mock') 94 | 95 | ipmi = create_connection(interface) 96 | assert ipmi.interface is not None 97 | assert isinstance(ipmi.interface, interfaces.mock.Mock) 98 | assert isinstance(ipmi.session, Session) 99 | assert isinstance(ipmi.session.interface, interfaces.mock.Mock) 100 | assert isinstance(ipmi.requester, NullRequester) 101 | 102 | 103 | def test_ipmi(): 104 | ipmi = Ipmi() 105 | assert ipmi.interface is None 106 | assert isinstance(ipmi.session, Session) 107 | assert isinstance(ipmi.requester, NullRequester) 108 | 109 | interface = interfaces.create_interface('mock') 110 | ipmi = Ipmi(interface=interface) 111 | assert isinstance(ipmi.interface, interfaces.mock.Mock) 112 | assert isinstance(ipmi.session, Session) 113 | assert isinstance(ipmi.session.interface, interfaces.mock.Mock) 114 | assert isinstance(ipmi.requester, NullRequester) 115 | 116 | 117 | def test_ipmi_with_statemetn(): 118 | interface = interfaces.create_interface('mock') 119 | 120 | with Ipmi(interface=interface) as ipmi: 121 | assert ipmi.interface is not None 122 | assert isinstance(ipmi.session, Session) 123 | assert isinstance(ipmi.requester, NullRequester) 124 | 125 | 126 | def test_ipmi_send_message_retry(): 127 | req = GetDeviceIdReq() 128 | rsp = GetDeviceIdRsp() 129 | 130 | interface = interfaces.create_interface('mock') 131 | cc = CompletionCodeError(CC_NODE_BUSY) 132 | mock = MagicMock(side_effect=(cc, 0)) 133 | mock.return_value = rsp 134 | interface.send_and_receive = mock 135 | ipmi = create_connection(interface) 136 | ipmi.target = None 137 | ipmi.send_message(req) 138 | assert mock.call_count == 2 139 | 140 | 141 | def test_ipmi_send_message_retry_error(): 142 | req = GetDeviceIdReq() 143 | rsp = GetDeviceIdRsp() 144 | 145 | with pytest.raises(RetryError): 146 | interface = interfaces.create_interface('mock') 147 | cc = CompletionCodeError(CC_NODE_BUSY) 148 | mock = MagicMock(side_effect=(cc, cc, cc)) 149 | mock.return_value = rsp 150 | interface.send_and_receive = mock 151 | ipmi = create_connection(interface) 152 | ipmi.target = None 153 | ipmi.send_message(req) 154 | 155 | 156 | def test_ipmi_send_message_with_name(): 157 | 158 | rsp = GetDeviceIdRsp() 159 | rsp.completion_code = 0 160 | 161 | mock_send_message = MagicMock() 162 | mock_send_message.return_value = rsp 163 | 164 | interface = interfaces.create_interface('mock') 165 | ipmi = create_connection(interface) 166 | ipmi.send_message = mock_send_message 167 | 168 | ipmi.send_message_with_name('GetDeviceId') 169 | args, kwargs = mock_send_message.call_args 170 | req = args[0] 171 | assert isinstance(req, GetDeviceIdReq) 172 | 173 | 174 | def test_ipmi_send_message_with_name_and_kwargs(): 175 | 176 | rsp = GetSensorReadingRsp() 177 | rsp.completion_code = 0 178 | 179 | mock_send_message = MagicMock() 180 | mock_send_message.return_value = rsp 181 | 182 | interface = interfaces.create_interface('mock') 183 | ipmi = create_connection(interface) 184 | ipmi.send_message = mock_send_message 185 | 186 | ipmi.send_message_with_name('GetSensorReading', sensor_number=5, lun=2) 187 | args, kwargs = mock_send_message.call_args 188 | req = args[0] 189 | assert isinstance(req, GetSensorReadingReq) 190 | assert req.sensor_number == 5 191 | assert req.lun == 2 192 | -------------------------------------------------------------------------------- /tests/test_ipmitool.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from pyipmi.ipmitool import parse_interface_options 4 | 5 | 6 | class TestParseInterfaceOptions: 7 | def test_options_aardvark(self): 8 | options = parse_interface_options('aardvark', 'serial=1234') 9 | assert options['serial_number'] == '1234' 10 | 11 | options = parse_interface_options('aardvark', 'pullups=on') 12 | assert options['enable_i2c_pullups'] is True 13 | 14 | options = parse_interface_options('aardvark', 'pullups=off') 15 | assert options['enable_i2c_pullups'] is False 16 | 17 | options = parse_interface_options('aardvark', 'power=on') 18 | assert options['enable_target_power'] is True 19 | 20 | options = parse_interface_options('aardvark', 'power=off') 21 | assert options['enable_target_power'] is False 22 | 23 | options = parse_interface_options('aardvark', 'fastmode=on') 24 | assert options['enable_fastmode'] is True 25 | 26 | options = parse_interface_options('aardvark', 'fastmode=off') 27 | assert options['enable_fastmode'] is False 28 | 29 | def test_options_ipmitool(self): 30 | options = parse_interface_options('ipmitool', 'interface_type=abcd,cipher=55') 31 | assert options['interface_type'] == 'abcd' 32 | assert options['cipher'] == '55' 33 | 34 | def test_options_ipmbdev(self): 35 | options = parse_interface_options('ipmbdev', 'port=/dev/ipmb0') 36 | assert options['port'] == '/dev/ipmb0' 37 | -------------------------------------------------------------------------------- /tests/test_lan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import pytest 5 | from array import array 6 | 7 | from pyipmi.lan import (data_to_ip_address, data_to_ip_source, 8 | data_to_mac_address, data_to_vlan, ip_address_to_data, 9 | ip_source_to_data, vlan_to_data) 10 | 11 | 12 | def test_datatoipaddress(): 13 | assert data_to_ip_address(array('B', [192, 168, 1, 1])) == "192.168.1.1" 14 | 15 | 16 | def test_ipaddresstodata(): 17 | assert ip_address_to_data("192.168.1.1").array == array('B', [192, 168, 1, 1]) 18 | 19 | 20 | def test_datatoipsource(): 21 | assert data_to_ip_source(array('B', [0])) == "unknown" 22 | assert data_to_ip_source(array('B', [1])) == "static" 23 | assert data_to_ip_source(array('B', [2])) == "dhcp" 24 | assert data_to_ip_source(array('B', [3])) == "bios" 25 | assert data_to_ip_source(array('B', [4])) == "other" 26 | 27 | 28 | def test_ipsourcetodata(): 29 | assert ip_source_to_data("static").array == array('B', [1]) 30 | assert ip_source_to_data("dhcp").array == array('B', [2]) 31 | 32 | 33 | def test_ipsourcetodata_raise_valueerror(): 34 | with pytest.raises(ValueError): 35 | ip_source_to_data("does not exist") 36 | 37 | 38 | def test_datatomacaddress(): 39 | assert data_to_mac_address(array('B', [0xab, 0xcd, 0xef, 0x12, 0x34, 0x56])) == "ab:cd:ef:12:34:56" 40 | 41 | 42 | def test_datatovlan(): 43 | assert data_to_vlan(array('B', [138, 129])) == 394 44 | assert data_to_vlan(array('B', [0, 0])) == 0 45 | assert data_to_vlan(array('B', [19, 128])) == 19 46 | 47 | 48 | def test_datatovlan_deactivated(): 49 | # Check if the data_to_vlan method returns 0 when a vlan data contains 50 | # non-null vlan ID but the "vlan activate" bit is set to 0 51 | assert data_to_vlan(array('B', [138, 1])) == 0 52 | 53 | 54 | def test_vlantodata(): 55 | assert vlan_to_data(394).array == array('B', [138, 129]) 56 | assert vlan_to_data(0).array == array('B', [0, 0]) 57 | assert vlan_to_data(19).array == array('B', [19, 128]) 58 | 59 | 60 | def test_vlantodata_raise_typeerror(): 61 | with pytest.raises(TypeError): 62 | vlan_to_data("wrong type of argument") 63 | 64 | 65 | def test_vlantodata_raise_valueerror(): 66 | with pytest.raises(ValueError): 67 | vlan_to_data(4096) 68 | -------------------------------------------------------------------------------- /tests/test_picmg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from pyipmi.picmg import LedState 4 | from pyipmi.msgs.picmg import SetFruLedStateReq 5 | 6 | 7 | def test_to_request(): 8 | req = SetFruLedStateReq() 9 | led = LedState(fru_id=1, led_id=2, color=LedState.COLOR_GREEN, 10 | function=LedState.FUNCTION_ON) 11 | led.to_request(req) 12 | 13 | assert req.fru_id == 1 14 | assert req.led_id == 2 15 | assert req.color == led.COLOR_GREEN 16 | assert req.led_function == 0xff 17 | assert req.on_duration == 0 18 | 19 | 20 | def test_to_request_function_on(): 21 | req = SetFruLedStateReq() 22 | led = LedState(fru_id=1, led_id=2, color=LedState.COLOR_RED) 23 | led.override_function = led.FUNCTION_ON 24 | led.to_request(req) 25 | 26 | assert req.color == LedState.COLOR_RED 27 | assert req.led_function == 0xff 28 | assert req.on_duration == 0 29 | 30 | 31 | def test_to_request_function_off(): 32 | req = SetFruLedStateReq() 33 | led = LedState(fru_id=1, led_id=2, color=LedState.COLOR_RED) 34 | led.override_function = led.FUNCTION_OFF 35 | led.to_request(req) 36 | 37 | assert req.color == LedState.COLOR_RED 38 | assert req.led_function == 0 39 | assert req.on_duration == 0 40 | 41 | 42 | def test_to_request_function_blinking(): 43 | req = SetFruLedStateReq() 44 | led = LedState(fru_id=1, led_id=2, color=LedState.COLOR_RED) 45 | led.override_function = led.FUNCTION_BLINKING 46 | led.override_off_duration = 3 47 | led.override_on_duration = 4 48 | led.to_request(req) 49 | 50 | assert req.color == LedState.COLOR_RED 51 | assert req.led_function == 3 52 | assert req.on_duration == 4 53 | 54 | 55 | def test_to_request_function_lamp_test(): 56 | req = SetFruLedStateReq() 57 | led = LedState(fru_id=1, led_id=2, color=LedState.COLOR_RED) 58 | led.override_function = led.FUNCTION_LAMP_TEST 59 | led.lamp_test_duration = 3 60 | led.to_request(req) 61 | 62 | assert req.color == LedState.COLOR_RED 63 | assert req.led_function == 0xfb 64 | assert req.on_duration == 3 65 | -------------------------------------------------------------------------------- /tests/test_sel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from unittest.mock import MagicMock 5 | 6 | from pyipmi import interfaces, create_connection 7 | from pyipmi.msgs.registry import create_response_by_name 8 | from pyipmi.sel import SelEntry, SelInfo 9 | 10 | 11 | class TestSel: 12 | 13 | def test_get_sel_entries(self): 14 | rsps = list() 15 | rsp = create_response_by_name('GetSelInfo') 16 | rsp .entries = 1 17 | rsps.append(rsp) 18 | rsp = create_response_by_name('ReserveSel') 19 | rsps.append(rsp) 20 | rsp = create_response_by_name('GetSelEntry') 21 | rsp.record_data = [0, 0, SelEntry.TYPE_SYSTEM_EVENT, 0, 0, 0, 22 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 23 | rsps.append(rsp) 24 | rsp = create_response_by_name('GetSelEntry') 25 | rsp.record_data = [0, 0, SelEntry.TYPE_SYSTEM_EVENT, 0, 0, 26 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 27 | rsp.next_record_id = 0xffff 28 | rsps.append(rsp) 29 | 30 | mock_send_message = MagicMock(side_effect=rsps) 31 | interface = interfaces.create_interface('mock') 32 | ipmi = create_connection(interface) 33 | ipmi.send_message = mock_send_message 34 | 35 | entries = ipmi.get_sel_entries() 36 | assert len(entries) == 2 37 | 38 | 39 | class TestSelInfo: 40 | rsp = create_response_by_name('GetSelInfo') 41 | rsp.version = 1 42 | rsp.entries = 1023 43 | rsp.free_bytes = 512 44 | info = SelInfo(rsp) 45 | 46 | assert info.version == 1 47 | assert info.entries == 1023 48 | assert info.free_bytes == 512 49 | 50 | rsp.operation_support.get_sel_allocation_info = 1 51 | rsp.operation_support.reserve_sel = 0 52 | rsp.operation_support.partial_add_sel_entry = 0 53 | rsp.operation_support.delete_sel = 0 54 | rsp.operation_support.overflow_flag = 0 55 | info = SelInfo(rsp) 56 | assert 'get_sel_allocation_info' in info.operation_support 57 | assert 'reserve_sel' not in info.operation_support 58 | assert 'partial_add_sel_entry' not in info.operation_support 59 | assert 'delete_sel' not in info.operation_support 60 | assert 'overflow_flag' not in info.operation_support 61 | 62 | rsp.operation_support.get_sel_allocation_info = 0 63 | rsp.operation_support.reserve_sel = 1 64 | rsp.operation_support.partial_add_sel_entry = 0 65 | rsp.operation_support.delete_sel = 0 66 | rsp.operation_support.overflow_flag = 0 67 | info = SelInfo(rsp) 68 | assert 'get_sel_allocation_info' not in info.operation_support 69 | assert 'reserve_sel' in info.operation_support 70 | assert 'partial_add_sel_entry' not in info.operation_support 71 | assert 'delete_sel' not in info.operation_support 72 | assert 'overflow_flag' not in info.operation_support 73 | 74 | rsp.operation_support.get_sel_allocation_info = 0 75 | rsp.operation_support.reserve_sel = 0 76 | rsp.operation_support.partial_add_sel_entry = 1 77 | rsp.operation_support.delete_sel = 0 78 | rsp.operation_support.overflow_flag = 0 79 | info = SelInfo(rsp) 80 | assert 'get_sel_allocation_info' not in info.operation_support 81 | assert 'reserve_sel' not in info.operation_support 82 | assert 'partial_add_sel_entry' in info.operation_support 83 | assert 'delete_sel' not in info.operation_support 84 | assert 'overflow_flag' not in info.operation_support 85 | 86 | rsp.operation_support.get_sel_allocation_info = 0 87 | rsp.operation_support.reserve_sel = 0 88 | rsp.operation_support.partial_add_sel_entry = 0 89 | rsp.operation_support.delete_sel = 1 90 | rsp.operation_support.overflow_flag = 0 91 | info = SelInfo(rsp) 92 | assert 'get_sel_allocation_info' not in info.operation_support 93 | assert 'reserve_sel' not in info.operation_support 94 | assert 'partial_add_sel_entry' not in info.operation_support 95 | assert 'delete_sel' in info.operation_support 96 | assert 'overflow_flag' not in info.operation_support 97 | 98 | rsp.operation_support.get_sel_allocation_info = 0 99 | rsp.operation_support.reserve_sel = 0 100 | rsp.operation_support.partial_add_sel_entry = 0 101 | rsp.operation_support.delete_sel = 0 102 | rsp.operation_support.overflow_flag = 1 103 | info = SelInfo(rsp) 104 | assert 'get_sel_allocation_info' not in info.operation_support 105 | assert 'reserve_sel' not in info.operation_support 106 | assert 'partial_add_sel_entry' not in info.operation_support 107 | assert 'delete_sel' not in info.operation_support 108 | assert 'overflow_flag' in info.operation_support 109 | 110 | 111 | class TestSelEnty: 112 | 113 | def test_from_data(self): 114 | data = [0xff, 0x03, 0x02, 0xf7, 0x61, 0xef, 0x52, 0x7e, 115 | 0x00, 0x04, 0xf2, 0x09, 0x7f, 0x00, 0xff, 0xff] 116 | entry = SelEntry(data) 117 | assert entry.type == 2 118 | assert entry.sensor_type == 0xf2 119 | 120 | def test_from_data_event_direction(self): 121 | data = [0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 122 | 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00] 123 | entry = SelEntry(data) 124 | assert entry.event_direction == 0 125 | 126 | data = [0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 127 | 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00] 128 | entry = SelEntry(data) 129 | assert entry.event_direction == 1 130 | 131 | def test_from_data_event_type(self): 132 | data = [0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 133 | 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00] 134 | entry = SelEntry(data) 135 | assert entry.event_type == 0x7f 136 | 137 | data = [0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 138 | 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00] 139 | entry = SelEntry(data) 140 | assert entry.event_type == 0x00 141 | 142 | def test_from_data_event_data(self): 143 | data = [0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 144 | 0x00, 0x00, 0x00, 0x00, 0xff, 0x11, 0x22, 0x33] 145 | entry = SelEntry(data) 146 | assert entry.event_data[0] == 0x11 147 | assert entry.event_data[1] == 0x22 148 | assert entry.event_data[2] == 0x33 149 | 150 | def test_type_to_string(self): 151 | assert SelEntry.type_to_string(0) is None 152 | assert SelEntry.type_to_string(0x02) == 'System Event' 153 | assert SelEntry.type_to_string(0xc0) == 'OEM timestamped (0xc0)' 154 | assert SelEntry.type_to_string(0xe0) == 'OEM non-timestamped (0xe0)' 155 | -------------------------------------------------------------------------------- /tests/test_sensor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from unittest.mock import MagicMock 5 | 6 | from pyipmi import interfaces, create_connection 7 | from pyipmi.msgs.sensor import (SetSensorThresholdsRsp, GetSensorThresholdsRsp, 8 | GetSensorReadingRsp, PlatformEventRsp) 9 | from pyipmi.sensor import (EVENT_READING_TYPE_SENSOR_SPECIFIC, 10 | SENSOR_TYPE_MODULE_HOT_SWAP) 11 | 12 | 13 | class TestSensor: 14 | 15 | def setup_method(self): 16 | self.mock_send_recv = MagicMock() 17 | 18 | interface = interfaces.create_interface('mock') 19 | self.ipmi = create_connection(interface) 20 | self.ipmi.send_message = self.mock_send_recv 21 | 22 | def test_set_sensor_thresholds(self): 23 | 24 | rsp = SetSensorThresholdsRsp() 25 | rsp.completion_code = 0 26 | self.mock_send_recv.return_value = rsp 27 | 28 | self.ipmi.set_sensor_thresholds(sensor_number=5, lun=1) 29 | args, _ = self.mock_send_recv.call_args 30 | req = args[0] 31 | assert req.lun == 1 32 | assert req.sensor_number == 5 33 | 34 | self.ipmi.set_sensor_thresholds(sensor_number=0, unr=10) 35 | args, _ = self.mock_send_recv.call_args 36 | req = args[0] 37 | assert req.set_mask.unr == 1 38 | assert req.threshold.unr == 10 39 | assert req.set_mask.ucr == 0 40 | assert req.threshold.ucr == 0 41 | assert req.set_mask.unc == 0 42 | assert req.threshold.unc == 0 43 | assert req.set_mask.lnc == 0 44 | assert req.threshold.lnc == 0 45 | assert req.set_mask.lcr == 0 46 | assert req.threshold.lcr == 0 47 | assert req.set_mask.lnr == 0 48 | assert req.threshold.lnr == 0 49 | 50 | self.ipmi.set_sensor_thresholds(sensor_number=5, ucr=11) 51 | args, _ = self.mock_send_recv.call_args 52 | req = args[0] 53 | assert req.lun == 0 54 | assert req.set_mask.unr == 0 55 | assert req.threshold.unr == 0 56 | assert req.set_mask.ucr == 1 57 | assert req.threshold.ucr == 11 58 | assert req.set_mask.unc == 0 59 | assert req.threshold.unc == 0 60 | assert req.set_mask.lnc == 0 61 | assert req.threshold.lnc == 0 62 | assert req.set_mask.lcr == 0 63 | assert req.threshold.lcr == 0 64 | assert req.set_mask.lnr == 0 65 | assert req.threshold.lnr == 0 66 | 67 | def test_send_platform_event(self): 68 | 69 | rsp = PlatformEventRsp() 70 | rsp.completion_code = 0 71 | self.mock_send_recv.return_value = rsp 72 | 73 | # Module handle closed event 74 | self.ipmi.send_platform_event(SENSOR_TYPE_MODULE_HOT_SWAP, 1, 75 | EVENT_READING_TYPE_SENSOR_SPECIFIC, 76 | asserted=True, 77 | event_data=[0, 0xff, 0xff]) 78 | args, _ = self.mock_send_recv.call_args 79 | req = args[0] 80 | assert req.event_message_rev == 4 81 | assert req.sensor_type == 0xf2 82 | assert req.sensor_number == 1 83 | assert req.event_type.type == 0x6f 84 | assert req.event_type.dir == 0 85 | assert req.event_data == [0, 0xff, 0xff] 86 | 87 | def test_get_sensor_thresholds(self): 88 | 89 | rsp = GetSensorThresholdsRsp() 90 | rsp.readable_mask.lnc = 1 91 | rsp.readable_mask.lcr = 1 92 | rsp.readable_mask.lnr = 1 93 | rsp.readable_mask.unc = 1 94 | rsp.readable_mask.ucr = 1 95 | rsp.readable_mask.unr = 1 96 | rsp.threshold.lnc = 1 97 | rsp.threshold.lcr = 2 98 | rsp.threshold.lnr = 3 99 | rsp.threshold.unc = 4 100 | rsp.threshold.ucr = 5 101 | rsp.threshold.unr = 6 102 | rsp.completion_code = 0 103 | self.mock_send_recv.return_value = rsp 104 | 105 | thresholds = self.ipmi.get_sensor_thresholds(0) 106 | assert thresholds['unr'] == 6 107 | assert thresholds['ucr'] == 5 108 | assert thresholds['unc'] == 4 109 | assert thresholds['lnc'] == 1 110 | assert thresholds['lcr'] == 2 111 | assert thresholds['lnr'] == 3 112 | 113 | def test_get_sensor_reading(self): 114 | 115 | # in progress 116 | rsp = GetSensorReadingRsp() 117 | rsp.sensor_reading = 0 118 | rsp.config.initial_update_in_progress = 1 119 | self.mock_send_recv.return_value = rsp 120 | 121 | (reading, states) = self.ipmi.get_sensor_reading(0) 122 | assert reading is None 123 | assert states is None 124 | 125 | # no states 126 | rsp = GetSensorReadingRsp() 127 | rsp.sensor_reading = 0x55 128 | rsp.config.initial_update_in_progress = 0 129 | self.mock_send_recv.return_value = rsp 130 | 131 | (reading, states) = self.ipmi.get_sensor_reading(0) 132 | assert reading == 0x55 133 | assert states is None 134 | 135 | # with states 136 | rsp = GetSensorReadingRsp() 137 | rsp.sensor_reading = 0 138 | rsp.states1 = 0x55 139 | rsp.states2 = 0x55 140 | rsp.config.initial_update_in_progress = 0 141 | self.mock_send_recv.return_value = rsp 142 | 143 | (reading, states) = self.ipmi.get_sensor_reading(0) 144 | assert reading == 0 145 | assert states == 0x5555 146 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import pytest 5 | 6 | from array import array 7 | 8 | import pyipmi.msgs.device_messaging 9 | from pyipmi.utils import (ByteBuffer, chunks, check_completion_code, 10 | check_rsp_completion_code) 11 | from pyipmi.errors import DecodingError, CompletionCodeError 12 | from pyipmi.msgs import decode_message 13 | 14 | 15 | def test_bytebuffer_init_from_list(): 16 | buf = ByteBuffer([0xf8]) 17 | assert buf.array == array('B', [0xf8]) 18 | 19 | 20 | def test_bytebuffer_init_from_tuple(): 21 | buf = ByteBuffer((0xf8,)) 22 | assert buf.array == array('B', [0xf8]) 23 | 24 | 25 | def test_bytebuffer_initi_fromstring(): 26 | buf = ByteBuffer(b'\xf8') 27 | assert buf.array == array('B', [0xf8]) 28 | 29 | 30 | def test_bytebuffer_push_unsigned_int(): 31 | buf = ByteBuffer((1, 0)) 32 | buf.push_unsigned_int(255, 1) 33 | assert buf[0] == 1 34 | assert buf[1] == 0 35 | assert buf[2] == 255 36 | buf.push_unsigned_int(255, 2) 37 | assert buf[3] == 255 38 | assert buf[4] == 0 39 | buf.push_unsigned_int(256, 2) 40 | assert buf[5] == 0 41 | assert buf[6] == 1 42 | 43 | 44 | def test_bytebuffer_pop_unsigned_int(): 45 | buf = ByteBuffer((1, 0, 0, 0)) 46 | assert buf.pop_unsigned_int(1) == 1 47 | 48 | buf = ByteBuffer((0, 1, 0, 0)) 49 | assert buf.pop_unsigned_int(2) == 0x100 50 | 51 | buf = ByteBuffer((0, 0, 1, 0)) 52 | assert buf.pop_unsigned_int(3) == 0x10000 53 | 54 | buf = ByteBuffer((0, 0, 0, 1)) 55 | assert buf.pop_unsigned_int(4) == 0x1000000 56 | 57 | 58 | def test_bytebuffer_pop_unsigned_int_error(): 59 | with pytest.raises(DecodingError): 60 | buf = ByteBuffer((0, 0)) 61 | buf.pop_unsigned_int(3) 62 | 63 | 64 | def test_bytebuffer_push_string(): 65 | buf = ByteBuffer() 66 | buf.push_string(b'0123') 67 | assert buf[0] == 0x30 68 | assert buf[1] == 0x31 69 | assert buf[2] == 0x32 70 | assert buf[3] == 0x33 71 | assert buf.tostring() == b'0123' 72 | 73 | buf = ByteBuffer() 74 | buf.push_string(b'\x00\xb4') 75 | assert buf.tostring() == b'\x00\xb4' 76 | 77 | 78 | def test_bytebuffer_pop_string(): 79 | buf = ByteBuffer(b'\x30\x31\x32\x33') 80 | assert buf.pop_string(2) == b'01' 81 | assert buf.tostring() == b'23' 82 | 83 | 84 | def test_bytebuffer_tostring(): 85 | buf = ByteBuffer(b'\x30\x31\x32\x33') 86 | assert buf.tostring() == b'0123' 87 | 88 | 89 | def test_bytebuffer_pop_slice(): 90 | buf = ByteBuffer(b'\x30\x31\x32\x33') 91 | cut = buf.pop_slice(1) 92 | assert buf.tostring() == b'123' 93 | assert cut.tostring() == b'0' 94 | 95 | buf = ByteBuffer(b'\x30\x31\x32\x33') 96 | cut = buf.pop_slice(2) 97 | assert buf.tostring() == b'23' 98 | assert cut.tostring() == b'01' 99 | 100 | buf = ByteBuffer(b'\x30\x31\x32\x33') 101 | cut = buf.pop_slice(3) 102 | assert buf.tostring() == b'3' 103 | assert cut.tostring() == b'012' 104 | 105 | buf = ByteBuffer(b'\x30\x31\x32\x33') 106 | cut = buf.pop_slice(4) 107 | assert buf.tostring() == b'' 108 | assert cut.tostring() == b'0123' 109 | 110 | 111 | def test_bytebuffer_pop_slice_error(): 112 | with pytest.raises(DecodingError): 113 | buf = ByteBuffer(b'\x30\x31\x32\x33') 114 | buf.pop_slice(5) 115 | 116 | 117 | def test_chunks(): 118 | data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 119 | result = list() 120 | for chunk in chunks(data, 2): 121 | result.extend(chunk) 122 | assert len(chunk) == 2 123 | assert result == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 124 | 125 | 126 | def test_check_completion_code_ok(): 127 | check_completion_code(0) 128 | 129 | 130 | def test_check_specific_completion_code_ok(): 131 | rsp = pyipmi.msgs.device_messaging.SetUserPasswordRsp() 132 | decode_message(rsp, b'\x00') 133 | check_rsp_completion_code(rsp) 134 | 135 | 136 | def test_check_completion_code_desc(): 137 | with pytest.raises(CompletionCodeError) as ex: 138 | check_completion_code(0xc1) 139 | assert ex.value.cc == 0xc1 140 | assert ex.value.cc_desc == "Invalid Command" 141 | 142 | 143 | def test_check_completion_code_unknown_desc(): 144 | with pytest.raises(CompletionCodeError) as ex: 145 | check_completion_code(0x81) 146 | assert ex.value.cc == 0x81 147 | assert ex.value.cc_desc == "Unknown error description" 148 | 149 | 150 | def test_check_rsp_completion_code_desc(): 151 | rsp = pyipmi.msgs.device_messaging.SetUserPasswordRsp() 152 | decode_message(rsp, b'\x81') 153 | with pytest.raises(CompletionCodeError) as ex: 154 | check_rsp_completion_code(rsp) 155 | assert rsp.cmdid == 71 156 | assert rsp.netfn == 6 | 1 157 | assert rsp.group_extension is None 158 | assert ex.value.cc == 0x81 159 | assert ex.value.cc_desc == "password test failed. Wrong password size was used." 160 | 161 | 162 | def test_check_rsp_completion_code_unknown_desc(): 163 | rsp = pyipmi.msgs.device_messaging.SetUserPasswordRsp() 164 | decode_message(rsp, b'\x42') 165 | with pytest.raises(CompletionCodeError) as ex: 166 | check_rsp_completion_code(rsp) 167 | assert ex.value.cc == 0x42 168 | assert ex.value.cc_desc == "Unknown error description" 169 | --------------------------------------------------------------------------------