mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-22 16:46:29 +00:00
Add JobManager API ignore (#2290)
* Disable job condition for unhealth & unsupported systems * Add JobManager API ignore * Apply suggestions from code review Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * Update tests/resolution/evaluation/test_evaluate_job_conditions.py Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * fix names * address comments * Update decorator.py * adjust security * add reset * Apply suggestions from code review Co-authored-by: Joakim Sørensen <joasoe@gmail.com> Co-authored-by: Paulus Schoutsen <balloob@gmail.com> Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
This commit is contained in:
parent
19d8de89df
commit
845c935b39
@ -18,6 +18,7 @@ from .homeassistant import APIHomeAssistant
|
|||||||
from .host import APIHost
|
from .host import APIHost
|
||||||
from .info import APIInfo
|
from .info import APIInfo
|
||||||
from .ingress import APIIngress
|
from .ingress import APIIngress
|
||||||
|
from .jobs import APIJobs
|
||||||
from .multicast import APIMulticast
|
from .multicast import APIMulticast
|
||||||
from .network import APINetwork
|
from .network import APINetwork
|
||||||
from .observer import APIObserver
|
from .observer import APIObserver
|
||||||
@ -72,6 +73,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
self._register_network()
|
self._register_network()
|
||||||
self._register_observer()
|
self._register_observer()
|
||||||
self._register_os()
|
self._register_os()
|
||||||
|
self._register_jobs()
|
||||||
self._register_panel()
|
self._register_panel()
|
||||||
self._register_proxy()
|
self._register_proxy()
|
||||||
self._register_resolution()
|
self._register_resolution()
|
||||||
@ -141,6 +143,19 @@ class RestAPI(CoreSysAttributes):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _register_jobs(self) -> None:
|
||||||
|
"""Register Jobs functions."""
|
||||||
|
api_jobs = APIJobs()
|
||||||
|
api_jobs.coresys = self.coresys
|
||||||
|
|
||||||
|
self.webapp.add_routes(
|
||||||
|
[
|
||||||
|
web.get("/jobs/info", api_jobs.info),
|
||||||
|
web.post("/jobs/options", api_jobs.options),
|
||||||
|
web.post("/jobs/reset", api_jobs.reset),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def _register_cli(self) -> None:
|
def _register_cli(self) -> None:
|
||||||
"""Register HA cli functions."""
|
"""Register HA cli functions."""
|
||||||
api_cli = APICli()
|
api_cli = APICli()
|
||||||
|
42
supervisor/api/jobs.py
Normal file
42
supervisor/api/jobs.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
"""Init file for Supervisor Jobs RESTful API."""
|
||||||
|
import logging
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from ..coresys import CoreSysAttributes
|
||||||
|
from ..jobs.const import ATTR_IGNORE_CONDITIONS, JobCondition
|
||||||
|
from .utils import api_process, api_validate
|
||||||
|
|
||||||
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SCHEMA_OPTIONS = vol.Schema(
|
||||||
|
{vol.Optional(ATTR_IGNORE_CONDITIONS): [vol.Coerce(JobCondition)]}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class APIJobs(CoreSysAttributes):
|
||||||
|
"""Handle RESTful API for OS functions."""
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def info(self, request: web.Request) -> Dict[str, Any]:
|
||||||
|
"""Return JobManager information."""
|
||||||
|
return {
|
||||||
|
ATTR_IGNORE_CONDITIONS: self.sys_jobs.ignore_conditions,
|
||||||
|
}
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def options(self, request: web.Request) -> None:
|
||||||
|
"""Set options for JobManager."""
|
||||||
|
body = await api_validate(SCHEMA_OPTIONS, request)
|
||||||
|
|
||||||
|
if ATTR_IGNORE_CONDITIONS in body:
|
||||||
|
self.sys_jobs.ignore_conditions = body[ATTR_IGNORE_CONDITIONS]
|
||||||
|
|
||||||
|
self.sys_jobs.save_data()
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def reset(self, request: web.Request) -> None:
|
||||||
|
"""Reset options for JobManager."""
|
||||||
|
self.sys_jobs.reset_data()
|
@ -87,6 +87,7 @@ ADDONS_ROLE_ACCESS = {
|
|||||||
r"|/core/.+"
|
r"|/core/.+"
|
||||||
r"|/dns/.+"
|
r"|/dns/.+"
|
||||||
r"|/docker/.+"
|
r"|/docker/.+"
|
||||||
|
r"|/jobs/.+"
|
||||||
r"|/hardware/.+"
|
r"|/hardware/.+"
|
||||||
r"|/hassos/.+"
|
r"|/hassos/.+"
|
||||||
r"|/homeassistant/.+"
|
r"|/homeassistant/.+"
|
||||||
|
@ -3,6 +3,9 @@ import logging
|
|||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from ..coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
|
from ..utils.json import JsonConfig
|
||||||
|
from .const import ATTR_IGNORE_CONDITIONS, FILE_CONFIG_JOBS, JobCondition
|
||||||
|
from .validate import SCHEMA_JOBS_CONFIG
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__package__)
|
_LOGGER: logging.Logger = logging.getLogger(__package__)
|
||||||
|
|
||||||
@ -46,11 +49,12 @@ class SupervisorJob(CoreSysAttributes):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class JobManager(CoreSysAttributes):
|
class JobManager(JsonConfig, CoreSysAttributes):
|
||||||
"""Job class."""
|
"""Job class."""
|
||||||
|
|
||||||
def __init__(self, coresys: CoreSys):
|
def __init__(self, coresys: CoreSys):
|
||||||
"""Initialize the JobManager class."""
|
"""Initialize the JobManager class."""
|
||||||
|
super().__init__(FILE_CONFIG_JOBS, SCHEMA_JOBS_CONFIG)
|
||||||
self.coresys: CoreSys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
self._jobs: Dict[str, SupervisorJob] = {}
|
self._jobs: Dict[str, SupervisorJob] = {}
|
||||||
|
|
||||||
@ -59,6 +63,16 @@ class JobManager(CoreSysAttributes):
|
|||||||
"""Return a list of current jobs."""
|
"""Return a list of current jobs."""
|
||||||
return self._jobs
|
return self._jobs
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ignore_conditions(self) -> List[JobCondition]:
|
||||||
|
"""Return a list of ingore condition."""
|
||||||
|
return self._data[ATTR_IGNORE_CONDITIONS]
|
||||||
|
|
||||||
|
@ignore_conditions.setter
|
||||||
|
def ignore_conditions(self, value: List[JobCondition]) -> None:
|
||||||
|
"""Set a list of ignored condition."""
|
||||||
|
self._data[ATTR_IGNORE_CONDITIONS] = value
|
||||||
|
|
||||||
def get_job(self, name: str) -> SupervisorJob:
|
def get_job(self, name: str) -> SupervisorJob:
|
||||||
"""Return a job, create one if it does not exsist."""
|
"""Return a job, create one if it does not exsist."""
|
||||||
if name not in self._jobs:
|
if name not in self._jobs:
|
||||||
|
19
supervisor/jobs/const.py
Normal file
19
supervisor/jobs/const.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
"""Jobs constants."""
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ..const import SUPERVISOR_DATA
|
||||||
|
|
||||||
|
FILE_CONFIG_JOBS = Path(SUPERVISOR_DATA, "jobs.json")
|
||||||
|
|
||||||
|
ATTR_IGNORE_CONDITIONS = "ignore_conditions"
|
||||||
|
|
||||||
|
|
||||||
|
class JobCondition(str, Enum):
|
||||||
|
"""Job condition enum."""
|
||||||
|
|
||||||
|
FREE_SPACE = "free_space"
|
||||||
|
HEALTHY = "healthy"
|
||||||
|
INTERNET_SYSTEM = "internet_system"
|
||||||
|
INTERNET_HOST = "internet_host"
|
||||||
|
RUNNING = "running"
|
@ -1,5 +1,4 @@
|
|||||||
"""Job decorator."""
|
"""Job decorator."""
|
||||||
from enum import Enum
|
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
@ -9,20 +8,11 @@ from ..const import CoreState
|
|||||||
from ..coresys import CoreSys
|
from ..coresys import CoreSys
|
||||||
from ..exceptions import HassioError, JobException
|
from ..exceptions import HassioError, JobException
|
||||||
from ..resolution.const import MINIMUM_FREE_SPACE_THRESHOLD, ContextType, IssueType
|
from ..resolution.const import MINIMUM_FREE_SPACE_THRESHOLD, ContextType, IssueType
|
||||||
|
from .const import JobCondition
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__package__)
|
_LOGGER: logging.Logger = logging.getLogger(__package__)
|
||||||
|
|
||||||
|
|
||||||
class JobCondition(str, Enum):
|
|
||||||
"""Job condition enum."""
|
|
||||||
|
|
||||||
FREE_SPACE = "free_space"
|
|
||||||
HEALTHY = "healthy"
|
|
||||||
INTERNET_SYSTEM = "internet_system"
|
|
||||||
INTERNET_HOST = "internet_host"
|
|
||||||
RUNNING = "running"
|
|
||||||
|
|
||||||
|
|
||||||
class Job:
|
class Job:
|
||||||
"""Supervisor job decorator."""
|
"""Supervisor job decorator."""
|
||||||
|
|
||||||
@ -76,66 +66,72 @@ class Job:
|
|||||||
|
|
||||||
def _check_conditions(self):
|
def _check_conditions(self):
|
||||||
"""Check conditions."""
|
"""Check conditions."""
|
||||||
if JobCondition.HEALTHY in self.conditions:
|
used_conditions = set(self.conditions) - set(
|
||||||
if not self._coresys.core.healthy:
|
self._coresys.jobs.ignore_conditions
|
||||||
_LOGGER.warning(
|
)
|
||||||
"'%s' blocked from execution, system is not healthy",
|
ignored_conditions = set(self.conditions) & set(
|
||||||
self._method.__qualname__,
|
self._coresys.jobs.ignore_conditions
|
||||||
)
|
)
|
||||||
return False
|
|
||||||
|
|
||||||
if JobCondition.RUNNING in self.conditions:
|
# Check if somethings is ignored
|
||||||
if self._coresys.core.state != CoreState.RUNNING:
|
if ignored_conditions:
|
||||||
_LOGGER.warning(
|
_LOGGER.critical(
|
||||||
"'%s' blocked from execution, system is not running",
|
"The following job conditions are ignored and will make the system unstable when they occur: %s",
|
||||||
self._method.__qualname__,
|
ignored_conditions,
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
if JobCondition.FREE_SPACE in self.conditions:
|
|
||||||
free_space = self._coresys.host.info.free_space
|
|
||||||
if free_space < MINIMUM_FREE_SPACE_THRESHOLD:
|
|
||||||
_LOGGER.warning(
|
|
||||||
"'%s' blocked from execution, not enough free space (%sGB) left on the device",
|
|
||||||
self._method.__qualname__,
|
|
||||||
free_space,
|
|
||||||
)
|
|
||||||
self._coresys.resolution.create_issue(
|
|
||||||
IssueType.FREE_SPACE, ContextType.SYSTEM
|
|
||||||
)
|
|
||||||
return False
|
|
||||||
|
|
||||||
if any(
|
|
||||||
internet in self.conditions
|
|
||||||
for internet in (
|
|
||||||
JobCondition.INTERNET_SYSTEM,
|
|
||||||
JobCondition.INTERNET_HOST,
|
|
||||||
)
|
)
|
||||||
):
|
|
||||||
if self._coresys.core.state not in (
|
|
||||||
CoreState.SETUP,
|
|
||||||
CoreState.RUNNING,
|
|
||||||
):
|
|
||||||
return True
|
|
||||||
|
|
||||||
if (
|
if JobCondition.HEALTHY in used_conditions and not self._coresys.core.healthy:
|
||||||
JobCondition.INTERNET_SYSTEM in self.conditions
|
_LOGGER.warning(
|
||||||
and not self._coresys.supervisor.connectivity
|
"'%s' blocked from execution, system is not healthy",
|
||||||
):
|
self._method.__qualname__,
|
||||||
_LOGGER.warning(
|
)
|
||||||
"'%s' blocked from execution, no supervisor internet connection",
|
return False
|
||||||
self._method.__qualname__,
|
|
||||||
)
|
if (
|
||||||
return False
|
JobCondition.RUNNING in used_conditions
|
||||||
elif (
|
and self._coresys.core.state != CoreState.RUNNING
|
||||||
JobCondition.INTERNET_HOST in self.conditions
|
):
|
||||||
and self._coresys.host.network.connectivity is not None
|
_LOGGER.warning(
|
||||||
and not self._coresys.host.network.connectivity
|
"'%s' blocked from execution, system is not running",
|
||||||
):
|
self._method.__qualname__,
|
||||||
_LOGGER.warning(
|
)
|
||||||
"'%s' blocked from execution, no host internet connection",
|
return False
|
||||||
self._method.__qualname__,
|
|
||||||
)
|
if (
|
||||||
return False
|
JobCondition.FREE_SPACE in used_conditions
|
||||||
|
and self._coresys.host.info.free_space < MINIMUM_FREE_SPACE_THRESHOLD
|
||||||
|
):
|
||||||
|
_LOGGER.warning(
|
||||||
|
"'%s' blocked from execution, not enough free space (%sGB) left on the device",
|
||||||
|
self._method.__qualname__,
|
||||||
|
self._coresys.host.info.free_space,
|
||||||
|
)
|
||||||
|
self._coresys.resolution.create_issue(
|
||||||
|
IssueType.FREE_SPACE, ContextType.SYSTEM
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if (
|
||||||
|
JobCondition.INTERNET_SYSTEM in self.conditions
|
||||||
|
and not self._coresys.supervisor.connectivity
|
||||||
|
and self._coresys.core.state in (CoreState.SETUP, CoreState.RUNNING)
|
||||||
|
):
|
||||||
|
_LOGGER.warning(
|
||||||
|
"'%s' blocked from execution, no supervisor internet connection",
|
||||||
|
self._method.__qualname__,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if (
|
||||||
|
JobCondition.INTERNET_HOST in self.conditions
|
||||||
|
and self._coresys.host.network.connectivity is not None
|
||||||
|
and not self._coresys.host.network.connectivity
|
||||||
|
and self._coresys.core.state in (CoreState.SETUP, CoreState.RUNNING)
|
||||||
|
):
|
||||||
|
_LOGGER.warning(
|
||||||
|
"'%s' blocked from execution, no host internet connection",
|
||||||
|
self._method.__qualname__,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
12
supervisor/jobs/validate.py
Normal file
12
supervisor/jobs/validate.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
"""Validate services schema."""
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from .const import ATTR_IGNORE_CONDITIONS, JobCondition
|
||||||
|
|
||||||
|
SCHEMA_JOBS_CONFIG = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(ATTR_IGNORE_CONDITIONS, default=list): [vol.Coerce(JobCondition)],
|
||||||
|
},
|
||||||
|
extra=vol.REMOVE_EXTRA,
|
||||||
|
)
|
@ -31,6 +31,7 @@ class UnsupportedReason(str, Enum):
|
|||||||
OS = "os"
|
OS = "os"
|
||||||
PRIVILEGED = "privileged"
|
PRIVILEGED = "privileged"
|
||||||
SYSTEMD = "systemd"
|
SYSTEMD = "systemd"
|
||||||
|
JOB_CONDITIONS = "job_conditions"
|
||||||
|
|
||||||
|
|
||||||
class UnhealthyReason(str, Enum):
|
class UnhealthyReason(str, Enum):
|
||||||
|
@ -7,6 +7,7 @@ from .evaluations.container import EvaluateContainer
|
|||||||
from .evaluations.dbus import EvaluateDbus
|
from .evaluations.dbus import EvaluateDbus
|
||||||
from .evaluations.docker_configuration import EvaluateDockerConfiguration
|
from .evaluations.docker_configuration import EvaluateDockerConfiguration
|
||||||
from .evaluations.docker_version import EvaluateDockerVersion
|
from .evaluations.docker_version import EvaluateDockerVersion
|
||||||
|
from .evaluations.job_conditions import EvaluateJobConditions
|
||||||
from .evaluations.lxc import EvaluateLxc
|
from .evaluations.lxc import EvaluateLxc
|
||||||
from .evaluations.network_manager import EvaluateNetworkManager
|
from .evaluations.network_manager import EvaluateNetworkManager
|
||||||
from .evaluations.operating_system import EvaluateOperatingSystem
|
from .evaluations.operating_system import EvaluateOperatingSystem
|
||||||
@ -39,6 +40,7 @@ class ResolutionEvaluation(CoreSysAttributes):
|
|||||||
self._operating_system = EvaluateOperatingSystem(coresys)
|
self._operating_system = EvaluateOperatingSystem(coresys)
|
||||||
self._privileged = EvaluatePrivileged(coresys)
|
self._privileged = EvaluatePrivileged(coresys)
|
||||||
self._systemd = EvaluateSystemd(coresys)
|
self._systemd = EvaluateSystemd(coresys)
|
||||||
|
self._job_conditions = EvaluateJobConditions(coresys)
|
||||||
|
|
||||||
async def evaluate_system(self) -> None:
|
async def evaluate_system(self) -> None:
|
||||||
"""Evaluate the system."""
|
"""Evaluate the system."""
|
||||||
@ -52,6 +54,7 @@ class ResolutionEvaluation(CoreSysAttributes):
|
|||||||
await self._operating_system()
|
await self._operating_system()
|
||||||
await self._privileged()
|
await self._privileged()
|
||||||
await self._systemd()
|
await self._systemd()
|
||||||
|
await self._job_conditions()
|
||||||
|
|
||||||
if any(reason in self.sys_resolution.unsupported for reason in UNHEALTHY):
|
if any(reason in self.sys_resolution.unsupported for reason in UNHEALTHY):
|
||||||
self.sys_resolution.unhealthy = UnhealthyReason.DOCKER
|
self.sys_resolution.unhealthy = UnhealthyReason.DOCKER
|
||||||
|
29
supervisor/resolution/evaluations/job_conditions.py
Normal file
29
supervisor/resolution/evaluations/job_conditions.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
"""Evaluation class for Job Conditions."""
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from ...const import CoreState
|
||||||
|
from ..const import UnsupportedReason
|
||||||
|
from .base import EvaluateBase
|
||||||
|
|
||||||
|
|
||||||
|
class EvaluateJobConditions(EvaluateBase):
|
||||||
|
"""Evaluate job conditions."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def reason(self) -> UnsupportedReason:
|
||||||
|
"""Return a UnsupportedReason enum."""
|
||||||
|
return UnsupportedReason.JOB_CONDITIONS
|
||||||
|
|
||||||
|
@property
|
||||||
|
def on_failure(self) -> str:
|
||||||
|
"""Return a string that is printed when self.evaluate is False."""
|
||||||
|
return "Found unsupported job conditions settings."
|
||||||
|
|
||||||
|
@property
|
||||||
|
def states(self) -> List[CoreState]:
|
||||||
|
"""Return a list of valid states when this evaluation can run."""
|
||||||
|
return [CoreState.INITIALIZE, CoreState.SETUP, CoreState.RUNNING]
|
||||||
|
|
||||||
|
async def evaluate(self) -> None:
|
||||||
|
"""Run evaluation."""
|
||||||
|
return len(self.sys_jobs.ignore_conditions) > 0
|
52
tests/api/test_jobs.py
Normal file
52
tests/api/test_jobs.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
"""Test Docker API."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from supervisor.jobs.const import ATTR_IGNORE_CONDITIONS, JobCondition
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_api_jobs_info(api_client):
|
||||||
|
"""Test jobs info api."""
|
||||||
|
resp = await api_client.get("/jobs/info")
|
||||||
|
result = await resp.json()
|
||||||
|
|
||||||
|
assert result["data"][ATTR_IGNORE_CONDITIONS] == []
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_api_jobs_options(api_client, coresys):
|
||||||
|
"""Test jobs options api."""
|
||||||
|
resp = await api_client.post(
|
||||||
|
"/jobs/options", json={ATTR_IGNORE_CONDITIONS: [JobCondition.HEALTHY]}
|
||||||
|
)
|
||||||
|
result = await resp.json()
|
||||||
|
assert result["result"] == "ok"
|
||||||
|
|
||||||
|
resp = await api_client.get("/jobs/info")
|
||||||
|
result = await resp.json()
|
||||||
|
assert result["data"][ATTR_IGNORE_CONDITIONS] == [JobCondition.HEALTHY]
|
||||||
|
|
||||||
|
assert coresys.jobs.save_data.called
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_api_jobs_reset(api_client, coresys):
|
||||||
|
"""Test jobs reset api."""
|
||||||
|
resp = await api_client.post(
|
||||||
|
"/jobs/options", json={ATTR_IGNORE_CONDITIONS: [JobCondition.HEALTHY]}
|
||||||
|
)
|
||||||
|
result = await resp.json()
|
||||||
|
assert result["result"] == "ok"
|
||||||
|
|
||||||
|
resp = await api_client.get("/jobs/info")
|
||||||
|
result = await resp.json()
|
||||||
|
assert result["data"][ATTR_IGNORE_CONDITIONS] == [JobCondition.HEALTHY]
|
||||||
|
|
||||||
|
assert coresys.jobs.save_data.called
|
||||||
|
assert coresys.jobs.ignore_conditions == [JobCondition.HEALTHY]
|
||||||
|
|
||||||
|
resp = await api_client.post("/jobs/reset")
|
||||||
|
result = await resp.json()
|
||||||
|
assert result["result"] == "ok"
|
||||||
|
|
||||||
|
assert coresys.jobs.ignore_conditions == []
|
@ -131,6 +131,7 @@ async def coresys(loop, docker, network_manager, aiohttp_client) -> CoreSys:
|
|||||||
coresys_obj._auth.save_data = MagicMock()
|
coresys_obj._auth.save_data = MagicMock()
|
||||||
coresys_obj._updater.save_data = MagicMock()
|
coresys_obj._updater.save_data = MagicMock()
|
||||||
coresys_obj._config.save_data = MagicMock()
|
coresys_obj._config.save_data = MagicMock()
|
||||||
|
coresys_obj._jobs.save_data = MagicMock()
|
||||||
|
|
||||||
# Mock test client
|
# Mock test client
|
||||||
coresys_obj.arch._default_arch = "amd64"
|
coresys_obj.arch._default_arch = "amd64"
|
||||||
|
@ -205,3 +205,30 @@ 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]
|
||||||
|
assert await test.execute()
|
||||||
|
46
tests/resolution/evaluation/test_evaluate_job_conditions.py
Normal file
46
tests/resolution/evaluation/test_evaluate_job_conditions.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
"""Test evaluation base."""
|
||||||
|
# pylint: disable=import-error,protected-access
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from supervisor.const import CoreState
|
||||||
|
from supervisor.coresys import CoreSys
|
||||||
|
from supervisor.jobs.const import JobCondition
|
||||||
|
from supervisor.resolution.evaluations.job_conditions import EvaluateJobConditions
|
||||||
|
|
||||||
|
|
||||||
|
async def test_evaluation(coresys: CoreSys):
|
||||||
|
"""Test evaluation."""
|
||||||
|
job_conditions = EvaluateJobConditions(coresys)
|
||||||
|
coresys.core.state = CoreState.SETUP
|
||||||
|
|
||||||
|
await job_conditions()
|
||||||
|
assert job_conditions.reason not in coresys.resolution.unsupported
|
||||||
|
|
||||||
|
coresys.jobs.ignore_conditions = [JobCondition.HEALTHY]
|
||||||
|
await job_conditions()
|
||||||
|
assert job_conditions.reason in coresys.resolution.unsupported
|
||||||
|
|
||||||
|
|
||||||
|
async def test_did_run(coresys: CoreSys):
|
||||||
|
"""Test that the evaluation ran as expected."""
|
||||||
|
job_conditions = EvaluateJobConditions(coresys)
|
||||||
|
should_run = job_conditions.states
|
||||||
|
should_not_run = [state for state in CoreState if state not in should_run]
|
||||||
|
assert len(should_run) != 0
|
||||||
|
assert len(should_not_run) != 0
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"supervisor.resolution.evaluations.job_conditions.EvaluateJobConditions.evaluate",
|
||||||
|
return_value=None,
|
||||||
|
) as evaluate:
|
||||||
|
for state in should_run:
|
||||||
|
coresys.core.state = state
|
||||||
|
await job_conditions()
|
||||||
|
evaluate.assert_called_once()
|
||||||
|
evaluate.reset_mock()
|
||||||
|
|
||||||
|
for state in should_not_run:
|
||||||
|
coresys.core.state = state
|
||||||
|
await job_conditions()
|
||||||
|
evaluate.assert_not_called()
|
||||||
|
evaluate.reset_mock()
|
Loading…
x
Reference in New Issue
Block a user