mirror of
https://github.com/home-assistant/core.git
synced 2026-08-03 20:24:55 +02:00
Fix via_device race in synology_dsm (#177769)
This commit is contained in:
@@ -134,6 +134,39 @@ async def async_setup_entry(hass: HomeAssistant, entry: SynologyDSMConfigEntry)
|
||||
coordinator_cameras=coordinator_cameras,
|
||||
coordinator_switches=coordinator_switches,
|
||||
)
|
||||
|
||||
# Register parent devices before forwarding platform setups so that child
|
||||
# devices (storage/USB devices, surveillance station, cameras) can resolve
|
||||
# their via_device_id regardless of platform setup order.
|
||||
if TYPE_CHECKING:
|
||||
assert api.information is not None
|
||||
assert api.network is not None
|
||||
central_device = dev_reg.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, api.information.serial)},
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, mac) for mac in api.network.macs},
|
||||
name=api.network.hostname,
|
||||
manufacturer="Synology",
|
||||
model=api.information.model,
|
||||
sw_version=api.information.version_string,
|
||||
configuration_url=api.config_url,
|
||||
)
|
||||
if api.surveillance_station is not None:
|
||||
dev_reg.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={
|
||||
(
|
||||
DOMAIN,
|
||||
f"{api.information.serial}_{SynoSurveillanceStation.INFO_API_KEY}",
|
||||
)
|
||||
},
|
||||
name=f"{api.network.hostname} Surveillance Station",
|
||||
manufacturer="Synology",
|
||||
model=api.information.model,
|
||||
sw_version=coordinator_switches.version if coordinator_switches else None,
|
||||
via_device_id=central_device.id,
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
if entry.options[CONF_BACKUP_SHARE]:
|
||||
|
||||
@@ -16,6 +16,7 @@ from homeassistant.components.camera import (
|
||||
CameraEntityFeature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -100,9 +101,13 @@ class SynoDSMCamera(SynologyDSMBaseEntity[SynologyDSMCameraUpdateCoordinator], C
|
||||
identifiers={(DOMAIN, f"{information.serial}_{self.camera_data.id}")},
|
||||
name=self.camera_data.name,
|
||||
model=self.camera_data.model,
|
||||
via_device=(
|
||||
DOMAIN,
|
||||
f"{information.serial}_{SynoSurveillanceStation.INFO_API_KEY}",
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
self.hass,
|
||||
(
|
||||
DOMAIN,
|
||||
f"{information.serial}_{SynoSurveillanceStation.INFO_API_KEY}",
|
||||
),
|
||||
config_entry_id=self.coordinator.config_entry.entry_id,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, override
|
||||
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
@@ -158,6 +159,10 @@ class SynologyDSMDeviceEntity(
|
||||
manufacturer=self._device_manufacturer,
|
||||
model=self._device_model,
|
||||
sw_version=self._device_firmware,
|
||||
via_device=(DOMAIN, information.serial),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
self.coordinator.hass,
|
||||
(DOMAIN, information.serial),
|
||||
config_entry_id=self.coordinator.config_entry.entry_id,
|
||||
),
|
||||
configuration_url=self._api.config_url,
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ from synology_dsm.api.surveillance_station import SynoSurveillanceStation
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -128,5 +129,9 @@ class SynoDSMSurveillanceHomeModeToggle(
|
||||
manufacturer="Synology",
|
||||
model=self._api.information.model,
|
||||
sw_version=self._version,
|
||||
via_device=(DOMAIN, self._api.information.serial),
|
||||
via_device_id=dr.async_get_device_id_by_identifier(
|
||||
self.hass,
|
||||
(DOMAIN, self._api.information.serial),
|
||||
config_entry_id=self.coordinator.config_entry.entry_id,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Configure Synology DSM tests."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -9,6 +9,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .common import mock_dsm_hardware, mock_dsm_information
|
||||
from .consts import HOST, MACS
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -34,7 +35,9 @@ def fixture_dsm():
|
||||
dsm.update = AsyncMock(return_value=True)
|
||||
|
||||
dsm.information = mock_dsm_information()
|
||||
dsm.network.update = AsyncMock(return_value=True)
|
||||
dsm.network = Mock(
|
||||
update=AsyncMock(return_value=True), macs=MACS, hostname=HOST
|
||||
)
|
||||
dsm.hardware = mock_dsm_hardware()
|
||||
dsm.surveillance_station.update = AsyncMock(return_value=True)
|
||||
dsm.upgrade.update = AsyncMock(return_value=True)
|
||||
|
||||
@@ -92,7 +92,9 @@ def mock_dsm_with_filestation():
|
||||
dsm.surveillance_station.update = AsyncMock(return_value=True)
|
||||
dsm.upgrade.update = AsyncMock(return_value=True)
|
||||
dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True))
|
||||
dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS)
|
||||
dsm.network = Mock(
|
||||
update=AsyncMock(return_value=True), macs=MACS, hostname=HOST
|
||||
)
|
||||
dsm.hardware = mock_dsm_hardware()
|
||||
dsm.storage = Mock(
|
||||
disks_ids=["sda", "sdb", "sdc"],
|
||||
@@ -146,7 +148,9 @@ def mock_dsm_without_filestation():
|
||||
dsm.surveillance_station.update = AsyncMock(return_value=True)
|
||||
dsm.upgrade.update = AsyncMock(return_value=True)
|
||||
dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True))
|
||||
dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS)
|
||||
dsm.network = Mock(
|
||||
update=AsyncMock(return_value=True), macs=MACS, hostname=HOST
|
||||
)
|
||||
dsm.hardware = mock_dsm_hardware()
|
||||
dsm.information = mock_dsm_information()
|
||||
dsm.storage = Mock(
|
||||
|
||||
@@ -67,7 +67,9 @@ def mock_controller_service():
|
||||
dsm.surveillance_station.update = AsyncMock(return_value=True)
|
||||
dsm.upgrade.update = AsyncMock(return_value=True)
|
||||
dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True))
|
||||
dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS)
|
||||
dsm.network = Mock(
|
||||
update=AsyncMock(return_value=True), macs=MACS, hostname=HOST
|
||||
)
|
||||
dsm.hardware = mock_dsm_hardware()
|
||||
dsm.storage = Mock(
|
||||
disks_ids=["sda", "sdb", "sdc"],
|
||||
@@ -91,7 +93,9 @@ def mock_controller_service_2sa():
|
||||
dsm.surveillance_station.update = AsyncMock(return_value=True)
|
||||
dsm.upgrade.update = AsyncMock(return_value=True)
|
||||
dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True))
|
||||
dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS)
|
||||
dsm.network = Mock(
|
||||
update=AsyncMock(return_value=True), macs=MACS, hostname=HOST
|
||||
)
|
||||
dsm.hardware = mock_dsm_hardware()
|
||||
dsm.storage = Mock(
|
||||
disks_ids=["sda", "sdb", "sdc"],
|
||||
@@ -113,7 +117,9 @@ def mock_controller_service_vdsm():
|
||||
dsm.surveillance_station.update = AsyncMock(return_value=True)
|
||||
dsm.upgrade.update = AsyncMock(return_value=True)
|
||||
dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True))
|
||||
dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS)
|
||||
dsm.network = Mock(
|
||||
update=AsyncMock(return_value=True), macs=MACS, hostname=HOST
|
||||
)
|
||||
dsm.hardware = mock_dsm_hardware()
|
||||
dsm.storage = Mock(
|
||||
disks_ids=[],
|
||||
@@ -135,7 +141,9 @@ def mock_controller_service_with_filestation():
|
||||
dsm.surveillance_station.update = AsyncMock(return_value=True)
|
||||
dsm.upgrade.update = AsyncMock(return_value=True)
|
||||
dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True))
|
||||
dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS)
|
||||
dsm.network = Mock(
|
||||
update=AsyncMock(return_value=True), macs=MACS, hostname=HOST
|
||||
)
|
||||
dsm.hardware = mock_dsm_hardware()
|
||||
dsm.storage = Mock(
|
||||
disks_ids=["sda", "sdb", "sdc"],
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
|
||||
from aiohttp import web
|
||||
import pytest
|
||||
@@ -41,7 +41,7 @@ def dsm_with_photos() -> MagicMock:
|
||||
dsm.login = AsyncMock(return_value=True)
|
||||
dsm.update = AsyncMock(return_value=True)
|
||||
dsm.information = mock_dsm_information()
|
||||
dsm.network.update = AsyncMock(return_value=True)
|
||||
dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS, hostname=HOST)
|
||||
dsm.hardware = mock_dsm_hardware()
|
||||
dsm.surveillance_station.update = AsyncMock(return_value=True)
|
||||
dsm.upgrade.update = AsyncMock(return_value=True)
|
||||
|
||||
@@ -41,7 +41,9 @@ def mock_dsm_with_filestation():
|
||||
dsm.surveillance_station.update = AsyncMock(return_value=True)
|
||||
dsm.upgrade.update = AsyncMock(return_value=True)
|
||||
dsm.utilisation = Mock(cpu_user_load=1, update=AsyncMock(return_value=True))
|
||||
dsm.network = Mock(update=AsyncMock(return_value=True), macs=MACS)
|
||||
dsm.network = Mock(
|
||||
update=AsyncMock(return_value=True), macs=MACS, hostname=HOST
|
||||
)
|
||||
dsm.hardware = mock_dsm_hardware()
|
||||
dsm.storage = Mock(
|
||||
disks_ids=["sda", "sdb", "sdc"],
|
||||
|
||||
@@ -406,3 +406,26 @@ async def test_hub_device_info_mac_connections(
|
||||
("mac", "00:11:32:xx:xx:59"),
|
||||
("mac", "00:11:32:xx:xx:5a"),
|
||||
}
|
||||
|
||||
|
||||
async def test_storage_device_via_device(
|
||||
hass: HomeAssistant,
|
||||
setup_dsm_with_usb: MagicMock,
|
||||
) -> None:
|
||||
"""Test that storage/USB child devices link to the hub via via_device_id."""
|
||||
dev_reg = dr.async_get(hass) # pylint: disable=home-assistant-tests-registry-fixtures
|
||||
entry_id = setup_dsm_with_usb.mock_entry.entry_id
|
||||
hub_device = dev_reg.async_get_device_by_identifier((DOMAIN, SERIAL), entry_id)
|
||||
assert hub_device is not None
|
||||
|
||||
volume_device = dev_reg.async_get_device_by_identifier(
|
||||
(DOMAIN, f"{SERIAL}_volume_1"), entry_id
|
||||
)
|
||||
assert volume_device is not None
|
||||
assert volume_device.via_device_id == hub_device.id
|
||||
|
||||
usb_partition_device = dev_reg.async_get_device_by_identifier(
|
||||
(DOMAIN, f"{SERIAL}_USB Disk 1 Partition 1"), entry_id
|
||||
)
|
||||
assert usb_partition_device is not None
|
||||
assert usb_partition_device.via_device_id == hub_device.id
|
||||
|
||||
Reference in New Issue
Block a user