Retry keyset cloud (#12270)

* Use less threads in helpers.event tests

* Add helpers.event.async_call_later

* Cloud: retry fetching keyset
pull/12267/head
Paulus Schoutsen 2018-02-10 02:40:24 -08:00
parent a9412d27aa
commit aad26599ae
6 changed files with 66 additions and 40 deletions

View File

@ -16,8 +16,7 @@ import voluptuous as vol
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE, CONF_NAME, CONF_TYPE)
from homeassistant.helpers import entityfilter
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import entityfilter, config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util import dt as dt_util
from homeassistant.components.alexa import smart_home as alexa_sh
@ -105,12 +104,7 @@ def async_setup(hass, config):
)
cloud = hass.data[DOMAIN] = Cloud(hass, **kwargs)
success = yield from cloud.initialize()
if not success:
return False
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, cloud.async_start)
yield from http_api.async_setup(hass)
return True
@ -192,19 +186,6 @@ class Cloud:
return self._gactions_config
@asyncio.coroutine
def initialize(self):
"""Initialize and load cloud info."""
jwt_success = yield from self._fetch_jwt_keyset()
if not jwt_success:
return False
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, self._start_cloud)
return True
def path(self, *parts):
"""Get config path inside cloud dir.
@ -234,8 +215,18 @@ class Cloud:
'refresh_token': self.refresh_token,
}, indent=4))
def _start_cloud(self, event):
@asyncio.coroutine
def async_start(self, _):
"""Start the cloud component."""
success = yield from self._fetch_jwt_keyset()
# Fetching keyset can fail if internet is not up yet.
if not success:
self.hass.helpers.async_call_later(5, self.async_start)
return
def load_config():
"""Load config."""
# Ensure config dir exists
path = self.hass.config.path(CONFIG_DIR)
if not os.path.isdir(path):
@ -243,10 +234,15 @@ class Cloud:
user_info = self.user_info_path
if not os.path.isfile(user_info):
return
return None
with open(user_info, 'rt') as file:
info = json.loads(file.read())
return json.loads(file.read())
info = yield from self.hass.async_add_job(load_config)
if info is None:
return
# Validate tokens
try:

View File

@ -1,4 +1,5 @@
"""Helpers for listening to events."""
from datetime import timedelta
import functools as ft
from homeassistant.loader import bind_hass
@ -219,6 +220,14 @@ track_point_in_utc_time = threaded_listener_factory(
async_track_point_in_utc_time)
@callback
@bind_hass
def async_call_later(hass, delay, action):
"""Add a listener that is called in <delay>."""
return async_track_point_in_utc_time(
hass, action, dt_util.utcnow() + timedelta(seconds=delay))
@callback
@bind_hass
def async_track_time_interval(hass, action, interval):

View File

@ -14,8 +14,8 @@ from tests.common import mock_coro
@pytest.fixture
def cloud_client(hass, test_client):
"""Fixture that can fetch from the cloud client."""
with patch('homeassistant.components.cloud.Cloud.initialize',
return_value=mock_coro(True)):
with patch('homeassistant.components.cloud.Cloud.async_start',
return_value=mock_coro()):
hass.loop.run_until_complete(async_setup_component(hass, 'cloud', {
'cloud': {
'mode': 'development',

View File

@ -87,7 +87,7 @@ def test_initialize_loads_info(mock_os, hass):
with patch('homeassistant.components.cloud.open', mopen, create=True), \
patch('homeassistant.components.cloud.Cloud._decode_claims'):
cl._start_cloud(None)
yield from cl.async_start(None)
assert cl.id_token == 'test-id-token'
assert cl.access_token == 'test-access-token'

View File

@ -266,8 +266,8 @@ def test_handler_alexa(hass):
hass.states.async_set(
'switch.test2', 'on', {'friendly_name': "Test switch 2"})
with patch('homeassistant.components.cloud.Cloud.initialize',
return_value=mock_coro(True)):
with patch('homeassistant.components.cloud.Cloud.async_start',
return_value=mock_coro()):
setup = yield from async_setup_component(hass, 'cloud', {
'cloud': {
'alexa': {
@ -309,8 +309,8 @@ def test_handler_google_actions(hass):
hass.states.async_set(
'switch.test2', 'on', {'friendly_name': "Test switch 2"})
with patch('homeassistant.components.cloud.Cloud.initialize',
return_value=mock_coro(True)):
with patch('homeassistant.components.cloud.Cloud.async_start',
return_value=mock_coro()):
setup = yield from async_setup_component(hass, 'cloud', {
'cloud': {
'google_actions': {

View File

@ -7,10 +7,12 @@ from datetime import datetime, timedelta
from astral import Astral
import pytest
from homeassistant.core import callback
from homeassistant.setup import setup_component
import homeassistant.core as ha
from homeassistant.const import MATCH_ALL
from homeassistant.helpers.event import (
async_call_later,
track_point_in_utc_time,
track_point_in_time,
track_utc_time_change,
@ -52,7 +54,7 @@ class TestEventHelpers(unittest.TestCase):
runs = []
track_point_in_utc_time(
self.hass, lambda x: runs.append(1), birthday_paulus)
self.hass, callback(lambda x: runs.append(1)), birthday_paulus)
self._send_time_changed(before_birthday)
self.hass.block_till_done()
@ -68,14 +70,14 @@ class TestEventHelpers(unittest.TestCase):
self.assertEqual(1, len(runs))
track_point_in_time(
self.hass, lambda x: runs.append(1), birthday_paulus)
self.hass, callback(lambda x: runs.append(1)), birthday_paulus)
self._send_time_changed(after_birthday)
self.hass.block_till_done()
self.assertEqual(2, len(runs))
unsub = track_point_in_time(
self.hass, lambda x: runs.append(1), birthday_paulus)
self.hass, callback(lambda x: runs.append(1)), birthday_paulus)
unsub()
self._send_time_changed(after_birthday)
@ -642,3 +644,22 @@ class TestEventHelpers(unittest.TestCase):
self._send_time_changed(datetime(2014, 5, 2, 0, 0, 0))
self.hass.block_till_done()
self.assertEqual(0, len(specific_runs))
@asyncio.coroutine
def test_async_call_later(hass):
"""Test calling an action later."""
def action(): pass
now = datetime(2017, 12, 19, 15, 40, 0, tzinfo=dt_util.UTC)
with patch('homeassistant.helpers.event'
'.async_track_point_in_utc_time') as mock, \
patch('homeassistant.util.dt.utcnow', return_value=now):
remove = async_call_later(hass, 3, action)
assert len(mock.mock_calls) == 1
p_hass, p_action, p_point = mock.mock_calls[0][1]
assert hass is hass
assert p_action is action
assert p_point == now + timedelta(seconds=3)
assert remove is mock()