From a1730c57520bee2d870950d45f0d5fd4dd039c7c Mon Sep 17 00:00:00 2001 From: Sebastian Goscik Date: Mon, 16 Oct 2023 18:12:31 +0100 Subject: [PATCH] Add IPMI Driver Initially supporting power control Signed-off-by: Sebastian Goscik --- doc/configuration.rst | 40 ++++++++++++++++++++ labgrid/driver/__init__.py | 1 + labgrid/driver/ipmi.py | 73 ++++++++++++++++++++++++++++++++++++ labgrid/remote/client.py | 4 +- labgrid/remote/exporter.py | 21 +++++++++++ labgrid/resource/__init__.py | 1 + labgrid/resource/ipmi.py | 22 +++++++++++ 7 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 labgrid/driver/ipmi.py create mode 100644 labgrid/resource/ipmi.py diff --git a/doc/configuration.rst b/doc/configuration.rst index 4a3ffea66..20eda6042 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -1261,6 +1261,28 @@ Arguments: Used by: - none +IPMIInterface +~~~~~~~~~~~~~ +:any:`IPMIInterface` describes an IPMI interface of a device. + +.. code-block:: yaml + + IPMIInterface: + host: 192.168.0.100 + username: admin + password: admin + + +Arguments: + - host (str): address for IPMI interface + - username (str): IPMI session username + - password (str): IPMI session password + - port (int): network port for IPMI (Default=623) + - interface (str): The IPMI interface type to use (Default=lanplus) + +Used by: + - `IMPIDriver`_ + Providers ~~~~~~~~~ Providers describe directories that are accessible by the target over a @@ -3281,6 +3303,24 @@ Implements: Arguments: - None +IMPIDriver +~~~~~~~~~~~~~~ +A :any:`IMPIDriver` controls a `IPMIInterface`_ using `ipmitool` + +Currently it supports: + - Power Control + +Binds to: + hub: + - `IPMIInterface`_ + +Implements: + - :any:`PowerProtocol` + - :any:`ResetProtocol` + +Arguments: + - None + .. _conf-strategies: Strategies diff --git a/labgrid/driver/__init__.py b/labgrid/driver/__init__.py index 721256bbf..a9d248012 100644 --- a/labgrid/driver/__init__.py +++ b/labgrid/driver/__init__.py @@ -48,3 +48,4 @@ from .deditecrelaisdriver import DeditecRelaisDriver from .dediprogflashdriver import DediprogFlashDriver from .httpdigitaloutput import HttpDigitalOutputDriver +from .ipmi import IMPIDriver diff --git a/labgrid/driver/ipmi.py b/labgrid/driver/ipmi.py new file mode 100644 index 000000000..685c61f3b --- /dev/null +++ b/labgrid/driver/ipmi.py @@ -0,0 +1,73 @@ +import subprocess + +import attr + +from ..factory import target_factory +from ..protocol import PowerProtocol +from ..step import step +from ..util.proxy import proxymanager +from .common import Driver +from .powerdriver import PowerResetMixin + + +@target_factory.reg_driver +@attr.s(eq=False) +class IMPIDriver(Driver, PowerResetMixin, PowerProtocol): + """IMPIDriver - Driver to interface with a device via its IPMI interface. + + Currently supports: + - Power Control + """ + + bindings = {"interface": {"IPMIInterface"}} + + def __attrs_post_init__(self): + super().__attrs_post_init__() + if self.target.env: + self.tool = self.target.env.config.get_tool("ipmitool") + else: + self.tool = "ipmitool" + + host, port = proxymanager.get_host_and_port(self.interface) + + self._base_command = [ + self.tool, + "-I", + self.interface.interface, + "-H", + host, + "-p", + str(port), + "-U", + self.interface.username, + "-P", + self.interface.password, + ] + + @Driver.check_active + @step() + def on(self): + subprocess.run([*self._base_command, "power", "on"], check=True, timeout=30) + + @Driver.check_active + @step() + def off(self): + subprocess.run([*self._base_command, "power", "off"], check=True, timeout=30) + + @Driver.check_active + @step() + def cycle(self): + subprocess.run([*self._base_command, "power", "cycle"], check=True, timeout=30) + + @Driver.check_active + @step() + def get(self): + output = subprocess.run( + [*self._base_command, "power", "status"], check=True, timeout=30, capture_output=True, text=True + ) + if output.stdout == "Chassis Power is off\n": + return False + elif output.stdout == "Chassis Power is on\n": + return True + else: + raise ValueError(f"Got unexpected IPMI power status: '{output}'") diff --git a/labgrid/remote/client.py b/labgrid/remote/client.py index 5ab4f0683..4e1b33fa8 100755 --- a/labgrid/remote/client.py +++ b/labgrid/remote/client.py @@ -853,7 +853,7 @@ def power(self): target = self._get_target(place) from ..resource.power import NetworkPowerPort, PDUDaemonPort from ..resource.remote import NetworkUSBPowerPort, NetworkSiSPMPowerPort - from ..resource import TasmotaPowerPort, NetworkYKUSHPowerPort + from ..resource import TasmotaPowerPort, NetworkYKUSHPowerPort, IPMIInterface drv = None try: @@ -874,6 +874,8 @@ def power(self): drv = self._get_driver_or_new(target, "TasmotaPowerDriver", name=name) elif isinstance(resource, NetworkYKUSHPowerPort): drv = self._get_driver_or_new(target, "YKUSHPowerDriver", name=name) + elif isinstance(resource, IPMIInterface): + drv = self._get_driver_or_new(target, "IMPIDriver", name=name) if drv: break diff --git a/labgrid/remote/exporter.py b/labgrid/remote/exporter.py index 7831ef8a7..94ec8f28c 100755 --- a/labgrid/remote/exporter.py +++ b/labgrid/remote/exporter.py @@ -696,6 +696,27 @@ def _get_params(self): exports["NetworkService"] = NetworkServiceExport +@attr.s +class IPMIInterfaceExport(ResourceExport): + """ResourceExport for a IPMIInterface""" + + def __attrs_post_init__(self): + super().__attrs_post_init__() + from ..resource.ipmi import IPMIInterface + + self.data["cls"] = "IPMIInterface" + self.local = IPMIInterface(target=None, name=None, **self.local_params) + + def _get_params(self): + """Helper function to return parameters""" + return { + **self.local_params, + } + + +exports["IPMIInterface"] = IPMIInterfaceExport + + @attr.s class HTTPVideoStreamExport(ResourceExport): """ResourceExport for an HTTPVideoStream""" diff --git a/labgrid/resource/__init__.py b/labgrid/resource/__init__.py index dd7554dff..c0cf74852 100644 --- a/labgrid/resource/__init__.py +++ b/labgrid/resource/__init__.py @@ -47,3 +47,4 @@ from .httpdigitalout import HttpDigitalOutput from .sigrok import SigrokDevice from .fastboot import AndroidNetFastboot +from .ipmi import IPMIInterface diff --git a/labgrid/resource/ipmi.py b/labgrid/resource/ipmi.py new file mode 100644 index 000000000..10aa417cc --- /dev/null +++ b/labgrid/resource/ipmi.py @@ -0,0 +1,22 @@ +import attr + +from ..factory import target_factory +from .common import NetworkResource + + +@target_factory.reg_resource +@attr.s(eq=False) +class IPMIInterface(NetworkResource): + """This resource describes a IPMI interface. + + Args: + host (str): address for IPMI interface + username (str): IPMI session username + password (str): IPMI session password + port (int): network port for IPMI (Default=623) + interface (str): The IPMI interface type to use (Default=lanplus)""" + + username = attr.ib(validator=attr.validators.instance_of(str)) + password = attr.ib(validator=attr.validators.instance_of(str)) + port = attr.ib(default=623, validator=attr.validators.instance_of(int)) + interface = attr.ib(default="lanplus", validator=attr.validators.instance_of(str))