Skip to content

Commit

Permalink
refactor: Extract necessary values from spec.dump
Browse files Browse the repository at this point in the history
- If applied, this commit will refactor the code to extract essential
values, including container engine, from the spec.dump file. This change
replaces the previous approach of retrieving these values from the config.dump file.
The spec.dump file stores these values in key-value pairs, providing a more
structured and consistent data source.

Signed-off-by: Parthiba-Hazra <[email protected]>
  • Loading branch information
Parthiba-Hazra committed Feb 27, 2024
1 parent 23e2986 commit 49c2417
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 96 deletions.
14 changes: 8 additions & 6 deletions cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ func list(cmd *cobra.Command, args []string) error {
"Namespace",
"Pod",
"Container",
"Time Created",
"Engine",
"Time Checkpointed",
"Checkpoint Name",
}

Expand All @@ -71,17 +72,18 @@ func list(cmd *cobra.Command, args []string) error {
fmt.Printf("Listing checkpoints in path: %s\n", checkpointPath)

for _, file := range files {
namespace, pod, container, timestamp, err := internal.ExtractConfigDump(file)
chkptConfig, err := internal.ExtractConfigDump(file)
if err != nil {
log.Printf("Error extracting information from %s: %v\n", file, err)
continue
}

row := []string{
namespace,
pod,
container,
timestamp.Format(time.RFC822),
chkptConfig.Namespace,
chkptConfig.Pod,
chkptConfig.Container,
chkptConfig.ContainerManager,
chkptConfig.Timestamp.Format(time.RFC822),
filepath.Base(file),
}

Expand Down
65 changes: 43 additions & 22 deletions internal/config_extractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,75 @@ package internal

import (
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"

metadata "github.com/checkpoint-restore/checkpointctl/lib"
)

func ExtractConfigDump(checkpointPath string) (namespace, pod, container string, timestamp time.Time, err error) {
type ChkptConfig struct {
Namespace string
Pod string
Container string
ContainerManager string
Timestamp time.Time
}

func ExtractConfigDump(checkpointPath string) (*ChkptConfig, error) {
tempDir, err := os.MkdirTemp("", "extracted-checkpoint")
if err != nil {
return "", "", "", time.Time{}, err
return nil, err
}
defer os.RemoveAll(tempDir)

filesToExtract := []string{"config.dump"}
filesToExtract := []string{"spec.dump", "config.dump"}
if err := UntarFiles(checkpointPath, tempDir, filesToExtract); err != nil {
log.Printf("Error extracting files from archive %s: %v\n", checkpointPath, err)
return "", "", "", time.Time{}, err
return nil, err
}

specDumpPath := filepath.Join(tempDir, "spec.dump")
specContent, err := os.ReadFile(specDumpPath)
if err != nil {
log.Printf("Error reading spec.dump file: %v\n", err)
return nil, err
}

configDumpPath := filepath.Join(tempDir, "config.dump")
content, err := os.ReadFile(configDumpPath)
configContent, err := os.ReadFile(configDumpPath)
if err != nil {
log.Printf("Error reading config.dump file: %v\n", err)
return "", "", "", time.Time{}, err
return nil, err
}

return extractConfigDumpContent(content)
return extractConfigDumpContent(configContent, specContent)
}

func extractConfigDumpContent(content []byte) (namespace, pod, container string, timestamp time.Time, err error) {
var c metadata.ContainerConfig
if err := json.Unmarshal(content, &c); err != nil {
return "", "", "", time.Time{}, err
}
func extractConfigDumpContent(configContent []byte, specContent []byte) (*ChkptConfig, error) {
var spec metadata.Spec
var config metadata.ContainerConfig

parts := strings.Split(c.Name, "_")
if err := json.Unmarshal(configContent, &config); err != nil {
return nil, err
}

if len(parts) >= 4 {
container = parts[1]
pod = parts[2]
namespace = parts[3]
} else {
return "", "", "", time.Time{}, fmt.Errorf("invalid name format")
if err := json.Unmarshal(specContent, &spec); err != nil {
return nil, err
}

return namespace, pod, container, c.CheckpointedAt, nil
namespace := spec.Annotations["io.kubernetes.pod.namespace"]
timestamp := config.CheckpointedAt
pod := spec.Annotations["io.kubernetes.pod.name"]
container := spec.Annotations["io.kubernetes.container.name"]
containerManager := spec.Annotations["io.container.manager"]

return &ChkptConfig{
Namespace: namespace,
Pod: pod,
Container: container,
ContainerManager: containerManager,
Timestamp: timestamp,
}, nil
}
36 changes: 9 additions & 27 deletions internal/config_extractor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,32 @@ package internal

import (
"os"
"path/filepath"
"testing"
"time"
)

func TestExtractConfigDump(t *testing.T) {
tempDir, err := os.MkdirTemp("", "test-extract-config-dump")
func TestExtractConfigDumpContent(t *testing.T) {
configContent, err := os.ReadFile("../test/data/list_config_spec.dump/config.dump")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDir)

checkpointFile := filepath.Join(tempDir, "checkpoint-123.tar")
if err := CreateTarWithFile(checkpointFile, "../test/data/list_config.dump/config.dump"); err != nil {
t.Fatal(err)
}

namespace, pod, container, timestamp, err := ExtractConfigDump(checkpointFile)
specContent, err := os.ReadFile("../test/data/list_config_spec.dump/spec.dump")
if err != nil {
t.Fatalf("ExtractConfigDump failed: %v", err)
}

expectedNamespace := "default"
expectedPod := "deployment-name"
expectedContainer := "container-name"
expectedTimestamp := time.Date(2024, 1, 28, 0, 10, 45, 673538606, time.FixedZone("", 19800))
if namespace != expectedNamespace || pod != expectedPod || container != expectedContainer || !timestamp.Equal(expectedTimestamp) {
t.Errorf("ExtractConfigDump returned unexpected values: namespace=%s, pod=%s, container=%s, timestamp=%v", namespace, pod, container, timestamp)
t.Fatal(err)
}
}

func TestExtractConfigDumpContent(t *testing.T) {
content := []byte(`{"id":"6924be1bd90c23f10e2667102b0ee0f74f09bba78b6661871e733cb3b1737821","name":"k8s_container-name_deployment-name_default_6975ee47-6765-45dc-9a2b-1e38d51031f7_0","checkpointedTime":"2024-01-28T00:10:45.673538606+05:30"}`)

namespace, pod, container, timestamp, err := extractConfigDumpContent(content)
chkptConfig, err := extractConfigDumpContent(configContent, specContent)
if err != nil {
t.Fatalf("ExtractConfigDumpContent failed: %v", err)
}

expectedNamespace := "default"
expectedPod := "deployment-name"
expectedPod := "pod-name"
expectedContainer := "container-name"
expectedContainerManager := "cri-o"
expectedTimestamp := time.Date(2024, 1, 28, 0, 10, 45, 673538606, time.FixedZone("", 19800))
if namespace != expectedNamespace || pod != expectedPod || container != expectedContainer || !timestamp.Equal(expectedTimestamp) {
t.Errorf("ExtractConfigDumpContent returned unexpected values: namespace=%s, pod=%s, container=%s, timestamp=%v", namespace, pod, container, timestamp)
if chkptConfig.Namespace != expectedNamespace || chkptConfig.Pod != expectedPod || chkptConfig.Container != expectedContainer || !chkptConfig.Timestamp.Equal(expectedTimestamp) || chkptConfig.ContainerManager != expectedContainerManager {
t.Errorf("ExtractConfigDumpContent returned unexpected values: namespace=%s, pod=%s, container=%s, timestamp=%v", chkptConfig.Namespace, chkptConfig.Pod, chkptConfig.Container, chkptConfig.Timestamp)
}
}
33 changes: 0 additions & 33 deletions internal/utils.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package internal

import (
"archive/tar"
"fmt"
"os"
"path/filepath"
"strings"
"time"

Expand Down Expand Up @@ -84,34 +82,3 @@ func CleanupTasks(tasks []Task) {
}
}
}

func CreateTarWithFile(tarPath string, filePath string) error {
file, err := os.Create(tarPath)
if err != nil {
return err
}
defer file.Close()
tarWriter := tar.NewWriter(file)
defer tarWriter.Close()

fileInfo, err := os.Stat(filePath)
if err != nil {
return err
}
header, err := tar.FileInfoHeader(fileInfo, "")
if err != nil {
return err
}
header.Name = filepath.Base(filePath)
if err := tarWriter.WriteHeader(header); err != nil {
return err
}
content, err := os.ReadFile(filePath)
if err != nil {
return err
}
if _, err := tarWriter.Write(content); err != nil {
return err
}
return nil
}
4 changes: 4 additions & 0 deletions lib/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ type ContainerConfig struct {
Restored bool `json:"restored"`
}

type Spec struct {
Annotations map[string]string `json:"annotations,omitempty"`
}

type ContainerdStatus struct {
CreatedAt int64
StartedAt int64
Expand Down
28 changes: 20 additions & 8 deletions test/checkpointctl.bats
Original file line number Diff line number Diff line change
Expand Up @@ -656,27 +656,39 @@ function teardown() {

@test "Run checkpointctl list with empty tar file" {
touch "$TEST_TMP_DIR1"/checkpoint-nginx-empty.tar
checkpointctl list -p "$TEST_TMP_DIR1"/
checkpointctl list -p "$TEST_TMP_DIR1"
[ "$status" -eq 0 ]
[[ "${lines[1]}" == *"Error reading config.dump file"* ]]
[[ "${lines[1]}" == *"Error reading spec.dump file"* ]]
[[ "${lines[2]}" == *"Error extracting information"* ]]
}

@test "Run checkpointctl list with tar file with empty config.dump" {
@test "Run checkpointctl list with tar file with valid spec.dump and empty config.dump" {
touch "$TEST_TMP_DIR1"/config.dump
cp data/list_config_spec.dump/spec.dump "$TEST_TMP_DIR1"
mkdir "$TEST_TMP_DIR1"/checkpoint
( cd "$TEST_TMP_DIR1" && tar cf "$TEST_TMP_DIR2"/checkpoint-empty-config.tar . )
( cd "$TEST_TMP_DIR1" && tar cf "$TEST_TMP_DIR2"/checkpoint-config.tar . )
checkpointctl list -p "$TEST_TMP_DIR2"
[ "$status" -eq 0 ]
[[ ${lines[1]} == *"Error extracting information from $TEST_TMP_DIR2/checkpoint-empty-config.tar: unexpected end of JSON input" ]]
[[ "${lines[1]}" == *"Error extracting information from $TEST_TMP_DIR2/checkpoint-config.tar: unexpected end of JSON input"* ]]
}

@test "Run checkpointctl list with tar file with valid config.dump" {
cp data/list_config.dump/config.dump "$TEST_TMP_DIR1"
@test "Run checkpointctl list with tar file with valid config.dump and empty spec.dump" {
touch "$TEST_TMP_DIR1"/spec.dump
cp data/list_config_spec.dump/config.dump "$TEST_TMP_DIR1"
mkdir "$TEST_TMP_DIR1"/checkpoint
( cd "$TEST_TMP_DIR1" && tar cf "$TEST_TMP_DIR2"/checkpoint-config.tar . )
checkpointctl list -p "$TEST_TMP_DIR2"
[ "$status" -eq 0 ]
[[ ${lines[1]} == *"Error extracting information from $TEST_TMP_DIR2/checkpoint-config.tar: unexpected end of JSON input" ]]
}

@test "Run checkpointctl list with tar file with valid config.dump nad spec.dump" {
cp data/list_config_spec.dump/config.dump "$TEST_TMP_DIR1"
cp data/list_config_spec.dump/spec.dump "$TEST_TMP_DIR1"
mkdir "$TEST_TMP_DIR1"/checkpoint
( cd "$TEST_TMP_DIR1" && tar cf "$TEST_TMP_DIR2"/checkpoint-valid-config.tar . )
checkpointctl list -p "$TEST_TMP_DIR2"
[ "$status" -eq 0 ]
[[ "${lines[4]}" == *"| default | deployment-name | container-name |"* ]]
[[ "${lines[4]}" == *"| default | pod-name | container-name | cri-o |"* ]]
[[ "${lines[4]}" == *"| checkpoint-valid-config.tar |"* ]]
}
File renamed without changes.
9 changes: 9 additions & 0 deletions test/data/list_config_spec.dump/spec.dump
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"annotations": {
"io.container.manager": "cri-o",
"io.kubernetes.container.hash": "1511917a",
"io.kubernetes.container.name": "container-name",
"io.kubernetes.pod.name": "pod-name",
"io.kubernetes.pod.namespace": "default"
}
}

0 comments on commit 49c2417

Please sign in to comment.