Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IPMI Driver #1567

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions doc/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions labgrid/driver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@
from .deditecrelaisdriver import DeditecRelaisDriver
from .dediprogflashdriver import DediprogFlashDriver
from .httpdigitaloutput import HttpDigitalOutputDriver
from .ipmi import IMPIDriver
73 changes: 73 additions & 0 deletions labgrid/driver/ipmi.py
Original file line number Diff line number Diff line change
@@ -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}'")
4 changes: 3 additions & 1 deletion labgrid/remote/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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

Expand Down
21 changes: 21 additions & 0 deletions labgrid/remote/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down
1 change: 1 addition & 0 deletions labgrid/resource/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@
from .httpdigitalout import HttpDigitalOutput
from .sigrok import SigrokDevice
from .fastboot import AndroidNetFastboot
from .ipmi import IPMIInterface
22 changes: 22 additions & 0 deletions labgrid/resource/ipmi.py
Original file line number Diff line number Diff line change
@@ -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))