From 6365a0fd84a472739069603bba88aee285b57896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Fri, 13 Mar 2026 15:42:34 +0100 Subject: [PATCH 1/3] Add issue and repair for NTP sync failure Notify user about NTP sync failures and create repair for issue/suggestion added in home-assistant/supervisor#6625. --- homeassistant/components/hassio/issues.py | 1 + homeassistant/components/hassio/strings.json | 13 ++ tests/components/hassio/test_issues.py | 55 +++++++ tests/components/hassio/test_repairs.py | 146 +++++++++++++++++++ 4 files changed, 215 insertions(+) diff --git a/homeassistant/components/hassio/issues.py b/homeassistant/components/hassio/issues.py index 25b4db9c861361..36f8ad9cb8b6d0 100644 --- a/homeassistant/components/hassio/issues.py +++ b/homeassistant/components/hassio/issues.py @@ -88,6 +88,7 @@ "issue_system_disk_lifetime", ISSUE_KEY_SYSTEM_FREE_SPACE, ISSUE_KEY_ADDON_PWNED, + "issue_system_ntp_sync_failed", } _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/hassio/strings.json b/homeassistant/components/hassio/strings.json index b9a4ec0fa2de3a..cf5e7249c5f9ac 100644 --- a/homeassistant/components/hassio/strings.json +++ b/homeassistant/components/hassio/strings.json @@ -164,6 +164,19 @@ }, "title": "Multiple data disks detected" }, + "issue_system_ntp_sync_failed": { + "fix_flow": { + "abort": { + "apply_suggestion_fail": "Could not re-enable NTP. Check the Supervisor logs for more details." + }, + "step": { + "system_enable_ntp": { + "description": "NTP time servers were unreachable and the system clock was found to be more than 1 hour off. The time has been corrected and the NTP service was temporarily disabled to allow this adjustment.\n\nCheck the **Host logs** to investigate why NTP servers could not be reached. Once resolved, select **Submit** to re-enable the NTP service." + } + } + }, + "title": "NTP sync failed - system time was corrected" + }, "issue_system_reboot_required": { "fix_flow": { "abort": { diff --git a/tests/components/hassio/test_issues.py b/tests/components/hassio/test_issues.py index 3bde6ef183eef1..cec83eea90e496 100644 --- a/tests/components/hassio/test_issues.py +++ b/tests/components/hassio/test_issues.py @@ -949,6 +949,61 @@ async def test_supervisor_issues_detached_addon_missing( ) +@pytest.mark.usefixtures("all_setup_requests") +async def test_supervisor_issues_ntp_sync_failed( + hass: HomeAssistant, + supervisor_client: AsyncMock, + hass_ws_client: WebSocketGenerator, +) -> None: + """Test supervisor issue for NTP sync failed.""" + mock_resolution_info(supervisor_client) + + result = await async_setup_component(hass, "hassio", {}) + assert result + + client = await hass_ws_client(hass) + + await client.send_json( + { + "id": 1, + "type": "supervisor/event", + "data": { + "event": "issue_changed", + "data": { + "uuid": (issue_uuid := uuid4().hex), + "type": "ntp_sync_failed", + "context": "system", + "reference": None, + "suggestions": [ + { + "uuid": uuid4().hex, + "type": "enable_ntp", + "context": "system", + "reference": None, + } + ], + }, + }, + } + ) + msg = await client.receive_json() + assert msg["success"] + await hass.async_block_till_done() + + await client.send_json({"id": 2, "type": "repairs/list_issues"}) + msg = await client.receive_json() + assert msg["success"] + assert len(msg["result"]["issues"]) == 1 + assert_issue_repair_in_list( + msg["result"]["issues"], + uuid=issue_uuid, + context="system", + type_="ntp_sync_failed", + fixable=True, + placeholders=None, + ) + + @pytest.mark.usefixtures("all_setup_requests") async def test_supervisor_issues_disk_lifetime( hass: HomeAssistant, diff --git a/tests/components/hassio/test_repairs.py b/tests/components/hassio/test_repairs.py index 25fddb949183f6..4adbc318343dbd 100644 --- a/tests/components/hassio/test_repairs.py +++ b/tests/components/hassio/test_repairs.py @@ -402,6 +402,152 @@ async def test_supervisor_issue_repair_flow_skip_confirmation( supervisor_client.resolution.apply_suggestion.assert_called_once_with(sugg_uuid) +@pytest.mark.usefixtures("all_setup_requests") +async def test_supervisor_issue_ntp_sync_failed_repair_flow( + hass: HomeAssistant, + supervisor_client: AsyncMock, + hass_client: ClientSessionGenerator, + issue_registry: ir.IssueRegistry, +) -> None: + """Test fix flow for NTP sync failed supervisor issue.""" + mock_resolution_info( + supervisor_client, + issues=[ + Issue( + type=IssueType.NTP_SYNC_FAILED, + context=ContextType.SYSTEM, + reference=None, + uuid=(issue_uuid := uuid4()), + ), + ], + suggestions_by_issue={ + issue_uuid: [ + Suggestion( + type=SuggestionType.ENABLE_NTP, + context=ContextType.SYSTEM, + reference=None, + uuid=(sugg_uuid := uuid4()), + auto=False, + ), + ] + }, + ) + + assert await async_setup_component(hass, "hassio", {}) + + repair_issue = issue_registry.async_get_issue( + domain="hassio", issue_id=issue_uuid.hex + ) + 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_enable_ntp", + "data_schema": [], + "errors": None, + "description_placeholders": None, + "last_step": True, + "preview": None, + } + + 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 == { + "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=issue_uuid.hex) + supervisor_client.resolution.apply_suggestion.assert_called_once_with(sugg_uuid) + + +@pytest.mark.usefixtures("all_setup_requests") +async def test_supervisor_issue_ntp_sync_failed_repair_flow_error( + hass: HomeAssistant, + supervisor_client: AsyncMock, + hass_client: ClientSessionGenerator, + issue_registry: ir.IssueRegistry, +) -> None: + """Test fix flow aborts when NTP re-enable fails.""" + mock_resolution_info( + supervisor_client, + issues=[ + Issue( + type=IssueType.NTP_SYNC_FAILED, + context=ContextType.SYSTEM, + reference=None, + uuid=(issue_uuid := uuid4()), + ), + ], + suggestions_by_issue={ + issue_uuid: [ + Suggestion( + type=SuggestionType.ENABLE_NTP, + context=ContextType.SYSTEM, + reference=None, + uuid=uuid4(), + auto=False, + ), + ] + }, + suggestion_result=SupervisorError("boom"), + ) + + assert await async_setup_component(hass, "hassio", {}) + + repair_issue = issue_registry.async_get_issue( + domain="hassio", issue_id=issue_uuid.hex + ) + 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"] + + 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 == { + "type": "abort", + "flow_id": flow_id, + "handler": "hassio", + "reason": "apply_suggestion_fail", + "description_placeholders": None, + } + + assert issue_registry.async_get_issue(domain="hassio", issue_id=issue_uuid.hex) + + @pytest.mark.usefixtures("all_setup_requests") async def test_mount_failed_repair_flow_error( hass: HomeAssistant, From 9845e880a4d4c2356aa7580da24de72bb63e570f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Wed, 18 Mar 2026 16:45:21 +0100 Subject: [PATCH 2/3] Elaborate issue description Co-authored-by: Stefan Agner --- homeassistant/components/hassio/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/hassio/strings.json b/homeassistant/components/hassio/strings.json index cf5e7249c5f9ac..a7dea3557e56ef 100644 --- a/homeassistant/components/hassio/strings.json +++ b/homeassistant/components/hassio/strings.json @@ -171,7 +171,7 @@ }, "step": { "system_enable_ntp": { - "description": "NTP time servers were unreachable and the system clock was found to be more than 1 hour off. The time has been corrected and the NTP service was temporarily disabled to allow this adjustment.\n\nCheck the **Host logs** to investigate why NTP servers could not be reached. Once resolved, select **Submit** to re-enable the NTP service." + "description": "The device could not contact its configured time servers (NTP). Using a secondary online time check, we detected that the system clock was more than 1 hour incorrect. The time has been corrected and the NTP service was temporarily disabled so the correction could be applied. To keep the system time accurate, we recommend fixing the issue preventing access to the NTP servers.\n\nCheck the **Host logs** to investigate why NTP servers could not be reached. Once resolved, select **Submit** to re-enable the NTP service." } } }, From 1a80de8e4a3500b864c2d5e529a390df37739854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Wed, 18 Mar 2026 16:48:03 +0100 Subject: [PATCH 3/3] Update issue title Co-authored-by: Stefan Agner --- homeassistant/components/hassio/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/hassio/strings.json b/homeassistant/components/hassio/strings.json index a7dea3557e56ef..9e93c053be59c4 100644 --- a/homeassistant/components/hassio/strings.json +++ b/homeassistant/components/hassio/strings.json @@ -175,7 +175,7 @@ } } }, - "title": "NTP sync failed - system time was corrected" + "title": "Time synchronization issue detected" }, "issue_system_reboot_required": { "fix_flow": {