From 8030b346e0fb02b0b0f799d76e16e7040954de86 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 28 Feb 2025 16:59:22 +0100 Subject: [PATCH] Load resolution evaluation, check and fixups early (#5696) * Load resolution evaluation, check and fixups early Before #5652, these modules were loaded in the constructor, hence early in `initialize_coresys()`. Moving them late actually exposed an issue where NetworkManager connectivity setter couldn't get the `connectivity_check` evaluation, leading to an exception early in bootstrap. Technically, it might be safe to load the resolution modules only in `Core.connect()`, however then we'd have to load them separately for pytest. Let's go conservative and load them the same place where they got loaded before #5652. * Load resolution modules in a single executor call * Fix pytest --- supervisor/bootstrap.py | 1 + supervisor/resolution/check.py | 24 +++++++++++------------- supervisor/resolution/evaluate.py | 24 +++++++++++------------- supervisor/resolution/fixup.py | 24 +++++++++++------------- supervisor/resolution/module.py | 15 +++++++++++---- tests/conftest.py | 3 --- tests/resolution/check/test_check.py | 2 +- 7 files changed, 46 insertions(+), 47 deletions(-) diff --git a/supervisor/bootstrap.py b/supervisor/bootstrap.py index 766f3d53e..b5b0fd230 100644 --- a/supervisor/bootstrap.py +++ b/supervisor/bootstrap.py @@ -55,6 +55,7 @@ async def initialize_coresys() -> CoreSys: # Initialize core objects coresys.docker = await DockerAPI(coresys).load_config() coresys.resolution = await ResolutionManager(coresys).load_config() + await coresys.resolution.load_modules() coresys.jobs = await JobManager(coresys).load_config() coresys.core = Core(coresys) coresys.plugins = await PluginManager(coresys).load_config() diff --git a/supervisor/resolution/check.py b/supervisor/resolution/check.py index 98f806203..019b354e2 100644 --- a/supervisor/resolution/check.py +++ b/supervisor/resolution/check.py @@ -32,20 +32,18 @@ class ResolutionCheck(CoreSysAttributes): """Return all list of all checks.""" return list(self._checks.values()) - async def load(self) -> None: - """Load all checks.""" + def load_modules(self) -> None: + """Load and setup all checks. - def _load() -> dict[str, CheckBase]: - """Load and setup checks in executor.""" - package = f"{__package__}.checks" - checks: dict[str, CheckBase] = {} - for module in get_valid_modules("checks"): - check_module = import_module(f"{package}.{module}") - check = check_module.setup(self.coresys) - checks[check.slug] = check - return checks - - self._checks = await self.sys_run_in_executor(_load) + Must be run in executor. + """ + package = f"{__package__}.checks" + checks: dict[str, CheckBase] = {} + for module in get_valid_modules("checks"): + check_module = import_module(f"{package}.{module}") + check = check_module.setup(self.coresys) + checks[check.slug] = check + self._checks = checks def get(self, slug: str) -> CheckBase: """Return check based on slug.""" diff --git a/supervisor/resolution/evaluate.py b/supervisor/resolution/evaluate.py index e454a86d7..80eeee484 100644 --- a/supervisor/resolution/evaluate.py +++ b/supervisor/resolution/evaluate.py @@ -33,20 +33,18 @@ class ResolutionEvaluation(CoreSysAttributes): """Return all list of all checks.""" return list(self._evalutions.values()) - async def load(self) -> None: - """Load all evaluations.""" + def load_modules(self) -> None: + """Load and setup all evaluations. - def _load() -> dict[str, EvaluateBase]: - """Load and setup evaluations in executor.""" - package = f"{__package__}.evaluations" - evaluations: dict[str, EvaluateBase] = {} - for module in get_valid_modules("evaluations"): - evaluate_module = import_module(f"{package}.{module}") - evaluation = evaluate_module.setup(self.coresys) - evaluations[evaluation.slug] = evaluation - return evaluations - - self._evalutions = await self.sys_run_in_executor(_load) + Must be run in executor. + """ + package = f"{__package__}.evaluations" + evaluations: dict[str, EvaluateBase] = {} + for module in get_valid_modules("evaluations"): + evaluate_module = import_module(f"{package}.{module}") + evaluation = evaluate_module.setup(self.coresys) + evaluations[evaluation.slug] = evaluation + self._evalutions = evaluations def get(self, slug: str) -> EvaluateBase: """Return check based on slug.""" diff --git a/supervisor/resolution/fixup.py b/supervisor/resolution/fixup.py index 97744b320..c929246c5 100644 --- a/supervisor/resolution/fixup.py +++ b/supervisor/resolution/fixup.py @@ -22,20 +22,18 @@ class ResolutionFixup(CoreSysAttributes): self.coresys = coresys self._fixups: dict[str, FixupBase] = {} - async def load(self) -> None: - """Load all fixups.""" + def load_modules(self) -> None: + """Load and setup all fixups. - def _load() -> dict[str, FixupBase]: - """Load and setup fixups in executor.""" - package = f"{__package__}.fixups" - fixups: dict[str, FixupBase] = {} - for module in get_valid_modules("fixups"): - fixup_module = import_module(f"{package}.{module}") - fixup = fixup_module.setup(self.coresys) - fixups[fixup.slug] = fixup - return fixups - - self._fixups = await self.sys_run_in_executor(_load) + Must be run in executor. + """ + package = f"{__package__}.fixups" + fixups: dict[str, FixupBase] = {} + for module in get_valid_modules("fixups"): + fixup_module = import_module(f"{package}.{module}") + fixup = fixup_module.setup(self.coresys) + fixups[fixup.slug] = fixup + self._fixups = fixups @property def all_fixes(self) -> list[FixupBase]: diff --git a/supervisor/resolution/module.py b/supervisor/resolution/module.py index edd0288aa..515a8d67a 100644 --- a/supervisor/resolution/module.py +++ b/supervisor/resolution/module.py @@ -46,6 +46,17 @@ class ResolutionManager(FileConfiguration, CoreSysAttributes): self._unsupported: list[UnsupportedReason] = [] self._unhealthy: list[UnhealthyReason] = [] + async def load_modules(self): + """Load resolution evaluation, check and fixup modules.""" + + def _load_modules(): + """Load and setup all resolution modules.""" + self._evaluate.load_modules() + self._check.load_modules() + self._fixup.load_modules() + + await self.sys_run_in_executor(_load_modules) + @property def data(self) -> dict[str, Any]: """Return data.""" @@ -195,10 +206,6 @@ class ResolutionManager(FileConfiguration, CoreSysAttributes): async def load(self): """Load the resoulution manager.""" - await self.check.load() - await self.fixup.load() - await self.evaluate.load() - # Initial healthcheck when the manager is loaded await self.healthcheck() diff --git a/tests/conftest.py b/tests/conftest.py index ec0f5f98f..065158d63 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -328,9 +328,6 @@ async def coresys( coresys_obj._store.save_data = AsyncMock() coresys_obj._mounts.save_data = AsyncMock() - # Load resolution center - await coresys_obj.resolution.load() - # Mock test client coresys_obj._supervisor.instance._meta = { "Config": {"Labels": {"io.hass.arch": "amd64"}} diff --git a/tests/resolution/check/test_check.py b/tests/resolution/check/test_check.py index 3063be9e9..ae112afaf 100644 --- a/tests/resolution/check/test_check.py +++ b/tests/resolution/check/test_check.py @@ -113,6 +113,6 @@ async def test_get_checks(coresys: CoreSys): async def test_dynamic_check_loader(coresys: CoreSys): """Test dynamic check loader, this ensures that all checks have defined a setup function.""" - await coresys.resolution.check.load() + coresys.resolution.check.load_modules() for check in get_valid_modules("checks"): assert check in coresys.resolution.check._checks