From 72a6bc23256d21e8b6474f90e7b05107f2ed7cbb Mon Sep 17 00:00:00 2001 From: pvizeli Date: Tue, 4 Apr 2017 17:22:25 +0200 Subject: [PATCH 01/10] Initial import for HassIO --- homeassistant/components/hassio.py | 239 +++++++++++++++++ homeassistant/components/services.yaml | 92 +++++++ tests/components/test_hassio.py | 341 +++++++++++++++++++++++++ tests/test_util/aiohttp.py | 4 +- 4 files changed, 675 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/hassio.py create mode 100644 tests/components/test_hassio.py diff --git a/homeassistant/components/hassio.py b/homeassistant/components/hassio.py new file mode 100644 index 00000000000000..4c54a10e18ee28 --- /dev/null +++ b/homeassistant/components/hassio.py @@ -0,0 +1,239 @@ +""" +Exposes regular rest commands as services. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/hassio/ +""" +import asyncio +import logging +import os + +import aiohttp +import async_timeout +import voluptuous as vol + +from homeassistant.config import load_yaml_config_file +from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv + +DOMAIN = 'hassio' + +_LOGGER = logging.getLogger(__name__) + +TIMEOUT = 900 +DATA_HASSIO = 'hassio' + +SERVICE_HOST_SHUTDOWN = 'host_shutdown' +SERVICE_HOST_REBOOT = 'host_reboot' +SERVICE_HOST_UPDATE = 'host_update' + +SERVICE_NETWORK_OPTIONS = 'network_options' + +SERVICE_SUPERVISOR_UPDATE = 'supervisor_update' +SERVICE_SUPERVISOR_OPTIONS = 'supervisor_options' + +SERVICE_HOMEASSISTANT_UPDATE = 'homeassistant_update' + +SERVICE_ADDON_INSTALL = 'addon_install' +SERVICE_ADDON_UNINSTALL = 'addon_uninstall' +SERVICE_ADDON_UPDATE = 'addon_update' +SERVICE_ADDON_START = 'addon_start' +SERVICE_ADDON_STOP = 'addon_stop' + +ATTR_ADDON = 'addon' +ATTR_BETA = 'beta' +ATTR_OPTIONS = 'options' +ATTR_IP = 'ip' +ATTR_SSID = 'ssid' +ATTR_PASSWORD = 'password' +ATTR_VERSION = 'version' + + +SCHEMA_SERVICE_UPDATE = vol.Schema({ + vol.Optional(ATTR_VERSION): cv.string, +}) + +SCHEMA_SERVICE_SUPERVISOR_OPTIONS = vol.Schema({ + vol.Optional(ATTR_BETA): cv.boolean, +}) + +SCHEMA_SERVICE_NETWORK_OPTIONS = vol.Schema({ + vol.Optional(ATTR_IP): cv.string, + vol.Optional(ATTR_SSID): cv.string, + vol.Optional(ATTR_PASSWORD): cv.string, +}) + +SCHEMA_SERVICE_ADDONS = vol.Schema({ + vol.Required(ATTR_ADDON): cv.slug, +}) + +SCHEMA_SERVICE_ADDONS_START = SCHEMA_SERVICE_ADDONS.extend({ + vol.Optional(ATTR_OPTIONS): dict, +}) + +SCHEMA_SERVICE_ADDONS_VERSION = SCHEMA_SERVICE_ADDONS.extend({ + vol.Optional(ATTR_VERSION): cv.string, +}) + + +@asyncio.coroutine +def async_setup(hass, config): + """Setup the hassio component.""" + websession = async_get_clientsession(hass) + hassio = HassIO(hass.loop, websession) + + if not hassio.connected: + _LOGGER.error("No HassIO supervisor detect!") + return False + + hass.data[DATA_HASSIO] = hassio + + descriptions = yield from hass.loop.run_in_executor( + None, load_yaml_config_file, os.path.join( + os.path.dirname(__file__), 'services.yaml')) + + @asyncio.coroutine + def async_service_handler(service): + """Handle HassIO service calls.""" + if service.service == SERVICE_HOST_UPDATE: + yield from hassio.send_command( + "/host/update", payload=service.data) + elif service.service == SERVICE_HOST_REBOOT: + yield from hassio.send_command("/host/reboot") + elif service.service == SERVICE_HOST_SHUTDOWN: + yield from hassio.send_command("/host/shutdown") + elif service.service == SERVICE_NETWORK_OPTIONS: + yield from hassio.send_command( + "/network/options", payload=service.data) + elif service.service == SERVICE_SUPERVISOR_UPDATE: + yield from hassio.send_command( + "/supervisor/update", payload=service.data) + elif service.service == SERVICE_SUPERVISOR_OPTIONS: + yield from hassio.send_command( + "/supervisor/options", payload=service.data) + elif service.service == SERVICE_HOMEASSISTANT_UPDATE: + yield from hassio.send_command( + "/homeassistant/update", payload=service.data) + + hass.services.async_register( + DOMAIN, SERVICE_HOST_SHUTDOWN, async_service_handler, + descriptions[DOMAIN][SERVICE_HOST_SHUTDOWN]) + hass.services.async_register( + DOMAIN, SERVICE_HOST_REBOOT, async_service_handler, + descriptions[DOMAIN][SERVICE_HOST_REBOOT]) + hass.services.async_register( + DOMAIN, SERVICE_HOST_UPDATE, async_service_handler, + descriptions[DOMAIN][SERVICE_HOST_UPDATE], + schema=SCHEMA_SERVICE_UPDATE) + hass.services.async_register( + DOMAIN, SERVICE_SUPERVISOR_UPDATE, async_service_handler, + descriptions[DOMAIN][SERVICE_SUPERVISOR_UPDATE], + schema=SCHEMA_SERVICE_UPDATE) + hass.services.async_register( + DOMAIN, SERVICE_SUPERVISOR_OPTIONS, async_service_handler, + descriptions[DOMAIN][SERVICE_SUPERVISOR_OPTIONS], + schema=SCHEMA_SERVICE_SUPERVISOR_OPTIONS) + hass.services.async_register( + DOMAIN, SERVICE_NETWORK_OPTIONS, async_service_handler, + descriptions[DOMAIN][SERVICE_NETWORK_OPTIONS], + schema=SCHEMA_SERVICE_NETWORK_OPTIONS) + hass.services.async_register( + DOMAIN, SERVICE_HOMEASSISTANT_UPDATE, async_service_handler, + descriptions[DOMAIN][SERVICE_HOMEASSISTANT_UPDATE], + schema=SCHEMA_SERVICE_UPDATE) + + @asyncio.coroutine + def async_service_handler_addons(service): + """Handle HassIO service calls for addons.""" + addon = service.data[ATTR_ADDON] + options = service.data.get(ATTR_OPTIONS) + + # extract version + if ATTR_VERSION in service.data: + version = {ATTR_VERSION: service.data[ATTR_VERSION]} + else: + version = None + + if service.service == SERVICE_ADDON_INSTALL: + yield from hassio.send_command( + "/addons/{}/install".format(addon), payload=version) + elif service.service == SERVICE_ADDON_UNINSTALL: + yield from hassio.send_command( + "/addons/{}/uninstall".format(addon)) + elif service.service == SERVICE_ADDON_START: + yield from hassio.send_command( + "/addons/{}/start".format(addon), payload=options) + elif service.service == SERVICE_ADDON_STOP: + yield from hassio.send_command("/addons/{}/stop".format(addon)) + elif service.service == SERVICE_ADDON_UPDATE: + yield from hassio.send_command( + "/addons/{}/update".format(addon), payload=version) + + hass.services.async_register( + DOMAIN, SERVICE_ADDON_UPDATE, async_service_handler_addons, + descriptions[DOMAIN][SERVICE_ADDON_UPDATE], + schema=SCHEMA_SERVICE_ADDONS_VERSION) + hass.services.async_register( + DOMAIN, SERVICE_ADDON_INSTALL, async_service_handler_addons, + descriptions[DOMAIN][SERVICE_ADDON_INSTALL], + schema=SCHEMA_SERVICE_ADDONS_VERSION) + hass.services.async_register( + DOMAIN, SERVICE_ADDON_UNINSTALL, async_service_handler_addons, + descriptions[DOMAIN][SERVICE_ADDON_UNINSTALL], + schema=SCHEMA_SERVICE_ADDONS) + hass.services.async_register( + DOMAIN, SERVICE_ADDON_START, async_service_handler_addons, + descriptions[DOMAIN][SERVICE_ADDON_START], + schema=SCHEMA_SERVICE_ADDONS_START) + hass.services.async_register( + DOMAIN, SERVICE_ADDON_STOP, async_service_handler_addons, + descriptions[DOMAIN][SERVICE_ADDON_STOP], + schema=SCHEMA_SERVICE_ADDONS) + + return True + + +class HassIO(object): + """Small API wrapper for HassIO.""" + + def __init__(self, loop, websession): + """Initialze HassIO api.""" + self.loop = loop + self.websession = websession + try: + self._ip = os.environ['HASSIO'] + except KeyError: + self._ip = None + + @property + def connected(self): + """Return True if it connected to HassIO supervisor.""" + return self._ip is not None + + @asyncio.coroutine + def send_command(self, cmd, payload=None): + """Send request to API.""" + try: + with async_timeout.timeout(TIMEOUT, loop=self.loop): + request = yield from self.websession.get( + "http://{}{}".format(self._ip, cmd), + timeout=None, json=payload + ) + + if request.status != 200: + _LOGGER.error("%s return code %d.", cmd, request.status) + return + + answer = yield from request.json() + if answer['result'] == 'ok': + return answer['data'] if answer['data'] else True + + _LOGGER.error("%s return error %s.", cmd, answer['message']) + + except asyncio.TimeoutError: + _LOGGER.error("Timeout on api request %s.", cmd) + + except aiohttp.ClientError: + _LOGGER.error("Client error on api request %s.", cmd) + + return False diff --git a/homeassistant/components/services.yaml b/homeassistant/components/services.yaml index a28a95969fb40a..82b661d3ee797d 100644 --- a/homeassistant/components/services.yaml +++ b/homeassistant/components/services.yaml @@ -316,3 +316,95 @@ ffmpeg: logger: set_level: description: Set log level for components. + +hassio: + host_reboot: + description: Reboot host computer. + + host_shutdown: + description: Poweroff host computer. + + host_update: + description: Update host computer. + fields: + version: + description: Optional or it will be use the latest version. + example: '0.3' + + network_options: + description: Set network options on HassIO host. + fields: + ip: + description: Optional IP address for fixed value. + example: '192.168.1.10' + ssid: + description: Optional SSID for wlan. + example: 'My_Wlan' + password: + description: Optional wlan password. + example: 'mysecretpasswordwlan' + + supervisor_options: + description: Set options inside supervisor. + fields: + beta: + description: Optional true or false value. Set HassIO to beta upstream. + example: 'true' + + supervisor_update: + description: Update HassIO supervisor. + fields: + version: + description: Optional or it will be use the latest version. + example: '0.3' + + homeassistant_update: + description: Update HomeAssistant docker image. + fields: + version: + description: Optional or it will be use the latest version. + example: '0.40.1' + + addon_install: + description: Install a HassIO docker addon. + fields: + addon: + description: Name of addon. + example: 'smb_config' + version: + description: Optional or it will be use the latest version. + example: '0.2' + + addon_uninstall: + description: Uninstall a HassIO docker addon. + fields: + addon: + description: Name of addon. + example: 'smb_config' + + addon_update: + description: Update a HassIO docker addon. + fields: + addon: + description: Name of addon. + example: 'smb_config' + version: + description: Optional or it will be use the latest version. + example: '0.2' + + addon_start: + description: Start a HassIO docker addon. + fields: + addon: + description: Name of addon. + example: 'smb_config' + options: + description: Optional options for addons customize. + example: '{}' + + addon_stop: + description: Stop a HassIO docker addon. + fields: + addon: + description: Name of addon. + example: 'smb_config' diff --git a/tests/components/test_hassio.py b/tests/components/test_hassio.py new file mode 100644 index 00000000000000..95f6f1c1045d4e --- /dev/null +++ b/tests/components/test_hassio.py @@ -0,0 +1,341 @@ +"""The tests for the hassio component.""" +import asyncio +import os + +import aiohttp + +import homeassistant.components.hassio as ho +from homeassistant.setup import setup_component + +from tests.common import ( + get_test_home_assistant, assert_setup_component) + + +class TestHassIOSetup(object): + """Test the hassio component.""" + + def setup_method(self): + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + + self.config = { + ho.DOMAIN: {}, + } + + os.environ['HASSIO'] = "127.0.0.1" + + def teardown_method(self): + """Stop everything that was started.""" + self.hass.stop() + + def test_setup_component(self): + """Test setup component.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + assert self.hass.data['hassio']._ip == "127.0.0.1" + + def test_setup_component_test_service(self): + """Test setup component and check if service exits.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + assert self.hass.services.has_service( + ho.DOMAIN, ho.SERVICE_HOST_REBOOT) + assert self.hass.services.has_service( + ho.DOMAIN, ho.SERVICE_HOST_SHUTDOWN) + assert self.hass.services.has_service( + ho.DOMAIN, ho.SERVICE_HOST_UPDATE) + + assert self.hass.services.has_service( + ho.DOMAIN, ho.SERVICE_NETWORK_OPTIONS) + + assert self.hass.services.has_service( + ho.DOMAIN, ho.SERVICE_SUPERVISOR_UPDATE) + assert self.hass.services.has_service( + ho.DOMAIN, ho.SERVICE_SUPERVISOR_OPTIONS) + + assert self.hass.services.has_service( + ho.DOMAIN, ho.SERVICE_ADDON_INSTALL) + assert self.hass.services.has_service( + ho.DOMAIN, ho.SERVICE_ADDON_UNINSTALL) + assert self.hass.services.has_service( + ho.DOMAIN, ho.SERVICE_ADDON_UPDATE) + assert self.hass.services.has_service( + ho.DOMAIN, ho.SERVICE_ADDON_START) + assert self.hass.services.has_service( + ho.DOMAIN, ho.SERVICE_ADDON_STOP) + + +class TestHassIOComponent(object): + """Test the HassIO component.""" + + def setup_method(self): + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + self.config = { + ho.DOMAIN: {}, + } + + os.environ['HASSIO'] = "127.0.0.1" + self.url = "http://127.0.0.1/{}" + + self.error_msg = { + 'result': 'error', + 'message': 'Test error', + } + self.ok_msg = { + 'result': 'ok', + 'data': None, + } + + def teardown_method(self): + """Stop everything that was started.""" + self.hass.stop() + + def test_rest_command_timeout(self, aioclient_mock): + """Call a hassio with timeout.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + aioclient_mock.get( + self.url.format("host/update"), exc=asyncio.TimeoutError()) + + self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_UPDATE, {}) + self.hass.block_till_done() + + assert len(aioclient_mock.mock_calls) == 1 + + def test_rest_command_aiohttp_error(self, aioclient_mock): + """Call a hassio with aiohttp exception.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + aioclient_mock.get( + self.url.format("host/update"), exc=aiohttp.ClientError()) + + self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_UPDATE, {}) + self.hass.block_till_done() + + assert len(aioclient_mock.mock_calls) == 1 + + def test_rest_command_http_error(self, aioclient_mock): + """Call a hassio with status code 503.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + aioclient_mock.get( + self.url.format("host/update"), status=503) + + self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_UPDATE, {}) + self.hass.block_till_done() + + assert len(aioclient_mock.mock_calls) == 1 + + def test_rest_command_http_error_api(self, aioclient_mock): + """Call a hassio with status code 503.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + aioclient_mock.get( + self.url.format("host/update"), json=self.error_msg) + + self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_UPDATE, {}) + self.hass.block_till_done() + + assert len(aioclient_mock.mock_calls) == 1 + + def test_rest_command_http_host_reboot(self, aioclient_mock): + """Call a hassio for host reboot.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + aioclient_mock.get( + self.url.format("host/reboot"), json=self.ok_msg) + + self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_REBOOT, {}) + self.hass.block_till_done() + + assert len(aioclient_mock.mock_calls) == 1 + + def test_rest_command_http_host_shutdown(self, aioclient_mock): + """Call a hassio for host shutdown.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + aioclient_mock.get( + self.url.format("host/shutdown"), json=self.ok_msg) + + self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_SHUTDOWN, {}) + self.hass.block_till_done() + + assert len(aioclient_mock.mock_calls) == 1 + + def test_rest_command_http_host_update(self, aioclient_mock): + """Call a hassio for host update.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + aioclient_mock.get( + self.url.format("host/update"), json=self.ok_msg) + + self.hass.services.call( + ho.DOMAIN, ho.SERVICE_HOST_UPDATE, {'version': '0.4'}) + self.hass.block_till_done() + + assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[0][2]['version'] == '0.4' + + def test_rest_command_http_network_options(self, aioclient_mock): + """Call a hassio for network options.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + aioclient_mock.get( + self.url.format("network/options"), json=self.ok_msg) + + self.hass.services.call( + ho.DOMAIN, ho.SERVICE_NETWORK_OPTIONS, { + 'ip': "192.168.1.10", + 'ssid': "My_Wlan", + 'password': "123456", + }) + self.hass.block_till_done() + + assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[0][2]['ip'] == "192.168.1.10" + assert aioclient_mock.mock_calls[0][2]['ssid'] == "My_Wlan" + assert aioclient_mock.mock_calls[0][2]['password'] == "123456" + + def test_rest_command_http_supervisor_update(self, aioclient_mock): + """Call a hassio for supervisor update.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + aioclient_mock.get( + self.url.format("supervisor/update"), json=self.ok_msg) + + self.hass.services.call( + ho.DOMAIN, ho.SERVICE_SUPERVISOR_UPDATE, {'version': '0.4'}) + self.hass.block_till_done() + + assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[0][2]['version'] == '0.4' + + def test_rest_command_http_supervisor_options(self, aioclient_mock): + """Call a hassio for supervisor options.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + aioclient_mock.get( + self.url.format("supervisor/options"), json=self.ok_msg) + + self.hass.services.call( + ho.DOMAIN, ho.SERVICE_SUPERVISOR_OPTIONS, {'beta': True}) + self.hass.block_till_done() + + assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[0][2]['beta'] + + def test_rest_command_http_homeassistant_update(self, aioclient_mock): + """Call a hassio for homeassistant update.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + aioclient_mock.get( + self.url.format("homeassistant/update"), json=self.ok_msg) + + self.hass.services.call( + ho.DOMAIN, ho.SERVICE_HOMEASSISTANT_UPDATE, {'version': '0.4'}) + self.hass.block_till_done() + + assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[0][2]['version'] == '0.4' + + def test_rest_command_http_addon_install(self, aioclient_mock): + """Call a hassio for addon install.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + aioclient_mock.get( + self.url.format("addons/smb_config/install"), json=self.ok_msg) + + self.hass.services.call( + ho.DOMAIN, ho.SERVICE_ADDON_INSTALL, { + 'addon': 'smb_config', + 'version': '0.4' + }) + self.hass.block_till_done() + + assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[0][2]['version'] == '0.4' + + def test_rest_command_http_addon_uninstall(self, aioclient_mock): + """Call a hassio for addon uninstall.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + aioclient_mock.get( + self.url.format("addons/smb_config/uninstall"), json=self.ok_msg) + + self.hass.services.call( + ho.DOMAIN, ho.SERVICE_ADDON_UNINSTALL, { + 'addon': 'smb_config' + }) + self.hass.block_till_done() + + assert len(aioclient_mock.mock_calls) == 1 + + def test_rest_command_http_addon_update(self, aioclient_mock): + """Call a hassio for addon update.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + aioclient_mock.get( + self.url.format("addons/smb_config/update"), json=self.ok_msg) + + self.hass.services.call( + ho.DOMAIN, ho.SERVICE_ADDON_UPDATE, { + 'addon': 'smb_config', + 'version': '0.4' + }) + self.hass.block_till_done() + + assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[0][2]['version'] == '0.4' + + def test_rest_command_http_addon_start(self, aioclient_mock): + """Call a hassio for addon start.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + aioclient_mock.get( + self.url.format("addons/smb_config/start"), json=self.ok_msg) + + self.hass.services.call( + ho.DOMAIN, ho.SERVICE_ADDON_START, { + 'addon': 'smb_config', + 'options': { + 'bla': 'bla2' + } + }) + self.hass.block_till_done() + + assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[0][2]['bla'] == 'bla2' + + def test_rest_command_http_addon_stop(self, aioclient_mock): + """Call a hassio for addon stop.""" + with assert_setup_component(0): + setup_component(self.hass, ho.DOMAIN, self.config) + + aioclient_mock.get( + self.url.format("addons/smb_config/stop"), json=self.ok_msg) + + self.hass.services.call( + ho.DOMAIN, ho.SERVICE_ADDON_STOP, { + 'addon': 'smb_config' + }) + self.hass.block_till_done() + + assert len(aioclient_mock.mock_calls) == 1 diff --git a/tests/test_util/aiohttp.py b/tests/test_util/aiohttp.py index 23e24cac0cd741..39e926ab7e7076 100644 --- a/tests/test_util/aiohttp.py +++ b/tests/test_util/aiohttp.py @@ -75,8 +75,10 @@ def clear_requests(self): @asyncio.coroutine # pylint: disable=unused-variable def match_request(self, method, url, *, data=None, auth=None, params=None, - headers=None, allow_redirects=None): + headers=None, allow_redirects=None, timeout=None, + json=None): """Match a request against pre-registered requests.""" + data = data or json for response in self._mocks: if response.match_request(method, url, params): self.mock_calls.append((method, url, data)) From 7bb1570791b5dcc54cf5a81a3864ad10a6ffbe2f Mon Sep 17 00:00:00 2001 From: pvizeli Date: Thu, 6 Apr 2017 09:15:37 +0200 Subject: [PATCH 02/10] Cleanup api code for views --- homeassistant/components/hassio.py | 227 +++++++++++++++-------------- tests/components/test_hassio.py | 79 ++-------- 2 files changed, 135 insertions(+), 171 deletions(-) diff --git a/homeassistant/components/hassio.py b/homeassistant/components/hassio.py index 4c54a10e18ee28..753e14999373e6 100644 --- a/homeassistant/components/hassio.py +++ b/homeassistant/components/hassio.py @@ -9,29 +9,28 @@ import os import aiohttp +from aiohttp import web +from aiohttp.web_exceptions import HTTPBadGateway import async_timeout import voluptuous as vol from homeassistant.config import load_yaml_config_file +from homeassistant.components.http import HomeAssistantView from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv DOMAIN = 'hassio' +DEPENDENCIES = ['http'] _LOGGER = logging.getLogger(__name__) TIMEOUT = 900 -DATA_HASSIO = 'hassio' SERVICE_HOST_SHUTDOWN = 'host_shutdown' SERVICE_HOST_REBOOT = 'host_reboot' -SERVICE_HOST_UPDATE = 'host_update' - -SERVICE_NETWORK_OPTIONS = 'network_options' +SERVICE_HOST_UPDATE = 'host_update' SERVICE_SUPERVISOR_UPDATE = 'supervisor_update' -SERVICE_SUPERVISOR_OPTIONS = 'supervisor_options' - SERVICE_HOMEASSISTANT_UPDATE = 'homeassistant_update' SERVICE_ADDON_INSTALL = 'addon_install' @@ -41,11 +40,6 @@ SERVICE_ADDON_STOP = 'addon_stop' ATTR_ADDON = 'addon' -ATTR_BETA = 'beta' -ATTR_OPTIONS = 'options' -ATTR_IP = 'ip' -ATTR_SSID = 'ssid' -ATTR_PASSWORD = 'password' ATTR_VERSION = 'version' @@ -53,29 +47,29 @@ vol.Optional(ATTR_VERSION): cv.string, }) -SCHEMA_SERVICE_SUPERVISOR_OPTIONS = vol.Schema({ - vol.Optional(ATTR_BETA): cv.boolean, -}) - -SCHEMA_SERVICE_NETWORK_OPTIONS = vol.Schema({ - vol.Optional(ATTR_IP): cv.string, - vol.Optional(ATTR_SSID): cv.string, - vol.Optional(ATTR_PASSWORD): cv.string, -}) - SCHEMA_SERVICE_ADDONS = vol.Schema({ vol.Required(ATTR_ADDON): cv.slug, }) -SCHEMA_SERVICE_ADDONS_START = SCHEMA_SERVICE_ADDONS.extend({ - vol.Optional(ATTR_OPTIONS): dict, -}) - SCHEMA_SERVICE_ADDONS_VERSION = SCHEMA_SERVICE_ADDONS.extend({ vol.Optional(ATTR_VERSION): cv.string, }) +SERVICE_MAP = { + SERVICE_HOST_SHUTDOWN: None, + SERVICE_HOST_REBOOT: None, + SERVICE_HOST_UPDATE: SCHEMA_SERVICE_UPDATE, + SERVICE_SUPERVISOR_UPDATE: SCHEMA_SERVICE_UPDATE, + SERVICE_HOMEASSISTANT_UPDATE: SCHEMA_SERVICE_UPDATE, + SERVICE_ADDON_INSTALL: SCHEMA_SERVICE_ADDONS_VERSION, + SERVICE_ADDON_UNINSTALL: SCHEMA_SERVICE_ADDONS, + SERVICE_ADDON_START: SCHEMA_SERVICE_ADDONS, + SERVICE_ADDON_STOP: SCHEMA_SERVICE_ADDONS, + SERVICE_ADDON_UPDATE: SCHEMA_SERVICE_ADDONS_VERSION, +} + + @asyncio.coroutine def async_setup(hass, config): """Setup the hassio component.""" @@ -86,109 +80,56 @@ def async_setup(hass, config): _LOGGER.error("No HassIO supervisor detect!") return False - hass.data[DATA_HASSIO] = hassio - - descriptions = yield from hass.loop.run_in_executor( - None, load_yaml_config_file, os.path.join( - os.path.dirname(__file__), 'services.yaml')) + # register api views + for base in ('host', 'supervisor', 'network', 'homeassistant'): + hass.http.register_view(HassIOBaseView(hassio, base)) + hass.http.register_view(HassIOAddonsView(hassio)) @asyncio.coroutine def async_service_handler(service): """Handle HassIO service calls.""" + addon = service.data.get(ATTR_ADDON) + if ATTR_VERSION in service.data: + version = {ATTR_VERSION: service.data[ATTR_VERSION]} + else: + version = None + + # map to api call if service.service == SERVICE_HOST_UPDATE: yield from hassio.send_command( - "/host/update", payload=service.data) + "/host/update", payload=version) elif service.service == SERVICE_HOST_REBOOT: yield from hassio.send_command("/host/reboot") elif service.service == SERVICE_HOST_SHUTDOWN: yield from hassio.send_command("/host/shutdown") - elif service.service == SERVICE_NETWORK_OPTIONS: - yield from hassio.send_command( - "/network/options", payload=service.data) elif service.service == SERVICE_SUPERVISOR_UPDATE: yield from hassio.send_command( - "/supervisor/update", payload=service.data) - elif service.service == SERVICE_SUPERVISOR_OPTIONS: - yield from hassio.send_command( - "/supervisor/options", payload=service.data) + "/supervisor/update", payload=version) elif service.service == SERVICE_HOMEASSISTANT_UPDATE: yield from hassio.send_command( - "/homeassistant/update", payload=service.data) - - hass.services.async_register( - DOMAIN, SERVICE_HOST_SHUTDOWN, async_service_handler, - descriptions[DOMAIN][SERVICE_HOST_SHUTDOWN]) - hass.services.async_register( - DOMAIN, SERVICE_HOST_REBOOT, async_service_handler, - descriptions[DOMAIN][SERVICE_HOST_REBOOT]) - hass.services.async_register( - DOMAIN, SERVICE_HOST_UPDATE, async_service_handler, - descriptions[DOMAIN][SERVICE_HOST_UPDATE], - schema=SCHEMA_SERVICE_UPDATE) - hass.services.async_register( - DOMAIN, SERVICE_SUPERVISOR_UPDATE, async_service_handler, - descriptions[DOMAIN][SERVICE_SUPERVISOR_UPDATE], - schema=SCHEMA_SERVICE_UPDATE) - hass.services.async_register( - DOMAIN, SERVICE_SUPERVISOR_OPTIONS, async_service_handler, - descriptions[DOMAIN][SERVICE_SUPERVISOR_OPTIONS], - schema=SCHEMA_SERVICE_SUPERVISOR_OPTIONS) - hass.services.async_register( - DOMAIN, SERVICE_NETWORK_OPTIONS, async_service_handler, - descriptions[DOMAIN][SERVICE_NETWORK_OPTIONS], - schema=SCHEMA_SERVICE_NETWORK_OPTIONS) - hass.services.async_register( - DOMAIN, SERVICE_HOMEASSISTANT_UPDATE, async_service_handler, - descriptions[DOMAIN][SERVICE_HOMEASSISTANT_UPDATE], - schema=SCHEMA_SERVICE_UPDATE) - - @asyncio.coroutine - def async_service_handler_addons(service): - """Handle HassIO service calls for addons.""" - addon = service.data[ATTR_ADDON] - options = service.data.get(ATTR_OPTIONS) - - # extract version - if ATTR_VERSION in service.data: - version = {ATTR_VERSION: service.data[ATTR_VERSION]} - else: - version = None - - if service.service == SERVICE_ADDON_INSTALL: + "/homeassistant/update", payload=version) + elif service.service == SERVICE_ADDON_INSTALL: yield from hassio.send_command( "/addons/{}/install".format(addon), payload=version) elif service.service == SERVICE_ADDON_UNINSTALL: yield from hassio.send_command( "/addons/{}/uninstall".format(addon)) elif service.service == SERVICE_ADDON_START: - yield from hassio.send_command( - "/addons/{}/start".format(addon), payload=options) + yield from hassio.send_command("/addons/{}/start".format(addon)) elif service.service == SERVICE_ADDON_STOP: yield from hassio.send_command("/addons/{}/stop".format(addon)) elif service.service == SERVICE_ADDON_UPDATE: yield from hassio.send_command( "/addons/{}/update".format(addon), payload=version) - hass.services.async_register( - DOMAIN, SERVICE_ADDON_UPDATE, async_service_handler_addons, - descriptions[DOMAIN][SERVICE_ADDON_UPDATE], - schema=SCHEMA_SERVICE_ADDONS_VERSION) - hass.services.async_register( - DOMAIN, SERVICE_ADDON_INSTALL, async_service_handler_addons, - descriptions[DOMAIN][SERVICE_ADDON_INSTALL], - schema=SCHEMA_SERVICE_ADDONS_VERSION) - hass.services.async_register( - DOMAIN, SERVICE_ADDON_UNINSTALL, async_service_handler_addons, - descriptions[DOMAIN][SERVICE_ADDON_UNINSTALL], - schema=SCHEMA_SERVICE_ADDONS) - hass.services.async_register( - DOMAIN, SERVICE_ADDON_START, async_service_handler_addons, - descriptions[DOMAIN][SERVICE_ADDON_START], - schema=SCHEMA_SERVICE_ADDONS_START) - hass.services.async_register( - DOMAIN, SERVICE_ADDON_STOP, async_service_handler_addons, - descriptions[DOMAIN][SERVICE_ADDON_STOP], - schema=SCHEMA_SERVICE_ADDONS) + descriptions = yield from hass.loop.run_in_executor( + None, load_yaml_config_file, os.path.join( + os.path.dirname(__file__), 'services.yaml')) + + for service, schema in SERVICE_MAP.items(): + hass.services.async_register( + DOMAIN, service, async_service_handler, + descriptions[DOMAIN][service], schema=schema) return True @@ -213,6 +154,16 @@ def connected(self): @asyncio.coroutine def send_command(self, cmd, payload=None): """Send request to API.""" + answer = yield from self.send_raw(cmd, payload=payload) + if answer['result'] == 'ok': + return answer['data'] if answer['data'] else True + + _LOGGER.error("%s return error %s.", cmd, answer['message']) + return False + + @asyncio.coroutine + def send_raw(self, cmd, payload=None): + """Send raw request to API.""" try: with async_timeout.timeout(TIMEOUT, loop=self.loop): request = yield from self.websession.get( @@ -224,11 +175,7 @@ def send_command(self, cmd, payload=None): _LOGGER.error("%s return code %d.", cmd, request.status) return - answer = yield from request.json() - if answer['result'] == 'ok': - return answer['data'] if answer['data'] else True - - _LOGGER.error("%s return error %s.", cmd, answer['message']) + return (yield from request.json()) except asyncio.TimeoutError: _LOGGER.error("Timeout on api request %s.", cmd) @@ -236,4 +183,68 @@ def send_command(self, cmd, payload=None): except aiohttp.ClientError: _LOGGER.error("Client error on api request %s.", cmd) - return False + +class HassIOBaseView(HomeAssistantView): + """HassIO view to handle base part.""" + + requires_auth = True + + def __init__(self, hassio, base): + """Initialize a hassio base view.""" + self.hassio = hassio + self._url_info = "/{}/info".format(base) + self._url_options = "/{}/options".format(base) + + self.url = "/api/hassio/{}".format(base) + self.name = "api:hassio:{}".format(base) + + @asyncio.coroutine + def get(self, request): + """Get base data.""" + data = yield from self.hassio.send_command(self._url_info) + if not data: + raise HTTPBadGateway() + return web.json_response(data) + + @asyncio.coroutine + def post(self, request): + """Set options on host.""" + data = yield from request.json() + + response = yield from self.hassio.send_raw( + self._url_options, payload=data) + if not response: + raise HTTPBadGateway() + return web.json_response(response) + + +class HassIOAddonsView(HomeAssistantView): + """HassIO view to handle addons part.""" + + requires_auth = True + url = "/api/hassio/addons/{addon}" + name = "api:hassio:addons" + + def __init__(self, hassio): + """Initialize a hassio addon view.""" + self.hassio = hassio + + @asyncio.coroutine + def get(self, request, addon): + """Get addon data.""" + data = yield from self.hassio.send_command( + "/addons/{}/info".format(addon)) + if not data: + raise HTTPBadGateway() + return web.json_response(data) + + @asyncio.coroutine + def post(self, request, addon): + """Set options on host.""" + data = yield from request.json() + + response = yield from self.hassio.send_raw( + "/addons/{}/options".format(addon), payload=data) + if not response: + raise HTTPBadGateway() + return web.json_response(response) diff --git a/tests/components/test_hassio.py b/tests/components/test_hassio.py index 95f6f1c1045d4e..58809eabfa5119 100644 --- a/tests/components/test_hassio.py +++ b/tests/components/test_hassio.py @@ -30,14 +30,12 @@ def teardown_method(self): def test_setup_component(self): """Test setup component.""" - with assert_setup_component(0): + with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) - assert self.hass.data['hassio']._ip == "127.0.0.1" - def test_setup_component_test_service(self): """Test setup component and check if service exits.""" - with assert_setup_component(0): + with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) assert self.hass.services.has_service( @@ -47,13 +45,8 @@ def test_setup_component_test_service(self): assert self.hass.services.has_service( ho.DOMAIN, ho.SERVICE_HOST_UPDATE) - assert self.hass.services.has_service( - ho.DOMAIN, ho.SERVICE_NETWORK_OPTIONS) - assert self.hass.services.has_service( ho.DOMAIN, ho.SERVICE_SUPERVISOR_UPDATE) - assert self.hass.services.has_service( - ho.DOMAIN, ho.SERVICE_SUPERVISOR_OPTIONS) assert self.hass.services.has_service( ho.DOMAIN, ho.SERVICE_ADDON_INSTALL) @@ -95,7 +88,7 @@ def teardown_method(self): def test_rest_command_timeout(self, aioclient_mock): """Call a hassio with timeout.""" - with assert_setup_component(0): + with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) aioclient_mock.get( @@ -108,7 +101,7 @@ def test_rest_command_timeout(self, aioclient_mock): def test_rest_command_aiohttp_error(self, aioclient_mock): """Call a hassio with aiohttp exception.""" - with assert_setup_component(0): + with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) aioclient_mock.get( @@ -121,7 +114,7 @@ def test_rest_command_aiohttp_error(self, aioclient_mock): def test_rest_command_http_error(self, aioclient_mock): """Call a hassio with status code 503.""" - with assert_setup_component(0): + with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) aioclient_mock.get( @@ -134,7 +127,7 @@ def test_rest_command_http_error(self, aioclient_mock): def test_rest_command_http_error_api(self, aioclient_mock): """Call a hassio with status code 503.""" - with assert_setup_component(0): + with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) aioclient_mock.get( @@ -147,7 +140,7 @@ def test_rest_command_http_error_api(self, aioclient_mock): def test_rest_command_http_host_reboot(self, aioclient_mock): """Call a hassio for host reboot.""" - with assert_setup_component(0): + with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) aioclient_mock.get( @@ -160,7 +153,7 @@ def test_rest_command_http_host_reboot(self, aioclient_mock): def test_rest_command_http_host_shutdown(self, aioclient_mock): """Call a hassio for host shutdown.""" - with assert_setup_component(0): + with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) aioclient_mock.get( @@ -173,7 +166,7 @@ def test_rest_command_http_host_shutdown(self, aioclient_mock): def test_rest_command_http_host_update(self, aioclient_mock): """Call a hassio for host update.""" - with assert_setup_component(0): + with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) aioclient_mock.get( @@ -186,30 +179,9 @@ def test_rest_command_http_host_update(self, aioclient_mock): assert len(aioclient_mock.mock_calls) == 1 assert aioclient_mock.mock_calls[0][2]['version'] == '0.4' - def test_rest_command_http_network_options(self, aioclient_mock): - """Call a hassio for network options.""" - with assert_setup_component(0): - setup_component(self.hass, ho.DOMAIN, self.config) - - aioclient_mock.get( - self.url.format("network/options"), json=self.ok_msg) - - self.hass.services.call( - ho.DOMAIN, ho.SERVICE_NETWORK_OPTIONS, { - 'ip': "192.168.1.10", - 'ssid': "My_Wlan", - 'password': "123456", - }) - self.hass.block_till_done() - - assert len(aioclient_mock.mock_calls) == 1 - assert aioclient_mock.mock_calls[0][2]['ip'] == "192.168.1.10" - assert aioclient_mock.mock_calls[0][2]['ssid'] == "My_Wlan" - assert aioclient_mock.mock_calls[0][2]['password'] == "123456" - def test_rest_command_http_supervisor_update(self, aioclient_mock): """Call a hassio for supervisor update.""" - with assert_setup_component(0): + with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) aioclient_mock.get( @@ -222,24 +194,9 @@ def test_rest_command_http_supervisor_update(self, aioclient_mock): assert len(aioclient_mock.mock_calls) == 1 assert aioclient_mock.mock_calls[0][2]['version'] == '0.4' - def test_rest_command_http_supervisor_options(self, aioclient_mock): - """Call a hassio for supervisor options.""" - with assert_setup_component(0): - setup_component(self.hass, ho.DOMAIN, self.config) - - aioclient_mock.get( - self.url.format("supervisor/options"), json=self.ok_msg) - - self.hass.services.call( - ho.DOMAIN, ho.SERVICE_SUPERVISOR_OPTIONS, {'beta': True}) - self.hass.block_till_done() - - assert len(aioclient_mock.mock_calls) == 1 - assert aioclient_mock.mock_calls[0][2]['beta'] - def test_rest_command_http_homeassistant_update(self, aioclient_mock): """Call a hassio for homeassistant update.""" - with assert_setup_component(0): + with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) aioclient_mock.get( @@ -254,7 +211,7 @@ def test_rest_command_http_homeassistant_update(self, aioclient_mock): def test_rest_command_http_addon_install(self, aioclient_mock): """Call a hassio for addon install.""" - with assert_setup_component(0): + with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) aioclient_mock.get( @@ -272,7 +229,7 @@ def test_rest_command_http_addon_install(self, aioclient_mock): def test_rest_command_http_addon_uninstall(self, aioclient_mock): """Call a hassio for addon uninstall.""" - with assert_setup_component(0): + with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) aioclient_mock.get( @@ -288,7 +245,7 @@ def test_rest_command_http_addon_uninstall(self, aioclient_mock): def test_rest_command_http_addon_update(self, aioclient_mock): """Call a hassio for addon update.""" - with assert_setup_component(0): + with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) aioclient_mock.get( @@ -306,7 +263,7 @@ def test_rest_command_http_addon_update(self, aioclient_mock): def test_rest_command_http_addon_start(self, aioclient_mock): """Call a hassio for addon start.""" - with assert_setup_component(0): + with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) aioclient_mock.get( @@ -315,18 +272,14 @@ def test_rest_command_http_addon_start(self, aioclient_mock): self.hass.services.call( ho.DOMAIN, ho.SERVICE_ADDON_START, { 'addon': 'smb_config', - 'options': { - 'bla': 'bla2' - } }) self.hass.block_till_done() assert len(aioclient_mock.mock_calls) == 1 - assert aioclient_mock.mock_calls[0][2]['bla'] == 'bla2' def test_rest_command_http_addon_stop(self, aioclient_mock): """Call a hassio for addon stop.""" - with assert_setup_component(0): + with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) aioclient_mock.get( From 2999a363079bc20f2f55f251f1fc9a947588645c Mon Sep 17 00:00:00 2001 From: pvizeli Date: Thu, 6 Apr 2017 14:49:54 +0200 Subject: [PATCH 03/10] First unittest for view --- homeassistant/components/hassio.py | 18 +++++++++++++--- tests/components/test_hassio.py | 34 +++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/hassio.py b/homeassistant/components/hassio.py index 753e14999373e6..2f9d43165bd783 100644 --- a/homeassistant/components/hassio.py +++ b/homeassistant/components/hassio.py @@ -80,9 +80,13 @@ def async_setup(hass, config): _LOGGER.error("No HassIO supervisor detect!") return False - # register api views - for base in ('host', 'supervisor', 'network', 'homeassistant'): + # register base api views + for base in ('host', 'homeassistant'): hass.http.register_view(HassIOBaseView(hassio, base)) + for base in ('supervisor', 'network'): + hass.http.register_view(HassIOBaseEditView(hassio, base)) + + # register view for addons hass.http.register_view(HassIOAddonsView(hassio)) @asyncio.coroutine @@ -193,7 +197,6 @@ def __init__(self, hassio, base): """Initialize a hassio base view.""" self.hassio = hassio self._url_info = "/{}/info".format(base) - self._url_options = "/{}/options".format(base) self.url = "/api/hassio/{}".format(base) self.name = "api:hassio:{}".format(base) @@ -206,6 +209,15 @@ def get(self, request): raise HTTPBadGateway() return web.json_response(data) + +class HassIOBaseEditView(HassIOBaseView): + """HassIO view to handle base with options support.""" + + def __init__(self, hassio, base): + """Initialize a hassio base edit view.""" + super().__init__(hassio, base) + self._url_options = "/{}/options".format(base) + @asyncio.coroutine def post(self, request): """Set options on host.""" diff --git a/tests/components/test_hassio.py b/tests/components/test_hassio.py index 58809eabfa5119..701016e7a7427f 100644 --- a/tests/components/test_hassio.py +++ b/tests/components/test_hassio.py @@ -5,7 +5,7 @@ import aiohttp import homeassistant.components.hassio as ho -from homeassistant.setup import setup_component +from homeassistant.setup import setup_component, async_setup_component from tests.common import ( get_test_home_assistant, assert_setup_component) @@ -292,3 +292,35 @@ def test_rest_command_http_addon_stop(self, aioclient_mock): self.hass.block_till_done() assert len(aioclient_mock.mock_calls) == 1 + + +@asyncio.coroutine +def test_async_hassio_host_view(aioclient_mock, hass, test_client): + """Test that it fetches the given url.""" + os.environ['HASSIO'] = "127.0.0.1" + + result = yield from async_setup_component(hass, ho.DOMAIN, {ho.DOMAIN: {}}) + assert result, 'Failed to setup hasio' + + client = yield from test_client(hass.http.app) + + aioclient_mock.get('http://127.0.0.1/host/info', json={ + 'result': 'ok', + 'data': { + 'os': 'resinos', + 'version': '0.3', + 'current': '0.4', + 'level': 16, + 'hostname': 'test', + } + }) + + resp = yield from client.get('/api/hassio/host') + data = yield from resp.json() + + assert resp.status == 200 + assert data['os'] == 'resinos' + assert data['version'] == '0.3' + assert data['current'] == '0.4' + assert data['level'] == 16 + assert data['hostname'] == 'test' From 51079abff7321e1dd9d9dba3601a1c66610bc2d8 Mon Sep 17 00:00:00 2001 From: pvizeli Date: Thu, 6 Apr 2017 16:14:31 +0200 Subject: [PATCH 04/10] Add test for edit view --- tests/components/test_hassio.py | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/components/test_hassio.py b/tests/components/test_hassio.py index 701016e7a7427f..68957ce06b078a 100644 --- a/tests/components/test_hassio.py +++ b/tests/components/test_hassio.py @@ -318,9 +318,80 @@ def test_async_hassio_host_view(aioclient_mock, hass, test_client): resp = yield from client.get('/api/hassio/host') data = yield from resp.json() + assert len(aioclient_mock.mock_calls) == 1 assert resp.status == 200 assert data['os'] == 'resinos' assert data['version'] == '0.3' assert data['current'] == '0.4' assert data['level'] == 16 assert data['hostname'] == 'test' + + +@asyncio.coroutine +def test_async_hassio_homeassistant_view(aioclient_mock, hass, test_client): + """Test that it fetches the given url.""" + os.environ['HASSIO'] = "127.0.0.1" + + result = yield from async_setup_component(hass, ho.DOMAIN, {ho.DOMAIN: {}}) + assert result, 'Failed to setup hasio' + + client = yield from test_client(hass.http.app) + + aioclient_mock.get('http://127.0.0.1/homeassistant/info', json={ + 'result': 'ok', + 'data': { + 'version': '0.41', + 'current': '0.41.1', + } + }) + + resp = yield from client.get('/api/hassio/homeassistant') + data = yield from resp.json() + + assert len(aioclient_mock.mock_calls) == 1 + assert resp.status == 200 + assert data['version'] == '0.41' + assert data['current'] == '0.41.1' + + +@asyncio.coroutine +def test_async_hassio_supervisor_view(aioclient_mock, hass, test_client): + """Test that it fetches the given url.""" + os.environ['HASSIO'] = "127.0.0.1" + + result = yield from async_setup_component(hass, ho.DOMAIN, {ho.DOMAIN: {}}) + assert result, 'Failed to setup hasio' + + client = yield from test_client(hass.http.app) + + aioclient_mock.get('http://127.0.0.1/supervisor/info', json={ + 'result': 'ok', + 'data': { + 'version': '0.3', + 'current': '0.4', + 'beta': False, + } + }) + + resp = yield from client.get('/api/hassio/supervisor') + data = yield from resp.json() + + assert len(aioclient_mock.mock_calls) == 1 + assert resp.status == 200 + assert data['version'] == '0.3' + assert data['current'] == '0.4' + assert not data['beta'] + + aioclient_mock.get('http://127.0.0.1/supervisor/options', json={ + 'result': 'ok', + 'data': {}, + }) + + resp = yield from client.post('/api/hassio/supervisor', json={ + 'beta': True, + }) + data = yield from resp.json() + + assert len(aioclient_mock.mock_calls) == 2 + assert resp.status == 200 + assert aioclient_mock.mock_calls[-1][2]['beta'] From 138915b0e63892af86ba7196dae90a9aa16d9eee Mon Sep 17 00:00:00 2001 From: pvizeli Date: Thu, 6 Apr 2017 16:54:32 +0200 Subject: [PATCH 05/10] Finish unittest --- tests/components/test_hassio.py | 97 +++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/tests/components/test_hassio.py b/tests/components/test_hassio.py index 68957ce06b078a..ffb7526f1f21cd 100644 --- a/tests/components/test_hassio.py +++ b/tests/components/test_hassio.py @@ -395,3 +395,100 @@ def test_async_hassio_supervisor_view(aioclient_mock, hass, test_client): assert len(aioclient_mock.mock_calls) == 2 assert resp.status == 200 assert aioclient_mock.mock_calls[-1][2]['beta'] + + +@asyncio.coroutine +def test_async_hassio_network_view(aioclient_mock, hass, test_client): + """Test that it fetches the given url.""" + os.environ['HASSIO'] = "127.0.0.1" + + result = yield from async_setup_component(hass, ho.DOMAIN, {ho.DOMAIN: {}}) + assert result, 'Failed to setup hasio' + + client = yield from test_client(hass.http.app) + + aioclient_mock.get('http://127.0.0.1/network/info', json={ + 'result': 'ok', + 'data': { + 'mode': 'dhcp', + 'ssid': 'my_wlan', + 'password': '123456', + } + }) + + resp = yield from client.get('/api/hassio/network') + data = yield from resp.json() + + assert len(aioclient_mock.mock_calls) == 1 + assert resp.status == 200 + assert data['mode'] == 'dhcp' + assert data['ssid'] == 'my_wlan' + assert data['password'] == '123456' + + aioclient_mock.get('http://127.0.0.1/network/options', json={ + 'result': 'ok', + 'data': {}, + }) + + resp = yield from client.post('/api/hassio/network', json={ + 'mode': 'dhcp', + 'ssid': 'my_wlan2', + 'password': '654321', + }) + data = yield from resp.json() + + assert len(aioclient_mock.mock_calls) == 2 + assert resp.status == 200 + assert aioclient_mock.mock_calls[-1][2]['ssid'] == 'my_wlan2' + assert aioclient_mock.mock_calls[-1][2]['password'] == '654321' + + +@asyncio.coroutine +def test_async_hassio_addon_view(aioclient_mock, hass, test_client): + """Test that it fetches the given url.""" + os.environ['HASSIO'] = "127.0.0.1" + + result = yield from async_setup_component(hass, ho.DOMAIN, {ho.DOMAIN: {}}) + assert result, 'Failed to setup hasio' + + client = yield from test_client(hass.http.app) + + aioclient_mock.get('http://127.0.0.1/addons/smb_config/info', json={ + 'result': 'ok', + 'data': { + 'name': 'SMB Config', + 'state': 'running', + 'boot': 'auto', + 'options': { + 'bla': False, + } + } + }) + + resp = yield from client.get('/api/hassio/addons/smb_config') + data = yield from resp.json() + + assert len(aioclient_mock.mock_calls) == 1 + assert resp.status == 200 + assert data['name'] == 'SMB Config' + assert data['state'] == 'running' + assert data['boot'] == 'auto' + assert data['options']['bla'] + + aioclient_mock.get('http://127.0.0.1/addons/smb_config/options', json={ + 'result': 'ok', + 'data': {}, + }) + + resp = yield from client.post('/api/hassio/addons/smb_config', json={ + 'boot': 'manual', + 'options': { + 'bla': True, + } + }) + data = yield from resp.json() + + assert len(aioclient_mock.mock_calls) == 2 + assert resp.status == 200 + assert aioclient_mock.mock_calls[-1][2]['boot'] == 'manual' + assert aioclient_mock.mock_calls[-1][2]['options']['bla'] From ce3fdf23ebc8d420ca7310b6a8bb0f34764b8023 Mon Sep 17 00:00:00 2001 From: pvizeli Date: Thu, 6 Apr 2017 17:30:24 +0200 Subject: [PATCH 06/10] fix addons test --- tests/components/test_hassio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/test_hassio.py b/tests/components/test_hassio.py index ffb7526f1f21cd..a33ad429cc0648 100644 --- a/tests/components/test_hassio.py +++ b/tests/components/test_hassio.py @@ -473,7 +473,7 @@ def test_async_hassio_addon_view(aioclient_mock, hass, test_client): assert data['name'] == 'SMB Config' assert data['state'] == 'running' assert data['boot'] == 'auto' - assert data['options']['bla'] + assert not data['options']['bla'] aioclient_mock.get('http://127.0.0.1/addons/smb_config/options', json={ 'result': 'ok', From 9b4f9b7ae9a4cf61fddabf8fd335827c83a09337 Mon Sep 17 00:00:00 2001 From: pvizeli Date: Thu, 6 Apr 2017 17:35:54 +0200 Subject: [PATCH 07/10] cleanup service.yaml --- homeassistant/components/services.yaml | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/homeassistant/components/services.yaml b/homeassistant/components/services.yaml index 82b661d3ee797d..cf5999200d8927 100644 --- a/homeassistant/components/services.yaml +++ b/homeassistant/components/services.yaml @@ -331,26 +331,6 @@ hassio: description: Optional or it will be use the latest version. example: '0.3' - network_options: - description: Set network options on HassIO host. - fields: - ip: - description: Optional IP address for fixed value. - example: '192.168.1.10' - ssid: - description: Optional SSID for wlan. - example: 'My_Wlan' - password: - description: Optional wlan password. - example: 'mysecretpasswordwlan' - - supervisor_options: - description: Set options inside supervisor. - fields: - beta: - description: Optional true or false value. Set HassIO to beta upstream. - example: 'true' - supervisor_update: description: Update HassIO supervisor. fields: @@ -398,9 +378,6 @@ hassio: addon: description: Name of addon. example: 'smb_config' - options: - description: Optional options for addons customize. - example: '{}' addon_stop: description: Stop a HassIO docker addon. From aff28011837d8741bc2063bad6402396e7e0e17a Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Thu, 6 Apr 2017 22:13:43 +0200 Subject: [PATCH 08/10] Address first round with ping command --- homeassistant/components/hassio.py | 32 +++++---- tests/components/test_hassio.py | 109 +++++++++++++++++++++-------- 2 files changed, 98 insertions(+), 43 deletions(-) diff --git a/homeassistant/components/hassio.py b/homeassistant/components/hassio.py index 2f9d43165bd783..b86bfe7fc31054 100644 --- a/homeassistant/components/hassio.py +++ b/homeassistant/components/hassio.py @@ -73,11 +73,18 @@ @asyncio.coroutine def async_setup(hass, config): """Setup the hassio component.""" + try: + ip = os.environ['HASSIO'] + except KeyError: + _LOGGER.error("No HassIO supervisor detect!") + return False + websession = async_get_clientsession(hass) - hassio = HassIO(hass.loop, websession) + hassio = HassIO(hass.loop, websession, ip) - if not hassio.connected: - _LOGGER.error("No HassIO supervisor detect!") + ok = yield from hassio.is_connected() + if not ok: + _LOGGER.error("Not connected with HassIO!") return False # register base api views @@ -141,19 +148,18 @@ def async_service_handler(service): class HassIO(object): """Small API wrapper for HassIO.""" - def __init__(self, loop, websession): + def __init__(self, loop, websession, ip): """Initialze HassIO api.""" self.loop = loop self.websession = websession - try: - self._ip = os.environ['HASSIO'] - except KeyError: - self._ip = None - - @property - def connected(self): - """Return True if it connected to HassIO supervisor.""" - return self._ip is not None + self._ip = ip + + def is_connected(self): + """Return True if it connected to HassIO supervisor. + + Return a coroutine. + """ + return self.send_command("/supervisor/ping") @asyncio.coroutine def send_command(self, cmd, payload=None): diff --git a/tests/components/test_hassio.py b/tests/components/test_hassio.py index a33ad429cc0648..bde419c410421f 100644 --- a/tests/components/test_hassio.py +++ b/tests/components/test_hassio.py @@ -28,13 +28,19 @@ def teardown_method(self): """Stop everything that was started.""" self.hass.stop() - def test_setup_component(self): + def test_setup_component(self, aioclient_mock): """Test setup component.""" + aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={ + 'result': 'ok', 'data': {} + }) with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) - def test_setup_component_test_service(self): + def test_setup_component_test_service(self, aioclient_mock): """Test setup component and check if service exits.""" + aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={ + 'result': 'ok', 'data': {} + }) with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) @@ -79,7 +85,7 @@ def setup_method(self): } self.ok_msg = { 'result': 'ok', - 'data': None, + 'data': {}, } def teardown_method(self): @@ -88,6 +94,8 @@ def teardown_method(self): def test_rest_command_timeout(self, aioclient_mock): """Call a hassio with timeout.""" + aioclient_mock.get( + "http://127.0.0.1/supervisor/ping", json=self.ok_msg) with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) @@ -97,10 +105,12 @@ def test_rest_command_timeout(self, aioclient_mock): self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_UPDATE, {}) self.hass.block_till_done() - assert len(aioclient_mock.mock_calls) == 1 + assert len(aioclient_mock.mock_calls) == 2 def test_rest_command_aiohttp_error(self, aioclient_mock): """Call a hassio with aiohttp exception.""" + aioclient_mock.get( + "http://127.0.0.1/supervisor/ping", json=self.ok_msg) with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) @@ -110,10 +120,12 @@ def test_rest_command_aiohttp_error(self, aioclient_mock): self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_UPDATE, {}) self.hass.block_till_done() - assert len(aioclient_mock.mock_calls) == 1 + assert len(aioclient_mock.mock_calls) == 2 def test_rest_command_http_error(self, aioclient_mock): """Call a hassio with status code 503.""" + aioclient_mock.get( + "http://127.0.0.1/supervisor/ping", json=self.ok_msg) with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) @@ -123,10 +135,12 @@ def test_rest_command_http_error(self, aioclient_mock): self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_UPDATE, {}) self.hass.block_till_done() - assert len(aioclient_mock.mock_calls) == 1 + assert len(aioclient_mock.mock_calls) == 2 def test_rest_command_http_error_api(self, aioclient_mock): """Call a hassio with status code 503.""" + aioclient_mock.get( + "http://127.0.0.1/supervisor/ping", json=self.ok_msg) with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) @@ -136,10 +150,12 @@ def test_rest_command_http_error_api(self, aioclient_mock): self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_UPDATE, {}) self.hass.block_till_done() - assert len(aioclient_mock.mock_calls) == 1 + assert len(aioclient_mock.mock_calls) == 2 def test_rest_command_http_host_reboot(self, aioclient_mock): """Call a hassio for host reboot.""" + aioclient_mock.get( + "http://127.0.0.1/supervisor/ping", json=self.ok_msg) with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) @@ -149,10 +165,12 @@ def test_rest_command_http_host_reboot(self, aioclient_mock): self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_REBOOT, {}) self.hass.block_till_done() - assert len(aioclient_mock.mock_calls) == 1 + assert len(aioclient_mock.mock_calls) == 2 def test_rest_command_http_host_shutdown(self, aioclient_mock): """Call a hassio for host shutdown.""" + aioclient_mock.get( + "http://127.0.0.1/supervisor/ping", json=self.ok_msg) with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) @@ -162,10 +180,12 @@ def test_rest_command_http_host_shutdown(self, aioclient_mock): self.hass.services.call(ho.DOMAIN, ho.SERVICE_HOST_SHUTDOWN, {}) self.hass.block_till_done() - assert len(aioclient_mock.mock_calls) == 1 + assert len(aioclient_mock.mock_calls) == 2 def test_rest_command_http_host_update(self, aioclient_mock): """Call a hassio for host update.""" + aioclient_mock.get( + "http://127.0.0.1/supervisor/ping", json=self.ok_msg) with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) @@ -176,11 +196,13 @@ def test_rest_command_http_host_update(self, aioclient_mock): ho.DOMAIN, ho.SERVICE_HOST_UPDATE, {'version': '0.4'}) self.hass.block_till_done() - assert len(aioclient_mock.mock_calls) == 1 - assert aioclient_mock.mock_calls[0][2]['version'] == '0.4' + assert len(aioclient_mock.mock_calls) == 2 + assert aioclient_mock.mock_calls[-1][2]['version'] == '0.4' def test_rest_command_http_supervisor_update(self, aioclient_mock): """Call a hassio for supervisor update.""" + aioclient_mock.get( + "http://127.0.0.1/supervisor/ping", json=self.ok_msg) with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) @@ -191,11 +213,13 @@ def test_rest_command_http_supervisor_update(self, aioclient_mock): ho.DOMAIN, ho.SERVICE_SUPERVISOR_UPDATE, {'version': '0.4'}) self.hass.block_till_done() - assert len(aioclient_mock.mock_calls) == 1 - assert aioclient_mock.mock_calls[0][2]['version'] == '0.4' + assert len(aioclient_mock.mock_calls) == 2 + assert aioclient_mock.mock_calls[-1][2]['version'] == '0.4' def test_rest_command_http_homeassistant_update(self, aioclient_mock): """Call a hassio for homeassistant update.""" + aioclient_mock.get( + "http://127.0.0.1/supervisor/ping", json=self.ok_msg) with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) @@ -206,11 +230,13 @@ def test_rest_command_http_homeassistant_update(self, aioclient_mock): ho.DOMAIN, ho.SERVICE_HOMEASSISTANT_UPDATE, {'version': '0.4'}) self.hass.block_till_done() - assert len(aioclient_mock.mock_calls) == 1 - assert aioclient_mock.mock_calls[0][2]['version'] == '0.4' + assert len(aioclient_mock.mock_calls) == 2 + assert aioclient_mock.mock_calls[-1][2]['version'] == '0.4' def test_rest_command_http_addon_install(self, aioclient_mock): """Call a hassio for addon install.""" + aioclient_mock.get( + "http://127.0.0.1/supervisor/ping", json=self.ok_msg) with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) @@ -224,11 +250,13 @@ def test_rest_command_http_addon_install(self, aioclient_mock): }) self.hass.block_till_done() - assert len(aioclient_mock.mock_calls) == 1 - assert aioclient_mock.mock_calls[0][2]['version'] == '0.4' + assert len(aioclient_mock.mock_calls) == 2 + assert aioclient_mock.mock_calls[-1][2]['version'] == '0.4' def test_rest_command_http_addon_uninstall(self, aioclient_mock): """Call a hassio for addon uninstall.""" + aioclient_mock.get( + "http://127.0.0.1/supervisor/ping", json=self.ok_msg) with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) @@ -241,10 +269,12 @@ def test_rest_command_http_addon_uninstall(self, aioclient_mock): }) self.hass.block_till_done() - assert len(aioclient_mock.mock_calls) == 1 + assert len(aioclient_mock.mock_calls) == 2 def test_rest_command_http_addon_update(self, aioclient_mock): """Call a hassio for addon update.""" + aioclient_mock.get( + "http://127.0.0.1/supervisor/ping", json=self.ok_msg) with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) @@ -258,11 +288,13 @@ def test_rest_command_http_addon_update(self, aioclient_mock): }) self.hass.block_till_done() - assert len(aioclient_mock.mock_calls) == 1 - assert aioclient_mock.mock_calls[0][2]['version'] == '0.4' + assert len(aioclient_mock.mock_calls) == 2 + assert aioclient_mock.mock_calls[-1][2]['version'] == '0.4' def test_rest_command_http_addon_start(self, aioclient_mock): """Call a hassio for addon start.""" + aioclient_mock.get( + "http://127.0.0.1/supervisor/ping", json=self.ok_msg) with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) @@ -275,10 +307,12 @@ def test_rest_command_http_addon_start(self, aioclient_mock): }) self.hass.block_till_done() - assert len(aioclient_mock.mock_calls) == 1 + assert len(aioclient_mock.mock_calls) == 2 def test_rest_command_http_addon_stop(self, aioclient_mock): """Call a hassio for addon stop.""" + aioclient_mock.get( + "http://127.0.0.1/supervisor/ping", json=self.ok_msg) with assert_setup_component(0, ho.DOMAIN): setup_component(self.hass, ho.DOMAIN, self.config) @@ -291,7 +325,7 @@ def test_rest_command_http_addon_stop(self, aioclient_mock): }) self.hass.block_till_done() - assert len(aioclient_mock.mock_calls) == 1 + assert len(aioclient_mock.mock_calls) == 2 @asyncio.coroutine @@ -299,6 +333,9 @@ def test_async_hassio_host_view(aioclient_mock, hass, test_client): """Test that it fetches the given url.""" os.environ['HASSIO'] = "127.0.0.1" + aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={ + 'result': 'ok', 'data': {} + }) result = yield from async_setup_component(hass, ho.DOMAIN, {ho.DOMAIN: {}}) assert result, 'Failed to setup hasio' @@ -318,7 +355,7 @@ def test_async_hassio_host_view(aioclient_mock, hass, test_client): resp = yield from client.get('/api/hassio/host') data = yield from resp.json() - assert len(aioclient_mock.mock_calls) == 1 + assert len(aioclient_mock.mock_calls) == 2 assert resp.status == 200 assert data['os'] == 'resinos' assert data['version'] == '0.3' @@ -332,6 +369,9 @@ def test_async_hassio_homeassistant_view(aioclient_mock, hass, test_client): """Test that it fetches the given url.""" os.environ['HASSIO'] = "127.0.0.1" + aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={ + 'result': 'ok', 'data': {} + }) result = yield from async_setup_component(hass, ho.DOMAIN, {ho.DOMAIN: {}}) assert result, 'Failed to setup hasio' @@ -348,7 +388,7 @@ def test_async_hassio_homeassistant_view(aioclient_mock, hass, test_client): resp = yield from client.get('/api/hassio/homeassistant') data = yield from resp.json() - assert len(aioclient_mock.mock_calls) == 1 + assert len(aioclient_mock.mock_calls) == 2 assert resp.status == 200 assert data['version'] == '0.41' assert data['current'] == '0.41.1' @@ -359,6 +399,9 @@ def test_async_hassio_supervisor_view(aioclient_mock, hass, test_client): """Test that it fetches the given url.""" os.environ['HASSIO'] = "127.0.0.1" + aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={ + 'result': 'ok', 'data': {} + }) result = yield from async_setup_component(hass, ho.DOMAIN, {ho.DOMAIN: {}}) assert result, 'Failed to setup hasio' @@ -376,7 +419,7 @@ def test_async_hassio_supervisor_view(aioclient_mock, hass, test_client): resp = yield from client.get('/api/hassio/supervisor') data = yield from resp.json() - assert len(aioclient_mock.mock_calls) == 1 + assert len(aioclient_mock.mock_calls) == 2 assert resp.status == 200 assert data['version'] == '0.3' assert data['current'] == '0.4' @@ -392,7 +435,7 @@ def test_async_hassio_supervisor_view(aioclient_mock, hass, test_client): }) data = yield from resp.json() - assert len(aioclient_mock.mock_calls) == 2 + assert len(aioclient_mock.mock_calls) == 3 assert resp.status == 200 assert aioclient_mock.mock_calls[-1][2]['beta'] @@ -402,6 +445,9 @@ def test_async_hassio_network_view(aioclient_mock, hass, test_client): """Test that it fetches the given url.""" os.environ['HASSIO'] = "127.0.0.1" + aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={ + 'result': 'ok', 'data': {} + }) result = yield from async_setup_component(hass, ho.DOMAIN, {ho.DOMAIN: {}}) assert result, 'Failed to setup hasio' @@ -419,7 +465,7 @@ def test_async_hassio_network_view(aioclient_mock, hass, test_client): resp = yield from client.get('/api/hassio/network') data = yield from resp.json() - assert len(aioclient_mock.mock_calls) == 1 + assert len(aioclient_mock.mock_calls) == 2 assert resp.status == 200 assert data['mode'] == 'dhcp' assert data['ssid'] == 'my_wlan' @@ -437,7 +483,7 @@ def test_async_hassio_network_view(aioclient_mock, hass, test_client): }) data = yield from resp.json() - assert len(aioclient_mock.mock_calls) == 2 + assert len(aioclient_mock.mock_calls) == 3 assert resp.status == 200 assert aioclient_mock.mock_calls[-1][2]['ssid'] == 'my_wlan2' assert aioclient_mock.mock_calls[-1][2]['password'] == '654321' @@ -448,6 +494,9 @@ def test_async_hassio_addon_view(aioclient_mock, hass, test_client): """Test that it fetches the given url.""" os.environ['HASSIO'] = "127.0.0.1" + aioclient_mock.get("http://127.0.0.1/supervisor/ping", json={ + 'result': 'ok', 'data': {} + }) result = yield from async_setup_component(hass, ho.DOMAIN, {ho.DOMAIN: {}}) assert result, 'Failed to setup hasio' @@ -468,7 +517,7 @@ def test_async_hassio_addon_view(aioclient_mock, hass, test_client): resp = yield from client.get('/api/hassio/addons/smb_config') data = yield from resp.json() - assert len(aioclient_mock.mock_calls) == 1 + assert len(aioclient_mock.mock_calls) == 2 assert resp.status == 200 assert data['name'] == 'SMB Config' assert data['state'] == 'running' @@ -488,7 +537,7 @@ def test_async_hassio_addon_view(aioclient_mock, hass, test_client): }) data = yield from resp.json() - assert len(aioclient_mock.mock_calls) == 2 + assert len(aioclient_mock.mock_calls) == 3 assert resp.status == 200 assert aioclient_mock.mock_calls[-1][2]['boot'] == 'manual' assert aioclient_mock.mock_calls[-1][2]['options']['bla'] From c6bd910ce7132bf3af0c02564508c498a20c154b Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Thu, 6 Apr 2017 22:33:39 +0200 Subject: [PATCH 09/10] handle timeout dynamic --- homeassistant/components/hassio.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/hassio.py b/homeassistant/components/hassio.py index b86bfe7fc31054..8404350b244e67 100644 --- a/homeassistant/components/hassio.py +++ b/homeassistant/components/hassio.py @@ -24,7 +24,8 @@ _LOGGER = logging.getLogger(__name__) -TIMEOUT = 900 +LONG_TASK_TIMEOUT = 900 +DEFAULT_TIMEOUT = 10 SERVICE_HOST_SHUTDOWN = 'host_shutdown' SERVICE_HOST_REBOOT = 'host_reboot' @@ -118,10 +119,12 @@ def async_service_handler(service): "/supervisor/update", payload=version) elif service.service == SERVICE_HOMEASSISTANT_UPDATE: yield from hassio.send_command( - "/homeassistant/update", payload=version) + "/homeassistant/update", payload=version, + timeout=LONG_TASK_TIMEOUT) elif service.service == SERVICE_ADDON_INSTALL: yield from hassio.send_command( - "/addons/{}/install".format(addon), payload=version) + "/addons/{}/install".format(addon), payload=version, + timeout=LONG_TASK_TIMEOUT) elif service.service == SERVICE_ADDON_UNINSTALL: yield from hassio.send_command( "/addons/{}/uninstall".format(addon)) @@ -131,7 +134,8 @@ def async_service_handler(service): yield from hassio.send_command("/addons/{}/stop".format(addon)) elif service.service == SERVICE_ADDON_UPDATE: yield from hassio.send_command( - "/addons/{}/update".format(addon), payload=version) + "/addons/{}/update".format(addon), payload=version, + timeout=LONG_TASK_TIMEOUT) descriptions = yield from hass.loop.run_in_executor( None, load_yaml_config_file, os.path.join( @@ -162,7 +166,7 @@ def is_connected(self): return self.send_command("/supervisor/ping") @asyncio.coroutine - def send_command(self, cmd, payload=None): + def send_command(self, cmd, payload=None, timeout=DEFAULT_TIMEOUT): """Send request to API.""" answer = yield from self.send_raw(cmd, payload=payload) if answer['result'] == 'ok': @@ -172,10 +176,10 @@ def send_command(self, cmd, payload=None): return False @asyncio.coroutine - def send_raw(self, cmd, payload=None): + def send_raw(self, cmd, payload=None, timeout=DEFAULT_TIMEOUT): """Send raw request to API.""" try: - with async_timeout.timeout(TIMEOUT, loop=self.loop): + with async_timeout.timeout(timeout, loop=self.loop): request = yield from self.websession.get( "http://{}{}".format(self._ip, cmd), timeout=None, json=payload From 10e7f3c19b3dbf3501a4e520d434b8103f39e073 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Thu, 6 Apr 2017 22:36:47 +0200 Subject: [PATCH 10/10] fix lint --- homeassistant/components/hassio.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/hassio.py b/homeassistant/components/hassio.py index 8404350b244e67..154be0917bbe40 100644 --- a/homeassistant/components/hassio.py +++ b/homeassistant/components/hassio.py @@ -75,16 +75,16 @@ def async_setup(hass, config): """Setup the hassio component.""" try: - ip = os.environ['HASSIO'] + host = os.environ['HASSIO'] except KeyError: _LOGGER.error("No HassIO supervisor detect!") return False websession = async_get_clientsession(hass) - hassio = HassIO(hass.loop, websession, ip) + hassio = HassIO(hass.loop, websession, host) - ok = yield from hassio.is_connected() - if not ok: + api_ok = yield from hassio.is_connected() + if not api_ok: _LOGGER.error("Not connected with HassIO!") return False