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:
J. Nick Koston 2021-04-04 00:31:58 -10:00 committed by GitHub
parent ecec3c8ab9
commit b5c679f3d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 107 additions and 13 deletions

View File

@ -9,9 +9,14 @@ from types import ModuleType
from typing import TYPE_CHECKING, Callable, Coroutine, Iterable
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 (
CALLBACK_TYPE,
CoreState,
HomeAssistant,
ServiceCall,
callback,
@ -215,23 +220,41 @@ class EntityPlatform:
hass.config.components.add(full_name)
self._setup_complete = True
return True
except PlatformNotReady:
except PlatformNotReady as ex:
tries += 1
wait_time = min(tries, 6) * PLATFORM_NOT_READY_BASE_WAIT_TIME
logger.warning(
"Platform %s not ready yet. Retrying in %d seconds.",
self.platform_name,
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(
"Platform %s not %s; Retrying in background in %d seconds",
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,
)
async def setup_again(now): # type: ignore[no-untyped-def]
async def setup_again(*_): # type: ignore[no-untyped-def]
"""Run setup again."""
self._async_cancel_retry_setup = None
await self._async_setup_platform(async_create_setup_task, tries)
self._async_cancel_retry_setup = async_call_later(
hass, wait_time, setup_again
)
if hass.state == CoreState.running:
self._async_cancel_retry_setup = async_call_later(
hass, wait_time, setup_again
)
else:
self._async_cancel_retry_setup = hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STARTED, setup_again
)
return False
except asyncio.TimeoutError:
logger.error(

View File

@ -6,8 +6,8 @@ from unittest.mock import Mock, patch
import pytest
from homeassistant.const import PERCENTAGE
from homeassistant.core import callback
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, PERCENTAGE
from homeassistant.core import CoreState, callback
from homeassistant.exceptions import HomeAssistantError, PlatformNotReady
from homeassistant.helpers import (
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
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):
"""Test that resetting a platform will cancel scheduled a setup retry."""
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
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):
"""Test for not fails on empty entities list."""
component = EntityComponent(_LOGGER, DOMAIN, hass)