From 7d9c4b1b265919b565d85e89edfc0497da9ffe8f Mon Sep 17 00:00:00 2001 From: Piotr Sarna Date: Fri, 6 Oct 2023 13:36:05 +0200 Subject: [PATCH 1/2] bottomless-cli: fix too eager create_dir_all() call The way create_dir_all() is called on a path to the database, it also creates the final file name as a directory. As a result, restoration later fails, because `data.tmp` cannot be moved to `data`, with `data` being already mistakenly created as a directory. --- bottomless-cli/src/main.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bottomless-cli/src/main.rs b/bottomless-cli/src/main.rs index e1b1805c..24073eca 100644 --- a/bottomless-cli/src/main.rs +++ b/bottomless-cli/src/main.rs @@ -125,7 +125,8 @@ async fn run() -> Result<()> { } } }; - let database = database + "/dbs/" + namespace.strip_prefix("ns-").unwrap() + "/data"; + let database_dir = database + "/dbs/" + namespace.strip_prefix("ns-").unwrap(); + let database = database_dir.clone() + "/data"; tracing::info!("Database: '{}' (namespace: {})", database, namespace); let mut client = Replicator::new(database.clone()).await?; @@ -149,7 +150,7 @@ async fn run() -> Result<()> { generation, utc_time, } => { - tokio::fs::create_dir_all(&database).await?; + tokio::fs::create_dir_all(&database_dir).await?; client.restore(generation, utc_time).await?; } Commands::Rm { From 63229ac5413a95d0d5c56c3343406aa16dee4fde Mon Sep 17 00:00:00 2001 From: Piotr Sarna Date: Fri, 6 Oct 2023 13:31:48 +0200 Subject: [PATCH 2/2] bottomless-cli: add `verify` command The command works similarly to `restore`, except it restores to a temporary directory and runs a `pragma integrity_check` query. On success it should return: ``` bottomless-cli -e http://localhost:9000 -n ns-:default -d e4e57664-01ad-76cc-9f19-a96700d5b2e4 verify Snapshot size: 13512704 Verification: ok ``` --- bottomless-cli/Cargo.toml | 1 + bottomless-cli/src/main.rs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/bottomless-cli/Cargo.toml b/bottomless-cli/Cargo.toml index 9a5f47ef..41a850d8 100644 --- a/bottomless-cli/Cargo.toml +++ b/bottomless-cli/Cargo.toml @@ -21,3 +21,4 @@ tokio = { version = "1.23.0", features = ["macros", "rt", "rt-multi-thread"] } tracing = "0.1.37" tracing-subscriber = "0.3.16" uuid = "1.4.1" +rusqlite = { workspace = true } diff --git a/bottomless-cli/src/main.rs b/bottomless-cli/src/main.rs index 24073eca..963c1da5 100644 --- a/bottomless-cli/src/main.rs +++ b/bottomless-cli/src/main.rs @@ -71,6 +71,22 @@ enum Commands { )] utc_time: Option, }, + #[clap(about = "Verify integrity of the database")] + Verify { + #[clap( + long, + short, + long_help = "Generation to verify.\nSkip this parameter to verify the newest generation." + )] + generation: Option, + #[clap( + long, + short, + conflicts_with = "generation", + long_help = "UTC timestamp which is an upper bound for the transactions to be verified." + )] + utc_time: Option, + }, #[clap(about = "Remove given generation from remote storage")] Rm { #[clap(long, short)] @@ -153,6 +169,26 @@ async fn run() -> Result<()> { tokio::fs::create_dir_all(&database_dir).await?; client.restore(generation, utc_time).await?; } + Commands::Verify { + generation, + utc_time, + } => { + let temp = std::env::temp_dir().join("bottomless-verification-do-not-touch"); + let mut client = Replicator::new(temp.display().to_string()).await?; + let _ = tokio::fs::remove_file(&temp).await; + client.restore(generation, utc_time).await?; + let size = tokio::fs::metadata(&temp).await?.len(); + println!("Snapshot size: {size}"); + let conn = rusqlite::Connection::open(&temp)?; + let mut stmt = conn.prepare("PRAGMA integrity_check")?; + let mut rows = stmt.query(())?; + let result: String = rows.next()?.unwrap().get(0)?; + println!("Verification: {result}"); + let _ = tokio::fs::remove_file(&temp).await; + if result != "ok" { + std::process::exit(1) + } + } Commands::Rm { generation, older_than,