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
This commit is contained in:
Stefan Agner 2025-02-28 16:59:22 +01:00 committed by GitHub
parent 53d97ce0c6
commit 8030b346e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 46 additions and 47 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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