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
This commit is contained in:
Pascal Vizeli 2021-09-20 12:52:51 +02:00 committed by GitHub
parent 74530baeb7
commit 53eae96a98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 109 additions and 6 deletions

View File

@ -6,3 +6,4 @@ ATTR_DT_UTC = "dt_utc"
ATTR_DT_SYNCHRONIZED = "dt_synchronized" ATTR_DT_SYNCHRONIZED = "dt_synchronized"
ATTR_DISK_DATA = "disk_data" ATTR_DISK_DATA = "disk_data"
ATTR_DEVICE = "device" ATTR_DEVICE = "device"
ATTR_AGENT_VERSION = "agent_version"

View File

@ -25,7 +25,13 @@ from ..const import (
CONTENT_TYPE_BINARY, CONTENT_TYPE_BINARY,
) )
from ..coresys import CoreSysAttributes 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 from .utils import api_process, api_process_raw, api_validate
SERVICE = "service" SERVICE = "service"
@ -40,6 +46,7 @@ class APIHost(CoreSysAttributes):
async def info(self, request): async def info(self, request):
"""Return host information.""" """Return host information."""
return { return {
ATTR_AGENT_VERSION: self.sys_dbus.agent.version,
ATTR_CHASSIS: self.sys_host.info.chassis, ATTR_CHASSIS: self.sys_host.info.chassis,
ATTR_CPE: self.sys_host.info.cpe, ATTR_CPE: self.sys_host.info.cpe,
ATTR_DEPLOYMENT: self.sys_host.info.deployment, ATTR_DEPLOYMENT: self.sys_host.info.deployment,

View File

@ -37,5 +37,10 @@ class DataDisk(DBusInterface):
@dbus_connected @dbus_connected
async def change_device(self, device: Path) -> None: 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()) await self.dbus.DataDisk.ChangeDevice(device.as_posix())
@dbus_connected
async def reload_device(self) -> None:
"""Reload device data."""
await self.dbus.DataDisk.ReloadDevice()

View File

@ -107,6 +107,10 @@ class HassOSJobError(HassOSError, JobException):
"""Function not supported by HassOS.""" """Function not supported by HassOS."""
class HassOSDataDiskError(HassOSError):
"""Issues with the DataDisk feature from HAOS."""
# HaCli # HaCli
@ -278,6 +282,10 @@ class DBusFatalError(DBusError):
"""DBus call going wrong.""" """DBus call going wrong."""
class DBusInterfaceMethodError(DBusInterfaceError):
"""Dbus method was not definied."""
class DBusParseError(DBusError): class DBusParseError(DBusError):
"""DBus parse error.""" """DBus parse error."""

View File

@ -3,8 +3,18 @@ import logging
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
from awesomeversion import AwesomeVersion
from ..coresys import CoreSys, CoreSysAttributes 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.const import JobCondition, JobExecutionLimit
from ..jobs.decorator import Job from ..jobs.decorator import Job
@ -23,6 +33,13 @@ class DataDisk(CoreSysAttributes):
"""Return Path to used Disk for data.""" """Return Path to used Disk for data."""
return self.sys_dbus.agent.datadisk.current_device 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( @Job(
conditions=[JobCondition.HAOS, JobCondition.OS_AGENT, JobCondition.HEALTHY], conditions=[JobCondition.HAOS, JobCondition.OS_AGENT, JobCondition.HEALTHY],
limit=JobExecutionLimit.ONCE, limit=JobExecutionLimit.ONCE,
@ -30,14 +47,32 @@ class DataDisk(CoreSysAttributes):
) )
async def migrate_disk(self, new_disk: Path) -> None: async def migrate_disk(self, new_disk: Path) -> None:
"""Move data partition to a new disk.""" """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: try:
await self.sys_dbus.agent.datadisk.change_device(new_disk) await self.sys_dbus.agent.datadisk.change_device(new_disk)
except DBusError as err: except DBusError as err:
raise HassOSError( raise HassOSDataDiskError(
f"Can't move data partition to {new_disk!s}: {err!s}", _LOGGER.error f"Can't move data partition to {new_disk!s}: {err!s}", _LOGGER.error
) from err ) from err
# Restart Host for finish the process
try: try:
await self.sys_host.control.reboot() await self.sys_host.control.reboot()
except HostError as err: except HostError as err:

View File

@ -146,6 +146,7 @@ class OSManager(CoreSysAttributes):
self._os_name = cpe.get_product()[0] self._os_name = cpe.get_product()[0]
await self.sys_dbus.rauc.update() await self.sys_dbus.rauc.update()
await self.datadisk.load()
_LOGGER.info( _LOGGER.info(
"Detect Home Assistant Operating System %s / BootSlot %s", "Detect Home Assistant Operating System %s / BootSlot %s",

View File

@ -16,6 +16,7 @@ from . import clean_env
from ..exceptions import ( from ..exceptions import (
DBusFatalError, DBusFatalError,
DBusInterfaceError, DBusInterfaceError,
DBusInterfaceMethodError,
DBusNotConnectedError, DBusNotConnectedError,
DBusParseError, DBusParseError,
DBusProgramError, DBusProgramError,
@ -296,7 +297,7 @@ class DBusCallWrapper:
def __call__(self) -> None: def __call__(self) -> None:
"""Catch this method from being called.""" """Catch this method from being called."""
_LOGGER.error("D-Bus method %s not exists!", self.interface) _LOGGER.error("D-Bus method %s not exists!", self.interface)
raise DBusFatalError() raise DBusInterfaceMethodError()
def __getattr__(self, name: str): def __getattr__(self, name: str):
"""Map to dbus method.""" """Map to dbus method."""

View File

@ -1,6 +1,10 @@
"""Test OS API.""" """Test OS API."""
import pytest import pytest
from supervisor.coresys import CoreSys
# pylint: disable=protected-access
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_api_os_info(api_client): async def test_api_os_info(api_client):
@ -17,3 +21,28 @@ async def test_api_os_info(api_client):
"disk_data", "disk_data",
): ):
assert attr in result["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!"

View File

@ -26,3 +26,14 @@ async def test_dbus_osagent_datadisk_change_device(coresys: CoreSys):
await coresys.dbus.agent.connect() await coresys.dbus.agent.connect()
assert await coresys.dbus.agent.datadisk.change_device(Path("/dev/sdb")) is None 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

View File

@ -0,0 +1 @@
(<true>,)

View File

@ -45,6 +45,10 @@
<arg type="b" direction="out"> <arg type="b" direction="out">
</arg> </arg>
</method> </method>
<method name="ReloadDevice">
<arg type="b" direction="out">
</arg>
</method>
<property name="CurrentDevice" type="s" access="read"> <property name="CurrentDevice" type="s" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"> <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true">
</annotation> </annotation>