Offload rauc logic with partition handling (#1404)

* Offload rauc logic with partition handling

* Fix name

* Fix detection

* Add to API
This commit is contained in:
Pascal Vizeli 2019-12-19 16:41:16 +01:00 committed by GitHub
parent a0d106529c
commit 5365aa4466
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 388 additions and 332 deletions

44
API.md
View File

@ -56,12 +56,10 @@ The addons from `addons` are only installed one.
"installed": "INSTALL_VERSION", "installed": "INSTALL_VERSION",
"icon": "bool", "icon": "bool",
"logo": "bool", "logo": "bool",
"state": "started|stopped", "state": "started|stopped"
} }
], ],
"addons_repositories": [ "addons_repositories": ["REPO_URL"]
"REPO_URL"
]
} }
``` ```
@ -85,9 +83,7 @@ Optional:
"debug": "bool", "debug": "bool",
"debug_block": "bool", "debug_block": "bool",
"logging": "debug|info|warning|error|critical", "logging": "debug|info|warning|error|critical",
"addons_repositories": [ "addons_repositories": ["REPO_URL"]
"REPO_URL"
]
} }
``` ```
@ -100,6 +96,7 @@ Reload addons/version.
Output is the raw docker log. Output is the raw docker log.
- GET `/supervisor/stats` - GET `/supervisor/stats`
```json ```json
{ {
"cpu_percent": 0.0, "cpu_percent": 0.0,
@ -140,6 +137,7 @@ Repair overlayfs issue and restore lost images
- POST `/snapshots/new/upload` - POST `/snapshots/new/upload`
return: return:
```json ```json
{ {
"slug": "" "slug": ""
@ -156,6 +154,7 @@ return:
``` ```
return: return:
```json ```json
{ {
"slug": "" "slug": ""
@ -174,6 +173,7 @@ return:
``` ```
return: return:
```json ```json
{ {
"slug": "" "slug": ""
@ -247,7 +247,7 @@ return:
"kernel": "4.15.7|null", "kernel": "4.15.7|null",
"chassis": "specific|null", "chassis": "specific|null",
"deployment": "stable|beta|dev|null", "deployment": "stable|beta|dev|null",
"cpe": "xy|null", "cpe": "xy|null"
} }
``` ```
@ -255,7 +255,7 @@ return:
```json ```json
{ {
"hostname": "", "hostname": ""
} }
``` ```
@ -264,6 +264,7 @@ return:
#### Services #### Services
- GET `/host/services` - GET `/host/services`
```json ```json
{ {
"services": [ "services": [
@ -285,17 +286,20 @@ return:
### HassOS ### HassOS
- GET `/hassos/info` - GET `/hassos/info`
```json ```json
{ {
"version": "2.3", "version": "2.3",
"version_cli": "7", "version_cli": "7",
"version_latest": "2.4", "version_latest": "2.4",
"version_cli_latest": "8", "version_cli_latest": "8",
"board": "ova|rpi" "board": "ova|rpi",
"boot": "rauc boot slot"
} }
``` ```
- POST `/hassos/update` - POST `/hassos/update`
```json ```json
{ {
"version": "optional" "version": "optional"
@ -303,6 +307,7 @@ return:
``` ```
- POST `/hassos/update/cli` - POST `/hassos/update/cli`
```json ```json
{ {
"version": "optional" "version": "optional"
@ -316,6 +321,7 @@ Load host configs from a USB stick.
### Hardware ### Hardware
- GET `/hardware/info` - GET `/hardware/info`
```json ```json
{ {
"serial": ["/dev/xy"], "serial": ["/dev/xy"],
@ -336,6 +342,7 @@ Load host configs from a USB stick.
``` ```
- GET `/hardware/audio` - GET `/hardware/audio`
```json ```json
{ {
"audio": { "audio": {
@ -421,6 +428,7 @@ Proxy to real home-assistant instance.
Proxy to real websocket instance. Proxy to real websocket instance.
- GET `/homeassistant/stats` - GET `/homeassistant/stats`
```json ```json
{ {
"cpu_percent": 0.0, "cpu_percent": 0.0,
@ -568,7 +576,7 @@ This function is not callable by itself.
```json ```json
{ {
"protected": "bool", "protected": "bool"
} }
``` ```
@ -597,6 +605,7 @@ Only supported for local build addons
Write data to add-on stdin Write data to add-on stdin
- GET `/addons/{addon}/stats` - GET `/addons/{addon}/stats`
```json ```json
{ {
"cpu_percent": 0.0, "cpu_percent": 0.0,
@ -647,6 +656,7 @@ Need ingress session as cookie.
### discovery ### discovery
- GET `/discovery` - GET `/discovery`
```json ```json
{ {
"discovery": [ "discovery": [
@ -661,6 +671,7 @@ Need ingress session as cookie.
``` ```
- GET `/discovery/{UUID}` - GET `/discovery/{UUID}`
```json ```json
{ {
"addon": "slug", "addon": "slug",
@ -671,6 +682,7 @@ Need ingress session as cookie.
``` ```
- POST `/discovery` - POST `/discovery`
```json ```json
{ {
"service": "name", "service": "name",
@ -679,6 +691,7 @@ Need ingress session as cookie.
``` ```
return: return:
```json ```json
{ {
"uuid": "uuid" "uuid": "uuid"
@ -690,6 +703,7 @@ return:
### Services ### Services
- GET `/services` - GET `/services`
```json ```json
{ {
"services": [ "services": [
@ -705,6 +719,7 @@ return:
#### MQTT #### MQTT
- GET `/services/mqtt` - GET `/services/mqtt`
```json ```json
{ {
"addon": "name", "addon": "name",
@ -718,6 +733,7 @@ return:
``` ```
- POST `/services/mqtt` - POST `/services/mqtt`
```json ```json
{ {
"host": "xy", "host": "xy",
@ -734,6 +750,7 @@ return:
### Misc ### Misc
- GET `/info` - GET `/info`
```json ```json
{ {
"supervisor": "version", "supervisor": "version",
@ -752,6 +769,7 @@ return:
### DNS ### DNS
- GET `/dns/info` - GET `/dns/info`
```json ```json
{ {
"host": "ip-address", "host": "ip-address",
@ -763,6 +781,7 @@ return:
``` ```
- POST `/dns/options` - POST `/dns/options`
```json ```json
{ {
"servers": ["dns://8.8.8.8"] "servers": ["dns://8.8.8.8"]
@ -770,6 +789,7 @@ return:
``` ```
- POST `/dns/update` - POST `/dns/update`
```json ```json
{ {
"version": "VERSION" "version": "VERSION"
@ -781,6 +801,7 @@ return:
- GET `/dns/logs` - GET `/dns/logs`
- GET `/dns/stats` - GET `/dns/stats`
```json ```json
{ {
"cpu_percent": 0.0, "cpu_percent": 0.0,
@ -802,6 +823,7 @@ supervisor.
You can call post `/auth` You can call post `/auth`
We support: We support:
- Json `{ "user|name": "...", "password": "..." }` - Json `{ "user|name": "...", "password": "..." }`
- application/x-www-form-urlencoded `user|name=...&password=...` - application/x-www-form-urlencoded `user|name=...&password=...`
- BasicAuth - BasicAuth

View File

@ -3,11 +3,12 @@ import asyncio
import logging import logging
from typing import Any, Awaitable, Dict from typing import Any, Awaitable, Dict
import voluptuous as vol
from aiohttp import web from aiohttp import web
import voluptuous as vol
from ..const import ( from ..const import (
ATTR_BOARD, ATTR_BOARD,
ATTR_BOOT,
ATTR_VERSION, ATTR_VERSION,
ATTR_VERSION_CLI, ATTR_VERSION_CLI,
ATTR_VERSION_CLI_LATEST, ATTR_VERSION_CLI_LATEST,
@ -33,6 +34,7 @@ class APIHassOS(CoreSysAttributes):
ATTR_VERSION_LATEST: self.sys_hassos.version_latest, ATTR_VERSION_LATEST: self.sys_hassos.version_latest,
ATTR_VERSION_CLI_LATEST: self.sys_hassos.version_cli_latest, ATTR_VERSION_CLI_LATEST: self.sys_hassos.version_cli_latest,
ATTR_BOARD: self.sys_hassos.board, ATTR_BOARD: self.sys_hassos.board,
ATTR_BOOT: self.sys_dbus.rauc.boot_slot,
} }
@api_process @api_process

View File

@ -79,7 +79,11 @@ class HassIO(CoreSysAttributes):
"""Start Hass.io orchestration.""" """Start Hass.io orchestration."""
await self.sys_api.start() await self.sys_api.start()
# on release channel, try update itself # Mark booted partition as healthy
if self.sys_hassos.available:
await self.sys_hassos.mark_healthy()
# On release channel, try update itself
if self.sys_supervisor.need_update: if self.sys_supervisor.need_update:
try: try:
if self.sys_dev: if self.sys_dev:
@ -92,7 +96,7 @@ class HassIO(CoreSysAttributes):
"future version of Home Assistant!" "future version of Home Assistant!"
) )
# start addon mark as initialize # Start addon mark as initialize
await self.sys_addons.boot(STARTUP_INITIALIZE) await self.sys_addons.boot(STARTUP_INITIALIZE)
try: try:

View File

@ -1,6 +1,7 @@
"""D-Bus interface for rauc.""" """D-Bus interface for rauc."""
import logging import logging
from typing import Optional from typing import Optional
from enum import Enum
from .interface import DBusInterface from .interface import DBusInterface
from .utils import dbus_connected from .utils import dbus_connected
@ -13,6 +14,14 @@ DBUS_NAME = "de.pengutronix.rauc"
DBUS_OBJECT = "/" DBUS_OBJECT = "/"
class RaucState(str, Enum):
"""Rauc slot states."""
GOOD = "good"
BAD = "bad"
ACTIVE = "active"
class Rauc(DBusInterface): class Rauc(DBusInterface):
"""Handle D-Bus interface for rauc.""" """Handle D-Bus interface for rauc."""
@ -82,6 +91,14 @@ class Rauc(DBusInterface):
""" """
return self.dbus.wait_signal(f"{DBUS_NAME}.Installer.Completed") return self.dbus.wait_signal(f"{DBUS_NAME}.Installer.Completed")
@dbus_connected
def mark(self, state: RaucState, slot_identifier: str):
"""Get slot status.
Return a coroutine.
"""
return self.dbus.Installer.Mark(state, slot_identifier)
@dbus_connected @dbus_connected
async def update(self): async def update(self):
"""Update Properties.""" """Update Properties."""

View File

@ -427,9 +427,10 @@ class DockerInterface(CoreSysAttributes):
continue continue
available_version.append(version) available_version.append(version)
assert available_version if not available_version:
raise ValueError()
except (docker.errors.DockerException, AssertionError): except (docker.errors.DockerException, ValueError):
_LOGGER.debug("No version found for %s", self.image) _LOGGER.debug("No version found for %s", self.image)
raise DockerAPIError() raise DockerAPIError()
else: else:

View File

@ -17,6 +17,7 @@ from .exceptions import (
HassOSUpdateError, HassOSUpdateError,
DockerAPIError, DockerAPIError,
) )
from .dbus.rauc import RaucState
_LOGGER: logging.Logger = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
@ -111,20 +112,19 @@ class HassOS(CoreSysAttributes):
async def load(self) -> None: async def load(self) -> None:
"""Load HassOS data.""" """Load HassOS data."""
try: try:
# Check needed host functions if self.sys_host.info.cpe is None:
assert self.sys_dbus.rauc.is_connected raise TypeError()
assert self.sys_dbus.systemd.is_connected
assert self.sys_dbus.hostname.is_connected
assert self.sys_host.info.cpe is not None
cpe = CPE(self.sys_host.info.cpe) cpe = CPE(self.sys_host.info.cpe)
assert cpe.get_product()[0] == "hassos"
except (AssertionError, NotImplementedError): if cpe.get_product()[0] != "hassos":
raise TypeError()
except TypeError:
_LOGGER.debug("Found no HassOS") _LOGGER.debug("Found no HassOS")
return return
else:
self._available = True
# Store meta data # Store meta data
self._available = True
self._version = cpe.get_version()[0] self._version = cpe.get_version()[0]
self._board = cpe.get_target_hardware()[0] self._board = cpe.get_target_hardware()[0]
@ -210,3 +210,12 @@ class HassOS(CoreSysAttributes):
await self.instance.install(self.version_cli, latest=True) await self.instance.install(self.version_cli, latest=True)
except DockerAPIError: except DockerAPIError:
_LOGGER.error("Repairing of HassOS CLI fails") _LOGGER.error("Repairing of HassOS CLI fails")
async def mark_healthy(self):
"""Set booted partition as good for rauc."""
try:
response = await self.sys_dbus.rauc.mark(RaucState.GOOD, "booted")
except DBusError:
_LOGGER.error("Can't mark booted partition as healty!")
else:
_LOGGER.info("Rauc: %s - %s", self.sys_dbus.rauc.boot_slot, response[1])

View File

@ -124,9 +124,10 @@ def secure_path(tar: tarfile.TarFile) -> Generator[tarfile.TarInfo, None, None]:
for member in tar: for member in tar:
file_path = Path(member.name) file_path = Path(member.name)
try: try:
assert not file_path.is_absolute() if file_path.is_absolute():
raise ValueError()
Path("/fake", file_path).resolve().relative_to("/fake") Path("/fake", file_path).resolve().relative_to("/fake")
except (ValueError, RuntimeError, AssertionError): except (ValueError, RuntimeError):
_LOGGER.warning("Issue with file %s", file_path) _LOGGER.warning("Issue with file %s", file_path)
continue continue
else: else: