//go:build linux package host import ( "context" "errors" "os" "path/filepath" "testing" "github.com/carlosframework/platform/internal/activator" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostfiles" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/registry" ) func TestFilesSeedPrivateBypassRequiresExactExclusionAndAbsence(t *testing.T) { f := newFilesSeedFixture(t) selected, err := f.inspect() if err != nil { t.Fatal(err) } op := &filesSeedOperation{ctx: t.Context(), claims: f.claims, actor: f.actor, files: f.files, selected: selected} svc := &activator.Service{} token, err := svc.AcquireSeedExclusion(t.Context(), filesHost, nil) if err != nil { t.Fatal(err) } guard := op.prerequisites(token) if err := guard(t.Context(), f.route, f.observed); err != nil { t.Fatal(err) } if err := servingFilesPrerequisites(f.claims, f.files)(t.Context(), f.route, f.observed); !errors.Is(err, hostfiles.ErrNoEvidence) { t.Fatal("ordinary guard bypassed", err) } if err := op.prerequisites(nil)(t.Context(), f.route, f.observed); err == nil { t.Fatal("no exclusion") } bad := f.route bad.DBPath = "/wrong" if err := guard(t.Context(), bad, f.observed); err == nil { t.Fatal("layout bypassed") } f.data.Put("data", hostfiles.ManifestKey(movesFilesPrefix(f.observed.Claim.Holder.Location, "")), []byte("{}")) if err := guard(t.Context(), f.route, f.observed); err == nil { t.Fatal("presence bypassed") } if err := token.Release(); err != nil { t.Fatal(err) } if err := guard(t.Context(), f.route, f.observed); err == nil { t.Fatal("released exclusion") } } func TestFilesSeedRerunSettlementRequiresExclusion(t *testing.T) { for _, fault := range []string{"none", "live-controller", "missing-record", "no-exclusion", "canceled"} { t.Run(fault, func(t *testing.T) { f := newRecoveryFixture(t) if fault == "missing-record" { f.recordPrelaunch = false } old := f.controller("boot-1", "controller-1", 4101, "start-1") o := old.admit(t, f) if fault != "missing-record" { if _, err := old.runtime.Prepare(t.Context(), registry.Route{Host: recoveryHost}, o, recoveryPolicy); err != nil { t.Fatal(err) } } current := f.controller("boot-1", "controller-2", 4202, "start-2") dir := t.TempDir() rt := registry.Route{Host: recoveryHost, DBPath: provision.NestedDBPath(dir, recoveryHost)} if err := os.MkdirAll(filepath.Dir(rt.DBPath), 0700); err != nil { t.Fatal(err) } files := &movesFiles{DataDir: dir, EnvFile: func(host, db string) (claimEnvFile, error) { return claimEnvFile{Nested: true, Dir: filepath.Dir(db), Name: host + ".env"}, nil }} svc := &activator.Service{} token, err := svc.AcquireSeedExclusion(t.Context(), rt.Host, nil) if err != nil { t.Fatal(err) } if fault == "no-exclusion" { token = nil } f.kernel.alive = fault == "live-controller" ctx := t.Context() if fault == "canceled" { var cancel context.CancelFunc ctx, cancel = context.WithCancel(ctx) cancel() } got, err := settleFilesSeedRerun(ctx, rt, files, current.wake.Retries, f.read(), token) if fault != "none" { if err == nil { t.Fatal("unproved settlement accepted") } if f.retry().State != "attempting" { t.Fatal("failed settlement changed debt") } return } if err != nil { t.Fatal(err) } retry := got.Claim.Holder.WakeRetry if retry.State != "open" || retry.AttemptsReserved != 1 || retry.Attempt != nil { t.Fatal("not settled without refund", retry) } if len(f.receipts()) != 1 || f.receipts()[0].ControllerID != "controller-1" { t.Fatal("wrong stop tuple", f.receipts()) } if _, err = settleFilesSeedRerun(ctx, rt, files, current.wake.Retries, got, token); err != nil { t.Fatal(err) } if _, err = settleFilesSeedRerun(ctx, rt, files, current.wake.Retries, o, token); !errors.Is(err, hostbox.ErrChanged) { t.Fatal("stale rerun", err) } }) } }