From 26cdfd79dd67fef87a36d82ada14e0f615e28b89 Mon Sep 17 00:00:00 2001 From: Joan Antoni RE Date: Fri, 10 Jul 2026 11:54:20 +0200 Subject: [PATCH 1/3] Validate KB and backup bucket exist before restore --- nucliadb/src/nucliadb/backups/const.py | 1 + nucliadb/src/nucliadb/backups/restore.py | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/nucliadb/src/nucliadb/backups/const.py b/nucliadb/src/nucliadb/backups/const.py index b1becb5f36..395a4458cd 100644 --- a/nucliadb/src/nucliadb/backups/const.py +++ b/nucliadb/src/nucliadb/backups/const.py @@ -32,6 +32,7 @@ class StorageKeys: Defines the key templates used to store backup files in the backups bucket of the storage. """ + BACKUP_FOLDER = "backups/{backup_id}" BACKUP_PREFIX = "backups/{backup_id}/" RESOURCES_PREFIX = "backups/{backup_id}/resources/" RESOURCE = "backups/{backup_id}/resources/{resource_id}.tar" diff --git a/nucliadb/src/nucliadb/backups/restore.py b/nucliadb/src/nucliadb/backups/restore.py index 6f6c7d0f43..82f4196158 100644 --- a/nucliadb/src/nucliadb/backups/restore.py +++ b/nucliadb/src/nucliadb/backups/restore.py @@ -18,7 +18,6 @@ # along with this program. If not, see . # - import asyncio import functools import json @@ -32,6 +31,7 @@ from nucliadb.backups.const import MaindbKeys, StorageKeys from nucliadb.backups.models import RestoreBackupRequest from nucliadb.backups.settings import settings +from nucliadb.common import datamanagers from nucliadb.common.context import ApplicationContext from nucliadb.export_import.utils import ( import_binary, @@ -74,6 +74,22 @@ async def restore_kb(context: ApplicationContext, kbid: str, backup_id: str): """ Downloads the backup files from the cloud storage and imports them into the KB. """ + kb_exists = await datamanagers.atomic.kb.exists_kb(kbid=kbid) + if not kb_exists: + logger.warning( + "Trying to restore on a KB that doesn't exist", + extra={"kbid": kbid, "backup_id": backup_id}, + ) + return + if not await context.blob_storage.exists_object( + bucket=settings.backups_bucket, key=StorageKeys.BACKUP_FOLDER.format(backup_id=backup_id) + ): + logger.warning( + "Trying to restore from a bucket that doesn't exist", + extra={"kbid": kbid, "backup_id": backup_id}, + ) + return + await restore_resources(context, kbid, backup_id) await restore_labels(context, kbid, backup_id) await restore_synonyms(context, kbid, backup_id) From 75d6b7948f25ae22c3ac45c88baf66d70f57ee5f Mon Sep 17 00:00:00 2001 From: Joan Antoni RE Date: Fri, 10 Jul 2026 13:30:22 +0200 Subject: [PATCH 2/3] Add tests --- nucliadb/src/nucliadb/backups/restore.py | 5 ++-- .../nucliadb/integration/test_backups.py | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/nucliadb/src/nucliadb/backups/restore.py b/nucliadb/src/nucliadb/backups/restore.py index 82f4196158..f3c499f79c 100644 --- a/nucliadb/src/nucliadb/backups/restore.py +++ b/nucliadb/src/nucliadb/backups/restore.py @@ -31,6 +31,7 @@ from nucliadb.backups.const import MaindbKeys, StorageKeys from nucliadb.backups.models import RestoreBackupRequest from nucliadb.backups.settings import settings +from nucliadb.backups.utils import exists_backup from nucliadb.common import datamanagers from nucliadb.common.context import ApplicationContext from nucliadb.export_import.utils import ( @@ -81,9 +82,7 @@ async def restore_kb(context: ApplicationContext, kbid: str, backup_id: str): extra={"kbid": kbid, "backup_id": backup_id}, ) return - if not await context.blob_storage.exists_object( - bucket=settings.backups_bucket, key=StorageKeys.BACKUP_FOLDER.format(backup_id=backup_id) - ): + if not exists_backup(context.blob_storage, backup_id): logger.warning( "Trying to restore from a bucket that doesn't exist", extra={"kbid": kbid, "backup_id": backup_id}, diff --git a/nucliadb/tests/nucliadb/integration/test_backups.py b/nucliadb/tests/nucliadb/integration/test_backups.py index 9ba387cbbd..ec6a59effa 100644 --- a/nucliadb/tests/nucliadb/integration/test_backups.py +++ b/nucliadb/tests/nucliadb/integration/test_backups.py @@ -23,6 +23,7 @@ import pytest from httpx import AsyncClient +from pytest import LogCaptureFixture from nucliadb.backups.const import StorageKeys from nucliadb.backups.create import backup_kb_task, get_metadata, set_metadata @@ -379,3 +380,29 @@ def test_all_broker_message_fields_are_backed_up(): or field_name in BM_FIELDS["common"] or field_name in BM_FIELDS["deprecated"] ), f"Field {field_name} is not being taken into account in the backup!" + + +@pytest.mark.deploy_modes("standalone") +async def test_restore_with_nonexistent_kb( + nucliadb_reader: AsyncClient, + settings: BackupSettings, + context: ApplicationContext, + caplog: LogCaptureFixture, +): + fake_kbid = str(uuid.uuid4()) + fake_backup_id = str(uuid.uuid4()) + await restore_kb_task(context, RestoreBackupRequest(kb_id=fake_kbid, backup_id=fake_backup_id)) + assert caplog.records[0].message == "Trying to restore on a KB that doesn't exist" + + +@pytest.mark.deploy_modes("standalone") +async def test_restore_with_nonexistent_backup_bucket( + nucliadb_reader: AsyncClient, + dst_kb: str, + settings: BackupSettings, + context: ApplicationContext, + caplog: LogCaptureFixture, +): + fake_backup_id = str(uuid.uuid4()) + await restore_kb_task(context, RestoreBackupRequest(kb_id=dst_kb, backup_id=fake_backup_id)) + assert caplog.records[0].message == "Trying to restore from a bucket that doesn't exist" From 79541ff18ed30b0441983cc73d7757e2e3fae5c6 Mon Sep 17 00:00:00 2001 From: Joan Antoni RE Date: Fri, 10 Jul 2026 14:08:48 +0200 Subject: [PATCH 3/3] Fix condition --- nucliadb/src/nucliadb/backups/restore.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nucliadb/src/nucliadb/backups/restore.py b/nucliadb/src/nucliadb/backups/restore.py index f3c499f79c..cbb7e743b4 100644 --- a/nucliadb/src/nucliadb/backups/restore.py +++ b/nucliadb/src/nucliadb/backups/restore.py @@ -82,7 +82,7 @@ async def restore_kb(context: ApplicationContext, kbid: str, backup_id: str): extra={"kbid": kbid, "backup_id": backup_id}, ) return - if not exists_backup(context.blob_storage, backup_id): + if not await exists_backup(context.blob_storage, backup_id): logger.warning( "Trying to restore from a bucket that doesn't exist", extra={"kbid": kbid, "backup_id": backup_id},