Вы можете добавить в 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"}.
Если при работе обработчика возможны ошибки, напишите для них исключения.
Логирование методов
Рекомендуем логировать выполнение методов: это поможет при отладке обработчика. Для этого:
- Добавьте в файл обработчика строку:
import logging - В методах обработчика используйте функции logging.info, logging.debug, logging.warning, logging.error и т.д.
Пример кода обработчика
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()
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")
Загрузка обработчика в платформу
-
Создайте директорию со следующей структурой:
handler_dir/ ├── __init__.py └── my_handler.pyПояснения -
Создайте архив tar.gz с этой директорией:
tar -czvf custom_handler.tar.gz handler_dirПояснения к команде -
Авторизуйтесь в 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 — токена авторизации.
-
Создайте описание для обработчика:
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} -
Загрузите архив с обработчиком в платформу:
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>"Пояснения к команде -
Задайте поля
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, содержащая файлы из архива обработчика.
Связанные статьи: