ipmi gpio driver uses ipmitool

This commit is contained in:
Devaev Maxim
2021-04-09 06:20:51 +03:00
parent 312605a70f
commit 1ca341069d
3 changed files with 71 additions and 29 deletions

View File

@@ -69,6 +69,7 @@ depends=(
iptables iptables
iproute2 iproute2
dnsmasq dnsmasq
ipmitool
"raspberrypi-io-access>=0.5" "raspberrypi-io-access>=0.5"
"ustreamer>=3.20" "ustreamer>=3.20"

View File

@@ -21,21 +21,24 @@
import asyncio import asyncio
import functools
from typing import List
from typing import Dict from typing import Dict
from typing import Optional from typing import Optional
from pyghmi.ipmi.command import Command as IpmiCommand
from ...logging import get_logger from ...logging import get_logger
from ... import tools
from ... import aiotools from ... import aiotools
from ... import aioproc
from ...yamlconf import Option from ...yamlconf import Option
from ...validators.basic import valid_float_f01 from ...validators.basic import valid_float_f01
from ...validators.net import valid_ip_or_host from ...validators.net import valid_ip_or_host
from ...validators.net import valid_port from ...validators.net import valid_port
from ...validators.os import valid_command
from . import GpioDriverOfflineError from . import GpioDriverOfflineError
from . import BaseUserGpioDriver from . import BaseUserGpioDriver
@@ -45,14 +48,15 @@ from . import BaseUserGpioDriver
_OUTPUTS = { _OUTPUTS = {
1: "on", 1: "on",
2: "off", 2: "off",
3: "shutdown", 3: "cycle",
4: "reset", 4: "reset",
5: "boot", 5: "diag",
6: "soft",
} }
# ===== # =====
class Plugin(BaseUserGpioDriver): class Plugin(BaseUserGpioDriver): # pylint: disable=too-many-instance-attributes
def __init__( # pylint: disable=super-init-not-called def __init__( # pylint: disable=super-init-not-called
self, self,
instance_name: str, instance_name: str,
@@ -62,6 +66,10 @@ class Plugin(BaseUserGpioDriver):
port: int, port: int,
user: str, user: str,
passwd: str, passwd: str,
passwd_env: str,
cmd: List[str],
state_poll: float, state_poll: float,
) -> None: ) -> None:
@@ -71,6 +79,10 @@ class Plugin(BaseUserGpioDriver):
self.__port = port self.__port = port
self.__user = user self.__user = user
self.__passwd = passwd self.__passwd = passwd
self.__passwd_env = passwd_env
self.__cmd = cmd
self.__state_poll = state_poll self.__state_poll = state_poll
self.__online = False self.__online = False
@@ -79,10 +91,20 @@ class Plugin(BaseUserGpioDriver):
@classmethod @classmethod
def get_plugin_options(cls) -> Dict: def get_plugin_options(cls) -> Dict:
return { return {
"host": Option("", type=valid_ip_or_host), "host": Option("", type=valid_ip_or_host),
"port": Option(623, type=valid_port), "port": Option(623, type=valid_port),
"user": Option(""), "user": Option(""),
"passwd": Option(""), "passwd": Option(""),
"passwd_env": Option("IPMI_PASSWORD"),
"cmd": Option([
"/usr/bin/ipmitool",
"-I", "lanplus",
"-U", "{user}", "-E",
"-H", "{host}", "-p", "{port}",
"power", "{action}",
], type=valid_command),
"state_poll": Option(1.0, type=valid_float_f01), "state_poll": Option(1.0, type=valid_float_f01),
} }
@@ -102,7 +124,7 @@ class Plugin(BaseUserGpioDriver):
async def run(self) -> None: async def run(self) -> None:
prev = (False, False) prev = (False, False)
while True: while True:
await aiotools.run_async(self.__update_power) await self.__update_power()
new = (self.__online, self.__power) new = (self.__online, self.__power)
if new != prev: if new != prev:
await self._notifier.notify() await self._notifier.notify()
@@ -112,40 +134,58 @@ class Plugin(BaseUserGpioDriver):
def cleanup(self) -> None: def cleanup(self) -> None:
pass pass
def read(self, pin: int) -> bool: async def read(self, pin: int) -> bool:
if not self.__online: if not self.__online:
raise GpioDriverOfflineError(self) raise GpioDriverOfflineError(self)
if pin == 0: if pin == 0:
return self.__power return self.__power
return False return False
def write(self, pin: int, state: bool) -> None: async def write(self, pin: int, state: bool) -> None:
if not self.__online: if not self.__online:
raise GpioDriverOfflineError(self) raise GpioDriverOfflineError(self)
request = _OUTPUTS[pin] action = _OUTPUTS[pin]
try: try:
self.__make_command().set_power(request) proc = await aioproc.log_process(**self.__make_ipmitool_kwargs(action), logger=get_logger(0))
except Exception: if proc.returncode != 0:
get_logger(0).exception("Can't send IPMI power-%s request to %s:%d", request, self.__host, self.__port) raise RuntimeError(f"Error while ipmitool execution: pid={proc.pid}; retcode={proc.returncode}")
except Exception as err:
get_logger(0).error("Can't send IPMI power-%s request to %s:%d: %s", action, self.__host, self.__port, tools.efmt(err))
raise GpioDriverOfflineError(self) raise GpioDriverOfflineError(self)
# ===== # =====
def __update_power(self) -> None: async def __update_power(self) -> None:
try: try:
self.__power = (self.__make_command().get_power()["powerstate"] == "on") (proc, text) = await aioproc.read_process(**self.__make_ipmitool_kwargs("status"))
self.__online = True if proc.returncode != 0:
except Exception: raise RuntimeError(f"Error while ipmitool execution: pid={proc.pid}; retcode={proc.returncode}")
self.__online = self.__power = False stripped = text.strip()
get_logger(0).exception("Can't fetch IPMI power status from %s:%d", self.__host, self.__port) if stripped.startswith("Chassis Power is "):
self.__power = (stripped != "Chassis Power is off")
self.__online = True
return
raise RuntimeError(f"Invalid ipmitool response: {text}")
except Exception as err:
get_logger(0).error("Can't fetch IPMI power status from %s:%d: %s", self.__host, self.__port, tools.efmt(err))
self.__power = False
self.__online = False
def __make_command(self) -> IpmiCommand: @functools.lru_cache()
return IpmiCommand( def __make_ipmitool_kwargs(self, action: str) -> Dict:
bmc=self.__host, return {
port=self.__port, "cmd": [
userid=(self.__user or None), part.format(
password=(self.__passwd or None), host=self.__host,
) port=self.__port,
user=self.__user,
passwd=self.__passwd,
action=action,
)
for part in self.__cmd
],
"env": ({self.__passwd_env: self.__passwd} if self.__passwd and self.__passwd_env else None),
}
def __str__(self) -> str: def __str__(self) -> str:
return f"IPMI({self._instance_name})" return f"IPMI({self._instance_name})"

View File

@@ -36,6 +36,7 @@ RUN pacman -Syu --noconfirm \
python-hidapi \ python-hidapi \
freetype2 \ freetype2 \
nginx-mainline \ nginx-mainline \
ipmitool \
socat \ socat \
eslint \ eslint \
npm \ npm \