Apply ConfigEntryNotReady improvements to PlatformNotReady (#48665)
* Apply ConfigEntryNotReady improvements to PlatformNotReady - Limit log spam #47201 - Log exception reason #48449 - Prevent startup blockage #48660 * coverage
This commit is contained in:
parent
ecec3c8ab9
commit
b5c679f3d0
2 changed files with 107 additions and 13 deletions
|
@ -9,9 +9,14 @@ from types import ModuleType
|
||||||
from typing import TYPE_CHECKING, Callable, Coroutine, Iterable
|
from typing import TYPE_CHECKING, Callable, Coroutine, Iterable
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import ATTR_RESTORED, DEVICE_DEFAULT_NAME
|
from homeassistant.const import (
|
||||||
|
ATTR_RESTORED,
|
||||||
|
DEVICE_DEFAULT_NAME,
|
||||||
|
EVENT_HOMEASSISTANT_STARTED,
|
||||||
|
)
|
||||||
from homeassistant.core import (
|
from homeassistant.core import (
|
||||||
CALLBACK_TYPE,
|
CALLBACK_TYPE,
|
||||||
|
CoreState,
|
||||||
HomeAssistant,
|
HomeAssistant,
|
||||||
ServiceCall,
|
ServiceCall,
|
||||||
callback,
|
callback,
|
||||||
|
@ -215,23 +220,41 @@ class EntityPlatform:
|
||||||
hass.config.components.add(full_name)
|
hass.config.components.add(full_name)
|
||||||
self._setup_complete = True
|
self._setup_complete = True
|
||||||
return True
|
return True
|
||||||
except PlatformNotReady:
|
except PlatformNotReady as ex:
|
||||||
tries += 1
|
tries += 1
|
||||||
wait_time = min(tries, 6) * PLATFORM_NOT_READY_BASE_WAIT_TIME
|
wait_time = min(tries, 6) * PLATFORM_NOT_READY_BASE_WAIT_TIME
|
||||||
|
message = str(ex)
|
||||||
|
if not message and ex.__cause__:
|
||||||
|
message = str(ex.__cause__)
|
||||||
|
ready_message = f"ready yet: {message}" if message else "ready yet"
|
||||||
|
if tries == 1:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Platform %s not ready yet. Retrying in %d seconds.",
|
"Platform %s not %s; Retrying in background in %d seconds",
|
||||||
self.platform_name,
|
self.platform_name,
|
||||||
|
ready_message,
|
||||||
|
wait_time,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.debug(
|
||||||
|
"Platform %s not %s; Retrying in %d seconds",
|
||||||
|
self.platform_name,
|
||||||
|
ready_message,
|
||||||
wait_time,
|
wait_time,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def setup_again(now): # type: ignore[no-untyped-def]
|
async def setup_again(*_): # type: ignore[no-untyped-def]
|
||||||
"""Run setup again."""
|
"""Run setup again."""
|
||||||
self._async_cancel_retry_setup = None
|
self._async_cancel_retry_setup = None
|
||||||
await self._async_setup_platform(async_create_setup_task, tries)
|
await self._async_setup_platform(async_create_setup_task, tries)
|
||||||
|
|
||||||
|
if hass.state == CoreState.running:
|
||||||
self._async_cancel_retry_setup = async_call_later(
|
self._async_cancel_retry_setup = async_call_later(
|
||||||
hass, wait_time, setup_again
|
hass, wait_time, setup_again
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
self._async_cancel_retry_setup = hass.bus.async_listen_once(
|
||||||
|
EVENT_HOMEASSISTANT_STARTED, setup_again
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
logger.error(
|
logger.error(
|
||||||
|
|
|
@ -6,8 +6,8 @@ from unittest.mock import Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.const import PERCENTAGE
|
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, PERCENTAGE
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import CoreState, callback
|
||||||
from homeassistant.exceptions import HomeAssistantError, PlatformNotReady
|
from homeassistant.exceptions import HomeAssistantError, PlatformNotReady
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
device_registry as dr,
|
device_registry as dr,
|
||||||
|
@ -592,6 +592,52 @@ async def test_setup_entry_platform_not_ready(hass, caplog):
|
||||||
assert len(mock_call_later.mock_calls) == 1
|
assert len(mock_call_later.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_entry_platform_not_ready_with_message(hass, caplog):
|
||||||
|
"""Test when an entry is not ready yet that includes a message."""
|
||||||
|
async_setup_entry = Mock(side_effect=PlatformNotReady("lp0 on fire"))
|
||||||
|
platform = MockPlatform(async_setup_entry=async_setup_entry)
|
||||||
|
config_entry = MockConfigEntry()
|
||||||
|
ent_platform = MockEntityPlatform(
|
||||||
|
hass, platform_name=config_entry.domain, platform=platform
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(entity_platform, "async_call_later") as mock_call_later:
|
||||||
|
assert not await ent_platform.async_setup_entry(config_entry)
|
||||||
|
|
||||||
|
full_name = f"{ent_platform.domain}.{config_entry.domain}"
|
||||||
|
assert full_name not in hass.config.components
|
||||||
|
assert len(async_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
assert "Platform test not ready yet" in caplog.text
|
||||||
|
assert "lp0 on fire" in caplog.text
|
||||||
|
assert len(mock_call_later.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_setup_entry_platform_not_ready_from_exception(hass, caplog):
|
||||||
|
"""Test when an entry is not ready yet that includes the causing exception string."""
|
||||||
|
original_exception = HomeAssistantError("The device dropped the connection")
|
||||||
|
platform_exception = PlatformNotReady()
|
||||||
|
platform_exception.__cause__ = original_exception
|
||||||
|
|
||||||
|
async_setup_entry = Mock(side_effect=platform_exception)
|
||||||
|
platform = MockPlatform(async_setup_entry=async_setup_entry)
|
||||||
|
config_entry = MockConfigEntry()
|
||||||
|
ent_platform = MockEntityPlatform(
|
||||||
|
hass, platform_name=config_entry.domain, platform=platform
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(entity_platform, "async_call_later") as mock_call_later:
|
||||||
|
assert not await ent_platform.async_setup_entry(config_entry)
|
||||||
|
|
||||||
|
full_name = f"{ent_platform.domain}.{config_entry.domain}"
|
||||||
|
assert full_name not in hass.config.components
|
||||||
|
assert len(async_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
assert "Platform test not ready yet" in caplog.text
|
||||||
|
assert "The device dropped the connection" in caplog.text
|
||||||
|
assert len(mock_call_later.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_reset_cancels_retry_setup(hass):
|
async def test_reset_cancels_retry_setup(hass):
|
||||||
"""Test that resetting a platform will cancel scheduled a setup retry."""
|
"""Test that resetting a platform will cancel scheduled a setup retry."""
|
||||||
async_setup_entry = Mock(side_effect=PlatformNotReady)
|
async_setup_entry = Mock(side_effect=PlatformNotReady)
|
||||||
|
@ -614,6 +660,31 @@ async def test_reset_cancels_retry_setup(hass):
|
||||||
assert ent_platform._async_cancel_retry_setup is None
|
assert ent_platform._async_cancel_retry_setup is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_reset_cancels_retry_setup_when_not_started(hass):
|
||||||
|
"""Test that resetting a platform will cancel scheduled a setup retry when not yet started."""
|
||||||
|
hass.state = CoreState.starting
|
||||||
|
async_setup_entry = Mock(side_effect=PlatformNotReady)
|
||||||
|
initial_listeners = hass.bus.async_listeners()[EVENT_HOMEASSISTANT_STARTED]
|
||||||
|
|
||||||
|
platform = MockPlatform(async_setup_entry=async_setup_entry)
|
||||||
|
config_entry = MockConfigEntry()
|
||||||
|
ent_platform = MockEntityPlatform(
|
||||||
|
hass, platform_name=config_entry.domain, platform=platform
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not await ent_platform.async_setup_entry(config_entry)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert (
|
||||||
|
hass.bus.async_listeners()[EVENT_HOMEASSISTANT_STARTED] == initial_listeners + 1
|
||||||
|
)
|
||||||
|
assert ent_platform._async_cancel_retry_setup is not None
|
||||||
|
|
||||||
|
await ent_platform.async_reset()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert hass.bus.async_listeners()[EVENT_HOMEASSISTANT_STARTED] == initial_listeners
|
||||||
|
assert ent_platform._async_cancel_retry_setup is None
|
||||||
|
|
||||||
|
|
||||||
async def test_not_fails_with_adding_empty_entities_(hass):
|
async def test_not_fails_with_adding_empty_entities_(hass):
|
||||||
"""Test for not fails on empty entities list."""
|
"""Test for not fails on empty entities list."""
|
||||||
component = EntityComponent(_LOGGER, DOMAIN, hass)
|
component = EntityComponent(_LOGGER, DOMAIN, hass)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue