Skip to content

Commit 351ac66

Browse files
committed
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.
1 parent 6962288 commit 351ac66

4 files changed

Lines changed: 219 additions & 0 deletions

File tree

homeassistant/components/hassio/issues.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
"issue_system_disk_lifetime",
8989
ISSUE_KEY_SYSTEM_FREE_SPACE,
9090
ISSUE_KEY_ADDON_PWNED,
91+
"issue_system_ntp_sync_failed",
9192
}
9293

9394
_LOGGER = logging.getLogger(__name__)

homeassistant/components/hassio/strings.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,19 @@
164164
},
165165
"title": "Multiple data disks detected"
166166
},
167+
"issue_system_ntp_sync_failed": {
168+
"fix_flow": {
169+
"abort": {
170+
"apply_suggestion_fail": "Could not re-enable NTP. Check the Supervisor logs for more details."
171+
},
172+
"step": {
173+
"system_enable_ntp": {
174+
"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."
175+
}
176+
}
177+
},
178+
"title": "NTP sync failed - system time was corrected"
179+
},
167180
"issue_system_reboot_required": {
168181
"fix_flow": {
169182
"abort": {

tests/components/hassio/test_issues.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,61 @@ async def test_supervisor_issues_detached_addon_missing(
949949
)
950950

951951

952+
@pytest.mark.usefixtures("all_setup_requests")
953+
async def test_supervisor_issues_ntp_sync_failed(
954+
hass: HomeAssistant,
955+
supervisor_client: AsyncMock,
956+
hass_ws_client: WebSocketGenerator,
957+
) -> None:
958+
"""Test supervisor issue for NTP sync failed."""
959+
mock_resolution_info(supervisor_client)
960+
961+
result = await async_setup_component(hass, "hassio", {})
962+
assert result
963+
964+
client = await hass_ws_client(hass)
965+
966+
await client.send_json(
967+
{
968+
"id": 1,
969+
"type": "supervisor/event",
970+
"data": {
971+
"event": "issue_changed",
972+
"data": {
973+
"uuid": (issue_uuid := uuid4().hex),
974+
"type": "ntp_sync_failed",
975+
"context": "system",
976+
"reference": None,
977+
"suggestions": [
978+
{
979+
"uuid": uuid4().hex,
980+
"type": "enable_ntp",
981+
"context": "system",
982+
"reference": None,
983+
}
984+
],
985+
},
986+
},
987+
}
988+
)
989+
msg = await client.receive_json()
990+
assert msg["success"]
991+
await hass.async_block_till_done()
992+
993+
await client.send_json({"id": 2, "type": "repairs/list_issues"})
994+
msg = await client.receive_json()
995+
assert msg["success"]
996+
assert len(msg["result"]["issues"]) == 1
997+
assert_issue_repair_in_list(
998+
msg["result"]["issues"],
999+
uuid=issue_uuid,
1000+
context="system",
1001+
type_="ntp_sync_failed",
1002+
fixable=True,
1003+
placeholders=None,
1004+
)
1005+
1006+
9521007
@pytest.mark.usefixtures("all_setup_requests")
9531008
async def test_supervisor_issues_disk_lifetime(
9541009
hass: HomeAssistant,

tests/components/hassio/test_repairs.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,156 @@ async def test_supervisor_issue_repair_flow_skip_confirmation(
402402
supervisor_client.resolution.apply_suggestion.assert_called_once_with(sugg_uuid)
403403

404404

405+
@pytest.mark.usefixtures("all_setup_requests")
406+
async def test_supervisor_issue_ntp_sync_failed_repair_flow(
407+
hass: HomeAssistant,
408+
supervisor_client: AsyncMock,
409+
hass_client: ClientSessionGenerator,
410+
issue_registry: ir.IssueRegistry,
411+
) -> None:
412+
"""Test fix flow for NTP sync failed supervisor issue."""
413+
mock_resolution_info(
414+
supervisor_client,
415+
issues=[
416+
Issue(
417+
# IssueType.NTP_SYNC_FAILED once aiohasupervisor >0.3.3 is released
418+
type="ntp_sync_failed",
419+
context=ContextType.SYSTEM,
420+
reference=None,
421+
uuid=(issue_uuid := uuid4()),
422+
),
423+
],
424+
suggestions_by_issue={
425+
issue_uuid: [
426+
Suggestion(
427+
# SuggestionType.ENABLE_NTP once aiohasupervisor >0.3.3 is released
428+
type="enable_ntp",
429+
context=ContextType.SYSTEM,
430+
reference=None,
431+
uuid=(sugg_uuid := uuid4()),
432+
auto=False,
433+
),
434+
]
435+
},
436+
)
437+
438+
assert await async_setup_component(hass, "hassio", {})
439+
440+
repair_issue = issue_registry.async_get_issue(
441+
domain="hassio", issue_id=issue_uuid.hex
442+
)
443+
assert repair_issue
444+
445+
client = await hass_client()
446+
447+
resp = await client.post(
448+
"/api/repairs/issues/fix",
449+
json={"handler": "hassio", "issue_id": repair_issue.issue_id},
450+
)
451+
452+
assert resp.status == HTTPStatus.OK
453+
data = await resp.json()
454+
455+
flow_id = data["flow_id"]
456+
assert data == {
457+
"type": "form",
458+
"flow_id": flow_id,
459+
"handler": "hassio",
460+
"step_id": "system_enable_ntp",
461+
"data_schema": [],
462+
"errors": None,
463+
"description_placeholders": None,
464+
"last_step": True,
465+
"preview": None,
466+
}
467+
468+
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
469+
470+
assert resp.status == HTTPStatus.OK
471+
data = await resp.json()
472+
473+
flow_id = data["flow_id"]
474+
assert data == {
475+
"type": "create_entry",
476+
"flow_id": flow_id,
477+
"handler": "hassio",
478+
"description": None,
479+
"description_placeholders": None,
480+
}
481+
482+
assert not issue_registry.async_get_issue(domain="hassio", issue_id=issue_uuid.hex)
483+
supervisor_client.resolution.apply_suggestion.assert_called_once_with(sugg_uuid)
484+
485+
486+
@pytest.mark.usefixtures("all_setup_requests")
487+
async def test_supervisor_issue_ntp_sync_failed_repair_flow_error(
488+
hass: HomeAssistant,
489+
supervisor_client: AsyncMock,
490+
hass_client: ClientSessionGenerator,
491+
issue_registry: ir.IssueRegistry,
492+
) -> None:
493+
"""Test fix flow aborts when NTP re-enable fails."""
494+
mock_resolution_info(
495+
supervisor_client,
496+
issues=[
497+
Issue(
498+
# IssueType.NTP_SYNC_FAILED once aiohasupervisor >0.3.3 is released
499+
type="ntp_sync_failed",
500+
context=ContextType.SYSTEM,
501+
reference=None,
502+
uuid=(issue_uuid := uuid4()),
503+
),
504+
],
505+
suggestions_by_issue={
506+
issue_uuid: [
507+
Suggestion(
508+
# SuggestionType.ENABLE_NTP once aiohasupervisor >0.3.3 is released
509+
type="enable_ntp",
510+
context=ContextType.SYSTEM,
511+
reference=None,
512+
uuid=uuid4(),
513+
auto=False,
514+
),
515+
]
516+
},
517+
suggestion_result=SupervisorError("boom"),
518+
)
519+
520+
assert await async_setup_component(hass, "hassio", {})
521+
522+
repair_issue = issue_registry.async_get_issue(
523+
domain="hassio", issue_id=issue_uuid.hex
524+
)
525+
assert repair_issue
526+
527+
client = await hass_client()
528+
529+
resp = await client.post(
530+
"/api/repairs/issues/fix",
531+
json={"handler": "hassio", "issue_id": repair_issue.issue_id},
532+
)
533+
534+
assert resp.status == HTTPStatus.OK
535+
data = await resp.json()
536+
flow_id = data["flow_id"]
537+
538+
resp = await client.post(f"/api/repairs/issues/fix/{flow_id}")
539+
540+
assert resp.status == HTTPStatus.OK
541+
data = await resp.json()
542+
543+
flow_id = data["flow_id"]
544+
assert data == {
545+
"type": "abort",
546+
"flow_id": flow_id,
547+
"handler": "hassio",
548+
"reason": "apply_suggestion_fail",
549+
"description_placeholders": None,
550+
}
551+
552+
assert issue_registry.async_get_issue(domain="hassio", issue_id=issue_uuid.hex)
553+
554+
405555
@pytest.mark.usefixtures("all_setup_requests")
406556
async def test_mount_failed_repair_flow_error(
407557
hass: HomeAssistant,

0 commit comments

Comments
 (0)