mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 22:27:07 +00:00
Add docker config repair for supervisor issue (#93820)
This commit is contained in:
parent
05c3d8bb37
commit
c25b26214b
@ -299,7 +299,7 @@ def get_supervisor_info(hass: HomeAssistant) -> dict[str, Any] | None:
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
@bind_hass
|
@bind_hass
|
||||||
def get_addons_info(hass):
|
def get_addons_info(hass: HomeAssistant) -> dict[str, dict[str, Any]] | None:
|
||||||
"""Return Addons info.
|
"""Return Addons info.
|
||||||
|
|
||||||
Async friendly.
|
Async friendly.
|
||||||
@ -367,6 +367,16 @@ def get_core_info(hass: HomeAssistant) -> dict[str, Any] | None:
|
|||||||
return hass.data.get(DATA_CORE_INFO)
|
return hass.data.get(DATA_CORE_INFO)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
@bind_hass
|
||||||
|
def get_issues_info(hass: HomeAssistant) -> SupervisorIssues | None:
|
||||||
|
"""Return Supervisor issues info.
|
||||||
|
|
||||||
|
Async friendly.
|
||||||
|
"""
|
||||||
|
return hass.data.get(DATA_KEY_SUPERVISOR_ISSUES)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@bind_hass
|
@bind_hass
|
||||||
def is_hassio(hass: HomeAssistant) -> bool:
|
def is_hassio(hass: HomeAssistant) -> bool:
|
||||||
@ -778,7 +788,7 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
|
|
||||||
new_data: dict[str, Any] = {}
|
new_data: dict[str, Any] = {}
|
||||||
supervisor_info = get_supervisor_info(self.hass) or {}
|
supervisor_info = get_supervisor_info(self.hass) or {}
|
||||||
addons_info = get_addons_info(self.hass)
|
addons_info = get_addons_info(self.hass) or {}
|
||||||
addons_stats = get_addons_stats(self.hass)
|
addons_stats = get_addons_stats(self.hass)
|
||||||
addons_changelogs = get_addons_changelogs(self.hass)
|
addons_changelogs = get_addons_changelogs(self.hass)
|
||||||
store_data = get_store(self.hass) or {}
|
store_data = get_store(self.hass) or {}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
"""Hass.io const variables."""
|
"""Hass.io const variables."""
|
||||||
from enum import Enum
|
from homeassistant.backports.enum import StrEnum
|
||||||
|
|
||||||
DOMAIN = "hassio"
|
DOMAIN = "hassio"
|
||||||
|
|
||||||
@ -77,9 +77,12 @@ DATA_KEY_HOST = "host"
|
|||||||
DATA_KEY_SUPERVISOR_ISSUES = "supervisor_issues"
|
DATA_KEY_SUPERVISOR_ISSUES = "supervisor_issues"
|
||||||
|
|
||||||
PLACEHOLDER_KEY_REFERENCE = "reference"
|
PLACEHOLDER_KEY_REFERENCE = "reference"
|
||||||
|
PLACEHOLDER_KEY_COMPONENTS = "components"
|
||||||
|
|
||||||
|
ISSUE_KEY_SYSTEM_DOCKER_CONFIG = "issue_system_docker_config"
|
||||||
|
|
||||||
|
|
||||||
class SupervisorEntityModel(str, Enum):
|
class SupervisorEntityModel(StrEnum):
|
||||||
"""Supervisor entity model."""
|
"""Supervisor entity model."""
|
||||||
|
|
||||||
ADDON = "Home Assistant Add-on"
|
ADDON = "Home Assistant Add-on"
|
||||||
@ -87,3 +90,17 @@ class SupervisorEntityModel(str, Enum):
|
|||||||
CORE = "Home Assistant Core"
|
CORE = "Home Assistant Core"
|
||||||
SUPERVIOSR = "Home Assistant Supervisor"
|
SUPERVIOSR = "Home Assistant Supervisor"
|
||||||
HOST = "Home Assistant Host"
|
HOST = "Home Assistant Host"
|
||||||
|
|
||||||
|
|
||||||
|
class SupervisorIssueContext(StrEnum):
|
||||||
|
"""Context for supervisor issues."""
|
||||||
|
|
||||||
|
ADDON = "addon"
|
||||||
|
CORE = "core"
|
||||||
|
DNS_SERVER = "dns_server"
|
||||||
|
MOUNT = "mount"
|
||||||
|
OS = "os"
|
||||||
|
PLUGIN = "plugin"
|
||||||
|
SUPERVISOR = "supervisor"
|
||||||
|
STORE = "store"
|
||||||
|
SYSTEM = "system"
|
||||||
|
@ -35,8 +35,10 @@ from .const import (
|
|||||||
EVENT_SUPERVISOR_EVENT,
|
EVENT_SUPERVISOR_EVENT,
|
||||||
EVENT_SUPERVISOR_UPDATE,
|
EVENT_SUPERVISOR_UPDATE,
|
||||||
EVENT_SUPPORTED_CHANGED,
|
EVENT_SUPPORTED_CHANGED,
|
||||||
|
ISSUE_KEY_SYSTEM_DOCKER_CONFIG,
|
||||||
PLACEHOLDER_KEY_REFERENCE,
|
PLACEHOLDER_KEY_REFERENCE,
|
||||||
UPDATE_KEY_SUPERVISOR,
|
UPDATE_KEY_SUPERVISOR,
|
||||||
|
SupervisorIssueContext,
|
||||||
)
|
)
|
||||||
from .handler import HassIO, HassioAPIError
|
from .handler import HassIO, HassioAPIError
|
||||||
|
|
||||||
@ -88,6 +90,7 @@ ISSUE_KEYS_FOR_REPAIRS = {
|
|||||||
"issue_mount_mount_failed",
|
"issue_mount_mount_failed",
|
||||||
"issue_system_multiple_data_disks",
|
"issue_system_multiple_data_disks",
|
||||||
"issue_system_reboot_required",
|
"issue_system_reboot_required",
|
||||||
|
ISSUE_KEY_SYSTEM_DOCKER_CONFIG,
|
||||||
}
|
}
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -107,22 +110,22 @@ class Suggestion:
|
|||||||
"""Suggestion from Supervisor which resolves an issue."""
|
"""Suggestion from Supervisor which resolves an issue."""
|
||||||
|
|
||||||
uuid: str
|
uuid: str
|
||||||
type_: str
|
type: str
|
||||||
context: str
|
context: SupervisorIssueContext
|
||||||
reference: str | None = None
|
reference: str | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key(self) -> str:
|
def key(self) -> str:
|
||||||
"""Get key for suggestion (combination of context and type)."""
|
"""Get key for suggestion (combination of context and type)."""
|
||||||
return f"{self.context}_{self.type_}"
|
return f"{self.context}_{self.type}"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: SuggestionDataType) -> Suggestion:
|
def from_dict(cls, data: SuggestionDataType) -> Suggestion:
|
||||||
"""Convert from dictionary representation."""
|
"""Convert from dictionary representation."""
|
||||||
return cls(
|
return cls(
|
||||||
uuid=data["uuid"],
|
uuid=data["uuid"],
|
||||||
type_=data["type"],
|
type=data["type"],
|
||||||
context=data["context"],
|
context=SupervisorIssueContext(data["context"]),
|
||||||
reference=data["reference"],
|
reference=data["reference"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -142,15 +145,15 @@ class Issue:
|
|||||||
"""Issue from Supervisor."""
|
"""Issue from Supervisor."""
|
||||||
|
|
||||||
uuid: str
|
uuid: str
|
||||||
type_: str
|
type: str
|
||||||
context: str
|
context: SupervisorIssueContext
|
||||||
reference: str | None = None
|
reference: str | None = None
|
||||||
suggestions: list[Suggestion] = field(default_factory=list, compare=False)
|
suggestions: list[Suggestion] = field(default_factory=list, compare=False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key(self) -> str:
|
def key(self) -> str:
|
||||||
"""Get key for issue (combination of context and type)."""
|
"""Get key for issue (combination of context and type)."""
|
||||||
return f"issue_{self.context}_{self.type_}"
|
return f"issue_{self.context}_{self.type}"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: IssueDataType) -> Issue:
|
def from_dict(cls, data: IssueDataType) -> Issue:
|
||||||
@ -158,8 +161,8 @@ class Issue:
|
|||||||
suggestions: list[SuggestionDataType] = data.get("suggestions", [])
|
suggestions: list[SuggestionDataType] = data.get("suggestions", [])
|
||||||
return cls(
|
return cls(
|
||||||
uuid=data["uuid"],
|
uuid=data["uuid"],
|
||||||
type_=data["type"],
|
type=data["type"],
|
||||||
context=data["context"],
|
context=SupervisorIssueContext(data["context"]),
|
||||||
reference=data["reference"],
|
reference=data["reference"],
|
||||||
suggestions=[
|
suggestions=[
|
||||||
Suggestion.from_dict(suggestion) for suggestion in suggestions
|
Suggestion.from_dict(suggestion) for suggestion in suggestions
|
||||||
@ -242,6 +245,11 @@ class SupervisorIssues:
|
|||||||
|
|
||||||
self._unsupported_reasons = reasons
|
self._unsupported_reasons = reasons
|
||||||
|
|
||||||
|
@property
|
||||||
|
def issues(self) -> set[Issue]:
|
||||||
|
"""Get issues."""
|
||||||
|
return set(self._issues.values())
|
||||||
|
|
||||||
def add_issue(self, issue: Issue) -> None:
|
def add_issue(self, issue: Issue) -> None:
|
||||||
"""Add or update an issue in the list. Create or update a repair if necessary."""
|
"""Add or update an issue in the list. Create or update a repair if necessary."""
|
||||||
if issue.key in ISSUE_KEYS_FOR_REPAIRS:
|
if issue.key in ISSUE_KEYS_FOR_REPAIRS:
|
||||||
@ -263,20 +271,10 @@ class SupervisorIssues:
|
|||||||
async def add_issue_from_data(self, data: IssueDataType) -> None:
|
async def add_issue_from_data(self, data: IssueDataType) -> None:
|
||||||
"""Add issue from data to list after getting latest suggestions."""
|
"""Add issue from data to list after getting latest suggestions."""
|
||||||
try:
|
try:
|
||||||
suggestions = (await self._client.get_suggestions_for_issue(data["uuid"]))[
|
data["suggestions"] = (
|
||||||
ATTR_SUGGESTIONS
|
await self._client.get_suggestions_for_issue(data["uuid"])
|
||||||
]
|
)[ATTR_SUGGESTIONS]
|
||||||
self.add_issue(
|
self.add_issue(Issue.from_dict(data))
|
||||||
Issue(
|
|
||||||
uuid=data["uuid"],
|
|
||||||
type_=data["type"],
|
|
||||||
context=data["context"],
|
|
||||||
reference=data["reference"],
|
|
||||||
suggestions=[
|
|
||||||
Suggestion.from_dict(suggestion) for suggestion in suggestions
|
|
||||||
],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except HassioAPIError:
|
except HassioAPIError:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Could not get suggestions for supervisor issue %s, skipping it",
|
"Could not get suggestions for supervisor issue %s, skipping it",
|
||||||
|
@ -10,9 +10,15 @@ from homeassistant.components.repairs import RepairsFlow
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
|
||||||
from .const import DATA_KEY_SUPERVISOR_ISSUES, PLACEHOLDER_KEY_REFERENCE
|
from . import get_addons_info, get_issues_info
|
||||||
|
from .const import (
|
||||||
|
ISSUE_KEY_SYSTEM_DOCKER_CONFIG,
|
||||||
|
PLACEHOLDER_KEY_COMPONENTS,
|
||||||
|
PLACEHOLDER_KEY_REFERENCE,
|
||||||
|
SupervisorIssueContext,
|
||||||
|
)
|
||||||
from .handler import HassioAPIError, async_apply_suggestion
|
from .handler import HassioAPIError, async_apply_suggestion
|
||||||
from .issues import Issue, Suggestion, SupervisorIssues
|
from .issues import Issue, Suggestion
|
||||||
|
|
||||||
SUGGESTION_CONFIRMATION_REQUIRED = {"system_execute_reboot"}
|
SUGGESTION_CONFIRMATION_REQUIRED = {"system_execute_reboot"}
|
||||||
|
|
||||||
@ -37,10 +43,8 @@ class SupervisorIssueRepairFlow(RepairsFlow):
|
|||||||
@property
|
@property
|
||||||
def issue(self) -> Issue | None:
|
def issue(self) -> Issue | None:
|
||||||
"""Get associated issue."""
|
"""Get associated issue."""
|
||||||
if not self._issue:
|
supervisor_issues = get_issues_info(self.hass)
|
||||||
supervisor_issues: SupervisorIssues = self.hass.data[
|
if not self._issue and supervisor_issues:
|
||||||
DATA_KEY_SUPERVISOR_ISSUES
|
|
||||||
]
|
|
||||||
self._issue = supervisor_issues.get_issue(self._issue_id)
|
self._issue = supervisor_issues.get_issue(self._issue_id)
|
||||||
|
|
||||||
return self._issue
|
return self._issue
|
||||||
@ -121,10 +125,49 @@ class SupervisorIssueRepairFlow(RepairsFlow):
|
|||||||
return _async_step
|
return _async_step
|
||||||
|
|
||||||
|
|
||||||
|
class DockerConfigIssueRepairFlow(SupervisorIssueRepairFlow):
|
||||||
|
"""Handler for docker config issue fixing flow."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description_placeholders(self) -> dict[str, str] | None:
|
||||||
|
"""Get description placeholders for steps."""
|
||||||
|
placeholders = {PLACEHOLDER_KEY_COMPONENTS: ""}
|
||||||
|
supervisor_issues = get_issues_info(self.hass)
|
||||||
|
if supervisor_issues and self.issue:
|
||||||
|
addons = get_addons_info(self.hass) or {}
|
||||||
|
components: list[str] = []
|
||||||
|
for issue in supervisor_issues.issues:
|
||||||
|
if issue.key == self.issue.key or issue.type != self.issue.type:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if issue.context == SupervisorIssueContext.CORE:
|
||||||
|
components.insert(0, "Home Assistant")
|
||||||
|
elif issue.context == SupervisorIssueContext.ADDON:
|
||||||
|
components.append(
|
||||||
|
next(
|
||||||
|
(
|
||||||
|
info["name"]
|
||||||
|
for slug, info in addons.items()
|
||||||
|
if slug == issue.reference
|
||||||
|
),
|
||||||
|
issue.reference or "",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
placeholders[PLACEHOLDER_KEY_COMPONENTS] = "\n- ".join(components)
|
||||||
|
|
||||||
|
return placeholders
|
||||||
|
|
||||||
|
|
||||||
async def async_create_fix_flow(
|
async def async_create_fix_flow(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
issue_id: str,
|
issue_id: str,
|
||||||
data: dict[str, str | int | float | None] | None,
|
data: dict[str, str | int | float | None] | None,
|
||||||
) -> RepairsFlow:
|
) -> RepairsFlow:
|
||||||
"""Create flow."""
|
"""Create flow."""
|
||||||
|
supervisor_issues = get_issues_info(hass)
|
||||||
|
issue = supervisor_issues and supervisor_issues.get_issue(issue_id)
|
||||||
|
if issue and issue.key == ISSUE_KEY_SYSTEM_DOCKER_CONFIG:
|
||||||
|
return DockerConfigIssueRepairFlow(issue_id)
|
||||||
|
|
||||||
return SupervisorIssueRepairFlow(issue_id)
|
return SupervisorIssueRepairFlow(issue_id)
|
||||||
|
@ -30,7 +30,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"apply_suggestion_fail": "Could not apply the fix. Check the supervisor logs for more details."
|
"apply_suggestion_fail": "Could not apply the fix. Check the Supervisor logs for more details."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"issue_system_docker_config": {
|
||||||
|
"title": "Restart(s) required",
|
||||||
|
"fix_flow": {
|
||||||
|
"step": {
|
||||||
|
"system_execute_rebuild": {
|
||||||
|
"description": "The default configuration for add-ons and Home Assistant has changed. To update the configuration with the new defaults, a restart is required for the following:\n\n- {components}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"apply_suggestion_fail": "One or more of the restarts failed. Check the Supervisor logs for more details."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -43,7 +56,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"apply_suggestion_fail": "Could not rename the filesystem. Check the supervisor logs for more details."
|
"apply_suggestion_fail": "Could not rename the filesystem. Check the Supervisor logs for more details."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -56,7 +69,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"apply_suggestion_fail": "Could not reboot the system. Check the supervisor logs for more details."
|
"apply_suggestion_fail": "Could not reboot the system. Check the Supervisor logs for more details."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -98,6 +98,10 @@ def all_setup_requests(
|
|||||||
aioclient_mock: AiohttpClientMocker, request: pytest.FixtureRequest
|
aioclient_mock: AiohttpClientMocker, request: pytest.FixtureRequest
|
||||||
):
|
):
|
||||||
"""Mock all setup requests."""
|
"""Mock all setup requests."""
|
||||||
|
include_addons = hasattr(request, "param") and request.param.get(
|
||||||
|
"include_addons", False
|
||||||
|
)
|
||||||
|
|
||||||
aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"})
|
aioclient_mock.post("http://127.0.0.1/homeassistant/options", json={"result": "ok"})
|
||||||
aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"})
|
aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={"result": "ok"})
|
||||||
aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"})
|
aioclient_mock.post("http://127.0.0.1/supervisor/options", json={"result": "ok"})
|
||||||
@ -157,7 +161,30 @@ def all_setup_requests(
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"version_latest": "1.0.0",
|
"version_latest": "1.0.0",
|
||||||
"auto_update": True,
|
"auto_update": True,
|
||||||
"addons": [],
|
"addons": [
|
||||||
|
{
|
||||||
|
"name": "test",
|
||||||
|
"slug": "test",
|
||||||
|
"update_available": False,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"version_latest": "1.0.0",
|
||||||
|
"repository": "core",
|
||||||
|
"state": "started",
|
||||||
|
"icon": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "test2",
|
||||||
|
"slug": "test2",
|
||||||
|
"update_available": False,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"version_latest": "1.0.0",
|
||||||
|
"repository": "core",
|
||||||
|
"state": "started",
|
||||||
|
"icon": False,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
if include_addons
|
||||||
|
else [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -165,3 +192,106 @@ def all_setup_requests(
|
|||||||
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
|
"http://127.0.0.1/ingress/panels", json={"result": "ok", "data": {"panels": {}}}
|
||||||
)
|
)
|
||||||
aioclient_mock.post("http://127.0.0.1/refresh_updates", json={"result": "ok"})
|
aioclient_mock.post("http://127.0.0.1/refresh_updates", json={"result": "ok"})
|
||||||
|
|
||||||
|
aioclient_mock.get("http://127.0.0.1/addons/test/changelog", text="")
|
||||||
|
aioclient_mock.get(
|
||||||
|
"http://127.0.0.1/addons/test/info",
|
||||||
|
json={
|
||||||
|
"result": "ok",
|
||||||
|
"data": {
|
||||||
|
"name": "test",
|
||||||
|
"slug": "test",
|
||||||
|
"update_available": False,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"version_latest": "1.0.0",
|
||||||
|
"repository": "core",
|
||||||
|
"state": "started",
|
||||||
|
"icon": False,
|
||||||
|
"url": "https://github.com/home-assistant/addons/test",
|
||||||
|
"auto_update": True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
aioclient_mock.get("http://127.0.0.1/addons/test2/changelog", text="")
|
||||||
|
aioclient_mock.get(
|
||||||
|
"http://127.0.0.1/addons/test2/info",
|
||||||
|
json={
|
||||||
|
"result": "ok",
|
||||||
|
"data": {
|
||||||
|
"name": "test2",
|
||||||
|
"slug": "test2",
|
||||||
|
"update_available": False,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"version_latest": "1.0.0",
|
||||||
|
"repository": "core",
|
||||||
|
"state": "started",
|
||||||
|
"icon": False,
|
||||||
|
"url": "https://github.com",
|
||||||
|
"auto_update": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
aioclient_mock.get(
|
||||||
|
"http://127.0.0.1/core/stats",
|
||||||
|
json={
|
||||||
|
"result": "ok",
|
||||||
|
"data": {
|
||||||
|
"cpu_percent": 0.99,
|
||||||
|
"memory_usage": 182611968,
|
||||||
|
"memory_limit": 3977146368,
|
||||||
|
"memory_percent": 4.59,
|
||||||
|
"network_rx": 362570232,
|
||||||
|
"network_tx": 82374138,
|
||||||
|
"blk_read": 46010945536,
|
||||||
|
"blk_write": 15051526144,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
aioclient_mock.get(
|
||||||
|
"http://127.0.0.1/supervisor/stats",
|
||||||
|
json={
|
||||||
|
"result": "ok",
|
||||||
|
"data": {
|
||||||
|
"cpu_percent": 0.99,
|
||||||
|
"memory_usage": 182611968,
|
||||||
|
"memory_limit": 3977146368,
|
||||||
|
"memory_percent": 4.59,
|
||||||
|
"network_rx": 362570232,
|
||||||
|
"network_tx": 82374138,
|
||||||
|
"blk_read": 46010945536,
|
||||||
|
"blk_write": 15051526144,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
aioclient_mock.get(
|
||||||
|
"http://127.0.0.1/addons/test/stats",
|
||||||
|
json={
|
||||||
|
"result": "ok",
|
||||||
|
"data": {
|
||||||
|
"cpu_percent": 0.99,
|
||||||
|
"memory_usage": 182611968,
|
||||||
|
"memory_limit": 3977146368,
|
||||||
|
"memory_percent": 4.59,
|
||||||
|
"network_rx": 362570232,
|
||||||
|
"network_tx": 82374138,
|
||||||
|
"blk_read": 46010945536,
|
||||||
|
"blk_write": 15051526144,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
aioclient_mock.get(
|
||||||
|
"http://127.0.0.1/addons/test2/stats",
|
||||||
|
json={
|
||||||
|
"result": "ok",
|
||||||
|
"data": {
|
||||||
|
"cpu_percent": 0.8,
|
||||||
|
"memory_usage": 51941376,
|
||||||
|
"memory_limit": 3977146368,
|
||||||
|
"memory_percent": 1.31,
|
||||||
|
"network_rx": 31338284,
|
||||||
|
"network_tx": 15692900,
|
||||||
|
"blk_read": 740077568,
|
||||||
|
"blk_write": 6004736,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
@ -496,7 +496,7 @@ async def test_supervisor_issues(
|
|||||||
{
|
{
|
||||||
"uuid": "1237",
|
"uuid": "1237",
|
||||||
"type": "should_not_be_repair",
|
"type": "should_not_be_repair",
|
||||||
"context": "fake",
|
"context": "os",
|
||||||
"reference": None,
|
"reference": None,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -19,16 +19,11 @@ from tests.typing import ClientSessionGenerator
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
async def setup_repairs(hass):
|
async def setup_repairs(hass: HomeAssistant):
|
||||||
"""Set up the repairs integration."""
|
"""Set up the repairs integration."""
|
||||||
assert await async_setup_component(hass, REPAIRS_DOMAIN, {REPAIRS_DOMAIN: {}})
|
assert await async_setup_component(hass, REPAIRS_DOMAIN, {REPAIRS_DOMAIN: {}})
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
async def mock_all(all_setup_requests):
|
|
||||||
"""Mock all setup requests."""
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
async def fixture_supervisor_environ():
|
async def fixture_supervisor_environ():
|
||||||
"""Mock os environ for supervisor."""
|
"""Mock os environ for supervisor."""
|
||||||
@ -40,9 +35,10 @@ async def test_supervisor_issue_repair_flow(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
aioclient_mock: AiohttpClientMocker,
|
aioclient_mock: AiohttpClientMocker,
|
||||||
hass_client: ClientSessionGenerator,
|
hass_client: ClientSessionGenerator,
|
||||||
|
issue_registry: ir.IssueRegistry,
|
||||||
|
all_setup_requests,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test fix flow for supervisor issue."""
|
"""Test fix flow for supervisor issue."""
|
||||||
issue_registry: ir.IssueRegistry = ir.async_get(hass)
|
|
||||||
mock_resolution_info(
|
mock_resolution_info(
|
||||||
aioclient_mock,
|
aioclient_mock,
|
||||||
issues=[
|
issues=[
|
||||||
@ -63,8 +59,7 @@ async def test_supervisor_issue_repair_flow(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
result = await async_setup_component(hass, "hassio", {})
|
assert await async_setup_component(hass, "hassio", {})
|
||||||
assert result
|
|
||||||
|
|
||||||
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||||
assert repair_issue
|
assert repair_issue
|
||||||
@ -119,9 +114,10 @@ async def test_supervisor_issue_repair_flow_with_multiple_suggestions(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
aioclient_mock: AiohttpClientMocker,
|
aioclient_mock: AiohttpClientMocker,
|
||||||
hass_client: ClientSessionGenerator,
|
hass_client: ClientSessionGenerator,
|
||||||
|
issue_registry: ir.IssueRegistry,
|
||||||
|
all_setup_requests,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test fix flow for supervisor issue with multiple suggestions."""
|
"""Test fix flow for supervisor issue with multiple suggestions."""
|
||||||
issue_registry: ir.IssueRegistry = ir.async_get(hass)
|
|
||||||
mock_resolution_info(
|
mock_resolution_info(
|
||||||
aioclient_mock,
|
aioclient_mock,
|
||||||
issues=[
|
issues=[
|
||||||
@ -148,8 +144,7 @@ async def test_supervisor_issue_repair_flow_with_multiple_suggestions(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
result = await async_setup_component(hass, "hassio", {})
|
assert await async_setup_component(hass, "hassio", {})
|
||||||
assert result
|
|
||||||
|
|
||||||
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||||
assert repair_issue
|
assert repair_issue
|
||||||
@ -214,9 +209,10 @@ async def test_supervisor_issue_repair_flow_with_multiple_suggestions_and_confir
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
aioclient_mock: AiohttpClientMocker,
|
aioclient_mock: AiohttpClientMocker,
|
||||||
hass_client: ClientSessionGenerator,
|
hass_client: ClientSessionGenerator,
|
||||||
|
issue_registry: ir.IssueRegistry,
|
||||||
|
all_setup_requests,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test fix flow for supervisor issue with multiple suggestions and choice requires confirmation."""
|
"""Test fix flow for supervisor issue with multiple suggestions and choice requires confirmation."""
|
||||||
issue_registry: ir.IssueRegistry = ir.async_get(hass)
|
|
||||||
mock_resolution_info(
|
mock_resolution_info(
|
||||||
aioclient_mock,
|
aioclient_mock,
|
||||||
issues=[
|
issues=[
|
||||||
@ -243,8 +239,7 @@ async def test_supervisor_issue_repair_flow_with_multiple_suggestions_and_confir
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
result = await async_setup_component(hass, "hassio", {})
|
assert await async_setup_component(hass, "hassio", {})
|
||||||
assert result
|
|
||||||
|
|
||||||
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||||
assert repair_issue
|
assert repair_issue
|
||||||
@ -327,9 +322,10 @@ async def test_supervisor_issue_repair_flow_skip_confirmation(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
aioclient_mock: AiohttpClientMocker,
|
aioclient_mock: AiohttpClientMocker,
|
||||||
hass_client: ClientSessionGenerator,
|
hass_client: ClientSessionGenerator,
|
||||||
|
issue_registry: ir.IssueRegistry,
|
||||||
|
all_setup_requests,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test confirmation skipped for fix flow for supervisor issue with one suggestion."""
|
"""Test confirmation skipped for fix flow for supervisor issue with one suggestion."""
|
||||||
issue_registry: ir.IssueRegistry = ir.async_get(hass)
|
|
||||||
mock_resolution_info(
|
mock_resolution_info(
|
||||||
aioclient_mock,
|
aioclient_mock,
|
||||||
issues=[
|
issues=[
|
||||||
@ -350,8 +346,7 @@ async def test_supervisor_issue_repair_flow_skip_confirmation(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
result = await async_setup_component(hass, "hassio", {})
|
assert await async_setup_component(hass, "hassio", {})
|
||||||
assert result
|
|
||||||
|
|
||||||
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||||
assert repair_issue
|
assert repair_issue
|
||||||
@ -406,9 +401,10 @@ async def test_mount_failed_repair_flow(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
aioclient_mock: AiohttpClientMocker,
|
aioclient_mock: AiohttpClientMocker,
|
||||||
hass_client: ClientSessionGenerator,
|
hass_client: ClientSessionGenerator,
|
||||||
|
issue_registry: ir.IssueRegistry,
|
||||||
|
all_setup_requests,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test repair flow for mount_failed issue."""
|
"""Test repair flow for mount_failed issue."""
|
||||||
issue_registry: ir.IssueRegistry = ir.async_get(hass)
|
|
||||||
mock_resolution_info(
|
mock_resolution_info(
|
||||||
aioclient_mock,
|
aioclient_mock,
|
||||||
issues=[
|
issues=[
|
||||||
@ -435,8 +431,7 @@ async def test_mount_failed_repair_flow(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
result = await async_setup_component(hass, "hassio", {})
|
assert await async_setup_component(hass, "hassio", {})
|
||||||
assert result
|
|
||||||
|
|
||||||
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||||
assert repair_issue
|
assert repair_issue
|
||||||
@ -499,3 +494,113 @@ async def test_mount_failed_repair_flow(
|
|||||||
str(aioclient_mock.mock_calls[-1][1])
|
str(aioclient_mock.mock_calls[-1][1])
|
||||||
== "http://127.0.0.1/resolution/suggestion/1235"
|
== "http://127.0.0.1/resolution/suggestion/1235"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"all_setup_requests", [{"include_addons": True}], indirect=True
|
||||||
|
)
|
||||||
|
async def test_supervisor_issue_docker_config_repair_flow(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
hass_client: ClientSessionGenerator,
|
||||||
|
issue_registry: ir.IssueRegistry,
|
||||||
|
all_setup_requests,
|
||||||
|
) -> None:
|
||||||
|
"""Test fix flow for supervisor issue."""
|
||||||
|
mock_resolution_info(
|
||||||
|
aioclient_mock,
|
||||||
|
issues=[
|
||||||
|
{
|
||||||
|
"uuid": "1234",
|
||||||
|
"type": "docker_config",
|
||||||
|
"context": "system",
|
||||||
|
"reference": None,
|
||||||
|
"suggestions": [
|
||||||
|
{
|
||||||
|
"uuid": "1235",
|
||||||
|
"type": "execute_rebuild",
|
||||||
|
"context": "system",
|
||||||
|
"reference": None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "1236",
|
||||||
|
"type": "docker_config",
|
||||||
|
"context": "core",
|
||||||
|
"reference": None,
|
||||||
|
"suggestions": [
|
||||||
|
{
|
||||||
|
"uuid": "1237",
|
||||||
|
"type": "execute_rebuild",
|
||||||
|
"context": "core",
|
||||||
|
"reference": None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "1238",
|
||||||
|
"type": "docker_config",
|
||||||
|
"context": "addon",
|
||||||
|
"reference": "test",
|
||||||
|
"suggestions": [
|
||||||
|
{
|
||||||
|
"uuid": "1239",
|
||||||
|
"type": "execute_rebuild",
|
||||||
|
"context": "addon",
|
||||||
|
"reference": "test",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert await async_setup_component(hass, "hassio", {})
|
||||||
|
|
||||||
|
repair_issue = issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||||
|
assert repair_issue
|
||||||
|
|
||||||
|
client = await hass_client()
|
||||||
|
|
||||||
|
resp = await client.post(
|
||||||
|
"/api/repairs/issues/fix",
|
||||||
|
json={"handler": "hassio", "issue_id": repair_issue.issue_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert resp.status == HTTPStatus.OK
|
||||||
|
data = await resp.json()
|
||||||
|
|
||||||
|
flow_id = data["flow_id"]
|
||||||
|
assert data == {
|
||||||
|
"type": "form",
|
||||||
|
"flow_id": flow_id,
|
||||||
|
"handler": "hassio",
|
||||||
|
"step_id": "system_execute_rebuild",
|
||||||
|
"data_schema": [],
|
||||||
|
"errors": None,
|
||||||
|
"description_placeholders": {"components": "Home Assistant\n- test"},
|
||||||
|
"last_step": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
|
||||||
|
|
||||||
|
assert resp.status == HTTPStatus.OK
|
||||||
|
data = await resp.json()
|
||||||
|
|
||||||
|
flow_id = data["flow_id"]
|
||||||
|
assert data == {
|
||||||
|
"version": 1,
|
||||||
|
"type": "create_entry",
|
||||||
|
"flow_id": flow_id,
|
||||||
|
"handler": "hassio",
|
||||||
|
"description": None,
|
||||||
|
"description_placeholders": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert not issue_registry.async_get_issue(domain="hassio", issue_id="1234")
|
||||||
|
|
||||||
|
assert aioclient_mock.mock_calls[-1][0] == "post"
|
||||||
|
assert (
|
||||||
|
str(aioclient_mock.mock_calls[-1][1])
|
||||||
|
== "http://127.0.0.1/resolution/suggestion/1235"
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user