From 6ca837b4e1346a6c49971179a1fb1f1654c5e971 Mon Sep 17 00:00:00 2001 From: Mike Degatano Date: Thu, 14 Mar 2024 05:55:04 -0400 Subject: [PATCH] Supervisor issues update retries on failure (#113373) --- homeassistant/components/hassio/issues.py | 12 +++- tests/components/hassio/test_issues.py | 78 ++++++++++++++++++++++- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hassio/issues.py b/homeassistant/components/hassio/issues.py index 8bd47faef080f..925c2d70afb82 100644 --- a/homeassistant/components/hassio/issues.py +++ b/homeassistant/components/hassio/issues.py @@ -3,11 +3,13 @@ import asyncio from dataclasses import dataclass, field +from datetime import datetime import logging from typing import Any, NotRequired, TypedDict -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HassJob, HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.event import async_call_later from homeassistant.helpers.issue_registry import ( IssueSeverity, async_create_issue, @@ -35,6 +37,7 @@ EVENT_SUPPORTED_CHANGED, ISSUE_KEY_SYSTEM_DOCKER_CONFIG, PLACEHOLDER_KEY_REFERENCE, + REQUEST_REFRESH_DELAY, UPDATE_KEY_SUPERVISOR, SupervisorIssueContext, ) @@ -302,12 +305,17 @@ async def setup(self) -> None: self._hass, EVENT_SUPERVISOR_EVENT, self._supervisor_events_to_issues ) - async def update(self) -> None: + async def update(self, _: datetime | None = None) -> None: """Update issues from Supervisor resolution center.""" try: data = await self._client.get_resolution_info() except HassioAPIError as err: _LOGGER.error("Failed to update supervisor issues: %r", err) + async_call_later( + self._hass, + REQUEST_REFRESH_DELAY, + HassJob(self.update, cancel_on_shutdown=True), + ) return self.unhealthy_reasons = set(data[ATTR_UNHEALTHY]) self.unsupported_reasons = set(data[ATTR_UNSUPPORTED]) diff --git a/tests/components/hassio/test_issues.py b/tests/components/hassio/test_issues.py index 83cb65b8ccae0..b5a852710fe38 100644 --- a/tests/components/hassio/test_issues.py +++ b/tests/components/hassio/test_issues.py @@ -1,6 +1,8 @@ """Test issues from supervisor issues.""" from __future__ import annotations +import asyncio +from http import HTTPStatus import os from typing import Any from unittest.mock import ANY, patch @@ -13,7 +15,7 @@ from .test_init import MOCK_ENVIRON -from tests.test_util.aiohttp import AiohttpClientMocker +from tests.test_util.aiohttp import AiohttpClientMocker, AiohttpClientMockResponse from tests.typing import WebSocketGenerator @@ -529,6 +531,80 @@ async def test_supervisor_issues( ) +async def test_supervisor_issues_initial_failure( + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, + hass_ws_client: WebSocketGenerator, +) -> None: + """Test issues manager retries after initial update failure.""" + responses = [ + AiohttpClientMockResponse( + method="get", + url="http://127.0.0.1/resolution/info", + status=HTTPStatus.BAD_REQUEST, + json={ + "result": "error", + "message": "System is not ready with state: setup", + }, + ), + AiohttpClientMockResponse( + method="get", + url="http://127.0.0.1/resolution/info", + status=HTTPStatus.OK, + json={ + "result": "ok", + "data": { + "unsupported": [], + "unhealthy": [], + "suggestions": [], + "issues": [ + { + "uuid": "1234", + "type": "reboot_required", + "context": "system", + "reference": None, + }, + ], + "checks": [ + {"enabled": True, "slug": "supervisor_trust"}, + {"enabled": True, "slug": "free_space"}, + ], + }, + }, + ), + ] + + async def mock_responses(*args): + nonlocal responses + return responses.pop(0) + + aioclient_mock.get( + "http://127.0.0.1/resolution/info", + side_effect=mock_responses, + ) + aioclient_mock.get( + "http://127.0.0.1/resolution/issue/1234/suggestions", + json={"result": "ok", "data": {"suggestions": []}}, + ) + + with patch("homeassistant.components.hassio.issues.REQUEST_REFRESH_DELAY", new=0.1): + result = await async_setup_component(hass, "hassio", {}) + assert result + + client = await hass_ws_client(hass) + + await client.send_json({"id": 1, "type": "repairs/list_issues"}) + msg = await client.receive_json() + assert msg["success"] + assert len(msg["result"]["issues"]) == 0 + + await asyncio.sleep(0.1) + await client.send_json({"id": 2, "type": "repairs/list_issues"}) + msg = await client.receive_json() + assert msg["success"] + assert len(msg["result"]["issues"]) == 1 + + async def test_supervisor_issues_add_remove( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker,