Skip to content

Commit

Permalink
Check if backup is still available when accessing
Browse files Browse the repository at this point in the history
When accessing a backup (e.g. to restore, download or delete it) check
if the locations of the backup file are still available. If not, force
a backup reload instead.
  • Loading branch information
agners committed Jan 2, 2025
1 parent c2f6e31 commit 97353d7
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 7 deletions.
25 changes: 20 additions & 5 deletions supervisor/api/backups.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,26 @@ def _ensure_list(item: Any) -> list:
class APIBackups(CoreSysAttributes):
"""Handle RESTful API for backups functions."""

def _extract_slug(self, request):
async def _extract_slug(self, request):
"""Return backup, throw an exception if it doesn't exist."""
backup = self.sys_backups.get(request.match_info.get("slug"))
if not backup:
raise APINotFound("Backup does not exist")

# Reload in case locations of this backup are no longer in sync
# This usually means the user
def _check_need_reload():
for location in backup.all_locations:
if not Path(location).exists():
return False
return True

if not await self.sys_run_in_executor(_check_need_reload):
_LOGGER.warning("Backup %s missing in at least one location, scheduling reload", backup.slug)
# Schedule a reload
self.sys_jobs.schedule_job(self.sys_backups.reload, JobSchedulerOptions())
raise APINotFound("Backup does not exist")

return backup

def _list_backups(self):
Expand Down Expand Up @@ -212,7 +227,7 @@ async def reload(self, _):
@api_process
async def backup_info(self, request):
"""Return backup info."""
backup = self._extract_slug(request)
backup = await self._extract_slug(request)

data_addons = []
for addon_data in backup.addons:
Expand Down Expand Up @@ -384,7 +399,7 @@ async def backup_partial(self, request: web.Request):
@api_process
async def restore_full(self, request: web.Request):
"""Full restore of a backup."""
backup = self._extract_slug(request)
backup = await self._extract_slug(request)
body = await api_validate(SCHEMA_RESTORE_FULL, request)
self._validate_cloud_backup_location(
request, body.get(ATTR_LOCATION, backup.location)
Expand All @@ -404,7 +419,7 @@ async def restore_full(self, request: web.Request):
@api_process
async def restore_partial(self, request: web.Request):
"""Partial restore a backup."""
backup = self._extract_slug(request)
backup = await self._extract_slug(request)
body = await api_validate(SCHEMA_RESTORE_PARTIAL, request)
self._validate_cloud_backup_location(
request, body.get(ATTR_LOCATION, backup.location)
Expand Down Expand Up @@ -435,7 +450,7 @@ async def thaw(self, request: web.Request):
@api_process
async def remove(self, request: web.Request):
"""Remove a backup."""
backup = self._extract_slug(request)
backup = await self._extract_slug(request)
body = await api_validate(SCHEMA_REMOVE, request)
locations: list[LOCATION_TYPE] | None = None

Expand Down
5 changes: 3 additions & 2 deletions supervisor/backups/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,18 +200,19 @@ def _create_backup(
return backup

def load(self) -> Awaitable[None]:
"""Load exists backups data.
"""Load existing backups data.
Return a coroutine.
"""
return self.reload()

@Job(name="backup_reload")
async def reload(
self,
location: LOCATION_TYPE | type[DEFAULT] = DEFAULT,
filename: str | None = None,
) -> bool:
"""Load exists backups."""
"""Load existing backups."""

async def _load_backup(location: str | None, tar_file: Path) -> bool:
"""Load the backup."""
Expand Down

0 comments on commit 97353d7

Please sign in to comment.