Files
dsh-web/internal/api/restore_snapshot_test.go

118 lines
3.8 KiB
Go

package api
import (
"context"
"encoding/json"
"fmt"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"syscall"
"testing"
)
func TestRestoreSnapshotCopyFailureDoesNotConsumeRetention(t *testing.T) {
if os.Geteuid() != 0 {
t.Skip("requires chown")
}
a, i, home := configTestAPI(t, false)
configWrite(t, filepath.Join(home, ".credentials.yaml"), "archive")
w := configCall(a, i, "POST", "backups", `{"password":"original-password"}`)
var result struct{ ID string }
if w.Code != 200 || json.Unmarshal(w.Body.Bytes(), &result) != nil {
t.Fatal(w.Code, w.Body.String())
}
configWrite(t, filepath.Join(home, ".credentials.yaml"), "live")
base := filepath.Dir(home)
for _, name := range []string{"pre-restore-valuable", "failed-restore-valuable"} {
if err := os.Mkdir(filepath.Join(base, name), 0700); err != nil {
t.Fatal(err)
}
configWrite(t, filepath.Join(base, name, "keep"), name)
}
if err := syscall.Mkfifo(filepath.Join(home, "dangling"), 0600); err != nil {
t.Fatal(err)
}
body := fmt.Sprintf(`{"backup_id":%q,"password":"original-password"}`, result.ID)
for attempt := 0; attempt < 3; attempt++ {
w = configCall(a, i, "POST", "restore", body)
if w.Code != 400 || !strings.Contains(w.Body.String(), "cannot snapshot home") {
t.Fatalf("attempt %d: %d %s", attempt, w.Code, w.Body.String())
}
entries, err := os.ReadDir(base)
if err != nil || len(entries) != 3 {
t.Fatalf("partial snapshot leaked: %v %v", entries, err)
}
for _, name := range []string{"pre-restore-valuable", "failed-restore-valuable"} {
data, err := os.ReadFile(filepath.Join(base, name, "keep"))
if err != nil || string(data) != name {
t.Fatalf("valuable snapshot lost: %s %v", name, err)
}
}
data, err := os.ReadFile(filepath.Join(home, ".credentials.yaml"))
if err != nil || string(data) != "live" {
t.Fatalf("live home changed: %q %v", data, err)
}
}
if err := os.Remove(filepath.Join(home, "dangling")); err != nil {
t.Fatal(err)
}
if w = configCall(a, i, "POST", "restore", body); w.Code != 200 {
t.Fatal(w.Code, w.Body.String())
}
entries, err := os.ReadDir(base)
if err != nil || len(entries) != 4 {
t.Fatalf("completed snapshot not retained: %v %v", entries, err)
}
if w = configCall(a, i, "POST", "restore", body); w.Code != 409 {
t.Fatal("retention not enforced", w.Code, w.Body.String())
}
}
func TestWorkspaceLeaseBlocksConfigAndUpgrade(t *testing.T) {
a, i, _ := configTestAPI(t, false)
a.workspaceReaders = map[string]int{i.ID: 1}
for _, action := range []string{"profile", "restore", "backups"} {
w := configCall(a, i, "POST", action, `{}`)
if w.Code != 409 || !strings.Contains(w.Body.String(), "instance files busy") {
t.Fatal(w.Code, w.Body.String())
}
}
w := configCall(a, i, "GET", "backups", "")
if w.Code != 200 {
t.Fatal(w.Code, w.Body.String())
}
w = httptest.NewRecorder()
body, _ := json.Marshal(map[string]string{"image_id": "sha256:" + strings.Repeat("a", 64), "password": "original-password"})
r := httptest.NewRequest("POST", "/", strings.NewReader(string(body)))
a.upgrade(w, r, i)
if w.Code != 409 || !strings.Contains(w.Body.String(), "instance files busy") {
t.Fatal(w.Code, w.Body.String())
}
}
func TestUpgradeCopyMultipleDirectoryBatches(t *testing.T) {
if os.Geteuid() != 0 {
t.Skip("requires chown")
}
root, err := os.OpenRoot(t.TempDir())
if err != nil {
t.Fatal(err)
}
defer root.Close()
if err = root.Mkdir("home", 0700); err != nil {
t.Fatal(err)
}
for n := 0; n < 257; n++ {
configWrite(t, filepath.Join(root.Name(), "home", fmt.Sprint(n)), "x")
}
if err = upgradeCopy(context.Background(), root, "home", "snapshot", 257); err != nil {
t.Fatal(err)
}
entries, err := os.ReadDir(filepath.Join(root.Name(), "snapshot"))
if err != nil || len(entries) != 257 {
t.Fatal(len(entries), err)
}
}