Allow all job conditions to be ignored (#4107)

* Allow all job conditions to be ignored

* Clear features cache in test

* patch out OS Agent supported feature
This commit is contained in:
Mike Degatano 2023-01-18 06:14:12 -05:00 committed by GitHub
parent 83e5359bd2
commit 72d81e43dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 77 additions and 60 deletions

View File

@ -199,14 +199,14 @@ class Job(CoreSysAttributes):
f"'{self._method.__qualname__}' blocked from execution, not enough free space ({self.sys_host.info.free_space}GB) left on the device" f"'{self._method.__qualname__}' blocked from execution, not enough free space ({self.sys_host.info.free_space}GB) left on the device"
) )
if JobCondition.INTERNET_SYSTEM in self.conditions: if JobCondition.INTERNET_SYSTEM in used_conditions:
await self.sys_supervisor.check_connectivity() await self.sys_supervisor.check_connectivity()
if not self.sys_supervisor.connectivity: if not self.sys_supervisor.connectivity:
raise JobConditionException( raise JobConditionException(
f"'{self._method.__qualname__}' blocked from execution, no supervisor internet connection" f"'{self._method.__qualname__}' blocked from execution, no supervisor internet connection"
) )
if JobCondition.INTERNET_HOST in self.conditions: if JobCondition.INTERNET_HOST in used_conditions:
await self.sys_host.network.check_connectivity() await self.sys_host.network.check_connectivity()
if ( if (
self.sys_host.network.connectivity is not None self.sys_host.network.connectivity is not None
@ -216,13 +216,13 @@ class Job(CoreSysAttributes):
f"'{self._method.__qualname__}' blocked from execution, no host internet connection" f"'{self._method.__qualname__}' blocked from execution, no host internet connection"
) )
if JobCondition.HAOS in self.conditions and not self.sys_os.available: if JobCondition.HAOS in used_conditions and not self.sys_os.available:
raise JobConditionException( raise JobConditionException(
f"'{self._method.__qualname__}' blocked from execution, no Home Assistant OS available" f"'{self._method.__qualname__}' blocked from execution, no Home Assistant OS available"
) )
if ( if (
JobCondition.OS_AGENT in self.conditions JobCondition.OS_AGENT in used_conditions
and HostFeature.OS_AGENT not in self.sys_host.features and HostFeature.OS_AGENT not in self.sys_host.features
): ):
raise JobConditionException( raise JobConditionException(
@ -230,7 +230,7 @@ class Job(CoreSysAttributes):
) )
if ( if (
JobCondition.HOST_NETWORK in self.conditions JobCondition.HOST_NETWORK in used_conditions
and not self.sys_dbus.network.is_connected and not self.sys_dbus.network.is_connected
): ):
raise JobConditionException( raise JobConditionException(
@ -238,7 +238,7 @@ class Job(CoreSysAttributes):
) )
if ( if (
JobCondition.AUTO_UPDATE in self.conditions JobCondition.AUTO_UPDATE in used_conditions
and not self.sys_updater.auto_update and not self.sys_updater.auto_update
): ):
raise JobConditionException( raise JobConditionException(
@ -246,14 +246,14 @@ class Job(CoreSysAttributes):
) )
if ( if (
JobCondition.SUPERVISOR_UPDATED in self.conditions JobCondition.SUPERVISOR_UPDATED in used_conditions
and self.sys_supervisor.need_update and self.sys_supervisor.need_update
): ):
raise JobConditionException( raise JobConditionException(
f"'{self._method.__qualname__}' blocked from execution, supervisor needs to be updated first" f"'{self._method.__qualname__}' blocked from execution, supervisor needs to be updated first"
) )
if JobCondition.PLUGINS_UPDATED in self.conditions and ( if JobCondition.PLUGINS_UPDATED in used_conditions and (
out_of_date := [ out_of_date := [
plugin for plugin in self.sys_plugins.all_plugins if plugin.need_update plugin for plugin in self.sys_plugins.all_plugins if plugin.need_update
] ]

View File

@ -16,13 +16,16 @@ from supervisor.exceptions import (
JobException, JobException,
PluginJobError, PluginJobError,
) )
from supervisor.host.const import HostFeature
from supervisor.host.manager import HostManager
from supervisor.jobs.const import JobExecutionLimit from supervisor.jobs.const import JobExecutionLimit
from supervisor.jobs.decorator import Job, JobCondition from supervisor.jobs.decorator import Job, JobCondition
from supervisor.plugins.audio import PluginAudio
from supervisor.resolution.const import UnhealthyReason from supervisor.resolution.const import UnhealthyReason
from supervisor.utils.dt import utcnow from supervisor.utils.dt import utcnow
async def test_healthy(coresys: CoreSys): async def test_healthy(coresys: CoreSys, caplog: pytest.LogCaptureFixture):
"""Test the healty decorator.""" """Test the healty decorator."""
class TestClass: class TestClass:
@ -42,6 +45,10 @@ async def test_healthy(coresys: CoreSys):
coresys.resolution.unhealthy = UnhealthyReason.DOCKER coresys.resolution.unhealthy = UnhealthyReason.DOCKER
assert not await test.execute() assert not await test.execute()
assert "blocked from execution, system is not healthy - docker" in caplog.text
coresys.jobs.ignore_conditions = [JobCondition.HEALTHY]
assert await test.execute()
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -93,6 +100,13 @@ async def test_internet(
assert await test.execute_host() is host_result assert await test.execute_host() is host_result
assert await test.execute_system() is system_result assert await test.execute_system() is system_result
coresys.jobs.ignore_conditions = [
JobCondition.INTERNET_HOST,
JobCondition.INTERNET_SYSTEM,
]
assert await test.execute_host()
assert await test.execute_system()
async def test_free_space(coresys: CoreSys): async def test_free_space(coresys: CoreSys):
"""Test the free_space decorator.""" """Test the free_space decorator."""
@ -116,6 +130,9 @@ 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()
coresys.jobs.ignore_conditions = [JobCondition.FREE_SPACE]
assert await test.execute()
async def test_haos(coresys: CoreSys): async def test_haos(coresys: CoreSys):
"""Test the haos decorator.""" """Test the haos decorator."""
@ -139,9 +156,12 @@ async def test_haos(coresys: CoreSys):
coresys.os._available = False coresys.os._available = False
assert not await test.execute() assert not await test.execute()
coresys.jobs.ignore_conditions = [JobCondition.HAOS]
assert await test.execute()
async def test_exception(coresys: CoreSys):
"""Test the healty decorator.""" async def test_exception(coresys: CoreSys, capture_exception: Mock):
"""Test handled exception."""
class TestClass: class TestClass:
"""Test class.""" """Test class."""
@ -160,9 +180,12 @@ async def test_exception(coresys: CoreSys):
with pytest.raises(HassioError): with pytest.raises(HassioError):
assert await test.execute() assert await test.execute()
capture_exception.assert_not_called()
async def test_exception_not_handle(coresys: CoreSys):
"""Test the healty decorator.""" async def test_exception_not_handle(coresys: CoreSys, capture_exception: Mock):
"""Test unhandled exception."""
err = Exception()
class TestClass: class TestClass:
"""Test class.""" """Test class."""
@ -174,13 +197,15 @@ async def test_exception_not_handle(coresys: CoreSys):
@Job(conditions=[JobCondition.HEALTHY]) @Job(conditions=[JobCondition.HEALTHY])
async def execute(self): async def execute(self):
"""Execute the class method.""" """Execute the class method."""
raise Exception() raise err
test = TestClass(coresys) test = TestClass(coresys)
with pytest.raises(JobException): with pytest.raises(JobException):
assert await test.execute() assert await test.execute()
capture_exception.assert_called_once_with(err)
async def test_running(coresys: CoreSys): async def test_running(coresys: CoreSys):
"""Test the running decorator.""" """Test the running decorator."""
@ -205,30 +230,6 @@ async def test_running(coresys: CoreSys):
coresys.core.state = CoreState.FREEZE coresys.core.state = CoreState.FREEZE
assert not await test.execute() assert not await test.execute()
async def test_ignore_conditions(coresys: CoreSys):
"""Test the ignore conditions decorator."""
class TestClass:
"""Test class."""
def __init__(self, coresys: CoreSys):
"""Initialize the test class."""
self.coresys = coresys
@Job(conditions=[JobCondition.RUNNING])
async def execute(self):
"""Execute the class method."""
return True
test = TestClass(coresys)
coresys.core.state = CoreState.RUNNING
assert await test.execute()
coresys.core.state = CoreState.FREEZE
assert not await test.execute()
coresys.jobs.ignore_conditions = [JobCondition.RUNNING] coresys.jobs.ignore_conditions = [JobCondition.RUNNING]
assert await test.execute() assert await test.execute()
@ -258,7 +259,7 @@ async def test_exception_conditions(coresys: CoreSys):
await test.execute() await test.execute()
async def test_exectution_limit_single_wait( async def test_execution_limit_single_wait(
coresys: CoreSys, loop: asyncio.BaseEventLoop coresys: CoreSys, loop: asyncio.BaseEventLoop
): ):
"""Test the single wait job execution limit.""" """Test the single wait job execution limit."""
@ -355,7 +356,7 @@ async def test_execution_limit_throttle_rate_limit(
assert test.call == 3 assert test.call == 3
async def test_exectution_limit_throttle(coresys: CoreSys, loop: asyncio.BaseEventLoop): async def test_execution_limit_throttle(coresys: CoreSys, loop: asyncio.BaseEventLoop):
"""Test the ignore conditions decorator.""" """Test the ignore conditions decorator."""
class TestClass: class TestClass:
@ -384,7 +385,7 @@ async def test_exectution_limit_throttle(coresys: CoreSys, loop: asyncio.BaseEve
assert test.call == 1 assert test.call == 1
async def test_exectution_limit_once(coresys: CoreSys, loop: asyncio.BaseEventLoop): async def test_execution_limit_once(coresys: CoreSys, loop: asyncio.BaseEventLoop):
"""Test the ignore conditions decorator.""" """Test the ignore conditions decorator."""
class TestClass: class TestClass:
@ -436,6 +437,9 @@ async def test_supervisor_updated(coresys: CoreSys):
): ):
assert not await test.execute() assert not await test.execute()
coresys.jobs.ignore_conditions = [JobCondition.SUPERVISOR_UPDATED]
assert await test.execute()
async def test_plugins_updated(coresys: CoreSys): async def test_plugins_updated(coresys: CoreSys):
"""Test the plugins updated decorator.""" """Test the plugins updated decorator."""
@ -459,13 +463,16 @@ async def test_plugins_updated(coresys: CoreSys):
assert await test.execute() assert await test.execute()
with patch.object( with patch.object(
type(coresys.plugins.audio), "need_update", new=PropertyMock(return_value=True) PluginAudio, "need_update", new=PropertyMock(return_value=True)
), patch.object( ), patch.object(
type(coresys.plugins.audio), "update", side_effect=[AudioUpdateError, None] PluginAudio, "update", side_effect=[AudioUpdateError, None, AudioUpdateError]
): ):
assert not await test.execute() assert not await test.execute()
assert await test.execute() assert await test.execute()
coresys.jobs.ignore_conditions = [JobCondition.PLUGINS_UPDATED]
assert await test.execute()
async def test_auto_update(coresys: CoreSys): async def test_auto_update(coresys: CoreSys):
"""Test the auto update decorator.""" """Test the auto update decorator."""
@ -489,9 +496,12 @@ async def test_auto_update(coresys: CoreSys):
coresys.updater.auto_update = False coresys.updater.auto_update = False
assert not await test.execute() assert not await test.execute()
coresys.jobs.ignore_conditions = [JobCondition.AUTO_UPDATE]
assert await test.execute()
async def test_unhealthy(coresys: CoreSys, caplog: pytest.LogCaptureFixture):
"""Test the healthy decorator when unhealthy.""" async def test_os_agent(coresys: CoreSys):
"""Test the os agent decorator."""
class TestClass: class TestClass:
"""Test class.""" """Test class."""
@ -500,23 +510,27 @@ async def test_unhealthy(coresys: CoreSys, caplog: pytest.LogCaptureFixture):
"""Initialize the test class.""" """Initialize the test class."""
self.coresys = coresys self.coresys = coresys
@Job(conditions=[JobCondition.HEALTHY]) @Job(conditions=[JobCondition.OS_AGENT])
async def execute(self) -> bool: async def execute(self) -> bool:
"""Execute the class method.""" """Execute the class method."""
return True return True
test = TestClass(coresys) test = TestClass(coresys)
coresys.resolution.unhealthy = UnhealthyReason.SETUP with patch.object(
assert not await test.execute() HostManager, "supported_features", return_value=[HostFeature.OS_AGENT]
assert "blocked from execution, system is not healthy - setup" in caplog.text ):
assert await test.execute()
coresys.jobs.ignore_conditions = [JobCondition.HEALTHY] coresys.host.supported_features.cache_clear()
assert await test.execute() with patch.object(HostManager, "supported_features", return_value=[]):
assert not await test.execute()
coresys.jobs.ignore_conditions = [JobCondition.OS_AGENT]
assert await test.execute()
async def test_unhandled_exception(coresys: CoreSys, capture_exception: Mock): async def test_host_network(coresys: CoreSys):
"""Test an unhandled exception from job.""" """Test the host network decorator."""
err = OSError()
class TestClass: class TestClass:
"""Test class.""" """Test class."""
@ -525,13 +539,16 @@ async def test_unhandled_exception(coresys: CoreSys, capture_exception: Mock):
"""Initialize the test class.""" """Initialize the test class."""
self.coresys = coresys self.coresys = coresys
@Job(conditions=[JobCondition.HEALTHY]) @Job(conditions=[JobCondition.HOST_NETWORK])
async def execute(self) -> None: async def execute(self) -> bool:
"""Execute the class method.""" """Execute the class method."""
raise err return True
test = TestClass(coresys) test = TestClass(coresys)
with pytest.raises(JobException): assert await test.execute()
await test.execute()
capture_exception.assert_called_once_with(err) coresys.dbus.network.disconnect()
assert not await test.execute()
coresys.jobs.ignore_conditions = [JobCondition.HOST_NETWORK]
assert await test.execute()