From c2918d4519a69e11a91284c0a9c7379c484a9754 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 13 Mar 2018 23:09:53 +0100 Subject: [PATCH] Use lock on homeassistant level --- hassio/docker/addon.py | 8 +-- hassio/docker/interface.py | 22 ++++---- hassio/docker/utils.py | 20 ------- hassio/homeassistant.py | 19 +++++-- hassio/utils/__init__.py | 17 ++++++ setup.py | 106 ++++++++++++++++++------------------- 6 files changed, 99 insertions(+), 93 deletions(-) delete mode 100644 hassio/docker/utils.py diff --git a/hassio/docker/addon.py b/hassio/docker/addon.py index 1f6986a4e..e60a99202 100644 --- a/hassio/docker/addon.py +++ b/hassio/docker/addon.py @@ -6,11 +6,11 @@ import docker import requests from .interface import DockerInterface -from .utils import docker_process from ..addons.build import AddonBuild from ..const import ( MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, MAP_SHARE, ENV_TOKEN, ENV_TIME) +from ..utils import process_lock _LOGGER = logging.getLogger(__name__) @@ -285,7 +285,7 @@ class DockerAddon(DockerInterface): _LOGGER.info("Build %s:%s done", self.image, tag) return True - @docker_process + @process_lock def export_image(self, path): """Export current images into a tar file.""" return self._loop.run_in_executor(None, self._export_image, path) @@ -313,7 +313,7 @@ class DockerAddon(DockerInterface): _LOGGER.info("Export image %s done", self.image) return True - @docker_process + @process_lock def import_image(self, path, tag): """Import a tar file as image.""" return self._loop.run_in_executor(None, self._import_image, path, tag) @@ -338,7 +338,7 @@ class DockerAddon(DockerInterface): self._cleanup() return True - @docker_process + @process_lock def write_stdin(self, data): """Write to add-on stdin.""" return self._loop.run_in_executor(None, self._write_stdin, data) diff --git a/hassio/docker/interface.py b/hassio/docker/interface.py index 99596de37..69e69052b 100644 --- a/hassio/docker/interface.py +++ b/hassio/docker/interface.py @@ -5,10 +5,10 @@ import logging import docker -from .utils import docker_process from .stats import DockerStats from ..const import LABEL_VERSION, LABEL_ARCH from ..coresys import CoreSysAttributes +from ..utils import process_lock _LOGGER = logging.getLogger(__name__) @@ -20,7 +20,7 @@ class DockerInterface(CoreSysAttributes): """Initialize docker base wrapper.""" self.coresys = coresys self._meta = None - self.lock = asyncio.Lock(loop=self._loop) + self.lock = asyncio.Lock(loop=coresys.loop) @property def timeout(self): @@ -58,7 +58,7 @@ class DockerInterface(CoreSysAttributes): """Return True if a task is in progress.""" return self.lock.locked() - @docker_process + @process_lock def install(self, tag): """Pull docker image.""" return self._loop.run_in_executor(None, self._install, tag) @@ -126,7 +126,7 @@ class DockerInterface(CoreSysAttributes): return True - @docker_process + @process_lock def attach(self): """Attach to running docker container.""" return self._loop.run_in_executor(None, self._attach) @@ -149,7 +149,7 @@ class DockerInterface(CoreSysAttributes): return True - @docker_process + @process_lock def run(self): """Run docker image.""" return self._loop.run_in_executor(None, self._run) @@ -161,7 +161,7 @@ class DockerInterface(CoreSysAttributes): """ raise NotImplementedError() - @docker_process + @process_lock def stop(self): """Stop/remove docker container.""" return self._loop.run_in_executor(None, self._stop) @@ -187,7 +187,7 @@ class DockerInterface(CoreSysAttributes): return True - @docker_process + @process_lock def remove(self): """Remove docker images.""" return self._loop.run_in_executor(None, self._remove) @@ -219,7 +219,7 @@ class DockerInterface(CoreSysAttributes): self._meta = None return True - @docker_process + @process_lock def update(self, tag): """Update a docker image.""" return self._loop.run_in_executor(None, self._update, tag) @@ -264,7 +264,7 @@ class DockerInterface(CoreSysAttributes): except docker.errors.DockerException as err: _LOGGER.warning("Can't grap logs from %s: %s", self.image, err) - @docker_process + @process_lock def restart(self): """Restart docker container.""" return self._loop.run_in_executor(None, self._restart) @@ -289,7 +289,7 @@ class DockerInterface(CoreSysAttributes): return True - @docker_process + @process_lock def cleanup(self): """Check if old version exists and cleanup.""" return self._loop.run_in_executor(None, self._cleanup) @@ -315,7 +315,7 @@ class DockerInterface(CoreSysAttributes): return True - @docker_process + @process_lock def execute_command(self, command): """Create a temporary container and run command.""" return self._loop.run_in_executor(None, self._execute_command, command) diff --git a/hassio/docker/utils.py b/hassio/docker/utils.py deleted file mode 100644 index 617dd27b3..000000000 --- a/hassio/docker/utils.py +++ /dev/null @@ -1,20 +0,0 @@ -"""HassIO docker utilitys.""" -import logging - -_LOGGER = logging.getLogger(__name__) - - -# pylint: disable=protected-access -def docker_process(method): - """Wrap function with only run once.""" - async def wrap_api(api, *args, **kwargs): - """Return api wrapper.""" - if api.lock.locked(): - _LOGGER.error( - "Can't excute %s while a task is in progress", method.__name__) - return False - - async with api.lock: - return await method(api, *args, **kwargs) - - return wrap_api diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index 1b12664fe..90df70de5 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -16,7 +16,7 @@ from .const import ( ATTR_WAIT_BOOT, HEADER_HA_ACCESS, CONTENT_TYPE_JSON) from .coresys import CoreSysAttributes from .docker.homeassistant import DockerHomeAssistant -from .utils import convert_to_ascii +from .utils import convert_to_ascii, process_lock from .utils.json import JsonConfig from .validate import SCHEMA_HASS_CONFIG @@ -35,6 +35,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG) self.coresys = coresys self.instance = DockerHomeAssistant(coresys) + self.lock = asyncio.Lock(loop=coresys.loop) async def load(self): """Prepare HomeAssistant object.""" @@ -162,6 +163,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): """Return a UUID of this HomeAssistant.""" return self._data[ATTR_UUID] + @process_lock async def install_landingpage(self): """Install a landingpage.""" _LOGGER.info("Setup HomeAssistant landingpage") @@ -172,8 +174,10 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): await asyncio.sleep(60, loop=self._loop) # Run landingpage after installation - await self.start() + await self.instance.run() + await self._block_till_run() + @process_lock async def install(self): """Install a landingpage.""" _LOGGER.info("Setup HomeAssistant") @@ -191,9 +195,11 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): # finishing _LOGGER.info("HomeAssistant docker now installed") if self.boot: - await self.start() + await self.instance.run() + await self._block_till_run() await self.instance.cleanup() + @process_lock async def update(self, version=None): """Update HomeAssistant version.""" version = version or self.last_version @@ -208,8 +214,10 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): return await self.instance.update(version) finally: if running: - await self.start() + await self.instance.run() + await self._block_till_run() + @process_lock async def start(self): """Run HomeAssistant docker.""" if not await self.instance.run(): @@ -224,6 +232,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): """ return self.instance.stop() + @process_lock async def restart(self): """Restart HomeAssistant docker.""" if not await self.instance.restart(): @@ -262,7 +271,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): @property def in_progress(self): """Return True if a task is in progress.""" - return self.instance.in_progress + return self.instance.in_progress or self.lock.locked() async def check_config(self): """Run homeassistant config check.""" diff --git a/hassio/utils/__init__.py b/hassio/utils/__init__.py index 2dd85814e..0e960af4e 100644 --- a/hassio/utils/__init__.py +++ b/hassio/utils/__init__.py @@ -1,7 +1,9 @@ """Tools file for HassIO.""" from datetime import datetime +import logging import re +_LOGGER = logging.getLogger(__name__) RE_STRING = re.compile(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))") @@ -10,6 +12,21 @@ def convert_to_ascii(raw): return RE_STRING.sub("", raw.decode()) +def process_lock(method): + """Wrap function with only run once.""" + async def wrap_api(api, *args, **kwargs): + """Return api wrapper.""" + if api.lock.locked(): + _LOGGER.error( + "Can't excute %s while a task is in progress", method.__name__) + return False + + async with api.lock: + return await method(api, *args, **kwargs) + + return wrap_api + + class AsyncThrottle(object): """ Decorator that prevents a function from being called more than once every diff --git a/setup.py b/setup.py index fb13f6294..d47002305 100644 --- a/setup.py +++ b/setup.py @@ -1,53 +1,53 @@ -from setuptools import setup - -from hassio.const import HASSIO_VERSION - - -setup( - name='HassIO', - version=HASSIO_VERSION, - license='BSD License', - author='The Home Assistant Authors', - author_email='hello@home-assistant.io', - url='https://home-assistant.io/', - description=('Open-source private cloud os for Home-Assistant' - ' based on ResinOS'), - long_description=('A maintainless private cloud operator system that' - 'setup a Home-Assistant instance. Based on ResinOS'), - classifiers=[ - 'Intended Audience :: End Users/Desktop', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: OS Independent', - 'Topic :: Home Automation' - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Scientific/Engineering :: Atmospheric Science', - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Programming Language :: Python :: 3.6', - ], - keywords=['docker', 'home-assistant', 'api'], - zip_safe=False, - platforms='any', - packages=[ - 'hassio', - 'hassio.docker', - 'hassio.addons', - 'hassio.api', - 'hassio.misc', - 'hassio.utils', - 'hassio.snapshots' - ], - include_package_data=True, - install_requires=[ - 'async_timeout==2.0.0', - 'aiohttp==3.0.7', - 'docker==3.1.1', - 'colorlog==3.1.2', - 'voluptuous==0.11.1', - 'gitpython==2.1.8', - 'pytz==2018.3', - 'pyudev==0.21.0', - 'pycryptodome==3.4.11' - ] -) +from setuptools import setup + +from hassio.const import HASSIO_VERSION + + +setup( + name='HassIO', + version=HASSIO_VERSION, + license='BSD License', + author='The Home Assistant Authors', + author_email='hello@home-assistant.io', + url='https://home-assistant.io/', + description=('Open-source private cloud os for Home-Assistant' + ' based on ResinOS'), + long_description=('A maintainless private cloud operator system that' + 'setup a Home-Assistant instance. Based on ResinOS'), + classifiers=[ + 'Intended Audience :: End Users/Desktop', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Topic :: Home Automation' + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Scientific/Engineering :: Atmospheric Science', + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Programming Language :: Python :: 3.6', + ], + keywords=['docker', 'home-assistant', 'api'], + zip_safe=False, + platforms='any', + packages=[ + 'hassio', + 'hassio.docker', + 'hassio.addons', + 'hassio.api', + 'hassio.misc', + 'hassio.utils', + 'hassio.snapshots' + ], + include_package_data=True, + install_requires=[ + 'async_timeout==2.0.0', + 'aiohttp==3.0.7', + 'docker==3.1.1', + 'colorlog==3.1.2', + 'voluptuous==0.11.1', + 'gitpython==2.1.8', + 'pytz==2018.3', + 'pyudev==0.21.0', + 'pycryptodome==3.4.11' + ] +)