mirror of
https://github.com/home-assistant/core.git
synced 2025-04-19 14:57:52 +00:00
Add agent retention config
This commit is contained in:
parent
8355727eb1
commit
6ce90841dc
@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass, field, replace
|
||||
import datetime as dt
|
||||
from datetime import datetime, timedelta
|
||||
@ -87,12 +88,26 @@ class BackupConfigData:
|
||||
else:
|
||||
time = None
|
||||
days = [Day(day) for day in data["schedule"]["days"]]
|
||||
agents = {}
|
||||
for agent_id, agent_data in data["agents"].items():
|
||||
protected = agent_data["protected"]
|
||||
stored_retention = agent_data["retention"]
|
||||
agent_retention: AgentRetentionConfig | None
|
||||
if stored_retention:
|
||||
agent_retention = AgentRetentionConfig(
|
||||
copies=stored_retention["copies"],
|
||||
days=stored_retention["days"],
|
||||
)
|
||||
else:
|
||||
agent_retention = None
|
||||
agent_config = AgentConfig(
|
||||
protected=protected,
|
||||
retention=agent_retention,
|
||||
)
|
||||
agents[agent_id] = agent_config
|
||||
|
||||
return cls(
|
||||
agents={
|
||||
agent_id: AgentConfig(protected=agent_data["protected"])
|
||||
for agent_id, agent_data in data["agents"].items()
|
||||
},
|
||||
agents=agents,
|
||||
automatic_backups_configured=data["automatic_backups_configured"],
|
||||
create_backup=CreateBackupConfig(
|
||||
agent_ids=data["create_backup"]["agent_ids"],
|
||||
@ -176,12 +191,36 @@ class BackupConfig:
|
||||
"""Update config."""
|
||||
if agents is not UNDEFINED:
|
||||
for agent_id, agent_config in agents.items():
|
||||
if agent_id not in self.data.agents:
|
||||
self.data.agents[agent_id] = AgentConfig(**agent_config)
|
||||
agent_retention = agent_config.get("retention", UNDEFINED)
|
||||
if agent_retention is UNDEFINED or agent_retention is None:
|
||||
new_agent_retention = None
|
||||
else:
|
||||
self.data.agents[agent_id] = replace(
|
||||
self.data.agents[agent_id], **agent_config
|
||||
new_agent_retention = AgentRetentionConfig(
|
||||
copies=agent_retention.get("copies"),
|
||||
days=agent_retention.get("days"),
|
||||
)
|
||||
if agent_id not in self.data.agents:
|
||||
old_agent_retention = None
|
||||
self.data.agents[agent_id] = AgentConfig(
|
||||
protected=agent_config.get("protected", False),
|
||||
retention=new_agent_retention,
|
||||
)
|
||||
else:
|
||||
new_agent_config = self.data.agents[agent_id]
|
||||
old_agent_retention = new_agent_config.retention
|
||||
if "protected" in agent_config:
|
||||
new_agent_config = replace(
|
||||
new_agent_config, protected=agent_config["protected"]
|
||||
)
|
||||
if "retention" in agent_config:
|
||||
new_agent_config = replace(
|
||||
new_agent_config, retention=new_agent_retention
|
||||
)
|
||||
self.data.agents[agent_id] = new_agent_config
|
||||
if new_agent_retention != old_agent_retention:
|
||||
# There's a single retention application method
|
||||
# for both global and agent retention settings.
|
||||
self.data.retention.apply(self._manager)
|
||||
if automatic_backups_configured is not UNDEFINED:
|
||||
self.data.automatic_backups_configured = automatic_backups_configured
|
||||
if create_backup is not UNDEFINED:
|
||||
@ -207,11 +246,24 @@ class AgentConfig:
|
||||
"""Represent the config for an agent."""
|
||||
|
||||
protected: bool
|
||||
"""Agent protected configuration.
|
||||
|
||||
If True, the agent backups are password protected.
|
||||
"""
|
||||
retention: AgentRetentionConfig | None = None
|
||||
"""Agent retention configuration.
|
||||
|
||||
If None, the global retention configuration is used.
|
||||
If not None, the global retention configuration is ignored for this agent.
|
||||
If an agent retention configuration is set and both copies and days are None,
|
||||
backups will be kept forever for that agent.
|
||||
"""
|
||||
|
||||
def to_dict(self) -> StoredAgentConfig:
|
||||
"""Convert agent config to a dict."""
|
||||
return {
|
||||
"protected": self.protected,
|
||||
"retention": self.retention.to_dict() if self.retention else None,
|
||||
}
|
||||
|
||||
|
||||
@ -219,24 +271,46 @@ class StoredAgentConfig(TypedDict):
|
||||
"""Represent the stored config for an agent."""
|
||||
|
||||
protected: bool
|
||||
retention: StoredRetentionConfig | None
|
||||
|
||||
|
||||
class AgentParametersDict(TypedDict, total=False):
|
||||
"""Represent the parameters for an agent."""
|
||||
|
||||
protected: bool
|
||||
retention: RetentionParametersDict | None
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class RetentionConfig:
|
||||
"""Represent the backup retention configuration."""
|
||||
class BaseRetentionConfig:
|
||||
"""Represent the base backup retention configuration."""
|
||||
|
||||
copies: int | None = None
|
||||
days: int | None = None
|
||||
|
||||
def to_dict(self) -> StoredRetentionConfig:
|
||||
"""Convert backup retention configuration to a dict."""
|
||||
return StoredRetentionConfig(
|
||||
copies=self.copies,
|
||||
days=self.days,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class RetentionConfig(BaseRetentionConfig):
|
||||
"""Represent the backup retention configuration."""
|
||||
|
||||
def apply(self, manager: BackupManager) -> None:
|
||||
"""Apply backup retention configuration."""
|
||||
if self.days is not None:
|
||||
agents_retention = {
|
||||
agent_id: agent_config.retention
|
||||
for agent_id, agent_config in manager.config.data.agents.items()
|
||||
}
|
||||
|
||||
if self.days is not None or any(
|
||||
agent_retention and agent_retention.days is not None
|
||||
for agent_retention in agents_retention.values()
|
||||
):
|
||||
LOGGER.debug(
|
||||
"Scheduling next automatic delete of backups older than %s in 1 day",
|
||||
self.days,
|
||||
@ -246,13 +320,6 @@ class RetentionConfig:
|
||||
LOGGER.debug("Unscheduling next automatic delete")
|
||||
self._unschedule_next(manager)
|
||||
|
||||
def to_dict(self) -> StoredRetentionConfig:
|
||||
"""Convert backup retention configuration to a dict."""
|
||||
return StoredRetentionConfig(
|
||||
copies=self.copies,
|
||||
days=self.days,
|
||||
)
|
||||
|
||||
@callback
|
||||
def _schedule_next(
|
||||
self,
|
||||
@ -271,16 +338,81 @@ class RetentionConfig:
|
||||
"""Return backups older than days to delete."""
|
||||
# we need to check here since we await before
|
||||
# this filter is applied
|
||||
if self.days is None:
|
||||
return {}
|
||||
now = dt_util.utcnow()
|
||||
return {
|
||||
backup_id: backup
|
||||
for backup_id, backup in backups.items()
|
||||
if dt_util.parse_datetime(backup.date, raise_on_error=True)
|
||||
+ timedelta(days=self.days)
|
||||
< now
|
||||
agents_retention = {
|
||||
agent_id: agent_config.retention
|
||||
for agent_id, agent_config in manager.config.data.agents.items()
|
||||
}
|
||||
has_agents_retention = any(
|
||||
agent_retention for agent_retention in agents_retention.values()
|
||||
)
|
||||
has_agents_retention_days = any(
|
||||
agent_retention and agent_retention.days is not None
|
||||
for agent_retention in agents_retention.values()
|
||||
)
|
||||
if (global_days := self.days) is None and not has_agents_retention_days:
|
||||
# No global retention days and no agent retention days
|
||||
return {}
|
||||
|
||||
now = dt_util.utcnow()
|
||||
if global_days is not None and not has_agents_retention:
|
||||
# Return early to avoid the longer filtering below.
|
||||
return {
|
||||
backup_id: backup
|
||||
for backup_id, backup in backups.items()
|
||||
if dt_util.parse_datetime(backup.date, raise_on_error=True)
|
||||
+ timedelta(days=global_days)
|
||||
< now
|
||||
}
|
||||
|
||||
# If there are any agent retention settings, we need to check
|
||||
# the retention settings, for every backup and agent combination.
|
||||
|
||||
backups_to_delete = {}
|
||||
|
||||
for backup_id, backup in backups.items():
|
||||
backup_date = dt_util.parse_datetime(
|
||||
backup.date, raise_on_error=True
|
||||
)
|
||||
agents_for_backup_to_delete = set(backup.agents)
|
||||
for agent_id in backup.agents:
|
||||
agent_retention = agents_retention.get(agent_id)
|
||||
if agent_retention is None:
|
||||
# This agent does not have a retention setting,
|
||||
# so the global retention setting should be used.
|
||||
if global_days is None:
|
||||
# This agent does not have a retention setting
|
||||
# and the global retention days setting is None,
|
||||
# so this backup should not be deleted.
|
||||
agents_for_backup_to_delete.discard(agent_id)
|
||||
continue
|
||||
days = global_days
|
||||
elif (agent_days := agent_retention.days) is None:
|
||||
# This agent has a retention setting
|
||||
# where days is set to None,
|
||||
# so the backup should not be deleted.
|
||||
agents_for_backup_to_delete.discard(agent_id)
|
||||
continue
|
||||
else:
|
||||
# This agent has a retention setting
|
||||
# where days is set to a number,
|
||||
# so the backup should be deleted.
|
||||
days = agent_days
|
||||
if backup_date + timedelta(days=days) >= now:
|
||||
# This backup is not older than the retention days,
|
||||
# so this agent should not be deleted.
|
||||
agents_for_backup_to_delete.discard(agent_id)
|
||||
|
||||
filtered_backup = replace(
|
||||
backup,
|
||||
agents={
|
||||
agent_id: agent_backup_status
|
||||
for agent_id, agent_backup_status in backup.agents.items()
|
||||
if agent_id in agents_for_backup_to_delete
|
||||
},
|
||||
)
|
||||
backups_to_delete[backup_id] = filtered_backup
|
||||
|
||||
return backups_to_delete
|
||||
|
||||
await manager.async_delete_filtered_backups(
|
||||
include_filter=_automatic_backups_filter, delete_filter=_delete_filter
|
||||
@ -312,6 +444,10 @@ class RetentionParametersDict(TypedDict, total=False):
|
||||
days: int | None
|
||||
|
||||
|
||||
class AgentRetentionConfig(BaseRetentionConfig):
|
||||
"""Represent an agent retention configuration."""
|
||||
|
||||
|
||||
class StoredBackupSchedule(TypedDict):
|
||||
"""Represent the stored backup schedule configuration."""
|
||||
|
||||
@ -554,16 +690,87 @@ async def delete_backups_exceeding_configured_count(manager: BackupManager) -> N
|
||||
backups: dict[str, ManagerBackup],
|
||||
) -> dict[str, ManagerBackup]:
|
||||
"""Return oldest backups more numerous than copies to delete."""
|
||||
agents_retention = {
|
||||
agent_id: agent_config.retention
|
||||
for agent_id, agent_config in manager.config.data.agents.items()
|
||||
}
|
||||
has_agents_retention = any(
|
||||
agent_retention for agent_retention in agents_retention.values()
|
||||
)
|
||||
has_agents_retention_copies = any(
|
||||
agent_retention and agent_retention.copies is not None
|
||||
for agent_retention in agents_retention.values()
|
||||
)
|
||||
# we need to check here since we await before
|
||||
# this filter is applied
|
||||
if manager.config.data.retention.copies is None:
|
||||
if (
|
||||
global_copies := manager.config.data.retention.copies
|
||||
) is None and not has_agents_retention_copies:
|
||||
# No global retention copies and no agent retention copies
|
||||
return {}
|
||||
return dict(
|
||||
sorted(
|
||||
backups.items(),
|
||||
key=lambda backup_item: backup_item[1].date,
|
||||
)[: max(len(backups) - manager.config.data.retention.copies, 0)]
|
||||
if global_copies is not None and not has_agents_retention:
|
||||
# Return early to avoid the longer filtering below.
|
||||
return dict(
|
||||
sorted(
|
||||
backups.items(),
|
||||
key=lambda backup_item: backup_item[1].date,
|
||||
)[: max(len(backups) - global_copies, 0)]
|
||||
)
|
||||
|
||||
backups_by_agent: dict[str, dict[str, ManagerBackup]] = defaultdict(dict)
|
||||
for backup_id, backup in backups.items():
|
||||
for agent_id in backup.agents:
|
||||
backups_by_agent[agent_id][backup_id] = backup
|
||||
|
||||
backups_to_delete_by_agent: dict[str, dict[str, ManagerBackup]] = defaultdict(
|
||||
dict
|
||||
)
|
||||
for agent_id, agent_backups in backups_by_agent.items():
|
||||
agent_retention = agents_retention.get(agent_id)
|
||||
if agent_retention is None:
|
||||
# This agent does not have a retention setting,
|
||||
# so the global retention setting should be used.
|
||||
if global_copies is None:
|
||||
# This agent does not have a retention setting
|
||||
# and the global retention copies setting is None,
|
||||
# so this backup should not be deleted.
|
||||
continue
|
||||
# This backup and agent combination should be deleted.
|
||||
copies = global_copies
|
||||
elif (agent_copies := agent_retention.copies) is None:
|
||||
# This agent has a retention setting
|
||||
# where copies is set to None,
|
||||
# so the backup should not be deleted.
|
||||
continue
|
||||
else:
|
||||
# This backup and agent combination should be deleted.
|
||||
copies = agent_copies
|
||||
|
||||
backups_to_delete_by_agent[agent_id] = dict(
|
||||
sorted(
|
||||
agent_backups.items(),
|
||||
key=lambda backup_item: backup_item[1].date,
|
||||
)[: max(len(agent_backups) - copies, 0)]
|
||||
)
|
||||
|
||||
backup_ids_to_delete: dict[str, set[str]] = defaultdict(set)
|
||||
for agent_id, to_delete in backups_to_delete_by_agent.items():
|
||||
for backup_id in to_delete:
|
||||
backup_ids_to_delete[backup_id].add(agent_id)
|
||||
backups_to_delete: dict[str, ManagerBackup] = {}
|
||||
for backup_id, agent_ids in backup_ids_to_delete.items():
|
||||
backup = backups[backup_id]
|
||||
# filter the backup to only include the agents that should be deleted
|
||||
filtered_backup = replace(
|
||||
backup,
|
||||
agents={
|
||||
agent_id: agent_backup_status
|
||||
for agent_id, agent_backup_status in backup.agents.items()
|
||||
if agent_id in agent_ids
|
||||
},
|
||||
)
|
||||
backups_to_delete[backup_id] = filtered_backup
|
||||
return backups_to_delete
|
||||
|
||||
await manager.async_delete_filtered_backups(
|
||||
include_filter=_automatic_backups_filter, delete_filter=_delete_filter
|
||||
|
@ -16,7 +16,7 @@ if TYPE_CHECKING:
|
||||
STORE_DELAY_SAVE = 30
|
||||
STORAGE_KEY = DOMAIN
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_VERSION_MINOR = 5
|
||||
STORAGE_VERSION_MINOR = 6
|
||||
|
||||
|
||||
class StoredBackupData(TypedDict):
|
||||
@ -72,6 +72,10 @@ class _BackupStore(Store[StoredBackupData]):
|
||||
data["config"]["automatic_backups_configured"] = (
|
||||
data["config"]["create_backup"]["password"] is not None
|
||||
)
|
||||
if old_minor_version < 6:
|
||||
# Version 1.6 adds agent retention settings
|
||||
for agent in data["config"]["agents"]:
|
||||
data["config"]["agents"][agent]["retention"] = None
|
||||
|
||||
# Note: We allow reading data with major version 2.
|
||||
# Reject if major version is higher than 2.
|
||||
|
@ -346,7 +346,28 @@ async def handle_config_info(
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "backup/config/update",
|
||||
vol.Optional("agents"): vol.Schema({str: {"protected": bool}}),
|
||||
vol.Optional("agents"): vol.Schema(
|
||||
{
|
||||
str: {
|
||||
vol.Optional("protected"): bool,
|
||||
vol.Optional("retention"): vol.Any(
|
||||
vol.Schema(
|
||||
{
|
||||
# Note: We can't use cv.positive_int because it allows 0 even
|
||||
# though 0 is not positive.
|
||||
vol.Optional("copies"): vol.Any(
|
||||
vol.All(int, vol.Range(min=1)), None
|
||||
),
|
||||
vol.Optional("days"): vol.Any(
|
||||
vol.All(int, vol.Range(min=1)), None
|
||||
),
|
||||
},
|
||||
),
|
||||
None,
|
||||
),
|
||||
}
|
||||
}
|
||||
),
|
||||
vol.Optional("automatic_backups_configured"): bool,
|
||||
vol.Optional("create_backup"): vol.Schema(
|
||||
{
|
||||
|
@ -40,7 +40,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -86,7 +86,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -131,7 +131,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -177,7 +177,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -196,6 +196,7 @@
|
||||
'agents': dict({
|
||||
'test.remote': dict({
|
||||
'protected': True,
|
||||
'retention': None,
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
@ -225,7 +226,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -244,6 +245,7 @@
|
||||
'agents': dict({
|
||||
'test.remote': dict({
|
||||
'protected': True,
|
||||
'retention': None,
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
@ -274,7 +276,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -293,6 +295,7 @@
|
||||
'agents': dict({
|
||||
'test.remote': dict({
|
||||
'protected': True,
|
||||
'retention': None,
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
@ -322,7 +325,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -341,6 +344,7 @@
|
||||
'agents': dict({
|
||||
'test.remote': dict({
|
||||
'protected': True,
|
||||
'retention': None,
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
@ -371,7 +375,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -390,6 +394,7 @@
|
||||
'agents': dict({
|
||||
'test.remote': dict({
|
||||
'protected': True,
|
||||
'retention': None,
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': True,
|
||||
@ -419,7 +424,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -438,6 +443,7 @@
|
||||
'agents': dict({
|
||||
'test.remote': dict({
|
||||
'protected': True,
|
||||
'retention': None,
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': True,
|
||||
@ -468,7 +474,112 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
# name: test_store_migration[store_data5]
|
||||
dict({
|
||||
'data': dict({
|
||||
'backups': list([
|
||||
dict({
|
||||
'backup_id': 'abc123',
|
||||
'failed_agent_ids': list([
|
||||
'test.remote',
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
'config': dict({
|
||||
'agents': dict({
|
||||
'test.remote': dict({
|
||||
'protected': True,
|
||||
'retention': dict({
|
||||
'copies': None,
|
||||
'days': None,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': True,
|
||||
'create_backup': dict({
|
||||
'agent_ids': list([
|
||||
]),
|
||||
'include_addons': None,
|
||||
'include_all_addons': False,
|
||||
'include_database': True,
|
||||
'include_folders': None,
|
||||
'name': None,
|
||||
'password': 'hunter2',
|
||||
}),
|
||||
'last_attempted_automatic_backup': None,
|
||||
'last_completed_automatic_backup': None,
|
||||
'retention': dict({
|
||||
'copies': None,
|
||||
'days': None,
|
||||
}),
|
||||
'schedule': dict({
|
||||
'days': list([
|
||||
]),
|
||||
'recurrence': 'never',
|
||||
'state': 'never',
|
||||
'time': None,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
# name: test_store_migration[store_data5].1
|
||||
dict({
|
||||
'data': dict({
|
||||
'backups': list([
|
||||
dict({
|
||||
'backup_id': 'abc123',
|
||||
'failed_agent_ids': list([
|
||||
'test.remote',
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
'config': dict({
|
||||
'agents': dict({
|
||||
'test.remote': dict({
|
||||
'protected': True,
|
||||
'retention': dict({
|
||||
'copies': None,
|
||||
'days': None,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': True,
|
||||
'create_backup': dict({
|
||||
'agent_ids': list([
|
||||
'test-agent',
|
||||
]),
|
||||
'include_addons': None,
|
||||
'include_all_addons': False,
|
||||
'include_database': True,
|
||||
'include_folders': None,
|
||||
'name': None,
|
||||
'password': 'hunter2',
|
||||
}),
|
||||
'last_attempted_automatic_backup': None,
|
||||
'last_completed_automatic_backup': None,
|
||||
'retention': dict({
|
||||
'copies': None,
|
||||
'days': None,
|
||||
}),
|
||||
'schedule': dict({
|
||||
'days': list([
|
||||
]),
|
||||
'recurrence': 'never',
|
||||
'state': 'never',
|
||||
'time': None,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
|
@ -300,6 +300,61 @@
|
||||
'type': 'result',
|
||||
})
|
||||
# ---
|
||||
# name: test_config_load_config_info[with_hassio-storage_data10]
|
||||
dict({
|
||||
'id': 1,
|
||||
'result': dict({
|
||||
'config': dict({
|
||||
'agents': dict({
|
||||
'test-agent1': dict({
|
||||
'protected': True,
|
||||
'retention': dict({
|
||||
'copies': 3,
|
||||
'days': None,
|
||||
}),
|
||||
}),
|
||||
'test-agent2': dict({
|
||||
'protected': False,
|
||||
'retention': dict({
|
||||
'copies': None,
|
||||
'days': 7,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
'create_backup': dict({
|
||||
'agent_ids': list([
|
||||
'test-agent',
|
||||
]),
|
||||
'include_addons': None,
|
||||
'include_all_addons': False,
|
||||
'include_database': False,
|
||||
'include_folders': None,
|
||||
'name': None,
|
||||
'password': None,
|
||||
}),
|
||||
'last_attempted_automatic_backup': None,
|
||||
'last_completed_automatic_backup': None,
|
||||
'next_automatic_backup': '2024-11-17T04:55:00+01:00',
|
||||
'next_automatic_backup_additional': False,
|
||||
'retention': dict({
|
||||
'copies': None,
|
||||
'days': None,
|
||||
}),
|
||||
'schedule': dict({
|
||||
'days': list([
|
||||
'mon',
|
||||
'sun',
|
||||
]),
|
||||
'recurrence': 'custom_days',
|
||||
'time': None,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'success': True,
|
||||
'type': 'result',
|
||||
})
|
||||
# ---
|
||||
# name: test_config_load_config_info[with_hassio-storage_data1]
|
||||
dict({
|
||||
'id': 1,
|
||||
@ -556,9 +611,11 @@
|
||||
'agents': dict({
|
||||
'test-agent1': dict({
|
||||
'protected': True,
|
||||
'retention': None,
|
||||
}),
|
||||
'test-agent2': dict({
|
||||
'protected': False,
|
||||
'retention': None,
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
@ -714,6 +771,61 @@
|
||||
'type': 'result',
|
||||
})
|
||||
# ---
|
||||
# name: test_config_load_config_info[without_hassio-storage_data10]
|
||||
dict({
|
||||
'id': 1,
|
||||
'result': dict({
|
||||
'config': dict({
|
||||
'agents': dict({
|
||||
'test-agent1': dict({
|
||||
'protected': True,
|
||||
'retention': dict({
|
||||
'copies': 3,
|
||||
'days': None,
|
||||
}),
|
||||
}),
|
||||
'test-agent2': dict({
|
||||
'protected': False,
|
||||
'retention': dict({
|
||||
'copies': None,
|
||||
'days': 7,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
'create_backup': dict({
|
||||
'agent_ids': list([
|
||||
'test-agent',
|
||||
]),
|
||||
'include_addons': None,
|
||||
'include_all_addons': False,
|
||||
'include_database': False,
|
||||
'include_folders': None,
|
||||
'name': None,
|
||||
'password': None,
|
||||
}),
|
||||
'last_attempted_automatic_backup': None,
|
||||
'last_completed_automatic_backup': None,
|
||||
'next_automatic_backup': '2024-11-17T04:55:00+01:00',
|
||||
'next_automatic_backup_additional': False,
|
||||
'retention': dict({
|
||||
'copies': None,
|
||||
'days': None,
|
||||
}),
|
||||
'schedule': dict({
|
||||
'days': list([
|
||||
'mon',
|
||||
'sun',
|
||||
]),
|
||||
'recurrence': 'custom_days',
|
||||
'time': None,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'success': True,
|
||||
'type': 'result',
|
||||
})
|
||||
# ---
|
||||
# name: test_config_load_config_info[without_hassio-storage_data1]
|
||||
dict({
|
||||
'id': 1,
|
||||
@ -966,9 +1078,11 @@
|
||||
'agents': dict({
|
||||
'test-agent1': dict({
|
||||
'protected': True,
|
||||
'retention': None,
|
||||
}),
|
||||
'test-agent2': dict({
|
||||
'protected': False,
|
||||
'retention': None,
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
@ -1198,7 +1312,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -1315,7 +1429,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -1432,7 +1546,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -1482,9 +1596,11 @@
|
||||
'agents': dict({
|
||||
'test-agent1': dict({
|
||||
'protected': True,
|
||||
'retention': None,
|
||||
}),
|
||||
'test-agent2': dict({
|
||||
'protected': False,
|
||||
'retention': None,
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
@ -1527,9 +1643,11 @@
|
||||
'agents': dict({
|
||||
'test-agent1': dict({
|
||||
'protected': True,
|
||||
'retention': None,
|
||||
}),
|
||||
'test-agent2': dict({
|
||||
'protected': False,
|
||||
'retention': None,
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
@ -1559,7 +1677,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -1609,9 +1727,11 @@
|
||||
'agents': dict({
|
||||
'test-agent1': dict({
|
||||
'protected': True,
|
||||
'retention': None,
|
||||
}),
|
||||
'test-agent2': dict({
|
||||
'protected': False,
|
||||
'retention': None,
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
@ -1653,9 +1773,11 @@
|
||||
'agents': dict({
|
||||
'test-agent1': dict({
|
||||
'protected': False,
|
||||
'retention': None,
|
||||
}),
|
||||
'test-agent2': dict({
|
||||
'protected': True,
|
||||
'retention': None,
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
@ -1690,6 +1812,104 @@
|
||||
})
|
||||
# ---
|
||||
# name: test_config_update[commands13].3
|
||||
dict({
|
||||
'id': 7,
|
||||
'result': dict({
|
||||
'config': dict({
|
||||
'agents': dict({
|
||||
'test-agent1': dict({
|
||||
'protected': False,
|
||||
'retention': dict({
|
||||
'copies': 3,
|
||||
'days': None,
|
||||
}),
|
||||
}),
|
||||
'test-agent2': dict({
|
||||
'protected': True,
|
||||
'retention': None,
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
'create_backup': dict({
|
||||
'agent_ids': list([
|
||||
]),
|
||||
'include_addons': None,
|
||||
'include_all_addons': False,
|
||||
'include_database': True,
|
||||
'include_folders': None,
|
||||
'name': None,
|
||||
'password': None,
|
||||
}),
|
||||
'last_attempted_automatic_backup': None,
|
||||
'last_completed_automatic_backup': None,
|
||||
'next_automatic_backup': None,
|
||||
'next_automatic_backup_additional': False,
|
||||
'retention': dict({
|
||||
'copies': None,
|
||||
'days': None,
|
||||
}),
|
||||
'schedule': dict({
|
||||
'days': list([
|
||||
]),
|
||||
'recurrence': 'never',
|
||||
'time': None,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'success': True,
|
||||
'type': 'result',
|
||||
})
|
||||
# ---
|
||||
# name: test_config_update[commands13].4
|
||||
dict({
|
||||
'id': 9,
|
||||
'result': dict({
|
||||
'config': dict({
|
||||
'agents': dict({
|
||||
'test-agent1': dict({
|
||||
'protected': False,
|
||||
'retention': None,
|
||||
}),
|
||||
'test-agent2': dict({
|
||||
'protected': True,
|
||||
'retention': dict({
|
||||
'copies': None,
|
||||
'days': 7,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
'create_backup': dict({
|
||||
'agent_ids': list([
|
||||
]),
|
||||
'include_addons': None,
|
||||
'include_all_addons': False,
|
||||
'include_database': True,
|
||||
'include_folders': None,
|
||||
'name': None,
|
||||
'password': None,
|
||||
}),
|
||||
'last_attempted_automatic_backup': None,
|
||||
'last_completed_automatic_backup': None,
|
||||
'next_automatic_backup': None,
|
||||
'next_automatic_backup_additional': False,
|
||||
'retention': dict({
|
||||
'copies': None,
|
||||
'days': None,
|
||||
}),
|
||||
'schedule': dict({
|
||||
'days': list([
|
||||
]),
|
||||
'recurrence': 'never',
|
||||
'time': None,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'success': True,
|
||||
'type': 'result',
|
||||
})
|
||||
# ---
|
||||
# name: test_config_update[commands13].5
|
||||
dict({
|
||||
'data': dict({
|
||||
'backups': list([
|
||||
@ -1698,9 +1918,14 @@
|
||||
'agents': dict({
|
||||
'test-agent1': dict({
|
||||
'protected': False,
|
||||
'retention': None,
|
||||
}),
|
||||
'test-agent2': dict({
|
||||
'protected': True,
|
||||
'retention': dict({
|
||||
'copies': None,
|
||||
'days': 7,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
@ -1730,7 +1955,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -1845,7 +2070,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -1960,7 +2185,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -2077,7 +2302,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -2196,7 +2421,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -2313,7 +2538,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -2434,7 +2659,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -2559,7 +2784,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -2676,7 +2901,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -2793,7 +3018,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -2910,7 +3135,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -3027,7 +3252,7 @@
|
||||
}),
|
||||
}),
|
||||
'key': 'backup',
|
||||
'minor_version': 5,
|
||||
'minor_version': 6,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@ -3259,6 +3484,158 @@
|
||||
'type': 'result',
|
||||
})
|
||||
# ---
|
||||
# name: test_config_update_errors[command12]
|
||||
dict({
|
||||
'id': 1,
|
||||
'result': dict({
|
||||
'config': dict({
|
||||
'agents': dict({
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
'create_backup': dict({
|
||||
'agent_ids': list([
|
||||
]),
|
||||
'include_addons': None,
|
||||
'include_all_addons': False,
|
||||
'include_database': True,
|
||||
'include_folders': None,
|
||||
'name': None,
|
||||
'password': None,
|
||||
}),
|
||||
'last_attempted_automatic_backup': None,
|
||||
'last_completed_automatic_backup': None,
|
||||
'next_automatic_backup': None,
|
||||
'next_automatic_backup_additional': False,
|
||||
'retention': dict({
|
||||
'copies': None,
|
||||
'days': None,
|
||||
}),
|
||||
'schedule': dict({
|
||||
'days': list([
|
||||
]),
|
||||
'recurrence': 'never',
|
||||
'time': None,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'success': True,
|
||||
'type': 'result',
|
||||
})
|
||||
# ---
|
||||
# name: test_config_update_errors[command12].1
|
||||
dict({
|
||||
'id': 3,
|
||||
'result': dict({
|
||||
'config': dict({
|
||||
'agents': dict({
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
'create_backup': dict({
|
||||
'agent_ids': list([
|
||||
]),
|
||||
'include_addons': None,
|
||||
'include_all_addons': False,
|
||||
'include_database': True,
|
||||
'include_folders': None,
|
||||
'name': None,
|
||||
'password': None,
|
||||
}),
|
||||
'last_attempted_automatic_backup': None,
|
||||
'last_completed_automatic_backup': None,
|
||||
'next_automatic_backup': None,
|
||||
'next_automatic_backup_additional': False,
|
||||
'retention': dict({
|
||||
'copies': None,
|
||||
'days': None,
|
||||
}),
|
||||
'schedule': dict({
|
||||
'days': list([
|
||||
]),
|
||||
'recurrence': 'never',
|
||||
'time': None,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'success': True,
|
||||
'type': 'result',
|
||||
})
|
||||
# ---
|
||||
# name: test_config_update_errors[command13]
|
||||
dict({
|
||||
'id': 1,
|
||||
'result': dict({
|
||||
'config': dict({
|
||||
'agents': dict({
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
'create_backup': dict({
|
||||
'agent_ids': list([
|
||||
]),
|
||||
'include_addons': None,
|
||||
'include_all_addons': False,
|
||||
'include_database': True,
|
||||
'include_folders': None,
|
||||
'name': None,
|
||||
'password': None,
|
||||
}),
|
||||
'last_attempted_automatic_backup': None,
|
||||
'last_completed_automatic_backup': None,
|
||||
'next_automatic_backup': None,
|
||||
'next_automatic_backup_additional': False,
|
||||
'retention': dict({
|
||||
'copies': None,
|
||||
'days': None,
|
||||
}),
|
||||
'schedule': dict({
|
||||
'days': list([
|
||||
]),
|
||||
'recurrence': 'never',
|
||||
'time': None,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'success': True,
|
||||
'type': 'result',
|
||||
})
|
||||
# ---
|
||||
# name: test_config_update_errors[command13].1
|
||||
dict({
|
||||
'id': 3,
|
||||
'result': dict({
|
||||
'config': dict({
|
||||
'agents': dict({
|
||||
}),
|
||||
'automatic_backups_configured': False,
|
||||
'create_backup': dict({
|
||||
'agent_ids': list([
|
||||
]),
|
||||
'include_addons': None,
|
||||
'include_all_addons': False,
|
||||
'include_database': True,
|
||||
'include_folders': None,
|
||||
'name': None,
|
||||
'password': None,
|
||||
}),
|
||||
'last_attempted_automatic_backup': None,
|
||||
'last_completed_automatic_backup': None,
|
||||
'next_automatic_backup': None,
|
||||
'next_automatic_backup_additional': False,
|
||||
'retention': dict({
|
||||
'copies': None,
|
||||
'days': None,
|
||||
}),
|
||||
'schedule': dict({
|
||||
'days': list([
|
||||
]),
|
||||
'recurrence': 'never',
|
||||
'time': None,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'success': True,
|
||||
'type': 'result',
|
||||
})
|
||||
# ---
|
||||
# name: test_config_update_errors[command1]
|
||||
dict({
|
||||
'id': 1,
|
||||
|
@ -98,7 +98,7 @@ def mock_delay_save() -> Generator[None]:
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"agents": {"test.remote": {"protected": True}},
|
||||
"agents": {"test.remote": {"protected": True, "retention": None}},
|
||||
"automatic_backups_configured": False,
|
||||
"create_backup": {
|
||||
"agent_ids": [],
|
||||
@ -200,6 +200,49 @@ def mock_delay_save() -> Generator[None]:
|
||||
"minor_version": 4,
|
||||
"version": 1,
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"backups": [
|
||||
{
|
||||
"backup_id": "abc123",
|
||||
"failed_agent_ids": ["test.remote"],
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"agents": {
|
||||
"test.remote": {
|
||||
"protected": True,
|
||||
"retention": {"copies": None, "days": None},
|
||||
}
|
||||
},
|
||||
"automatic_backups_configured": True,
|
||||
"create_backup": {
|
||||
"agent_ids": [],
|
||||
"include_addons": None,
|
||||
"include_all_addons": False,
|
||||
"include_database": True,
|
||||
"include_folders": None,
|
||||
"name": None,
|
||||
"password": "hunter2",
|
||||
},
|
||||
"last_attempted_automatic_backup": None,
|
||||
"last_completed_automatic_backup": None,
|
||||
"retention": {
|
||||
"copies": None,
|
||||
"days": None,
|
||||
},
|
||||
"schedule": {
|
||||
"days": [],
|
||||
"recurrence": "never",
|
||||
"state": "never",
|
||||
"time": None,
|
||||
},
|
||||
},
|
||||
},
|
||||
"key": DOMAIN,
|
||||
"minor_version": 6,
|
||||
"version": 1,
|
||||
},
|
||||
],
|
||||
)
|
||||
async def test_store_migration(
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Tests for the Backup integration."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from dataclasses import replace
|
||||
from typing import Any
|
||||
from unittest.mock import ANY, AsyncMock, MagicMock, Mock, call, patch
|
||||
|
||||
@ -9,6 +10,7 @@ import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.backup import (
|
||||
AddonInfo,
|
||||
AgentBackup,
|
||||
BackupAgentError,
|
||||
BackupNotFound,
|
||||
@ -81,6 +83,21 @@ DEFAULT_STORAGE_DATA: dict[str, Any] = {
|
||||
}
|
||||
DAILY = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
|
||||
|
||||
TEST_MANAGER_BACKUP = ManagerBackup(
|
||||
addons=[AddonInfo(name="Test", slug="test", version="1.0.0")],
|
||||
agents={"test.test-agent": AgentBackupStatus(protected=True, size=0)},
|
||||
backup_id="backup-1",
|
||||
date="1970-01-01T00:00:00.000Z",
|
||||
database_included=True,
|
||||
extra_metadata={"instance_id": "abc123", "with_automatic_settings": True},
|
||||
folders=[Folder.MEDIA, Folder.SHARE],
|
||||
homeassistant_included=True,
|
||||
homeassistant_version="2024.12.0",
|
||||
name="Test",
|
||||
failed_agent_ids=[],
|
||||
with_automatic_settings=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sync_access_token_proxy(
|
||||
@ -1160,8 +1177,8 @@ async def test_agents_info(
|
||||
"backups": [],
|
||||
"config": {
|
||||
"agents": {
|
||||
"test-agent1": {"protected": True},
|
||||
"test-agent2": {"protected": False},
|
||||
"test-agent1": {"protected": True, "retention": None},
|
||||
"test-agent2": {"protected": False, "retention": None},
|
||||
},
|
||||
"automatic_backups_configured": False,
|
||||
"create_backup": {
|
||||
@ -1253,6 +1270,47 @@ async def test_agents_info(
|
||||
"minor_version": store.STORAGE_VERSION_MINOR,
|
||||
},
|
||||
},
|
||||
{
|
||||
"backup": {
|
||||
"data": {
|
||||
"backups": [],
|
||||
"config": {
|
||||
"agents": {
|
||||
"test-agent1": {
|
||||
"protected": True,
|
||||
"retention": {"copies": 3, "days": None},
|
||||
},
|
||||
"test-agent2": {
|
||||
"protected": False,
|
||||
"retention": {"copies": None, "days": 7},
|
||||
},
|
||||
},
|
||||
"automatic_backups_configured": False,
|
||||
"create_backup": {
|
||||
"agent_ids": ["test-agent"],
|
||||
"include_addons": None,
|
||||
"include_all_addons": False,
|
||||
"include_database": False,
|
||||
"include_folders": None,
|
||||
"name": None,
|
||||
"password": None,
|
||||
},
|
||||
"retention": {"copies": None, "days": None},
|
||||
"last_attempted_automatic_backup": None,
|
||||
"last_completed_automatic_backup": None,
|
||||
"schedule": {
|
||||
"days": ["mon", "sun"],
|
||||
"recurrence": "custom_days",
|
||||
"state": "never",
|
||||
"time": None,
|
||||
},
|
||||
},
|
||||
},
|
||||
"key": DOMAIN,
|
||||
"version": store.STORAGE_VERSION,
|
||||
"minor_version": store.STORAGE_VERSION_MINOR,
|
||||
},
|
||||
},
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
@ -1271,7 +1329,7 @@ async def test_config_load_config_info(
|
||||
snapshot: SnapshotAssertion,
|
||||
hass_storage: dict[str, Any],
|
||||
with_hassio: bool,
|
||||
storage_data: dict[str, Any] | None,
|
||||
storage_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test loading stored backup config and reading it via config/info."""
|
||||
client = await hass_ws_client(hass)
|
||||
@ -1412,6 +1470,20 @@ async def test_config_load_config_info(
|
||||
"test-agent2": {"protected": True},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "backup/config/update",
|
||||
"agents": {
|
||||
"test-agent1": {"retention": {"copies": 3}},
|
||||
"test-agent2": {"retention": None},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "backup/config/update",
|
||||
"agents": {
|
||||
"test-agent1": {"retention": None},
|
||||
"test-agent2": {"retention": {"days": 7}},
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
@ -1433,7 +1505,7 @@ async def test_config_update(
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
snapshot: SnapshotAssertion,
|
||||
commands: dict[str, Any],
|
||||
commands: list[dict[str, Any]],
|
||||
hass_storage: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test updating the backup config."""
|
||||
@ -1522,6 +1594,14 @@ async def test_config_update(
|
||||
"type": "backup/config/update",
|
||||
"retention": {"days": 0},
|
||||
},
|
||||
{
|
||||
"type": "backup/config/update",
|
||||
"agents": {"test-agent1": {"retention": {"copies": 0}}},
|
||||
},
|
||||
{
|
||||
"type": "backup/config/update",
|
||||
"agents": {"test-agent1": {"retention": {"days": 0}}},
|
||||
},
|
||||
],
|
||||
)
|
||||
async def test_config_update_errors(
|
||||
@ -2489,6 +2569,253 @@ async def test_config_schedule_logic(
|
||||
1,
|
||||
{},
|
||||
),
|
||||
(
|
||||
{
|
||||
"type": "backup/config/update",
|
||||
"agents": {
|
||||
"test.test-agent": {
|
||||
"protected": True,
|
||||
"retention": None,
|
||||
},
|
||||
"test.test-agent2": {
|
||||
"protected": True,
|
||||
"retention": {
|
||||
"copies": 1,
|
||||
"days": None,
|
||||
},
|
||||
},
|
||||
},
|
||||
"create_backup": {"agent_ids": ["test.test-agent"]},
|
||||
"retention": {"copies": 3, "days": None},
|
||||
"schedule": {"recurrence": "daily"},
|
||||
},
|
||||
{
|
||||
"backup-1": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-1",
|
||||
date="2024-11-09T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-2": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-2",
|
||||
date="2024-11-10T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-3": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-3",
|
||||
date="2024-11-11T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-4": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-4",
|
||||
date="2024-11-12T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-5": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-5",
|
||||
date="2024-11-12T04:45:00+01:00",
|
||||
with_automatic_settings=False,
|
||||
),
|
||||
},
|
||||
{},
|
||||
{},
|
||||
"2024-11-11T04:45:00+01:00",
|
||||
"2024-11-12T04:45:00+01:00",
|
||||
"2024-11-12T04:45:00+01:00",
|
||||
1,
|
||||
1,
|
||||
{
|
||||
"test.test-agent": [call("backup-1")],
|
||||
"test.test-agent2": [call("backup-1"), call("backup-2")],
|
||||
},
|
||||
),
|
||||
(
|
||||
{
|
||||
"type": "backup/config/update",
|
||||
"agents": {
|
||||
"test.test-agent": {
|
||||
"protected": True,
|
||||
"retention": None,
|
||||
},
|
||||
"test.test-agent2": {
|
||||
"protected": True,
|
||||
"retention": {
|
||||
"copies": 1,
|
||||
"days": None,
|
||||
},
|
||||
},
|
||||
},
|
||||
"create_backup": {"agent_ids": ["test.test-agent"]},
|
||||
"retention": {"copies": None, "days": None},
|
||||
"schedule": {"recurrence": "daily"},
|
||||
},
|
||||
{
|
||||
"backup-1": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-1",
|
||||
date="2024-11-09T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-2": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-2",
|
||||
date="2024-11-10T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-3": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-3",
|
||||
date="2024-11-11T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-4": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-4",
|
||||
date="2024-11-12T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-5": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-5",
|
||||
date="2024-11-12T04:45:00+01:00",
|
||||
with_automatic_settings=False,
|
||||
),
|
||||
},
|
||||
{},
|
||||
{},
|
||||
"2024-11-11T04:45:00+01:00",
|
||||
"2024-11-12T04:45:00+01:00",
|
||||
"2024-11-12T04:45:00+01:00",
|
||||
1,
|
||||
1,
|
||||
{
|
||||
"test.test-agent2": [call("backup-1"), call("backup-2")],
|
||||
},
|
||||
),
|
||||
(
|
||||
{
|
||||
"type": "backup/config/update",
|
||||
"agents": {
|
||||
"test.test-agent": {
|
||||
"protected": True,
|
||||
"retention": {
|
||||
"copies": None,
|
||||
"days": None,
|
||||
},
|
||||
},
|
||||
"test.test-agent2": {
|
||||
"protected": True,
|
||||
"retention": None,
|
||||
},
|
||||
},
|
||||
"create_backup": {"agent_ids": ["test.test-agent"]},
|
||||
"retention": {"copies": 2, "days": None},
|
||||
"schedule": {"recurrence": "daily"},
|
||||
},
|
||||
{
|
||||
"backup-1": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-1",
|
||||
date="2024-11-09T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-2": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-2",
|
||||
date="2024-11-10T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-3": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-3",
|
||||
date="2024-11-11T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-4": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-4",
|
||||
date="2024-11-12T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-5": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-5",
|
||||
date="2024-11-12T04:45:00+01:00",
|
||||
with_automatic_settings=False,
|
||||
),
|
||||
},
|
||||
{},
|
||||
{},
|
||||
"2024-11-11T04:45:00+01:00",
|
||||
"2024-11-12T04:45:00+01:00",
|
||||
"2024-11-12T04:45:00+01:00",
|
||||
1,
|
||||
1,
|
||||
{
|
||||
"test.test-agent2": [call("backup-1")],
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
@patch("homeassistant.components.backup.config.BACKUP_START_TIME_JITTER", 0)
|
||||
@ -3221,6 +3548,223 @@ async def test_config_retention_copies_logic_manual_backup(
|
||||
1,
|
||||
{"test.test-agent": [call("backup-1"), call("backup-2")]},
|
||||
),
|
||||
(
|
||||
None,
|
||||
[
|
||||
{
|
||||
"type": "backup/config/update",
|
||||
"agents": {
|
||||
"test.test-agent": {
|
||||
"protected": True,
|
||||
"retention": {"days": 3},
|
||||
},
|
||||
"test.test-agent2": {
|
||||
"protected": True,
|
||||
"retention": None,
|
||||
},
|
||||
},
|
||||
"create_backup": {"agent_ids": ["test-agent"]},
|
||||
"retention": {"copies": None, "days": 2},
|
||||
"schedule": {"recurrence": "never"},
|
||||
}
|
||||
],
|
||||
{
|
||||
"backup-1": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-1",
|
||||
date="2024-11-09T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-2": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-2",
|
||||
date="2024-11-10T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-3": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-3",
|
||||
date="2024-11-11T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-4": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-4",
|
||||
date="2024-11-10T04:45:00+01:00",
|
||||
with_automatic_settings=False,
|
||||
),
|
||||
},
|
||||
{},
|
||||
{},
|
||||
"2024-11-11T04:45:00+01:00",
|
||||
"2024-11-11T12:00:00+01:00",
|
||||
"2024-11-12T12:00:00+01:00",
|
||||
1,
|
||||
{
|
||||
"test.test-agent": [call("backup-1")],
|
||||
"test.test-agent2": [call("backup-1"), call("backup-2")],
|
||||
},
|
||||
),
|
||||
(
|
||||
None,
|
||||
[
|
||||
{
|
||||
"type": "backup/config/update",
|
||||
"agents": {
|
||||
"test.test-agent": {
|
||||
"protected": True,
|
||||
"retention": {"days": 3},
|
||||
},
|
||||
"test.test-agent2": {
|
||||
"protected": True,
|
||||
"retention": None,
|
||||
},
|
||||
},
|
||||
"create_backup": {"agent_ids": ["test-agent"]},
|
||||
"retention": {"copies": None, "days": None},
|
||||
"schedule": {"recurrence": "never"},
|
||||
}
|
||||
],
|
||||
{
|
||||
"backup-1": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-1",
|
||||
date="2024-11-09T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-2": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-2",
|
||||
date="2024-11-10T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-3": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-3",
|
||||
date="2024-11-11T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-4": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-4",
|
||||
date="2024-11-10T04:45:00+01:00",
|
||||
with_automatic_settings=False,
|
||||
),
|
||||
},
|
||||
{},
|
||||
{},
|
||||
"2024-11-11T04:45:00+01:00",
|
||||
"2024-11-11T12:00:00+01:00",
|
||||
"2024-11-12T12:00:00+01:00",
|
||||
1,
|
||||
{
|
||||
"test.test-agent": [call("backup-1")],
|
||||
},
|
||||
),
|
||||
(
|
||||
None,
|
||||
[
|
||||
{
|
||||
"type": "backup/config/update",
|
||||
"agents": {
|
||||
"test.test-agent": {
|
||||
"protected": True,
|
||||
"retention": None,
|
||||
},
|
||||
"test.test-agent2": {
|
||||
"protected": True,
|
||||
"retention": {"copies": None, "days": None},
|
||||
},
|
||||
},
|
||||
"create_backup": {"agent_ids": ["test-agent"]},
|
||||
"retention": {"copies": None, "days": 2},
|
||||
"schedule": {"recurrence": "never"},
|
||||
}
|
||||
],
|
||||
{
|
||||
"backup-1": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-1",
|
||||
date="2024-11-09T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-2": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-2",
|
||||
date="2024-11-10T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-3": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-3",
|
||||
date="2024-11-11T04:45:00+01:00",
|
||||
with_automatic_settings=True,
|
||||
),
|
||||
"backup-4": replace(
|
||||
TEST_MANAGER_BACKUP,
|
||||
agents={
|
||||
"test.test-agent": MagicMock(spec=AgentBackupStatus),
|
||||
"test.test-agent2": MagicMock(spec=AgentBackupStatus),
|
||||
},
|
||||
backup_id="backup-4",
|
||||
date="2024-11-10T04:45:00+01:00",
|
||||
with_automatic_settings=False,
|
||||
),
|
||||
},
|
||||
{},
|
||||
{},
|
||||
"2024-11-11T04:45:00+01:00",
|
||||
"2024-11-11T12:00:00+01:00",
|
||||
"2024-11-12T12:00:00+01:00",
|
||||
1,
|
||||
{
|
||||
"test.test-agent": [call("backup-1"), call("backup-2")],
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_config_retention_days_logic(
|
||||
@ -3278,7 +3822,7 @@ async def test_config_retention_days_logic(
|
||||
freezer.move_to(start_time)
|
||||
|
||||
mock_agents = await setup_backup_integration(
|
||||
hass, remote_agents=["test.test-agent"]
|
||||
hass, remote_agents=["test.test-agent", "test.test-agent2"]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user