mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-18 06:36:30 +00:00
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:
parent
83e5359bd2
commit
72d81e43dd
@ -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
|
||||||
]
|
]
|
||||||
|
@ -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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user