116 lines
4.9 KiB
Go
116 lines
4.9 KiB
Go
|
|
package k8s
|
||
|
|
|
||
|
|
import (
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"scm.bstein.dev/bstein/soteria/internal/api"
|
||
|
|
"scm.bstein.dev/bstein/soteria/internal/config"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestBuildBackupJobAppliesSelectorsServiceAccountAndMetadata(t *testing.T) {
|
||
|
|
cfg := &config.Config{
|
||
|
|
ResticImage: "restic/restic:latest",
|
||
|
|
ResticRepository: "s3:https://repo/root",
|
||
|
|
ResticBackupArgs: []string{"--exclude", "*.tmp"},
|
||
|
|
ResticForgetArgs: []string{"--keep-daily", "7", "--prune"},
|
||
|
|
S3Endpoint: "https://s3.us-west-001.backblazeb2.com",
|
||
|
|
S3Region: "us-west-001",
|
||
|
|
JobTTLSeconds: 3600,
|
||
|
|
JobNodeSelector: map[string]string{"hardware": "rpi5"},
|
||
|
|
WorkerServiceAccount: "soteria-worker",
|
||
|
|
}
|
||
|
|
keepLast := 3
|
||
|
|
req := api.BackupRequest{
|
||
|
|
Namespace: "apps",
|
||
|
|
PVC: "data",
|
||
|
|
Tags: []string{"nightly"},
|
||
|
|
KeepLast: &keepLast,
|
||
|
|
}
|
||
|
|
|
||
|
|
job := buildBackupJob(cfg, req, "backup-job", "restic-secret", "s3:https://repo/root/isolated/apps/data", false, keepLast)
|
||
|
|
|
||
|
|
if job.Name != "backup-job" || job.Namespace != "apps" {
|
||
|
|
t.Fatalf("expected backup job identity, got %#v", job.ObjectMeta)
|
||
|
|
}
|
||
|
|
if job.Labels[labelPVC] != "data" || job.Annotations[annotationDedupeEnabled] != "false" || job.Annotations[annotationKeepLast] != "3" {
|
||
|
|
t.Fatalf("expected pvc/dedupe/keep-last metadata, got labels=%#v annotations=%#v", job.Labels, job.Annotations)
|
||
|
|
}
|
||
|
|
if got := job.Spec.Template.Spec.NodeSelector["hardware"]; got != "rpi5" {
|
||
|
|
t.Fatalf("expected node selector to be applied, got %#v", job.Spec.Template.Spec.NodeSelector)
|
||
|
|
}
|
||
|
|
if got := job.Spec.Template.Spec.ServiceAccountName; got != "soteria-worker" {
|
||
|
|
t.Fatalf("expected worker service account, got %q", got)
|
||
|
|
}
|
||
|
|
if len(job.Spec.Template.Spec.Containers) != 1 {
|
||
|
|
t.Fatalf("expected one backup container, got %#v", job.Spec.Template.Spec.Containers)
|
||
|
|
}
|
||
|
|
container := job.Spec.Template.Spec.Containers[0]
|
||
|
|
if container.Name != "restic" || container.Image != "restic/restic:latest" {
|
||
|
|
t.Fatalf("expected restic container, got %#v", container)
|
||
|
|
}
|
||
|
|
if len(container.Args) != 1 || !strings.Contains(container.Args[0], "restic backup /data") || !strings.Contains(container.Args[0], "--keep-last 3") {
|
||
|
|
t.Fatalf("expected backup command payload, got %#v", container.Args)
|
||
|
|
}
|
||
|
|
if len(job.Spec.Template.Spec.Volumes) != 2 || job.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim == nil {
|
||
|
|
t.Fatalf("expected pvc + cache volumes, got %#v", job.Spec.Template.Spec.Volumes)
|
||
|
|
}
|
||
|
|
if got := *job.Spec.BackoffLimit; got != 0 {
|
||
|
|
t.Fatalf("expected zero backoff limit, got %d", got)
|
||
|
|
}
|
||
|
|
if got := *job.Spec.TTLSecondsAfterFinished; got != 3600 {
|
||
|
|
t.Fatalf("expected ttl from config, got %d", got)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBuildRestoreJobCoversDefaultAndTargetPVCRestoreShapes(t *testing.T) {
|
||
|
|
cfg := &config.Config{
|
||
|
|
ResticImage: "restic/restic:latest",
|
||
|
|
ResticRepository: "s3:https://repo/root",
|
||
|
|
S3Endpoint: "https://s3.us-west-001.backblazeb2.com",
|
||
|
|
S3Region: "us-west-001",
|
||
|
|
JobTTLSeconds: 1800,
|
||
|
|
JobNodeSelector: map[string]string{"hardware": "rpi5"},
|
||
|
|
WorkerServiceAccount: "soteria-worker",
|
||
|
|
}
|
||
|
|
|
||
|
|
defaultJob := buildRestoreJob(cfg, api.RestoreTestRequest{
|
||
|
|
Namespace: "apps",
|
||
|
|
}, "restore-job", "restic-secret", "latest", "s3:https://repo/root")
|
||
|
|
if got := defaultJob.Spec.Template.Spec.Volumes[0].EmptyDir; got == nil {
|
||
|
|
t.Fatalf("expected default restore target to use emptyDir, got %#v", defaultJob.Spec.Template.Spec.Volumes[0])
|
||
|
|
}
|
||
|
|
if got := defaultJob.Spec.Template.Spec.ServiceAccountName; got != "soteria-worker" {
|
||
|
|
t.Fatalf("expected worker service account, got %q", got)
|
||
|
|
}
|
||
|
|
if got := defaultJob.Spec.Template.Spec.NodeSelector["hardware"]; got != "rpi5" {
|
||
|
|
t.Fatalf("expected node selector to be applied, got %#v", defaultJob.Spec.Template.Spec.NodeSelector)
|
||
|
|
}
|
||
|
|
|
||
|
|
targetPVCJob := buildRestoreJob(cfg, api.RestoreTestRequest{
|
||
|
|
Namespace: "apps",
|
||
|
|
TargetPVC: "restore-data",
|
||
|
|
}, "restore-job", "restic-secret", "snapshot-1", "s3:https://repo/root")
|
||
|
|
if targetPVCJob.Labels[labelPVC] != "restore-data" {
|
||
|
|
t.Fatalf("expected restore pvc label, got %#v", targetPVCJob.Labels)
|
||
|
|
}
|
||
|
|
if claim := targetPVCJob.Spec.Template.Spec.Volumes[0].PersistentVolumeClaim; claim == nil || claim.ClaimName != "restore-data" {
|
||
|
|
t.Fatalf("expected target pvc volume mount, got %#v", targetPVCJob.Spec.Template.Spec.Volumes[0])
|
||
|
|
}
|
||
|
|
if len(targetPVCJob.Spec.Template.Spec.Containers) != 1 || !strings.Contains(targetPVCJob.Spec.Template.Spec.Containers[0].Args[0], "restic restore snapshot-1") {
|
||
|
|
t.Fatalf("expected restore command payload, got %#v", targetPVCJob.Spec.Template.Spec.Containers)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestRestoreCommandAndInt32PtrHelpers(t *testing.T) {
|
||
|
|
command := restoreCommand("snapshot-42")
|
||
|
|
if !strings.Contains(command, "restic restore snapshot-42") || !strings.Contains(command, "cp -a /cache/restore") {
|
||
|
|
t.Fatalf("expected restore shell command, got %q", command)
|
||
|
|
}
|
||
|
|
|
||
|
|
value := int32Ptr(17)
|
||
|
|
if value == nil || *value != 17 {
|
||
|
|
t.Fatalf("expected int32 pointer helper to preserve value, got %#v", value)
|
||
|
|
}
|
||
|
|
}
|