mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-09 18:26:30 +00:00
Support dynamic device access cgroup (#3421)
* Support dynamic device access cgroup * Clean listener better * Update supervisor/docker/addon.py Co-authored-by: Stefan Agner <stefan@agner.ch> * Update addon.py * Fix black Co-authored-by: Stefan Agner <stefan@agner.ch>
This commit is contained in:
parent
724eaddf19
commit
caacb421c1
@ -12,6 +12,14 @@ from .coresys import CoreSys, CoreSysAttributes
|
|||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(slots=True, frozen=True)
|
||||||
|
class EventListener:
|
||||||
|
"""Event listener."""
|
||||||
|
|
||||||
|
event_type: BusEvent = attr.ib()
|
||||||
|
callback: Callable[[Any], Awaitable[None]] = attr.ib()
|
||||||
|
|
||||||
|
|
||||||
class Bus(CoreSysAttributes):
|
class Bus(CoreSysAttributes):
|
||||||
"""Handle Bus event system."""
|
"""Handle Bus event system."""
|
||||||
|
|
||||||
@ -34,10 +42,9 @@ class Bus(CoreSysAttributes):
|
|||||||
for listener in self._listeners.get(event, []):
|
for listener in self._listeners.get(event, []):
|
||||||
self.sys_create_task(listener.callback(reference))
|
self.sys_create_task(listener.callback(reference))
|
||||||
|
|
||||||
|
def remove_listener(self, listener: EventListener) -> None:
|
||||||
@attr.s(slots=True, frozen=True)
|
"""Unregister an listener."""
|
||||||
class EventListener:
|
try:
|
||||||
"""Event listener."""
|
self._listeners[listener.event_type].remove(listener)
|
||||||
|
except (ValueError, KeyError):
|
||||||
event_type: BusEvent = attr.ib()
|
_LOGGER.warning("Listener %s not registered", listener)
|
||||||
callback: Callable[[Any], Awaitable[None]] = attr.ib()
|
|
||||||
|
@ -13,6 +13,7 @@ import docker
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from ..addons.build import AddonBuild
|
from ..addons.build import AddonBuild
|
||||||
|
from ..bus import EventListener
|
||||||
from ..const import (
|
from ..const import (
|
||||||
DOCKER_CPU_RUNTIME_ALLOCATION,
|
DOCKER_CPU_RUNTIME_ALLOCATION,
|
||||||
ENV_TIME,
|
ENV_TIME,
|
||||||
@ -28,10 +29,19 @@ from ..const import (
|
|||||||
SECURITY_PROFILE,
|
SECURITY_PROFILE,
|
||||||
SYSTEMD_JOURNAL_PERSISTENT,
|
SYSTEMD_JOURNAL_PERSISTENT,
|
||||||
SYSTEMD_JOURNAL_VOLATILE,
|
SYSTEMD_JOURNAL_VOLATILE,
|
||||||
|
BusEvent,
|
||||||
)
|
)
|
||||||
from ..coresys import CoreSys
|
from ..coresys import CoreSys
|
||||||
from ..exceptions import CoreDNSError, DockerError, DockerNotFound, HardwareNotFound
|
from ..exceptions import (
|
||||||
|
CoreDNSError,
|
||||||
|
DBusError,
|
||||||
|
DockerError,
|
||||||
|
DockerNotFound,
|
||||||
|
HardwareNotFound,
|
||||||
|
)
|
||||||
from ..hardware.const import PolicyGroup
|
from ..hardware.const import PolicyGroup
|
||||||
|
from ..hardware.data import Device
|
||||||
|
from ..jobs.decorator import Job, JobCondition
|
||||||
from ..resolution.const import ContextType, IssueType, SuggestionType
|
from ..resolution.const import ContextType, IssueType, SuggestionType
|
||||||
from ..utils import process_lock
|
from ..utils import process_lock
|
||||||
from .const import Capabilities
|
from .const import Capabilities
|
||||||
@ -52,7 +62,9 @@ class DockerAddon(DockerInterface):
|
|||||||
def __init__(self, coresys: CoreSys, addon: Addon):
|
def __init__(self, coresys: CoreSys, addon: Addon):
|
||||||
"""Initialize Docker Home Assistant wrapper."""
|
"""Initialize Docker Home Assistant wrapper."""
|
||||||
super().__init__(coresys)
|
super().__init__(coresys)
|
||||||
self.addon = addon
|
self.addon: Addon = addon
|
||||||
|
|
||||||
|
self._hw_listener: EventListener | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def image(self) -> str | None:
|
def image(self) -> str | None:
|
||||||
@ -495,6 +507,12 @@ class DockerAddon(DockerInterface):
|
|||||||
_LOGGER.warning("Can't update DNS for %s", self.name)
|
_LOGGER.warning("Can't update DNS for %s", self.name)
|
||||||
self.sys_capture_exception(err)
|
self.sys_capture_exception(err)
|
||||||
|
|
||||||
|
# Hardware Access
|
||||||
|
if self.addon.static_devices:
|
||||||
|
self._hw_listener = self.sys_bus.register_event(
|
||||||
|
BusEvent.HARDWARE_NEW_DEVICE, self._hardware_events
|
||||||
|
)
|
||||||
|
|
||||||
def _install(
|
def _install(
|
||||||
self, version: AwesomeVersion, image: str | None = None, latest: bool = False
|
self, version: AwesomeVersion, image: str | None = None, latest: bool = False
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -636,15 +654,55 @@ class DockerAddon(DockerInterface):
|
|||||||
|
|
||||||
Need run inside executor.
|
Need run inside executor.
|
||||||
"""
|
"""
|
||||||
|
# DNS
|
||||||
if self.ip_address != NO_ADDDRESS:
|
if self.ip_address != NO_ADDDRESS:
|
||||||
try:
|
try:
|
||||||
self.sys_plugins.dns.delete_host(self.addon.hostname)
|
self.sys_plugins.dns.delete_host(self.addon.hostname)
|
||||||
except CoreDNSError as err:
|
except CoreDNSError as err:
|
||||||
_LOGGER.warning("Can't update DNS for %s", self.name)
|
_LOGGER.warning("Can't update DNS for %s", self.name)
|
||||||
self.sys_capture_exception(err)
|
self.sys_capture_exception(err)
|
||||||
|
|
||||||
|
# Hardware
|
||||||
|
if self._hw_listener:
|
||||||
|
self.sys_bus.remove_listener(self._hw_listener)
|
||||||
|
self._hw_listener = None
|
||||||
|
|
||||||
super()._stop(remove_container)
|
super()._stop(remove_container)
|
||||||
|
|
||||||
def _validate_trust(
|
def _validate_trust(
|
||||||
self, image_id: str, image: str, version: AwesomeVersion
|
self, image_id: str, image: str, version: AwesomeVersion
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Validate trust of content."""
|
"""Validate trust of content."""
|
||||||
|
|
||||||
|
@Job(conditions=[JobCondition.OS_AGENT])
|
||||||
|
async def _hardware_events(self, device: Device) -> None:
|
||||||
|
"""Process Hardware events for adjust device access."""
|
||||||
|
if not any(
|
||||||
|
device_path in (device.path, device.sysfs)
|
||||||
|
for device_path in self.addon.static_devices
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
docker_container = self.sys_docker.containers.get(self.name)
|
||||||
|
except docker.errors.NotFound:
|
||||||
|
self.sys_bus.remove_listener(self._hw_listener)
|
||||||
|
self._hw_listener = None
|
||||||
|
return
|
||||||
|
except (docker.errors.DockerException, requests.RequestException) as err:
|
||||||
|
raise DockerError(
|
||||||
|
f"Can't process Hardware Event on {self.name}: {err!s}", _LOGGER.error
|
||||||
|
) from err
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.sys_dbus.agent.cgroup.add_devices_allowed(
|
||||||
|
docker_container.id, self.sys_hardware.policy.get_cgroups_rule(device)
|
||||||
|
)
|
||||||
|
_LOGGER.info(
|
||||||
|
"Added cgroup permissions for device %s to %s", device.path, self.name
|
||||||
|
)
|
||||||
|
except DBusError as err:
|
||||||
|
raise DockerError(
|
||||||
|
f"Can't set cgroup permission on the host for {self.name}",
|
||||||
|
_LOGGER.error,
|
||||||
|
) from err
|
||||||
|
@ -42,3 +42,29 @@ async def test_bus_event_not_called(coresys: CoreSys) -> None:
|
|||||||
coresys.bus.fire_event(BusEvent.HARDWARE_REMOVE_DEVICE, None)
|
coresys.bus.fire_event(BusEvent.HARDWARE_REMOVE_DEVICE, None)
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
||||||
assert len(results) == 0
|
assert len(results) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_bus_event_removed(coresys: CoreSys) -> None:
|
||||||
|
"""Test bus events over the backend and remove."""
|
||||||
|
results = []
|
||||||
|
|
||||||
|
async def callback(data) -> None:
|
||||||
|
"""Test callback."""
|
||||||
|
results.append(data)
|
||||||
|
|
||||||
|
listener = coresys.bus.register_event(BusEvent.HARDWARE_NEW_DEVICE, callback)
|
||||||
|
|
||||||
|
coresys.bus.fire_event(BusEvent.HARDWARE_NEW_DEVICE, None)
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
assert results[-1] is None
|
||||||
|
|
||||||
|
coresys.bus.fire_event(BusEvent.HARDWARE_NEW_DEVICE, "test")
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
assert results[-1] == "test"
|
||||||
|
|
||||||
|
coresys.bus.remove_listener(listener)
|
||||||
|
|
||||||
|
coresys.bus.fire_event(BusEvent.HARDWARE_NEW_DEVICE, None)
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
assert results[-1] == "test"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user