Skip to content

Commit 640af3d

Browse files
committed
feat: Add opt in automatic database migration
* Rename --upgrade to --db-upgrade (keeping --upgrade as deprecated alias) * Make it an enum, allowing true, false, auto; defaulting to true * Add it to the config file (disallowing "true", which would prevent the management from starting)
1 parent bb9ada5 commit 640af3d

File tree

5 files changed

+68
-23
lines changed

5 files changed

+68
-23
lines changed

mgmtd/assets/beegfs-mgmtd.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88
# binary with `--help` to display it.
99

1010

11+
# Decides how to upgrade the managements database schema, when required.
12+
# Can be set to "auto" to perform an auto upgrade on startup without having to manually run with
13+
# the --db-upgrade flag. Automatically creates a backup of the existing database file in the same
14+
# directory.
15+
# db-upgrade = "false"
16+
1117
# Managements database file location.
1218
# db-file = "/var/lib/beegfs/mgmtd.sqlite"
1319

mgmtd/src/config.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,15 @@ generate_structs! {
119119
#[serde(skip)]
120120
fs_uuid: Option<Uuid> = None,
121121

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

130132
/// Imports a BeeGFS v7 installation from the provided directory into a new database.
131133
///
@@ -584,3 +586,14 @@ impl From<LogLevel> for LevelFilter {
584586
}
585587
}
586588
}
589+
590+
/// DB upgrade mode
591+
#[derive(Clone, Debug, PartialEq, Eq, ValueEnum, Deserialize)]
592+
#[serde(rename_all = "kebab-case")]
593+
pub enum DbUpgrade {
594+
// We never want the True setting in the config file, so we skip this one
595+
#[serde(skip)]
596+
True,
597+
False,
598+
Auto,
599+
}

mgmtd/src/lib.rs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ mod types;
1414

1515
use crate::app::RuntimeApp;
1616
use crate::config::Config;
17-
use anyhow::Result;
17+
use anyhow::{Context, Result};
1818
use app::App;
19+
use config::DbUpgrade;
1920
use db::node_nic::ReplaceNic;
2021
use license::LicenseVerifier;
2122
use shared::bee_msg::target::RefreshTargetStates;
@@ -82,8 +83,19 @@ pub async fn start(info: StaticInfo, license: LicenseVerifier) -> Result<RunCont
8283
info.use_ipv6,
8384
);
8485

85-
let mut db = sqlite::Connections::new(info.user_config.db_file.as_path());
86-
sqlite::check_schema_async(&mut db, db::MIGRATIONS).await?;
86+
let db = sqlite::Connections::new(info.user_config.db_file.as_path());
87+
88+
let schema_check = db
89+
.read_tx(|tx| Ok(sqlite::check_schema(tx, db::MIGRATIONS)))
90+
.await?;
91+
92+
if info.user_config.db_upgrade == DbUpgrade::Auto {
93+
if schema_check.is_err() {
94+
upgrade_db(&db).await?;
95+
}
96+
} else {
97+
schema_check?;
98+
}
8799

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

167+
// Db schema auto upgrade. This requires slightly different handling and logging than the version
168+
// in main.rs.
169+
async fn upgrade_db(db: &sqlite::Connections) -> Result<()> {
170+
db.conn(|conn| {
171+
let backup_file = sqlite::backup_db(conn)?;
172+
log::warn!("Old database backed up to {backup_file:?}");
173+
Ok(())
174+
})
175+
.await?;
176+
177+
let version = db
178+
.write_tx(|tx| {
179+
sqlite::migrate_schema(tx, db::MIGRATIONS)
180+
.with_context(|| "Upgrading database schema failed")
181+
})
182+
.await?;
183+
184+
log::warn!("Database upgraded to version {version}");
185+
Ok(())
186+
}
187+
155188
/// Controls the running application.
156189
#[derive(Debug)]
157190
pub struct RunControl {

mgmtd/src/main.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use anyhow::{Context, Result, anyhow, bail};
22
use log::LevelFilter;
3-
use mgmtd::config::LogTarget;
3+
use mgmtd::config::{DbUpgrade, LogTarget};
44
use mgmtd::db::{self};
55
use mgmtd::license::LicenseVerifier;
66
use mgmtd::{StaticInfo, start};
@@ -72,7 +72,7 @@ fn inner_main() -> Result<()> {
7272
return Ok(());
7373
}
7474

75-
if user_config.upgrade {
75+
if user_config.db_upgrade == DbUpgrade::True {
7676
upgrade_db(&user_config.db_file)?;
7777
return Ok(());
7878
}
@@ -217,15 +217,16 @@ beegfs-mgmtd.conf. Before starting the management, you must MANUALLY transfer yo
217217
fn upgrade_db(db_file: &Path) -> Result<()> {
218218
let mut conn = sqlite::open(db_file)?;
219219

220-
let backup_file = sqlite::backup_db(&mut conn)?;
220+
let tx = conn.transaction()?;
221+
222+
let backup_file = sqlite::backup_db(&tx)?;
221223
println!("Old database backed up to {backup_file:?}");
222224

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

228-
println!("Upgraded database to version {version}");
229+
println!("Database upgraded to version {version}");
229230
Ok(())
230231
}
231232

sqlite/src/migration.rs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use crate::Connections;
21
use anyhow::{Context, Result, anyhow, bail};
32
use std::fmt::Write;
43
use std::path::{Path, PathBuf};
@@ -125,18 +124,11 @@ pub fn flatten_migrations(migrations: &[OwnedMigration]) -> Result<String> {
125124
}
126125

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

134-
let version: u32 = conn
135-
.read_tx(|tx| {
136-
// The databases version is stored in this special sqlite header variable
137-
Ok(tx.query_row("PRAGMA user_version", [], |row| row.get(0))?)
138-
})
139-
.await?;
130+
// The databases version is stored in this special sqlite header variable
131+
let version: u32 = tx.query_row("PRAGMA user_version", [], |row| row.get(0))?;
140132

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

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

195187
let Some(db_file) = conn.path() else {

0 commit comments

Comments
 (0)