mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-16 13:46:31 +00:00
List current job tree in api (#4514)
This commit is contained in:
parent
f93b753c03
commit
4838b280ad
@ -33,6 +33,7 @@ ATTR_FALLBACK = "fallback"
|
||||
ATTR_FILESYSTEMS = "filesystems"
|
||||
ATTR_HEARTBEAT_LED = "heartbeat_led"
|
||||
ATTR_IDENTIFIERS = "identifiers"
|
||||
ATTR_JOBS = "jobs"
|
||||
ATTR_LLMNR = "llmnr"
|
||||
ATTR_LLMNR_HOSTNAME = "llmnr_hostname"
|
||||
ATTR_MDNS = "mdns"
|
||||
|
@ -6,7 +6,9 @@ from aiohttp import web
|
||||
import voluptuous as vol
|
||||
|
||||
from ..coresys import CoreSysAttributes
|
||||
from ..jobs import SupervisorJob
|
||||
from ..jobs.const import ATTR_IGNORE_CONDITIONS, JobCondition
|
||||
from .const import ATTR_JOBS
|
||||
from .utils import api_process, api_validate
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
@ -19,11 +21,45 @@ SCHEMA_OPTIONS = vol.Schema(
|
||||
class APIJobs(CoreSysAttributes):
|
||||
"""Handle RESTful API for OS functions."""
|
||||
|
||||
def _list_jobs(self) -> list[dict[str, Any]]:
|
||||
"""Return current job tree."""
|
||||
jobs_by_parent: dict[str | None, list[SupervisorJob]] = {}
|
||||
for job in self.sys_jobs.jobs:
|
||||
if job.internal:
|
||||
continue
|
||||
|
||||
if job.parent_id not in jobs_by_parent:
|
||||
jobs_by_parent[job.parent_id] = [job]
|
||||
else:
|
||||
jobs_by_parent[job.parent_id].append(job)
|
||||
|
||||
job_list: list[dict[str, Any]] = []
|
||||
queue: list[tuple[list[dict[str, Any]], SupervisorJob]] = [
|
||||
(job_list, job) for job in jobs_by_parent.get(None, [])
|
||||
]
|
||||
|
||||
while queue:
|
||||
(current_list, current_job) = queue.pop(0)
|
||||
child_jobs: list[dict[str, Any]] = []
|
||||
|
||||
# We remove parent_id and instead use that info to represent jobs as a tree
|
||||
job_dict = current_job.as_dict() | {"child_jobs": child_jobs}
|
||||
job_dict.pop("parent_id")
|
||||
current_list.append(job_dict)
|
||||
|
||||
if current_job.uuid in jobs_by_parent:
|
||||
queue.extend(
|
||||
[(child_jobs, job) for job in jobs_by_parent.get(current_job.uuid)]
|
||||
)
|
||||
|
||||
return job_list
|
||||
|
||||
@api_process
|
||||
async def info(self, request: web.Request) -> dict[str, Any]:
|
||||
"""Return JobManager information."""
|
||||
return {
|
||||
ATTR_IGNORE_CONDITIONS: self.sys_jobs.ignore_conditions,
|
||||
ATTR_JOBS: self._list_jobs(),
|
||||
}
|
||||
|
||||
@api_process
|
||||
|
@ -1,20 +1,25 @@
|
||||
"""Test Docker API."""
|
||||
import pytest
|
||||
|
||||
import asyncio
|
||||
from unittest.mock import ANY
|
||||
|
||||
from aiohttp.test_utils import TestClient
|
||||
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.jobs.const import ATTR_IGNORE_CONDITIONS, JobCondition
|
||||
from supervisor.jobs.decorator import Job
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_jobs_info(api_client):
|
||||
async def test_api_jobs_info(api_client: TestClient):
|
||||
"""Test jobs info api."""
|
||||
resp = await api_client.get("/jobs/info")
|
||||
result = await resp.json()
|
||||
|
||||
assert result["data"][ATTR_IGNORE_CONDITIONS] == []
|
||||
assert result["data"]["jobs"] == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_jobs_options(api_client, coresys):
|
||||
async def test_api_jobs_options(api_client: TestClient, coresys: CoreSys):
|
||||
"""Test jobs options api."""
|
||||
resp = await api_client.post(
|
||||
"/jobs/options", json={ATTR_IGNORE_CONDITIONS: [JobCondition.HEALTHY]}
|
||||
@ -29,8 +34,7 @@ async def test_api_jobs_options(api_client, coresys):
|
||||
assert coresys.jobs.save_data.called
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_jobs_reset(api_client, coresys):
|
||||
async def test_api_jobs_reset(api_client: TestClient, coresys: CoreSys):
|
||||
"""Test jobs reset api."""
|
||||
resp = await api_client.post(
|
||||
"/jobs/options", json={ATTR_IGNORE_CONDITIONS: [JobCondition.HEALTHY]}
|
||||
@ -52,3 +56,93 @@ async def test_api_jobs_reset(api_client, coresys):
|
||||
|
||||
assert coresys.jobs.ignore_conditions == []
|
||||
coresys.jobs.save_data.assert_called_once()
|
||||
|
||||
|
||||
async def test_jobs_tree_representation(api_client: TestClient, coresys: CoreSys):
|
||||
"""Test jobs are correctly represented in a tree."""
|
||||
|
||||
class TestClass:
|
||||
"""Test class."""
|
||||
|
||||
def __init__(self, coresys: CoreSys):
|
||||
"""Initialize the test class."""
|
||||
self.coresys = coresys
|
||||
self.event = asyncio.Event()
|
||||
|
||||
@Job(name="test_jobs_tree_outer")
|
||||
async def test_jobs_tree_outer(self):
|
||||
"""Outer test method."""
|
||||
coresys.jobs.current.progress = 50
|
||||
await self.test_jobs_tree_inner()
|
||||
|
||||
@Job(name="test_jobs_tree_inner")
|
||||
async def test_jobs_tree_inner(self):
|
||||
"""Inner test method."""
|
||||
await self.event.wait()
|
||||
|
||||
@Job(name="test_jobs_tree_alt", cleanup=False)
|
||||
async def test_jobs_tree_alt(self):
|
||||
"""Alternate test method."""
|
||||
coresys.jobs.current.stage = "init"
|
||||
await self.test_jobs_tree_internal()
|
||||
coresys.jobs.current.stage = "end"
|
||||
|
||||
@Job(name="test_jobs_tree_internal", internal=True)
|
||||
async def test_jobs_tree_internal(self):
|
||||
"""Internal test method."""
|
||||
await self.event.wait()
|
||||
|
||||
test = TestClass(coresys)
|
||||
asyncio.create_task(test.test_jobs_tree_outer())
|
||||
asyncio.create_task(test.test_jobs_tree_alt())
|
||||
await asyncio.sleep(0)
|
||||
|
||||
resp = await api_client.get("/jobs/info")
|
||||
result = await resp.json()
|
||||
assert result["data"]["jobs"] == [
|
||||
{
|
||||
"name": "test_jobs_tree_outer",
|
||||
"reference": None,
|
||||
"uuid": ANY,
|
||||
"progress": 50,
|
||||
"stage": None,
|
||||
"done": False,
|
||||
"child_jobs": [
|
||||
{
|
||||
"name": "test_jobs_tree_inner",
|
||||
"reference": None,
|
||||
"uuid": ANY,
|
||||
"progress": 0,
|
||||
"stage": None,
|
||||
"done": False,
|
||||
"child_jobs": [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "test_jobs_tree_alt",
|
||||
"reference": None,
|
||||
"uuid": ANY,
|
||||
"progress": 0,
|
||||
"stage": "init",
|
||||
"done": False,
|
||||
"child_jobs": [],
|
||||
},
|
||||
]
|
||||
|
||||
test.event.set()
|
||||
await asyncio.sleep(0)
|
||||
|
||||
resp = await api_client.get("/jobs/info")
|
||||
result = await resp.json()
|
||||
assert result["data"]["jobs"] == [
|
||||
{
|
||||
"name": "test_jobs_tree_alt",
|
||||
"reference": None,
|
||||
"uuid": ANY,
|
||||
"progress": 0,
|
||||
"stage": "end",
|
||||
"done": True,
|
||||
"child_jobs": [],
|
||||
},
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user