Cleanup Loop handling

This commit is contained in:
Pascal Vizeli 2018-04-21 16:30:31 +02:00
parent 265c36b345
commit 55c2127baa
21 changed files with 81 additions and 218 deletions

43
API.md
View File

@ -613,46 +613,3 @@ This service perform a auto discovery to Home-Assistant.
```
- DEL `/services/mqtt`
## Host Control
Communicate over UNIX socket with a host daemon.
- commands
```
# info
-> {'type', 'version', 'last_version', 'features', 'hostname'}
# reboot
# shutdown
# host-update [v]
# hostname xy
# network info
-> {}
# network wlan ssd xy
# network wlan password xy
# network int ip xy
# network int netmask xy
# network int route xy
```
Features:
- shutdown
- reboot
- update
- hostname
- network_info
- network_control
Answer:
```
{}|OK|ERROR|WRONG
```
- {}: json
- OK: call was successfully
- ERROR: error on call
- WRONG: not supported

View File

@ -66,7 +66,7 @@ class AddonManager(CoreSysAttributes):
tasks = [repository.update() for repository in
self.repositories_obj.values()]
if tasks:
await asyncio.wait(tasks, loop=self._loop)
await asyncio.wait(tasks)
# read data from repositories
self.data.reload()
@ -94,7 +94,7 @@ class AddonManager(CoreSysAttributes):
tasks = [_add_repository(url) for url in new_rep - old_rep]
if tasks:
await asyncio.wait(tasks, loop=self._loop)
await asyncio.wait(tasks)
# del new repository
for url in old_rep - new_rep - BUILTIN_REPOSITORIES:
@ -125,7 +125,7 @@ class AddonManager(CoreSysAttributes):
self.addons_obj[addon_slug] = addon
if tasks:
await asyncio.wait(tasks, loop=self._loop)
await asyncio.wait(tasks)
# remove
for addon_slug in del_addons:
@ -141,5 +141,5 @@ class AddonManager(CoreSysAttributes):
_LOGGER.info("Startup %s run %d addons", stage, len(tasks))
if tasks:
await asyncio.wait(tasks, loop=self._loop)
await asyncio.sleep(self._config.wait_boot, loop=self._loop)
await asyncio.wait(tasks)
await asyncio.sleep(self._config.wait_boot)

View File

@ -28,7 +28,7 @@ class RestAPI(CoreSysAttributes):
self.coresys = coresys
self.security = SecurityMiddleware(coresys)
self.webapp = web.Application(
middlewares=[self.security.token_validation], loop=self._loop)
middlewares=[self.security.token_validation], loop=coresys.loop)
# service stuff
self._handler = None
@ -221,7 +221,7 @@ class RestAPI(CoreSysAttributes):
async def start(self):
"""Run rest api webserver."""
self._handler = self.webapp.make_handler(loop=self._loop)
self._handler = self.webapp.make_handler()
try:
self.server = await self._loop.create_server(

View File

@ -98,7 +98,7 @@ class APIAddons(CoreSysAttributes):
@api_process
async def reload(self, request):
"""Reload all addons data."""
await asyncio.shield(self._addons.reload(), loop=self._loop)
await asyncio.shield(self._addons.reload())
return True
@api_process
@ -194,13 +194,13 @@ class APIAddons(CoreSysAttributes):
def install(self, request):
"""Install addon."""
addon = self._extract_addon(request, check_installed=False)
return asyncio.shield(addon.install(), loop=self._loop)
return asyncio.shield(addon.install())
@api_process
def uninstall(self, request):
"""Uninstall addon."""
addon = self._extract_addon(request)
return asyncio.shield(addon.uninstall(), loop=self._loop)
return asyncio.shield(addon.uninstall())
@api_process
def start(self, request):
@ -214,13 +214,13 @@ class APIAddons(CoreSysAttributes):
except vol.Invalid as ex:
raise RuntimeError(humanize_error(options, ex)) from None
return asyncio.shield(addon.start(), loop=self._loop)
return asyncio.shield(addon.start())
@api_process
def stop(self, request):
"""Stop addon."""
addon = self._extract_addon(request)
return asyncio.shield(addon.stop(), loop=self._loop)
return asyncio.shield(addon.stop())
@api_process
def update(self, request):
@ -230,13 +230,13 @@ class APIAddons(CoreSysAttributes):
if addon.last_version == addon.version_installed:
raise RuntimeError("No update available!")
return asyncio.shield(addon.update(), loop=self._loop)
return asyncio.shield(addon.update())
@api_process
def restart(self, request):
"""Restart addon."""
addon = self._extract_addon(request)
return asyncio.shield(addon.restart(), loop=self._loop)
return asyncio.shield(addon.restart())
@api_process
def rebuild(self, request):
@ -245,7 +245,7 @@ class APIAddons(CoreSysAttributes):
if not addon.need_build:
raise RuntimeError("Only local build addons are supported")
return asyncio.shield(addon.rebuild(), loop=self._loop)
return asyncio.shield(addon.rebuild())
@api_process_raw(CONTENT_TYPE_BINARY)
def logs(self, request):
@ -291,4 +291,4 @@ class APIAddons(CoreSysAttributes):
raise RuntimeError("STDIN not supported by addon")
data = await request.read()
return await asyncio.shield(addon.write_stdin(data), loop=self._loop)
return await asyncio.shield(addon.write_stdin(data))

View File

@ -111,22 +111,22 @@ class APIHomeAssistant(CoreSysAttributes):
raise RuntimeError("Version {} is already in use".format(version))
return await asyncio.shield(
self._homeassistant.update(version), loop=self._loop)
self._homeassistant.update(version))
@api_process
def stop(self, request):
"""Stop homeassistant."""
return asyncio.shield(self._homeassistant.stop(), loop=self._loop)
return asyncio.shield(self._homeassistant.stop())
@api_process
def start(self, request):
"""Start homeassistant."""
return asyncio.shield(self._homeassistant.start(), loop=self._loop)
return asyncio.shield(self._homeassistant.start())
@api_process
def restart(self, request):
"""Restart homeassistant."""
return asyncio.shield(self._homeassistant.restart(), loop=self._loop)
return asyncio.shield(self._homeassistant.restart())
@api_process_raw(CONTENT_TYPE_BINARY)
def logs(self, request):

View File

@ -58,4 +58,4 @@ class APIHost(CoreSysAttributes):
raise RuntimeError(f"Version {version} is already in use")
return await asyncio.shield(
self._host_control.update(version=version), loop=self._loop)
self._host_control.update(version=version))

View File

@ -38,7 +38,7 @@ class APIProxy(CoreSysAttributes):
params = request.query or None
# read data
with async_timeout.timeout(30, loop=self._loop):
with async_timeout.timeout(30):
data = await request.read()
if data:
@ -181,15 +181,15 @@ class APIProxy(CoreSysAttributes):
while not server.closed and not client.closed:
if not client_read:
client_read = asyncio.ensure_future(
client.receive_str(), loop=self._loop)
client.receive_str())
if not server_read:
server_read = asyncio.ensure_future(
server.receive_str(), loop=self._loop)
server.receive_str())
# wait until data need to be processed
await asyncio.wait(
[client_read, server_read],
loop=self._loop, return_when=asyncio.FIRST_COMPLETED
return_when=asyncio.FIRST_COMPLETED
)
# server

View File

@ -75,7 +75,7 @@ class APISnapshots(CoreSysAttributes):
@api_process
async def reload(self, request):
"""Reload snapshot list."""
await asyncio.shield(self._snapshots.reload(), loop=self._loop)
await asyncio.shield(self._snapshots.reload())
return True
@api_process
@ -110,7 +110,7 @@ class APISnapshots(CoreSysAttributes):
"""Full-Snapshot a snapshot."""
body = await api_validate(SCHEMA_SNAPSHOT_FULL, request)
snapshot = await asyncio.shield(
self._snapshots.do_snapshot_full(**body), loop=self._loop)
self._snapshots.do_snapshot_full(**body))
if snapshot:
return {ATTR_SLUG: snapshot.slug}
@ -121,7 +121,7 @@ class APISnapshots(CoreSysAttributes):
"""Partial-Snapshot a snapshot."""
body = await api_validate(SCHEMA_SNAPSHOT_PARTIAL, request)
snapshot = await asyncio.shield(
self._snapshots.do_snapshot_partial(**body), loop=self._loop)
self._snapshots.do_snapshot_partial(**body))
if snapshot:
return {ATTR_SLUG: snapshot.slug}
@ -134,9 +134,7 @@ class APISnapshots(CoreSysAttributes):
body = await api_validate(SCHEMA_RESTORE_FULL, request)
return await asyncio.shield(
self._snapshots.do_restore_full(snapshot, **body),
loop=self._loop
)
self._snapshots.do_restore_full(snapshot, **body))
@api_process
async def restore_partial(self, request):
@ -145,9 +143,7 @@ class APISnapshots(CoreSysAttributes):
body = await api_validate(SCHEMA_RESTORE_PARTIAL, request)
return await asyncio.shield(
self._snapshots.do_restore_partial(snapshot, **body),
loop=self._loop
)
self._snapshots.do_restore_partial(snapshot, **body))
@api_process
async def remove(self, request):
@ -183,7 +179,7 @@ class APISnapshots(CoreSysAttributes):
return False
snapshot = await asyncio.shield(
self._snapshots.import_snapshot(tar_file), loop=self._loop)
self._snapshots.import_snapshot(tar_file))
if snapshot:
return {ATTR_SLUG: snapshot.slug}

View File

@ -115,7 +115,7 @@ class APISupervisor(CoreSysAttributes):
raise RuntimeError("Version {} is already in use".format(version))
return await asyncio.shield(
self._supervisor.update(version), loop=self._loop)
self._supervisor.update(version))
@api_process
async def reload(self, request):
@ -124,7 +124,7 @@ class APISupervisor(CoreSysAttributes):
self._updater.reload(),
]
results, _ = await asyncio.shield(
asyncio.wait(tasks, loop=self._loop), loop=self._loop)
asyncio.wait(tasks))
for result in results:
if result.exception() is not None:

View File

@ -114,4 +114,4 @@ class HassIO(CoreSysAttributes):
self._dns.stop(),
self._websession.close(),
self._websession_ssl.close()
], loop=self._loop)
])

View File

@ -177,7 +177,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
if await self.instance.install('landingpage'):
break
_LOGGER.warning("Fails install landingpage, retry after 60sec")
await asyncio.sleep(60, loop=self._loop)
await asyncio.sleep(60)
# Run landingpage after installation
await self._start()
@ -195,7 +195,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
if tag and await self.instance.install(tag):
break
_LOGGER.warning("Error on install HomeAssistant. Retry in 60sec")
await asyncio.sleep(60, loop=self._loop)
await asyncio.sleep(60)
# finishing
_LOGGER.info("HomeAssistant docker now installed")
@ -364,7 +364,7 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
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)
await asyncio.sleep(10)
_LOGGER.warning("Don't wait anymore of Home-Assistant startup!")
return False

1
hassio/host/network.py Normal file
View File

@ -0,0 +1 @@
"""Interface to NetworkManager over dbus."""

1
hassio/host/rauc.py Normal file
View File

@ -0,0 +1 @@
"""Interface to Rauc OTA over dbus."""

32
hassio/host/system.py Normal file
View File

@ -0,0 +1,32 @@
"""Interface to Systemd over dbus."""
from ..utils.gdbus import DBus, DBusError
DBUS_NAME = 'org.freedesktop.systemd1'
DBUS_OBJECT = '/org/freedesktop/systemd1/Manager'
class System(object):
"""Systemd function handler."""
def __init__(self):
"""Initialize systemd."""
self.dbus = None
async def load(self):
"""Connect do bus."""
try:
self.dbus = await DBus.connect(DBUS_NAME, DBUS_OBJECT)
except DBusError:
return
async def reboot():
"""Reboot host computer."""
try:
await self.dbus.Reboot()
except DBusError:
_LOGGER.error("Can't reboot host")
async def shutdown():
"""Shutdown host computer."""

View File

@ -1,124 +0,0 @@
"""Host control for HassIO."""
import asyncio
import json
import logging
import async_timeout
from ..const import (
SOCKET_HC, ATTR_LAST_VERSION, ATTR_VERSION, ATTR_TYPE, ATTR_FEATURES,
ATTR_HOSTNAME, ATTR_OS)
_LOGGER = logging.getLogger(__name__)
TIMEOUT = 15
UNKNOWN = 'unknown'
FEATURES_SHUTDOWN = 'shutdown'
FEATURES_REBOOT = 'reboot'
FEATURES_UPDATE = 'update'
FEATURES_HOSTNAME = 'hostname'
FEATURES_NETWORK_INFO = 'network_info'
FEATURES_NETWORK_CONTROL = 'network_control'
class HostControl(object):
"""Client for host control."""
def __init__(self, loop):
"""Initialize HostControl socket client."""
self.loop = loop
self.active = False
self.version = UNKNOWN
self.last_version = UNKNOWN
self.type = UNKNOWN
self.features = []
self.hostname = UNKNOWN
self.os_info = UNKNOWN
if SOCKET_HC.is_socket():
self.active = True
async def _send_command(self, command):
"""Send command to host.
Is a coroutine.
"""
if not self.active:
return
reader, writer = await asyncio.open_unix_connection(
str(SOCKET_HC), loop=self.loop)
try:
# send
_LOGGER.info("Send '%s' to HostControl.", command)
with async_timeout.timeout(TIMEOUT, loop=self.loop):
writer.write("{}\n".format(command).encode())
data = await reader.readline()
response = data.decode().rstrip()
_LOGGER.info("Receive from HostControl: %s.", response)
if response == "OK":
return True
elif response == "ERROR":
return False
elif response == "WRONG":
return None
else:
try:
return json.loads(response)
except json.JSONDecodeError:
_LOGGER.warning("Json parse error from HostControl '%s'.",
response)
except asyncio.TimeoutError:
_LOGGER.error("Timeout from HostControl!")
finally:
writer.close()
async def load(self):
"""Load Info from host.
Return a coroutine.
"""
info = await self._send_command("info")
if not info:
return
self.version = info.get(ATTR_VERSION, UNKNOWN)
self.last_version = info.get(ATTR_LAST_VERSION, UNKNOWN)
self.type = info.get(ATTR_TYPE, UNKNOWN)
self.features = info.get(ATTR_FEATURES, [])
self.hostname = info.get(ATTR_HOSTNAME, UNKNOWN)
self.os_info = info.get(ATTR_OS, UNKNOWN)
def reboot(self):
"""Reboot the host system.
Return a coroutine.
"""
return self._send_command("reboot")
def shutdown(self):
"""Shutdown the host system.
Return a coroutine.
"""
return self._send_command("shutdown")
def update(self, version=None):
"""Update the host system.
Return a coroutine.
"""
if version:
return self._send_command("update {}".format(version))
return self._send_command("update")
def set_hostname(self, hostname):
"""Update hostname on host."""
return self._send_command("hostname {}".format(hostname))

View File

@ -69,7 +69,7 @@ class SnapshotManager(CoreSysAttributes):
_LOGGER.info("Found %d snapshot files", len(tasks))
if tasks:
await asyncio.wait(tasks, loop=self._loop)
await asyncio.wait(tasks)
def remove(self, snapshot):
"""Remove a snapshot."""
@ -229,7 +229,7 @@ class SnapshotManager(CoreSysAttributes):
if tasks:
_LOGGER.info("Restore %s stop tasks", snapshot.slug)
await asyncio.wait(tasks, loop=self._loop)
await asyncio.wait(tasks)
# Restore folders
_LOGGER.info("Restore %s run folders", snapshot.slug)
@ -253,7 +253,7 @@ class SnapshotManager(CoreSysAttributes):
if tasks:
_LOGGER.info("Restore %s remove add-ons", snapshot.slug)
await asyncio.wait(tasks, loop=self._loop)
await asyncio.wait(tasks)
# Restore add-ons
_LOGGER.info("Restore %s old add-ons", snapshot.slug)

View File

@ -273,7 +273,7 @@ class Snapshot(CoreSysAttributes):
# Run tasks
tasks = [_addon_save(addon) for addon in addon_list]
if tasks:
await asyncio.wait(tasks, loop=self._loop)
await asyncio.wait(tasks)
async def restore_addons(self, addon_list=None):
"""Restore a list add-on from snapshot."""
@ -303,7 +303,7 @@ class Snapshot(CoreSysAttributes):
# Run tasks
tasks = [_addon_restore(addon) for addon in addon_list]
if tasks:
await asyncio.wait(tasks, loop=self._loop)
await asyncio.wait(tasks)
async def store_folders(self, folder_list=None):
"""Backup hassio data into snapshot."""
@ -335,7 +335,7 @@ class Snapshot(CoreSysAttributes):
tasks = [self._loop.run_in_executor(None, _folder_save, folder)
for folder in folder_list]
if tasks:
await asyncio.wait(tasks, loop=self._loop)
await asyncio.wait(tasks)
async def restore_folders(self, folder_list=None):
"""Backup hassio data into snapshot."""
@ -369,7 +369,7 @@ class Snapshot(CoreSysAttributes):
tasks = [self._loop.run_in_executor(None, _folder_restore, folder)
for folder in folder_list]
if tasks:
await asyncio.wait(tasks, loop=self._loop)
await asyncio.wait(tasks)
def store_homeassistant(self):
"""Read all data from homeassistant object."""

View File

@ -70,7 +70,7 @@ class Tasks(CoreSysAttributes):
if tasks:
_LOGGER.info("Addon auto update process %d tasks", len(tasks))
await asyncio.wait(tasks, loop=self._loop)
await asyncio.wait(tasks)
async def _update_supervisor(self):
"""Check and run update of supervisor hassio."""

View File

@ -68,7 +68,7 @@ class Updater(JsonConfig, CoreSysAttributes):
url = URL_HASSIO_VERSION.format(CHANNEL_TO_BRANCH[self.channel])
try:
_LOGGER.info("Fetch update data from %s", url)
with async_timeout.timeout(10, loop=self._loop):
with async_timeout.timeout(10):
async with self._websession.get(url) as request:
data = await request.json(content_type=None)

View File

@ -29,7 +29,7 @@ async def fetch_timezone(websession):
"""Read timezone from freegeoip."""
data = {}
try:
with async_timeout.timeout(10, loop=websession.loop):
with async_timeout.timeout(10):
async with websession.get(FREEGEOIP_URL) as request:
data = await request.json()