Update error handling in update coordinator (#32452)

This commit is contained in:
Paulus Schoutsen 2020-03-04 08:05:46 -08:00 committed by GitHub
parent f62322cfb4
commit b27c46750c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 58 additions and 67 deletions

View file

@ -3,7 +3,6 @@ import asyncio
from datetime import timedelta from datetime import timedelta
import logging import logging
import aiohttp
import async_timeout import async_timeout
import coronavirus import coronavirus
@ -73,16 +72,13 @@ async def get_coordinator(hass):
return hass.data[DOMAIN] return hass.data[DOMAIN]
async def async_get_cases(): async def async_get_cases():
try: with async_timeout.timeout(10):
with async_timeout.timeout(10): return {
return { case.country: case
case.country: case for case in await coronavirus.get_cases(
for case in await coronavirus.get_cases( aiohttp_client.async_get_clientsession(hass)
aiohttp_client.async_get_clientsession(hass) )
) }
}
except (asyncio.TimeoutError, aiohttp.ClientError):
raise update_coordinator.UpdateFailed
hass.data[DOMAIN] = update_coordinator.DataUpdateCoordinator( hass.data[DOMAIN] = update_coordinator.DataUpdateCoordinator(
hass, hass,

View file

@ -1,11 +1,9 @@
"""Support for the Philips Hue lights.""" """Support for the Philips Hue lights."""
import asyncio
from datetime import timedelta from datetime import timedelta
from functools import partial from functools import partial
import logging import logging
import random import random
from aiohttp import client_exceptions
import aiohue import aiohue
import async_timeout import async_timeout
@ -172,13 +170,9 @@ async def async_safe_fetch(bridge, fetch_method):
return await bridge.async_request_call(fetch_method) return await bridge.async_request_call(fetch_method)
except aiohue.Unauthorized: except aiohue.Unauthorized:
await bridge.handle_unauthorized_error() await bridge.handle_unauthorized_error()
raise UpdateFailed raise UpdateFailed("Unauthorized")
except ( except (aiohue.AiohueException,) as err:
asyncio.TimeoutError, raise UpdateFailed(f"Hue error: {err}")
aiohue.AiohueException,
client_exceptions.ClientError,
):
raise UpdateFailed
@callback @callback

View file

@ -1,9 +1,7 @@
"""Support for the Philips Hue sensors as a platform.""" """Support for the Philips Hue sensors as a platform."""
import asyncio
from datetime import timedelta from datetime import timedelta
import logging import logging
from aiohttp import client_exceptions
from aiohue import AiohueException, Unauthorized from aiohue import AiohueException, Unauthorized
from aiohue.sensors import TYPE_ZLL_PRESENCE from aiohue.sensors import TYPE_ZLL_PRESENCE
import async_timeout import async_timeout
@ -60,9 +58,9 @@ class SensorManager:
) )
except Unauthorized: except Unauthorized:
await self.bridge.handle_unauthorized_error() await self.bridge.handle_unauthorized_error()
raise UpdateFailed raise UpdateFailed("Unauthorized")
except (asyncio.TimeoutError, AiohueException, client_exceptions.ClientError): except AiohueException as err:
raise UpdateFailed raise UpdateFailed(f"Hue error: {err}")
async def async_register_component(self, binary, async_add_entities): async def async_register_component(self, binary, async_add_entities):
"""Register async_add_entities methods for components.""" """Register async_add_entities methods for components."""

View file

@ -36,7 +36,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
try: try:
return await tankerkoenig.fetch_data() return await tankerkoenig.fetch_data()
except LookupError: except LookupError:
raise UpdateFailed raise UpdateFailed("Failed to fetch data")
coordinator = DataUpdateCoordinator( coordinator = DataUpdateCoordinator(
hass, hass,

View file

@ -1,12 +1,10 @@
"""Support to check for available updates.""" """Support to check for available updates."""
import asyncio
from datetime import timedelta from datetime import timedelta
from distutils.version import StrictVersion from distutils.version import StrictVersion
import json import json
import logging import logging
import uuid import uuid
import aiohttp
import async_timeout import async_timeout
from distro import linux_distribution # pylint: disable=import-error from distro import linux_distribution # pylint: disable=import-error
import voluptuous as vol import voluptuous as vol
@ -156,29 +154,27 @@ async def get_newest_version(hass, huuid, include_components):
info_object = {} info_object = {}
session = async_get_clientsession(hass) session = async_get_clientsession(hass)
try:
with async_timeout.timeout(5): with async_timeout.timeout(15):
req = await session.post(UPDATER_URL, json=info_object) req = await session.post(UPDATER_URL, json=info_object)
_LOGGER.info(
( _LOGGER.info(
"Submitted analytics to Home Assistant servers. " (
"Information submitted includes %s" "Submitted analytics to Home Assistant servers. "
), "Information submitted includes %s"
info_object, ),
) info_object,
except (asyncio.TimeoutError, aiohttp.ClientError): )
_LOGGER.error("Could not contact Home Assistant Update to check for updates")
raise update_coordinator.UpdateFailed
try: try:
res = await req.json() res = await req.json()
except ValueError: except ValueError:
_LOGGER.error("Received invalid JSON from Home Assistant Update") raise update_coordinator.UpdateFailed(
raise update_coordinator.UpdateFailed "Received invalid JSON from Home Assistant Update"
)
try: try:
res = RESPONSE_SCHEMA(res) res = RESPONSE_SCHEMA(res)
return res["version"], res["release-notes"] return res["version"], res["release-notes"]
except vol.Invalid: except vol.Invalid as err:
_LOGGER.error("Got unexpected response: %s", res) raise update_coordinator.UpdateFailed(f"Got unexpected response: {err}")
raise update_coordinator.UpdateFailed

View file

@ -5,6 +5,8 @@ import logging
from time import monotonic from time import monotonic
from typing import Any, Awaitable, Callable, List, Optional from typing import Any, Awaitable, Callable, List, Optional
import aiohttp
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow from homeassistant.util.dt import utcnow
@ -114,6 +116,16 @@ class DataUpdateCoordinator:
start = monotonic() start = monotonic()
self.data = await self.update_method() self.data = await self.update_method()
except asyncio.TimeoutError:
if self.last_update_success:
self.logger.error("Timeout fetching %s data", self.name)
self.last_update_success = False
except aiohttp.ClientError as err:
if self.last_update_success:
self.logger.error("Error requesting %s data: %s", self.name, err)
self.last_update_success = False
except UpdateFailed as err: except UpdateFailed as err:
if self.last_update_success: if self.last_update_success:
self.logger.error("Error fetching %s data: %s", self.name, err) self.logger.error("Error fetching %s data: %s", self.name, err)

View file

@ -1,5 +1,4 @@
"""The tests for the Updater component.""" """The tests for the Updater component."""
import asyncio
from unittest.mock import Mock from unittest.mock import Mock
from asynctest import patch from asynctest import patch
@ -130,17 +129,6 @@ async def test_get_newest_version_analytics_when_huuid(hass, aioclient_mock):
assert res == (MOCK_RESPONSE["version"], MOCK_RESPONSE["release-notes"]) assert res == (MOCK_RESPONSE["version"], MOCK_RESPONSE["release-notes"])
async def test_error_fetching_new_version_timeout(hass):
"""Test we handle timeout error while fetching new version."""
with patch(
"homeassistant.helpers.system_info.async_get_system_info",
Mock(return_value=mock_coro({"fake": "bla"})),
), patch("async_timeout.timeout", side_effect=asyncio.TimeoutError), pytest.raises(
UpdateFailed
):
await updater.get_newest_version(hass, MOCK_HUUID, False)
async def test_error_fetching_new_version_bad_json(hass, aioclient_mock): async def test_error_fetching_new_version_bad_json(hass, aioclient_mock):
"""Test we handle json error while fetching new version.""" """Test we handle json error while fetching new version."""
aioclient_mock.post(updater.UPDATER_URL, text="not json") aioclient_mock.post(updater.UPDATER_URL, text="not json")

View file

@ -1,7 +1,9 @@
"""Tests for the update coordinator.""" """Tests for the update coordinator."""
import asyncio
from datetime import timedelta from datetime import timedelta
import logging import logging
import aiohttp
from asynctest import CoroutineMock, Mock from asynctest import CoroutineMock, Mock
import pytest import pytest
@ -70,25 +72,30 @@ async def test_request_refresh(crd):
assert crd.last_update_success is True assert crd.last_update_success is True
async def test_refresh_fail(crd, caplog): @pytest.mark.parametrize(
"""Test a failing update function.""" "err_msg",
crd.update_method = CoroutineMock(side_effect=update_coordinator.UpdateFailed) [
(asyncio.TimeoutError, "Timeout fetching test data"),
(aiohttp.ClientError, "Error requesting test data"),
(update_coordinator.UpdateFailed, "Error fetching test data"),
],
)
async def test_refresh_known_errors(err_msg, crd, caplog):
"""Test raising known errors."""
crd.update_method = CoroutineMock(side_effect=err_msg[0])
await crd.async_refresh() await crd.async_refresh()
assert crd.data is None assert crd.data is None
assert crd.last_update_success is False assert crd.last_update_success is False
assert "Error fetching test data" in caplog.text assert err_msg[1] in caplog.text
crd.update_method = CoroutineMock(return_value=1)
async def test_refresh_fail_unknown(crd, caplog):
"""Test raising unknown error."""
await crd.async_refresh() await crd.async_refresh()
assert crd.data == 1
assert crd.last_update_success is True
crd.update_method = CoroutineMock(side_effect=ValueError) crd.update_method = CoroutineMock(side_effect=ValueError)
caplog.clear()
await crd.async_refresh() await crd.async_refresh()