LCOV - code coverage report
Current view: top level - storage_scrubber/src - main.rs (source / functions) Coverage Total Hit
Test: 465a86b0c1fda0069b3e0f6c1c126e6b635a1f72.info Lines: 0.0 % 150 0
Test Date: 2024-06-25 15:47:26 Functions: 0.0 % 53 0

            Line data    Source code
       1              : use anyhow::bail;
       2              : use camino::Utf8PathBuf;
       3              : use pageserver_api::shard::TenantShardId;
       4              : use storage_scrubber::garbage::{find_garbage, purge_garbage, PurgeMode};
       5              : use storage_scrubber::pageserver_physical_gc::GcMode;
       6              : use storage_scrubber::scan_pageserver_metadata::scan_metadata;
       7              : use storage_scrubber::tenant_snapshot::SnapshotDownloader;
       8              : use storage_scrubber::{
       9              :     init_logging, pageserver_physical_gc::pageserver_physical_gc,
      10              :     scan_safekeeper_metadata::scan_safekeeper_metadata, BucketConfig, ConsoleConfig, NodeKind,
      11              :     TraversingDepth,
      12              : };
      13              : 
      14              : use clap::{Parser, Subcommand};
      15              : use utils::id::TenantId;
      16              : 
      17            0 : #[derive(Parser)]
      18              : #[command(author, version, about, long_about = None)]
      19              : #[command(arg_required_else_help(true))]
      20              : struct Cli {
      21              :     #[command(subcommand)]
      22              :     command: Command,
      23              : 
      24            0 :     #[arg(short, long, default_value_t = false)]
      25            0 :     delete: bool,
      26              : }
      27              : 
      28            0 : #[derive(Subcommand, Debug)]
      29              : enum Command {
      30              :     FindGarbage {
      31              :         #[arg(short, long)]
      32            0 :         node_kind: NodeKind,
      33            0 :         #[arg(short, long, default_value_t=TraversingDepth::Tenant)]
      34            0 :         depth: TraversingDepth,
      35            0 :         #[arg(short, long, default_value_t = String::from("garbage.json"))]
      36            0 :         output_path: String,
      37              :     },
      38              :     PurgeGarbage {
      39              :         #[arg(short, long)]
      40            0 :         input_path: String,
      41            0 :         #[arg(short, long, default_value_t = PurgeMode::DeletedOnly)]
      42            0 :         mode: PurgeMode,
      43              :     },
      44              :     #[command(verbatim_doc_comment)]
      45              :     ScanMetadata {
      46              :         #[arg(short, long)]
      47            0 :         node_kind: NodeKind,
      48            0 :         #[arg(short, long, default_value_t = false)]
      49            0 :         json: bool,
      50              :         #[arg(long = "tenant-id", num_args = 0..)]
      51            0 :         tenant_ids: Vec<TenantShardId>,
      52              :         #[arg(long, default_value = None)]
      53              :         /// For safekeeper node_kind only, points to db with debug dump
      54              :         dump_db_connstr: Option<String>,
      55              :         /// For safekeeper node_kind only, table in the db with debug dump
      56              :         #[arg(long, default_value = None)]
      57              :         dump_db_table: Option<String>,
      58              :     },
      59              :     TenantSnapshot {
      60              :         #[arg(long = "tenant-id")]
      61            0 :         tenant_id: TenantId,
      62            0 :         #[arg(long = "concurrency", short = 'j', default_value_t = 8)]
      63            0 :         concurrency: usize,
      64              :         #[arg(short, long)]
      65            0 :         output_path: Utf8PathBuf,
      66              :     },
      67              :     PageserverPhysicalGc {
      68              :         #[arg(long = "tenant-id", num_args = 0..)]
      69            0 :         tenant_ids: Vec<TenantShardId>,
      70              :         #[arg(long = "min-age")]
      71            0 :         min_age: humantime::Duration,
      72            0 :         #[arg(short, long, default_value_t = GcMode::IndicesOnly)]
      73            0 :         mode: GcMode,
      74              :     },
      75              : }
      76              : 
      77              : #[tokio::main]
      78            0 : async fn main() -> anyhow::Result<()> {
      79            0 :     let cli = Cli::parse();
      80            0 : 
      81            0 :     let bucket_config = BucketConfig::from_env()?;
      82            0 : 
      83            0 :     let command_log_name = match &cli.command {
      84            0 :         Command::ScanMetadata { .. } => "scan",
      85            0 :         Command::FindGarbage { .. } => "find-garbage",
      86            0 :         Command::PurgeGarbage { .. } => "purge-garbage",
      87            0 :         Command::TenantSnapshot { .. } => "tenant-snapshot",
      88            0 :         Command::PageserverPhysicalGc { .. } => "pageserver-physical-gc",
      89            0 :     };
      90            0 :     let _guard = init_logging(&format!(
      91            0 :         "{}_{}_{}_{}.log",
      92            0 :         std::env::args().next().unwrap(),
      93            0 :         command_log_name,
      94            0 :         bucket_config.bucket,
      95            0 :         chrono::Utc::now().format("%Y_%m_%d__%H_%M_%S")
      96            0 :     ));
      97            0 : 
      98            0 :     match cli.command {
      99            0 :         Command::ScanMetadata {
     100            0 :             json,
     101            0 :             tenant_ids,
     102            0 :             node_kind,
     103            0 :             dump_db_connstr,
     104            0 :             dump_db_table,
     105            0 :         } => {
     106            0 :             if let NodeKind::Safekeeper = node_kind {
     107            0 :                 let dump_db_connstr =
     108            0 :                     dump_db_connstr.ok_or(anyhow::anyhow!("dump_db_connstr not specified"))?;
     109            0 :                 let dump_db_table =
     110            0 :                     dump_db_table.ok_or(anyhow::anyhow!("dump_db_table not specified"))?;
     111            0 : 
     112            0 :                 let summary = scan_safekeeper_metadata(
     113            0 :                     bucket_config.clone(),
     114            0 :                     tenant_ids.iter().map(|tshid| tshid.tenant_id).collect(),
     115            0 :                     dump_db_connstr,
     116            0 :                     dump_db_table,
     117            0 :                 )
     118            0 :                 .await?;
     119            0 :                 if json {
     120            0 :                     println!("{}", serde_json::to_string(&summary).unwrap())
     121            0 :                 } else {
     122            0 :                     println!("{}", summary.summary_string());
     123            0 :                 }
     124            0 :                 if summary.is_fatal() {
     125            0 :                     bail!("Fatal scrub errors detected");
     126            0 :                 }
     127            0 :                 if summary.is_empty() {
     128            0 :                     // Strictly speaking an empty bucket is a valid bucket, but if someone ran the
     129            0 :                     // scrubber they were likely expecting to scan something, and if we see no timelines
     130            0 :                     // at all then it's likely due to some configuration issues like a bad prefix
     131            0 :                     bail!(
     132            0 :                         "No timelines found in bucket {} prefix {}",
     133            0 :                         bucket_config.bucket,
     134            0 :                         bucket_config
     135            0 :                             .prefix_in_bucket
     136            0 :                             .unwrap_or("<none>".to_string())
     137            0 :                     );
     138            0 :                 }
     139            0 :                 Ok(())
     140            0 :             } else {
     141            0 :                 match scan_metadata(bucket_config.clone(), tenant_ids).await {
     142            0 :                     Err(e) => {
     143            0 :                         tracing::error!("Failed: {e}");
     144            0 :                         Err(e)
     145            0 :                     }
     146            0 :                     Ok(summary) => {
     147            0 :                         if json {
     148            0 :                             println!("{}", serde_json::to_string(&summary).unwrap())
     149            0 :                         } else {
     150            0 :                             println!("{}", summary.summary_string());
     151            0 :                         }
     152            0 :                         if summary.is_fatal() {
     153            0 :                             Err(anyhow::anyhow!("Fatal scrub errors detected"))
     154            0 :                         } else if summary.is_empty() {
     155            0 :                             // Strictly speaking an empty bucket is a valid bucket, but if someone ran the
     156            0 :                             // scrubber they were likely expecting to scan something, and if we see no timelines
     157            0 :                             // at all then it's likely due to some configuration issues like a bad prefix
     158            0 :                             Err(anyhow::anyhow!(
     159            0 :                                 "No timelines found in bucket {} prefix {}",
     160            0 :                                 bucket_config.bucket,
     161            0 :                                 bucket_config
     162            0 :                                     .prefix_in_bucket
     163            0 :                                     .unwrap_or("<none>".to_string())
     164            0 :                             ))
     165            0 :                         } else {
     166            0 :                             Ok(())
     167            0 :                         }
     168            0 :                     }
     169            0 :                 }
     170            0 :             }
     171            0 :         }
     172            0 :         Command::FindGarbage {
     173            0 :             node_kind,
     174            0 :             depth,
     175            0 :             output_path,
     176            0 :         } => {
     177            0 :             let console_config = ConsoleConfig::from_env()?;
     178            0 :             find_garbage(bucket_config, console_config, depth, node_kind, output_path).await
     179            0 :         }
     180            0 :         Command::PurgeGarbage { input_path, mode } => {
     181            0 :             purge_garbage(input_path, mode, !cli.delete).await
     182            0 :         }
     183            0 :         Command::TenantSnapshot {
     184            0 :             tenant_id,
     185            0 :             output_path,
     186            0 :             concurrency,
     187            0 :         } => {
     188            0 :             let downloader =
     189            0 :                 SnapshotDownloader::new(bucket_config, tenant_id, output_path, concurrency)?;
     190            0 :             downloader.download().await
     191            0 :         }
     192            0 :         Command::PageserverPhysicalGc {
     193            0 :             tenant_ids,
     194            0 :             min_age,
     195            0 :             mode,
     196            0 :         } => {
     197            0 :             let summary =
     198            0 :                 pageserver_physical_gc(bucket_config, tenant_ids, min_age.into(), mode).await?;
     199            0 :             println!("{}", serde_json::to_string(&summary).unwrap());
     200            0 :             Ok(())
     201            0 :         }
     202            0 :     }
     203            0 : }
        

Generated by: LCOV version 2.1-beta