summaryrefslogtreecommitdiff
path: root/kvmd
diff options
context:
space:
mode:
authorDevaev Maxim <[email protected]>2021-04-09 06:20:51 +0300
committerDevaev Maxim <[email protected]>2021-04-09 06:20:51 +0300
commit1ca341069d81a536c95c20467e21c983057af35f (patch)
tree3c23ad3ce7ca4307f35605f7cea022d84a4758db /kvmd
parent312605a70f50d430640e9f6af4017c922e4c3f76 (diff)
ipmi gpio driver uses ipmitool
Diffstat (limited to 'kvmd')
-rw-r--r--kvmd/plugins/ugpio/ipmi.py100
1 files changed, 70 insertions, 30 deletions
diff --git a/kvmd/plugins/ugpio/ipmi.py b/kvmd/plugins/ugpio/ipmi.py
index 7b1622f9..794912bd 100644
--- a/kvmd/plugins/ugpio/ipmi.py
+++ b/kvmd/plugins/ugpio/ipmi.py
@@ -21,21 +21,24 @@
import asyncio
+import functools
+from typing import List
from typing import Dict
from typing import Optional
-from pyghmi.ipmi.command import Command as IpmiCommand
-
from ...logging import get_logger
+from ... import tools
from ... import aiotools
+from ... import aioproc
from ...yamlconf import Option
from ...validators.basic import valid_float_f01
from ...validators.net import valid_ip_or_host
from ...validators.net import valid_port
+from ...validators.os import valid_command
from . import GpioDriverOfflineError
from . import BaseUserGpioDriver
@@ -45,14 +48,15 @@ from . import BaseUserGpioDriver
_OUTPUTS = {
1: "on",
2: "off",
- 3: "shutdown",
+ 3: "cycle",
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
self,
instance_name: str,
@@ -62,6 +66,10 @@ class Plugin(BaseUserGpioDriver):
port: int,
user: str,
passwd: str,
+
+ passwd_env: str,
+ cmd: List[str],
+
state_poll: float,
) -> None:
@@ -71,6 +79,10 @@ class Plugin(BaseUserGpioDriver):
self.__port = port
self.__user = user
self.__passwd = passwd
+
+ self.__passwd_env = passwd_env
+ self.__cmd = cmd
+
self.__state_poll = state_poll
self.__online = False
@@ -79,10 +91,20 @@ class Plugin(BaseUserGpioDriver):
@classmethod
def get_plugin_options(cls) -> Dict:
return {
- "host": Option("", type=valid_ip_or_host),
- "port": Option(623, type=valid_port),
- "user": Option(""),
- "passwd": Option(""),
+ "host": Option("", type=valid_ip_or_host),
+ "port": Option(623, type=valid_port),
+ "user": 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),
}
@@ -102,7 +124,7 @@ class Plugin(BaseUserGpioDriver):
async def run(self) -> None:
prev = (False, False)
while True:
- await aiotools.run_async(self.__update_power)
+ await self.__update_power()
new = (self.__online, self.__power)
if new != prev:
await self._notifier.notify()
@@ -112,40 +134,58 @@ class Plugin(BaseUserGpioDriver):
def cleanup(self) -> None:
pass
- def read(self, pin: int) -> bool:
+ async def read(self, pin: int) -> bool:
if not self.__online:
raise GpioDriverOfflineError(self)
if pin == 0:
return self.__power
return False
- def write(self, pin: int, state: bool) -> None:
+ async def write(self, pin: int, state: bool) -> None:
if not self.__online:
raise GpioDriverOfflineError(self)
- request = _OUTPUTS[pin]
+ action = _OUTPUTS[pin]
try:
- self.__make_command().set_power(request)
- except Exception:
- get_logger(0).exception("Can't send IPMI power-%s request to %s:%d", request, self.__host, self.__port)
+ proc = await aioproc.log_process(**self.__make_ipmitool_kwargs(action), logger=get_logger(0))
+ if proc.returncode != 0:
+ 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)
# =====
- def __update_power(self) -> None:
+ async def __update_power(self) -> None:
try:
- self.__power = (self.__make_command().get_power()["powerstate"] == "on")
- self.__online = True
- except Exception:
- self.__online = self.__power = False
- get_logger(0).exception("Can't fetch IPMI power status from %s:%d", self.__host, self.__port)
-
- def __make_command(self) -> IpmiCommand:
- return IpmiCommand(
- bmc=self.__host,
- port=self.__port,
- userid=(self.__user or None),
- password=(self.__passwd or None),
- )
+ (proc, text) = await aioproc.read_process(**self.__make_ipmitool_kwargs("status"))
+ if proc.returncode != 0:
+ raise RuntimeError(f"Error while ipmitool execution: pid={proc.pid}; retcode={proc.returncode}")
+ stripped = text.strip()
+ 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
+
+ @functools.lru_cache()
+ def __make_ipmitool_kwargs(self, action: str) -> Dict:
+ return {
+ "cmd": [
+ part.format(
+ 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:
return f"IPMI({self._instance_name})"