From d5eb66bc0da4c9ffc7ce59f713f46dbfca75c9c5 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 28 Jun 2017 16:22:44 +0200 Subject: [PATCH] Use tmp folder / fix log bug / add executor (#83) * Use tmp folder / fix log bug / add executor * Update __main__.py * Update .travis.yml * Add autoupdate on startup. * Fix bug * Move selfupdate code part into start --- .travis.yml | 2 +- hassio/__main__.py | 14 +++++++++++++- hassio/addons/addon.py | 1 + hassio/api/addons.py | 4 ++-- hassio/bootstrap.py | 25 +++++++++++++++++++------ hassio/config.py | 40 +++++++++++++++++++++------------------- hassio/const.py | 6 +++--- hassio/core.py | 40 ++++++++++++++++++++-------------------- hassio/dock/__init__.py | 2 +- hassio/dock/addon.py | 2 +- hassio/tasks.py | 7 ++++--- 11 files changed, 86 insertions(+), 57 deletions(-) diff --git a/.travis.yml b/.travis.yml index 129484ea8..e16f6a9fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ sudo: false matrix: fast_finish: true include: - - python: "3.5" + - python: "3.6" cache: directories: diff --git a/hassio/__main__.py b/hassio/__main__.py index 8f4d2bd0d..d8ba5bd6c 100644 --- a/hassio/__main__.py +++ b/hassio/__main__.py @@ -1,5 +1,6 @@ """Main file for HassIO.""" import asyncio +from concurrent.futures import ThreadPoolExecutor import logging import sys @@ -17,7 +18,14 @@ if __name__ == "__main__": exit(1) loop = asyncio.get_event_loop() - hassio = core.HassIO(loop) + executor = ThreadPoolExecutor(thread_name_prefix="SyncWorker") + loop.set_default_executor(executor) + + _LOGGER.info("Initialize Hassio setup") + config = bootstrap.initialize_system_data() + hassio = core.HassIO(loop, config) + + bootstrap.migrate_system_env(config) _LOGGER.info("Run Hassio setup") loop.run_until_complete(hassio.setup()) @@ -26,7 +34,11 @@ if __name__ == "__main__": loop.call_soon_threadsafe(loop.create_task, hassio.start()) loop.call_soon_threadsafe(bootstrap.reg_signal, loop, hassio) + _LOGGER.info("Run Hassio loop") loop.run_forever() + + _LOGGER.info("Cleanup system") + executor.shutdown(wait=False) loop.close() _LOGGER.info("Close Hassio") diff --git a/hassio/addons/addon.py b/hassio/addons/addon.py index 1ef79e685..7b8da7299 100644 --- a/hassio/addons/addon.py +++ b/hassio/addons/addon.py @@ -96,6 +96,7 @@ class Addon(object): **self.data.system[self._id][ATTR_OPTIONS], **self.data.user[self._id][ATTR_OPTIONS], } + return self.data.cache[self._id][ATTR_OPTIONS] @options.setter def options(self, value): diff --git a/hassio/api/addons.py b/hassio/api/addons.py index 504a5f85b..68f625550 100644 --- a/hassio/api/addons.py +++ b/hassio/api/addons.py @@ -79,10 +79,10 @@ class APIAddons(object): return True @api_process - async def install(self, request, check_installed=False): + async def install(self, request): """Install addon.""" body = await api_validate(SCHEMA_VERSION, request) - addon = self._extract_addon(request) + addon = self._extract_addon(request, check_installed=False) version = body.get(ATTR_VERSION) return await asyncio.shield( diff --git a/hassio/bootstrap.py b/hassio/bootstrap.py index 81648467e..0e161f9d5 100644 --- a/hassio/bootstrap.py +++ b/hassio/bootstrap.py @@ -2,6 +2,7 @@ import logging import os import signal +from pathlib import Path from colorlog import ColoredFormatter @@ -11,9 +12,9 @@ from .config import CoreConfig _LOGGER = logging.getLogger(__name__) -def initialize_system_data(websession): +def initialize_system_data(): """Setup default config and create folders.""" - config = CoreConfig(websession) + config = CoreConfig() # homeassistant config folder if not config.path_config.is_dir(): @@ -42,10 +43,10 @@ def initialize_system_data(websession): config.path_addons_git) config.path_addons_git.mkdir(parents=True) - if not config.path_addons_build.is_dir(): - _LOGGER.info("Create Home-Assistant addon build folder %s", - config.path_addons_build) - config.path_addons_build.mkdir(parents=True) + # hassio tmp folder + if not config.path_tmp.is_dir(): + _LOGGER.info("Create hassio temp folder %s", config.path_tmp) + config.path_tmp.mkdir(parents=True) # hassio backup folder if not config.path_backup.is_dir(): @@ -60,6 +61,18 @@ def initialize_system_data(websession): return config +def migrate_system_env(config): + """Cleanup some stuff after update.""" + + # hass.io 0.37 -> 0.38 + old_build = Path(config.path_hassio, "addons/build") + if old_build.is_dir(): + try: + old_build.rmdir() + except OSError: + _LOGGER.warning("Can't cleanup old addons build dir.") + + def initialize_logging(): """Setup the logging.""" logging.basicConfig(level=logging.INFO) diff --git a/hassio/config.py b/hassio/config.py index 9e7ea3793..30ff88d08 100644 --- a/hassio/config.py +++ b/hassio/config.py @@ -8,7 +8,7 @@ from pathlib import Path, PurePath import voluptuous as vol from voluptuous.humanize import humanize_error -from .const import FILE_HASSIO_CONFIG, HASSIO_SHARE +from .const import FILE_HASSIO_CONFIG, HASSIO_DATA from .tools import ( fetch_last_versions, write_json_file, read_json_file, validate_timezone) @@ -27,12 +27,11 @@ ADDONS_CORE = PurePath("addons/core") ADDONS_LOCAL = PurePath("addons/local") ADDONS_GIT = PurePath("addons/git") ADDONS_DATA = PurePath("addons/data") -ADDONS_BUILD = PurePath("addons/build") ADDONS_CUSTOM_LIST = 'addons_custom_list' BACKUP_DATA = PurePath("backup") - SHARE_DATA = PurePath("share") +TMP_DATA = PurePath("tmp") UPSTREAM_BETA = 'upstream_beta' API_ENDPOINT = 'api_endpoint' @@ -88,9 +87,8 @@ class Config(object): class CoreConfig(Config): """Hold all core config data.""" - def __init__(self, websession): + def __init__(self): """Initialize config object.""" - self.websession = websession self.arch = None super().__init__(FILE_HASSIO_CONFIG) @@ -103,10 +101,9 @@ class CoreConfig(Config): _LOGGER.warning( "Invalid config %s", humanize_error(self._data, ex)) - async def fetch_update_infos(self): + async def fetch_update_infos(self, websession): """Read current versions from web.""" - last = await fetch_last_versions( - self.websession, beta=self.upstream_beta) + last = await fetch_last_versions(websession, beta=self.upstream_beta) if last: self._data.update({ @@ -176,6 +173,11 @@ class CoreConfig(Config): """Actual version of hassio.""" return self._data.get(HASSIO_LAST) + @property + def path_hassio(self): + """Return hassio data path.""" + return HASSIO_DATA + @property def path_extern_hassio(self): """Return hassio data path extern for docker.""" @@ -189,7 +191,7 @@ class CoreConfig(Config): @property def path_config(self): """Return config path inside supervisor.""" - return Path(HASSIO_SHARE, HOMEASSISTANT_CONFIG) + return Path(HASSIO_DATA, HOMEASSISTANT_CONFIG) @property def path_extern_ssl(self): @@ -199,22 +201,22 @@ class CoreConfig(Config): @property def path_ssl(self): """Return SSL path inside supervisor.""" - return Path(HASSIO_SHARE, HASSIO_SSL) + return Path(HASSIO_DATA, HASSIO_SSL) @property def path_addons_core(self): """Return git path for core addons.""" - return Path(HASSIO_SHARE, ADDONS_CORE) + return Path(HASSIO_DATA, ADDONS_CORE) @property def path_addons_git(self): """Return path for git addons.""" - return Path(HASSIO_SHARE, ADDONS_GIT) + return Path(HASSIO_DATA, ADDONS_GIT) @property def path_addons_local(self): """Return path for customs addons.""" - return Path(HASSIO_SHARE, ADDONS_LOCAL) + return Path(HASSIO_DATA, ADDONS_LOCAL) @property def path_extern_addons_local(self): @@ -224,7 +226,7 @@ class CoreConfig(Config): @property def path_addons_data(self): """Return root addon data folder.""" - return Path(HASSIO_SHARE, ADDONS_DATA) + return Path(HASSIO_DATA, ADDONS_DATA) @property def path_extern_addons_data(self): @@ -232,14 +234,14 @@ class CoreConfig(Config): return PurePath(self.path_extern_hassio, ADDONS_DATA) @property - def path_addons_build(self): - """Return root addon build folder.""" - return Path(HASSIO_SHARE, ADDONS_BUILD) + def path_tmp(self): + """Return hass.io temp folder.""" + return Path(HASSIO_DATA, TMP_DATA) @property def path_backup(self): """Return root backup data folder.""" - return Path(HASSIO_SHARE, BACKUP_DATA) + return Path(HASSIO_DATA, BACKUP_DATA) @property def path_extern_backup(self): @@ -249,7 +251,7 @@ class CoreConfig(Config): @property def path_share(self): """Return root share data folder.""" - return Path(HASSIO_SHARE, SHARE_DATA) + return Path(HASSIO_DATA, SHARE_DATA) @property def path_extern_share(self): diff --git a/hassio/const.py b/hassio/const.py index a3ba6bcac..d1c9df809 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -10,7 +10,7 @@ URL_HASSIO_VERSION_BETA = ('https://raw.githubusercontent.com/home-assistant/' URL_HASSIO_ADDONS = 'https://github.com/home-assistant/hassio-addons' -HASSIO_SHARE = Path("/data") +HASSIO_DATA = Path("/data") RUN_UPDATE_INFO_TASKS = 28800 RUN_UPDATE_SUPERVISOR_TASKS = 29100 @@ -20,8 +20,8 @@ RUN_CLEANUP_API_SESSIONS = 900 RESTART_EXIT_CODE = 100 -FILE_HASSIO_ADDONS = Path(HASSIO_SHARE, "addons.json") -FILE_HASSIO_CONFIG = Path(HASSIO_SHARE, "config.json") +FILE_HASSIO_ADDONS = Path(HASSIO_DATA, "addons.json") +FILE_HASSIO_CONFIG = Path(HASSIO_DATA, "config.json") SOCKET_DOCKER = Path("/var/run/docker.sock") SOCKET_HC = Path("/var/run/hassio-hc.sock") diff --git a/hassio/core.py b/hassio/core.py index 93792476b..93f820b3e 100644 --- a/hassio/core.py +++ b/hassio/core.py @@ -4,7 +4,6 @@ import logging import aiohttp import docker -from . import bootstrap from .addons import AddonManager from .api import RestAPI from .host_control import HostControl @@ -27,28 +26,26 @@ _LOGGER = logging.getLogger(__name__) class HassIO(object): """Main object of hassio.""" - def __init__(self, loop): + def __init__(self, loop, config): """Initialize hassio object.""" self.exit_code = 0 self.loop = loop - self.websession = aiohttp.ClientSession(loop=self.loop) - self.config = bootstrap.initialize_system_data(self.websession) - self.scheduler = Scheduler(self.loop) - self.api = RestAPI(self.config, self.loop) + self.config = config + self.websession = aiohttp.ClientSession(loop=loop) + self.scheduler = Scheduler(loop) + self.api = RestAPI(config, loop) self.dock = docker.DockerClient( base_url="unix:/{}".format(str(SOCKET_DOCKER)), version='auto') # init basic docker container - self.supervisor = DockerSupervisor( - self.config, self.loop, self.dock, self.stop) - self.homeassistant = DockerHomeAssistant( - self.config, self.loop, self.dock) + self.supervisor = DockerSupervisor(config, loop, self.dock, self.stop) + self.homeassistant = DockerHomeAssistant(config, loop, self.dock) # init HostControl - self.host_control = HostControl(self.loop) + self.host_control = HostControl(loop) # init addon system - self.addons = AddonManager(self.config, self.loop, self.dock) + self.addons = AddonManager(config, loop, self.dock) async def setup(self): """Setup HassIO orchestration.""" @@ -72,8 +69,8 @@ class HassIO(object): # schedule update info tasks self.scheduler.register_task( - self.host_control.load, RUN_UPDATE_INFO_TASKS) + # rest api views self.api.register_host(self.host_control) self.api.register_network(self.host_control) @@ -89,16 +86,11 @@ class HassIO(object): api_sessions_cleanup(self.config), RUN_CLEANUP_API_SESSIONS, now=True) - # schedule update info tasks - self.scheduler.register_task( - self.config.fetch_update_infos, RUN_UPDATE_INFO_TASKS, - now=True) - # first start of supervisor? if not await self.homeassistant.exists(): _LOGGER.info("No HomeAssistant docker found.") await homeassistant_setup( - self.config, self.loop, self.homeassistant) + self.config, self.loop, self.homeassistant, self.websession) else: await self.homeassistant.attach() @@ -111,7 +103,7 @@ class HassIO(object): # schedule self update task self.scheduler.register_task( - hassio_update(self.config, self.supervisor), + hassio_update(self.config, self.supervisor, self.websession), RUN_UPDATE_SUPERVISOR_TASKS) # start addon mark as initialize @@ -119,6 +111,14 @@ class HassIO(object): async def start(self): """Start HassIO orchestration.""" + # on release channel, try update itself + # on beta channel, only read new versions + if not self.config.upstream_beta: + await self.loop.create_task( + hassio_update(self.config, self.supervisor, self.websession)()) + else: + await self.config.fetch_update_infos(self.websession) + # start api await self.api.start() _LOGGER.info("Start hassio api on %s", self.config.api_endpoint) diff --git a/hassio/dock/__init__.py b/hassio/dock/__init__.py index ba8c8b2f5..e53a361c4 100644 --- a/hassio/dock/__init__.py +++ b/hassio/dock/__init__.py @@ -267,7 +267,7 @@ class DockerBase(object): """Return docker logs of container.""" if self._lock.locked(): _LOGGER.error("Can't excute logs while a task is in progress") - return False + return b"" async with self._lock: return await self.loop.run_in_executor(None, self._logs) diff --git a/hassio/dock/addon.py b/hassio/dock/addon.py index c0e196787..1f6b1a317 100644 --- a/hassio/dock/addon.py +++ b/hassio/dock/addon.py @@ -145,7 +145,7 @@ class DockerAddon(DockerBase): Need run inside executor. """ - build_dir = Path(self.config.path_addons_build, self.addon.slug) + build_dir = Path(self.config.path_tmp, self.addon.slug) try: # prepare temporary addon build folder try: diff --git a/hassio/tasks.py b/hassio/tasks.py index 32e4c8a6d..824a5dc1c 100644 --- a/hassio/tasks.py +++ b/hassio/tasks.py @@ -18,10 +18,11 @@ def api_sessions_cleanup(config): return _api_sessions_cleanup -def hassio_update(config, supervisor): +def hassio_update(config, supervisor, websession): """Create scheduler task for update of supervisor hassio.""" async def _hassio_update(): """Check and run update of supervisor hassio.""" + await config.fetch_update_infos(websession) if config.last_hassio == supervisor.version: return @@ -43,12 +44,12 @@ def homeassistant_watchdog(loop, homeassistant): return _homeassistant_watchdog -async def homeassistant_setup(config, loop, homeassistant): +async def homeassistant_setup(config, loop, homeassistant, websession): """Install a homeassistant docker container.""" while True: # read homeassistant tag and install it if not config.last_homeassistant: - await config.fetch_update_infos() + await config.fetch_update_infos(websession) tag = config.last_homeassistant if tag and await homeassistant.install(tag):