add database backup with admin commands

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk 2024-03-19 04:37:35 -07:00 committed by June
parent ece817c562
commit fa942aedd7
8 changed files with 144 additions and 2 deletions

View file

@ -1,4 +1,4 @@
use std::{future::Future, pin::Pin, sync::Arc};
use std::{error::Error, future::Future, pin::Pin, sync::Arc};
use super::Config;
use crate::Result;
@ -26,6 +26,14 @@ pub(crate) trait KeyValueDatabaseEngine: Send + Sync {
#[allow(dead_code)]
fn clear_caches(&self) {}
fn backup(&self) -> Result<(), Box<dyn Error>> {
unimplemented!()
}
fn backup_list(&self) -> Result<String> {
Ok(String::new())
}
}
pub(crate) trait KvTree: Send + Sync {

View file

@ -3,12 +3,17 @@ use std::{
pin::Pin,
sync::{Arc, RwLock},
};
use chrono::{
DateTime,
Utc,
};
use rust_rocksdb::{
backup::{BackupEngine, BackupEngineOptions},
LogLevel::{Debug, Error, Fatal, Info, Warn},
WriteBatchWithTransaction,
};
use tracing::{debug, info};
use tracing::{debug, error, info};
use super::{super::Config, watchers::Watchers, KeyValueDatabaseEngine, KvTree};
use crate::{utils, Result};
@ -221,6 +226,68 @@ impl KeyValueDatabaseEngine for Arc<Engine> {
Ok(())
}
fn backup(&self) -> Result<(), Box<dyn std::error::Error>> {
let path = self.config.database_backup_path.as_ref();
if path.is_none() || path.is_some_and(String::is_empty) {
return Ok(());
}
let options = BackupEngineOptions::new(&path.unwrap())?;
let mut engine = BackupEngine::open(&options, &self.env)?;
let ret = if self.config.database_backups_to_keep > 0 {
match engine.create_new_backup_flush(&self.rocks, true) {
Err(e) => return Err(Box::new(e)),
Ok(_) => {
let _info = engine.get_backup_info();
let info = &_info.last().unwrap();
info!(
"Created database backup #{} using {} bytes in {} files",
info.backup_id,
info.size,
info.num_files,
);
Ok(())
},
}
} else {
Ok(())
};
if self.config.database_backups_to_keep >= 0 {
let keep = u32::try_from(self.config.database_backups_to_keep)?;
if let Err(e) = engine.purge_old_backups(keep.try_into()?) {
error!("Failed to purge old backup: {:?}", e.to_string())
}
}
ret
}
fn backup_list(&self) -> Result<String> {
let path = self.config.database_backup_path.as_ref();
if path.is_none() || path.is_some_and(String::is_empty) {
return Ok("Configure database_backup_path to enable backups".to_owned());
}
let mut res = String::new();
let options = BackupEngineOptions::new(&path.unwrap())?;
let engine = BackupEngine::open(&options, &self.env)?;
for info in engine.get_backup_info() {
std::fmt::write(&mut res, format_args!(
"#{} {}: {} bytes, {} files\n",
info.backup_id,
DateTime::<Utc>::from_timestamp(info.timestamp, 0)
.unwrap()
.to_rfc2822(),
info.size,
info.num_files,
))
.unwrap();
}
Ok(res)
}
// TODO: figure out if this is needed for rocksdb
#[allow(dead_code)]
fn clear_caches(&self) {}

View file

@ -272,4 +272,12 @@ lasttimelinecount_cache: {lasttimelinecount_cache}\n"
self.global.insert(b"version", &new_version.to_be_bytes())?;
Ok(())
}
fn backup(&self) -> Result<(), Box<dyn std::error::Error>> {
self.db.backup()
}
fn backup_list(&self) -> Result<String> {
self.db.backup_list()
}
}