Skip to content
Open
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
6 changes: 6 additions & 0 deletions mgmtd/assets/beegfs-mgmtd.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
# binary with `--help` to display it.


# Decides how to upgrade the managements database schema, when required.
# Can be set to "auto" to perform an auto upgrade on startup without having to manually run with
# the --db-upgrade flag. Automatically creates a backup of the existing database file in the same
# directory.
# db-upgrade = "false"
Comment on lines +11 to +15
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion(blocking): after thinking it over I think it would be more user friendly to just introduce a new boolean db-auto-upgrade parameter. That way we can just keep the --upgrade flag as it is today which avoids needing to update docs, training, etc.


# Managements database file location.
# db-file = "/var/lib/beegfs/mgmtd.sqlite"

Expand Down
19 changes: 16 additions & 3 deletions mgmtd/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,15 @@ generate_structs! {
#[serde(skip)]
fs_uuid: Option<Uuid> = None,

/// Upgrades an outdated management database to the current version, then exits.
/// Upgrades an outdated management database schema to the current version, then exits.
///
/// Can be set to "auto" to perform an auto upgrade on startup without having to manually run
/// with the --db-upgrade flag.
/// Automatically creates a backup of the existing database file in the same directory.
#[arg(long)]
#[arg(num_args = 0..=1, default_missing_value = "true")]
#[serde(skip)]
upgrade: bool = false,
#[arg(alias("upgrade"))]
db_upgrade: DbUpgrade = DbUpgrade::False,

/// Imports a BeeGFS v7 installation from the provided directory into a new database.
///
Expand Down Expand Up @@ -584,3 +586,14 @@ impl From<LogLevel> for LevelFilter {
}
}
}

/// DB upgrade mode
#[derive(Clone, Debug, PartialEq, Eq, ValueEnum, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum DbUpgrade {
// We never want the True setting in the config file, so we skip this one
#[serde(skip)]
True,
False,
Auto,
}
39 changes: 36 additions & 3 deletions mgmtd/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ mod types;

use crate::app::RuntimeApp;
use crate::config::Config;
use anyhow::Result;
use anyhow::{Context, Result};
use app::App;
use config::DbUpgrade;
use db::node_nic::ReplaceNic;
use license::LicenseVerifier;
use shared::bee_msg::target::RefreshTargetStates;
Expand Down Expand Up @@ -82,8 +83,19 @@ pub async fn start(info: StaticInfo, license: LicenseVerifier) -> Result<RunCont
info.use_ipv6,
);

let mut db = sqlite::Connections::new(info.user_config.db_file.as_path());
sqlite::check_schema_async(&mut db, db::MIGRATIONS).await?;
let db = sqlite::Connections::new(info.user_config.db_file.as_path());

let schema_check = db
.read_tx(|tx| Ok(sqlite::check_schema(tx, db::MIGRATIONS)))
.await?;

if info.user_config.db_upgrade == DbUpgrade::Auto {
if schema_check.is_err() {
upgrade_db(&db).await?;
}
Comment on lines +93 to +95
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion(non-blocking): This would try to upgrade the DB on any error from the schema_check() including the outside the valid range error where this binary shouldn't try to migrate the on-disk DB.

The migration would never actually be attempted due to the second check in migrate_schema(), but it would try to take a backup then later fail. It would be better just to fail fast.

} else {
schema_check?;
}

log::info!(
"Opened database at {:?}",
Expand Down Expand Up @@ -152,6 +164,27 @@ pub async fn start(info: StaticInfo, license: LicenseVerifier) -> Result<RunCont
})
}

// Db schema auto upgrade. This requires slightly different handling and logging than the version
// in main.rs.
async fn upgrade_db(db: &sqlite::Connections) -> Result<()> {
db.conn(|conn| {
let backup_file = sqlite::backup_db(conn)?;
log::warn!("Old database backed up to {backup_file:?}");
Ok(())
})
.await?;

let version = db
.write_tx(|tx| {
sqlite::migrate_schema(tx, db::MIGRATIONS)
.with_context(|| "Upgrading database schema failed")
})
.await?;

log::warn!("Database upgraded to version {version}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion(non-blocking):

Suggested change
log::warn!("Database upgraded to version {version}");
log::warn!("Database automatically upgraded to version {version}");

Ok(())
}

/// Controls the running application.
#[derive(Debug)]
pub struct RunControl {
Expand Down
12 changes: 7 additions & 5 deletions mgmtd/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::{Context, Result, anyhow, bail};
use log::LevelFilter;
use mgmtd::config::LogTarget;
use mgmtd::config::{DbUpgrade, LogTarget};
use mgmtd::db::{self};
use mgmtd::license::LicenseVerifier;
use mgmtd::{StaticInfo, start};
Expand All @@ -16,6 +16,7 @@ use uuid::Uuid;

fn main() -> Result<(), i32> {
inner_main().map_err(|err| {
log::error!("{err:#}");
eprintln!("{err:#}");
1
})?;
Expand Down Expand Up @@ -72,7 +73,7 @@ fn inner_main() -> Result<()> {
return Ok(());
}

if user_config.upgrade {
if user_config.db_upgrade == DbUpgrade::True {
upgrade_db(&user_config.db_file)?;
return Ok(());
}
Expand Down Expand Up @@ -217,15 +218,16 @@ beegfs-mgmtd.conf. Before starting the management, you must MANUALLY transfer yo
fn upgrade_db(db_file: &Path) -> Result<()> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion(non-blocking): it would be worth mentioning this only is used for the manual upgrade path and for automatic upgrades see the version in lib.rs.

let mut conn = sqlite::open(db_file)?;

let backup_file = sqlite::backup_db(&mut conn)?;
let tx = conn.transaction()?;

let backup_file = sqlite::backup_db(&tx)?;
println!("Old database backed up to {backup_file:?}");

let tx = conn.transaction()?;
let version = sqlite::migrate_schema(&tx, db::MIGRATIONS)
.with_context(|| "Upgrading database schema failed")?;
tx.commit()?;

println!("Upgraded database to version {version}");
println!("Database upgraded to version {version}");
Ok(())
}

Expand Down
16 changes: 4 additions & 12 deletions sqlite/src/migration.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::Connections;
use anyhow::{Context, Result, anyhow, bail};
use std::fmt::Write;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -125,18 +124,11 @@ pub fn flatten_migrations(migrations: &[OwnedMigration]) -> Result<String> {
}

/// Checks that the database schema is up to date to the current latest migrations
pub async fn check_schema_async(
conn: &mut Connections,
migrations: &'static [Migration],
) -> Result<()> {
pub fn check_schema(tx: &rusqlite::Transaction, migrations: &'static [Migration]) -> Result<()> {
let (base, latest) = check_migration_versions(migrations.iter().map(|m| m.version))?;

let version: u32 = conn
.read_tx(|tx| {
// The databases version is stored in this special sqlite header variable
Ok(tx.query_row("PRAGMA user_version", [], |row| row.get(0))?)
})
.await?;
// The databases version is stored in this special sqlite header variable
let version: u32 = tx.query_row("PRAGMA user_version", [], |row| row.get(0))?;

if version == latest {
Ok(())
Expand Down Expand Up @@ -189,7 +181,7 @@ pub fn migrate_schema(tx: &rusqlite::Transaction, migrations: &[Migration]) -> R
}

/// Safely backs up the database
pub fn backup_db(conn: &mut rusqlite::Connection) -> Result<PathBuf> {
pub fn backup_db(conn: &rusqlite::Connection) -> Result<PathBuf> {
let version: u32 = conn.query_row("PRAGMA user_version", [], |row| row.get(0))?;

let Some(db_file) = conn.path() else {
Expand Down
Loading