From 44aad2b8211e93ae40a69bb8df993562821e144f Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 26 Jun 2024 11:43:51 +0200 Subject: [PATCH] Improve Matter Server version incompatibility handling (#120416) * Improve Matter Server version incompatibility handling Improve the handling of Matter Server version. Noteably fix the issues raised (add strings for the issue) and split the version check into two cases: One if the server is too old and one if the server is too new. * Bump Python Matter Server library to 6.2.0b1 * Address review feedback --- homeassistant/components/matter/__init__.py | 32 +++++++--- homeassistant/components/matter/manifest.json | 2 +- homeassistant/components/matter/strings.json | 10 +++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/matter/test_init.py | 63 ++++++++++++++++--- 6 files changed, 90 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/matter/__init__.py b/homeassistant/components/matter/__init__.py index 86b642f7389..75ae3df6b1a 100644 --- a/homeassistant/components/matter/__init__.py +++ b/homeassistant/components/matter/__init__.py @@ -7,7 +7,12 @@ from contextlib import suppress from functools import cache from matter_server.client import MatterClient -from matter_server.client.exceptions import CannotConnect, InvalidServerVersion +from matter_server.client.exceptions import ( + CannotConnect, + InvalidServerVersion, + ServerVersionTooNew, + ServerVersionTooOld, +) from matter_server.common.errors import MatterError, NodeNotExists from homeassistant.components.hassio import AddonError, AddonManager, AddonState @@ -71,17 +76,27 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: except (CannotConnect, TimeoutError) as err: raise ConfigEntryNotReady("Failed to connect to matter server") from err except InvalidServerVersion as err: - if use_addon: - addon_manager = _get_addon_manager(hass) - addon_manager.async_schedule_update_addon(catch_error=True) - else: + if isinstance(err, ServerVersionTooOld): + if use_addon: + addon_manager = _get_addon_manager(hass) + addon_manager.async_schedule_update_addon(catch_error=True) + else: + async_create_issue( + hass, + DOMAIN, + "server_version_version_too_old", + is_fixable=False, + severity=IssueSeverity.ERROR, + translation_key="server_version_version_too_old", + ) + elif isinstance(err, ServerVersionTooNew): async_create_issue( hass, DOMAIN, - "invalid_server_version", + "server_version_version_too_new", is_fixable=False, severity=IssueSeverity.ERROR, - translation_key="invalid_server_version", + translation_key="server_version_version_too_new", ) raise ConfigEntryNotReady(f"Invalid server version: {err}") from err @@ -91,7 +106,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: "Unknown error connecting to the Matter server" ) from err - async_delete_issue(hass, DOMAIN, "invalid_server_version") + async_delete_issue(hass, DOMAIN, "server_version_version_too_old") + async_delete_issue(hass, DOMAIN, "server_version_version_too_new") async def on_hass_stop(event: Event) -> None: """Handle incoming stop event from Home Assistant.""" diff --git a/homeassistant/components/matter/manifest.json b/homeassistant/components/matter/manifest.json index 369657df90c..8c88fcc8be2 100644 --- a/homeassistant/components/matter/manifest.json +++ b/homeassistant/components/matter/manifest.json @@ -6,6 +6,6 @@ "dependencies": ["websocket_api"], "documentation": "https://www.home-assistant.io/integrations/matter", "iot_class": "local_push", - "requirements": ["python-matter-server==6.1.0"], + "requirements": ["python-matter-server==6.2.0b1"], "zeroconf": ["_matter._tcp.local.", "_matterc._udp.local."] } diff --git a/homeassistant/components/matter/strings.json b/homeassistant/components/matter/strings.json index e94ab2e1780..3389a4bfe81 100644 --- a/homeassistant/components/matter/strings.json +++ b/homeassistant/components/matter/strings.json @@ -157,6 +157,16 @@ } } }, + "issues": { + "server_version_version_too_old": { + "description": "The version of the Matter Server you are currently running is too old for this version of Home Assistant. Please update the Matter Server to the latest version to fix this issue.", + "title": "Newer version of Matter Server needed" + }, + "server_version_version_too_new": { + "description": "The version of the Matter Server you are currently running is too new for this version of Home Assistant. Please update Home Assistant or downgrade the Matter Server to an older version to fix this issue.", + "title": "Older version of Matter Server needed" + } + }, "services": { "open_commissioning_window": { "name": "Open commissioning window", diff --git a/requirements_all.txt b/requirements_all.txt index 5967b3f2a94..1a297ef2b5c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2284,7 +2284,7 @@ python-kasa[speedups]==0.7.0.1 # python-lirc==1.2.3 # homeassistant.components.matter -python-matter-server==6.1.0 +python-matter-server==6.2.0b1 # homeassistant.components.xiaomi_miio python-miio==0.5.12 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e1fb561ecee..88623000c5e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1781,7 +1781,7 @@ python-juicenet==1.1.0 python-kasa[speedups]==0.7.0.1 # homeassistant.components.matter -python-matter-server==6.1.0 +python-matter-server==6.2.0b1 # homeassistant.components.xiaomi_miio python-miio==0.5.12 diff --git a/tests/components/matter/test_init.py b/tests/components/matter/test_init.py index d3712f24d12..c28385efca3 100644 --- a/tests/components/matter/test_init.py +++ b/tests/components/matter/test_init.py @@ -5,7 +5,11 @@ from __future__ import annotations import asyncio from unittest.mock import AsyncMock, MagicMock, call, patch -from matter_server.client.exceptions import CannotConnect, InvalidServerVersion +from matter_server.client.exceptions import ( + CannotConnect, + ServerVersionTooNew, + ServerVersionTooOld, +) from matter_server.client.models.node import MatterNode from matter_server.common.errors import MatterError from matter_server.common.helpers.util import dataclass_from_dict @@ -362,12 +366,30 @@ async def test_addon_info_failure( "backup_calls", "update_addon_side_effect", "create_backup_side_effect", + "connect_side_effect", ), [ - ("1.0.0", True, 1, 1, None, None), - ("1.0.0", False, 0, 0, None, None), - ("1.0.0", True, 1, 1, HassioAPIError("Boom"), None), - ("1.0.0", True, 0, 1, None, HassioAPIError("Boom")), + ("1.0.0", True, 1, 1, None, None, ServerVersionTooOld("Invalid version")), + ("1.0.0", True, 0, 0, None, None, ServerVersionTooNew("Invalid version")), + ("1.0.0", False, 0, 0, None, None, ServerVersionTooOld("Invalid version")), + ( + "1.0.0", + True, + 1, + 1, + HassioAPIError("Boom"), + None, + ServerVersionTooOld("Invalid version"), + ), + ( + "1.0.0", + True, + 0, + 1, + None, + HassioAPIError("Boom"), + ServerVersionTooOld("Invalid version"), + ), ], ) async def test_update_addon( @@ -386,13 +408,14 @@ async def test_update_addon( backup_calls: int, update_addon_side_effect: Exception | None, create_backup_side_effect: Exception | None, + connect_side_effect: Exception, ) -> None: """Test update the Matter add-on during entry setup.""" addon_info.return_value["version"] = addon_version addon_info.return_value["update_available"] = update_available create_backup.side_effect = create_backup_side_effect update_addon.side_effect = update_addon_side_effect - matter_client.connect.side_effect = InvalidServerVersion("Invalid version") + matter_client.connect.side_effect = connect_side_effect entry = MockConfigEntry( domain=DOMAIN, title="Matter", @@ -413,12 +436,32 @@ async def test_update_addon( # This tests needs to be adjusted to remove lingering tasks @pytest.mark.parametrize("expected_lingering_tasks", [True]) +@pytest.mark.parametrize( + ( + "connect_side_effect", + "issue_raised", + ), + [ + ( + ServerVersionTooOld("Invalid version"), + "server_version_version_too_old", + ), + ( + ServerVersionTooNew("Invalid version"), + "server_version_version_too_new", + ), + ], +) async def test_issue_registry_invalid_version( - hass: HomeAssistant, matter_client: MagicMock, issue_registry: ir.IssueRegistry + hass: HomeAssistant, + matter_client: MagicMock, + issue_registry: ir.IssueRegistry, + connect_side_effect: Exception, + issue_raised: str, ) -> None: """Test issue registry for invalid version.""" original_connect_side_effect = matter_client.connect.side_effect - matter_client.connect.side_effect = InvalidServerVersion("Invalid version") + matter_client.connect.side_effect = connect_side_effect entry = MockConfigEntry( domain=DOMAIN, title="Matter", @@ -434,7 +477,7 @@ async def test_issue_registry_invalid_version( entry_state = entry.state assert entry_state is ConfigEntryState.SETUP_RETRY - assert issue_registry.async_get_issue(DOMAIN, "invalid_server_version") + assert issue_registry.async_get_issue(DOMAIN, issue_raised) matter_client.connect.side_effect = original_connect_side_effect @@ -442,7 +485,7 @@ async def test_issue_registry_invalid_version( await hass.async_block_till_done() assert entry.state is ConfigEntryState.LOADED - assert not issue_registry.async_get_issue(DOMAIN, "invalid_server_version") + assert not issue_registry.async_get_issue(DOMAIN, issue_raised) @pytest.mark.parametrize(