mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-19 07:06:30 +00:00
Support OS-Agent Data disk (#3120)
* Support OS-Agent Data disk * fix lint * add tests * Fix empty path * revert change * Using as_posix() * clean not needed cast * rename * Rename files
This commit is contained in:
parent
f4c7f2cae1
commit
271e4f0cc4
2
pylintrc
2
pylintrc
@ -2,7 +2,7 @@
|
|||||||
reports=no
|
reports=no
|
||||||
jobs=2
|
jobs=2
|
||||||
|
|
||||||
good-names=id,i,j,k,ex,Run,_,fp,T
|
good-names=id,i,j,k,ex,Run,_,fp,T,os
|
||||||
|
|
||||||
extension-pkg-whitelist=
|
extension-pkg-whitelist=
|
||||||
ciso8601
|
ciso8601
|
||||||
|
@ -145,6 +145,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.get("/os/info", api_os.info),
|
web.get("/os/info", api_os.info),
|
||||||
web.post("/os/update", api_os.update),
|
web.post("/os/update", api_os.update),
|
||||||
web.post("/os/config/sync", api_os.config_sync),
|
web.post("/os/config/sync", api_os.config_sync),
|
||||||
|
web.post("/os/datadisk/move", api_os.migrate_data),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -4,3 +4,5 @@ ATTR_USE_RTC = "use_rtc"
|
|||||||
ATTR_USE_NTP = "use_ntp"
|
ATTR_USE_NTP = "use_ntp"
|
||||||
ATTR_DT_UTC = "dt_utc"
|
ATTR_DT_UTC = "dt_utc"
|
||||||
ATTR_DT_SYNCHRONIZED = "dt_synchronized"
|
ATTR_DT_SYNCHRONIZED = "dt_synchronized"
|
||||||
|
ATTR_DISK_DATA = "disk_data"
|
||||||
|
ATTR_DEVICE = "device"
|
||||||
|
@ -36,7 +36,7 @@ class APIInfo(CoreSysAttributes):
|
|||||||
return {
|
return {
|
||||||
ATTR_SUPERVISOR: self.sys_supervisor.version,
|
ATTR_SUPERVISOR: self.sys_supervisor.version,
|
||||||
ATTR_HOMEASSISTANT: self.sys_homeassistant.version,
|
ATTR_HOMEASSISTANT: self.sys_homeassistant.version,
|
||||||
ATTR_HASSOS: self.sys_hassos.version,
|
ATTR_HASSOS: self.sys_os.version,
|
||||||
ATTR_DOCKER: self.sys_docker.info.version,
|
ATTR_DOCKER: self.sys_docker.info.version,
|
||||||
ATTR_HOSTNAME: self.sys_host.info.hostname,
|
ATTR_HOSTNAME: self.sys_host.info.hostname,
|
||||||
ATTR_OPERATING_SYSTEM: self.sys_host.info.operating_system,
|
ATTR_OPERATING_SYSTEM: self.sys_host.info.operating_system,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Init file for Supervisor HassOS RESTful API."""
|
"""Init file for Supervisor HassOS RESTful API."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any, Awaitable, Dict
|
from typing import Any, Awaitable, Dict
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
@ -15,11 +16,13 @@ from ..const import (
|
|||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..validate import version_tag
|
from ..validate import version_tag
|
||||||
|
from .const import ATTR_DEVICE, ATTR_DISK_DATA
|
||||||
from .utils import api_process, api_validate
|
from .utils import api_process, api_validate
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): version_tag})
|
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): version_tag})
|
||||||
|
SCHEMA_DISK = vol.Schema({vol.Required(ATTR_DEVICE): vol.All(str, vol.Coerce(Path))})
|
||||||
|
|
||||||
|
|
||||||
class APIOS(CoreSysAttributes):
|
class APIOS(CoreSysAttributes):
|
||||||
@ -29,22 +32,30 @@ class APIOS(CoreSysAttributes):
|
|||||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
async def info(self, request: web.Request) -> Dict[str, Any]:
|
||||||
"""Return OS information."""
|
"""Return OS information."""
|
||||||
return {
|
return {
|
||||||
ATTR_VERSION: self.sys_hassos.version,
|
ATTR_VERSION: self.sys_os.version,
|
||||||
ATTR_VERSION_LATEST: self.sys_hassos.latest_version,
|
ATTR_VERSION_LATEST: self.sys_os.latest_version,
|
||||||
ATTR_UPDATE_AVAILABLE: self.sys_hassos.need_update,
|
ATTR_UPDATE_AVAILABLE: self.sys_os.need_update,
|
||||||
ATTR_BOARD: self.sys_hassos.board,
|
ATTR_BOARD: self.sys_os.board,
|
||||||
ATTR_BOOT: self.sys_dbus.rauc.boot_slot,
|
ATTR_BOOT: self.sys_dbus.rauc.boot_slot,
|
||||||
|
ATTR_DISK_DATA: self.sys_os.datadisk.disk_used,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def update(self, request: web.Request) -> None:
|
async def update(self, request: web.Request) -> None:
|
||||||
"""Update OS."""
|
"""Update OS."""
|
||||||
body = await api_validate(SCHEMA_VERSION, request)
|
body = await api_validate(SCHEMA_VERSION, request)
|
||||||
version = body.get(ATTR_VERSION, self.sys_hassos.latest_version)
|
version = body.get(ATTR_VERSION, self.sys_os.latest_version)
|
||||||
|
|
||||||
await asyncio.shield(self.sys_hassos.update(version))
|
await asyncio.shield(self.sys_os.update(version))
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
def config_sync(self, request: web.Request) -> Awaitable[None]:
|
def config_sync(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Trigger config reload on OS."""
|
"""Trigger config reload on OS."""
|
||||||
return asyncio.shield(self.sys_hassos.config_sync())
|
return asyncio.shield(self.sys_os.config_sync())
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def migrate_data(self, request: web.Request) -> None:
|
||||||
|
"""Trigger data disk migration on Host."""
|
||||||
|
body = await api_validate(SCHEMA_DISK, request)
|
||||||
|
|
||||||
|
await asyncio.shield(self.sys_os.datadisk.migrate_disk(body[ATTR_DEVICE]))
|
||||||
|
@ -38,13 +38,13 @@ from .coresys import CoreSys
|
|||||||
from .dbus.manager import DBusManager
|
from .dbus.manager import DBusManager
|
||||||
from .discovery import Discovery
|
from .discovery import Discovery
|
||||||
from .hardware.module import HardwareManager
|
from .hardware.module import HardwareManager
|
||||||
from .hassos import HassOS
|
|
||||||
from .homeassistant.module import HomeAssistant
|
from .homeassistant.module import HomeAssistant
|
||||||
from .host.manager import HostManager
|
from .host.manager import HostManager
|
||||||
from .ingress import Ingress
|
from .ingress import Ingress
|
||||||
from .misc.filter import filter_data
|
from .misc.filter import filter_data
|
||||||
from .misc.scheduler import Scheduler
|
from .misc.scheduler import Scheduler
|
||||||
from .misc.tasks import Tasks
|
from .misc.tasks import Tasks
|
||||||
|
from .os.manager import OSManager
|
||||||
from .plugins.manager import PluginManager
|
from .plugins.manager import PluginManager
|
||||||
from .resolution.module import ResolutionManager
|
from .resolution.module import ResolutionManager
|
||||||
from .security import Security
|
from .security import Security
|
||||||
@ -81,7 +81,7 @@ async def initialize_coresys() -> CoreSys:
|
|||||||
coresys.store = StoreManager(coresys)
|
coresys.store = StoreManager(coresys)
|
||||||
coresys.discovery = Discovery(coresys)
|
coresys.discovery = Discovery(coresys)
|
||||||
coresys.dbus = DBusManager(coresys)
|
coresys.dbus = DBusManager(coresys)
|
||||||
coresys.hassos = HassOS(coresys)
|
coresys.os = OSManager(coresys)
|
||||||
coresys.scheduler = Scheduler(coresys)
|
coresys.scheduler = Scheduler(coresys)
|
||||||
coresys.security = Security(coresys)
|
coresys.security = Security(coresys)
|
||||||
coresys.bus = Bus(coresys)
|
coresys.bus = Bus(coresys)
|
||||||
|
@ -125,7 +125,7 @@ class Core(CoreSysAttributes):
|
|||||||
# Load CPU/Arch
|
# Load CPU/Arch
|
||||||
self.sys_arch.load(),
|
self.sys_arch.load(),
|
||||||
# Load HassOS
|
# Load HassOS
|
||||||
self.sys_hassos.load(),
|
self.sys_os.load(),
|
||||||
# Load Stores
|
# Load Stores
|
||||||
self.sys_store.load(),
|
self.sys_store.load(),
|
||||||
# Load Add-ons
|
# Load Add-ons
|
||||||
@ -169,7 +169,7 @@ class Core(CoreSysAttributes):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Mark booted partition as healthy
|
# Mark booted partition as healthy
|
||||||
await self.sys_hassos.mark_healthy()
|
await self.sys_os.mark_healthy()
|
||||||
|
|
||||||
# On release channel, try update itself
|
# On release channel, try update itself
|
||||||
if self.sys_supervisor.need_update:
|
if self.sys_supervisor.need_update:
|
||||||
|
@ -26,7 +26,7 @@ if TYPE_CHECKING:
|
|||||||
from .dbus.manager import DBusManager
|
from .dbus.manager import DBusManager
|
||||||
from .discovery import Discovery
|
from .discovery import Discovery
|
||||||
from .hardware.module import HardwareManager
|
from .hardware.module import HardwareManager
|
||||||
from .hassos import HassOS
|
from .os.manager import OSManager
|
||||||
from .homeassistant.module import HomeAssistant
|
from .homeassistant.module import HomeAssistant
|
||||||
from .host.manager import HostManager
|
from .host.manager import HostManager
|
||||||
from .ingress import Ingress
|
from .ingress import Ingress
|
||||||
@ -79,7 +79,7 @@ class CoreSys:
|
|||||||
self._host: Optional[HostManager] = None
|
self._host: Optional[HostManager] = None
|
||||||
self._ingress: Optional[Ingress] = None
|
self._ingress: Optional[Ingress] = None
|
||||||
self._dbus: Optional[DBusManager] = None
|
self._dbus: Optional[DBusManager] = None
|
||||||
self._hassos: Optional[HassOS] = None
|
self._os: Optional[OSManager] = None
|
||||||
self._services: Optional[ServiceManager] = None
|
self._services: Optional[ServiceManager] = None
|
||||||
self._scheduler: Optional[Scheduler] = None
|
self._scheduler: Optional[Scheduler] = None
|
||||||
self._store: Optional[StoreManager] = None
|
self._store: Optional[StoreManager] = None
|
||||||
@ -411,18 +411,18 @@ class CoreSys:
|
|||||||
self._ingress = value
|
self._ingress = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hassos(self) -> HassOS:
|
def os(self) -> OSManager:
|
||||||
"""Return HassOS object."""
|
"""Return OSManager object."""
|
||||||
if self._hassos is None:
|
if self._os is None:
|
||||||
raise RuntimeError("HassOS not set!")
|
raise RuntimeError("OSManager not set!")
|
||||||
return self._hassos
|
return self._os
|
||||||
|
|
||||||
@hassos.setter
|
@os.setter
|
||||||
def hassos(self, value: HassOS) -> None:
|
def os(self, value: OSManager) -> None:
|
||||||
"""Set a HassOS object."""
|
"""Set a OSManager object."""
|
||||||
if self._hassos:
|
if self._os:
|
||||||
raise RuntimeError("HassOS already set!")
|
raise RuntimeError("OSManager already set!")
|
||||||
self._hassos = value
|
self._os = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def resolution(self) -> ResolutionManager:
|
def resolution(self) -> ResolutionManager:
|
||||||
@ -650,9 +650,9 @@ class CoreSysAttributes:
|
|||||||
return self.coresys.ingress
|
return self.coresys.ingress
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sys_hassos(self) -> HassOS:
|
def sys_os(self) -> OSManager:
|
||||||
"""Return HassOS object."""
|
"""Return OSManager object."""
|
||||||
return self.coresys.hassos
|
return self.coresys.os
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sys_resolution(self) -> ResolutionManager:
|
def sys_resolution(self) -> ResolutionManager:
|
||||||
|
@ -91,7 +91,7 @@ class HostManager(CoreSysAttributes):
|
|||||||
if self.sys_dbus.agent.is_connected:
|
if self.sys_dbus.agent.is_connected:
|
||||||
features.append(HostFeature.AGENT)
|
features.append(HostFeature.AGENT)
|
||||||
|
|
||||||
if self.sys_hassos.available:
|
if self.sys_os.available:
|
||||||
features.append(HostFeature.HAOS)
|
features.append(HostFeature.HAOS)
|
||||||
|
|
||||||
return features
|
return features
|
||||||
|
@ -18,6 +18,7 @@ class JobCondition(str, Enum):
|
|||||||
INTERNET_HOST = "internet_host"
|
INTERNET_HOST = "internet_host"
|
||||||
RUNNING = "running"
|
RUNNING = "running"
|
||||||
HAOS = "haos"
|
HAOS = "haos"
|
||||||
|
OS_AGENT = "os_agent"
|
||||||
|
|
||||||
|
|
||||||
class JobExecutionLimit(str, Enum):
|
class JobExecutionLimit(str, Enum):
|
||||||
|
@ -10,6 +10,7 @@ import sentry_sdk
|
|||||||
from ..const import CoreState
|
from ..const import CoreState
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import HassioError, JobConditionException, JobException
|
from ..exceptions import HassioError, JobConditionException, JobException
|
||||||
|
from ..host.const import HostFeature
|
||||||
from ..resolution.const import MINIMUM_FREE_SPACE_THRESHOLD, ContextType, IssueType
|
from ..resolution.const import MINIMUM_FREE_SPACE_THRESHOLD, ContextType, IssueType
|
||||||
from .const import JobCondition, JobExecutionLimit
|
from .const import JobCondition, JobExecutionLimit
|
||||||
|
|
||||||
@ -167,11 +168,19 @@ class Job(CoreSysAttributes):
|
|||||||
f"'{self._method.__qualname__}' blocked from execution, no host internet connection"
|
f"'{self._method.__qualname__}' blocked from execution, no host internet connection"
|
||||||
)
|
)
|
||||||
|
|
||||||
if JobCondition.HAOS in self.conditions and not self.sys_hassos.available:
|
if JobCondition.HAOS in self.conditions and not self.sys_os.available:
|
||||||
raise JobConditionException(
|
raise JobConditionException(
|
||||||
f"'{self._method.__qualname__}' blocked from execution, no Home Assistant OS available"
|
f"'{self._method.__qualname__}' blocked from execution, no Home Assistant OS available"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
JobCondition.OS_AGENT in self.conditions
|
||||||
|
and HostFeature.AGENT not in self.sys_host.features
|
||||||
|
):
|
||||||
|
raise JobConditionException(
|
||||||
|
f"'{self._method.__qualname__}' blocked from execution, no Home Assistant OS-Agent available"
|
||||||
|
)
|
||||||
|
|
||||||
async def _acquire_exection_limit(self) -> None:
|
async def _acquire_exection_limit(self) -> None:
|
||||||
"""Process exection limits."""
|
"""Process exection limits."""
|
||||||
if self.limit not in (
|
if self.limit not in (
|
||||||
|
@ -56,7 +56,7 @@ def filter_data(coresys: CoreSys, event: dict, hint: dict) -> dict:
|
|||||||
},
|
},
|
||||||
"host": {
|
"host": {
|
||||||
"arch": coresys.arch.default,
|
"arch": coresys.arch.default,
|
||||||
"board": coresys.hassos.board,
|
"board": coresys.os.board,
|
||||||
"deployment": coresys.host.info.deployment,
|
"deployment": coresys.host.info.deployment,
|
||||||
"disk_free_space": coresys.host.info.free_space,
|
"disk_free_space": coresys.host.info.free_space,
|
||||||
"host": coresys.host.info.operating_system,
|
"host": coresys.host.info.operating_system,
|
||||||
@ -72,7 +72,7 @@ def filter_data(coresys: CoreSys, event: dict, hint: dict) -> dict:
|
|||||||
"docker": coresys.docker.info.version,
|
"docker": coresys.docker.info.version,
|
||||||
"multicast": coresys.plugins.multicast.version,
|
"multicast": coresys.plugins.multicast.version,
|
||||||
"observer": coresys.plugins.observer.version,
|
"observer": coresys.plugins.observer.version,
|
||||||
"os": coresys.hassos.version,
|
"os": coresys.os.version,
|
||||||
"supervisor": coresys.supervisor.version,
|
"supervisor": coresys.supervisor.version,
|
||||||
},
|
},
|
||||||
"resolution": {
|
"resolution": {
|
||||||
@ -87,7 +87,7 @@ def filter_data(coresys: CoreSys, event: dict, hint: dict) -> dict:
|
|||||||
)
|
)
|
||||||
event.setdefault("tags", []).extend(
|
event.setdefault("tags", []).extend(
|
||||||
[
|
[
|
||||||
["installation_type", "os" if coresys.hassos.available else "supervised"],
|
["installation_type", "os" if coresys.os.available else "supervised"],
|
||||||
["machine", coresys.machine],
|
["machine", coresys.machine],
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
1
supervisor/os/__init__.py
Normal file
1
supervisor/os/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Home Assistant Operating-System backend."""
|
47
supervisor/os/data_disk.py
Normal file
47
supervisor/os/data_disk.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
"""Home Assistant Operating-System DataDisk."""
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
|
from ..exceptions import DBusError, HassOSError, HassOSJobError, HostError
|
||||||
|
from ..jobs.const import JobCondition, JobExecutionLimit
|
||||||
|
from ..jobs.decorator import Job
|
||||||
|
|
||||||
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DataDisk(CoreSysAttributes):
|
||||||
|
"""Handle DataDisk feature from OS."""
|
||||||
|
|
||||||
|
def __init__(self, coresys: CoreSys) -> None:
|
||||||
|
"""Initialize DataDisk."""
|
||||||
|
self.coresys = coresys
|
||||||
|
|
||||||
|
@property
|
||||||
|
def disk_used(self) -> Optional[Path]:
|
||||||
|
"""Return Path to used Disk for data."""
|
||||||
|
return self.sys_dbus.agent.datadisk.current_device
|
||||||
|
|
||||||
|
@Job(
|
||||||
|
conditions=[JobCondition.HAOS, JobCondition.OS_AGENT, JobCondition.HEALTHY],
|
||||||
|
limit=JobExecutionLimit.ONCE,
|
||||||
|
on_condition=HassOSJobError,
|
||||||
|
)
|
||||||
|
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
|
||||||
|
try:
|
||||||
|
await self.sys_dbus.agent.datadisk.change_device(new_disk)
|
||||||
|
except DBusError as err:
|
||||||
|
raise HassOSError(
|
||||||
|
f"Can't move data partition to {new_disk!s}: {err!s}", _LOGGER.error
|
||||||
|
) from err
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.sys_host.control.reboot()
|
||||||
|
except HostError as err:
|
||||||
|
raise HassOSError(
|
||||||
|
f"Can't restart device to finish disk migration: {err!s}",
|
||||||
|
_LOGGER.warning,
|
||||||
|
) from err
|
@ -1,4 +1,4 @@
|
|||||||
"""HassOS support on supervisor."""
|
"""OS support on supervisor."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -8,21 +8,23 @@ import aiohttp
|
|||||||
from awesomeversion import AwesomeVersion, AwesomeVersionException
|
from awesomeversion import AwesomeVersion, AwesomeVersionException
|
||||||
from cpe import CPE
|
from cpe import CPE
|
||||||
|
|
||||||
from .coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from .dbus.rauc import RaucState
|
from ..dbus.rauc import RaucState
|
||||||
from .exceptions import DBusError, HassOSJobError, HassOSUpdateError
|
from ..exceptions import DBusError, HassOSJobError, HassOSUpdateError
|
||||||
from .jobs.const import JobCondition, JobExecutionLimit
|
from ..jobs.const import JobCondition, JobExecutionLimit
|
||||||
from .jobs.decorator import Job
|
from ..jobs.decorator import Job
|
||||||
|
from .data_disk import DataDisk
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class HassOS(CoreSysAttributes):
|
class OSManager(CoreSysAttributes):
|
||||||
"""HassOS interface inside supervisor."""
|
"""OS interface inside supervisor."""
|
||||||
|
|
||||||
def __init__(self, coresys: CoreSys):
|
def __init__(self, coresys: CoreSys):
|
||||||
"""Initialize HassOS handler."""
|
"""Initialize HassOS handler."""
|
||||||
self.coresys: CoreSys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
|
self._datadisk: DataDisk = DataDisk(coresys)
|
||||||
self._available: bool = False
|
self._available: bool = False
|
||||||
self._version: Optional[AwesomeVersion] = None
|
self._version: Optional[AwesomeVersion] = None
|
||||||
self._board: Optional[str] = None
|
self._board: Optional[str] = None
|
||||||
@ -61,6 +63,11 @@ class HassOS(CoreSysAttributes):
|
|||||||
"""Return OS name."""
|
"""Return OS name."""
|
||||||
return self._os_name
|
return self._os_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def datadisk(self) -> DataDisk:
|
||||||
|
"""Return Operating-System datadisk."""
|
||||||
|
return self._datadisk
|
||||||
|
|
||||||
def _get_download_url(self, version: AwesomeVersion) -> str:
|
def _get_download_url(self, version: AwesomeVersion) -> str:
|
||||||
raw_url = self.sys_updater.ota_url
|
raw_url = self.sys_updater.ota_url
|
||||||
if raw_url is None:
|
if raw_url is None:
|
@ -34,6 +34,6 @@ class EvaluateOperatingSystem(EvaluateBase):
|
|||||||
|
|
||||||
async def evaluate(self):
|
async def evaluate(self):
|
||||||
"""Run evaluation."""
|
"""Run evaluation."""
|
||||||
if self.sys_hassos.available:
|
if self.sys_os.available:
|
||||||
return False
|
return False
|
||||||
return self.sys_host.info.operating_system not in SUPPORTED_OS
|
return self.sys_host.info.operating_system not in SUPPORTED_OS
|
||||||
|
@ -241,10 +241,10 @@ class Updater(FileConfiguration, CoreSysAttributes):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Update HassOS version
|
# Update HassOS version
|
||||||
if self.sys_hassos.board:
|
if self.sys_os.board:
|
||||||
events.append("os")
|
events.append("os")
|
||||||
self._data[ATTR_HASSOS] = AwesomeVersion(
|
self._data[ATTR_HASSOS] = AwesomeVersion(
|
||||||
data["hassos"][self.sys_hassos.board]
|
data["hassos"][self.sys_os.board]
|
||||||
)
|
)
|
||||||
self._data[ATTR_OTA] = data["ota"]
|
self._data[ATTR_OTA] = data["ota"]
|
||||||
|
|
||||||
|
@ -25,9 +25,7 @@ class JSONEncoder(json.JSONEncoder):
|
|||||||
if isinstance(o, set):
|
if isinstance(o, set):
|
||||||
return list(o)
|
return list(o)
|
||||||
if isinstance(o, Path):
|
if isinstance(o, Path):
|
||||||
return str(o)
|
return o.as_posix()
|
||||||
if hasattr(o, "as_dict"):
|
|
||||||
return o.as_dict()
|
|
||||||
|
|
||||||
return json.JSONEncoder.default(self, o)
|
return json.JSONEncoder.default(self, o)
|
||||||
|
|
||||||
|
19
tests/api/test_os.py
Normal file
19
tests/api/test_os.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
"""Test OS API."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_api_os_info(api_client):
|
||||||
|
"""Test docker info api."""
|
||||||
|
resp = await api_client.get("/os/info")
|
||||||
|
result = await resp.json()
|
||||||
|
|
||||||
|
for attr in (
|
||||||
|
"version",
|
||||||
|
"version_latest",
|
||||||
|
"update_available",
|
||||||
|
"board",
|
||||||
|
"boot",
|
||||||
|
"disk_data",
|
||||||
|
):
|
||||||
|
assert attr in result["data"]
|
@ -119,10 +119,10 @@ async def test_haos(coresys: CoreSys):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
test = TestClass(coresys)
|
test = TestClass(coresys)
|
||||||
coresys.hassos._available = True
|
coresys.os._available = True
|
||||||
assert await test.execute()
|
assert await test.execute()
|
||||||
|
|
||||||
coresys.hassos._available = False
|
coresys.os._available = False
|
||||||
assert not await test.execute()
|
assert not await test.execute()
|
||||||
|
|
||||||
|
|
||||||
|
1
tests/os/__init__.py
Normal file
1
tests/os/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Home Assistant Operating-System backend tests."""
|
@ -11,8 +11,8 @@ from supervisor.coresys import CoreSys
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_ota_url_generic_x86_64_rename(coresys: CoreSys) -> None:
|
async def test_ota_url_generic_x86_64_rename(coresys: CoreSys) -> None:
|
||||||
"""Test download URL generated."""
|
"""Test download URL generated."""
|
||||||
coresys.hassos._board = "intel-nuc"
|
coresys.os._board = "intel-nuc"
|
||||||
coresys.hassos._version = AwesomeVersion("5.13")
|
coresys.os._version = AwesomeVersion("5.13")
|
||||||
await coresys.updater.fetch_data()
|
await coresys.updater.fetch_data()
|
||||||
|
|
||||||
version6 = AwesomeVersion("6.0")
|
version6 = AwesomeVersion("6.0")
|
||||||
@ -20,7 +20,7 @@ async def test_ota_url_generic_x86_64_rename(coresys: CoreSys) -> None:
|
|||||||
version=str(version6), board="generic-x86-64", os_name="haos"
|
version=str(version6), board="generic-x86-64", os_name="haos"
|
||||||
)
|
)
|
||||||
|
|
||||||
assert coresys.hassos._get_download_url(version6) == url
|
assert coresys.os._get_download_url(version6) == url
|
||||||
|
|
||||||
|
|
||||||
def test_ota_url_os_name(coresys: CoreSys) -> None:
|
def test_ota_url_os_name(coresys: CoreSys) -> None:
|
||||||
@ -33,11 +33,11 @@ def test_ota_url_os_name(coresys: CoreSys) -> None:
|
|||||||
url = "https://github.com/home-assistant/operating-system/releases/download/{version}/{os_name}_{board}-{version}.raucb"
|
url = "https://github.com/home-assistant/operating-system/releases/download/{version}/{os_name}_{board}-{version}.raucb"
|
||||||
url_formatted = url.format(version=versionstr, board=board, os_name=os_name)
|
url_formatted = url.format(version=versionstr, board=board, os_name=os_name)
|
||||||
|
|
||||||
coresys.hassos._board = board
|
coresys.os._board = board
|
||||||
coresys.hassos._os_name = os_name
|
coresys.os._os_name = os_name
|
||||||
coresys.updater._data = {"ota": url}
|
coresys.updater._data = {"ota": url}
|
||||||
|
|
||||||
url = coresys.hassos._get_download_url(AwesomeVersion(versionstr))
|
url = coresys.os._get_download_url(AwesomeVersion(versionstr))
|
||||||
assert url == url_formatted
|
assert url == url_formatted
|
||||||
|
|
||||||
|
|
||||||
@ -51,9 +51,9 @@ def test_ota_url_os_name_rel_5_downgrade(coresys: CoreSys) -> None:
|
|||||||
url = "https://github.com/home-assistant/operating-system/releases/download/{version}/{os_name}_{board}-{version}.raucb"
|
url = "https://github.com/home-assistant/operating-system/releases/download/{version}/{os_name}_{board}-{version}.raucb"
|
||||||
url_formatted = url.format(version=versionstr, board=board, os_name="hassos")
|
url_formatted = url.format(version=versionstr, board=board, os_name="hassos")
|
||||||
|
|
||||||
coresys.hassos._board = board
|
coresys.os._board = board
|
||||||
coresys.hassos._os_name = "haos"
|
coresys.os._os_name = "haos"
|
||||||
coresys.updater._data = {"ota": url}
|
coresys.updater._data = {"ota": url}
|
||||||
|
|
||||||
url = coresys.hassos._get_download_url(AwesomeVersion(versionstr))
|
url = coresys.os._get_download_url(AwesomeVersion(versionstr))
|
||||||
assert url == url_formatted
|
assert url == url_formatted
|
@ -21,10 +21,10 @@ async def test_evaluation(coresys: CoreSys):
|
|||||||
await operating_system()
|
await operating_system()
|
||||||
assert operating_system.reason in coresys.resolution.unsupported
|
assert operating_system.reason in coresys.resolution.unsupported
|
||||||
|
|
||||||
coresys.hassos._available = True
|
coresys.os._available = True
|
||||||
await operating_system()
|
await operating_system()
|
||||||
assert operating_system.reason not in coresys.resolution.unsupported
|
assert operating_system.reason not in coresys.resolution.unsupported
|
||||||
coresys.hassos._available = False
|
coresys.os._available = False
|
||||||
|
|
||||||
coresys.host._info = MagicMock(operating_system=SUPPORTED_OS[0])
|
coresys.host._info = MagicMock(operating_system=SUPPORTED_OS[0])
|
||||||
await operating_system()
|
await operating_system()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user