Allow ZHA startup to fail instead of raising ConfigEntryNotReady (#77417)

* Retry startup within ZHA instead of raising `ConfigEntryNotReady`

* Add unit tests

* Disable pylint warning for intentional broad except
This commit is contained in:
puddly 2022-08-29 11:42:01 -04:00 committed by GitHub
parent 795691038f
commit 2e8d598795
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 76 additions and 14 deletions

View File

@ -408,3 +408,7 @@ class Strobe(t.enum8):
No_Strobe = 0x00
Strobe = 0x01
STARTUP_FAILURE_DELAY_S = 3
STARTUP_RETRIES = 3

View File

@ -13,7 +13,6 @@ import time
import traceback
from typing import TYPE_CHECKING, Any, NamedTuple, Union
from serial import SerialException
from zigpy.application import ControllerApplication
from zigpy.config import CONF_DEVICE
import zigpy.device
@ -25,7 +24,6 @@ from homeassistant import __path__ as HOMEASSISTANT_PATH
from homeassistant.components.system_log import LogEntry, _figure_out_source
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity import DeviceInfo
@ -62,6 +60,8 @@ from .const import (
SIGNAL_ADD_ENTITIES,
SIGNAL_GROUP_MEMBERSHIP_CHANGE,
SIGNAL_REMOVE,
STARTUP_FAILURE_DELAY_S,
STARTUP_RETRIES,
UNKNOWN_MANUFACTURER,
UNKNOWN_MODEL,
ZHA_GW_MSG,
@ -166,17 +166,27 @@ class ZHAGateway:
app_config[CONF_DEVICE] = self.config_entry.data[CONF_DEVICE]
app_config = app_controller_cls.SCHEMA(app_config)
for attempt in range(STARTUP_RETRIES):
try:
self.application_controller = await app_controller_cls.new(
app_config, auto_form=True, start_radio=True
)
except (asyncio.TimeoutError, SerialException, OSError) as exception:
_LOGGER.error(
"Couldn't start %s coordinator",
except Exception as exc: # pylint: disable=broad-except
_LOGGER.warning(
"Couldn't start %s coordinator (attempt %s of %s)",
self.radio_description,
exc_info=exception,
attempt + 1,
STARTUP_RETRIES,
exc_info=exc,
)
raise ConfigEntryNotReady from exception
if attempt == STARTUP_RETRIES - 1:
raise exc
await asyncio.sleep(STARTUP_FAILURE_DELAY_S)
else:
break
self.application_controller.add_listener(self)
self.application_controller.groups.add_listener(self)

View File

@ -1,6 +1,6 @@
"""Test ZHA Gateway."""
import asyncio
from unittest.mock import patch
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
import zigpy.profiles.zha as zha
@ -211,3 +211,51 @@ async def test_gateway_create_group_with_id(hass, device_light_1, coordinator):
assert len(zha_group.members) == 1
assert zha_group.members[0].device is device_light_1
assert zha_group.group_id == 0x1234
@patch(
"homeassistant.components.zha.core.gateway.ZHAGateway.async_load_devices",
MagicMock(),
)
@patch(
"homeassistant.components.zha.core.gateway.ZHAGateway.async_load_groups",
MagicMock(),
)
@patch("homeassistant.components.zha.core.gateway.STARTUP_FAILURE_DELAY_S", 0.01)
@pytest.mark.parametrize(
"startup",
[
[asyncio.TimeoutError(), FileNotFoundError(), MagicMock()],
[asyncio.TimeoutError(), MagicMock()],
[MagicMock()],
],
)
async def test_gateway_initialize_success(startup, hass, device_light_1, coordinator):
"""Test ZHA initializing the gateway successfully."""
zha_gateway = get_zha_gateway(hass)
assert zha_gateway is not None
zha_gateway.shutdown = AsyncMock()
with patch(
"bellows.zigbee.application.ControllerApplication.new", side_effect=startup
) as mock_new:
await zha_gateway.async_initialize()
assert mock_new.call_count == len(startup)
@patch("homeassistant.components.zha.core.gateway.STARTUP_FAILURE_DELAY_S", 0.01)
async def test_gateway_initialize_failure(hass, device_light_1, coordinator):
"""Test ZHA failing to initialize the gateway."""
zha_gateway = get_zha_gateway(hass)
assert zha_gateway is not None
with patch(
"bellows.zigbee.application.ControllerApplication.new",
side_effect=[asyncio.TimeoutError(), FileNotFoundError(), RuntimeError()],
) as mock_new:
with pytest.raises(RuntimeError):
await zha_gateway.async_initialize()
assert mock_new.call_count == 3