mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-16 21:56:29 +00:00
Optimize flow / reduce call time (#2250)
* Optimize flow / reduce call time * rename * freeze too * add connectivity task * use newstyle timeout * Fix tests Co-authored-by: Ludeeus <ludeeus@ludeeus.dev>
This commit is contained in:
parent
06ab7e904f
commit
5552b1da49
2
setup.py
2
setup.py
@ -41,7 +41,7 @@ setup(
|
|||||||
"supervisor.docker",
|
"supervisor.docker",
|
||||||
"supervisor.homeassistant",
|
"supervisor.homeassistant",
|
||||||
"supervisor.host",
|
"supervisor.host",
|
||||||
"supervisor.job",
|
"supervisor.jobs",
|
||||||
"supervisor.misc",
|
"supervisor.misc",
|
||||||
"supervisor.plugins",
|
"supervisor.plugins",
|
||||||
"supervisor.resolution.evaluations",
|
"supervisor.resolution.evaluations",
|
||||||
|
@ -10,7 +10,7 @@ import sentry_sdk
|
|||||||
from sentry_sdk.integrations.aiohttp import AioHttpIntegration
|
from sentry_sdk.integrations.aiohttp import AioHttpIntegration
|
||||||
from sentry_sdk.integrations.logging import LoggingIntegration
|
from sentry_sdk.integrations.logging import LoggingIntegration
|
||||||
|
|
||||||
from supervisor.job import JobManager
|
from supervisor.jobs import JobManager
|
||||||
|
|
||||||
from .addons import AddonManager
|
from .addons import AddonManager
|
||||||
from .api import RestAPI
|
from .api import RestAPI
|
||||||
|
@ -57,9 +57,6 @@ class Core(CoreSysAttributes):
|
|||||||
# Load information from container
|
# Load information from container
|
||||||
await self.sys_supervisor.load()
|
await self.sys_supervisor.load()
|
||||||
|
|
||||||
# Check internet on startup
|
|
||||||
await self.sys_supervisor.check_connectivity()
|
|
||||||
|
|
||||||
# Evaluate the system
|
# Evaluate the system
|
||||||
await self.sys_resolution.evaluate.evaluate_system()
|
await self.sys_resolution.evaluate.evaluate_system()
|
||||||
|
|
||||||
@ -140,6 +137,9 @@ class Core(CoreSysAttributes):
|
|||||||
"System running in a unhealthy state and need manual intervention!"
|
"System running in a unhealthy state and need manual intervention!"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Check internet on startup
|
||||||
|
await self.sys_supervisor.check_connectivity()
|
||||||
|
|
||||||
# Mark booted partition as healthy
|
# Mark booted partition as healthy
|
||||||
if self.sys_hassos.available:
|
if self.sys_hassos.available:
|
||||||
await self.sys_hassos.mark_healthy()
|
await self.sys_hassos.mark_healthy()
|
||||||
|
@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
|||||||
from .homeassistant import HomeAssistant
|
from .homeassistant import HomeAssistant
|
||||||
from .host import HostManager
|
from .host import HostManager
|
||||||
from .ingress import Ingress
|
from .ingress import Ingress
|
||||||
from .job import JobManager
|
from .jobs import JobManager
|
||||||
from .misc.hwmon import HwMonitor
|
from .misc.hwmon import HwMonitor
|
||||||
from .misc.scheduler import Scheduler
|
from .misc.scheduler import Scheduler
|
||||||
from .misc.tasks import Tasks
|
from .misc.tasks import Tasks
|
||||||
|
@ -75,7 +75,8 @@ class NetworkManager(CoreSysAttributes):
|
|||||||
try:
|
try:
|
||||||
state = await self.sys_dbus.network.check_connectivity()
|
state = await self.sys_dbus.network.check_connectivity()
|
||||||
self._connectivity = state[0] == 4
|
self._connectivity = state[0] == 4
|
||||||
except DBusError:
|
except DBusError as err:
|
||||||
|
_LOGGER.warning("Can't update connectivity information: %s", err)
|
||||||
self._connectivity = False
|
self._connectivity = False
|
||||||
|
|
||||||
def get(self, inet_name: str) -> Interface:
|
def get(self, inet_name: str) -> Interface:
|
||||||
@ -95,9 +96,11 @@ class NetworkManager(CoreSysAttributes):
|
|||||||
except DBusError:
|
except DBusError:
|
||||||
_LOGGER.warning("Can't update network information!")
|
_LOGGER.warning("Can't update network information!")
|
||||||
except DBusNotConnectedError as err:
|
except DBusNotConnectedError as err:
|
||||||
_LOGGER.error("No hostname D-Bus connection available")
|
_LOGGER.error("No network D-Bus connection available")
|
||||||
raise HostNotSupportedError() from err
|
raise HostNotSupportedError() from err
|
||||||
|
|
||||||
|
await self.check_connectivity()
|
||||||
|
|
||||||
async def apply_changes(self, interface: Interface) -> None:
|
async def apply_changes(self, interface: Interface) -> None:
|
||||||
"""Apply Interface changes to host."""
|
"""Apply Interface changes to host."""
|
||||||
inet = self.sys_dbus.network.interfaces.get(interface.name)
|
inet = self.sys_dbus.network.interfaces.get(interface.name)
|
||||||
|
@ -53,7 +53,7 @@ class Job:
|
|||||||
|
|
||||||
job = self._coresys.jobs.get_job(self.name)
|
job = self._coresys.jobs.get_job(self.name)
|
||||||
|
|
||||||
if self.conditions and not await self._check_conditions():
|
if self.conditions and not self._check_conditions():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -69,7 +69,7 @@ class Job:
|
|||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
async def _check_conditions(self):
|
def _check_conditions(self):
|
||||||
"""Check conditions."""
|
"""Check conditions."""
|
||||||
if JobCondition.HEALTHY in self.conditions:
|
if JobCondition.HEALTHY in self.conditions:
|
||||||
if not self._coresys.core.healthy:
|
if not self._coresys.core.healthy:
|
||||||
@ -93,10 +93,12 @@ class Job:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
if JobCondition.INTERNET in self.conditions:
|
if JobCondition.INTERNET in self.conditions:
|
||||||
if self._coresys.core.state == CoreState.RUNNING:
|
if self._coresys.core.state not in (
|
||||||
if self._coresys.dbus.network.is_connected:
|
CoreState.SETUP,
|
||||||
await self._coresys.host.network.check_connectivity()
|
CoreState.RUNNING,
|
||||||
await self._coresys.supervisor.check_connectivity()
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
if not self._coresys.supervisor.connectivity:
|
if not self._coresys.supervisor.connectivity:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"'%s' blocked from execution, no supervisor internet connection",
|
"'%s' blocked from execution, no supervisor internet connection",
|
@ -12,7 +12,7 @@ from ..exceptions import (
|
|||||||
MulticastError,
|
MulticastError,
|
||||||
ObserverError,
|
ObserverError,
|
||||||
)
|
)
|
||||||
from ..job.decorator import Job, JobCondition
|
from ..jobs.decorator import Job, JobCondition
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -47,6 +47,8 @@ RUN_WATCHDOG_OBSERVER_APPLICATION = 180
|
|||||||
|
|
||||||
RUN_REFRESH_ADDON = 15
|
RUN_REFRESH_ADDON = 15
|
||||||
|
|
||||||
|
RUN_CHECK_CONNECTIVITY = 30
|
||||||
|
|
||||||
|
|
||||||
class Tasks(CoreSysAttributes):
|
class Tasks(CoreSysAttributes):
|
||||||
"""Handle Tasks inside Supervisor."""
|
"""Handle Tasks inside Supervisor."""
|
||||||
@ -111,6 +113,11 @@ class Tasks(CoreSysAttributes):
|
|||||||
# Refresh
|
# Refresh
|
||||||
self.sys_scheduler.register_task(self._refresh_addon, RUN_REFRESH_ADDON)
|
self.sys_scheduler.register_task(self._refresh_addon, RUN_REFRESH_ADDON)
|
||||||
|
|
||||||
|
# Connectivity
|
||||||
|
self.sys_scheduler.register_task(
|
||||||
|
self._check_connectivity, RUN_CHECK_CONNECTIVITY
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER.info("All core tasks are scheduled")
|
_LOGGER.info("All core tasks are scheduled")
|
||||||
|
|
||||||
@Job(conditions=[JobCondition.HEALTHY, JobCondition.FREE_SPACE])
|
@Job(conditions=[JobCondition.HEALTHY, JobCondition.FREE_SPACE])
|
||||||
@ -422,3 +429,29 @@ class Tasks(CoreSysAttributes):
|
|||||||
|
|
||||||
# Adjust state
|
# Adjust state
|
||||||
addon.state = AddonState.STOPPED
|
addon.state = AddonState.STOPPED
|
||||||
|
|
||||||
|
async def _check_connectivity(self) -> None:
|
||||||
|
"""Check system connectivity."""
|
||||||
|
value = self._cache.get("connectivity", 0)
|
||||||
|
|
||||||
|
# Need only full check if not connected or each 10min
|
||||||
|
if value >= 600:
|
||||||
|
pass
|
||||||
|
elif (
|
||||||
|
self.sys_supervisor.connectivity
|
||||||
|
and self.sys_host.network.connectivity is None
|
||||||
|
) or (
|
||||||
|
self.sys_supervisor.connectivity
|
||||||
|
and self.sys_host.network.connectivity is not None
|
||||||
|
and self.sys_host.network.connectivity
|
||||||
|
):
|
||||||
|
self._cache["connectivity"] = value + RUN_CHECK_CONNECTIVITY
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check connectivity
|
||||||
|
try:
|
||||||
|
await self.sys_supervisor.check_connectivity()
|
||||||
|
if self.sys_dbus.network.is_connected:
|
||||||
|
await self.sys_host.network.check_connectivity()
|
||||||
|
finally:
|
||||||
|
self._cache["connectivity"] = 0
|
||||||
|
@ -12,7 +12,7 @@ from supervisor.utils.json import read_json_file
|
|||||||
from ..const import REPOSITORY_CORE, REPOSITORY_LOCAL
|
from ..const import REPOSITORY_CORE, REPOSITORY_LOCAL
|
||||||
from ..coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from ..exceptions import JsonFileError, StoreError, StoreGitError
|
from ..exceptions import JsonFileError, StoreError, StoreGitError
|
||||||
from ..job.decorator import Job, JobCondition
|
from ..jobs.decorator import Job, JobCondition
|
||||||
from .addon import AddonStore
|
from .addon import AddonStore
|
||||||
from .data import StoreData
|
from .data import StoreData
|
||||||
from .repository import Repository
|
from .repository import Repository
|
||||||
|
@ -33,7 +33,7 @@ class Supervisor(CoreSysAttributes):
|
|||||||
"""Initialize hass object."""
|
"""Initialize hass object."""
|
||||||
self.coresys: CoreSys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
self.instance: DockerSupervisor = DockerSupervisor(coresys)
|
self.instance: DockerSupervisor = DockerSupervisor(coresys)
|
||||||
self._connectivity: bool = False
|
self._connectivity: bool = True
|
||||||
|
|
||||||
async def load(self) -> None:
|
async def load(self) -> None:
|
||||||
"""Prepare Home Assistant object."""
|
"""Prepare Home Assistant object."""
|
||||||
@ -176,9 +176,10 @@ class Supervisor(CoreSysAttributes):
|
|||||||
|
|
||||||
async def check_connectivity(self):
|
async def check_connectivity(self):
|
||||||
"""Check the connection."""
|
"""Check the connection."""
|
||||||
|
timeout = aiohttp.ClientTimeout(total=10)
|
||||||
try:
|
try:
|
||||||
await self.sys_websession.head(
|
await self.sys_websession.head(
|
||||||
"https://version.home-assistant.io/online.txt", timeout=10
|
"https://version.home-assistant.io/online.txt", timeout=timeout
|
||||||
)
|
)
|
||||||
except (ClientError, asyncio.TimeoutError):
|
except (ClientError, asyncio.TimeoutError):
|
||||||
self._connectivity = False
|
self._connectivity = False
|
||||||
|
@ -25,7 +25,7 @@ from .const import (
|
|||||||
)
|
)
|
||||||
from .coresys import CoreSysAttributes
|
from .coresys import CoreSysAttributes
|
||||||
from .exceptions import HassioUpdaterError
|
from .exceptions import HassioUpdaterError
|
||||||
from .job.decorator import Job, JobCondition
|
from .jobs.decorator import Job, JobCondition
|
||||||
from .utils import AsyncThrottle
|
from .utils import AsyncThrottle
|
||||||
from .utils.json import JsonConfig
|
from .utils.json import JsonConfig
|
||||||
from .validate import SCHEMA_UPDATER_CONFIG
|
from .validate import SCHEMA_UPDATER_CONFIG
|
||||||
|
1
tests/fixtures/org_freedesktop_NetworkManager-CheckConnectivity.fixture
vendored
Normal file
1
tests/fixtures/org_freedesktop_NetworkManager-CheckConnectivity.fixture
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
[4]
|
@ -2,8 +2,9 @@
|
|||||||
# pylint: disable=protected-access,import-error
|
# pylint: disable=protected-access,import-error
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from supervisor.const import CoreState
|
||||||
from supervisor.coresys import CoreSys
|
from supervisor.coresys import CoreSys
|
||||||
from supervisor.job.decorator import Job, JobCondition
|
from supervisor.jobs.decorator import Job, JobCondition
|
||||||
|
|
||||||
|
|
||||||
async def test_healthy(coresys: CoreSys):
|
async def test_healthy(coresys: CoreSys):
|
||||||
@ -30,6 +31,7 @@ async def test_healthy(coresys: CoreSys):
|
|||||||
|
|
||||||
async def test_internet(coresys: CoreSys):
|
async def test_internet(coresys: CoreSys):
|
||||||
"""Test the internet decorator."""
|
"""Test the internet decorator."""
|
||||||
|
coresys.core.state = CoreState.RUNNING
|
||||||
|
|
||||||
class TestClass:
|
class TestClass:
|
||||||
"""Test class."""
|
"""Test class."""
|
||||||
@ -83,3 +85,44 @@ async def test_free_space(coresys: CoreSys):
|
|||||||
|
|
||||||
with patch("shutil.disk_usage", return_value=(42, 42, (512.0 ** 3))):
|
with patch("shutil.disk_usage", return_value=(42, 42, (512.0 ** 3))):
|
||||||
assert not await test.execute()
|
assert not await test.execute()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_internet_connectivity_with_core_state(coresys: CoreSys):
|
||||||
|
"""Test the different core states and the impact for internet condition."""
|
||||||
|
|
||||||
|
class TestClass:
|
||||||
|
"""Test class."""
|
||||||
|
|
||||||
|
def __init__(self, coresys: CoreSys):
|
||||||
|
"""Initialize the test class."""
|
||||||
|
self.coresys = coresys
|
||||||
|
|
||||||
|
@Job(conditions=[JobCondition.INTERNET])
|
||||||
|
async def execute(self):
|
||||||
|
"""Execute the class method."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
test = TestClass(coresys)
|
||||||
|
coresys.host.network._connectivity = False
|
||||||
|
coresys.supervisor._connectivity = False
|
||||||
|
|
||||||
|
coresys.core.state = CoreState.INITIALIZE
|
||||||
|
assert await test.execute()
|
||||||
|
|
||||||
|
coresys.core.state = CoreState.SETUP
|
||||||
|
assert not await test.execute()
|
||||||
|
|
||||||
|
coresys.core.state = CoreState.STARTUP
|
||||||
|
assert await test.execute()
|
||||||
|
|
||||||
|
coresys.core.state = CoreState.RUNNING
|
||||||
|
assert not await test.execute()
|
||||||
|
|
||||||
|
coresys.core.state = CoreState.CLOSE
|
||||||
|
assert await test.execute()
|
||||||
|
|
||||||
|
coresys.core.state = CoreState.SHUTDOWN
|
||||||
|
assert await test.execute()
|
||||||
|
|
||||||
|
coresys.core.state = CoreState.STOPPING
|
||||||
|
assert await test.execute()
|
||||||
|
61
tests/misc/test_connectivity_task.py
Normal file
61
tests/misc/test_connectivity_task.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
"""Test periodic connectivity task."""
|
||||||
|
# pylint: disable=protected-access,import-error
|
||||||
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
|
from supervisor.coresys import CoreSys
|
||||||
|
|
||||||
|
|
||||||
|
async def test_no_connectivity(coresys: CoreSys):
|
||||||
|
"""Test periodic connectivity task."""
|
||||||
|
coresys.host.network.check_connectivity = AsyncMock()
|
||||||
|
coresys.supervisor.check_connectivity = AsyncMock()
|
||||||
|
|
||||||
|
coresys.tasks._cache["connectivity"] = 0
|
||||||
|
coresys.host.network._connectivity = False
|
||||||
|
coresys.supervisor._connectivity = False
|
||||||
|
|
||||||
|
await coresys.tasks._check_connectivity()
|
||||||
|
|
||||||
|
coresys.host.network.check_connectivity.assert_called_once()
|
||||||
|
coresys.supervisor.check_connectivity.assert_called_once()
|
||||||
|
assert coresys.tasks._cache["connectivity"] == 0
|
||||||
|
coresys.host.network.check_connectivity.reset_mock()
|
||||||
|
coresys.supervisor.check_connectivity.reset_mock()
|
||||||
|
|
||||||
|
await coresys.tasks._check_connectivity()
|
||||||
|
|
||||||
|
coresys.host.network.check_connectivity.assert_called_once()
|
||||||
|
coresys.supervisor.check_connectivity.assert_called_once()
|
||||||
|
assert coresys.tasks._cache["connectivity"] == 0
|
||||||
|
|
||||||
|
|
||||||
|
async def test_connectivity(coresys: CoreSys):
|
||||||
|
"""Test periodic connectivity task."""
|
||||||
|
coresys.host.network.check_connectivity = AsyncMock()
|
||||||
|
coresys.supervisor.check_connectivity = AsyncMock()
|
||||||
|
|
||||||
|
coresys.tasks._cache["connectivity"] = 0
|
||||||
|
coresys.host.network._connectivity = True
|
||||||
|
coresys.supervisor._connectivity = True
|
||||||
|
|
||||||
|
await coresys.tasks._check_connectivity()
|
||||||
|
|
||||||
|
coresys.host.network.check_connectivity.assert_not_called()
|
||||||
|
coresys.supervisor.check_connectivity.assert_not_called()
|
||||||
|
assert coresys.tasks._cache["connectivity"] == 30
|
||||||
|
|
||||||
|
|
||||||
|
async def test_connectivity_cache_reached(coresys: CoreSys):
|
||||||
|
"""Test periodic connectivity task."""
|
||||||
|
coresys.host.network.check_connectivity = AsyncMock()
|
||||||
|
coresys.supervisor.check_connectivity = AsyncMock()
|
||||||
|
|
||||||
|
coresys.tasks._cache["connectivity"] = 600
|
||||||
|
coresys.host.network._connectivity = True
|
||||||
|
coresys.supervisor._connectivity = True
|
||||||
|
|
||||||
|
await coresys.tasks._check_connectivity()
|
||||||
|
|
||||||
|
coresys.host.network.check_connectivity.assert_called_once()
|
||||||
|
coresys.supervisor.check_connectivity.assert_called_once()
|
||||||
|
assert coresys.tasks._cache["connectivity"] == 0
|
Loading…
x
Reference in New Issue
Block a user