From 53eae96a98ffc7af124684f5a5686bc95a1e0db0 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 20 Sep 2021 12:52:51 +0200 Subject: [PATCH] DataDisk reload devices (#3129) * DataDisk reload devices * improve loading * simplify * validate input device * add comments * Add agent version to API * more tests * fix test lint --- supervisor/api/const.py | 1 + supervisor/api/host.py | 9 +++- supervisor/dbus/agent/datadisk.py | 7 +++- supervisor/exceptions.py | 8 ++++ supervisor/os/data_disk.py | 41 +++++++++++++++++-- supervisor/os/manager.py | 1 + supervisor/utils/gdbus.py | 3 +- tests/api/test_os.py | 29 +++++++++++++ tests/dbus/agent/test_datadisk.py | 11 +++++ .../io_hass_os_DataDisk-ReloadDevice.fixture | 1 + tests/fixtures/io_hass_os_DataDisk.xml | 4 ++ 11 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 tests/fixtures/io_hass_os_DataDisk-ReloadDevice.fixture diff --git a/supervisor/api/const.py b/supervisor/api/const.py index f0e423f76..92c18e52a 100644 --- a/supervisor/api/const.py +++ b/supervisor/api/const.py @@ -6,3 +6,4 @@ ATTR_DT_UTC = "dt_utc" ATTR_DT_SYNCHRONIZED = "dt_synchronized" ATTR_DISK_DATA = "disk_data" ATTR_DEVICE = "device" +ATTR_AGENT_VERSION = "agent_version" diff --git a/supervisor/api/host.py b/supervisor/api/host.py index 2f21dacaf..945bde624 100644 --- a/supervisor/api/host.py +++ b/supervisor/api/host.py @@ -25,7 +25,13 @@ from ..const import ( CONTENT_TYPE_BINARY, ) from ..coresys import CoreSysAttributes -from .const import ATTR_DT_SYNCHRONIZED, ATTR_DT_UTC, ATTR_USE_NTP, ATTR_USE_RTC +from .const import ( + ATTR_AGENT_VERSION, + ATTR_DT_SYNCHRONIZED, + ATTR_DT_UTC, + ATTR_USE_NTP, + ATTR_USE_RTC, +) from .utils import api_process, api_process_raw, api_validate SERVICE = "service" @@ -40,6 +46,7 @@ class APIHost(CoreSysAttributes): async def info(self, request): """Return host information.""" return { + ATTR_AGENT_VERSION: self.sys_dbus.agent.version, ATTR_CHASSIS: self.sys_host.info.chassis, ATTR_CPE: self.sys_host.info.cpe, ATTR_DEPLOYMENT: self.sys_host.info.deployment, diff --git a/supervisor/dbus/agent/datadisk.py b/supervisor/dbus/agent/datadisk.py index 479a2fb89..461c96cb9 100644 --- a/supervisor/dbus/agent/datadisk.py +++ b/supervisor/dbus/agent/datadisk.py @@ -37,5 +37,10 @@ class DataDisk(DBusInterface): @dbus_connected async def change_device(self, device: Path) -> None: - """Load/Update AppArmor profile.""" + """Migrate data disk to a new device.""" await self.dbus.DataDisk.ChangeDevice(device.as_posix()) + + @dbus_connected + async def reload_device(self) -> None: + """Reload device data.""" + await self.dbus.DataDisk.ReloadDevice() diff --git a/supervisor/exceptions.py b/supervisor/exceptions.py index 11021ed09..53e51b98f 100644 --- a/supervisor/exceptions.py +++ b/supervisor/exceptions.py @@ -107,6 +107,10 @@ class HassOSJobError(HassOSError, JobException): """Function not supported by HassOS.""" +class HassOSDataDiskError(HassOSError): + """Issues with the DataDisk feature from HAOS.""" + + # HaCli @@ -278,6 +282,10 @@ class DBusFatalError(DBusError): """DBus call going wrong.""" +class DBusInterfaceMethodError(DBusInterfaceError): + """Dbus method was not definied.""" + + class DBusParseError(DBusError): """DBus parse error.""" diff --git a/supervisor/os/data_disk.py b/supervisor/os/data_disk.py index 009059b2b..9ac2eb436 100644 --- a/supervisor/os/data_disk.py +++ b/supervisor/os/data_disk.py @@ -3,8 +3,18 @@ import logging from pathlib import Path from typing import Optional +from awesomeversion import AwesomeVersion + from ..coresys import CoreSys, CoreSysAttributes -from ..exceptions import DBusError, HassOSError, HassOSJobError, HostError +from ..exceptions import ( + DBusError, + HardwareNotFound, + HassOSDataDiskError, + HassOSError, + HassOSJobError, + HostError, +) +from ..hardware.const import UdevSubsystem from ..jobs.const import JobCondition, JobExecutionLimit from ..jobs.decorator import Job @@ -23,6 +33,13 @@ class DataDisk(CoreSysAttributes): """Return Path to used Disk for data.""" return self.sys_dbus.agent.datadisk.current_device + @Job(conditions=[JobCondition.OS_AGENT]) + async def load(self) -> None: + """Load DataDisk feature.""" + # Update datadisk details on OS-Agent + if self.sys_dbus.agent.version >= AwesomeVersion("1.2.0"): + await self.sys_dbus.agent.datadisk.reload_device() + @Job( conditions=[JobCondition.HAOS, JobCondition.OS_AGENT, JobCondition.HEALTHY], limit=JobExecutionLimit.ONCE, @@ -30,14 +47,32 @@ class DataDisk(CoreSysAttributes): ) async def migrate_disk(self, new_disk: Path) -> None: """Move data partition to a new disk.""" - # Need some error handling, but we need know what disk_used will return + # Validate integrity of the data input + try: + device = self.sys_hardware.get_by_path(new_disk) + except HardwareNotFound: + raise HassOSDataDiskError( + f"'{new_disk!s}' don't exists on the host!", _LOGGER.error + ) from None + + if device.subsystem != UdevSubsystem.DISK: + raise HassOSDataDiskError( + f"'{new_disk!s}' is not a harddisk!", _LOGGER.error + ) + if self.sys_hardware.disk.is_system_partition(device): + raise HassOSDataDiskError( + f"'{new_disk}' is a system disk and can't be used!", _LOGGER.error + ) + + # Migrate data on Host try: await self.sys_dbus.agent.datadisk.change_device(new_disk) except DBusError as err: - raise HassOSError( + raise HassOSDataDiskError( f"Can't move data partition to {new_disk!s}: {err!s}", _LOGGER.error ) from err + # Restart Host for finish the process try: await self.sys_host.control.reboot() except HostError as err: diff --git a/supervisor/os/manager.py b/supervisor/os/manager.py index 9a3f0fe48..10f4c5926 100644 --- a/supervisor/os/manager.py +++ b/supervisor/os/manager.py @@ -146,6 +146,7 @@ class OSManager(CoreSysAttributes): self._os_name = cpe.get_product()[0] await self.sys_dbus.rauc.update() + await self.datadisk.load() _LOGGER.info( "Detect Home Assistant Operating System %s / BootSlot %s", diff --git a/supervisor/utils/gdbus.py b/supervisor/utils/gdbus.py index 6766d8a96..c054cab67 100644 --- a/supervisor/utils/gdbus.py +++ b/supervisor/utils/gdbus.py @@ -16,6 +16,7 @@ from . import clean_env from ..exceptions import ( DBusFatalError, DBusInterfaceError, + DBusInterfaceMethodError, DBusNotConnectedError, DBusParseError, DBusProgramError, @@ -296,7 +297,7 @@ class DBusCallWrapper: def __call__(self) -> None: """Catch this method from being called.""" _LOGGER.error("D-Bus method %s not exists!", self.interface) - raise DBusFatalError() + raise DBusInterfaceMethodError() def __getattr__(self, name: str): """Map to dbus method.""" diff --git a/tests/api/test_os.py b/tests/api/test_os.py index 857310ff9..036ec4557 100644 --- a/tests/api/test_os.py +++ b/tests/api/test_os.py @@ -1,6 +1,10 @@ """Test OS API.""" import pytest +from supervisor.coresys import CoreSys + +# pylint: disable=protected-access + @pytest.mark.asyncio async def test_api_os_info(api_client): @@ -17,3 +21,28 @@ async def test_api_os_info(api_client): "disk_data", ): assert attr in result["data"] + + +@pytest.mark.asyncio +async def test_api_os_info_with_agent(api_client, coresys: CoreSys): + """Test docker info api.""" + await coresys.dbus.agent.connect() + await coresys.dbus.agent.update() + + resp = await api_client.get("/os/info") + result = await resp.json() + + assert result["data"]["disk_data"] == "/dev/sda" + + +@pytest.mark.asyncio +async def test_api_os_move_data(api_client, coresys: CoreSys): + """Test docker info api.""" + await coresys.dbus.agent.connect() + await coresys.dbus.agent.update() + coresys.os._available = True + + resp = await api_client.post("/os/datadisk/move", json={"device": "/dev/sdaaaa"}) + result = await resp.json() + + assert result["message"] == "'/dev/sdaaaa' don't exists on the host!" diff --git a/tests/dbus/agent/test_datadisk.py b/tests/dbus/agent/test_datadisk.py index a0d72b5c4..6cec9aa25 100644 --- a/tests/dbus/agent/test_datadisk.py +++ b/tests/dbus/agent/test_datadisk.py @@ -26,3 +26,14 @@ async def test_dbus_osagent_datadisk_change_device(coresys: CoreSys): await coresys.dbus.agent.connect() assert await coresys.dbus.agent.datadisk.change_device(Path("/dev/sdb")) is None + + +async def test_dbus_osagent_datadisk_reload_device(coresys: CoreSys): + """Change datadisk on device.""" + + with pytest.raises(DBusNotConnectedError): + await coresys.dbus.agent.datadisk.reload_device() + + await coresys.dbus.agent.connect() + + assert await coresys.dbus.agent.datadisk.reload_device() is None diff --git a/tests/fixtures/io_hass_os_DataDisk-ReloadDevice.fixture b/tests/fixtures/io_hass_os_DataDisk-ReloadDevice.fixture new file mode 100644 index 000000000..3c2c10f15 --- /dev/null +++ b/tests/fixtures/io_hass_os_DataDisk-ReloadDevice.fixture @@ -0,0 +1 @@ +(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_DataDisk.xml b/tests/fixtures/io_hass_os_DataDisk.xml index 9c64e9a2d..71706a294 100644 --- a/tests/fixtures/io_hass_os_DataDisk.xml +++ b/tests/fixtures/io_hass_os_DataDisk.xml @@ -45,6 +45,10 @@ + + + +