mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-16 05:36:29 +00:00
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:
parent
8da686fc34
commit
028b170cff
@ -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()
|
||||
|
17
supervisor/api/resolution.py
Normal file
17
supervisor/api/resolution.py
Normal 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}
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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]:
|
||||
|
24
supervisor/resolution/__init__.py
Normal file
24
supervisor/resolution/__init__.py
Normal 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)
|
13
tests/api/test_resolution.py
Normal file
13
tests/api/test_resolution.py
Normal 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]
|
@ -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))):
|
||||
|
14
tests/resolution/test_resolution_manager.py
Normal file
14
tests/resolution/test_resolution_manager.py
Normal 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
|
Loading…
x
Reference in New Issue
Block a user