//go:build linux && rootrunner package host import ( "encoding/json" "os" "sort" "strconv" "strings" "testing" "github.com/carlosframework/platform/internal/lease/leasetest" ) type lifecycleRemoteCheckpoint struct { Boot, Snapshot string Objects []seedRebootObject } func lifecycleSaveBucket(t *testing.T, model *leasetest.VersionedBucket) { t.Helper() boot, err := os.ReadFile("/proc/sys/kernel/random/boot_id") lifecycleCheck(t, "checkpoint boot", err) saved := lifecycleRemoteCheckpoint{Boot: strings.TrimSpace(string(boot)), Snapshot: model.Snapshot()} versions, err := model.ListVersions(t.Context(), "data", "") lifecycleCheck(t, "checkpoint versions", err) for _, version := range versions { object := seedRebootObject{Version: version} if !version.DeleteMarker { object.Body, err = model.GetVersion(t.Context(), "data", version.Key, version.VersionID) lifecycleCheck(t, "checkpoint bytes", err) } saved.Objects = append(saved.Objects, object) } raw, err := json.Marshal(saved) lifecycleCheck(t, "checkpoint JSON", err) lifecycleCheck(t, "checkpoint remote service", os.WriteFile(lifecycleHome+"/remote.json", raw, 0600)) } func lifecycleRestoreBucket(t *testing.T, model *leasetest.VersionedBucket) { t.Helper() raw, err := os.ReadFile(lifecycleHome + "/remote.json") lifecycleCheck(t, "read remote checkpoint", err) var saved lifecycleRemoteCheckpoint lifecycleCheck(t, "decode remote checkpoint", json.Unmarshal(raw, &saved)) boot, err := os.ReadFile("/proc/sys/kernel/random/boot_id") lifecycleCheck(t, "read reboot identity", err) if saved.Boot == "" || saved.Boot == strings.TrimSpace(string(boot)) { t.Fatal("lifecycle runner did not reboot") } sort.Slice(saved.Objects, func(i, j int) bool { a, _ := strconv.Atoi(strings.TrimPrefix(saved.Objects[i].Version.VersionID, "v")) b, _ := strconv.Atoi(strings.TrimPrefix(saved.Objects[j].Version.VersionID, "v")) return a < b }) next := 1 for _, object := range saved.Objects { n, err := strconv.Atoi(strings.TrimPrefix(object.Version.VersionID, "v")) lifecycleCheck(t, "version number", err) for next < n { id := model.Put("data", "checkpoint-gap", nil) lifecycleCheck(t, "preserve version gap", model.DeleteVersion(t.Context(), "data", "checkpoint-gap", id)) next++ } var id string if object.Version.DeleteMarker { id = model.BareDelete("data", object.Version.Key) } else { id = model.Put("data", object.Version.Key, object.Body) } if id != object.Version.VersionID { t.Fatal("remote version identity changed", id, object.Version.VersionID) } next++ } if model.Snapshot() != saved.Snapshot { t.Fatal("remote state changed across reboot") } }