[pve-devel] [PATCH rrd-migration-tool] migrate storage: properly handle storage IDs with a dot
Fiona Ebner
f.ebner at proxmox.com
Fri Aug 1 18:15:58 CEST 2025
Storage IDs in Proxmox VE may contain dots, which makes a simple
rename adding a '.old' extension impossible without potential
breakage. The storage RRD is grouped by nodes, so to fix it, create
a '.old' directory for each node and move migrated RRD files there.
If a previous migration with the logic before this change is detected,
the old logic will be kept to avoid picking up '.old' RRD files
created by that previous migration.
Signed-off-by: Fiona Ebner <f.ebner at proxmox.com>
---
src/main.rs | 47 ++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 40 insertions(+), 7 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index fb58d3a..03a84b8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -317,7 +317,10 @@ fn mv_old(file: &str) -> Result<()> {
}
/// Colllect all RRD files in the provided directory
-fn collect_rrd_files(location: &PathBuf) -> Result<Vec<(CString, OsString)>> {
+fn collect_rrd_files(
+ location: &PathBuf,
+ include_files_with_extension: bool,
+) -> Result<Vec<(CString, OsString)>> {
let mut files: Vec<(CString, OsString)> = Vec::new();
let contents = match fs::read_dir(location) {
@@ -331,7 +334,7 @@ fn collect_rrd_files(location: &PathBuf) -> Result<Vec<(CString, OsString)>> {
contents
.filter(|f| f.is_ok())
.map(|f| f.unwrap().path())
- .filter(|f| f.is_file() && f.extension().is_none())
+ .filter(|f| f.is_file() && (include_files_with_extension || f.extension().is_none()))
.for_each(|file| {
let path = CString::new(file.as_path().as_os_str().as_bytes())
.expect("Could not convert path to CString.");
@@ -416,7 +419,7 @@ fn migrate_guests(
println!("Migrating RRD metrics data for virtual guests…");
println!("Using {threads} thread(s)");
- let guest_source_files = collect_rrd_files(&source_dir_guests)?;
+ let guest_source_files = collect_rrd_files(&source_dir_guests, false)?;
if guest_source_files.is_empty() {
println!("No guest metrics to migrate");
@@ -518,7 +521,7 @@ fn migrate_nodes(
std::fs::create_dir(&target_dir_nodes)?;
}
- let node_source_files = collect_rrd_files(&source_dir_nodes)?;
+ let node_source_files = collect_rrd_files(&source_dir_nodes, false)?;
let mut no_migration_err = true;
for file in node_source_files {
@@ -579,17 +582,25 @@ fn migrate_storage(
let mut no_migration_err = true;
// storage has another layer of directories per node over which we need to iterate
+ // Storage IDs may contain dots, so the old RRD files are moved to a .old directory per node
+ // rather than renamed themselves.
fs::read_dir(&source_dir_storage)?
.filter(|f| f.is_ok())
.map(|f| f.unwrap().path())
- .filter(|f| f.is_dir())
+ .filter(|f| f.is_dir() && f.extension().is_none())
.try_for_each(|node| {
let mut source_storage_subdir = source_dir_storage.clone();
source_storage_subdir.push(node.file_name().unwrap());
+ let source_storage_subdir_old = source_storage_subdir.as_path().with_extension("old");
+
let mut target_storage_subdir = target_dir_storage.clone();
target_storage_subdir.push(node.file_name().unwrap());
+ // If already migrated using the old rename logic, don't try to migrate with new logic.
+ let migrated_using_old_rename =
+ target_storage_subdir.exists() && !source_storage_subdir_old.exists();
+
if !target_storage_subdir.exists() && migrate {
fs::create_dir(target_storage_subdir.as_path())?;
let metadata = target_storage_subdir.metadata()?;
@@ -598,7 +609,18 @@ fn migrate_storage(
fs::set_permissions(&target_storage_subdir, permissions)?;
}
- let storage_source_files = collect_rrd_files(&source_storage_subdir)?;
+ if !source_storage_subdir_old.exists() && migrate && !migrated_using_old_rename {
+ fs::create_dir(source_storage_subdir_old.as_path())?;
+ let metadata = source_storage_subdir_old.metadata()?;
+ let mut permissions = metadata.permissions();
+ permissions.set_mode(0o755);
+ fs::set_permissions(&source_storage_subdir_old, permissions)?;
+ }
+
+ // Storage IDs may contain dots, so need to consider all extensions.
+ // If old logic was used already before, don't use new logic.
+ let storage_source_files =
+ collect_rrd_files(&source_storage_subdir, !migrated_using_old_rename)?;
for file in storage_source_files {
println!(
"Migrating metrics for storage '{}/{}'",
@@ -609,6 +631,7 @@ fn migrate_storage(
);
let full_path = file.0.clone().into_string().unwrap();
+ let target_path = source_storage_subdir_old.join(file.1.clone());
match do_rrd_migration(
file,
&target_storage_subdir,
@@ -617,7 +640,12 @@ fn migrate_storage(
force,
) {
Ok(()) => {
- mv_old(full_path.as_str())?;
+ // Keep using old logic if already migrated with old logic.
+ if migrated_using_old_rename {
+ mv_old(full_path.as_str())?;
+ } else {
+ fs::rename(full_path, target_path)?;
+ }
}
Err(err) => {
eprintln!("{err}"); // includes information messages, so just print.
@@ -625,6 +653,11 @@ fn migrate_storage(
}
}
}
+
+ if source_storage_subdir.read_dir()?.next().is_none() {
+ fs::remove_dir(source_storage_subdir)?;
+ }
+
Ok::<(), Error>(())
})?;
--
2.47.2
More information about the pve-devel
mailing list