Ensure we do not start discovered flows until after the started event has fired (#38047)

* Ensure we do not start discovered flows until after the start event has fired

This change makes zeroconf and ssdp match discovery behavior of not
creating config flows until the start event has been fired.  This
prevents config flow creation/dependency installs for discovered
config flows from competing for cpu time during startup.

* Start discovery/service browser/ssdp when EVENT_HOMEASSISTANT_STARTED is fired instead of EVENT_HOMEASSISTANT_START
This commit is contained in:
J. Nick Koston 2020-07-21 14:18:43 -10:00 committed by GitHub
parent e766a119d2
commit 5cf7b1b1bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 43 additions and 10 deletions

View File

@ -15,7 +15,7 @@ import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components import zeroconf from homeassistant.components import zeroconf
from homeassistant.const import EVENT_HOMEASSISTANT_START from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
from homeassistant.core import callback from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import async_discover, async_load_platform from homeassistant.helpers.discovery import async_discover, async_load_platform
@ -209,7 +209,7 @@ async def async_setup(hass, config):
"""Schedule the first discovery when Home Assistant starts up.""" """Schedule the first discovery when Home Assistant starts up."""
async_track_point_in_utc_time(hass, scan_devices, dt_util.utcnow()) async_track_point_in_utc_time(hass, scan_devices, dt_util.utcnow())
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, schedule_first) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, schedule_first)
return True return True

View File

@ -7,6 +7,7 @@ import aiohttp
from defusedxml import ElementTree from defusedxml import ElementTree
from netdisco import ssdp, util from netdisco import ssdp, util
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
from homeassistant.generated.ssdp import SSDP from homeassistant.generated.ssdp import SSDP
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
@ -33,12 +34,12 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup(hass, config): async def async_setup(hass, config):
"""Set up the SSDP integration.""" """Set up the SSDP integration."""
async def initialize(): async def initialize(_):
scanner = Scanner(hass) scanner = Scanner(hass)
await scanner.async_scan(None) await scanner.async_scan(None)
async_track_time_interval(hass, scanner.async_scan, SCAN_INTERVAL) async_track_time_interval(hass, scanner.async_scan, SCAN_INTERVAL)
hass.loop.create_task(initialize()) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, initialize)
return True return True

View File

@ -21,6 +21,7 @@ from homeassistant import util
from homeassistant.const import ( from homeassistant.const import (
ATTR_NAME, ATTR_NAME,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STARTED,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
__version__, __version__,
) )
@ -247,8 +248,14 @@ def setup(hass, config):
if HOMEKIT_TYPE not in ZEROCONF: if HOMEKIT_TYPE not in ZEROCONF:
types.append(HOMEKIT_TYPE) types.append(HOMEKIT_TYPE)
def zeroconf_hass_started(_event):
"""Start the service browser."""
_LOGGER.debug("Starting Zeroconf browser")
HaServiceBrowser(zeroconf, types, handlers=[service_update]) HaServiceBrowser(zeroconf, types, handlers=[service_update])
hass.bus.listen_once(EVENT_HOMEASSISTANT_STARTED, zeroconf_hass_started)
return True return True

View File

@ -6,6 +6,7 @@ import pytest
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.bootstrap import async_setup_component from homeassistant.bootstrap import async_setup_component
from homeassistant.components import discovery from homeassistant.components import discovery
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
from homeassistant.util.dt import utcnow from homeassistant.util.dt import utcnow
from tests.async_mock import patch from tests.async_mock import patch
@ -36,10 +37,12 @@ def netdisco_mock():
async def mock_discovery(hass, discoveries, config=BASE_CONFIG): async def mock_discovery(hass, discoveries, config=BASE_CONFIG):
"""Mock discoveries.""" """Mock discoveries."""
result = await async_setup_component(hass, "discovery", config) with patch("homeassistant.components.zeroconf.async_get_instance"):
assert result assert await async_setup_component(hass, "discovery", config)
await hass.async_block_till_done()
await hass.async_start() await hass.async_start()
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
with patch.object(discovery, "_discover", discoveries), patch( with patch.object(discovery, "_discover", discoveries), patch(
"homeassistant.components.discovery.async_discover", return_value=mock_coro() "homeassistant.components.discovery.async_discover", return_value=mock_coro()

View File

@ -4,7 +4,7 @@ from zeroconf import InterfaceChoice, IPVersion, ServiceInfo, ServiceStateChange
from homeassistant.components import zeroconf from homeassistant.components import zeroconf
from homeassistant.components.zeroconf import CONF_DEFAULT_INTERFACE, CONF_IPV6 from homeassistant.components.zeroconf import CONF_DEFAULT_INTERFACE, CONF_IPV6
from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP
from homeassistant.generated import zeroconf as zc_gen from homeassistant.generated import zeroconf as zc_gen
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
@ -76,6 +76,8 @@ async def test_setup(hass, mock_zeroconf):
) as mock_service_browser: ) as mock_service_browser:
mock_zeroconf.get_service_info.side_effect = get_service_info_mock mock_zeroconf.get_service_info.side_effect = get_service_info_mock
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
assert len(mock_service_browser.mock_calls) == 1 assert len(mock_service_browser.mock_calls) == 1
expected_flow_calls = 0 expected_flow_calls = 0
@ -97,6 +99,8 @@ async def test_setup_with_default_interface(hass, mock_zeroconf):
assert await async_setup_component( assert await async_setup_component(
hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {CONF_DEFAULT_INTERFACE: True}} hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {CONF_DEFAULT_INTERFACE: True}}
) )
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
assert mock_zeroconf.called_with(interface_choice=InterfaceChoice.Default) assert mock_zeroconf.called_with(interface_choice=InterfaceChoice.Default)
@ -123,6 +127,8 @@ async def test_setup_without_ipv6(hass, mock_zeroconf):
assert await async_setup_component( assert await async_setup_component(
hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {CONF_IPV6: False}} hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {CONF_IPV6: False}}
) )
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
assert mock_zeroconf.called_with(ip_version=IPVersion.V4Only) assert mock_zeroconf.called_with(ip_version=IPVersion.V4Only)
@ -136,6 +142,8 @@ async def test_setup_with_ipv6(hass, mock_zeroconf):
assert await async_setup_component( assert await async_setup_component(
hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {CONF_IPV6: True}} hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {CONF_IPV6: True}}
) )
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
assert mock_zeroconf.called_with() assert mock_zeroconf.called_with()
@ -147,6 +155,8 @@ async def test_setup_with_ipv6_default(hass, mock_zeroconf):
): ):
mock_zeroconf.get_service_info.side_effect = get_service_info_mock mock_zeroconf.get_service_info.side_effect = get_service_info_mock
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
assert mock_zeroconf.called_with() assert mock_zeroconf.called_with()
@ -164,6 +174,8 @@ async def test_homekit_match_partial_space(hass, mock_zeroconf):
"LIFX bulb", HOMEKIT_STATUS_UNPAIRED "LIFX bulb", HOMEKIT_STATUS_UNPAIRED
) )
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
assert len(mock_service_browser.mock_calls) == 1 assert len(mock_service_browser.mock_calls) == 1
assert len(mock_config_flow.mock_calls) == 1 assert len(mock_config_flow.mock_calls) == 1
@ -183,6 +195,8 @@ async def test_homekit_match_partial_dash(hass, mock_zeroconf):
"Rachio-fa46ba", HOMEKIT_STATUS_UNPAIRED "Rachio-fa46ba", HOMEKIT_STATUS_UNPAIRED
) )
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
assert len(mock_service_browser.mock_calls) == 1 assert len(mock_service_browser.mock_calls) == 1
assert len(mock_config_flow.mock_calls) == 1 assert len(mock_config_flow.mock_calls) == 1
@ -202,6 +216,8 @@ async def test_homekit_match_full(hass, mock_zeroconf):
"BSB002", HOMEKIT_STATUS_UNPAIRED "BSB002", HOMEKIT_STATUS_UNPAIRED
) )
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
homekit_mock = get_homekit_info_mock("BSB002", HOMEKIT_STATUS_UNPAIRED) homekit_mock = get_homekit_info_mock("BSB002", HOMEKIT_STATUS_UNPAIRED)
info = homekit_mock("_hap._tcp.local.", "BSB002._hap._tcp.local.") info = homekit_mock("_hap._tcp.local.", "BSB002._hap._tcp.local.")
@ -226,6 +242,8 @@ async def test_homekit_already_paired(hass, mock_zeroconf):
"tado", HOMEKIT_STATUS_PAIRED "tado", HOMEKIT_STATUS_PAIRED
) )
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
assert len(mock_service_browser.mock_calls) == 1 assert len(mock_service_browser.mock_calls) == 1
assert len(mock_config_flow.mock_calls) == 2 assert len(mock_config_flow.mock_calls) == 2
@ -246,6 +264,8 @@ async def test_homekit_invalid_paring_status(hass, mock_zeroconf):
"tado", b"invalid" "tado", b"invalid"
) )
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
assert len(mock_service_browser.mock_calls) == 1 assert len(mock_service_browser.mock_calls) == 1
assert len(mock_config_flow.mock_calls) == 1 assert len(mock_config_flow.mock_calls) == 1
@ -265,6 +285,8 @@ async def test_homekit_not_paired(hass, mock_zeroconf):
"this_will_not_match_any_integration", HOMEKIT_STATUS_UNPAIRED "this_will_not_match_any_integration", HOMEKIT_STATUS_UNPAIRED
) )
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}}) assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
assert len(mock_service_browser.mock_calls) == 1 assert len(mock_service_browser.mock_calls) == 1
assert len(mock_config_flow.mock_calls) == 1 assert len(mock_config_flow.mock_calls) == 1