mirror of
https://github.com/home-assistant/core.git
synced 2025-08-07 06:35:10 +02:00
Refactor BackupManager (#130947)
* Refactor BackupManager * Adjust * Adjust backup creation * Copy in executor
This commit is contained in:
@@ -11,7 +11,12 @@ from homeassistant.helpers.typing import ConfigType
|
||||
from .agent import BackupAgent, BackupAgentPlatformProtocol
|
||||
from .const import DOMAIN, LOGGER
|
||||
from .http import async_register_http_views
|
||||
from .manager import Backup, BackupManager, BackupPlatformProtocol
|
||||
from .manager import (
|
||||
Backup,
|
||||
BackupManager,
|
||||
BackupPlatformProtocol,
|
||||
CoreBackupReaderWriter,
|
||||
)
|
||||
from .models import BackupUploadMetadata, BaseBackup
|
||||
from .websocket import async_register_websocket_handlers
|
||||
|
||||
@@ -31,7 +36,9 @@ SERVICE_CREATE_SCHEMA = vol.Schema({vol.Optional(CONF_PASSWORD): str})
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the Backup integration."""
|
||||
hass.data[DOMAIN] = backup_manager = BackupManager(hass)
|
||||
hass.data[DOMAIN] = backup_manager = BackupManager(
|
||||
hass, CoreBackupReaderWriter(hass)
|
||||
)
|
||||
await backup_manager.async_setup()
|
||||
|
||||
with_hassio = is_hassio(hass)
|
||||
|
@@ -8,12 +8,11 @@ from typing import TYPE_CHECKING
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .manager import BaseBackupManager
|
||||
from .models import BaseBackup
|
||||
from .manager import BackupManager
|
||||
|
||||
BUF_SIZE = 2**20 * 4 # 4MB
|
||||
DOMAIN = "backup"
|
||||
DATA_MANAGER: HassKey[BaseBackupManager[BaseBackup]] = HassKey(DOMAIN)
|
||||
DATA_MANAGER: HassKey[BackupManager] = HassKey(DOMAIN)
|
||||
LOGGER = getLogger(__package__)
|
||||
|
||||
EXCLUDE_FROM_BACKUP = [
|
||||
|
@@ -15,7 +15,6 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.util import slugify
|
||||
|
||||
from .const import DATA_MANAGER
|
||||
from .manager import BackupManager
|
||||
|
||||
# pylint: disable=fixme
|
||||
# TODO: Don't forget to remove this when the implementation is complete
|
||||
@@ -47,7 +46,7 @@ class DownloadBackupView(HomeAssistantView):
|
||||
except KeyError:
|
||||
return Response(status=HTTPStatus.BAD_REQUEST)
|
||||
|
||||
manager = cast(BackupManager, request.app[KEY_HASS].data[DATA_MANAGER])
|
||||
manager = request.app[KEY_HASS].data[DATA_MANAGER]
|
||||
if agent_id not in manager.backup_agents:
|
||||
return Response(status=HTTPStatus.BAD_REQUEST)
|
||||
agent = manager.backup_agents[agent_id]
|
||||
|
@@ -15,11 +15,10 @@ import shutil
|
||||
import tarfile
|
||||
from tempfile import TemporaryDirectory
|
||||
import time
|
||||
from typing import Any, Generic, Protocol
|
||||
from typing import TYPE_CHECKING, Any, Protocol
|
||||
|
||||
import aiohttp
|
||||
from securetar import SecureTarFile, atomic_contents_add
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
from homeassistant.backup_restore import RESTORE_BACKUP_FILE, password_to_key
|
||||
from homeassistant.const import __version__ as HAVERSION
|
||||
@@ -38,6 +37,7 @@ from .agent import (
|
||||
from .config import BackupConfig
|
||||
from .const import (
|
||||
BUF_SIZE,
|
||||
DATA_MANAGER,
|
||||
DOMAIN,
|
||||
EXCLUDE_DATABASE_FROM_BACKUP,
|
||||
EXCLUDE_FROM_BACKUP,
|
||||
@@ -46,8 +46,6 @@ from .const import (
|
||||
from .models import BackupUploadMetadata, BaseBackup
|
||||
from .util import read_backup
|
||||
|
||||
_BackupT = TypeVar("_BackupT", bound=BaseBackup, default=BaseBackup)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class NewBackup:
|
||||
@@ -82,24 +80,62 @@ class BackupPlatformProtocol(Protocol):
|
||||
"""Perform operations after a backup finishes."""
|
||||
|
||||
|
||||
class BaseBackupManager(abc.ABC, Generic[_BackupT]):
|
||||
class BackupReaderWriter(abc.ABC):
|
||||
"""Abstract class for reading and writing backups."""
|
||||
|
||||
temp_backup_dir: Path
|
||||
|
||||
@abc.abstractmethod
|
||||
async def async_create_backup(
|
||||
self,
|
||||
*,
|
||||
addons_included: list[str] | None,
|
||||
agent_ids: list[str],
|
||||
database_included: bool,
|
||||
backup_name: str,
|
||||
folders_included: list[str] | None,
|
||||
on_progress: Callable[[BackupProgress], None] | None,
|
||||
password: str | None,
|
||||
) -> tuple[NewBackup, asyncio.Task[tuple[BaseBackup, Path]]]:
|
||||
"""Create a backup."""
|
||||
|
||||
@abc.abstractmethod
|
||||
async def async_restore_backup(
|
||||
self,
|
||||
backup_id: str,
|
||||
*,
|
||||
agent_id: str,
|
||||
password: str | None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Restore a backup."""
|
||||
|
||||
|
||||
class BackupManager:
|
||||
"""Define the format that backup managers can have."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant) -> None:
|
||||
def __init__(self, hass: HomeAssistant, reader_writer: BackupReaderWriter) -> None:
|
||||
"""Initialize the backup manager."""
|
||||
self.hass = hass
|
||||
self.backup_task: asyncio.Task | None = None
|
||||
self.backup_task: asyncio.Task[tuple[BaseBackup, Path]] | None = None
|
||||
self.finish_backup_task: asyncio.Task[None] | None = None
|
||||
self.platforms: dict[str, BackupPlatformProtocol] = {}
|
||||
self.backup_agents: dict[str, BackupAgent] = {}
|
||||
self.local_backup_agents: dict[str, LocalBackupAgent] = {}
|
||||
self.config = BackupConfig(hass)
|
||||
self.syncing = False
|
||||
self._reader_writer = reader_writer
|
||||
|
||||
async def async_setup(self) -> None:
|
||||
"""Set up the backup manager."""
|
||||
await self.config.load()
|
||||
await self.load_platforms()
|
||||
|
||||
@property
|
||||
def temp_backup_dir(self) -> Path:
|
||||
"""Return the temporary backup directory."""
|
||||
return self._reader_writer.temp_backup_dir
|
||||
|
||||
@callback
|
||||
def _add_platform_pre_post_handler(
|
||||
self,
|
||||
@@ -182,74 +218,6 @@ class BaseBackupManager(abc.ABC, Generic[_BackupT]):
|
||||
LOGGER.debug("Loaded %s platforms", len(self.platforms))
|
||||
LOGGER.debug("Loaded %s agents", len(self.backup_agents))
|
||||
|
||||
@abc.abstractmethod
|
||||
async def async_restore_backup(
|
||||
self,
|
||||
backup_id: str,
|
||||
*,
|
||||
agent_id: str,
|
||||
password: str | None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Restore a backup."""
|
||||
|
||||
@abc.abstractmethod
|
||||
async def async_create_backup(
|
||||
self,
|
||||
*,
|
||||
addons_included: list[str] | None,
|
||||
agent_ids: list[str],
|
||||
database_included: bool,
|
||||
folders_included: list[str] | None,
|
||||
name: str | None,
|
||||
on_progress: Callable[[BackupProgress], None] | None,
|
||||
password: str | None,
|
||||
**kwargs: Any,
|
||||
) -> NewBackup:
|
||||
"""Initiate generating a backup.
|
||||
|
||||
:param on_progress: A callback that will be called with the progress of the
|
||||
backup.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
async def async_get_backups(
|
||||
self, **kwargs: Any
|
||||
) -> tuple[dict[str, Backup], dict[str, Exception]]:
|
||||
"""Get backups.
|
||||
|
||||
Return a dictionary of Backup instances keyed by their ID.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
async def async_get_backup(
|
||||
self, backup_id: str, **kwargs: Any
|
||||
) -> tuple[_BackupT | None, dict[str, Exception]]:
|
||||
"""Get a backup."""
|
||||
|
||||
@abc.abstractmethod
|
||||
async def async_delete_backup(self, backup_id: str, **kwargs: Any) -> None:
|
||||
"""Delete a backup."""
|
||||
|
||||
@abc.abstractmethod
|
||||
async def async_receive_backup(
|
||||
self,
|
||||
*,
|
||||
agent_ids: list[str],
|
||||
contents: aiohttp.BodyPartReader,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Receive and store a backup file from upload."""
|
||||
|
||||
|
||||
class BackupManager(BaseBackupManager[Backup]):
|
||||
"""Backup manager for the Backup integration."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant) -> None:
|
||||
"""Initialize the backup manager."""
|
||||
super().__init__(hass=hass)
|
||||
self.temp_backup_dir = Path(hass.config.path("tmp_backups"))
|
||||
|
||||
async def _async_upload_backup(
|
||||
self,
|
||||
*,
|
||||
@@ -288,7 +256,10 @@ class BackupManager(BaseBackupManager[Backup]):
|
||||
async def async_get_backups(
|
||||
self, **kwargs: Any
|
||||
) -> tuple[dict[str, Backup], dict[str, Exception]]:
|
||||
"""Return backups."""
|
||||
"""Get backups.
|
||||
|
||||
Return a dictionary of Backup instances keyed by their ID.
|
||||
"""
|
||||
backups: dict[str, Backup] = {}
|
||||
agent_errors: dict[str, Exception] = {}
|
||||
agent_ids = list(self.backup_agents)
|
||||
@@ -320,7 +291,7 @@ class BackupManager(BaseBackupManager[Backup]):
|
||||
async def async_get_backup(
|
||||
self, backup_id: str, **kwargs: Any
|
||||
) -> tuple[Backup | None, dict[str, Exception]]:
|
||||
"""Return a backup."""
|
||||
"""Get a backup."""
|
||||
backup: Backup | None = None
|
||||
agent_errors: dict[str, Exception] = {}
|
||||
agent_ids = list(self.backup_agents.keys())
|
||||
@@ -453,7 +424,11 @@ class BackupManager(BaseBackupManager[Backup]):
|
||||
password: str | None,
|
||||
**kwargs: Any,
|
||||
) -> NewBackup:
|
||||
"""Initiate generating a backup."""
|
||||
"""Initiate generating a backup.
|
||||
|
||||
:param on_progress: A callback that will be called with the progress of the
|
||||
backup.
|
||||
"""
|
||||
if self.backup_task:
|
||||
raise HomeAssistantError("Backup already in progress")
|
||||
if not agent_ids:
|
||||
@@ -461,9 +436,118 @@ class BackupManager(BaseBackupManager[Backup]):
|
||||
if any(agent_id not in self.backup_agents for agent_id in agent_ids):
|
||||
raise HomeAssistantError("Invalid agent selected")
|
||||
backup_name = name or f"Core {HAVERSION}"
|
||||
new_backup, self.backup_task = await self._reader_writer.async_create_backup(
|
||||
addons_included=addons_included,
|
||||
agent_ids=agent_ids,
|
||||
backup_name=backup_name,
|
||||
database_included=database_included,
|
||||
folders_included=folders_included,
|
||||
on_progress=on_progress,
|
||||
password=password,
|
||||
)
|
||||
self.finish_backup_task = self.hass.async_create_task(
|
||||
self._async_finish_backup(agent_ids),
|
||||
name="backup_manager_finish_backup",
|
||||
)
|
||||
return new_backup
|
||||
|
||||
async def _async_finish_backup(self, agent_ids: list[str]) -> None:
|
||||
if TYPE_CHECKING:
|
||||
assert self.backup_task is not None
|
||||
try:
|
||||
backup, tar_file_path = await self.backup_task
|
||||
except Exception as err: # noqa: BLE001
|
||||
LOGGER.debug("Backup upload failed", exc_info=err)
|
||||
else:
|
||||
LOGGER.debug(
|
||||
"Generated new backup with backup_id %s, uploading to agents %s",
|
||||
backup.backup_id,
|
||||
agent_ids,
|
||||
)
|
||||
local_file_paths = [
|
||||
self.local_backup_agents[agent_id].get_backup_path(backup.backup_id)
|
||||
for agent_id in agent_ids
|
||||
if agent_id in self.local_backup_agents
|
||||
]
|
||||
keep_path = False
|
||||
for local_path in local_file_paths:
|
||||
if local_path == tar_file_path:
|
||||
keep_path = True
|
||||
continue
|
||||
await self.hass.async_add_executor_job(
|
||||
shutil.copy, tar_file_path, local_path
|
||||
)
|
||||
await self._async_upload_backup(
|
||||
backup=backup, agent_ids=agent_ids, path=tar_file_path
|
||||
)
|
||||
if not keep_path:
|
||||
await self.hass.async_add_executor_job(tar_file_path.unlink, True)
|
||||
finally:
|
||||
self.backup_task = None
|
||||
self.finish_backup_task = None
|
||||
|
||||
async def async_restore_backup(
|
||||
self,
|
||||
backup_id: str,
|
||||
*,
|
||||
agent_id: str,
|
||||
password: str | None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Initiate restoring a backup.
|
||||
|
||||
:param on_progress: A callback that will be called with the progress of the
|
||||
restore. Home Assistant Core may need to be restarted during the backup
|
||||
restore process, which means the restore process may not be able to report
|
||||
when it's done.
|
||||
"""
|
||||
|
||||
if agent_id in self.local_backup_agents:
|
||||
local_agent = self.local_backup_agents[agent_id]
|
||||
if not await local_agent.async_get_backup(backup_id):
|
||||
raise HomeAssistantError(
|
||||
f"Backup {backup_id} not found in agent {agent_id}"
|
||||
)
|
||||
else:
|
||||
path = self.temp_backup_dir / f"{backup_id}.tar"
|
||||
agent = self.backup_agents[agent_id]
|
||||
if not await agent.async_get_backup(backup_id):
|
||||
raise HomeAssistantError(
|
||||
f"Backup {backup_id} not found in agent {agent_id}"
|
||||
)
|
||||
await agent.async_download_backup(backup_id, path=path)
|
||||
|
||||
await self._reader_writer.async_restore_backup(
|
||||
backup_id=backup_id,
|
||||
agent_id=agent_id,
|
||||
password=password,
|
||||
)
|
||||
|
||||
|
||||
class CoreBackupReaderWriter(BackupReaderWriter):
|
||||
"""Class for reading and writing backups in core and container installations."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant) -> None:
|
||||
"""Initialize the backup reader/writer."""
|
||||
self._hass = hass
|
||||
self.temp_backup_dir = Path(hass.config.path("tmp_backups"))
|
||||
|
||||
async def async_create_backup(
|
||||
self,
|
||||
*,
|
||||
addons_included: list[str] | None,
|
||||
agent_ids: list[str],
|
||||
database_included: bool,
|
||||
backup_name: str,
|
||||
folders_included: list[str] | None,
|
||||
on_progress: Callable[[BackupProgress], None] | None,
|
||||
password: str | None,
|
||||
) -> tuple[NewBackup, asyncio.Task[tuple[BaseBackup, Path]]]:
|
||||
"""Initiate generating a backup."""
|
||||
date_str = dt_util.now().isoformat()
|
||||
backup_id = _generate_backup_id(date_str, backup_name)
|
||||
self.backup_task = self.hass.async_create_task(
|
||||
|
||||
backup_task = self._hass.async_create_task(
|
||||
self._async_create_backup(
|
||||
addons_included=addons_included,
|
||||
agent_ids=agent_ids,
|
||||
@@ -478,32 +562,35 @@ class BackupManager(BaseBackupManager[Backup]):
|
||||
name="backup_manager_create_backup",
|
||||
eager_start=False, # To ensure the task is not started before we return
|
||||
)
|
||||
return NewBackup(backup_id=backup_id)
|
||||
|
||||
return (NewBackup(backup_id=backup_id), backup_task)
|
||||
|
||||
async def _async_create_backup(
|
||||
self,
|
||||
*,
|
||||
addons_included: list[str] | None,
|
||||
agent_ids: list[str],
|
||||
backup_id: str,
|
||||
database_included: bool,
|
||||
backup_name: str,
|
||||
date_str: str,
|
||||
folders_included: list[str] | None,
|
||||
on_progress: Callable[[BackupProgress], None] | None,
|
||||
password: str | None,
|
||||
backup_id: str,
|
||||
) -> BaseBackup:
|
||||
) -> tuple[BaseBackup, Path]:
|
||||
"""Generate a backup."""
|
||||
manager = self._hass.data[DATA_MANAGER]
|
||||
success = False
|
||||
|
||||
local_file_paths = [
|
||||
self.local_backup_agents[agent_id].get_backup_path(backup_id)
|
||||
for agent_id in agent_ids
|
||||
if agent_id in self.local_backup_agents
|
||||
]
|
||||
suggested_tar_file_path = None
|
||||
for agent_id in agent_ids:
|
||||
if local_agent := manager.local_backup_agents.get(agent_id):
|
||||
suggested_tar_file_path = local_agent.get_backup_path(backup_id)
|
||||
break
|
||||
|
||||
try:
|
||||
await self.async_pre_backup_actions()
|
||||
# Inform integrations a backup is about to be made
|
||||
await manager.async_pre_backup_actions()
|
||||
|
||||
backup_data = {
|
||||
"compressed": True,
|
||||
@@ -519,12 +606,12 @@ class BackupManager(BaseBackupManager[Backup]):
|
||||
"type": "partial",
|
||||
}
|
||||
|
||||
tar_file_path, size_in_bytes = await self.hass.async_add_executor_job(
|
||||
tar_file_path, size_in_bytes = await self._hass.async_add_executor_job(
|
||||
self._mkdir_and_generate_backup_contents,
|
||||
local_file_paths,
|
||||
backup_data,
|
||||
database_included,
|
||||
password,
|
||||
suggested_tar_file_path,
|
||||
)
|
||||
backup = BaseBackup(
|
||||
backup_id=backup_id,
|
||||
@@ -533,35 +620,23 @@ class BackupManager(BaseBackupManager[Backup]):
|
||||
protected=password is not None,
|
||||
size=round(size_in_bytes / 1_048_576, 2),
|
||||
)
|
||||
LOGGER.debug(
|
||||
"Generated new backup with backup_id %s, uploading to agents %s",
|
||||
backup_id,
|
||||
agent_ids,
|
||||
)
|
||||
await self._async_upload_backup(
|
||||
backup=backup, agent_ids=agent_ids, path=tar_file_path
|
||||
)
|
||||
if not local_file_paths:
|
||||
await self.hass.async_add_executor_job(tar_file_path.unlink, True)
|
||||
success = True
|
||||
return backup
|
||||
return (backup, tar_file_path)
|
||||
finally:
|
||||
if on_progress:
|
||||
on_progress(BackupProgress(done=True, stage=None, success=success))
|
||||
self.backup_task = None
|
||||
await self.async_post_backup_actions()
|
||||
# Inform integrations the backup is done
|
||||
await manager.async_post_backup_actions()
|
||||
|
||||
def _mkdir_and_generate_backup_contents(
|
||||
self,
|
||||
tar_file_paths: list[Path],
|
||||
backup_data: dict[str, Any],
|
||||
database_included: bool,
|
||||
password: str | None,
|
||||
tar_file_path: Path | None,
|
||||
) -> tuple[Path, int]:
|
||||
"""Generate backup contents and return the size."""
|
||||
if tar_file_paths:
|
||||
tar_file_path = tar_file_paths[0]
|
||||
else:
|
||||
if not tar_file_path:
|
||||
tar_file_path = self.temp_backup_dir / f"{backup_data['slug']}.tar"
|
||||
if not (backup_dir := tar_file_path.parent).exists():
|
||||
LOGGER.debug("Creating backup directory %s", backup_dir)
|
||||
@@ -588,12 +663,10 @@ class BackupManager(BaseBackupManager[Backup]):
|
||||
) as core_tar:
|
||||
atomic_contents_add(
|
||||
tar_file=core_tar,
|
||||
origin_path=Path(self.hass.config.path()),
|
||||
origin_path=Path(self._hass.config.path()),
|
||||
excludes=excludes,
|
||||
arcname="data",
|
||||
)
|
||||
for local_path in tar_file_paths[1:]:
|
||||
shutil.copy(tar_file_path, local_path)
|
||||
return (tar_file_path, tar_file_path.stat().st_size)
|
||||
|
||||
async def async_restore_backup(
|
||||
@@ -610,31 +683,22 @@ class BackupManager(BaseBackupManager[Backup]):
|
||||
will be handled during startup by the restore_backup module.
|
||||
"""
|
||||
|
||||
if agent_id in self.local_backup_agents:
|
||||
local_agent = self.local_backup_agents[agent_id]
|
||||
if not await local_agent.async_get_backup(backup_id):
|
||||
raise HomeAssistantError(
|
||||
f"Backup {backup_id} not found in agent {agent_id}"
|
||||
)
|
||||
manager = self._hass.data[DATA_MANAGER]
|
||||
if agent_id in manager.local_backup_agents:
|
||||
local_agent = manager.local_backup_agents[agent_id]
|
||||
path = local_agent.get_backup_path(backup_id)
|
||||
else:
|
||||
path = self.temp_backup_dir / f"{backup_id}.tar"
|
||||
agent = self.backup_agents[agent_id]
|
||||
if not await agent.async_get_backup(backup_id):
|
||||
raise HomeAssistantError(
|
||||
f"Backup {backup_id} not found in agent {agent_id}"
|
||||
)
|
||||
await agent.async_download_backup(backup_id, path=path)
|
||||
|
||||
def _write_restore_file() -> None:
|
||||
"""Write the restore file."""
|
||||
Path(self.hass.config.path(RESTORE_BACKUP_FILE)).write_text(
|
||||
Path(self._hass.config.path(RESTORE_BACKUP_FILE)).write_text(
|
||||
json.dumps({"path": path.as_posix(), "password": password}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
await self.hass.async_add_executor_job(_write_restore_file)
|
||||
await self.hass.services.async_call("homeassistant", "restart", {})
|
||||
await self._hass.async_add_executor_job(_write_restore_file)
|
||||
await self._hass.services.async_call("homeassistant", "restart", {})
|
||||
|
||||
|
||||
def _generate_backup_id(date: str, name: str) -> str:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"""Websocket commands for the Backup integration."""
|
||||
|
||||
from typing import Any, cast
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -8,7 +8,7 @@ from homeassistant.components import websocket_api
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
||||
from .const import DATA_MANAGER, LOGGER
|
||||
from .manager import BackupManager, BackupProgress
|
||||
from .manager import BackupProgress
|
||||
|
||||
|
||||
@callback
|
||||
@@ -254,7 +254,7 @@ async def backup_agents_download(
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Download an uploaded backup."""
|
||||
manager = cast(BackupManager, hass.data[DATA_MANAGER])
|
||||
manager = hass.data[DATA_MANAGER]
|
||||
if not (agent := manager.backup_agents.get(msg["agent_id"])):
|
||||
connection.send_error(
|
||||
msg["id"], "unknown_agent", f"Agent {msg['agent_id']} not found"
|
||||
|
@@ -18,7 +18,11 @@ from homeassistant.components.backup import (
|
||||
BaseBackup,
|
||||
backup as local_backup_platform,
|
||||
)
|
||||
from homeassistant.components.backup.manager import BackupProgress
|
||||
from homeassistant.components.backup.const import DATA_MANAGER
|
||||
from homeassistant.components.backup.manager import (
|
||||
BackupProgress,
|
||||
CoreBackupReaderWriter,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.setup import async_setup_component
|
||||
@@ -71,7 +75,8 @@ async def _mock_backup_generation(
|
||||
assert manager.backup_task is not None
|
||||
assert progress == []
|
||||
|
||||
backup = await manager.backup_task
|
||||
backup, _ = await manager.backup_task
|
||||
await manager.finish_backup_task
|
||||
assert progress == [BackupProgress(done=True, stage=None, success=True)]
|
||||
|
||||
assert mocked_json_bytes.call_count == 1
|
||||
@@ -132,13 +137,13 @@ async def _setup_backup_platform(
|
||||
|
||||
async def test_constructor(hass: HomeAssistant) -> None:
|
||||
"""Test BackupManager constructor."""
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
assert manager.temp_backup_dir.as_posix() == hass.config.path("tmp_backups")
|
||||
|
||||
|
||||
async def test_load_backups(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None:
|
||||
"""Test loading backups."""
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
|
||||
await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform)
|
||||
await manager.load_platforms()
|
||||
@@ -170,7 +175,7 @@ async def test_load_backups_with_exception(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test loading backups with exception."""
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
|
||||
await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform)
|
||||
await manager.load_platforms()
|
||||
@@ -194,7 +199,7 @@ async def test_deleting_backup(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test deleting backup."""
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
|
||||
await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform)
|
||||
await manager.load_platforms()
|
||||
@@ -213,7 +218,7 @@ async def test_deleting_non_existing_backup(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test deleting not existing backup."""
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
|
||||
await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform)
|
||||
await manager.load_platforms()
|
||||
@@ -227,7 +232,7 @@ async def test_getting_backup_that_does_not_exist(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test getting backup that does not exist."""
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
|
||||
await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform)
|
||||
await manager.load_platforms()
|
||||
@@ -253,7 +258,7 @@ async def test_getting_backup_that_does_not_exist(
|
||||
async def test_async_create_backup_when_backing_up(hass: HomeAssistant) -> None:
|
||||
"""Test generate backup."""
|
||||
event = asyncio.Event()
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
manager.backup_task = hass.async_create_task(event.wait())
|
||||
with pytest.raises(HomeAssistantError, match="Backup already in progress"):
|
||||
await manager.async_create_backup(
|
||||
@@ -279,7 +284,7 @@ async def test_async_create_backup_wrong_agent_id(
|
||||
hass: HomeAssistant, agent_ids: list[str], expected_error: str
|
||||
) -> None:
|
||||
"""Test generate backup."""
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
with pytest.raises(HomeAssistantError, match=expected_error):
|
||||
await manager.async_create_backup(
|
||||
addons_included=[],
|
||||
@@ -320,7 +325,8 @@ async def test_async_create_backup(
|
||||
backup_directory: str,
|
||||
) -> None:
|
||||
"""Test generate backup."""
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
hass.data[DATA_MANAGER] = manager
|
||||
|
||||
await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform)
|
||||
await _setup_backup_platform(
|
||||
@@ -358,7 +364,7 @@ async def test_loading_platforms(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test loading backup platforms."""
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
|
||||
assert not manager.platforms
|
||||
|
||||
@@ -383,7 +389,7 @@ async def test_loading_agents(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test loading backup agents."""
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
|
||||
assert not manager.platforms
|
||||
|
||||
@@ -407,7 +413,7 @@ async def test_not_loading_bad_platforms(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test loading backup platforms."""
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
|
||||
assert not manager.platforms
|
||||
|
||||
@@ -424,7 +430,7 @@ async def test_exception_plaform_pre(
|
||||
hass: HomeAssistant, mocked_json_bytes: Mock, mocked_tarfile: Mock
|
||||
) -> None:
|
||||
"""Test exception in pre step."""
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
manager.loaded_backups = True
|
||||
|
||||
async def _mock_step(hass: HomeAssistant) -> None:
|
||||
@@ -447,7 +453,7 @@ async def test_exception_plaform_post(
|
||||
hass: HomeAssistant, mocked_json_bytes: Mock, mocked_tarfile: Mock
|
||||
) -> None:
|
||||
"""Test exception in post step."""
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
manager.loaded_backups = True
|
||||
|
||||
async def _mock_step(hass: HomeAssistant) -> None:
|
||||
@@ -471,7 +477,7 @@ async def test_async_receive_backup(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test receiving a backup file."""
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
|
||||
await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform)
|
||||
await manager.load_platforms()
|
||||
@@ -516,7 +522,8 @@ async def test_async_trigger_restore(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test trigger restore."""
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
hass.data[DATA_MANAGER] = manager
|
||||
|
||||
await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform)
|
||||
await manager.load_platforms()
|
||||
@@ -545,7 +552,8 @@ async def test_async_trigger_restore_with_password(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test trigger restore."""
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
hass.data[DATA_MANAGER] = manager
|
||||
|
||||
await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform)
|
||||
await manager.load_platforms()
|
||||
@@ -573,7 +581,7 @@ async def test_async_trigger_restore_with_password(
|
||||
|
||||
async def test_async_trigger_restore_missing_backup(hass: HomeAssistant) -> None:
|
||||
"""Test trigger restore."""
|
||||
manager = BackupManager(hass)
|
||||
manager = BackupManager(hass, CoreBackupReaderWriter(hass))
|
||||
|
||||
await _setup_backup_platform(hass, domain=DOMAIN, platform=local_backup_platform)
|
||||
await manager.load_platforms()
|
||||
|
Reference in New Issue
Block a user