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
This commit is contained in:
Stefan Agner 2024-06-26 11:43:51 +02:00 committed by GitHub
parent 7b7b97a7a4
commit 44aad2b821
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 90 additions and 21 deletions

View File

@ -7,7 +7,12 @@ from contextlib import suppress
from functools import cache from functools import cache
from matter_server.client import MatterClient 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 matter_server.common.errors import MatterError, NodeNotExists
from homeassistant.components.hassio import AddonError, AddonManager, AddonState 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: except (CannotConnect, TimeoutError) as err:
raise ConfigEntryNotReady("Failed to connect to matter server") from err raise ConfigEntryNotReady("Failed to connect to matter server") from err
except InvalidServerVersion as err: except InvalidServerVersion as err:
if use_addon: if isinstance(err, ServerVersionTooOld):
addon_manager = _get_addon_manager(hass) if use_addon:
addon_manager.async_schedule_update_addon(catch_error=True) addon_manager = _get_addon_manager(hass)
else: 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( async_create_issue(
hass, hass,
DOMAIN, DOMAIN,
"invalid_server_version", "server_version_version_too_new",
is_fixable=False, is_fixable=False,
severity=IssueSeverity.ERROR, severity=IssueSeverity.ERROR,
translation_key="invalid_server_version", translation_key="server_version_version_too_new",
) )
raise ConfigEntryNotReady(f"Invalid server version: {err}") from err 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" "Unknown error connecting to the Matter server"
) from err ) 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: async def on_hass_stop(event: Event) -> None:
"""Handle incoming stop event from Home Assistant.""" """Handle incoming stop event from Home Assistant."""

View File

@ -6,6 +6,6 @@
"dependencies": ["websocket_api"], "dependencies": ["websocket_api"],
"documentation": "https://www.home-assistant.io/integrations/matter", "documentation": "https://www.home-assistant.io/integrations/matter",
"iot_class": "local_push", "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."] "zeroconf": ["_matter._tcp.local.", "_matterc._udp.local."]
} }

View File

@ -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": { "services": {
"open_commissioning_window": { "open_commissioning_window": {
"name": "Open commissioning window", "name": "Open commissioning window",

View File

@ -2284,7 +2284,7 @@ python-kasa[speedups]==0.7.0.1
# python-lirc==1.2.3 # python-lirc==1.2.3
# homeassistant.components.matter # homeassistant.components.matter
python-matter-server==6.1.0 python-matter-server==6.2.0b1
# homeassistant.components.xiaomi_miio # homeassistant.components.xiaomi_miio
python-miio==0.5.12 python-miio==0.5.12

View File

@ -1781,7 +1781,7 @@ python-juicenet==1.1.0
python-kasa[speedups]==0.7.0.1 python-kasa[speedups]==0.7.0.1
# homeassistant.components.matter # homeassistant.components.matter
python-matter-server==6.1.0 python-matter-server==6.2.0b1
# homeassistant.components.xiaomi_miio # homeassistant.components.xiaomi_miio
python-miio==0.5.12 python-miio==0.5.12

View File

@ -5,7 +5,11 @@ from __future__ import annotations
import asyncio import asyncio
from unittest.mock import AsyncMock, MagicMock, call, patch 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.client.models.node import MatterNode
from matter_server.common.errors import MatterError from matter_server.common.errors import MatterError
from matter_server.common.helpers.util import dataclass_from_dict from matter_server.common.helpers.util import dataclass_from_dict
@ -362,12 +366,30 @@ async def test_addon_info_failure(
"backup_calls", "backup_calls",
"update_addon_side_effect", "update_addon_side_effect",
"create_backup_side_effect", "create_backup_side_effect",
"connect_side_effect",
), ),
[ [
("1.0.0", True, 1, 1, None, None), ("1.0.0", True, 1, 1, None, None, ServerVersionTooOld("Invalid version")),
("1.0.0", False, 0, 0, None, None), ("1.0.0", True, 0, 0, None, None, ServerVersionTooNew("Invalid version")),
("1.0.0", True, 1, 1, HassioAPIError("Boom"), None), ("1.0.0", False, 0, 0, None, None, ServerVersionTooOld("Invalid version")),
("1.0.0", True, 0, 1, None, HassioAPIError("Boom")), (
"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( async def test_update_addon(
@ -386,13 +408,14 @@ async def test_update_addon(
backup_calls: int, backup_calls: int,
update_addon_side_effect: Exception | None, update_addon_side_effect: Exception | None,
create_backup_side_effect: Exception | None, create_backup_side_effect: Exception | None,
connect_side_effect: Exception,
) -> None: ) -> None:
"""Test update the Matter add-on during entry setup.""" """Test update the Matter add-on during entry setup."""
addon_info.return_value["version"] = addon_version addon_info.return_value["version"] = addon_version
addon_info.return_value["update_available"] = update_available addon_info.return_value["update_available"] = update_available
create_backup.side_effect = create_backup_side_effect create_backup.side_effect = create_backup_side_effect
update_addon.side_effect = update_addon_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( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
title="Matter", title="Matter",
@ -413,12 +436,32 @@ async def test_update_addon(
# This tests needs to be adjusted to remove lingering tasks # This tests needs to be adjusted to remove lingering tasks
@pytest.mark.parametrize("expected_lingering_tasks", [True]) @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( 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: ) -> None:
"""Test issue registry for invalid version.""" """Test issue registry for invalid version."""
original_connect_side_effect = matter_client.connect.side_effect 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( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
title="Matter", title="Matter",
@ -434,7 +477,7 @@ async def test_issue_registry_invalid_version(
entry_state = entry.state entry_state = entry.state
assert entry_state is ConfigEntryState.SETUP_RETRY 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 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() await hass.async_block_till_done()
assert entry.state is ConfigEntryState.LOADED 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( @pytest.mark.parametrize(