DCImanager 6
en En
es Es

Creating a BMC handler

You can add your own BMC handler to DCImanager 6. To do this, create the handler code and upload it to the platform.

Preparing the environment

The BMC handler must be written in Python. We recommend using Python version 3.9.

We recommend creating a handler based on an existing project. To copy a project, connect to the DCImanager 6 location server via SSH and run the command: 

docker cp eservice_handler:/opt/ispsystem/equip_handler ./

The project directories may be useful when creating a handler:

  • /common – common helper classes and functions.
  • /ipmi_common – helper classes and functions for working with BMC.
  • /ipmi_common/handlers – BMC handlers.

You can see the required Python libraries and their versions in the project requirements.txt file. To install the required libraries, run the command: 

pip3 install -r requirements.txt

To check the data types in the project, we recommend using the mypy analyzer.

Creating the handler

The platform uses handlers to interact with BMC via two protocols:

  • IPMI – uses the ipmitool utility.
  • Redfish – uses the REST API.

The handler must inherit from base classes:

  • From BaseIpmi — for IPMI.
  • Simultaneously from BaseRedfishIpmi and BaseIpmi — for Redfish.

BaseRedfishIpmi is a class that has get, put, post, patch, and delete methods for making corresponding HTTP requests to a BMC with a Redfish interface.

The IpmiData class is also used to create an instance of the BMC handler. It contains information about the BMC:

  • IP address;
  • username;
  • password;
  • other parameters.

The IpmiData class has attributes:

  • device – specifies the name of the custom handler, for example, "my_handler".
  • custom_params – can be used to pass any parameters required for the handler's operation.

The handler file must contain a function that returns a handler instance:

def make_handler(ipmi_data: IpmiData) -> BaseIpmi:
    """Make handler instance"""
    return MyHandler(ipmi_data)

If any handler function needs to remain unimplemented, you can:

  • write an exception. For example: NotImplementedError("Is not supported by this handler");
  • return a result with a message about the unsupported operation. For example: return {"result": "Operation is unsupported"}.

You can raise exceptions in handler methods if an error occurs during method execution.

Method Logging

We recommend logging method execution to help with handler debugging. To do this:

  1. Add the following line to the handler file:

    import logging
  2. Use functions like logging.info, logging.debug, logging.warning, logging.error, etc., in the handler methods.

Handler code example

IMPI handler example code Expand source
from typing import Optional

import response_examples

from ipmi_common.base_ipmi import (
    BaseIpmi,
    IpmiListResult,
    IpmiResult,
    LogInfoType,
    SdrParseType,
)
from ipmi_common.helper import (
    BmcUsers,
    BootorderData,
    DNSServers,
    FruInfoView,
    IpmiConfigureData,
    IpmiData,
    IpmiUserData,
    IsoData,
    LdapData,
    NTPServers,
    ServerProfileSettings,
    SslData,
)


def make_handler(ipmi_data: IpmiData) -> BaseIpmi:
    """
    Returns common IPMI handler object
    :param ipmi_data: IPMI data object
    :return:
    """

    return IpmiExampleHandler(ipmi_data)


class IpmiExampleHandler(BaseIpmi):
    """Ipmi common work class"""

    def status(self) -> IpmiResult:
        """Get IPMI status"""

        return IpmiResult()

    def power_on(self) -> IpmiResult:
        """Turn on server"""

        return IpmiResult()

    def power_off(self) -> IpmiResult:
        """Turn off server"""

        return IpmiResult()
    def power_reset(self) -> IpmiResult:
        """Power reset"""

        return IpmiResult()

    def change_bootdev(self, bootorder: BootorderData) -> IpmiResult:
        """Change server boot order"""

        return IpmiResult()

    def user_status(self) -> IpmiListResult:
        """Get IPMI user status"""

        return IpmiListResult()

    def user_modify(self, user: IpmiUserData) -> IpmiResult:
        """Get IPMI user status"""

        return IpmiResult()

    def user_delete(self, user: IpmiUserData) -> IpmiResult:
        """Delete IPMI user"""

        return IpmiResult()

    def get_sensors_info(self) -> dict[str, SdrParseType]:
        """Get detailed sensors info"""

        return response_examples.SENSOR_INFO_EXAMPLE

    def get_sensors_values(self) -> dict[str, SdrParseType]:
        """Get IPMI sensors values"""

        return {}

    def get_log(self, log_format: str, log_info: LogInfoType) -> dict[str, dict]:
        """Get IPMI log"""

        return {}

    def configure(self, configure_data: IpmiConfigureData) -> dict[str, str]:
        """Configure IPMI.

        Change main IP-adress, mac and gateway of the IPMI.
        Args:
            configure_data (IpmiConfigureData): IPMI configure data

        Returns:
            dict: Configure result
        """

        return {}

    def get_fru(self) -> FruInfoView:
        """Get IPMI fru info"""

        return FruInfoView()

    def get_component_info(self) -> IpmiResult:
        """Get server components info"""

        raise NotImplementedError("Is not supported by this handler")

    def mount_iso(self, iso_data: IsoData) -> IpmiResult:
        """Mount ISO in BMC"""

        raise NotImplementedError("Is not supported by this handler")

    def unmount_iso(self) -> IpmiResult:
        """Unmount ISO in BMC"""

        raise NotImplementedError("Is not supported by this handler")

    def configure_hostname(self, hostname: str) -> IpmiResult:
        """Configures server hostname"""

        raise NotImplementedError("Is not supported by this handler")

    def set_ssl(self, ssl: SslData) -> IpmiResult:
        """Set ssl in BMC"""

        raise NotImplementedError("Is not supported by this handler")

    def set_power_restore_policy(self, power_restore_policy: str) -> IpmiResult:
        """Set Power Restore Policy in BMC"""

        raise NotImplementedError("Is not supported by this handler")

    def set_datetime(self, datetime: str) -> IpmiResult:
        """Set DateTime in BMC"""

        raise NotImplementedError("Is not supported by this handler")

    def set_ntp_servers(self, ntp_servers: NTPServers) -> IpmiResult:
        """Set NTP servers in BMC"""

        raise NotImplementedError("Is not supported by this handler")

    def set_dns_servers(self, dns_servers: DNSServers) -> IpmiResult:
        """Set DNS servers in BMC"""

        raise NotImplementedError("Is not supported by this handler")

    def set_ldap(self, ldap_data: LdapData) -> IpmiResult:
        """Set LDAP params in BMC"""

        raise NotImplementedError("Is not supported by this handler")

    def set_bmc_users(self, users: BmcUsers) -> IpmiResult:
        """Set BMC users"""

        raise NotImplementedError("Is not supported by this handler")

    def get_server_profile_info(self, settings: ServerProfileSettings) -> IpmiResult:
        """Get actual server profile info"""

        raise NotImplementedError("Is not supported by this handler")

    def bmc_reset(self, reset_type: Optional[str] = None) -> IpmiResult:
        """BMC reset"""

        return IpmiResult()

Redfish handler example code Expand source
from typing import Optional

from ipmi_common.base_ipmi import (
    BaseIpmi,
    IpmiListResult,
    IpmiResult,
    LogInfoType,
    SdrParseType,
)
from ipmi_common.helper import (
    BmcUsers,
    BootorderData,
    DNSServers,
    FirmwareUpdateParams,
    FirmwareWaitParams,
    FruInfoView,
    IpmiConfigureData,
    IpmiData,
    IpmiUserData,
    IsoData,
    LdapData,
    NTPServers,
    ServerProfileSettings,
    SslData,
)
from ipmi_common.base_redfish_ipmi import (
    BaseRedfishInfo,
    BaseRedfishIpmi,
)


class RedfishExampleHandler(BaseRedfishIpmi, BaseIpmi):
    """IPMI Redfish v1 work class"""

    def __init__(self, ipmi_data: IpmiData, base_info: Optional[BaseRedfishInfo] = None):
        super().__init__(ipmi_data, base_info)
        BaseIpmi.__init__(self, ipmi_data)

    def status(self) -> IpmiResult:
        """Get IPMI status"""

        return IpmiResult()

    def power_on(self) -> IpmiResult:
        """Turn on server"""

        return IpmiResult()

    def power_off(self) -> IpmiResult:
        """Turn off server"""

        return IpmiResult()

    def power_reset(self) -> IpmiResult:
        """Power reset"""

        return IpmiResult

    def user_status(self) -> IpmiListResult:
        """Get IPMI user status"""

        return IpmiListResult()

    def user_modify(self, user: IpmiUserData) -> IpmiResult:
        """Get IPMI user status"""

        return IpmiResult()

    def user_delete(self, user: IpmiUserData) -> IpmiResult:
        """Delete IPMI user"""

        return IpmiResult()

    def get_sensors_info(self) -> dict[str, SdrParseType]:
        """Get detailed sensors info"""

        return {}

    def get_sensors_values(self) -> dict[str, SdrParseType]:
        """Get IPMI sensors values"""

        return {}

    def get_fru(self) -> FruInfoView:
        """Get IPMI fru info"""

        return FruInfoView()

    def get_log(self, log_format: str, log_info: LogInfoType) -> dict[str, dict]:
        """Get IPMI log"""

        return {}

    def get_component_info(self) -> IpmiResult:
        """Get server components info"""

        return IpmiResult()


    def change_bootdev(self, bootorder: BootorderData) -> IpmiResult:
        """Change server boot order"""

        raise NotImplementedError("Is not supported by this handler")

    def configure(self, configure_data: IpmiConfigureData) -> dict[str, str]:
        """Configure IPMI.

        Change main IP-adress, mac and gateway of the IPMI.
        Args:
            configure_data (IpmiConfigureData): IPMI configure data

        Returns:
            dict: Configure result
        """

        raise NotImplementedError("Is not supported by this handler")

    def mount_iso(self, iso_data: IsoData) -> IpmiResult:
        """Mount ISO in BMC"""

        raise NotImplementedError("Is not supported by this handler")

    def unmount_iso(self) -> IpmiResult:
        """Unmount ISO in BMC"""

        raise NotImplementedError("Is not supported by this handler")

    def configure_hostname(self, hostname: str) -> IpmiResult:
        """Configures server hostname"""

        raise NotImplementedError("Is not supported by this handler")

    def set_ssl(self, ssl: SslData) -> IpmiResult:
        """Set ssl in BMC"""

        raise NotImplementedError("Is not supported by this handler")

    def set_power_restore_policy(self, power_restore_policy: str) -> IpmiResult:
        """Set Power Restore Policy in BMC"""

        raise NotImplementedError("Is not supported by this handler")

    def set_datetime(self, datetime: str) -> IpmiResult:
        """Set DateTime in BMC"""

        raise NotImplementedError("Is not supported by this handler")

    def set_ntp_servers(self, ntp_servers: NTPServers) -> IpmiResult:
        """Set NTP servers in BMC"""

        raise NotImplementedError("Is not supported by this handler")

    def set_dns_servers(self, dns_servers: DNSServers) -> IpmiResult:
        """Set DNS servers in BMC"""

        raise NotImplementedError("Is not supported by this handler")

    def set_ldap(self, ldap_data: LdapData) -> IpmiResult:
        """Set LDAP params in BMC"""

        raise NotImplementedError("Is not supported by this handler")

    def set_bmc_users(self, users: BmcUsers) -> IpmiResult:
        """Set BMC users"""

        raise NotImplementedError("Is not supported by this handler")

    def get_server_profile_info(self, settings: ServerProfileSettings) -> IpmiResult:
        """Get actual server profile info"""

        raise NotImplementedError("Is not supported by this handler")

    def bmc_reset(self, reset_type: Optional[str] = None) -> IpmiResult:
        """BMC reset"""

        raise NotImplementedError("Is not supported by this handler")

    def firmware_update(  # pylint: disable=no-self-use,unused-argument
        self, params: FirmwareUpdateParams
    ) -> IpmiResult:
        """Upload and process firmware"""

        raise NotImplementedError("Is not supported by this handler")

    def firmware_wait(self, params: FirmwareWaitParams) -> IpmiResult:  # pylint: disable=no-self-use,unused-argument
        """Wait firmware updating status"""

        raise NotImplementedError("Is not supported by this handler")

    def get_bios_attributes(self) -> IpmiResult:  # pylint: disable=no-self-use
        """Returns BIOS attributes"""

        raise NotImplementedError("Is not supported by this handler")

    def update_bios_attributes(  # pylint: disable=no-self-use,unused-argument
        self, bios_attributes: dict
    ) -> IpmiResult:
        """Updates BIOS attributes"""

        raise NotImplementedError("Is not supported by this handler")

Loading the handler into the platform

To load the handler into the platform:

  1. Create a directory with the following structure:

    handler_dir/
    ├── __init__.py
    └── my_handler.py
    Comments
  2. Create a tar.gz archive with the following directory:

    tar -czvf custom_handler.tar.gz handler_dir
    Comments to the command
  3. Log in to DCImanager 6 with administrator permissions:

    curl -o- -k https://example.com/api/auth/v4/public/token \
        -H "isp-box-instance: true" \
        -d '{
            "email": "<admin_email>",
            "password": "<admin_pass>"
        }'
    
    Comments to the command

    A response message in the format below will be received:

    {"id":"24","token":"24-cee181d2-ccaa-4b64-a229-234aa7a25db6"}

     Save the value of the token parameter from the received response.

  4. Create a description for the handler:

    curl -o- -k https://example.com/api/eservice/v3/custom_equipment \
        -H "isp-box-instance: true" \
        -H "x-xsrf-token: <token>" \
        -d '{
            "device_type": "ipmi",
            "handler": "<internal_handler_name>",
            "name": "<handler_name>",
            "protocol": ["<handler_protocol>"],
        }'
    
    Comments to the command

    The response will contain the id of the created handler. Save this value.

    {"id": 1}
  5. Load the archive with the handler into the platform:

    curl -o- -k https://example.com/api/eservice/v3/custom_equipment/<handler_id>/content \
        -H "isp-box-instance: true" \
        -H "x-xsrf-token: <token>" \
        -F "data=@custom_handler.tar.gz" \
        -F "handler_import=<import_path>"
    Comments to the command
     You can also use this command to upload new handler versions to the platform.
  6. Set the device and custom_params fields for IPMI in dci_back:
    curl -o- -k https://example.com/dci/v3/ipmi/{ipmi_id} \
        -H "isp-box-instance: true" \
        -H "x-xsrf-token: <token>" \
        -d '{"custom_params" : <custom_handler_params>, "device" : <custom_handler_name>}'
    Click here to expand...

    If the query is successful, you'll get a reply in the form of:

    {"id": 1}

After that, you can use the custom handler to manage the BMC. To do this, start the BMC poll in the DCImanager graphical interface or execute the request:

curl 'https://example.com/dci/v3/ipmi/1/status' \
-H "isp-box-instance: true" \
-H "x-xsrf-token: <token>" \
-d '{}'

After this, a directory named handler_dir should appear in the container of the eservice_handler service, located at /opt/ispsystem/equip_handler/custom_handlers/ipmi. This directory will contain the files from the handler archive.