Compare commits

...
Sign in to create a new pull request.

3 commits

Author SHA1 Message Date
Stefan Agner
9726228630 Address mypy issues 2024-06-05 10:35:32 +02:00
Stefan Agner
b278976bc2 Add tests 2024-06-05 10:35:32 +02:00
Stefan Agner
0754fdcf12 Automatically update matter server add-on if needed
Update to the latest add-on if the schema version of the client is
newer than the Server schema version.
2024-06-05 10:35:32 +02:00
2 changed files with 71 additions and 24 deletions

View file

@ -8,9 +8,11 @@ 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
from matter_server.common.const import SCHEMA_VERSION
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
from homeassistant.components.hassio.addon_manager import AddonInfo
from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import CONF_URL, EVENT_HOMEASSISTANT_STOP from homeassistant.const import CONF_URL, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import Event, HomeAssistant, callback from homeassistant.core import Event, HomeAssistant, callback
@ -62,7 +64,7 @@ def get_matter_device_info(
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Matter from a config entry.""" """Set up Matter from a config entry."""
if use_addon := entry.data.get(CONF_USE_ADDON): if use_addon := entry.data.get(CONF_USE_ADDON):
await _async_ensure_addon_running(hass, entry) addon_info = await _async_ensure_addon_running(hass, entry)
matter_client = MatterClient(entry.data[CONF_URL], async_get_clientsession(hass)) matter_client = MatterClient(entry.data[CONF_URL], async_get_clientsession(hass))
try: try:
@ -71,26 +73,44 @@ 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: # This is raised when the Server requires a newer client than we are :(
addon_manager = _get_addon_manager(hass) # We can't do anything about it even in the add-on case.
addon_manager.async_schedule_update_addon(catch_error=True) async_create_issue(
else: hass,
async_create_issue( DOMAIN,
hass, "invalid_server_version",
DOMAIN, is_fixable=False,
"invalid_server_version", severity=IssueSeverity.ERROR,
is_fixable=False, translation_key="invalid_server_version",
severity=IssueSeverity.ERROR, )
translation_key="invalid_server_version",
)
raise ConfigEntryNotReady(f"Invalid server version: {err}") from err raise ConfigEntryNotReady(f"Invalid server version: {err}") from err
except Exception as err: except Exception as err:
LOGGER.exception("Failed to connect to matter server") LOGGER.exception("Failed to connect to matter server")
raise ConfigEntryNotReady( raise ConfigEntryNotReady(
"Unknown error connecting to the Matter server" "Unknown error connecting to the Matter server"
) from err ) from err
if (
matter_client.server_info is not None
and matter_client.server_info.schema_version < SCHEMA_VERSION
):
if use_addon and addon_info.update_available:
LOGGER.info(
"Matter server schema version is too old (%d < %d), scheduling add-on update",
matter_client.server_info.schema_version,
SCHEMA_VERSION,
)
await matter_client.disconnect()
addon_manager = _get_addon_manager(hass)
addon_manager.async_schedule_update_addon(catch_error=True)
raise ConfigEntryNotReady("Matter server version too old")
LOGGER.warning(
"Matter server schema version is too old (%d < %d)",
matter_client.server_info.schema_version,
SCHEMA_VERSION,
)
async_delete_issue(hass, DOMAIN, "invalid_server_version") async_delete_issue(hass, DOMAIN, "invalid_server_version")
async def on_hass_stop(event: Event) -> None: async def on_hass_stop(event: Event) -> None:
@ -238,8 +258,13 @@ async def async_remove_config_entry_device(
return True return True
async def _async_ensure_addon_running(hass: HomeAssistant, entry: ConfigEntry) -> None: async def _async_ensure_addon_running(
"""Ensure that Matter Server add-on is installed and running.""" hass: HomeAssistant, entry: ConfigEntry
) -> AddonInfo:
"""Ensure that Matter Server add-on is installed and running.
Returns add-on information if successful, raises ConfigEntryNotReady otherwise.
"""
addon_manager = _get_addon_manager(hass) addon_manager = _get_addon_manager(hass)
try: try:
addon_info = await addon_manager.async_get_addon_info() addon_info = await addon_manager.async_get_addon_info()
@ -259,6 +284,8 @@ async def _async_ensure_addon_running(hass: HomeAssistant, entry: ConfigEntry) -
addon_manager.async_schedule_start_addon(catch_error=True) addon_manager.async_schedule_start_addon(catch_error=True)
raise ConfigEntryNotReady raise ConfigEntryNotReady
return addon_info
@callback @callback
def _get_addon_manager(hass: HomeAssistant) -> AddonManager: def _get_addon_manager(hass: HomeAssistant) -> AddonManager:

View file

@ -8,6 +8,7 @@ from unittest.mock import AsyncMock, MagicMock, call, patch
from matter_server.client.exceptions import CannotConnect, InvalidServerVersion from matter_server.client.exceptions import CannotConnect, InvalidServerVersion
from matter_server.client.models.node import MatterNode from matter_server.client.models.node import MatterNode
from matter_server.common.const import SCHEMA_VERSION
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
from matter_server.common.models import MatterNodeData from matter_server.common.models import MatterNodeData
@ -356,18 +357,36 @@ async def test_addon_info_failure(
@pytest.mark.parametrize( @pytest.mark.parametrize(
( (
"addon_version",
"update_available", "update_available",
"update_calls", "update_calls",
"backup_calls", "backup_calls",
"update_addon_side_effect", "update_addon_side_effect",
"create_backup_side_effect", "create_backup_side_effect",
"matter_client_connect_side_effect",
"matter_client_server_info_schema_version",
), ),
[ [
("1.0.0", True, 1, 1, None, None), (True, 1, 1, None, None, None, 1),
("1.0.0", False, 0, 0, None, None), (
("1.0.0", True, 1, 1, HassioAPIError("Boom"), None), False,
("1.0.0", True, 0, 1, None, HassioAPIError("Boom")), 0,
0,
None,
None,
InvalidServerVersion("Invalid version"),
1,
),
(
False,
0,
0,
None,
None,
InvalidServerVersion("Invalid version"),
SCHEMA_VERSION,
),
(True, 1, 1, HassioAPIError("Boom"), None, None, 1),
(True, 0, 1, None, HassioAPIError("Boom"), None, 1),
], ],
) )
async def test_update_addon( async def test_update_addon(
@ -380,19 +399,20 @@ async def test_update_addon(
create_backup: AsyncMock, create_backup: AsyncMock,
update_addon: AsyncMock, update_addon: AsyncMock,
matter_client: MagicMock, matter_client: MagicMock,
addon_version: str,
update_available: bool, update_available: bool,
update_calls: int, update_calls: int,
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,
matter_client_connect_side_effect: Exception | None,
matter_client_server_info_schema_version: int,
): ):
"""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["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 = matter_client_connect_side_effect
matter_client.server_info.schema_version = matter_client_server_info_schema_version
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
title="Matter", title="Matter",