Use lock on homeassistant level

This commit is contained in:
Pascal Vizeli 2018-03-13 23:09:53 +01:00
parent 1efdcd4691
commit c2918d4519
6 changed files with 99 additions and 93 deletions

View File

@ -6,11 +6,11 @@ import docker
import requests import requests
from .interface import DockerInterface from .interface import DockerInterface
from .utils import docker_process
from ..addons.build import AddonBuild from ..addons.build import AddonBuild
from ..const import ( from ..const import (
MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, MAP_SHARE, ENV_TOKEN, MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, MAP_SHARE, ENV_TOKEN,
ENV_TIME) ENV_TIME)
from ..utils import process_lock
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -285,7 +285,7 @@ class DockerAddon(DockerInterface):
_LOGGER.info("Build %s:%s done", self.image, tag) _LOGGER.info("Build %s:%s done", self.image, tag)
return True return True
@docker_process @process_lock
def export_image(self, path): def export_image(self, path):
"""Export current images into a tar file.""" """Export current images into a tar file."""
return self._loop.run_in_executor(None, self._export_image, path) 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) _LOGGER.info("Export image %s done", self.image)
return True return True
@docker_process @process_lock
def import_image(self, path, tag): def import_image(self, path, tag):
"""Import a tar file as image.""" """Import a tar file as image."""
return self._loop.run_in_executor(None, self._import_image, path, tag) return self._loop.run_in_executor(None, self._import_image, path, tag)
@ -338,7 +338,7 @@ class DockerAddon(DockerInterface):
self._cleanup() self._cleanup()
return True return True
@docker_process @process_lock
def write_stdin(self, data): def write_stdin(self, data):
"""Write to add-on stdin.""" """Write to add-on stdin."""
return self._loop.run_in_executor(None, self._write_stdin, data) return self._loop.run_in_executor(None, self._write_stdin, data)

View File

@ -5,10 +5,10 @@ import logging
import docker import docker
from .utils import docker_process
from .stats import DockerStats from .stats import DockerStats
from ..const import LABEL_VERSION, LABEL_ARCH from ..const import LABEL_VERSION, LABEL_ARCH
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..utils import process_lock
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -20,7 +20,7 @@ class DockerInterface(CoreSysAttributes):
"""Initialize docker base wrapper.""" """Initialize docker base wrapper."""
self.coresys = coresys self.coresys = coresys
self._meta = None self._meta = None
self.lock = asyncio.Lock(loop=self._loop) self.lock = asyncio.Lock(loop=coresys.loop)
@property @property
def timeout(self): def timeout(self):
@ -58,7 +58,7 @@ class DockerInterface(CoreSysAttributes):
"""Return True if a task is in progress.""" """Return True if a task is in progress."""
return self.lock.locked() return self.lock.locked()
@docker_process @process_lock
def install(self, tag): def install(self, tag):
"""Pull docker image.""" """Pull docker image."""
return self._loop.run_in_executor(None, self._install, tag) return self._loop.run_in_executor(None, self._install, tag)
@ -126,7 +126,7 @@ class DockerInterface(CoreSysAttributes):
return True return True
@docker_process @process_lock
def attach(self): def attach(self):
"""Attach to running docker container.""" """Attach to running docker container."""
return self._loop.run_in_executor(None, self._attach) return self._loop.run_in_executor(None, self._attach)
@ -149,7 +149,7 @@ class DockerInterface(CoreSysAttributes):
return True return True
@docker_process @process_lock
def run(self): def run(self):
"""Run docker image.""" """Run docker image."""
return self._loop.run_in_executor(None, self._run) return self._loop.run_in_executor(None, self._run)
@ -161,7 +161,7 @@ class DockerInterface(CoreSysAttributes):
""" """
raise NotImplementedError() raise NotImplementedError()
@docker_process @process_lock
def stop(self): def stop(self):
"""Stop/remove docker container.""" """Stop/remove docker container."""
return self._loop.run_in_executor(None, self._stop) return self._loop.run_in_executor(None, self._stop)
@ -187,7 +187,7 @@ class DockerInterface(CoreSysAttributes):
return True return True
@docker_process @process_lock
def remove(self): def remove(self):
"""Remove docker images.""" """Remove docker images."""
return self._loop.run_in_executor(None, self._remove) return self._loop.run_in_executor(None, self._remove)
@ -219,7 +219,7 @@ class DockerInterface(CoreSysAttributes):
self._meta = None self._meta = None
return True return True
@docker_process @process_lock
def update(self, tag): def update(self, tag):
"""Update a docker image.""" """Update a docker image."""
return self._loop.run_in_executor(None, self._update, tag) return self._loop.run_in_executor(None, self._update, tag)
@ -264,7 +264,7 @@ class DockerInterface(CoreSysAttributes):
except docker.errors.DockerException as err: except docker.errors.DockerException as err:
_LOGGER.warning("Can't grap logs from %s: %s", self.image, err) _LOGGER.warning("Can't grap logs from %s: %s", self.image, err)
@docker_process @process_lock
def restart(self): def restart(self):
"""Restart docker container.""" """Restart docker container."""
return self._loop.run_in_executor(None, self._restart) return self._loop.run_in_executor(None, self._restart)
@ -289,7 +289,7 @@ class DockerInterface(CoreSysAttributes):
return True return True
@docker_process @process_lock
def cleanup(self): def cleanup(self):
"""Check if old version exists and cleanup.""" """Check if old version exists and cleanup."""
return self._loop.run_in_executor(None, self._cleanup) return self._loop.run_in_executor(None, self._cleanup)
@ -315,7 +315,7 @@ class DockerInterface(CoreSysAttributes):
return True return True
@docker_process @process_lock
def execute_command(self, command): def execute_command(self, command):
"""Create a temporary container and run command.""" """Create a temporary container and run command."""
return self._loop.run_in_executor(None, self._execute_command, command) return self._loop.run_in_executor(None, self._execute_command, command)

View File

@ -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

View File

@ -16,7 +16,7 @@ from .const import (
ATTR_WAIT_BOOT, HEADER_HA_ACCESS, CONTENT_TYPE_JSON) ATTR_WAIT_BOOT, HEADER_HA_ACCESS, CONTENT_TYPE_JSON)
from .coresys import CoreSysAttributes from .coresys import CoreSysAttributes
from .docker.homeassistant import DockerHomeAssistant 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 .utils.json import JsonConfig
from .validate import SCHEMA_HASS_CONFIG from .validate import SCHEMA_HASS_CONFIG
@ -35,6 +35,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG) super().__init__(FILE_HASSIO_HOMEASSISTANT, SCHEMA_HASS_CONFIG)
self.coresys = coresys self.coresys = coresys
self.instance = DockerHomeAssistant(coresys) self.instance = DockerHomeAssistant(coresys)
self.lock = asyncio.Lock(loop=coresys.loop)
async def load(self): async def load(self):
"""Prepare HomeAssistant object.""" """Prepare HomeAssistant object."""
@ -162,6 +163,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
"""Return a UUID of this HomeAssistant.""" """Return a UUID of this HomeAssistant."""
return self._data[ATTR_UUID] return self._data[ATTR_UUID]
@process_lock
async def install_landingpage(self): async def install_landingpage(self):
"""Install a landingpage.""" """Install a landingpage."""
_LOGGER.info("Setup HomeAssistant landingpage") _LOGGER.info("Setup HomeAssistant landingpage")
@ -172,8 +174,10 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
await asyncio.sleep(60, loop=self._loop) await asyncio.sleep(60, loop=self._loop)
# Run landingpage after installation # Run landingpage after installation
await self.start() await self.instance.run()
await self._block_till_run()
@process_lock
async def install(self): async def install(self):
"""Install a landingpage.""" """Install a landingpage."""
_LOGGER.info("Setup HomeAssistant") _LOGGER.info("Setup HomeAssistant")
@ -191,9 +195,11 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
# finishing # finishing
_LOGGER.info("HomeAssistant docker now installed") _LOGGER.info("HomeAssistant docker now installed")
if self.boot: if self.boot:
await self.start() await self.instance.run()
await self._block_till_run()
await self.instance.cleanup() await self.instance.cleanup()
@process_lock
async def update(self, version=None): async def update(self, version=None):
"""Update HomeAssistant version.""" """Update HomeAssistant version."""
version = version or self.last_version version = version or self.last_version
@ -208,8 +214,10 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
return await self.instance.update(version) return await self.instance.update(version)
finally: finally:
if running: if running:
await self.start() await self.instance.run()
await self._block_till_run()
@process_lock
async def start(self): async def start(self):
"""Run HomeAssistant docker.""" """Run HomeAssistant docker."""
if not await self.instance.run(): if not await self.instance.run():
@ -224,6 +232,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
""" """
return self.instance.stop() return self.instance.stop()
@process_lock
async def restart(self): async def restart(self):
"""Restart HomeAssistant docker.""" """Restart HomeAssistant docker."""
if not await self.instance.restart(): if not await self.instance.restart():
@ -262,7 +271,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
@property @property
def in_progress(self): def in_progress(self):
"""Return True if a task is in progress.""" """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): async def check_config(self):
"""Run homeassistant config check.""" """Run homeassistant config check."""

View File

@ -1,7 +1,9 @@
"""Tools file for HassIO.""" """Tools file for HassIO."""
from datetime import datetime from datetime import datetime
import logging
import re import re
_LOGGER = logging.getLogger(__name__)
RE_STRING = re.compile(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))") RE_STRING = re.compile(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))")
@ -10,6 +12,21 @@ def convert_to_ascii(raw):
return RE_STRING.sub("", raw.decode()) 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): class AsyncThrottle(object):
""" """
Decorator that prevents a function from being called more than once every Decorator that prevents a function from being called more than once every

106
setup.py
View File

@ -1,53 +1,53 @@
from setuptools import setup from setuptools import setup
from hassio.const import HASSIO_VERSION from hassio.const import HASSIO_VERSION
setup( setup(
name='HassIO', name='HassIO',
version=HASSIO_VERSION, version=HASSIO_VERSION,
license='BSD License', license='BSD License',
author='The Home Assistant Authors', author='The Home Assistant Authors',
author_email='hello@home-assistant.io', author_email='hello@home-assistant.io',
url='https://home-assistant.io/', url='https://home-assistant.io/',
description=('Open-source private cloud os for Home-Assistant' description=('Open-source private cloud os for Home-Assistant'
' based on ResinOS'), ' based on ResinOS'),
long_description=('A maintainless private cloud operator system that' long_description=('A maintainless private cloud operator system that'
'setup a Home-Assistant instance. Based on ResinOS'), 'setup a Home-Assistant instance. Based on ResinOS'),
classifiers=[ classifiers=[
'Intended Audience :: End Users/Desktop', 'Intended Audience :: End Users/Desktop',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License', 'License :: OSI Approved :: Apache Software License',
'Operating System :: OS Independent', 'Operating System :: OS Independent',
'Topic :: Home Automation' 'Topic :: Home Automation'
'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Scientific/Engineering :: Atmospheric Science', 'Topic :: Scientific/Engineering :: Atmospheric Science',
'Development Status :: 5 - Production/Stable', 'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.6',
], ],
keywords=['docker', 'home-assistant', 'api'], keywords=['docker', 'home-assistant', 'api'],
zip_safe=False, zip_safe=False,
platforms='any', platforms='any',
packages=[ packages=[
'hassio', 'hassio',
'hassio.docker', 'hassio.docker',
'hassio.addons', 'hassio.addons',
'hassio.api', 'hassio.api',
'hassio.misc', 'hassio.misc',
'hassio.utils', 'hassio.utils',
'hassio.snapshots' 'hassio.snapshots'
], ],
include_package_data=True, include_package_data=True,
install_requires=[ install_requires=[
'async_timeout==2.0.0', 'async_timeout==2.0.0',
'aiohttp==3.0.7', 'aiohttp==3.0.7',
'docker==3.1.1', 'docker==3.1.1',
'colorlog==3.1.2', 'colorlog==3.1.2',
'voluptuous==0.11.1', 'voluptuous==0.11.1',
'gitpython==2.1.8', 'gitpython==2.1.8',
'pytz==2018.3', 'pytz==2018.3',
'pyudev==0.21.0', 'pyudev==0.21.0',
'pycryptodome==3.4.11' 'pycryptodome==3.4.11'
] ]
) )