Add resolution manager and unsupported flags (#2124)

* Add unsupported reason flags

* Restore test_network.py

* Add Resolution manager object

* fix import
This commit is contained in:
Joakim Sørensen 2020-10-13 12:54:17 +02:00 committed by GitHub
parent 8da686fc34
commit 028b170cff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 132 additions and 18 deletions

View File

@ -23,6 +23,7 @@ from .network import APINetwork
from .observer import APIObserver
from .os import APIOS
from .proxy import APIProxy
from .resolution import APIResoulution
from .security import SecurityMiddleware
from .services import APIServices
from .snapshots import APISnapshots
@ -73,6 +74,7 @@ class RestAPI(CoreSysAttributes):
self._register_os()
self._register_panel()
self._register_proxy()
self._register_resolution()
self._register_services()
self._register_snapshots()
self._register_supervisor()
@ -190,6 +192,13 @@ class RestAPI(CoreSysAttributes):
self.webapp.add_routes([web.get("/info", api_info.info)])
def _register_resolution(self) -> None:
"""Register info functions."""
api_resolution = APIResoulution()
api_resolution.coresys = self.coresys
self.webapp.add_routes([web.get("/resolution", api_resolution.base)])
def _register_auth(self) -> None:
"""Register auth functions."""
api_auth = APIAuth()

View File

@ -0,0 +1,17 @@
"""Handle REST API for resoulution."""
from typing import Any, Dict
from aiohttp import web
from ..const import ATTR_UNSUPPORTED
from ..coresys import CoreSysAttributes
from .utils import api_process
class APIResoulution(CoreSysAttributes):
"""Handle REST API for resoulution."""
@api_process
async def base(self, request: web.Request) -> Dict[str, Any]:
"""Return network information."""
return {ATTR_UNSUPPORTED: self.sys_resolution.unsupported}

View File

@ -39,6 +39,7 @@ from .misc.hwmon import HwMonitor
from .misc.scheduler import Scheduler
from .misc.tasks import Tasks
from .plugins import PluginManager
from .resolution import ResolutionManager
from .services import ServiceManager
from .snapshots import SnapshotManager
from .store import StoreManager
@ -54,6 +55,7 @@ async def initialize_coresys() -> CoreSys:
coresys = CoreSys()
# Initialize core objects
coresys.resolution = ResolutionManager(coresys)
coresys.core = Core(coresys)
coresys.plugins = PluginManager(coresys)
coresys.arch = CpuArch(coresys)

View File

@ -261,6 +261,7 @@ ATTR_TOTP = "totp"
ATTR_TYPE = "type"
ATTR_UDEV = "udev"
ATTR_UNSAVED = "unsaved"
ATTR_UNSUPPORTED = "unsupported"
ATTR_URL = "url"
ATTR_USB = "usb"
ATTR_USER = "user"
@ -428,3 +429,17 @@ class HostFeature(str, Enum):
REBOOT = "reboot"
SERVICES = "services"
SHUTDOWN = "shutdown"
class UnsupportedReason(str, Enum):
"""Reasons for unsupported status."""
CONTAINER = "container"
DBUS = "dbus"
DOCKER_CONFIGURATION = "docker_configuration"
DOCKER_VERSION = "docker_version"
LXC = "lxc"
NETWORK_MANAGER = "network_manager"
OS = "os"
PRIVILEGED = "privileged"
SYSTEMD = "systemd"

View File

@ -13,6 +13,7 @@ from .const import (
AddonStartup,
CoreState,
HostFeature,
UnsupportedReason,
)
from .coresys import CoreSys, CoreSysAttributes
from .exceptions import (
@ -33,8 +34,6 @@ class Core(CoreSysAttributes):
"""Initialize Supervisor object."""
self.coresys: CoreSys = coresys
self.healthy: bool = True
self.supported: bool = True
self._state: Optional[CoreState] = None
@property
@ -42,6 +41,11 @@ class Core(CoreSysAttributes):
"""Return state of the core."""
return self._state
@property
def supported(self) -> CoreState:
"""Return true if the installation is supported."""
return len(self.sys_resolution.unsupported) == 0
@state.setter
def state(self, new_state: CoreState) -> None:
"""Set core into new state."""
@ -61,29 +65,29 @@ class Core(CoreSysAttributes):
# If host docker is supported?
if not self.sys_docker.info.supported_version:
self.supported = False
self.sys_resolution.unsupported = UnsupportedReason.DOCKER_VERSION
self.healthy = False
_LOGGER.error(
"Docker version %s is not supported by Supervisor!",
self.sys_docker.info.version,
)
elif self.sys_docker.info.inside_lxc:
self.supported = False
self.sys_resolution.unsupported = UnsupportedReason.LXC
self.healthy = False
_LOGGER.error(
"Detected Docker running inside LXC. Running Home Assistant with the Supervisor on LXC is not supported!"
)
elif not self.sys_supervisor.instance.privileged:
self.supported = False
self.sys_resolution.unsupported = UnsupportedReason.PRIVILEGED
self.healthy = False
_LOGGER.error("Supervisor does not run in Privileged mode.")
if self.sys_docker.info.check_requirements():
self.supported = False
self.sys_resolution.unsupported = UnsupportedReason.DOCKER_CONFIGURATION
# Dbus available
if not SOCKET_DBUS.exists():
self.supported = False
self.sys_resolution.unsupported = UnsupportedReason.DBUS
_LOGGER.error(
"DBus is required for Home Assistant. This system is not supported!"
)
@ -158,7 +162,7 @@ class Core(CoreSysAttributes):
# Check supported OS
if not self.sys_hassos.available:
if self.sys_host.info.operating_system not in SUPERVISED_SUPPORTED_OS:
self.supported = False
self.sys_resolution.unsupported = UnsupportedReason.OS
_LOGGER.error(
"Detected unsupported OS: %s",
self.sys_host.info.operating_system,
@ -166,7 +170,7 @@ class Core(CoreSysAttributes):
# Check Host features
if HostFeature.NETWORK not in self.sys_host.supported_features:
self.supported = False
self.sys_resolution.unsupported = UnsupportedReason.NETWORK_MANAGER
_LOGGER.error("NetworkManager is not correct working")
if any(
feature not in self.sys_host.supported_features
@ -177,13 +181,13 @@ class Core(CoreSysAttributes):
HostFeature.REBOOT,
)
):
self.supported = False
self.sys_resolution.unsupported = UnsupportedReason.SYSTEMD
_LOGGER.error("Systemd is not correct working")
# Check if image names from denylist exist
try:
if await self.sys_run_in_executor(self.sys_docker.check_denylist_images):
self.supported = False
self.sys_resolution.unsupported = UnsupportedReason.CONTAINER
self.healthy = False
except DockerError:
self.healthy = False

View File

@ -33,6 +33,7 @@ if TYPE_CHECKING:
from .store import StoreManager
from .updater import Updater
from .plugins import PluginManager
from .resolution import ResolutionManager
T = TypeVar("T")
@ -80,6 +81,7 @@ class CoreSys:
self._discovery: Optional[Discovery] = None
self._hwmonitor: Optional[HwMonitor] = None
self._plugins: Optional[PluginManager] = None
self._resolution: Optional[ResolutionManager] = None
@property
def dev(self) -> bool:
@ -398,6 +400,20 @@ class CoreSys:
raise RuntimeError("HassOS already set!")
self._hassos = value
@property
def resolution(self) -> ResolutionManager:
"""Return resolution manager object."""
if self._resolution is None:
raise RuntimeError("resolution manager not set!")
return self._resolution
@resolution.setter
def resolution(self, value: ResolutionManager) -> None:
"""Set a resolution manager object."""
if self._resolution:
raise RuntimeError("resolution manager already set!")
self._resolution = value
@property
def machine(self) -> Optional[str]:
"""Return machine type string."""
@ -568,6 +584,11 @@ class CoreSysAttributes:
"""Return HassOS object."""
return self.coresys.hassos
@property
def sys_resolution(self) -> ResolutionManager:
"""Return Resolution manager object."""
return self.coresys.resolution
def sys_run_in_executor(
self, funct: Callable[..., T], *args: Any
) -> Coroutine[Any, Any, T]:

View File

@ -0,0 +1,24 @@
"""Supervisor resolution center."""
from typing import List
from ..const import UnsupportedReason
from ..coresys import CoreSys, CoreSysAttributes
class ResolutionManager(CoreSysAttributes):
"""Resolution manager for supervisor."""
def __init__(self, coresys: CoreSys):
"""Initialize Resolution manager."""
self.coresys: CoreSys = coresys
self._unsupported: List[UnsupportedReason] = []
@property
def unsupported(self) -> List[UnsupportedReason]:
"""Return a list of unsupported reasons."""
return self._unsupported
@unsupported.setter
def unsupported(self, reason: UnsupportedReason) -> None:
"""Add a reason for unsupported."""
self._unsupported.append(reason)

View File

@ -0,0 +1,13 @@
"""Test Resolution API."""
import pytest
from supervisor.const import ATTR_UNSUPPORTED, UnsupportedReason
@pytest.mark.asyncio
async def test_api_resolution_base(coresys, api_client):
"""Test resolution manager api."""
coresys.resolution.unsupported = UnsupportedReason.OS
resp = await api_client.get("/resolution")
result = await resp.json()
assert UnsupportedReason.OS in result["data"][ATTR_UNSUPPORTED]

View File

@ -4,7 +4,7 @@ from unittest.mock import patch
import pytest
from supervisor.const import SUPERVISOR_VERSION, CoreState
from supervisor.const import SUPERVISOR_VERSION, CoreState, UnsupportedReason
from supervisor.exceptions import AddonConfigurationError
from supervisor.misc.filter import filter_data
@ -27,21 +27,19 @@ def test_ignored_exception(coresys):
def test_diagnostics_disabled(coresys):
"""Test if diagnostics is disabled."""
coresys.config.diagnostics = False
coresys.core.supported = True
assert filter_data(coresys, SAMPLE_EVENT, {}) is None
def test_not_supported(coresys):
"""Test if not supported."""
coresys.config.diagnostics = True
coresys.core.supported = False
coresys.resolution.unsupported = UnsupportedReason.DOCKER_VERSION
assert filter_data(coresys, SAMPLE_EVENT, {}) is None
def test_is_dev(coresys):
"""Test if dev."""
coresys.config.diagnostics = True
coresys.core.supported = True
with patch("os.environ", return_value=[("ENV_SUPERVISOR_DEV", "1")]):
assert filter_data(coresys, SAMPLE_EVENT, {}) is None
@ -49,7 +47,6 @@ def test_is_dev(coresys):
def test_not_started(coresys):
"""Test if supervisor not fully started."""
coresys.config.diagnostics = True
coresys.core.supported = True
coresys.core.state = CoreState.INITIALIZE
assert filter_data(coresys, SAMPLE_EVENT, {}) == SAMPLE_EVENT
@ -61,7 +58,6 @@ def test_not_started(coresys):
def test_defaults(coresys):
"""Test event defaults."""
coresys.config.diagnostics = True
coresys.supported = True
coresys.core.state = CoreState.RUNNING
with patch("shutil.disk_usage", return_value=(42, 42, 2 * (1024.0 ** 3))):
@ -89,7 +85,6 @@ def test_sanitize(coresys):
},
}
coresys.config.diagnostics = True
coresys.supported = True
coresys.core.state = CoreState.RUNNING
with patch("shutil.disk_usage", return_value=(42, 42, 2 * (1024.0 ** 3))):

View File

@ -0,0 +1,14 @@
"""Tests for resolution manager."""
from supervisor.const import UnsupportedReason
from supervisor.coresys import CoreSys
def test_properies(coresys: CoreSys):
"""Test resolution manager properties."""
assert coresys.core.supported
coresys.resolution.unsupported = UnsupportedReason.OS
assert not coresys.core.supported