From 42069a358e9338d7b1ef4e89e5e419eea411527c Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 11 Feb 2025 13:35:24 +0100 Subject: [PATCH 1/3] Deprecate i386 and armhf Supervisor architectures --- supervisor/homeassistant/core.py | 1 + supervisor/jobs/const.py | 1 + supervisor/jobs/decorator.py | 9 ++++- supervisor/misc/tasks.py | 1 + supervisor/plugins/const.py | 1 + supervisor/resolution/const.py | 1 + .../evaluations/system_architecture.py | 37 +++++++++++++++++++ supervisor/updater.py | 2 +- 8 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 supervisor/resolution/evaluations/system_architecture.py diff --git a/supervisor/homeassistant/core.py b/supervisor/homeassistant/core.py index 29e829085..ec669d4f7 100644 --- a/supervisor/homeassistant/core.py +++ b/supervisor/homeassistant/core.py @@ -221,6 +221,7 @@ class HomeAssistantCore(JobGroup): JobCondition.INTERNET_HOST, JobCondition.PLUGINS_UPDATED, JobCondition.SUPERVISOR_UPDATED, + JobCondition.ARCHITECTURE_SUPPORTED, ], limit=JobExecutionLimit.GROUP_ONCE, on_condition=HomeAssistantJobError, diff --git a/supervisor/jobs/const.py b/supervisor/jobs/const.py index f7b398910..8c4e03358 100644 --- a/supervisor/jobs/const.py +++ b/supervisor/jobs/const.py @@ -32,6 +32,7 @@ class JobCondition(StrEnum): PLUGINS_UPDATED = "plugins_updated" RUNNING = "running" SUPERVISOR_UPDATED = "supervisor_updated" + ARCHITECTURE_SUPPORTED = "architecture_supported" class JobExecutionLimit(StrEnum): diff --git a/supervisor/jobs/decorator.py b/supervisor/jobs/decorator.py index 5f8934b21..b2b8086a5 100644 --- a/supervisor/jobs/decorator.py +++ b/supervisor/jobs/decorator.py @@ -17,7 +17,7 @@ from ..exceptions import ( JobGroupExecutionLimitExceeded, ) from ..host.const import HostFeature -from ..resolution.const import MINIMUM_FREE_SPACE_THRESHOLD, ContextType, IssueType +from ..resolution.const import MINIMUM_FREE_SPACE_THRESHOLD, ContextType, IssueType, UnsupportedReason from ..utils.sentry import capture_exception from . import SupervisorJob from .const import JobCondition, JobExecutionLimit @@ -435,6 +435,13 @@ class Job(CoreSysAttributes): raise JobConditionException( f"'{method_name}' blocked from execution, supervisor needs to be updated first" ) + if ( + JobCondition.ARCHITECTURE_SUPPORTED in used_conditions + and UnsupportedReason.SYSTEM_ARCHITECTURE in coresys.sys_resolution.unsupported + ): + raise JobConditionException( + f"'{method_name}' blocked from execution, unsupported system architecture" + ) if JobCondition.PLUGINS_UPDATED in used_conditions and ( out_of_date := [ diff --git a/supervisor/misc/tasks.py b/supervisor/misc/tasks.py index 37af8a9de..098082bfb 100644 --- a/supervisor/misc/tasks.py +++ b/supervisor/misc/tasks.py @@ -150,6 +150,7 @@ class Tasks(CoreSysAttributes): JobCondition.HEALTHY, JobCondition.INTERNET_HOST, JobCondition.RUNNING, + JobCondition.ARCHITECTURE_SUPPORTED, ], limit=JobExecutionLimit.ONCE, ) diff --git a/supervisor/plugins/const.py b/supervisor/plugins/const.py index 94701d03b..6338cba66 100644 --- a/supervisor/plugins/const.py +++ b/supervisor/plugins/const.py @@ -23,4 +23,5 @@ PLUGIN_UPDATE_CONDITIONS = [ JobCondition.HEALTHY, JobCondition.INTERNET_HOST, JobCondition.SUPERVISOR_UPDATED, + JobCondition.ARCHITECTURE_SUPPORTED, ] diff --git a/supervisor/resolution/const.py b/supervisor/resolution/const.py index 9a09908d1..53c14e29c 100644 --- a/supervisor/resolution/const.py +++ b/supervisor/resolution/const.py @@ -58,6 +58,7 @@ class UnsupportedReason(StrEnum): SYSTEMD_JOURNAL = "systemd_journal" SYSTEMD_RESOLVED = "systemd_resolved" VIRTUALIZATION_IMAGE = "virtualization_image" + SYSTEM_ARCHITECTURE = "system_architecture" class UnhealthyReason(StrEnum): diff --git a/supervisor/resolution/evaluations/system_architecture.py b/supervisor/resolution/evaluations/system_architecture.py new file mode 100644 index 000000000..dc5b97e39 --- /dev/null +++ b/supervisor/resolution/evaluations/system_architecture.py @@ -0,0 +1,37 @@ +"""Evaluation class for system architecture support.""" + +from ...const import CoreState +from ...coresys import CoreSys +from ..const import UnsupportedReason +from .base import EvaluateBase + + +def setup(coresys: CoreSys) -> EvaluateBase: + """Initialize evaluation-setup function.""" + return EvaluateSystemArchitecture(coresys) + + +class EvaluateSystemArchitecture(EvaluateBase): + """Evaluate if the current Supervisor architecture is supported.""" + + @property + def reason(self) -> UnsupportedReason: + """Return a UnsupportedReason enum.""" + return UnsupportedReason.SYSTEM_ARCHITECTURE + + @property + def on_failure(self) -> str: + """Return a string that is printed when self.evaluate is True.""" + return "System architecture is no longer supported. Move to a supported system architecture." + + @property + def states(self) -> list[CoreState]: + """Return a list of valid states when this evaluation can run.""" + return [CoreState.INITIALIZE] + + async def evaluate(self): + """Run evaluation.""" + return self.sys_host.info.sys_arch.supervisor in { + "i386", + "armhf", + } diff --git a/supervisor/updater.py b/supervisor/updater.py index 425ff7ffd..16c5d24ba 100644 --- a/supervisor/updater.py +++ b/supervisor/updater.py @@ -196,7 +196,7 @@ class Updater(FileConfiguration, CoreSysAttributes): @Job( name="updater_fetch_data", - conditions=[JobCondition.INTERNET_SYSTEM], + conditions=[JobCondition.INTERNET_SYSTEM, JobCondition.ARCHITECTURE_SUPPORTED], on_condition=UpdaterJobError, limit=JobExecutionLimit.THROTTLE_WAIT, throttle_period=timedelta(seconds=30), From 3ae4744dd0b1cdf264938920ee1cb302bee0c051 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 11 Feb 2025 13:50:32 +0100 Subject: [PATCH 2/3] Add test for unsupported evalation --- .../evaluation/test_system_architecture.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/resolution/evaluation/test_system_architecture.py diff --git a/tests/resolution/evaluation/test_system_architecture.py b/tests/resolution/evaluation/test_system_architecture.py new file mode 100644 index 000000000..05706087d --- /dev/null +++ b/tests/resolution/evaluation/test_system_architecture.py @@ -0,0 +1,44 @@ +"""Test evaluation supported system architectures.""" + +from unittest.mock import PropertyMock, patch + +import pytest + +from supervisor.const import CoreState +from supervisor.coresys import CoreSys +from supervisor.resolution.evaluations.system_architecture import ( + EvaluateSystemArchitecture, +) + + +@pytest.mark.parametrize("arch", ["i386", "armhf"]) +async def test_evaluation_unsupported_architectures( + coresys: CoreSys, + arch: str, +): + """Test evaluation of unsupported system architectures.""" + system_architecture = EvaluateSystemArchitecture(coresys) + coresys.core.state = CoreState.INITIALIZE + + with patch.object( + type(coresys.supervisor), "arch", PropertyMock(return_value=arch) + ): + await system_architecture() + assert system_architecture.reason in coresys.resolution.unsupported + + +@pytest.mark.parametrize("arch", ["amd64", "aarch64", "armv7"]) +async def test_evaluation_supported_architectures( + coresys: CoreSys, + arch: str, +): + """Test evaluation of supported system architectures.""" + system_architecture = EvaluateSystemArchitecture(coresys) + coresys.core.state = CoreState.INITIALIZE + + with patch.object( + type(coresys.supervisor), "arch", PropertyMock(return_value=arch) + ): + await system_architecture() + assert system_architecture.reason not in coresys.resolution.unsupported + From 503dc232c4edf0a567c049b30573b7a4cce405b4 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Tue, 11 Feb 2025 14:10:14 +0100 Subject: [PATCH 3/3] Run ruff format --- supervisor/jobs/decorator.py | 10 ++++++++-- .../resolution/evaluation/test_system_architecture.py | 5 ++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/supervisor/jobs/decorator.py b/supervisor/jobs/decorator.py index b2b8086a5..fc324d671 100644 --- a/supervisor/jobs/decorator.py +++ b/supervisor/jobs/decorator.py @@ -17,7 +17,12 @@ from ..exceptions import ( JobGroupExecutionLimitExceeded, ) from ..host.const import HostFeature -from ..resolution.const import MINIMUM_FREE_SPACE_THRESHOLD, ContextType, IssueType, UnsupportedReason +from ..resolution.const import ( + MINIMUM_FREE_SPACE_THRESHOLD, + ContextType, + IssueType, + UnsupportedReason, +) from ..utils.sentry import capture_exception from . import SupervisorJob from .const import JobCondition, JobExecutionLimit @@ -437,7 +442,8 @@ class Job(CoreSysAttributes): ) if ( JobCondition.ARCHITECTURE_SUPPORTED in used_conditions - and UnsupportedReason.SYSTEM_ARCHITECTURE in coresys.sys_resolution.unsupported + and UnsupportedReason.SYSTEM_ARCHITECTURE + in coresys.sys_resolution.unsupported ): raise JobConditionException( f"'{method_name}' blocked from execution, unsupported system architecture" diff --git a/tests/resolution/evaluation/test_system_architecture.py b/tests/resolution/evaluation/test_system_architecture.py index 05706087d..d271b4199 100644 --- a/tests/resolution/evaluation/test_system_architecture.py +++ b/tests/resolution/evaluation/test_system_architecture.py @@ -21,7 +21,7 @@ async def test_evaluation_unsupported_architectures( coresys.core.state = CoreState.INITIALIZE with patch.object( - type(coresys.supervisor), "arch", PropertyMock(return_value=arch) + type(coresys.supervisor), "arch", PropertyMock(return_value=arch) ): await system_architecture() assert system_architecture.reason in coresys.resolution.unsupported @@ -37,8 +37,7 @@ async def test_evaluation_supported_architectures( coresys.core.state = CoreState.INITIALIZE with patch.object( - type(coresys.supervisor), "arch", PropertyMock(return_value=arch) + type(coresys.supervisor), "arch", PropertyMock(return_value=arch) ): await system_architecture() assert system_architecture.reason not in coresys.resolution.unsupported -