diff --git a/checkpointctl.go b/checkpointctl.go index f92d6cb7..0b062367 100644 --- a/checkpointctl.go +++ b/checkpointctl.go @@ -14,6 +14,8 @@ var ( name string version string printStats bool + showMounts bool + fullPaths bool ) func main() { @@ -48,11 +50,27 @@ func setupShow() *cobra.Command { false, "Print checkpointing statistics if available", ) + flags.BoolVar( + &showMounts, + "mounts", + false, + "Print overview about mounts used in the checkpoints", + ) + flags.BoolVar( + &fullPaths, + "full-paths", + false, + "Display mounts with full paths", + ) return cmd } func show(cmd *cobra.Command, args []string) error { + if fullPaths && !showMounts { + return fmt.Errorf("Cannot use --full-paths without --mounts option") + } + input := args[0] tar, err := os.Stat(input) if err != nil { @@ -74,6 +92,5 @@ func show(cmd *cobra.Command, args []string) error { if err := archive.UntarPath(input, dir); err != nil { return fmt.Errorf("unpacking of checkpoint archive %s failed: %w", input, err) } - return showContainerCheckpoint(dir) } diff --git a/container.go b/container.go index a11be114..0143c285 100644 --- a/container.go +++ b/container.go @@ -9,6 +9,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "time" metadata "github.com/checkpoint-restore/checkpointctl/lib" @@ -147,41 +148,63 @@ func showContainerCheckpoint(checkpointDirectory string) error { table.Append(row) table.Render() - if !printStats { - return nil + if showMounts { + table = tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{ + "Destination", + "Type", + "Source", + }) + // Get overview of mounts from spec.dump + for _, data := range specDump.Mounts { + table.Append([]string{ + data.Destination, + data.Type, + func() string { + if fullPaths { + return data.Source + } + return shortenPath(data.Source) + }(), + }) + } + fmt.Println("\nOverview of Mounts") + table.Render() } - cpDir, err := os.Open(checkpointDirectory) - if err != nil { - return err - } - defer cpDir.Close() + if printStats { + cpDir, err := os.Open(checkpointDirectory) + if err != nil { + return err + } + defer cpDir.Close() - // Get dump statistics with crit - dumpStatistics, err := crit.GetDumpStats(cpDir.Name()) - if err != nil { - return fmt.Errorf("unable to display checkpointing statistics: %w", err) - } + // Get dump statistics with crit + dumpStatistics, err := crit.GetDumpStats(cpDir.Name()) + if err != nil { + return fmt.Errorf("unable to display checkpointing statistics: %w", err) + } - table = tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{ - "Freezing Time", - "Frozen Time", - "Memdump Time", - "Memwrite Time", - "Pages Scanned", - "Pages Written", - }) - table.Append([]string{ - fmt.Sprintf("%d us", dumpStatistics.GetFreezingTime()), - fmt.Sprintf("%d us", dumpStatistics.GetFrozenTime()), - fmt.Sprintf("%d us", dumpStatistics.GetMemdumpTime()), - fmt.Sprintf("%d us", dumpStatistics.GetMemwriteTime()), - fmt.Sprintf("%d", dumpStatistics.GetPagesScanned()), - fmt.Sprintf("%d", dumpStatistics.GetPagesWritten()), - }) - fmt.Println("CRIU dump statistics") - table.Render() + table = tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{ + "Freezing Time", + "Frozen Time", + "Memdump Time", + "Memwrite Time", + "Pages Scanned", + "Pages Written", + }) + table.Append([]string{ + fmt.Sprintf("%d us", dumpStatistics.GetFreezingTime()), + fmt.Sprintf("%d us", dumpStatistics.GetFrozenTime()), + fmt.Sprintf("%d us", dumpStatistics.GetMemdumpTime()), + fmt.Sprintf("%d us", dumpStatistics.GetMemwriteTime()), + fmt.Sprintf("%d", dumpStatistics.GetPagesScanned()), + fmt.Sprintf("%d", dumpStatistics.GetPagesWritten()), + }) + fmt.Println("\nCRIU dump statistics") + table.Render() + } return nil } @@ -206,3 +229,11 @@ func getCheckpointSize(path string) (size int64, err error) { return dirSize(dir) } + +func shortenPath(path string) string { + parts := strings.Split(path, string(filepath.Separator)) + if len(parts) <= 2 { + return path + } + return filepath.Join("..", filepath.Join(parts[len(parts)-2:]...)) +} diff --git a/test/checkpointctl.bats b/test/checkpointctl.bats index 63baa17c..5fcaedbe 100644 --- a/test/checkpointctl.bats +++ b/test/checkpointctl.bats @@ -136,6 +136,41 @@ function teardown() { [[ ${lines[10]} == *"446571 us"* ]] } +@test "Run checkpointctl show with tar file and --mounts and valid spec.dump" { + cp test/config.dump "$TEST_TMP_DIR1" + cp test/spec.dump "$TEST_TMP_DIR1" + mkdir "$TEST_TMP_DIR1"/checkpoint + ( cd "$TEST_TMP_DIR1" && tar cf "$TEST_TMP_DIR2"/test.tar . ) + checkpointctl show "$TEST_TMP_DIR2"/test.tar --mounts + [ "$status" -eq 0 ] + [[ ${lines[6]} == *"Overview of Mounts"* ]] + [[ ${lines[8]} == *"DESTINATION"* ]] + [[ ${lines[10]} == *"/proc"* ]] +} + +@test "Run checkpointctl show with tar file and --mounts and --full-paths and valid spec.dump" { + cp test/config.dump "$TEST_TMP_DIR1" + cp test/spec.dump "$TEST_TMP_DIR1" + mkdir "$TEST_TMP_DIR1"/checkpoint + ( cd "$TEST_TMP_DIR1" && tar cf "$TEST_TMP_DIR2"/test.tar . ) + checkpointctl show "$TEST_TMP_DIR2"/test.tar --mounts --full-paths + [ "$status" -eq 0 ] + [[ ${lines[6]} == *"Overview of Mounts"* ]] + [[ ${lines[8]} == *"DESTINATION"* ]] + [[ ${lines[10]} == *"/proc"* ]] +} + + +@test "Run checkpointctl show with tar file and missing --mounts and --full-paths" { + cp test/config.dump "$TEST_TMP_DIR1" + cp test/spec.dump "$TEST_TMP_DIR1" + mkdir "$TEST_TMP_DIR1"/checkpoint + ( cd "$TEST_TMP_DIR1" && tar cf "$TEST_TMP_DIR2"/test.tar . ) + checkpointctl show "$TEST_TMP_DIR2"/test.tar --full-paths + [ "$status" -eq 1 ] + [[ ${lines[0]} == *"Error: Cannot use --full-paths without --mounts option"* ]] +} + @test "Run checkpointctl show with tar file with valid config.dump and valid spec.dump (CRI-O) and no checkpoint directory" { cp test/config.dump "$TEST_TMP_DIR1" cp test/spec.dump.cri-o "$TEST_TMP_DIR1"/spec.dump diff --git a/test/spec.dump b/test/spec.dump index 237795b8..d7c94dfc 100644 --- a/test/spec.dump +++ b/test/spec.dump @@ -1,3 +1,17 @@ { -"annotations": {"io.container.manager": "libpod"} + "mounts": [ + { + "destination": "/proc", + "type": "proc", + "source": "proc" + }, + { + "destination": "/etc/hostname", + "type": "bind", + "source": "/run/containers/storage/overlay-containers/d5eee7931a29b2d6bf51469e3ab7284bb22a9e6dad073277e30e2a29256efc84/userdata/hostname" + } + ], + "annotations": { + "io.container.manager": "libpod" + } }