Slowdown snapshots to make it faster on slow IO (#1803)

* Slowdown snapshots to make it faster on slow IO

* Fix error handling

* fix lint
This commit is contained in:
Pascal Vizeli 2020-06-28 10:58:13 +02:00 committed by GitHub
parent 5a3ebaf683
commit 7c5f710deb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 53 additions and 47 deletions

View File

@ -5,6 +5,7 @@ from pathlib import Path
from ..const import FOLDER_HOMEASSISTANT, SNAPSHOT_FULL, SNAPSHOT_PARTIAL, CoreStates from ..const import FOLDER_HOMEASSISTANT, SNAPSHOT_FULL, SNAPSHOT_PARTIAL, CoreStates
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..exceptions import AddonsError
from ..utils.dt import utcnow from ..utils.dt import utcnow
from .snapshot import Snapshot from .snapshot import Snapshot
from .utils import create_slug from .utils import create_slug
@ -219,8 +220,6 @@ class SnapshotManager(CoreSysAttributes):
await self.lock.acquire() await self.lock.acquire()
async with snapshot: async with snapshot:
tasks = []
# Stop Home-Assistant / Add-ons # Stop Home-Assistant / Add-ons
await self.sys_core.shutdown() await self.sys_core.shutdown()
@ -240,14 +239,17 @@ class SnapshotManager(CoreSysAttributes):
await snapshot.restore_repositories() await snapshot.restore_repositories()
# Delete delta add-ons # Delete delta add-ons
tasks.clear() _LOGGER.info("Restore %s remove add-ons", snapshot.slug)
for addon in self.sys_addons.installed: for addon in self.sys_addons.installed:
if addon.slug not in snapshot.addon_list: if addon.slug in snapshot.addon_list:
tasks.append(addon.uninstall()) continue
if tasks: # Remove Add-on because it's not a part of the new env
_LOGGER.info("Restore %s remove add-ons", snapshot.slug) # Do it sequential avoid issue on slow IO
await asyncio.wait(tasks) try:
await addon.uninstall()
except AddonsError:
_LOGGER.warning("Can't uninstall Add-on %s", addon.slug)
# Restore add-ons # Restore add-ons
_LOGGER.info("Restore %s old add-ons", snapshot.slug) _LOGGER.info("Restore %s old add-ons", snapshot.slug)

View File

@ -1,12 +1,11 @@
"""Representation of a snapshot file.""" """Representation of a snapshot file."""
import asyncio
from base64 import b64decode, b64encode from base64 import b64decode, b64encode
import json import json
import logging import logging
from pathlib import Path from pathlib import Path
import tarfile import tarfile
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from typing import Any, Dict, Optional from typing import Any, Dict, List, Optional, Set
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding from cryptography.hazmat.primitives import padding
@ -14,6 +13,7 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import voluptuous as vol import voluptuous as vol
from voluptuous.humanize import humanize_error from voluptuous.humanize import humanize_error
from ..addons import Addon
from ..const import ( from ..const import (
ATTR_ADDONS, ATTR_ADDONS,
ATTR_AUDIO_INPUT, ATTR_AUDIO_INPUT,
@ -42,11 +42,7 @@ from ..const import (
from ..coresys import CoreSys, CoreSysAttributes from ..coresys import CoreSys, CoreSysAttributes
from ..exceptions import AddonsError from ..exceptions import AddonsError
from ..utils.json import write_json_file from ..utils.json import write_json_file
from ..utils.tar import ( from ..utils.tar import SecureTarFile, atomic_contents_add, secure_path
SecureTarFile,
secure_path,
atomic_contents_add,
)
from .utils import key_to_iv, password_for_validating, password_to_key, remove_folder from .utils import key_to_iv, password_for_validating, password_to_key, remove_folder
from .validate import ALL_FOLDERS, SCHEMA_SNAPSHOT from .validate import ALL_FOLDERS, SCHEMA_SNAPSHOT
@ -297,11 +293,11 @@ class Snapshot(CoreSysAttributes):
finally: finally:
self._tmp.cleanup() self._tmp.cleanup()
async def store_addons(self, addon_list=None): async def store_addons(self, addon_list: Optional[List[Addon]] = None):
"""Add a list of add-ons into snapshot.""" """Add a list of add-ons into snapshot."""
addon_list = addon_list or self.sys_addons.installed addon_list: List[Addon] = addon_list or self.sys_addons.installed
async def _addon_save(addon): async def _addon_save(addon: Addon):
"""Task to store an add-on into snapshot.""" """Task to store an add-on into snapshot."""
addon_file = SecureTarFile( addon_file = SecureTarFile(
Path(self._tmp.name, f"{addon.slug}.tar.gz"), "w", key=self._key Path(self._tmp.name, f"{addon.slug}.tar.gz"), "w", key=self._key
@ -324,16 +320,19 @@ class Snapshot(CoreSysAttributes):
} }
) )
# Run tasks # Save Add-ons sequential
tasks = [_addon_save(addon) for addon in addon_list] # avoid issue on slow IO
if tasks: for addon in addon_list:
await asyncio.wait(tasks) try:
await _addon_save(addon)
except Exception as err: # pylint: disable=broad-except
_LOGGER.warning("Can't save Add-on %s: %s", addon.slug, err)
async def restore_addons(self, addon_list=None): async def restore_addons(self, addon_list: Optional[List[str]] = None):
"""Restore a list add-on from snapshot.""" """Restore a list add-on from snapshot."""
addon_list = addon_list or self.addon_list addon_list: List[str] = addon_list or self.addon_list
async def _addon_restore(addon_slug): async def _addon_restore(addon_slug: str):
"""Task to restore an add-on into snapshot.""" """Task to restore an add-on into snapshot."""
addon_file = SecureTarFile( addon_file = SecureTarFile(
Path(self._tmp.name, f"{addon_slug}.tar.gz"), "r", key=self._key Path(self._tmp.name, f"{addon_slug}.tar.gz"), "r", key=self._key
@ -350,16 +349,19 @@ class Snapshot(CoreSysAttributes):
except AddonsError: except AddonsError:
_LOGGER.error("Can't restore snapshot for %s", addon_slug) _LOGGER.error("Can't restore snapshot for %s", addon_slug)
# Run tasks # Save Add-ons sequential
tasks = [_addon_restore(slug) for slug in addon_list] # avoid issue on slow IO
if tasks: for slug in addon_list:
await asyncio.wait(tasks) try:
await _addon_restore(slug)
except Exception as err: # pylint: disable=broad-except
_LOGGER.warning("Can't restore Add-on %s: %s", slug, err)
async def store_folders(self, folder_list=None): async def store_folders(self, folder_list: Optional[List[str]] = None):
"""Backup Supervisor data into snapshot.""" """Backup Supervisor data into snapshot."""
folder_list = set(folder_list or ALL_FOLDERS) folder_list: Set[str] = set(folder_list or ALL_FOLDERS)
def _folder_save(name): def _folder_save(name: str):
"""Take snapshot of a folder.""" """Take snapshot of a folder."""
slug_name = name.replace("/", "_") slug_name = name.replace("/", "_")
tar_name = Path(self._tmp.name, f"{slug_name}.tar.gz") tar_name = Path(self._tmp.name, f"{slug_name}.tar.gz")
@ -386,18 +388,19 @@ class Snapshot(CoreSysAttributes):
except (tarfile.TarError, OSError) as err: except (tarfile.TarError, OSError) as err:
_LOGGER.warning("Can't snapshot folder %s: %s", name, err) _LOGGER.warning("Can't snapshot folder %s: %s", name, err)
# Run tasks # Save folder sequential
tasks = [ # avoid issue on slow IO
self.sys_run_in_executor(_folder_save, folder) for folder in folder_list for folder in folder_list:
] try:
if tasks: await self.sys_run_in_executor(_folder_save, folder)
await asyncio.wait(tasks) except Exception as err: # pylint: disable=broad-except
_LOGGER.warning("Can't save folder %s: %s", folder, err)
async def restore_folders(self, folder_list=None): async def restore_folders(self, folder_list: Optional[List[str]] = None):
"""Backup Supervisor data into snapshot.""" """Backup Supervisor data into snapshot."""
folder_list = set(folder_list or self.folders) folder_list: Set[str] = set(folder_list or self.folders)
def _folder_restore(name): def _folder_restore(name: str):
"""Intenal function to restore a folder.""" """Intenal function to restore a folder."""
slug_name = name.replace("/", "_") slug_name = name.replace("/", "_")
tar_name = Path(self._tmp.name, f"{slug_name}.tar.gz") tar_name = Path(self._tmp.name, f"{slug_name}.tar.gz")
@ -421,12 +424,13 @@ class Snapshot(CoreSysAttributes):
except (tarfile.TarError, OSError) as err: except (tarfile.TarError, OSError) as err:
_LOGGER.warning("Can't restore folder %s: %s", name, err) _LOGGER.warning("Can't restore folder %s: %s", name, err)
# Run tasks # Restore folder sequential
tasks = [ # avoid issue on slow IO
self.sys_run_in_executor(_folder_restore, folder) for folder in folder_list for folder in folder_list:
] try:
if tasks: await self.sys_run_in_executor(_folder_restore, folder)
await asyncio.wait(tasks) except Exception as err: # pylint: disable=broad-except
_LOGGER.warning("Can't restore folder %s: %s", folder, err)
def store_homeassistant(self): def store_homeassistant(self):
"""Read all data from Home Assistant object.""" """Read all data from Home Assistant object."""