From 55c2127baa50fa337a7fa37deb61e6a8833eb713 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Sat, 21 Apr 2018 16:30:31 +0200 Subject: [PATCH] Cleanup Loop handling --- API.md | 43 ----------- hassio/addons/__init__.py | 10 +-- hassio/api/__init__.py | 4 +- hassio/api/addons.py | 18 ++--- hassio/api/homeassistant.py | 8 +-- hassio/api/host.py | 2 +- hassio/api/proxy.py | 8 +-- hassio/api/snapshots.py | 16 ++--- hassio/api/supervisor.py | 4 +- hassio/core.py | 2 +- hassio/homeassistant.py | 6 +- hassio/host/network.py | 1 + hassio/host/rauc.py | 1 + hassio/host/system.py | 32 +++++++++ hassio/misc/host_control.py | 124 -------------------------------- hassio/snapshots/__init__.py | 6 +- hassio/snapshots/snapshot.py | 8 +-- hassio/tasks.py | 2 +- hassio/updater.py | 2 +- hassio/utils/dt.py | 2 +- hassio/{misc => utils}/gdbus.py | 0 21 files changed, 81 insertions(+), 218 deletions(-) create mode 100644 hassio/host/network.py create mode 100644 hassio/host/rauc.py create mode 100644 hassio/host/system.py delete mode 100644 hassio/misc/host_control.py rename hassio/{misc => utils}/gdbus.py (100%) diff --git a/API.md b/API.md index 58e5d136c..bd3f72360 100644 --- a/API.md +++ b/API.md @@ -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 diff --git a/hassio/addons/__init__.py b/hassio/addons/__init__.py index d75dfab96..7d3f0db17 100644 --- a/hassio/addons/__init__.py +++ b/hassio/addons/__init__.py @@ -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) diff --git a/hassio/api/__init__.py b/hassio/api/__init__.py index b91d79485..e0727cffe 100644 --- a/hassio/api/__init__.py +++ b/hassio/api/__init__.py @@ -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( diff --git a/hassio/api/addons.py b/hassio/api/addons.py index a4d5b4593..1aba6266c 100644 --- a/hassio/api/addons.py +++ b/hassio/api/addons.py @@ -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)) diff --git a/hassio/api/homeassistant.py b/hassio/api/homeassistant.py index 86bfaf296..2bbe54a68 100644 --- a/hassio/api/homeassistant.py +++ b/hassio/api/homeassistant.py @@ -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): diff --git a/hassio/api/host.py b/hassio/api/host.py index a0b63f2c9..dd22aee34 100644 --- a/hassio/api/host.py +++ b/hassio/api/host.py @@ -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)) diff --git a/hassio/api/proxy.py b/hassio/api/proxy.py index 62bb22dc8..0701d260b 100644 --- a/hassio/api/proxy.py +++ b/hassio/api/proxy.py @@ -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 diff --git a/hassio/api/snapshots.py b/hassio/api/snapshots.py index 46396adbf..9f4922649 100644 --- a/hassio/api/snapshots.py +++ b/hassio/api/snapshots.py @@ -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} diff --git a/hassio/api/supervisor.py b/hassio/api/supervisor.py index 10d35701f..788401aae 100644 --- a/hassio/api/supervisor.py +++ b/hassio/api/supervisor.py @@ -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: diff --git a/hassio/core.py b/hassio/core.py index 6acea8879..06be7708d 100644 --- a/hassio/core.py +++ b/hassio/core.py @@ -114,4 +114,4 @@ class HassIO(CoreSysAttributes): self._dns.stop(), self._websession.close(), self._websession_ssl.close() - ], loop=self._loop) + ]) diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index 899050b83..ff49bfd35 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -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 diff --git a/hassio/host/network.py b/hassio/host/network.py new file mode 100644 index 000000000..a677e893d --- /dev/null +++ b/hassio/host/network.py @@ -0,0 +1 @@ +"""Interface to NetworkManager over dbus.""" diff --git a/hassio/host/rauc.py b/hassio/host/rauc.py new file mode 100644 index 000000000..62098b393 --- /dev/null +++ b/hassio/host/rauc.py @@ -0,0 +1 @@ +"""Interface to Rauc OTA over dbus.""" diff --git a/hassio/host/system.py b/hassio/host/system.py new file mode 100644 index 000000000..d4176992e --- /dev/null +++ b/hassio/host/system.py @@ -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.""" diff --git a/hassio/misc/host_control.py b/hassio/misc/host_control.py deleted file mode 100644 index 848a7e0aa..000000000 --- a/hassio/misc/host_control.py +++ /dev/null @@ -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)) diff --git a/hassio/snapshots/__init__.py b/hassio/snapshots/__init__.py index ade6e355a..d9ec14769 100644 --- a/hassio/snapshots/__init__.py +++ b/hassio/snapshots/__init__.py @@ -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) diff --git a/hassio/snapshots/snapshot.py b/hassio/snapshots/snapshot.py index 7077e9c4e..4ffd4345d 100644 --- a/hassio/snapshots/snapshot.py +++ b/hassio/snapshots/snapshot.py @@ -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.""" diff --git a/hassio/tasks.py b/hassio/tasks.py index 7e8ea6b08..2858be578 100644 --- a/hassio/tasks.py +++ b/hassio/tasks.py @@ -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.""" diff --git a/hassio/updater.py b/hassio/updater.py index ea65a0503..47007c140 100644 --- a/hassio/updater.py +++ b/hassio/updater.py @@ -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) diff --git a/hassio/utils/dt.py b/hassio/utils/dt.py index f8f9601d7..c529c07fc 100644 --- a/hassio/utils/dt.py +++ b/hassio/utils/dt.py @@ -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() diff --git a/hassio/misc/gdbus.py b/hassio/utils/gdbus.py similarity index 100% rename from hassio/misc/gdbus.py rename to hassio/utils/gdbus.py