From dec04386bfab24a0fc210e2bac0d5fafeb26bfbc Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Fri, 9 Feb 2018 01:27:45 +0100 Subject: [PATCH] Add support for home-assistant bootup (#349) * Add support for home-assistant bootup * fix bug * fix * fix ip bug * bugfix --- API.md | 6 ++-- hassio/addons/addon.py | 24 ++++++------- hassio/api/homeassistant.py | 10 ++++-- hassio/const.py | 1 + hassio/core.py | 2 +- hassio/docker/addon.py | 13 ------- hassio/homeassistant.py | 68 ++++++++++++++++++++++++++++-------- hassio/snapshots/__init__.py | 4 +-- hassio/snapshots/snapshot.py | 14 +++++++- hassio/snapshots/validate.py | 4 ++- hassio/tasks.py | 2 +- hassio/validate.py | 5 ++- 12 files changed, 101 insertions(+), 52 deletions(-) diff --git a/API.md b/API.md index 79b69c953..531bb6297 100644 --- a/API.md +++ b/API.md @@ -268,7 +268,8 @@ Optional: "boot": "bool", "port": 8123, "ssl": "bool", - "watchdog": "bool" + "watchdog": "bool", + "startup_time": 600 } ``` @@ -300,7 +301,8 @@ Output is the raw Docker log. "port": "port for access hass", "ssl": "bool", "password": "", - "watchdog": "bool" + "watchdog": "bool", + "startup_time": 600 } ``` diff --git a/hassio/addons/addon.py b/hassio/addons/addon.py index 4fc2715c6..6d283c245 100644 --- a/hassio/addons/addon.py +++ b/hassio/addons/addon.py @@ -581,12 +581,12 @@ class Addon(CoreSysAttributes): return STATE_STOPPED @check_installed - def start(self): - """Set options and start addon. + async def start(self): + """Set options and start addon.""" + if not self.write_options(): + return False - Return a coroutine. - """ - return self.instance.run() + return await self.instance.run() @check_installed def stop(self): @@ -611,16 +611,14 @@ class Addon(CoreSysAttributes): # restore state if last_state == STATE_STARTED: - await self.instance.run() + await self.start() return True @check_installed - def restart(self): - """Restart addon. - - Return a coroutine. - """ - return self.instance.restart() + async def restart(self): + """Restart addon.""" + await self.stop() + return await self.start() @check_installed def logs(self): @@ -656,7 +654,7 @@ class Addon(CoreSysAttributes): # restore state if last_state == STATE_STARTED: - await self.instance.run() + await self.start() return True @check_installed diff --git a/hassio/api/homeassistant.py b/hassio/api/homeassistant.py index 33b68977d..a138b2792 100644 --- a/hassio/api/homeassistant.py +++ b/hassio/api/homeassistant.py @@ -9,7 +9,7 @@ from ..const import ( ATTR_VERSION, ATTR_LAST_VERSION, ATTR_IMAGE, ATTR_CUSTOM, ATTR_BOOT, ATTR_PORT, ATTR_PASSWORD, ATTR_SSL, ATTR_WATCHDOG, ATTR_CPU_PERCENT, ATTR_MEMORY_USAGE, ATTR_MEMORY_LIMIT, ATTR_NETWORK_RX, ATTR_NETWORK_TX, - ATTR_BLK_READ, ATTR_BLK_WRITE, CONTENT_TYPE_BINARY) + ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_STARTUP_TIME, CONTENT_TYPE_BINARY) from ..coresys import CoreSysAttributes from ..validate import NETWORK_PORT, DOCKER_IMAGE @@ -27,6 +27,8 @@ SCHEMA_OPTIONS = vol.Schema({ vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str)), vol.Optional(ATTR_SSL): vol.Boolean(), vol.Optional(ATTR_WATCHDOG): vol.Boolean(), + vol.Optional(ATTR_STARTUP_TIME): + vol.All(vol.Coerce(int), vol.Range(min=60)), }) SCHEMA_VERSION = vol.Schema({ @@ -49,6 +51,7 @@ class APIHomeAssistant(CoreSysAttributes): ATTR_PORT: self._homeassistant.api_port, ATTR_SSL: self._homeassistant.api_ssl, ATTR_WATCHDOG: self._homeassistant.watchdog, + ATTR_STARTUP_TIME: self._homeassistant.startup_time, } @api_process @@ -75,6 +78,9 @@ class APIHomeAssistant(CoreSysAttributes): if ATTR_WATCHDOG in body: self._homeassistant.watchdog = body[ATTR_WATCHDOG] + if ATTR_STARTUP_TIME in body: + self._homeassistant.startup_time = body[ATTR_STARTUP_TIME] + self._homeassistant.save_data() return True @@ -115,7 +121,7 @@ class APIHomeAssistant(CoreSysAttributes): @api_process def start(self, request): """Start homeassistant.""" - return asyncio.shield(self._homeassistant.run(), loop=self._loop) + return asyncio.shield(self._homeassistant.start(), loop=self._loop) @api_process def restart(self, request): diff --git a/hassio/const.py b/hassio/const.py index 5e86e6dae..99bb0a703 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -155,6 +155,7 @@ ATTR_CONFIG = 'config' ATTR_DISCOVERY_ID = 'discovery_id' ATTR_SERVICES = 'services' ATTR_DISCOVERY = 'discovery' +ATTR_STARTUP_TIME = 'startup_time' SERVICE_MQTT = 'mqtt' diff --git a/hassio/core.py b/hassio/core.py index 613128499..d2a47f32d 100644 --- a/hassio/core.py +++ b/hassio/core.py @@ -84,7 +84,7 @@ class HassIO(CoreSysAttributes): # run HomeAssistant if self._homeassistant.boot: - await self._homeassistant.run() + await self._homeassistant.start() # start addon mark as application await self._addons.auto_boot(STARTUP_APPLICATION) diff --git a/hassio/docker/addon.py b/hassio/docker/addon.py index 759beff32..73c2b9583 100644 --- a/hassio/docker/addon.py +++ b/hassio/docker/addon.py @@ -225,10 +225,6 @@ class DockerAddon(DockerInterface): # cleanup self._stop() - # write config - if not self.addon.write_options(): - return False - ret = self._docker.run( self.image, name=self.name, @@ -337,15 +333,6 @@ class DockerAddon(DockerInterface): self._cleanup() return True - def _restart(self): - """Restart docker container. - - Addons prepare some thing on start and that is normaly not repeatable. - Need run inside executor. - """ - self._stop() - return self._run() - @docker_process def write_stdin(self, data): """Write to add-on stdin.""" diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index 85f7bbef6..1b42900a3 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -3,6 +3,8 @@ import asyncio import logging import os import re +import socket +import time import aiohttp from aiohttp.hdrs import CONTENT_TYPE @@ -10,7 +12,7 @@ from aiohttp.hdrs import CONTENT_TYPE from .const import ( FILE_HASSIO_HOMEASSISTANT, ATTR_IMAGE, ATTR_LAST_VERSION, ATTR_UUID, ATTR_BOOT, ATTR_PASSWORD, ATTR_PORT, ATTR_SSL, ATTR_WATCHDOG, - HEADER_HA_ACCESS, CONTENT_TYPE_JSON) + ATTR_STARTUP_TIME, HEADER_HA_ACCESS, CONTENT_TYPE_JSON) from .coresys import CoreSysAttributes from .docker.homeassistant import DockerHomeAssistant from .utils import convert_to_ascii @@ -91,6 +93,16 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): """Return True if the watchdog should protect Home-Assistant.""" self._data[ATTR_WATCHDOG] = value + @property + def startup_time(self): + """Return time to wait for Home-Assistant startup.""" + return self._data[ATTR_STARTUP_TIME] + + @startup_time.setter + def startup_time(self, value): + """Set time to wait for Home-Assistant startup.""" + self._data[ATTR_STARTUP_TIME] = value + @property def version(self): """Return version of running homeassistant.""" @@ -156,8 +168,8 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): _LOGGER.warning("Fails install landingpage, retry after 60sec") await asyncio.sleep(60, loop=self._loop) - # run landingpage after installation - await self.instance.run() + # Run landingpage after installation + await self.start() async def install(self): """Install a landingpage.""" @@ -176,7 +188,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): # finishing _LOGGER.info("HomeAssistant docker now installed") if self.boot: - await self.instance.run() + await self.start() await self.instance.cleanup() async def update(self, version=None): @@ -193,14 +205,14 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): return await self.instance.update(version) finally: if running: - await self.instance.run() + await self.start() - def run(self): - """Run HomeAssistant docker. + async def start(self): + """Run HomeAssistant docker.""" + if not await self.instance.run(): + return False - Return a coroutine. - """ - return self.instance.run() + return await self._block_till_run() def stop(self): """Stop HomeAssistant docker. @@ -209,12 +221,12 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): """ return self.instance.stop() - def restart(self): - """Restart HomeAssistant docker. + async def restart(self): + """Restart HomeAssistant docker.""" + if not await self.instance.restart(): + return False - Return a coroutine. - """ - return self.instance.restart() + return await self._block_till_run() def logs(self): """Get HomeAssistant docker logs. @@ -310,3 +322,29 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): _LOGGER.warning("Home-Assistant event %s fails", event_type) return False return True + + async def _block_till_run(self): + """Block until Home-Assistant is booting up or startup timeout.""" + start_time = time.monotonic() + + def check_port(): + """Check if port is mapped.""" + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + result = sock.connect_ex((str(self.api_ip), self.api_port)) + sock.close() + + if result == 0: + return True + return False + except OSError: + pass + + while time.monotonic() - start_time < self.startup_time: + if await self._loop.run_in_executor(None, check_port): + _LOGGER.info("Detect a running Home-Assistant instance") + return True + await asyncio.sleep(10, loop=self._loop) + + _LOGGER.warning("Don't wait anymore of Home-Assistant startup!") + return False diff --git a/hassio/snapshots/__init__.py b/hassio/snapshots/__init__.py index af041b1eb..af82a06e1 100644 --- a/hassio/snapshots/__init__.py +++ b/hassio/snapshots/__init__.py @@ -249,7 +249,7 @@ class SnapshotManager(CoreSysAttributes): _LOGGER.info("Full-Restore %s wait until homeassistant ready", snapshot.slug) await task_hass - await self._homeassistant.run() + await self._homeassistant.start() except (OSError, ValueError, tarfile.TarError) as err: _LOGGER.info("Full-Restore %s error: %s", snapshot.slug, err) @@ -310,7 +310,7 @@ class SnapshotManager(CoreSysAttributes): await asyncio.wait(tasks, loop=self._loop) # make sure homeassistant run agen - await self._homeassistant.run() + await self._homeassistant.start() except (OSError, ValueError, tarfile.TarError) as err: _LOGGER.info("Partial-Restore %s error: %s", snapshot.slug, err) diff --git a/hassio/snapshots/snapshot.py b/hassio/snapshots/snapshot.py index caaa2bbf6..5277b6331 100644 --- a/hassio/snapshots/snapshot.py +++ b/hassio/snapshots/snapshot.py @@ -15,7 +15,7 @@ from ..const import ( ATTR_SLUG, ATTR_NAME, ATTR_DATE, ATTR_ADDONS, ATTR_REPOSITORIES, ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_VERSION, ATTR_TYPE, ATTR_IMAGE, ATTR_PORT, ATTR_SSL, ATTR_PASSWORD, ATTR_WATCHDOG, ATTR_BOOT, - ATTR_LAST_VERSION) + ATTR_LAST_VERSION, ATTR_STARTUP_TIME) from ..coresys import CoreSysAttributes from ..utils.json import write_json_file @@ -142,6 +142,16 @@ class Snapshot(CoreSysAttributes): """Set snapshot homeassistant watchdog options.""" self._data[ATTR_HOMEASSISTANT][ATTR_WATCHDOG] = value + @property + def homeassistant_startup_time(self): + """Return snapshot homeassistant startup time options.""" + return self._data[ATTR_HOMEASSISTANT].get(ATTR_STARTUP_TIME) + + @homeassistant_startup_time.setter + def homeassistant_startup_time(self, value): + """Set snapshot homeassistant startup time options.""" + self._data[ATTR_HOMEASSISTANT][ATTR_STARTUP_TIME] = value + @property def homeassistant_boot(self): """Return snapshot homeassistant boot options.""" @@ -339,6 +349,7 @@ class Snapshot(CoreSysAttributes): self.homeassistant_version = self._homeassistant.version self.homeassistant_watchdog = self._homeassistant.watchdog self.homeassistant_boot = self._homeassistant.boot + self.homeassistant_startup_time = self._homeassistant.startup_time # custom image if self._homeassistant.is_custom_image: @@ -354,6 +365,7 @@ class Snapshot(CoreSysAttributes): """Write all data to homeassistant object.""" self._homeassistant.watchdog = self.homeassistant_watchdog self._homeassistant.boot = self.homeassistant_boot + self._homeassistant.startup_time = self.homeassistant_startup_time # custom image if self.homeassistant_image: diff --git a/hassio/snapshots/validate.py b/hassio/snapshots/validate.py index d27cbde22..e9b7d7f59 100644 --- a/hassio/snapshots/validate.py +++ b/hassio/snapshots/validate.py @@ -6,7 +6,7 @@ from ..const import ( ATTR_REPOSITORIES, ATTR_ADDONS, ATTR_NAME, ATTR_SLUG, ATTR_DATE, ATTR_VERSION, ATTR_HOMEASSISTANT, ATTR_FOLDERS, ATTR_TYPE, ATTR_IMAGE, ATTR_PASSWORD, ATTR_PORT, ATTR_SSL, ATTR_WATCHDOG, ATTR_BOOT, - ATTR_LAST_VERSION, + ATTR_LAST_VERSION, ATTR_STARTUP_TIME, FOLDER_SHARE, FOLDER_HOMEASSISTANT, FOLDER_ADDONS, FOLDER_SSL, SNAPSHOT_FULL, SNAPSHOT_PARTIAL) from ..validate import NETWORK_PORT, REPOSITORIES, DOCKER_IMAGE @@ -38,6 +38,8 @@ SCHEMA_SNAPSHOT = vol.Schema({ vol.Optional(ATTR_PORT, default=8123): NETWORK_PORT, vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str)), vol.Optional(ATTR_WATCHDOG, default=True): vol.Boolean(), + vol.Optional(ATTR_STARTUP_TIME, default=600): + vol.All(vol.Coerce(int), vol.Range(min=60)), }, extra=vol.REMOVE_EXTRA), vol.Optional(ATTR_FOLDERS, default=list): vol.All([vol.In(ALL_FOLDERS)], vol.Unique()), diff --git a/hassio/tasks.py b/hassio/tasks.py index 277064781..ab95b0365 100644 --- a/hassio/tasks.py +++ b/hassio/tasks.py @@ -98,7 +98,7 @@ class Tasks(CoreSysAttributes): return _LOGGER.warning("Watchdog found a problem with Home-Assistant docker!") - await self._homeassistant.run() + await self._homeassistant.start() async def _watchdog_homeassistant_api(self): """Create scheduler task for montoring running state of API. diff --git a/hassio/validate.py b/hassio/validate.py index 2c81f1a40..9605e3f89 100644 --- a/hassio/validate.py +++ b/hassio/validate.py @@ -8,7 +8,8 @@ from .const import ( ATTR_IMAGE, ATTR_LAST_VERSION, ATTR_BETA_CHANNEL, ATTR_TIMEZONE, ATTR_ADDONS_CUSTOM_LIST, ATTR_AUDIO_OUTPUT, ATTR_AUDIO_INPUT, ATTR_PASSWORD, ATTR_HOMEASSISTANT, ATTR_HASSIO, ATTR_BOOT, ATTR_LAST_BOOT, - ATTR_SSL, ATTR_PORT, ATTR_WATCHDOG, ATTR_WAIT_BOOT, ATTR_UUID) + ATTR_SSL, ATTR_PORT, ATTR_WATCHDOG, ATTR_WAIT_BOOT, ATTR_UUID, + ATTR_STARTUP_TIME) NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)) @@ -72,6 +73,8 @@ SCHEMA_HASS_CONFIG = vol.Schema({ vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str)), vol.Optional(ATTR_SSL, default=False): vol.Boolean(), vol.Optional(ATTR_WATCHDOG, default=True): vol.Boolean(), + vol.Optional(ATTR_STARTUP_TIME, default=600): + vol.All(vol.Coerce(int), vol.Range(min=60)), }, extra=vol.REMOVE_EXTRA)