Multicast support on Hass.io Network (#1634)

* Add multicast layer to docker

* support network forward

* add pluginmanager

* finish multicast plugin

* fix lint

* Add shutdown for plugins

* Add API

* Add watchdog

* Fix black

* Fix path
This commit is contained in:
Pascal Vizeli 2020-04-05 23:26:22 +02:00 committed by GitHub
parent 2364e1e652
commit f0ed2eba2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 730 additions and 171 deletions

36
API.md
View File

@ -935,6 +935,42 @@ return:
} }
``` ```
### Multicast
- GET `/multicast/info`
```json
{
"version": "1",
"version_latest": "2"
}
```
- POST `/multicast/update`
```json
{
"version": "VERSION"
}
```
- POST `/multicast/restart`
- GET `/multicast/stats`
```json
{
"cpu_percent": 0.0,
"memory_usage": 283123,
"memory_limit": 329392,
"memory_percent": 1.4,
"network_tx": 0,
"network_rx": 0,
"blk_read": 0,
"blk_write": 0
}
```
### Auth / SSO API ### Auth / SSO API
You can use the user system on homeassistant. We handle this auth system on You can use the user system on homeassistant. We handle this auth system on

View File

@ -325,10 +325,10 @@ class AddonManager(CoreSysAttributes):
for addon in self.installed: for addon in self.installed:
if not await addon.instance.is_running(): if not await addon.instance.is_running():
continue continue
self.sys_dns.add_host( self.sys_plugins.dns.add_host(
ipv4=addon.ip_address, names=[addon.hostname], write=False ipv4=addon.ip_address, names=[addon.hostname], write=False
) )
# Write hosts files # Write hosts files
with suppress(CoreDNSError): with suppress(CoreDNSError):
self.sys_dns.write_hosts() self.sys_plugins.dns.write_hosts()

View File

@ -389,7 +389,7 @@ class Addon(AddonModel):
def write_pulse(self): def write_pulse(self):
"""Write asound config to file and return True on success.""" """Write asound config to file and return True on success."""
pulse_config = self.sys_audio.pulse_client( pulse_config = self.sys_plugins.audio.pulse_client(
input_profile=self.audio_input, output_profile=self.audio_output input_profile=self.audio_input, output_profile=self.audio_output
) )

View File

@ -23,6 +23,7 @@ from .security import SecurityMiddleware
from .services import APIServices from .services import APIServices
from .snapshots import APISnapshots from .snapshots import APISnapshots
from .supervisor import APISupervisor from .supervisor import APISupervisor
from .multicast import APIMulticast
_LOGGER: logging.Logger = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
@ -52,6 +53,7 @@ class RestAPI(CoreSysAttributes):
self._register_host() self._register_host()
self._register_os() self._register_os()
self._register_cli() self._register_cli()
self._register_multicast()
self._register_hardware() self._register_hardware()
self._register_homeassistant() self._register_homeassistant()
self._register_proxy() self._register_proxy()
@ -113,6 +115,20 @@ class RestAPI(CoreSysAttributes):
] ]
) )
def _register_multicast(self) -> None:
"""Register Multicast functions."""
api_multicast = APIMulticast()
api_multicast.coresys = self.coresys
self.webapp.add_routes(
[
web.get("/multicast/info", api_multicast.info),
web.get("/multicast/stats", api_multicast.stats),
web.post("/multicast/update", api_multicast.update),
web.post("/multicast/restart", api_multicast.restart),
]
)
def _register_hardware(self) -> None: def _register_hardware(self) -> None:
"""Register hardware functions.""" """Register hardware functions."""
api_hardware = APIHardware() api_hardware = APIHardware()

View File

@ -68,8 +68,8 @@ class APIAudio(CoreSysAttributes):
async def info(self, request: web.Request) -> Dict[str, Any]: async def info(self, request: web.Request) -> Dict[str, Any]:
"""Return Audio information.""" """Return Audio information."""
return { return {
ATTR_VERSION: self.sys_audio.version, ATTR_VERSION: self.sys_plugins.audio.version,
ATTR_VERSION_LATEST: self.sys_audio.latest_version, ATTR_VERSION_LATEST: self.sys_plugins.audio.latest_version,
ATTR_HOST: str(self.sys_docker.network.audio), ATTR_HOST: str(self.sys_docker.network.audio),
ATTR_AUDIO: { ATTR_AUDIO: {
ATTR_CARD: [attr.asdict(card) for card in self.sys_host.sound.cards], ATTR_CARD: [attr.asdict(card) for card in self.sys_host.sound.cards],
@ -88,7 +88,7 @@ class APIAudio(CoreSysAttributes):
@api_process @api_process
async def stats(self, request: web.Request) -> Dict[str, Any]: async def stats(self, request: web.Request) -> Dict[str, Any]:
"""Return resource information.""" """Return resource information."""
stats = await self.sys_audio.stats() stats = await self.sys_plugins.audio.stats()
return { return {
ATTR_CPU_PERCENT: stats.cpu_percent, ATTR_CPU_PERCENT: stats.cpu_percent,
@ -105,21 +105,21 @@ class APIAudio(CoreSysAttributes):
async def update(self, request: web.Request) -> None: async def update(self, request: web.Request) -> None:
"""Update Audio plugin.""" """Update Audio plugin."""
body = await api_validate(SCHEMA_VERSION, request) body = await api_validate(SCHEMA_VERSION, request)
version = body.get(ATTR_VERSION, self.sys_audio.latest_version) version = body.get(ATTR_VERSION, self.sys_plugins.audio.latest_version)
if version == self.sys_audio.version: if version == self.sys_plugins.audio.version:
raise APIError("Version {} is already in use".format(version)) raise APIError("Version {} is already in use".format(version))
await asyncio.shield(self.sys_audio.update(version)) await asyncio.shield(self.sys_plugins.audio.update(version))
@api_process_raw(CONTENT_TYPE_BINARY) @api_process_raw(CONTENT_TYPE_BINARY)
def logs(self, request: web.Request) -> Awaitable[bytes]: def logs(self, request: web.Request) -> Awaitable[bytes]:
"""Return Audio Docker logs.""" """Return Audio Docker logs."""
return self.sys_audio.logs() return self.sys_plugins.audio.logs()
@api_process @api_process
def restart(self, request: web.Request) -> Awaitable[None]: def restart(self, request: web.Request) -> Awaitable[None]:
"""Restart Audio plugin.""" """Restart Audio plugin."""
return asyncio.shield(self.sys_audio.restart()) return asyncio.shield(self.sys_plugins.audio.restart())
@api_process @api_process
def reload(self, request: web.Request) -> Awaitable[None]: def reload(self, request: web.Request) -> Awaitable[None]:

View File

@ -33,14 +33,14 @@ class APICli(CoreSysAttributes):
async def info(self, request: web.Request) -> Dict[str, Any]: async def info(self, request: web.Request) -> Dict[str, Any]:
"""Return HA cli information.""" """Return HA cli information."""
return { return {
ATTR_VERSION: self.sys_cli.version, ATTR_VERSION: self.sys_plugins.cli.version,
ATTR_VERSION_LATEST: self.sys_cli.latest_version, ATTR_VERSION_LATEST: self.sys_plugins.cli.latest_version,
} }
@api_process @api_process
async def stats(self, request: web.Request) -> Dict[str, Any]: async def stats(self, request: web.Request) -> Dict[str, Any]:
"""Return resource information.""" """Return resource information."""
stats = await self.sys_cli.stats() stats = await self.sys_plugins.cli.stats()
return { return {
ATTR_CPU_PERCENT: stats.cpu_percent, ATTR_CPU_PERCENT: stats.cpu_percent,
@ -57,6 +57,6 @@ class APICli(CoreSysAttributes):
async def update(self, request: web.Request) -> None: async def update(self, request: web.Request) -> None:
"""Update HA CLI.""" """Update HA CLI."""
body = await api_validate(SCHEMA_VERSION, request) body = await api_validate(SCHEMA_VERSION, request)
version = body.get(ATTR_VERSION, self.sys_cli.latest_version) version = body.get(ATTR_VERSION, self.sys_plugins.cli.latest_version)
await asyncio.shield(self.sys_cli.update(version)) await asyncio.shield(self.sys_plugins.cli.update(version))

View File

@ -42,10 +42,10 @@ class APICoreDNS(CoreSysAttributes):
async def info(self, request: web.Request) -> Dict[str, Any]: async def info(self, request: web.Request) -> Dict[str, Any]:
"""Return DNS information.""" """Return DNS information."""
return { return {
ATTR_VERSION: self.sys_dns.version, ATTR_VERSION: self.sys_plugins.dns.version,
ATTR_VERSION_LATEST: self.sys_dns.latest_version, ATTR_VERSION_LATEST: self.sys_plugins.dns.latest_version,
ATTR_HOST: str(self.sys_docker.network.dns), ATTR_HOST: str(self.sys_docker.network.dns),
ATTR_SERVERS: self.sys_dns.servers, ATTR_SERVERS: self.sys_plugins.dns.servers,
ATTR_LOCALS: self.sys_host.network.dns_servers, ATTR_LOCALS: self.sys_host.network.dns_servers,
} }
@ -55,15 +55,15 @@ class APICoreDNS(CoreSysAttributes):
body = await api_validate(SCHEMA_OPTIONS, request) body = await api_validate(SCHEMA_OPTIONS, request)
if ATTR_SERVERS in body: if ATTR_SERVERS in body:
self.sys_dns.servers = body[ATTR_SERVERS] self.sys_plugins.dns.servers = body[ATTR_SERVERS]
self.sys_create_task(self.sys_dns.restart()) self.sys_create_task(self.sys_plugins.dns.restart())
self.sys_dns.save_data() self.sys_plugins.dns.save_data()
@api_process @api_process
async def stats(self, request: web.Request) -> Dict[str, Any]: async def stats(self, request: web.Request) -> Dict[str, Any]:
"""Return resource information.""" """Return resource information."""
stats = await self.sys_dns.stats() stats = await self.sys_plugins.dns.stats()
return { return {
ATTR_CPU_PERCENT: stats.cpu_percent, ATTR_CPU_PERCENT: stats.cpu_percent,
@ -80,23 +80,23 @@ class APICoreDNS(CoreSysAttributes):
async def update(self, request: web.Request) -> None: async def update(self, request: web.Request) -> None:
"""Update DNS plugin.""" """Update DNS plugin."""
body = await api_validate(SCHEMA_VERSION, request) body = await api_validate(SCHEMA_VERSION, request)
version = body.get(ATTR_VERSION, self.sys_dns.latest_version) version = body.get(ATTR_VERSION, self.sys_plugins.dns.latest_version)
if version == self.sys_dns.version: if version == self.sys_plugins.dns.version:
raise APIError("Version {} is already in use".format(version)) raise APIError("Version {} is already in use".format(version))
await asyncio.shield(self.sys_dns.update(version)) await asyncio.shield(self.sys_plugins.dns.update(version))
@api_process_raw(CONTENT_TYPE_BINARY) @api_process_raw(CONTENT_TYPE_BINARY)
def logs(self, request: web.Request) -> Awaitable[bytes]: def logs(self, request: web.Request) -> Awaitable[bytes]:
"""Return DNS Docker logs.""" """Return DNS Docker logs."""
return self.sys_dns.logs() return self.sys_plugins.dns.logs()
@api_process @api_process
def restart(self, request: web.Request) -> Awaitable[None]: def restart(self, request: web.Request) -> Awaitable[None]:
"""Restart CoreDNS plugin.""" """Restart CoreDNS plugin."""
return asyncio.shield(self.sys_dns.restart()) return asyncio.shield(self.sys_plugins.dns.restart())
@api_process @api_process
def reset(self, request: web.Request) -> Awaitable[None]: def reset(self, request: web.Request) -> Awaitable[None]:
"""Reset CoreDNS plugin.""" """Reset CoreDNS plugin."""
return asyncio.shield(self.sys_dns.reset()) return asyncio.shield(self.sys_plugins.dns.reset())

View File

@ -0,0 +1,76 @@
"""Init file for Supervisor Multicast RESTful API."""
import asyncio
import logging
from typing import Any, Awaitable, Dict
from aiohttp import web
import voluptuous as vol
from ..const import (
ATTR_BLK_READ,
ATTR_BLK_WRITE,
ATTR_CPU_PERCENT,
ATTR_VERSION_LATEST,
ATTR_MEMORY_LIMIT,
ATTR_MEMORY_PERCENT,
ATTR_MEMORY_USAGE,
ATTR_NETWORK_RX,
ATTR_NETWORK_TX,
ATTR_VERSION,
CONTENT_TYPE_BINARY,
)
from ..coresys import CoreSysAttributes
from ..exceptions import APIError
from .utils import api_process, api_process_raw, api_validate
_LOGGER: logging.Logger = logging.getLogger(__name__)
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})
class APIMulticast(CoreSysAttributes):
"""Handle RESTful API for Multicast functions."""
@api_process
async def info(self, request: web.Request) -> Dict[str, Any]:
"""Return Multicast information."""
return {
ATTR_VERSION: self.sys_plugins.multicast.version,
ATTR_VERSION_LATEST: self.sys_plugins.multicast.latest_version,
}
@api_process
async def stats(self, request: web.Request) -> Dict[str, Any]:
"""Return resource information."""
stats = await self.sys_plugins.multicast.stats()
return {
ATTR_CPU_PERCENT: stats.cpu_percent,
ATTR_MEMORY_USAGE: stats.memory_usage,
ATTR_MEMORY_LIMIT: stats.memory_limit,
ATTR_MEMORY_PERCENT: stats.memory_percent,
ATTR_NETWORK_RX: stats.network_rx,
ATTR_NETWORK_TX: stats.network_tx,
ATTR_BLK_READ: stats.blk_read,
ATTR_BLK_WRITE: stats.blk_write,
}
@api_process
async def update(self, request: web.Request) -> None:
"""Update Multicast plugin."""
body = await api_validate(SCHEMA_VERSION, request)
version = body.get(ATTR_VERSION, self.sys_plugins.multicast.latest_version)
if version == self.sys_plugins.multicast.version:
raise APIError("Version {} is already in use".format(version))
await asyncio.shield(self.sys_plugins.multicast.update(version))
@api_process_raw(CONTENT_TYPE_BINARY)
def logs(self, request: web.Request) -> Awaitable[bytes]:
"""Return Multicast Docker logs."""
return self.sys_plugins.multicast.logs()
@api_process
def restart(self, request: web.Request) -> Awaitable[None]:
"""Restart Multicast plugin."""
return asyncio.shield(self.sys_plugins.multicast.restart())

View File

@ -130,7 +130,7 @@ class SecurityMiddleware(CoreSysAttributes):
# Host # Host
# Remove machine_id handling later if all use new CLI # Remove machine_id handling later if all use new CLI
if supervisor_token in (self.sys_machine_id, self.sys_cli.supervisor_token): if supervisor_token in (self.sys_machine_id, self.sys_plugins.cli.supervisor_token):
_LOGGER.debug("%s access from Host", request.path) _LOGGER.debug("%s access from Host", request.path)
request_from = self.sys_host request_from = self.sys_host

View File

@ -35,10 +35,8 @@ from .supervisor import Supervisor
from .tasks import Tasks from .tasks import Tasks
from .updater import Updater from .updater import Updater
from .secrets import SecretsManager from .secrets import SecretsManager
from .plugins import PluginManager
from .utils.dt import fetch_timezone from .utils.dt import fetch_timezone
from .plugins.dns import CoreDNS
from .plugins.cli import HaCli
from .plugins.audio import Audio
_LOGGER: logging.Logger = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
@ -52,9 +50,8 @@ async def initialize_coresys():
# Initialize core objects # Initialize core objects
coresys.core = Core(coresys) coresys.core = Core(coresys)
coresys.dns = CoreDNS(coresys) coresys.plugins = PluginManager(coresys)
coresys.arch = CpuArch(coresys) coresys.arch = CpuArch(coresys)
coresys.audio = Audio(coresys)
coresys.auth = Auth(coresys) coresys.auth = Auth(coresys)
coresys.updater = Updater(coresys) coresys.updater = Updater(coresys)
coresys.api = RestAPI(coresys) coresys.api = RestAPI(coresys)
@ -72,7 +69,6 @@ async def initialize_coresys():
coresys.dbus = DBusManager(coresys) coresys.dbus = DBusManager(coresys)
coresys.hassos = HassOS(coresys) coresys.hassos = HassOS(coresys)
coresys.secrets = SecretsManager(coresys) coresys.secrets = SecretsManager(coresys)
coresys.cli = HaCli(coresys)
# bootstrap config # bootstrap config
initialize_system_data(coresys) initialize_system_data(coresys)

View File

@ -28,6 +28,7 @@ FILE_HASSIO_INGRESS = Path(SUPERVISOR_DATA, "ingress.json")
FILE_HASSIO_DNS = Path(SUPERVISOR_DATA, "dns.json") FILE_HASSIO_DNS = Path(SUPERVISOR_DATA, "dns.json")
FILE_HASSIO_AUDIO = Path(SUPERVISOR_DATA, "audio.json") FILE_HASSIO_AUDIO = Path(SUPERVISOR_DATA, "audio.json")
FILE_HASSIO_CLI = Path(SUPERVISOR_DATA, "cli.json") FILE_HASSIO_CLI = Path(SUPERVISOR_DATA, "cli.json")
FILE_HASSIO_MULTICAST = Path(SUPERVISOR_DATA, "multicast.json")
SOCKET_DOCKER = Path("/run/docker.sock") SOCKET_DOCKER = Path("/run/docker.sock")
@ -67,6 +68,7 @@ HEADER_TOKEN_OLD = "X-Hassio-Key"
ENV_TOKEN_OLD = "HASSIO_TOKEN" ENV_TOKEN_OLD = "HASSIO_TOKEN"
ENV_TOKEN = "SUPERVISOR_TOKEN" ENV_TOKEN = "SUPERVISOR_TOKEN"
ENV_TIME = "TZ" ENV_TIME = "TZ"
ENV_HASSIO_NETWORK = "HASSIO_NETWORK"
ENV_HOMEASSISTANT_REPOSITORY = "HOMEASSISTANT_REPOSITORY" ENV_HOMEASSISTANT_REPOSITORY = "HOMEASSISTANT_REPOSITORY"
ENV_SUPERVISOR_SHARE = "SUPERVISOR_SHARE" ENV_SUPERVISOR_SHARE = "SUPERVISOR_SHARE"
@ -77,6 +79,7 @@ REQUEST_FROM = "HASSIO_FROM"
ATTR_SUPERVISOR = "supervisor" ATTR_SUPERVISOR = "supervisor"
ATTR_MACHINE = "machine" ATTR_MACHINE = "machine"
ATTR_MULTICAST = "multicast"
ATTR_WAIT_BOOT = "wait_boot" ATTR_WAIT_BOOT = "wait_boot"
ATTR_DEPLOYMENT = "deployment" ATTR_DEPLOYMENT = "deployment"
ATTR_WATCHDOG = "watchdog" ATTR_WATCHDOG = "watchdog"

View File

@ -41,9 +41,7 @@ class Core(CoreSysAttributes):
await self.sys_host.load() await self.sys_host.load()
# Load Plugins container # Load Plugins container
await asyncio.wait( await self.sys_plugins.load()
[self.sys_dns.load(), self.sys_audio.load(), self.sys_cli.load()]
)
# Load Home Assistant # Load Home Assistant
await self.sys_homeassistant.load() await self.sys_homeassistant.load()
@ -172,7 +170,7 @@ class Core(CoreSysAttributes):
self.sys_websession.close(), self.sys_websession.close(),
self.sys_websession_ssl.close(), self.sys_websession_ssl.close(),
self.sys_ingress.unload(), self.sys_ingress.unload(),
self.sys_dns.unload(), self.sys_plugins.unload(),
self.sys_hwmonitor.unload(), self.sys_hwmonitor.unload(),
] ]
) )
@ -193,6 +191,9 @@ class Core(CoreSysAttributes):
await self.sys_addons.shutdown(STARTUP_SYSTEM) await self.sys_addons.shutdown(STARTUP_SYSTEM)
await self.sys_addons.shutdown(STARTUP_INITIALIZE) await self.sys_addons.shutdown(STARTUP_INITIALIZE)
# Shutdown all Plugins
await self.sys_plugins.shutdown()
def _update_last_boot(self): def _update_last_boot(self):
"""Update last boot time.""" """Update last boot time."""
self.sys_config.last_boot = self.sys_hardware.last_boot self.sys_config.last_boot = self.sys_hardware.last_boot
@ -204,9 +205,7 @@ class Core(CoreSysAttributes):
await self.sys_run_in_executor(self.sys_docker.repair) await self.sys_run_in_executor(self.sys_docker.repair)
# Fix plugins # Fix plugins
await asyncio.wait( await self.sys_plugins.repair()
[self.sys_dns.repair(), self.sys_audio.repair(), self.sys_cli.repair()]
)
# Restore core functionality # Restore core functionality
await self.sys_addons.repair() await self.sys_addons.repair()

View File

@ -31,9 +31,7 @@ if TYPE_CHECKING:
from .store import StoreManager from .store import StoreManager
from .tasks import Tasks from .tasks import Tasks
from .updater import Updater from .updater import Updater
from .plugins.cli import HaCli from .plugins import PluginManager
from .plugins.audio import Audio
from .plugins.dns import CoreDNS
class CoreSys: class CoreSys:
@ -61,10 +59,7 @@ class CoreSys:
# Internal objects pointers # Internal objects pointers
self._core: Optional[Core] = None self._core: Optional[Core] = None
self._arch: Optional[CpuArch] = None self._arch: Optional[CpuArch] = None
self._audio: Optional[Audio] = None
self._auth: Optional[Auth] = None self._auth: Optional[Auth] = None
self._dns: Optional[CoreDNS] = None
self._cli: Optional[HaCli] = None
self._homeassistant: Optional[HomeAssistant] = None self._homeassistant: Optional[HomeAssistant] = None
self._supervisor: Optional[Supervisor] = None self._supervisor: Optional[Supervisor] = None
self._addons: Optional[AddonManager] = None self._addons: Optional[AddonManager] = None
@ -81,6 +76,7 @@ class CoreSys:
self._store: Optional[StoreManager] = None self._store: Optional[StoreManager] = None
self._discovery: Optional[Discovery] = None self._discovery: Optional[Discovery] = None
self._hwmonitor: Optional[HwMonitor] = None self._hwmonitor: Optional[HwMonitor] = None
self._plugins: Optional[PluginManager] = None
@property @property
def dev(self) -> bool: def dev(self) -> bool:
@ -140,16 +136,16 @@ class CoreSys:
self._core = value self._core = value
@property @property
def cli(self) -> HaCli: def plugins(self) -> PluginManager:
"""Return HaCli object.""" """Return PluginManager object."""
return self._cli return self._plugins
@cli.setter @plugins.setter
def cli(self, value: HaCli): def plugins(self, value: PluginManager):
"""Set a HaCli object.""" """Set a PluginManager object."""
if self._cli: if self._plugins:
raise RuntimeError("HaCli already set!") raise RuntimeError("PluginManager already set!")
self._cli = value self._plugins = value
@property @property
def arch(self) -> CpuArch: def arch(self) -> CpuArch:
@ -175,18 +171,6 @@ class CoreSys:
raise RuntimeError("Auth already set!") raise RuntimeError("Auth already set!")
self._auth = value self._auth = value
@property
def audio(self) -> Audio:
"""Return Audio object."""
return self._audio
@audio.setter
def audio(self, value: Audio):
"""Set a Audio object."""
if self._audio:
raise RuntimeError("Audio already set!")
self._audio = value
@property @property
def homeassistant(self) -> HomeAssistant: def homeassistant(self) -> HomeAssistant:
"""Return Home Assistant object.""" """Return Home Assistant object."""
@ -331,18 +315,6 @@ class CoreSys:
raise RuntimeError("DBusManager already set!") raise RuntimeError("DBusManager already set!")
self._dbus = value self._dbus = value
@property
def dns(self) -> CoreDNS:
"""Return CoreDNS object."""
return self._dns
@dns.setter
def dns(self, value: CoreDNS):
"""Set a CoreDNS object."""
if self._dns:
raise RuntimeError("CoreDNS already set!")
self._dns = value
@property @property
def host(self) -> HostManager: def host(self) -> HostManager:
"""Return HostManager object.""" """Return HostManager object."""
@ -482,9 +454,9 @@ class CoreSysAttributes:
return self.coresys.core return self.coresys.core
@property @property
def sys_cli(self) -> HaCli: def sys_plugins(self) -> PluginManager:
"""Return HaCli object.""" """Return PluginManager object."""
return self.coresys.cli return self.coresys.plugins
@property @property
def sys_arch(self) -> CpuArch: def sys_arch(self) -> CpuArch:
@ -496,11 +468,6 @@ class CoreSysAttributes:
"""Return Auth object.""" """Return Auth object."""
return self.coresys.auth return self.coresys.auth
@property
def sys_audio(self) -> Audio:
"""Return Audio object."""
return self.coresys.audio
@property @property
def sys_homeassistant(self) -> HomeAssistant: def sys_homeassistant(self) -> HomeAssistant:
"""Return Home Assistant object.""" """Return Home Assistant object."""
@ -561,11 +528,6 @@ class CoreSysAttributes:
"""Return DBusManager object.""" """Return DBusManager object."""
return self.coresys.dbus return self.coresys.dbus
@property
def sys_dns(self) -> CoreDNS:
"""Return CoreDNS object."""
return self.coresys.dns
@property @property
def sys_host(self) -> HostManager: def sys_host(self) -> HostManager:
"""Return HostManager object.""" """Return HostManager object."""

View File

@ -308,11 +308,11 @@ class DockerAddon(DockerInterface):
"bind": "/etc/pulse/client.conf", "bind": "/etc/pulse/client.conf",
"mode": "ro", "mode": "ro",
}, },
str(self.sys_audio.path_extern_pulse): { str(self.sys_plugins.audio.path_extern_pulse): {
"bind": "/run/audio", "bind": "/run/audio",
"mode": "ro", "mode": "ro",
}, },
str(self.sys_audio.path_extern_asound): { str(self.sys_plugins.audio.path_extern_asound): {
"bind": "/etc/asound.conf", "bind": "/etc/asound.conf",
"mode": "ro", "mode": "ro",
}, },
@ -364,7 +364,7 @@ class DockerAddon(DockerInterface):
_LOGGER.info("Start Docker add-on %s with version %s", self.image, self.version) _LOGGER.info("Start Docker add-on %s with version %s", self.image, self.version)
# Write data to DNS server # Write data to DNS server
self.sys_dns.add_host(ipv4=self.ip_address, names=[self.addon.hostname]) self.sys_plugins.dns.add_host(ipv4=self.ip_address, names=[self.addon.hostname])
def _install( def _install(
self, tag: str, image: Optional[str] = None, latest: bool = False self, tag: str, image: Optional[str] = None, latest: bool = False
@ -490,5 +490,5 @@ class DockerAddon(DockerInterface):
Need run inside executor. Need run inside executor.
""" """
if self.ip_address != NO_ADDDRESS: if self.ip_address != NO_ADDDRESS:
self.sys_dns.delete_host(self.addon.hostname) self.sys_plugins.dns.delete_host(self.addon.hostname)
super()._stop(remove_container) super()._stop(remove_container)

View File

@ -20,7 +20,7 @@ class DockerAudio(DockerInterface, CoreSysAttributes):
@property @property
def image(self) -> str: def image(self) -> str:
"""Return name of Supervisor Audio image.""" """Return name of Supervisor Audio image."""
return self.sys_audio.image return self.sys_plugins.audio.image
@property @property
def name(self) -> str: def name(self) -> str:
@ -58,7 +58,7 @@ class DockerAudio(DockerInterface, CoreSysAttributes):
# Create & Run container # Create & Run container
docker_container = self.sys_docker.run( docker_container = self.sys_docker.run(
self.image, self.image,
version=self.sys_audio.version, version=self.sys_plugins.audio.version,
init=False, init=False,
ipv4=self.sys_docker.network.audio, ipv4=self.sys_docker.network.audio,
name=self.name, name=self.name,

View File

@ -18,7 +18,7 @@ class DockerCli(DockerInterface, CoreSysAttributes):
@property @property
def image(self): def image(self):
"""Return name of HA cli image.""" """Return name of HA cli image."""
return self.sys_cli.image return self.sys_plugins.cli.image
@property @property
def name(self) -> str: def name(self) -> str:
@ -42,7 +42,7 @@ class DockerCli(DockerInterface, CoreSysAttributes):
self.image, self.image,
entrypoint=["/init"], entrypoint=["/init"],
command=["/bin/bash", "-c", "sleep infinity"], command=["/bin/bash", "-c", "sleep infinity"],
version=self.sys_cli.version, version=self.sys_plugins.cli.version,
init=False, init=False,
ipv4=self.sys_docker.network.cli, ipv4=self.sys_docker.network.cli,
name=self.name, name=self.name,
@ -51,7 +51,7 @@ class DockerCli(DockerInterface, CoreSysAttributes):
extra_hosts={"supervisor": self.sys_docker.network.supervisor}, extra_hosts={"supervisor": self.sys_docker.network.supervisor},
environment={ environment={
ENV_TIME: self.sys_timezone, ENV_TIME: self.sys_timezone,
ENV_TOKEN: self.sys_cli.supervisor_token, ENV_TOKEN: self.sys_plugins.cli.supervisor_token,
}, },
) )

View File

@ -18,7 +18,7 @@ class DockerDNS(DockerInterface, CoreSysAttributes):
@property @property
def image(self) -> str: def image(self) -> str:
"""Return name of Supervisor DNS image.""" """Return name of Supervisor DNS image."""
return self.sys_dns.image return self.sys_plugins.dns.image
@property @property
def name(self) -> str: def name(self) -> str:
@ -40,7 +40,7 @@ class DockerDNS(DockerInterface, CoreSysAttributes):
# Create & Run container # Create & Run container
docker_container = self.sys_docker.run( docker_container = self.sys_docker.run(
self.image, self.image,
version=self.sys_dns.version, version=self.sys_plugins.dns.version,
init=False, init=False,
dns=False, dns=False,
ipv4=self.sys_docker.network.dns, ipv4=self.sys_docker.network.dns,

View File

@ -72,11 +72,11 @@ class DockerHomeAssistant(DockerInterface):
"bind": "/etc/pulse/client.conf", "bind": "/etc/pulse/client.conf",
"mode": "ro", "mode": "ro",
}, },
str(self.sys_audio.path_extern_pulse): { str(self.sys_plugins.audio.path_extern_pulse): {
"bind": "/run/audio", "bind": "/run/audio",
"mode": "ro", "mode": "ro",
}, },
str(self.sys_audio.path_extern_asound): { str(self.sys_plugins.audio.path_extern_asound): {
"bind": "/etc/asound.conf", "bind": "/etc/asound.conf",
"mode": "ro", "mode": "ro",
}, },

View File

@ -0,0 +1,59 @@
"""HA Cli docker object."""
from contextlib import suppress
import logging
from ..const import DOCKER_NETWORK_MASK, ENV_HASSIO_NETWORK, ENV_TIME
from ..coresys import CoreSysAttributes
from ..exceptions import DockerAPIError
from .interface import DockerInterface
_LOGGER: logging.Logger = logging.getLogger(__name__)
MULTICAST_DOCKER_NAME: str = "hassio_multicast"
class DockerMulticast(DockerInterface, CoreSysAttributes):
"""Docker Supervisor wrapper for HA multicast."""
@property
def image(self):
"""Return name of HA multicast image."""
return self.sys_plugins.multicast.image
@property
def name(self) -> str:
"""Return name of Docker container."""
return MULTICAST_DOCKER_NAME
def _run(self) -> None:
"""Run Docker image.
Need run inside executor.
"""
if self._is_running():
return
# Cleanup
with suppress(DockerAPIError):
self._stop()
# Create & Run container
docker_container = self.sys_docker.run(
self.image,
version=self.sys_plugins.multicast.version,
init=False,
name=self.name,
hostname=self.name.replace("_", "-"),
network_mode="host",
detach=True,
extra_hosts={"supervisor": self.sys_docker.network.supervisor},
environment={
ENV_TIME: self.sys_timezone,
ENV_HASSIO_NETWORK: str(DOCKER_NETWORK_MASK),
},
)
self._meta = docker_container.attrs
_LOGGER.info(
"Start Multicast %s with version %s - Host", self.image, self.version
)

View File

@ -61,10 +61,21 @@ class CliError(HassioError):
"""HA cli exception.""" """HA cli exception."""
class CliUpdateError(HassOSError): class CliUpdateError(CliError):
"""Error on update of a HA cli.""" """Error on update of a HA cli."""
# Multicast
class MulticastError(HassioError):
"""Multicast exception."""
class MulticastUpdateError(MulticastError):
"""Error on update of a multicast."""
# DNS # DNS

View File

@ -636,7 +636,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
def write_pulse(self): def write_pulse(self):
"""Write asound config to file and return True on success.""" """Write asound config to file and return True on success."""
pulse_config = self.sys_audio.pulse_client( pulse_config = self.sys_plugins.audio.pulse_client(
input_profile=self.audio_input, output_profile=self.audio_output input_profile=self.audio_input, output_profile=self.audio_output
) )

View File

@ -1 +1,76 @@
"""Plugin for Supervisor backend.""" """Plugin for Supervisor backend."""
import asyncio
import logging
from ..coresys import CoreSys, CoreSysAttributes
from .audio import Audio
from .cli import HaCli
from .dns import CoreDNS
from .multicast import Multicast
_LOGGER: logging.Logger = logging.getLogger(__name__)
class PluginManager(CoreSysAttributes):
"""Manage supported function for plugins."""
def __init__(self, coresys: CoreSys):
"""Initialize plugin manager."""
self.coresys: CoreSys = coresys
self._cli: HaCli = HaCli(coresys)
self._dns: CoreDNS = CoreDNS(coresys)
self._audio: Audio = Audio(coresys)
self._multicast: Multicast = Multicast(coresys)
@property
def cli(self) -> HaCli:
"""Return cli handler."""
return self._cli
@property
def dns(self) -> CoreDNS:
"""Return dns handler."""
return self._dns
@property
def audio(self) -> Audio:
"""Return audio handler."""
return self._audio
@property
def multicast(self) -> Multicast:
"""Return multicast handler."""
return self._multicast
async def load(self):
"""Load Supervisor plugins."""
await asyncio.wait(
[self.dns.load(), self.audio.load(), self.cli.load(), self.multicast.load()]
)
async def repair(self):
"""Repair Supervisor plugins."""
await asyncio.wait(
[
self.dns.repair(),
self.audio.repair(),
self.cli.repair(),
self.multicast.repair(),
]
)
async def unload(self) -> None:
"""Unload Supervisor plugin."""
await asyncio.wait([self.dns.unload()])
async def shutdown(self) -> None:
"""Shutdown Supervisor plugin."""
await asyncio.wait(
[
self.dns.stop(),
self.audio.stop(),
self.cli.stop(),
self.multicast.stop(),
]
)

View File

@ -1,4 +1,7 @@
"""Home Assistant control object.""" """Home Assistant audio plugin.
Code: https://github.com/home-assistant/plugin-audio
"""
import asyncio import asyncio
from contextlib import suppress from contextlib import suppress
import logging import logging
@ -14,12 +17,12 @@ from ..docker.audio import DockerAudio
from ..docker.stats import DockerStats from ..docker.stats import DockerStats
from ..exceptions import AudioError, AudioUpdateError, DockerAPIError from ..exceptions import AudioError, AudioUpdateError, DockerAPIError
from ..utils.json import JsonConfig from ..utils.json import JsonConfig
from ..validate import SCHEMA_AUDIO_CONFIG from .validate import SCHEMA_AUDIO_CONFIG
_LOGGER: logging.Logger = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
PULSE_CLIENT_TMPL: Path = Path(__file__).parents[0].joinpath("data/pulse-client.tmpl") PULSE_CLIENT_TMPL: Path = Path(__file__).parents[1].joinpath("data/pulse-client.tmpl")
ASOUND_TMPL: Path = Path(__file__).parents[0].joinpath("data/asound.tmpl") ASOUND_TMPL: Path = Path(__file__).parents[1].joinpath("data/asound.tmpl")
class Audio(JsonConfig, CoreSysAttributes): class Audio(JsonConfig, CoreSysAttributes):
@ -177,7 +180,6 @@ class Audio(JsonConfig, CoreSysAttributes):
async def start(self) -> None: async def start(self) -> None:
"""Run CoreDNS.""" """Run CoreDNS."""
# Start Instance
_LOGGER.info("Start Audio plugin") _LOGGER.info("Start Audio plugin")
try: try:
await self.instance.run() await self.instance.run()
@ -185,6 +187,15 @@ class Audio(JsonConfig, CoreSysAttributes):
_LOGGER.error("Can't start Audio plugin") _LOGGER.error("Can't start Audio plugin")
raise AudioError() from None raise AudioError() from None
async def stop(self) -> None:
"""Stop CoreDNS."""
_LOGGER.info("Stop Audio plugin")
try:
await self.instance.stop()
except DockerAPIError:
_LOGGER.error("Can't stop Audio plugin")
raise AudioError() from None
def logs(self) -> Awaitable[bytes]: def logs(self) -> Awaitable[bytes]:
"""Get CoreDNS docker logs. """Get CoreDNS docker logs.

View File

@ -1,4 +1,7 @@
"""CLI support on supervisor.""" """Home Assistant cli plugin.
Code: https://github.com/home-assistant/plugin-cli
"""
import asyncio import asyncio
from contextlib import suppress from contextlib import suppress
import logging import logging
@ -11,7 +14,7 @@ from ..docker.cli import DockerCli
from ..docker.stats import DockerStats from ..docker.stats import DockerStats
from ..exceptions import CliError, CliUpdateError, DockerAPIError from ..exceptions import CliError, CliUpdateError, DockerAPIError
from ..utils.json import JsonConfig from ..utils.json import JsonConfig
from ..validate import SCHEMA_CLI_CONFIG from .validate import SCHEMA_CLI_CONFIG
_LOGGER: logging.Logger = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
@ -158,6 +161,15 @@ class HaCli(CoreSysAttributes, JsonConfig):
_LOGGER.error("Can't start cli plugin") _LOGGER.error("Can't start cli plugin")
raise CliError() from None raise CliError() from None
async def stop(self) -> None:
"""Stop cli."""
_LOGGER.info("Stop cli plugin")
try:
await self.instance.stop()
except DockerAPIError:
_LOGGER.error("Can't stop cli plugin")
raise CliError() from None
async def stats(self) -> DockerStats: async def stats(self) -> DockerStats:
"""Return stats of cli.""" """Return stats of cli."""
try: try:

View File

@ -1,4 +1,7 @@
"""Home Assistant control object.""" """Home Assistant dns plugin.
Code: https://github.com/home-assistant/plugin-dns
"""
import asyncio import asyncio
from contextlib import suppress from contextlib import suppress
from ipaddress import IPv4Address from ipaddress import IPv4Address
@ -17,12 +20,13 @@ from ..docker.stats import DockerStats
from ..exceptions import CoreDNSError, CoreDNSUpdateError, DockerAPIError from ..exceptions import CoreDNSError, CoreDNSUpdateError, DockerAPIError
from ..misc.forwarder import DNSForward from ..misc.forwarder import DNSForward
from ..utils.json import JsonConfig from ..utils.json import JsonConfig
from ..validate import SCHEMA_DNS_CONFIG, dns_url from .validate import SCHEMA_DNS_CONFIG
from ..validate import dns_url
_LOGGER: logging.Logger = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
COREDNS_TMPL: Path = Path(__file__).parents[0].joinpath("data/coredns.tmpl") COREDNS_TMPL: Path = Path(__file__).parents[1].joinpath("data/coredns.tmpl")
RESOLV_TMPL: Path = Path(__file__).parents[0].joinpath("data/resolv.tmpl") RESOLV_TMPL: Path = Path(__file__).parents[1].joinpath("data/resolv.tmpl")
HOST_RESOLV: Path = Path("/etc/resolv.conf") HOST_RESOLV: Path = Path("/etc/resolv.conf")
@ -212,8 +216,12 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
async def restart(self) -> None: async def restart(self) -> None:
"""Restart CoreDNS plugin.""" """Restart CoreDNS plugin."""
self._write_corefile() self._write_corefile()
with suppress(DockerAPIError): _LOGGER.info("Restart CoreDNS plugin")
try:
await self.instance.restart() await self.instance.restart()
except DockerAPIError:
_LOGGER.error("Can't start CoreDNS plugin")
raise CoreDNSError()
async def start(self) -> None: async def start(self) -> None:
"""Run CoreDNS.""" """Run CoreDNS."""
@ -227,6 +235,15 @@ class CoreDNS(JsonConfig, CoreSysAttributes):
_LOGGER.error("Can't start CoreDNS plugin") _LOGGER.error("Can't start CoreDNS plugin")
raise CoreDNSError() from None raise CoreDNSError() from None
async def stop(self) -> None:
"""Stop CoreDNS."""
_LOGGER.info("Stop CoreDNS plugin")
try:
await self.instance.stop()
except DockerAPIError:
_LOGGER.error("Can't stop CoreDNS plugin")
raise CoreDNSError() from None
async def reset(self) -> None: async def reset(self) -> None:
"""Reset DNS and hosts.""" """Reset DNS and hosts."""
# Reset manually defined DNS # Reset manually defined DNS

View File

@ -0,0 +1,208 @@
"""Home Assistant multicast plugin.
Code: https://github.com/home-assistant/plugin-multicast
"""
import asyncio
from contextlib import suppress
import logging
from typing import Awaitable, Optional
from ..const import ATTR_IMAGE, ATTR_VERSION, FILE_HASSIO_MULTICAST
from ..coresys import CoreSys, CoreSysAttributes
from ..docker.multicast import DockerMulticast
from ..docker.stats import DockerStats
from ..exceptions import DockerAPIError, MulticastError, MulticastUpdateError
from ..utils.json import JsonConfig
from .validate import SCHEMA_MULTICAST_CONFIG
_LOGGER: logging.Logger = logging.getLogger(__name__)
class Multicast(JsonConfig, CoreSysAttributes):
"""Home Assistant core object for handle it."""
def __init__(self, coresys: CoreSys):
"""Initialize hass object."""
super().__init__(FILE_HASSIO_MULTICAST, SCHEMA_MULTICAST_CONFIG)
self.coresys: CoreSys = coresys
self.instance: DockerMulticast = DockerMulticast(coresys)
@property
def version(self) -> Optional[str]:
"""Return current version of Multicast."""
return self._data.get(ATTR_VERSION)
@version.setter
def version(self, value: str) -> None:
"""Return current version of Multicast."""
self._data[ATTR_VERSION] = value
@property
def image(self) -> str:
"""Return current image of Multicast."""
if self._data.get(ATTR_IMAGE):
return self._data[ATTR_IMAGE]
return f"homeassistant/{self.sys_arch.supervisor}-hassio-multicast"
@image.setter
def image(self, value: str) -> None:
"""Return current image of Multicast."""
self._data[ATTR_IMAGE] = value
@property
def latest_version(self) -> Optional[str]:
"""Return latest version of Multicast."""
return self.sys_updater.version_multicast
@property
def in_progress(self) -> bool:
"""Return True if a task is in progress."""
return self.instance.in_progress
@property
def need_update(self) -> bool:
"""Return True if an update is available."""
return self.version != self.latest_version
async def load(self) -> None:
"""Load multicast setup."""
# Check Multicast state
try:
# Evaluate Version if we lost this information
if not self.version:
self.version = await self.instance.get_latest_version(key=int)
await self.instance.attach(tag=self.version)
except DockerAPIError:
_LOGGER.info(
"No Multicast plugin Docker image %s found.", self.instance.image
)
# Install Multicast plugin
with suppress(MulticastError):
await self.install()
else:
self.version = self.instance.version
self.image = self.instance.image
self.save_data()
# Run Multicast plugin
with suppress(MulticastError):
if await self.instance.is_running():
await self.restart()
else:
await self.start()
async def install(self) -> None:
"""Install Multicast."""
_LOGGER.info("Setup Multicast plugin")
while True:
# read homeassistant tag and install it
if not self.latest_version:
await self.sys_updater.reload()
if self.latest_version:
with suppress(DockerAPIError):
await self.instance.install(
self.latest_version, image=self.sys_updater.image_multicast
)
break
_LOGGER.warning("Error on install Multicast plugin. Retry in 30sec")
await asyncio.sleep(30)
_LOGGER.info("Multicast plugin now installed")
self.version = self.instance.version
self.image = self.sys_updater.image_multicast
self.save_data()
async def update(self, version: Optional[str] = None) -> None:
"""Update Multicast plugin."""
version = version or self.latest_version
old_image = self.image
if version == self.version:
_LOGGER.warning("Version %s is already installed for Multicast", version)
return
# Update
try:
await self.instance.update(version, image=self.sys_updater.image_multicast)
except DockerAPIError:
_LOGGER.error("Multicast update fails")
raise MulticastUpdateError() from None
else:
self.version = version
self.image = self.sys_updater.image_multicast
self.save_data()
# Cleanup
with suppress(DockerAPIError):
await self.instance.cleanup(old_image=old_image)
# Start Multicast plugin
await self.start()
async def restart(self) -> None:
"""Restart Multicast plugin."""
_LOGGER.info("Restart Multicast plugin")
try:
await self.instance.restart()
except DockerAPIError:
_LOGGER.error("Can't start Multicast plugin")
raise MulticastError()
async def start(self) -> None:
"""Run Multicast."""
_LOGGER.info("Start Multicast plugin")
try:
await self.instance.run()
except DockerAPIError:
_LOGGER.error("Can't start Multicast plugin")
raise MulticastError()
async def stop(self) -> None:
"""Stop Multicast."""
_LOGGER.info("Stop Multicast plugin")
try:
await self.instance.stop()
except DockerAPIError:
_LOGGER.error("Can't stop Multicast plugin")
raise MulticastError()
def logs(self) -> Awaitable[bytes]:
"""Get Multicast docker logs.
Return Coroutine.
"""
return self.instance.logs()
async def stats(self) -> DockerStats:
"""Return stats of Multicast."""
try:
return await self.instance.stats()
except DockerAPIError:
raise MulticastError() from None
def is_running(self) -> Awaitable[bool]:
"""Return True if Docker container is running.
Return a coroutine.
"""
return self.instance.is_running()
def is_fails(self) -> Awaitable[bool]:
"""Return True if a Docker container is fails state.
Return a coroutine.
"""
return self.instance.is_fails()
async def repair(self) -> None:
"""Repair Multicast plugin."""
if await self.instance.exists():
return
_LOGGER.info("Repair Multicast %s", self.version)
try:
await self.instance.install(self.version)
except DockerAPIError:
_LOGGER.error("Repairing of Multicast fails")

View File

@ -0,0 +1,44 @@
"""Validate functions."""
import voluptuous as vol
from ..const import ATTR_ACCESS_TOKEN, ATTR_IMAGE, ATTR_SERVERS, ATTR_VERSION
from ..validate import dns_server_list, docker_image, token
SCHEMA_DNS_CONFIG = vol.Schema(
{
vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_IMAGE): docker_image,
vol.Optional(ATTR_SERVERS, default=list): dns_server_list,
},
extra=vol.REMOVE_EXTRA,
)
SCHEMA_AUDIO_CONFIG = vol.Schema(
{
vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_IMAGE): docker_image,
},
extra=vol.REMOVE_EXTRA,
)
SCHEMA_CLI_CONFIG = vol.Schema(
{
vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_IMAGE): docker_image,
vol.Optional(ATTR_ACCESS_TOKEN): token,
},
extra=vol.REMOVE_EXTRA,
)
SCHEMA_MULTICAST_CONFIG = vol.Schema(
{
vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_IMAGE): docker_image,
},
extra=vol.REMOVE_EXTRA,
)

View File

@ -3,7 +3,13 @@ import asyncio
import logging import logging
from .coresys import CoreSysAttributes from .coresys import CoreSysAttributes
from .exceptions import AudioError, CliError, CoreDNSError, HomeAssistantError from .exceptions import (
AudioError,
CliError,
CoreDNSError,
HomeAssistantError,
MulticastError,
)
_LOGGER: logging.Logger = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
@ -14,6 +20,7 @@ RUN_UPDATE_ADDONS = 57600
RUN_UPDATE_CLI = 28100 RUN_UPDATE_CLI = 28100
RUN_UPDATE_DNS = 30100 RUN_UPDATE_DNS = 30100
RUN_UPDATE_AUDIO = 30200 RUN_UPDATE_AUDIO = 30200
RUN_UPDATE_MULTICAST = 30300
RUN_RELOAD_ADDONS = 10800 RUN_RELOAD_ADDONS = 10800
RUN_RELOAD_SNAPSHOTS = 72000 RUN_RELOAD_SNAPSHOTS = 72000
@ -27,6 +34,7 @@ RUN_WATCHDOG_HOMEASSISTANT_API = 300
RUN_WATCHDOG_DNS_DOCKER = 20 RUN_WATCHDOG_DNS_DOCKER = 20
RUN_WATCHDOG_AUDIO_DOCKER = 30 RUN_WATCHDOG_AUDIO_DOCKER = 30
RUN_WATCHDOG_CLI_DOCKER = 40 RUN_WATCHDOG_CLI_DOCKER = 40
RUN_WATCHDOG_MULTICAST_DOCKER = 50
class Tasks(CoreSysAttributes): class Tasks(CoreSysAttributes):
@ -58,6 +66,11 @@ class Tasks(CoreSysAttributes):
self.jobs.add( self.jobs.add(
self.sys_scheduler.register_task(self._update_audio, RUN_UPDATE_AUDIO) self.sys_scheduler.register_task(self._update_audio, RUN_UPDATE_AUDIO)
) )
self.jobs.add(
self.sys_scheduler.register_task(
self._update_multicast, RUN_UPDATE_MULTICAST
)
)
# Reload # Reload
self.jobs.add( self.jobs.add(
@ -108,6 +121,11 @@ class Tasks(CoreSysAttributes):
self._watchdog_cli_docker, RUN_WATCHDOG_CLI_DOCKER self._watchdog_cli_docker, RUN_WATCHDOG_CLI_DOCKER
) )
) )
self.jobs.add(
self.sys_scheduler.register_task(
self._watchdog_multicast_docker, RUN_WATCHDOG_MULTICAST_DOCKER
)
)
_LOGGER.info("All core tasks are scheduled") _LOGGER.info("All core tasks are scheduled")
@ -209,66 +227,92 @@ class Tasks(CoreSysAttributes):
async def _update_cli(self): async def _update_cli(self):
"""Check and run update of cli.""" """Check and run update of cli."""
if not self.sys_cli.need_update: if not self.sys_plugins.cli.need_update:
return return
_LOGGER.info("Found new cli version") _LOGGER.info("Found new cli version")
await self.sys_cli.update() await self.sys_plugins.cli.update()
async def _update_dns(self): async def _update_dns(self):
"""Check and run update of CoreDNS plugin.""" """Check and run update of CoreDNS plugin."""
if not self.sys_dns.need_update: if not self.sys_plugins.dns.need_update:
return return
_LOGGER.info("Found new CoreDNS plugin version") _LOGGER.info("Found new CoreDNS plugin version")
await self.sys_dns.update() await self.sys_plugins.dns.update()
async def _update_audio(self): async def _update_audio(self):
"""Check and run update of PulseAudio plugin.""" """Check and run update of PulseAudio plugin."""
if not self.sys_audio.need_update: if not self.sys_plugins.audio.need_update:
return return
_LOGGER.info("Found new PulseAudio plugin version") _LOGGER.info("Found new PulseAudio plugin version")
await self.sys_audio.update() await self.sys_plugins.audio.update()
async def _update_multicast(self):
"""Check and run update of multicast."""
if not self.sys_plugins.multicast.need_update:
return
_LOGGER.info("Found new Multicast version")
await self.sys_plugins.multicast.update()
async def _watchdog_dns_docker(self): async def _watchdog_dns_docker(self):
"""Check running state of Docker and start if they is close.""" """Check running state of Docker and start if they is close."""
# if CoreDNS is active # if CoreDNS is active
if await self.sys_dns.is_running() or self.sys_dns.in_progress: if await self.sys_plugins.dns.is_running() or self.sys_plugins.dns.in_progress:
return return
_LOGGER.warning("Watchdog found a problem with CoreDNS plugin!") _LOGGER.warning("Watchdog found a problem with CoreDNS plugin!")
# Reset of fails # Reset of fails
if await self.sys_dns.is_fails(): if await self.sys_plugins.dns.is_fails():
_LOGGER.error("CoreDNS plugin is in fails state / Reset config") _LOGGER.error("CoreDNS plugin is in fails state / Reset config")
await self.sys_dns.reset() await self.sys_plugins.dns.reset()
await self.sys_dns.loop_detection() await self.sys_plugins.dns.loop_detection()
try: try:
await self.sys_dns.start() await self.sys_plugins.dns.start()
except CoreDNSError: except CoreDNSError:
_LOGGER.error("Watchdog CoreDNS reanimation fails!") _LOGGER.error("Watchdog CoreDNS reanimation fails!")
async def _watchdog_audio_docker(self): async def _watchdog_audio_docker(self):
"""Check running state of Docker and start if they is close.""" """Check running state of Docker and start if they is close."""
# if PulseAudio plugin is active # if PulseAudio plugin is active
if await self.sys_audio.is_running() or self.sys_audio.in_progress: if (
await self.sys_plugins.audio.is_running()
or self.sys_plugins.audio.in_progress
):
return return
_LOGGER.warning("Watchdog found a problem with PulseAudio plugin!") _LOGGER.warning("Watchdog found a problem with PulseAudio plugin!")
try: try:
await self.sys_audio.start() await self.sys_plugins.audio.start()
except AudioError: except AudioError:
_LOGGER.error("Watchdog PulseAudio reanimation fails!") _LOGGER.error("Watchdog PulseAudio reanimation fails!")
async def _watchdog_cli_docker(self): async def _watchdog_cli_docker(self):
"""Check running state of Docker and start if they is close.""" """Check running state of Docker and start if they is close."""
# if cli plugin is active # if cli plugin is active
if await self.sys_cli.is_running() or self.sys_cli.in_progress: if await self.sys_plugins.cli.is_running() or self.sys_plugins.cli.in_progress:
return return
_LOGGER.warning("Watchdog found a problem with cli plugin!") _LOGGER.warning("Watchdog found a problem with cli plugin!")
try: try:
await self.sys_cli.start() await self.sys_plugins.cli.start()
except CliError: except CliError:
_LOGGER.error("Watchdog cli reanimation fails!") _LOGGER.error("Watchdog cli reanimation fails!")
async def _watchdog_multicast_docker(self):
"""Check running state of Docker and start if they is close."""
# if multicast plugin is active
if (
await self.sys_plugins.multicast.is_running()
or self.sys_plugins.multicast.in_progress
):
return
_LOGGER.warning("Watchdog found a problem with Multicast plugin!")
try:
await self.sys_plugins.multicast.start()
except MulticastError:
_LOGGER.error("Watchdog Multicast reanimation fails!")

View File

@ -13,10 +13,11 @@ from .const import (
ATTR_CHANNEL, ATTR_CHANNEL,
ATTR_CLI, ATTR_CLI,
ATTR_DNS, ATTR_DNS,
ATTR_SUPERVISOR,
ATTR_HASSOS, ATTR_HASSOS,
ATTR_HOMEASSISTANT, ATTR_HOMEASSISTANT,
ATTR_IMAGE, ATTR_IMAGE,
ATTR_MULTICAST,
ATTR_SUPERVISOR,
FILE_HASSIO_UPDATER, FILE_HASSIO_UPDATER,
URL_HASSIO_VERSION, URL_HASSIO_VERSION,
UpdateChannels, UpdateChannels,
@ -78,6 +79,11 @@ class Updater(JsonConfig, CoreSysAttributes):
"""Return latest version of Audio.""" """Return latest version of Audio."""
return self._data.get(ATTR_AUDIO) return self._data.get(ATTR_AUDIO)
@property
def version_multicast(self) -> Optional[str]:
"""Return latest version of Multicast."""
return self._data.get(ATTR_MULTICAST)
@property @property
def image_homeassistant(self) -> Optional[str]: def image_homeassistant(self) -> Optional[str]:
"""Return latest version of Home Assistant.""" """Return latest version of Home Assistant."""
@ -123,6 +129,15 @@ class Updater(JsonConfig, CoreSysAttributes):
.format(arch=self.sys_arch.supervisor) .format(arch=self.sys_arch.supervisor)
) )
@property
def image_multicast(self) -> Optional[str]:
"""Return latest version of Multicast."""
return (
self._data[ATTR_IMAGE]
.get(ATTR_MULTICAST, "")
.format(arch=self.sys_arch.supervisor)
)
@property @property
def channel(self) -> UpdateChannels: def channel(self) -> UpdateChannels:
"""Return upstream channel of Supervisor instance.""" """Return upstream channel of Supervisor instance."""
@ -171,10 +186,11 @@ class Updater(JsonConfig, CoreSysAttributes):
if self.sys_hassos.board: if self.sys_hassos.board:
self._data[ATTR_HASSOS] = data["hassos"][self.sys_hassos.board] self._data[ATTR_HASSOS] = data["hassos"][self.sys_hassos.board]
# Update Home Assistant services # Update Home Assistant plugins
self._data[ATTR_CLI] = data["cli"] self._data[ATTR_CLI] = data["cli"]
self._data[ATTR_DNS] = data["dns"] self._data[ATTR_DNS] = data["dns"]
self._data[ATTR_AUDIO] = data["audio"] self._data[ATTR_AUDIO] = data["audio"]
self._data[ATTR_MULTICAST] = data["multicast"]
# Update images for that versions # Update images for that versions
self._data[ATTR_IMAGE][ATTR_HOMEASSISTANT] = data["image"]["core"] self._data[ATTR_IMAGE][ATTR_HOMEASSISTANT] = data["image"]["core"]
@ -182,6 +198,7 @@ class Updater(JsonConfig, CoreSysAttributes):
self._data[ATTR_IMAGE][ATTR_AUDIO] = data["image"]["audio"] self._data[ATTR_IMAGE][ATTR_AUDIO] = data["image"]["audio"]
self._data[ATTR_IMAGE][ATTR_CLI] = data["image"]["cli"] self._data[ATTR_IMAGE][ATTR_CLI] = data["image"]["cli"]
self._data[ATTR_IMAGE][ATTR_DNS] = data["image"]["dns"] self._data[ATTR_IMAGE][ATTR_DNS] = data["image"]["dns"]
self._data[ATTR_IMAGE][ATTR_MULTICAST] = data["image"]["multicast"]
except KeyError as err: except KeyError as err:
_LOGGER.warning("Can't process version data: %s", err) _LOGGER.warning("Can't process version data: %s", err)

View File

@ -17,18 +17,18 @@ from .const import (
ATTR_DEBUG, ATTR_DEBUG,
ATTR_DEBUG_BLOCK, ATTR_DEBUG_BLOCK,
ATTR_DNS, ATTR_DNS,
ATTR_SUPERVISOR,
ATTR_HASSOS, ATTR_HASSOS,
ATTR_HOMEASSISTANT, ATTR_HOMEASSISTANT,
ATTR_IMAGE, ATTR_IMAGE,
ATTR_LAST_BOOT, ATTR_LAST_BOOT,
ATTR_LOGGING, ATTR_LOGGING,
ATTR_MULTICAST,
ATTR_PORT, ATTR_PORT,
ATTR_PORTS, ATTR_PORTS,
ATTR_REFRESH_TOKEN, ATTR_REFRESH_TOKEN,
ATTR_SERVERS,
ATTR_SESSION, ATTR_SESSION,
ATTR_SSL, ATTR_SSL,
ATTR_SUPERVISOR,
ATTR_TIMEZONE, ATTR_TIMEZONE,
ATTR_UUID, ATTR_UUID,
ATTR_VERSION, ATTR_VERSION,
@ -129,6 +129,7 @@ SCHEMA_UPDATER_CONFIG = vol.Schema(
vol.Optional(ATTR_CLI): vol.Coerce(str), vol.Optional(ATTR_CLI): vol.Coerce(str),
vol.Optional(ATTR_DNS): vol.Coerce(str), vol.Optional(ATTR_DNS): vol.Coerce(str),
vol.Optional(ATTR_AUDIO): vol.Coerce(str), vol.Optional(ATTR_AUDIO): vol.Coerce(str),
vol.Optional(ATTR_MULTICAST): vol.Coerce(str),
vol.Optional(ATTR_IMAGE, default=dict): vol.Schema( vol.Optional(ATTR_IMAGE, default=dict): vol.Schema(
{ {
vol.Optional(ATTR_HOMEASSISTANT): docker_image, vol.Optional(ATTR_HOMEASSISTANT): docker_image,
@ -136,6 +137,7 @@ SCHEMA_UPDATER_CONFIG = vol.Schema(
vol.Optional(ATTR_CLI): docker_image, vol.Optional(ATTR_CLI): docker_image,
vol.Optional(ATTR_DNS): docker_image, vol.Optional(ATTR_DNS): docker_image,
vol.Optional(ATTR_AUDIO): docker_image, vol.Optional(ATTR_AUDIO): docker_image,
vol.Optional(ATTR_MULTICAST): docker_image,
}, },
extra=vol.REMOVE_EXTRA, extra=vol.REMOVE_EXTRA,
), ),
@ -176,32 +178,3 @@ SCHEMA_INGRESS_CONFIG = vol.Schema(
}, },
extra=vol.REMOVE_EXTRA, extra=vol.REMOVE_EXTRA,
) )
SCHEMA_DNS_CONFIG = vol.Schema(
{
vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_IMAGE): docker_image,
vol.Optional(ATTR_SERVERS, default=list): dns_server_list,
},
extra=vol.REMOVE_EXTRA,
)
SCHEMA_AUDIO_CONFIG = vol.Schema(
{
vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_IMAGE): docker_image,
},
extra=vol.REMOVE_EXTRA,
)
SCHEMA_CLI_CONFIG = vol.Schema(
{
vol.Optional(ATTR_VERSION): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_IMAGE): docker_image,
vol.Optional(ATTR_ACCESS_TOKEN): token,
},
extra=vol.REMOVE_EXTRA,
)