Retry lifx setup later if device has an unexpected serial (#98783)

This commit is contained in:
J. Nick Koston 2023-08-22 10:17:15 -05:00 committed by GitHub
parent 426fd62ee3
commit d0fc0aea40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 52 additions and 23 deletions

View file

@ -30,7 +30,7 @@ from .coordinator import LIFXUpdateCoordinator
from .discovery import async_discover_devices, async_trigger_discovery from .discovery import async_discover_devices, async_trigger_discovery
from .manager import LIFXManager from .manager import LIFXManager
from .migration import async_migrate_entities_devices, async_migrate_legacy_entries from .migration import async_migrate_entities_devices, async_migrate_legacy_entries
from .util import async_entry_is_legacy, async_get_legacy_entry from .util import async_entry_is_legacy, async_get_legacy_entry, formatted_serial
CONF_SERVER = "server" CONF_SERVER = "server"
CONF_BROADCAST = "broadcast" CONF_BROADCAST = "broadcast"
@ -218,6 +218,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
connection.async_stop() connection.async_stop()
raise raise
serial = formatted_serial(coordinator.serial_number)
if serial != entry.unique_id:
# If the serial number of the device does not match the unique_id
# of the config entry, it likely means the DHCP lease has expired
# and the device has been assigned a new IP address. We need to
# wait for the next discovery to find the device at its new address
# and update the config entry so we do not mix up devices.
raise ConfigEntryNotReady(
f"Unexpected device found at {host}; expected {entry.unique_id}, found {serial}"
)
domain_data[entry.entry_id] = coordinator domain_data[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True

View file

@ -21,7 +21,6 @@ from homeassistant.util import dt as dt_util
from . import ( from . import (
DEFAULT_ENTRY_TITLE, DEFAULT_ENTRY_TITLE,
IP_ADDRESS, IP_ADDRESS,
MAC_ADDRESS,
SERIAL, SERIAL,
_mocked_clean_bulb, _mocked_clean_bulb,
_patch_config_flow_try_connect, _patch_config_flow_try_connect,
@ -38,7 +37,7 @@ async def test_hev_cycle_state(hass: HomeAssistant) -> None:
domain=lifx.DOMAIN, domain=lifx.DOMAIN,
title=DEFAULT_ENTRY_TITLE, title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS}, data={CONF_HOST: IP_ADDRESS},
unique_id=MAC_ADDRESS, unique_id=SERIAL,
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
bulb = _mocked_clean_bulb() bulb = _mocked_clean_bulb()

View file

@ -14,7 +14,6 @@ from homeassistant.setup import async_setup_component
from . import ( from . import (
DEFAULT_ENTRY_TITLE, DEFAULT_ENTRY_TITLE,
IP_ADDRESS, IP_ADDRESS,
MAC_ADDRESS,
SERIAL, SERIAL,
_mocked_bulb, _mocked_bulb,
_patch_config_flow_try_connect, _patch_config_flow_try_connect,
@ -38,7 +37,7 @@ async def test_button_restart(hass: HomeAssistant) -> None:
domain=DOMAIN, domain=DOMAIN,
title=DEFAULT_ENTRY_TITLE, title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS}, data={CONF_HOST: IP_ADDRESS},
unique_id=MAC_ADDRESS, unique_id=SERIAL,
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
bulb = _mocked_bulb() bulb = _mocked_bulb()
@ -70,7 +69,7 @@ async def test_button_identify(hass: HomeAssistant) -> None:
domain=DOMAIN, domain=DOMAIN,
title=DEFAULT_ENTRY_TITLE, title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS}, data={CONF_HOST: IP_ADDRESS},
unique_id=MAC_ADDRESS, unique_id=SERIAL,
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
bulb = _mocked_bulb() bulb = _mocked_bulb()

View file

@ -7,7 +7,7 @@ from homeassistant.setup import async_setup_component
from . import ( from . import (
DEFAULT_ENTRY_TITLE, DEFAULT_ENTRY_TITLE,
IP_ADDRESS, IP_ADDRESS,
MAC_ADDRESS, SERIAL,
_mocked_bulb, _mocked_bulb,
_mocked_clean_bulb, _mocked_clean_bulb,
_mocked_infrared_bulb, _mocked_infrared_bulb,
@ -30,7 +30,7 @@ async def test_bulb_diagnostics(
domain=lifx.DOMAIN, domain=lifx.DOMAIN,
title=DEFAULT_ENTRY_TITLE, title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS}, data={CONF_HOST: IP_ADDRESS},
unique_id=MAC_ADDRESS, unique_id=SERIAL,
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
bulb = _mocked_bulb() bulb = _mocked_bulb()
@ -77,7 +77,7 @@ async def test_clean_bulb_diagnostics(
domain=lifx.DOMAIN, domain=lifx.DOMAIN,
title=DEFAULT_ENTRY_TITLE, title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS}, data={CONF_HOST: IP_ADDRESS},
unique_id=MAC_ADDRESS, unique_id=SERIAL,
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
bulb = _mocked_clean_bulb() bulb = _mocked_clean_bulb()
@ -129,7 +129,7 @@ async def test_infrared_bulb_diagnostics(
domain=lifx.DOMAIN, domain=lifx.DOMAIN,
title=DEFAULT_ENTRY_TITLE, title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS}, data={CONF_HOST: IP_ADDRESS},
unique_id=MAC_ADDRESS, unique_id=SERIAL,
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
bulb = _mocked_infrared_bulb() bulb = _mocked_infrared_bulb()
@ -177,7 +177,7 @@ async def test_legacy_multizone_bulb_diagnostics(
domain=lifx.DOMAIN, domain=lifx.DOMAIN,
title=DEFAULT_ENTRY_TITLE, title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS}, data={CONF_HOST: IP_ADDRESS},
unique_id=MAC_ADDRESS, unique_id=SERIAL,
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
bulb = _mocked_light_strip() bulb = _mocked_light_strip()
@ -288,7 +288,7 @@ async def test_multizone_bulb_diagnostics(
domain=lifx.DOMAIN, domain=lifx.DOMAIN,
title=DEFAULT_ENTRY_TITLE, title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS}, data={CONF_HOST: IP_ADDRESS},
unique_id=MAC_ADDRESS, unique_id=SERIAL,
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
bulb = _mocked_light_strip() bulb = _mocked_light_strip()

View file

@ -5,6 +5,8 @@ from datetime import timedelta
import socket import socket
from unittest.mock import patch from unittest.mock import patch
import pytest
from homeassistant.components import lifx from homeassistant.components import lifx
from homeassistant.components.lifx import DOMAIN, discovery from homeassistant.components.lifx import DOMAIN, discovery
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
@ -149,3 +151,23 @@ async def test_dns_error_at_startup(hass: HomeAssistant) -> None:
await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
await hass.async_block_till_done() await hass.async_block_till_done()
assert already_migrated_config_entry.state == ConfigEntryState.SETUP_RETRY assert already_migrated_config_entry.state == ConfigEntryState.SETUP_RETRY
async def test_config_entry_wrong_serial(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test config entry enters setup retry when serial mismatches."""
mismatched_serial = f"{SERIAL[:-1]}0"
already_migrated_config_entry = MockConfigEntry(
domain=DOMAIN, data={CONF_HOST: "127.0.0.1"}, unique_id=mismatched_serial
)
already_migrated_config_entry.add_to_hass(hass)
with _patch_discovery(), _patch_config_flow_try_connect(), _patch_device():
await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}})
await hass.async_block_till_done()
assert already_migrated_config_entry.state == ConfigEntryState.SETUP_RETRY
assert (
"Unexpected device found at 127.0.0.1; expected aa:bb:cc:dd:ee:c0, found aa:bb:cc:dd:ee:cc"
in caplog.text
)

View file

@ -13,7 +13,6 @@ from homeassistant.util import dt as dt_util
from . import ( from . import (
DEFAULT_ENTRY_TITLE, DEFAULT_ENTRY_TITLE,
IP_ADDRESS, IP_ADDRESS,
MAC_ADDRESS,
SERIAL, SERIAL,
MockLifxCommand, MockLifxCommand,
_mocked_infrared_bulb, _mocked_infrared_bulb,
@ -32,7 +31,7 @@ async def test_theme_select(hass: HomeAssistant) -> None:
domain=DOMAIN, domain=DOMAIN,
title=DEFAULT_ENTRY_TITLE, title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS}, data={CONF_HOST: IP_ADDRESS},
unique_id=MAC_ADDRESS, unique_id=SERIAL,
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
bulb = _mocked_light_strip() bulb = _mocked_light_strip()
@ -70,7 +69,7 @@ async def test_infrared_brightness(hass: HomeAssistant) -> None:
domain=DOMAIN, domain=DOMAIN,
title=DEFAULT_ENTRY_TITLE, title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS}, data={CONF_HOST: IP_ADDRESS},
unique_id=MAC_ADDRESS, unique_id=SERIAL,
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
bulb = _mocked_infrared_bulb() bulb = _mocked_infrared_bulb()
@ -100,7 +99,7 @@ async def test_set_infrared_brightness_25_percent(hass: HomeAssistant) -> None:
domain=DOMAIN, domain=DOMAIN,
title=DEFAULT_ENTRY_TITLE, title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS}, data={CONF_HOST: IP_ADDRESS},
unique_id=MAC_ADDRESS, unique_id=SERIAL,
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
bulb = _mocked_infrared_bulb() bulb = _mocked_infrared_bulb()
@ -139,7 +138,7 @@ async def test_set_infrared_brightness_50_percent(hass: HomeAssistant) -> None:
domain=DOMAIN, domain=DOMAIN,
title=DEFAULT_ENTRY_TITLE, title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS}, data={CONF_HOST: IP_ADDRESS},
unique_id=MAC_ADDRESS, unique_id=SERIAL,
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
bulb = _mocked_infrared_bulb() bulb = _mocked_infrared_bulb()
@ -178,7 +177,7 @@ async def test_set_infrared_brightness_100_percent(hass: HomeAssistant) -> None:
domain=DOMAIN, domain=DOMAIN,
title=DEFAULT_ENTRY_TITLE, title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS}, data={CONF_HOST: IP_ADDRESS},
unique_id=MAC_ADDRESS, unique_id=SERIAL,
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
bulb = _mocked_infrared_bulb() bulb = _mocked_infrared_bulb()
@ -217,7 +216,7 @@ async def test_disable_infrared(hass: HomeAssistant) -> None:
domain=DOMAIN, domain=DOMAIN,
title=DEFAULT_ENTRY_TITLE, title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS}, data={CONF_HOST: IP_ADDRESS},
unique_id=MAC_ADDRESS, unique_id=SERIAL,
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
bulb = _mocked_infrared_bulb() bulb = _mocked_infrared_bulb()
@ -256,7 +255,7 @@ async def test_invalid_infrared_brightness(hass: HomeAssistant) -> None:
domain=DOMAIN, domain=DOMAIN,
title=DEFAULT_ENTRY_TITLE, title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS}, data={CONF_HOST: IP_ADDRESS},
unique_id=MAC_ADDRESS, unique_id=SERIAL,
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
bulb = _mocked_infrared_bulb() bulb = _mocked_infrared_bulb()

View file

@ -20,7 +20,7 @@ from homeassistant.util import dt as dt_util
from . import ( from . import (
DEFAULT_ENTRY_TITLE, DEFAULT_ENTRY_TITLE,
IP_ADDRESS, IP_ADDRESS,
MAC_ADDRESS, SERIAL,
_mocked_bulb, _mocked_bulb,
_mocked_bulb_old_firmware, _mocked_bulb_old_firmware,
_patch_config_flow_try_connect, _patch_config_flow_try_connect,
@ -38,7 +38,7 @@ async def test_rssi_sensor(hass: HomeAssistant) -> None:
domain=lifx.DOMAIN, domain=lifx.DOMAIN,
title=DEFAULT_ENTRY_TITLE, title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS}, data={CONF_HOST: IP_ADDRESS},
unique_id=MAC_ADDRESS, unique_id=SERIAL,
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
bulb = _mocked_bulb() bulb = _mocked_bulb()
@ -89,7 +89,7 @@ async def test_rssi_sensor_old_firmware(hass: HomeAssistant) -> None:
domain=lifx.DOMAIN, domain=lifx.DOMAIN,
title=DEFAULT_ENTRY_TITLE, title=DEFAULT_ENTRY_TITLE,
data={CONF_HOST: IP_ADDRESS}, data={CONF_HOST: IP_ADDRESS},
unique_id=MAC_ADDRESS, unique_id=SERIAL,
) )
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
bulb = _mocked_bulb_old_firmware() bulb = _mocked_bulb_old_firmware()