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 # Initialize core objects
coresys.docker = await DockerAPI(coresys).load_config() coresys.docker = await DockerAPI(coresys).load_config()
coresys.resolution = await ResolutionManager(coresys).load_config() coresys.resolution = await ResolutionManager(coresys).load_config()
await coresys.resolution.load_modules()
coresys.jobs = await JobManager(coresys).load_config() coresys.jobs = await JobManager(coresys).load_config()
coresys.core = Core(coresys) coresys.core = Core(coresys)
coresys.plugins = await PluginManager(coresys).load_config() coresys.plugins = await PluginManager(coresys).load_config()

View File

@ -32,20 +32,18 @@ class ResolutionCheck(CoreSysAttributes):
"""Return all list of all checks.""" """Return all list of all checks."""
return list(self._checks.values()) return list(self._checks.values())
async def load(self) -> None: def load_modules(self) -> None:
"""Load all checks.""" """Load and setup all checks.
def _load() -> dict[str, CheckBase]: Must be run in executor.
"""Load and setup checks in executor.""" """
package = f"{__package__}.checks" package = f"{__package__}.checks"
checks: dict[str, CheckBase] = {} checks: dict[str, CheckBase] = {}
for module in get_valid_modules("checks"): for module in get_valid_modules("checks"):
check_module = import_module(f"{package}.{module}") check_module = import_module(f"{package}.{module}")
check = check_module.setup(self.coresys) check = check_module.setup(self.coresys)
checks[check.slug] = check checks[check.slug] = check
return checks self._checks = checks
self._checks = await self.sys_run_in_executor(_load)
def get(self, slug: str) -> CheckBase: def get(self, slug: str) -> CheckBase:
"""Return check based on slug.""" """Return check based on slug."""

View File

@ -33,20 +33,18 @@ class ResolutionEvaluation(CoreSysAttributes):
"""Return all list of all checks.""" """Return all list of all checks."""
return list(self._evalutions.values()) return list(self._evalutions.values())
async def load(self) -> None: def load_modules(self) -> None:
"""Load all evaluations.""" """Load and setup all evaluations.
def _load() -> dict[str, EvaluateBase]: Must be run in executor.
"""Load and setup evaluations in executor.""" """
package = f"{__package__}.evaluations" package = f"{__package__}.evaluations"
evaluations: dict[str, EvaluateBase] = {} evaluations: dict[str, EvaluateBase] = {}
for module in get_valid_modules("evaluations"): for module in get_valid_modules("evaluations"):
evaluate_module = import_module(f"{package}.{module}") evaluate_module = import_module(f"{package}.{module}")
evaluation = evaluate_module.setup(self.coresys) evaluation = evaluate_module.setup(self.coresys)
evaluations[evaluation.slug] = evaluation evaluations[evaluation.slug] = evaluation
return evaluations self._evalutions = evaluations
self._evalutions = await self.sys_run_in_executor(_load)
def get(self, slug: str) -> EvaluateBase: def get(self, slug: str) -> EvaluateBase:
"""Return check based on slug.""" """Return check based on slug."""

View File

@ -22,20 +22,18 @@ class ResolutionFixup(CoreSysAttributes):
self.coresys = coresys self.coresys = coresys
self._fixups: dict[str, FixupBase] = {} self._fixups: dict[str, FixupBase] = {}
async def load(self) -> None: def load_modules(self) -> None:
"""Load all fixups.""" """Load and setup all fixups.
def _load() -> dict[str, FixupBase]: Must be run in executor.
"""Load and setup fixups in executor.""" """
package = f"{__package__}.fixups" package = f"{__package__}.fixups"
fixups: dict[str, FixupBase] = {} fixups: dict[str, FixupBase] = {}
for module in get_valid_modules("fixups"): for module in get_valid_modules("fixups"):
fixup_module = import_module(f"{package}.{module}") fixup_module = import_module(f"{package}.{module}")
fixup = fixup_module.setup(self.coresys) fixup = fixup_module.setup(self.coresys)
fixups[fixup.slug] = fixup fixups[fixup.slug] = fixup
return fixups self._fixups = fixups
self._fixups = await self.sys_run_in_executor(_load)
@property @property
def all_fixes(self) -> list[FixupBase]: def all_fixes(self) -> list[FixupBase]:

View File

@ -46,6 +46,17 @@ class ResolutionManager(FileConfiguration, CoreSysAttributes):
self._unsupported: list[UnsupportedReason] = [] self._unsupported: list[UnsupportedReason] = []
self._unhealthy: list[UnhealthyReason] = [] 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 @property
def data(self) -> dict[str, Any]: def data(self) -> dict[str, Any]:
"""Return data.""" """Return data."""
@ -195,10 +206,6 @@ class ResolutionManager(FileConfiguration, CoreSysAttributes):
async def load(self): async def load(self):
"""Load the resoulution manager.""" """Load the resoulution manager."""
await self.check.load()
await self.fixup.load()
await self.evaluate.load()
# Initial healthcheck when the manager is loaded # Initial healthcheck when the manager is loaded
await self.healthcheck() await self.healthcheck()

View File

@ -328,9 +328,6 @@ async def coresys(
coresys_obj._store.save_data = AsyncMock() coresys_obj._store.save_data = AsyncMock()
coresys_obj._mounts.save_data = AsyncMock() coresys_obj._mounts.save_data = AsyncMock()
# Load resolution center
await coresys_obj.resolution.load()
# Mock test client # Mock test client
coresys_obj._supervisor.instance._meta = { coresys_obj._supervisor.instance._meta = {
"Config": {"Labels": {"io.hass.arch": "amd64"}} "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): async def test_dynamic_check_loader(coresys: CoreSys):
"""Test dynamic check loader, this ensures that all checks have defined a setup function.""" """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"): for check in get_valid_modules("checks"):
assert check in coresys.resolution.check._checks assert check in coresys.resolution.check._checks