Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 54 additions & 52 deletions fdbbackup/backup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2529,16 +2529,16 @@ std::pair<Version, Version> getMaxMinRestorableVersions(const BackupDescription&
// If restoreVersion is invalidVersion or latestVersion, use the maximum or minimum restorable version respectively for
// selected key ranges. If restoreTimestamp is specified, any specified restoreVersion will be overridden to the version
// resolved to that timestamp.
Future<Void> queryBackup(const char* name,
std::string destinationContainer,
Optional<std::string> proxy,
Standalone<VectorRef<KeyRangeRef>> keyRangesFilter,
Version restoreVersion,
Version snapshotVersion,
std::string originalClusterFile,
std::string restoreTimestamp,
Verbose verbose,
Optional<Database> cx) {
Future<int> queryBackup(const char* name,
std::string destinationContainer,
Optional<std::string> proxy,
Standalone<VectorRef<KeyRangeRef>> keyRangesFilter,
Version restoreVersion,
Version snapshotVersion,
std::string originalClusterFile,
std::string restoreTimestamp,
Verbose verbose,
Optional<Database> cx) {
UID operationId = deterministicRandom()->randomUniqueID();
JsonBuilderObject result;
std::string errorMessage;
Expand All @@ -2554,37 +2554,38 @@ Future<Void> queryBackup(const char* name,
.detail("RestoreTimestamp", restoreTimestamp)
.detail("BackupClusterFile", originalClusterFile);

// Resolve restoreTimestamp if given
if (!restoreTimestamp.empty()) {
if (originalClusterFile.empty()) {
reportBackupQueryError(
operationId,
result,
format("a cluster file must be given in order to resolve restore target timestamp '%s'",
restoreTimestamp.c_str()));
co_return;
}

if (!fileExists(originalClusterFile)) {
reportBackupQueryError(operationId,
result,
format("The specified original source database cluster file '%s' does not exist\n",
originalClusterFile.c_str()));
co_return;
}

Database origDb = Database::createDatabase(originalClusterFile, ApiVersion::LATEST_VERSION);
Version v = co_await timeKeeperVersionFromDatetime(restoreTimestamp, origDb);
result["restore_timestamp"] = restoreTimestamp;
result["restore_timestamp_resolved_version"] = v;
restoreVersion = v;
}

int64_t totalRangeFilesSize = 0;
int64_t totalLogFilesSize = 0;
JsonBuilderArray rangeFilesJson;
JsonBuilderArray logFilesJson;
try {
// Resolve restoreTimestamp if given
if (!restoreTimestamp.empty()) {
if (originalClusterFile.empty()) {
reportBackupQueryError(
operationId,
result,
format("a cluster file must be given in order to resolve restore target timestamp '%s'",
restoreTimestamp.c_str()));
co_return FDB_EXIT_ERROR;
}

if (!fileExists(originalClusterFile)) {
reportBackupQueryError(
operationId,
result,
format("The specified original source database cluster file '%s' does not exist\n",
originalClusterFile.c_str()));
co_return FDB_EXIT_ERROR;
}

Database origDb = Database::createDatabase(originalClusterFile, ApiVersion::LATEST_VERSION);
Version v = co_await timeKeeperVersionFromDatetime(restoreTimestamp, origDb);
result["restore_timestamp"] = restoreTimestamp;
result["restore_timestamp_resolved_version"] = v;
restoreVersion = v;
}

Reference<IBackupContainer> bc = openBackupContainer(name, destinationContainer, proxy, {}, 0);
BackupDescription desc = co_await bc->describeBackup();
// Use continuous log end version for the maximum restorable version for the key ranges when a restorable
Expand All @@ -2607,7 +2608,7 @@ Future<Void> queryBackup(const char* name,
result,
errorMessage =
format("the specified restorable version %lld is not valid", restoreVersion));
co_return;
co_return FDB_EXIT_ERROR;
}

Optional<RestorableFileSet> fileSet;
Expand Down Expand Up @@ -2655,7 +2656,7 @@ Future<Void> queryBackup(const char* name,
result,
format("no restorable files set found for specified key ranges from snapshotVersion %lld",
snapshotVersion));
co_return;
co_return FDB_EXIT_ERROR;
}

// We only need to know all the mutation logs from `snapshotVersion` to `restoreVersion`.
Expand Down Expand Up @@ -2698,12 +2699,12 @@ Future<Void> queryBackup(const char* name,
.detail("LogFilesBytes", totalLogFilesSize);
} else if (snapshotVersion == invalidVersion) {
reportBackupQueryError(operationId, result, "no restorable files set found for specified key ranges");
co_return;
co_return FDB_EXIT_ERROR;
}

} catch (Error& e) {
reportBackupQueryError(operationId, result, e.what());
co_return;
co_return FDB_EXIT_ERROR;
}

result["total_range_files_size"] = totalRangeFilesSize;
Expand All @@ -2715,6 +2716,7 @@ Future<Void> queryBackup(const char* name,
}

printf("%s\n", result.getJson().c_str());
co_return FDB_EXIT_SUCCESS;
}

Future<Void> listBackup(std::string baseUrl, Optional<std::string> proxy) {
Expand Down Expand Up @@ -4279,16 +4281,16 @@ int main(int argc, char* argv[]) {

case BackupType::QUERY:
initTraceFile();
f = stopAfter(queryBackup(newArgV[0],
destinationContainer,
proxy,
backupKeysFilter,
restoreVersion,
snapshotVersion,
clusterFile,
restoreTimestamp,
Verbose{ !quietDisplay },
db));
fstatus = stopAfter(queryBackup(newArgV[0],
destinationContainer,
proxy,
backupKeysFilter,
restoreVersion,
snapshotVersion,
clusterFile,
restoreTimestamp,
Verbose{ !quietDisplay },
db));
break;

case BackupType::DUMP:
Expand Down Expand Up @@ -4432,8 +4434,8 @@ int main(int argc, char* argv[]) {
status = FDB_EXIT_ERROR;
}

if (fstatus.isValid() && fstatus.isReady() && !fstatus.isError() && fstatus.get().present()) {
status = fstatus.get().get();
if (fstatus.isValid() && fstatus.isReady() && !fstatus.isError()) {
status = fstatus.get().orDefault(FDB_EXIT_ERROR);
}

#ifdef ALLOC_INSTRUMENTATION
Expand Down
44 changes: 44 additions & 0 deletions fdbbackup/tests/dir_backup_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,40 @@ function restore {
fi
}

# fdbbackup query reports failure as a JSON object on stdout, so only the exit status distinguishes it from success.
# $1 build directory
# $2 a url that does not hold a backup
# $3 the url of an existing backup; empty to skip the success check, as query has no way to be given an
# encryption key
function test_query_exit_codes {
local local_build_dir="${1}"
local bad_url="${2}"
local good_url="${3:-}"

local out
local rc
if [[ -n "${good_url}" ]]; then
set +e
out=$("${local_build_dir}"/bin/fdbbackup query -d "${good_url}" 2>/dev/null)
rc=$?
set -e
if (( rc != 0 )); then
err "fdbbackup query on an existing backup exited ${rc}; output: ${out}"
return 1
fi
fi

set +e
out=$("${local_build_dir}"/bin/fdbbackup query -d "${bad_url}" 2>/dev/null)
rc=$?
set -e
if (( rc == 0 )); then
err "fdbbackup query on a missing backup exited 0; output: ${out}"
return 1
fi
log "fdbbackup query exit codes OK (missing backup exit: ${rc})"
}

# Run a backup to the fs and then a restore.
# $1 build directory
# $2 the scratch directory
Expand Down Expand Up @@ -182,6 +216,16 @@ function test_dir_backup_and_restore {
backup_name=$(basename "${backup}")
test_encryption_mismatches "${local_build_dir}" "${scratch_dir}" "file://${scratch_dir}/backups/${backup_name}" "${TAG}" "${local_encryption_key_file}"

log "Testing fdbbackup query exit codes"
local query_url=
if [[ -z "${local_encryption_key_file}" ]]; then
query_url="file://${scratch_dir}/backups/${backup_name}"
fi
if ! test_query_exit_codes "${local_build_dir}" "file://${scratch_dir}/no_such_backup" "${query_url}"; then
err "Failed fdbbackup query exit code test"
return 1
fi

log "Restore"
if ! restore "${local_build_dir}" "${scratch_dir}" "${local_encryption_key_file}"; then
err "Failed restore"
Expand Down
Loading