Skip to content

Commit

Permalink
implement and test RestoreVolumeSnapshot (to a new pvc)
Browse files Browse the repository at this point in the history
  • Loading branch information
majodev committed Jan 10, 2025
1 parent 5699b6c commit fc854bd
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 0 deletions.
96 changes: 96 additions & 0 deletions internal/lib/vs.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,99 @@ func PruneVolumeSnapshot(namespace, volumeSnapshotName string, wait bool) error

return nil
}

// RestoreVolumeSnapshot creates a new PVC from a VolumeSnapshot
func RestoreVolumeSnapshot(namespace string, dryRun bool, vsName string, pvcName string, storageClass string, wait bool, waitTimeout string) error {
// Create PVC manifest
pvcObject := map[string]interface{}{
"apiVersion": "v1",
"kind": "PersistentVolumeClaim",
"metadata": map[string]interface{}{
"name": pvcName,
"namespace": namespace,
},
"spec": map[string]interface{}{
"dataSource": map[string]interface{}{
"name": vsName,
"kind": "VolumeSnapshot",
"apiGroup": "snapshot.storage.k8s.io",
},
"accessModes": []string{"ReadWriteOnce"},
},
}

if storageClass != "" {
pvcObject["spec"].(map[string]interface{})["storageClassName"] = storageClass
}

// Get the VolumeSnapshot to copy its storage request
// #nosec G204
cmd := exec.Command("kubectl", "get", "volumesnapshot", vsName, "-n", namespace, "-o", "json")
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("error getting VolumeSnapshot details: %w. Output:\n%s", err, string(output))
}

var vs map[string]interface{}
if err := json.Unmarshal(output, &vs); err != nil {
return fmt.Errorf("error unmarshaling VolumeSnapshot: %w", err)
}

// Get the storage size from the VolumeSnapshot status
if status, ok := vs["status"].(map[string]interface{}); ok {
if restoreSize, ok := status["restoreSize"].(string); ok {
pvcObject["spec"].(map[string]interface{})["resources"] = map[string]interface{}{
"requests": map[string]interface{}{
"storage": restoreSize,
},
}
}
}

stringifiedPVCObject, err := json.MarshalIndent(pvcObject, "", " ")
if err != nil {
return fmt.Errorf("error marshaling PVC object: %w", err)
}

log.Printf("Creating PVC '%s' in namespace '%s' from VolumeSnapshot '%s'...\n%s",
pvcName, namespace, vsName, string(stringifiedPVCObject))

if dryRun {
log.Println("Skipping PVC creation - dry run mode is active")
return nil
}

pvcJSON, err := json.Marshal(pvcObject)
if err != nil {
return fmt.Errorf("error marshaling PVC object: %w", err)
}

cmd = exec.Command("kubectl", "apply", "-f", "-", "-n", namespace)
cmd.Stdin = bytes.NewReader(pvcJSON)
output, err = cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("error creating PVC: %w. Output:\n%s", err, string(output))
}

if wait {
log.Printf("Waiting for PVC '%s' to be bound (timeout: %s)...", pvcName, waitTimeout)
// #nosec G204
cmd = exec.Command("kubectl", "wait", "--for=jsonpath={.status.phase}=Bound", "--timeout", waitTimeout,
"pvc/"+pvcName, "-n", namespace)
log.Println(cmd.Args)
output, err = cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("PVC '%s' did not become bound: %w. Output:\n%s",
pvcName, err, string(output))
}
}
// #nosec G204
cmd = exec.Command("kubectl", "get", "pvc/"+pvcName, "-n", namespace)
output, err = cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("error getting PVC details: %w. Output:\n%s", err, string(output))
}

log.Printf("PVC details:\n%s", string(output))
return nil
}
64 changes: 64 additions & 0 deletions internal/lib/vs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,67 @@ func TestVolumeCreateSimulatedForWeek(t *testing.T) {
})
}
}

func TestRestoreVolumeSnapshot(t *testing.T) {
// Create a test snapshot first
namespace := "generic-test"
vsName := fmt.Sprintf("test-backup-restore-%s", lib.GenerateRandomStringOrPanic(6))

labelVSConfig := lib.LabelVSConfig{
Type: "adhoc",
Pod: "gotest",
Retain: "days",
RetainDays: 30,
}

vsLabels := lib.GenerateVSLabels(namespace, "data", labelVSConfig, time.Now())
vsAnnotations := lib.GenerateVSAnnotations(lib.GetBAKEnvVars())
vsObject := lib.GenerateVSObject(namespace, "csi-hostpath-snapclass", "data", vsName, vsLabels, vsAnnotations)

// Create the snapshot and wait for it to be ready
err := lib.CreateVolumeSnapshot(namespace, false, vsName, vsObject, true, "25s")
require.NoError(t, err, "Failed to create test snapshot")

t.Run("successful restore", func(t *testing.T) {
pvcName := fmt.Sprintf("%s-restored", vsName)
err := lib.RestoreVolumeSnapshot(namespace, false, vsName, pvcName, "csi-hostpath-sc", true, "25s")
require.NoError(t, err)

// Verify the PVC was created
cmd := exec.Command("kubectl", "get", "pvc", pvcName, "-n", namespace)
output, err := cmd.CombinedOutput()
require.NoError(t, err, "Failed to get restored PVC: %s", string(output))

// Clean up the restored PVC
cmd = exec.Command("kubectl", "delete", "pvc", pvcName, "-n", namespace)
output, err = cmd.CombinedOutput()
require.NoError(t, err, "Failed to clean up restored PVC: %s", string(output))
})

t.Run("dry run mode", func(t *testing.T) {
pvcName := fmt.Sprintf("%s-restored2", vsName)
err := lib.RestoreVolumeSnapshot(namespace, true, vsName, pvcName, "csi-hostpath-sc", false, "25s")
require.NoError(t, err)

// Verify the PVC was not created
cmd := exec.Command("kubectl", "get", "pvc", pvcName, "-n", namespace)
_, err = cmd.CombinedOutput()
require.Error(t, err, "PVC should not exist in dry run mode")
})

t.Run("non-existent namespace", func(t *testing.T) {
pvcName := fmt.Sprintf("%s-restored3", vsName)
err := lib.RestoreVolumeSnapshot("non-existent-namespace", false, vsName, pvcName, "csi-hostpath-sc", false, "25s")
require.Error(t, err)
})

t.Run("non-existent snapshot", func(t *testing.T) {
pvcName := "test-pvc"
err := lib.RestoreVolumeSnapshot(namespace, false, "non-existent-snapshot", pvcName, "csi-hostpath-sc", false, "25s")
require.Error(t, err)
})

// Clean up the test snapshot
err = lib.PruneVolumeSnapshot(namespace, vsName, false)
require.NoError(t, err, "Failed to clean up test snapshot")
}

0 comments on commit fc854bd

Please sign in to comment.