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:
Pascal Vizeli 2020-11-13 12:19:10 +01:00 committed by GitHub
parent 06ab7e904f
commit 5552b1da49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 164 additions and 20 deletions

View File

@ -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",

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
[4]

View File

@ -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()

View 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