From 7da01975f72c2b295e46b4a3c9c281b7d5d572fc Mon Sep 17 00:00:00 2001 From: Jan Eitzinger Date: Thu, 4 Dec 2025 15:08:03 +0100 Subject: [PATCH] archive-migration: Add check for archive version and rewrite version after migration --- tools/archive-migration/main.go | 43 ++++++++++++++++++++++++++- tools/archive-migration/transforms.go | 20 ++++++------- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/tools/archive-migration/main.go b/tools/archive-migration/main.go index edd48f4..9bbed12 100644 --- a/tools/archive-migration/main.go +++ b/tools/archive-migration/main.go @@ -5,9 +5,12 @@ package main import ( + "bufio" "flag" "fmt" "os" + "path/filepath" + "strings" cclog "github.com/ClusterCockpit/cc-lib/ccLogger" ) @@ -41,17 +44,33 @@ func main() { cclog.Fatalf("Archive path does not exist: %s", archivePath) } + // Check archive version + if err := checkVersion(archivePath); err != nil { + cclog.Fatalf("Version check failed: %v", err) + } + // Display warning for non-dry-run mode if !dryRun { cclog.Warn("WARNING: This will modify files in the archive!") cclog.Warn("It is strongly recommended to backup your archive first.") cclog.Warn("Run with --dry-run first to preview changes.") cclog.Info("") + + fmt.Print("Are you sure you want to continue? [y/N]: ") + reader := bufio.NewReader(os.Stdin) + input, err := reader.ReadString('\n') + if err != nil { + cclog.Fatalf("Error reading input: %v", err) + } + if strings.ToLower(strings.TrimSpace(input)) != "y" { + cclog.Info("Aborted by user.") + os.Exit(0) + } } // Run migration migrated, failed, err := migrateArchive(archivePath, dryRun, numWorkers) - + if err != nil { cclog.Errorf("Migration completed with errors: %s", err.Error()) if failed > 0 { @@ -62,6 +81,28 @@ func main() { if dryRun { cclog.Infof("Dry run completed: %d jobs would be migrated", migrated) } else { + if err := updateVersion(archivePath); err != nil { + cclog.Errorf("Failed to update archive version: %v", err) + os.Exit(1) + } cclog.Infof("Migration completed successfully: %d jobs migrated", migrated) } } + +func checkVersion(archivePath string) error { + versionFile := filepath.Join(archivePath, "version.txt") + data, err := os.ReadFile(versionFile) + if err != nil { + return fmt.Errorf("failed to read version.txt: %v", err) + } + versionStr := strings.TrimSpace(string(data)) + if versionStr != "2" { + return fmt.Errorf("archive version is %s, expected 2", versionStr) + } + return nil +} + +func updateVersion(archivePath string) error { + versionFile := filepath.Join(archivePath, "version.txt") + return os.WriteFile(versionFile, []byte("3\n"), 0644) +} diff --git a/tools/archive-migration/transforms.go b/tools/archive-migration/transforms.go index 5d8798f..6558e47 100644 --- a/tools/archive-migration/transforms.go +++ b/tools/archive-migration/transforms.go @@ -17,11 +17,11 @@ import ( // transformExclusiveToShared converts the old 'exclusive' field to the new 'shared' field // Mapping: 0 -> "multi_user", 1 -> "none", 2 -> "single_user" -func transformExclusiveToShared(jobData map[string]interface{}) error { +func transformExclusiveToShared(jobData map[string]any) error { // Check if 'exclusive' field exists if exclusive, ok := jobData["exclusive"]; ok { var exclusiveVal int - + // Handle both int and float64 (JSON unmarshaling can produce float64) switch v := exclusive.(type) { case float64: @@ -48,7 +48,7 @@ func transformExclusiveToShared(jobData map[string]interface{}) error { // Add shared field and remove exclusive jobData["shared"] = shared delete(jobData, "exclusive") - + cclog.Debugf("Transformed exclusive=%d to shared=%s", exclusiveVal, shared) } @@ -56,7 +56,7 @@ func transformExclusiveToShared(jobData map[string]interface{}) error { } // addMissingFields adds fields that are required in the current schema but might be missing in old archives -func addMissingFields(jobData map[string]interface{}) error { +func addMissingFields(jobData map[string]any) error { // Add submitTime if missing (default to startTime) if _, ok := jobData["submitTime"]; !ok { if startTime, ok := jobData["startTime"]; ok { @@ -85,7 +85,7 @@ func addMissingFields(jobData map[string]interface{}) error { } // removeDeprecatedFields removes fields that are no longer in the current schema -func removeDeprecatedFields(jobData map[string]interface{}) error { +func removeDeprecatedFields(jobData map[string]any) error { // List of deprecated fields to remove deprecatedFields := []string{ "mem_used_max", @@ -109,7 +109,7 @@ func removeDeprecatedFields(jobData map[string]interface{}) error { } // migrateJobMetadata applies all transformations to a job metadata map -func migrateJobMetadata(jobData map[string]interface{}) error { +func migrateJobMetadata(jobData map[string]any) error { // Apply transformations in order if err := transformExclusiveToShared(jobData); err != nil { return fmt.Errorf("transformExclusiveToShared failed: %w", err) @@ -135,7 +135,7 @@ func processJob(metaPath string, dryRun bool) error { } // Parse JSON - var jobData map[string]interface{} + var jobData map[string]any if err := json.Unmarshal(data, &jobData); err != nil { return fmt.Errorf("failed to parse JSON from %s: %w", metaPath, err) } @@ -157,7 +157,7 @@ func processJob(metaPath string, dryRun bool) error { return fmt.Errorf("failed to marshal migrated data: %w", err) } - if err := os.WriteFile(metaPath, migratedData, 0644); err != nil { + if err := os.WriteFile(metaPath, migratedData, 0o644); err != nil { return fmt.Errorf("failed to write %s: %w", metaPath, err) } @@ -175,11 +175,11 @@ func migrateArchive(archivePath string, dryRun bool, numWorkers int) (int, int, var failed int32 // Channel for job paths - jobs :=make(chan string, numWorkers*2) + jobs := make(chan string, numWorkers*2) var wg sync.WaitGroup // Start worker goroutines - for i := 0; i < numWorkers; i++ { + for i := range numWorkers { wg.Add(1) go func(workerID int) { defer wg.Done()