121 lines
3.9 KiB
Go
121 lines
3.9 KiB
Go
package platform
|
|
|
|
import (
|
|
"context"
|
|
"git.casaderoll.de/michael/urbm/internal/model"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func fakeDocker(t *testing.T, body string) string {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "docker")
|
|
if err := os.WriteFile(path, []byte("#!/bin/sh\n"+body), 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Setenv("PATH", dir+":"+os.Getenv("PATH"))
|
|
return dir
|
|
}
|
|
|
|
func TestPreparePreservesStoppedContainersAndIncludesVolumes(t *testing.T) {
|
|
dir := fakeDocker(t, `case "$1" in
|
|
inspect)
|
|
if [ "$2" = --format ]; then
|
|
if [ "$4" = off ]; then echo '{"Running":false}'; else echo '{"Running":true}'; fi
|
|
else echo '[{"Mounts":[{"Type":"bind","Source":"/appdata"},{"Type":"volume","Source":"/docker/volumes/db/_data"},{"Type":"tmpfs","Source":""}]}]'; fi;;
|
|
stop|start) echo "$1 $2" >> "$ACTIONS";;
|
|
esac
|
|
`)
|
|
actions := filepath.Join(dir, "actions")
|
|
t.Setenv("ACTIONS", actions)
|
|
w := &WorkloadManager{RuntimeDir: dir}
|
|
job := model.Job{ID: "test", Type: model.JobDocker, Sources: []model.Source{{WorkloadID: "off"}, {WorkloadID: "on"}}}
|
|
job.Consistency.Mode = "stop"
|
|
p, err := w.Prepare(context.Background(), job)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(p.Stopped) != 1 || p.Stopped[0].WorkloadID != "on" {
|
|
t.Fatalf("stopped: %+v", p.Stopped)
|
|
}
|
|
if !strings.Contains(strings.Join(p.Sources, "\n"), "/docker/volumes/db/_data") {
|
|
t.Fatal("named volume omitted")
|
|
}
|
|
if err = w.Cleanup(context.Background(), p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data, _ := os.ReadFile(actions)
|
|
if strings.Contains(string(data), "start off") || !strings.Contains(string(data), "start on") {
|
|
t.Fatalf("actions: %s", data)
|
|
}
|
|
}
|
|
|
|
func TestRecoveryContinuesAfterFailureAndCancellation(t *testing.T) {
|
|
dir := fakeDocker(t, `echo "$1 $2" >> "$ACTIONS"
|
|
if [ "$2" = broken ]; then exit 1; fi
|
|
`)
|
|
actions := filepath.Join(dir, "actions")
|
|
t.Setenv("ACTIONS", actions)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
w := &WorkloadManager{}
|
|
err := w.Cleanup(ctx, Prepared{Kind: model.JobDocker, Stopped: []model.Source{{WorkloadID: "healthy"}, {WorkloadID: "broken"}}})
|
|
if err == nil || !strings.Contains(err.Error(), "broken") {
|
|
t.Fatalf("error: %v", err)
|
|
}
|
|
data, _ := os.ReadFile(actions)
|
|
if !strings.Contains(string(data), "start healthy") {
|
|
t.Fatalf("recovery aborted: %s", data)
|
|
}
|
|
}
|
|
|
|
func TestPrepareRollsBackPreviouslyStoppedContainers(t *testing.T) {
|
|
dir := fakeDocker(t, `case "$1" in
|
|
inspect)
|
|
if [ "$2" = bad ]; then exit 1; fi
|
|
if [ "$2" = --format ]; then echo '{"Running":true}'; else echo '[{"Mounts":[]}]'; fi;;
|
|
stop|start) echo "$1 $2" >> "$ACTIONS";;
|
|
esac
|
|
`)
|
|
actions := filepath.Join(dir, "actions")
|
|
t.Setenv("ACTIONS", actions)
|
|
w := &WorkloadManager{RuntimeDir: dir}
|
|
job := model.Job{ID: "test", Type: model.JobDocker, Sources: []model.Source{{WorkloadID: "first"}, {WorkloadID: "bad"}}}
|
|
job.Consistency.Mode = "stop"
|
|
if _, err := w.Prepare(context.Background(), job); err == nil {
|
|
t.Fatal("expected failure")
|
|
}
|
|
data, _ := os.ReadFile(actions)
|
|
if !strings.Contains(string(data), "start first") {
|
|
t.Fatalf("missing rollback: %s", data)
|
|
}
|
|
}
|
|
|
|
func TestSlowRecoveryDoesNotExhaustOtherStarts(t *testing.T) {
|
|
dir := fakeDocker(t, `if [ "$2" = slow ]; then sleep 6; fi
|
|
echo "$2" >> "$ACTIONS"
|
|
`)
|
|
actions := filepath.Join(dir, "actions")
|
|
t.Setenv("ACTIONS", actions)
|
|
err := (&WorkloadManager{}).Cleanup(context.Background(), Prepared{Kind: model.JobDocker, Stopped: []model.Source{{WorkloadID: "next"}, {WorkloadID: "slow"}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data, _ := os.ReadFile(actions)
|
|
if string(data) != "slow\nnext\n" {
|
|
t.Fatalf("incomplete recovery: %s", data)
|
|
}
|
|
}
|
|
|
|
func TestPersistentMountWithoutPathFails(t *testing.T) {
|
|
dir := fakeDocker(t, `echo '[{"Mounts":[{"Type":"volume","Source":""}]}]'
|
|
`)
|
|
w := &WorkloadManager{}
|
|
if _, err := w.captureMetadata(context.Background(), model.JobDocker, "db", dir); err == nil {
|
|
t.Fatal("missing volume path silently omitted")
|
|
}
|
|
}
|