Режим фокусировки

Создание обработчика BMC

Вы можете добавить в DCImanager 6 собственный обработчик BMC. Для этого создайте код обработчика и загрузите его в платформу.

Подготовка окружения

Обработчик BMC должен быть написан на языке Python. Рекомендуем использовать версию Python 3.9.

Рекомендуем создать обработчик на основе существующего проекта. Чтобы скопировать проект, подключитесь к серверу-локации DCImanager 6 по SSH и выполните команду:

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

При создании обработчика могут быть полезны директории проекта:

  • /common — общие вспомогательные классы и функции;
  • /ipmi_common — вспомогательные классы и функции для работы с BMC;
  • /ipmi_common/handlers — обработчики BMC.

Требуемые библиотеки Python и их версии вы можете посмотреть в файле проекта requirements.txt. Чтобы установить нужные библиотеки, выполните команду:

pip3 install -r requirements.txt

Для проверки типов данных в проекте рекомендуется использовать анализатор mypy.

Создание обработчика

Платформа использует обработчики для взаимодействия с BMC через два протокола:

  • IPMI — использует утилиту ipmitool;
  • Redfish — использует REST API.

Обработчик должен наследоваться от базовых классов:

  • от BaseIpmi — для IPMI;
  • одновременно от BaseRedfishIpmi и BaseIpmi — для Redfish.

BaseRedfishIpmi — класс, у которого есть методы get, put, post, patch и delete для выполнения соответствующих HTTP запросов к BMC с интерфейсом Redfish.

Для создания экземпляра обработчика BMC также используется класс IpmiData, который содержит информацию о BMC:

  • IP-адрес;
  • имя пользователя;
  • пароль;
  • другие параметры.

В классе IpmiData есть атрибуты:

  • device, который отвечает за имя пользовательского обработчика, например "my_handler";
  • custom_params — в него вы можете передать любые параметры, которые могут быть необходимы для работы обработчика.

Файл обработчика должен содержать функцию, которая возвращает экземпляр обработчика:

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

Если какая-либо функция обработчика должна остаться нереализованной, то вы можете:

  • написать исключение. Например, NotImplementedError("Is not supported by this handler");
  • вернуть результат с сообщением о неподдерживаемой операции. Например, return {"result": "Operation is unsupported"}.

Если при работе обработчика возможны ошибки, напишите для них исключения.

Логирование методов

Рекомендуем логировать выполнение методов: это поможет при отладке обработчика. Для этого:

  1. Добавьте в файл обработчика строку:
    import logging
  2. В методах обработчика используйте функции logging.info, logging.debug, logging.warning, logging.error и т.д.

Пример кода обработчика

Пример кода обработчика для IMPI 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 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")

Загрузка обработчика в платформу

  1. Создайте директорию со следующей структурой:

    handler_dir/
    ├── __init__.py
    └── my_handler.py
    Пояснения
  2. Создайте архив tar.gz с этой директорией: 

    tar -czvf custom_handler.tar.gz handler_dir
    Пояснения к команде
  3. Авторизуйтесь в DCImanager 6 с правами администратора: 

    curl -o- -k https://example.com/api/auth/v4/public/token \
        -H "isp-box-instance: true" \
        -d '{
            "email": "<admin_email>",
            "password": "<admin_pass>"
        }'
    
    Пояснения к команде

    В ответ придёт сообщение вида: 

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

    Сохраните из полученного ответа значение параметра token — токена авторизации.

  4. Создайте описание для обработчика: 

    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>"],
        }'
    Пояснения к команде

    Ответ будет содержать id созданного обработчика. Сохраните это значение.

    Пример ответа
    {"id": 1}
  5. Загрузите архив с обработчиком в платформу: 

    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>"
    Пояснения к команде
  6. Задайте поля device и custom_params для IPMI в 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>}'
    Пояснения к команде

    В случае успешного запроса вернётся ответ вида:

    {"id": 1}

После этого вы можете использовать пользовательский обработчик для управления BMC. Для этого запустите опрос BMC в графическом интерфейсе DCImanager или выполните запрос:

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

После этого в контейнере eservice_handler в директории /opt/ispsystem/equip_handler/custom_handlers/ipmi должна появиться директория handler_dir, содержащая файлы из архива обработчика.