//go:build linux package host import ( "context" "errors" "fmt" "io" "os" "path/filepath" "reflect" "testing" "time" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostfiles" "github.com/carlosframework/platform/internal/lease" "github.com/carlosframework/platform/internal/lease/leasetest" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/registry" ) type filesEvidenceMetadata struct { lease.VersionedStore lists, reads int } func (s *filesEvidenceMetadata) ListVersions(ctx context.Context, b, k string) ([]lease.ObjectVersion, error) { s.lists++ return s.VersionedStore.ListVersions(ctx, b, k) } func (s *filesEvidenceMetadata) GetVersion(context.Context, string, string, string) ([]byte, error) { s.reads++ return nil, hostfiles.ErrNoEvidence } func (s *filesEvidenceMetadata) OpenVersion(context.Context, string, string, string) (io.ReadCloser, error) { s.reads++ return nil, hostfiles.ErrNoEvidence } func TestUnseededServingRequestsSpendNoRetry(t *testing.T) { for _, state := range []string{"none", "attempting", "open", "failed"} { t.Run(state, func(t *testing.T) { f := newServingRootFixture(t, true) count := 0 if state != "none" { count = 1 } if state == "failed" { count = 5 } for i := 0; i < count; i++ { o := f.f.read() attempt := hostclaim.WakeRetryAttempt{ID: fmt.Sprint("attempt-", i), Box: f.actor.Box, Enrollment: f.actor.Enrollment, BootID: "old-boot", ControllerID: "old-controller", InvocationID: fmt.Sprint("invocation-", i), LaunchInputs: "inputs"} reserved, err := hostclaim.Update(t.Context(), f.f.claims, o, hostclaim.ReserveWakeRetryAttempt(hostclaim.WakeRetryReservation{HolderIncarnation: o.Claim.Holder.Incarnation, EpisodeID: "episode", Attempt: attempt, Now: f.f.now})) if err != nil { t.Fatal(err) } if state != "attempting" { if _, err := hostclaim.Update(t.Context(), f.f.claims, reserved, hostclaim.FailWakeRetryAttempt(hostclaim.WakeRetryResult{HolderIncarnation: o.Claim.Holder.Incarnation, EpisodeID: "episode", Attempt: attempt, Now: f.f.now})); err != nil { t.Fatal(err) } f.f.now = f.f.now.Add(time.Minute) } } bucket := leasetest.NewVersionedBucket() bucket.AddBucket("data") data := &filesEvidenceMetadata{VersionedStore: bucket} provider := &movesFiles{Data: data, Bucket: "data", Box: f.actor.Box, Region: f.actor.Region, DataDir: f.f.guard.DataDir, EnvFile: func(host, db string) (claimEnvFile, error) { return claimEnvFile{Dir: filepath.Dir(db), Name: host + ".env", Nested: true}, nil }} p := newServingPreparation(t, f, "boot", "controller", func(w *ClaimWakeAssembly) { w.ServingPrerequisites = servingFilesPrerequisites(f.f.claims, provider) }) before := f.f.read() disk := servingBootSnapshot(t, f.disk.dir) roots := servingBootSnapshot(t, f.rootDisk.dir) for i := 0; i < 12; i++ { f.f.now = f.f.now.Add(time.Minute) err := p.runtime.Prerequisites(t.Context(), registry.Route{Host: before.Host, DBPath: provision.NestedDBPath(f.f.guard.DataDir, before.Host)}, before, recoveryPolicy) if !errors.Is(err, hostfiles.ErrNoEvidence) { t.Fatal(err) } } after := f.f.read() if !reflect.DeepEqual(before, after) || !reflect.DeepEqual(disk, servingBootSnapshot(t, f.disk.dir)) || !reflect.DeepEqual(roots, servingBootSnapshot(t, f.rootDisk.dir)) || len(p.events) != 0 { t.Fatal("missing evidence spent retries or changed root state", p.events) } if data.reads != 0 || data.lists < 12 { t.Fatal("guard did not use metadata only", data.lists, data.reads) } }) } } func TestServingEmptyBootstrapNeedsNoExistingInstanceDirectory(t *testing.T) { f := newFilesSeedFixture(t) if err := os.RemoveAll(filepath.Dir(f.route.DBPath)); err != nil { t.Fatal(err) } o := f.observed o.Claim.Holder.Generation = "" o.Claim.Holder.Session = 0 o.Claim.Holder.Admissions = nil o.Claim.Holder.WriterBox = "" // Match the production empty-authority predicate, not absence of a manifest. if !claimEmptyBootstrap(o.Claim.Holder) { t.Fatal("fixture not bootstrap") } if err := f.files.checkServingFilesEvidence(t.Context(), o, f.route); err != nil { t.Fatal(err) } }