package host import ( "context" "encoding/json" "fmt" "os" "testing" "github.com/carlosframework/platform/internal/hostclaim" execlease "github.com/carlosframework/platform/internal/hostexec" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/rootparticipation" ) func TestPlan14ExecutionMigrationIsIndependent(t *testing.T) { t.Parallel() for _, attack := range []string{"none", "missing", "legacy marker", "incomplete", "pending", "never enrolled", "enrollment", "box", "generation"} { t.Run(attack, func(t *testing.T) { _, root, iq := plan14InstallRegistration(t, hostclaim.KindLanding, "checking") if _, err := root.commitRootInventory(t.Context(), iq); err != nil { t.Fatal(err) } disk := &rollbackDisk{dir: t.TempDir()} census := rootparticipation.Census{Schema: 1, Box: "box-1", Enrollment: "en", Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true} switch attack { case "incomplete": census.Complete = false case "pending": census.Pending = "acquisition" case "never enrolled": census.NeverEnrolled = true case "enrollment": census.Enrollment = "other" case "box": census.Box = "other" case "generation": census.Migration++ } if attack != "missing" && attack != "legacy marker" { raw, _ := json.Marshal(census) if err := disk.Write("census.json", raw); err != nil { t.Fatal(err) } } if attack == "legacy marker" { if err := disk.Write("migration.json", []byte(`{"complete":true}`)); err != nil { t.Fatal(err) } } q := execlease.ExecutionRequest{Schema: 1, ID: "begin", Operation: "executionBegin", Host: iq.Target.Host, Incarnation: iq.Target.Incarnation, Stamp: iq.Target.Stamp, Session: iq.Target.Session, Op: iq.Target.Op} a, err := root.executionAuthorityWithMigration(t.Context(), disk, q, execlease.ControllerProcess{}) if attack == "none" { if err != nil || a.Migration != 1 { t.Fatalf("valid migration: %+v %v", a, err) } } else if err == nil { t.Fatal("invalid migration admitted", attack) } }) } } func TestPlan14ExecutionPreinstallationBindsMigration(t *testing.T) { _, root, iq := plan14InstallRegistration(t, hostclaim.KindLanding, "checking") disk := &rollbackDisk{dir: t.TempDir()} raw, _ := json.Marshal(rootparticipation.Census{Schema: 1, Box: "box-1", Enrollment: "en", Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true}) if err := disk.Write("census.json", raw); err != nil { t.Fatal(err) } q := execlease.ExecutionRequest{Schema: 1, ID: "begin", Operation: "executionBegin", Host: iq.Target.Host, Incarnation: iq.Target.Incarnation, Stamp: iq.Target.Stamp, Session: iq.Target.Session, Op: iq.Target.Op} a, err := root.executionAuthorityWithMigration(context.Background(), disk, q, execlease.ControllerProcess{}) if err != nil || !a.ParticipationOnly || a.Migration != 1 || a.Installation != "" { t.Fatalf("participation before registration: %+v %v", a, err) } if _, err := disk.Read(q.Host + ".json"); !os.IsNotExist(err) { t.Fatal("authority read created participation", err) } } func TestPlan14ExecutionServingBeforeInstallation(t *testing.T) { _, root, iq := plan14InstallRegistration(t, hostclaim.KindServing, "") disk := &rollbackDisk{dir: t.TempDir()} raw, _ := json.Marshal(rootparticipation.Census{Schema: 1, Box: "box-1", Enrollment: "en", Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true}) if err := disk.Write("census.json", raw); err != nil { t.Fatal(err) } boot, err := execlease.BootID() if err != nil { t.Fatal(err) } q := execlease.ExecutionRequest{Schema: 1, ID: "first-serving", Operation: "executionView", Host: iq.Target.Host, Incarnation: iq.Target.Incarnation, Stamp: iq.Target.Stamp, Session: iq.Target.Session} peer := execlease.ControllerProcess{KernelBootID: boot, PID: os.Getpid(), Start: "fixture"} broker, err := execlease.NewRootBroker(execlease.RootBrokerConfig{ State: disk, Remote: root.Stop.Coordinator.Evidence, Clock: execlease.SystemClock{}, BootID: boot, Identity: "broker", Authority: func(ctx context.Context, q execlease.ExecutionRequest, p execlease.ControllerProcess) (execlease.ExecutionAuthority, error) { return root.executionAuthorityWithMigration(ctx, disk, q, p) }, Alive: func(context.Context, execlease.ControllerProcess) error { return nil }, Cleanup: func(context.Context, execlease.ExecutionRecord) error { t.Error("serving cleanup"); return nil }, Settle: func(context.Context, execlease.ExecutionRecord) error { t.Error("serving settlement"); return nil }, Exclude: func(context.Context, func() error) error { t.Error("serving acquired lease") return execlease.ErrState }, }) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = broker.Close(context.Background()) }) out, err := broker.Execute(t.Context(), peer, q) if err != nil || out.Status != "serving" || out.Token != "" { t.Fatalf("first serving participation: %+v %v", out, err) } if _, err := disk.Read(q.Host + ".json"); !os.IsNotExist(err) { t.Fatal("serving created lease", err) } } func TestPlan14ExecutionRetainedSourceParticipation(t *testing.T) { t.Parallel() for _, attack := range []string{"none", "source", "enrollment", "stamp"} { t.Run(attack, func(t *testing.T) { f, root, iq := plan14InstallRegistration(t, hostclaim.KindServing, "") if _, err := root.commitRootInventory(t.Context(), iq); err != nil { t.Fatal(err) } prior := f.read().Claim.Holder.Stamp _, err := hostclaim.Update(t.Context(), f.claims, f.read(), func(c *hostclaim.Claim) error { c.Holder.Stamp++ c.Op = hostclaim.Op{ID: "move", Kind: hostclaim.KindLanding, Phase: "checking", Exec: "box-2", Participants: []string{"box-1", "box-2"}, Intent: map[string]string{"source": "box-1", "source_enrollment": "en", "target": "box-2", "to": "ie"}, Journal: map[string]string{hostclaim.JournalSourceStamp: fmt.Sprint(prior)}} switch attack { case "source": c.Op.Intent["source"] = "box-3" case "enrollment": c.Op.Intent["source_enrollment"] = "other" case "stamp": c.Op.Journal[hostclaim.JournalSourceStamp] = fmt.Sprint(prior + 1) } return nil }) if err != nil { t.Fatal(err) } q := executionExpectation(f.read().Claim) q.ID, q.Operation = "source", "executionBegin" a, err := root.executionAuthority(t.Context(), q, execlease.ControllerProcess{}) if attack != "none" { if err == nil { t.Fatal("foreign retained source admitted") } return } if err != nil || !a.ParticipationOnly || a.Installation == "" { t.Fatalf("retained source: %+v %v", a, err) } }) } } func TestPlan14ExecutionInstalledWithoutLaunchCleanup(t *testing.T) { t.Parallel() for _, positive := range []bool{false, true} { t.Run(fmt.Sprint(positive), func(t *testing.T) { f, root, iq := plan14InstallRegistration(t, hostclaim.KindLanding, "checking") if _, err := root.commitRootInventory(t.Context(), iq); err != nil { t.Fatal(err) } q := executionExpectation(f.read().Claim) a, err := root.executionAuthority(t.Context(), q, execlease.ControllerProcess{}) if err != nil { t.Fatal(err) } calls := 0 root.Stop.Coordinator.Kernel = executionNoLaunchKernel{stopKernelFunc(func(context.Context, hostguard.FenceTarget, StopInventory, DrainAcknowledgement) (StopKernelEvidence, error) { return StopKernelEvidence{}, ErrClaimStopInventory }), func(_ context.Context, target hostguard.FenceTarget, inv StopInventory) (StopKernelEvidence, error) { calls++ if target.Host != q.Host || target.Stamp != q.Stamp || target.Session != 0 || inv.InstallationStamp != q.Stamp { t.Fatal("wrong empty installation") } if !positive { return StopKernelEvidence{}, ErrClaimStopInventory } return StopKernelEvidence{}, nil }} err = root.cleanupExecution(t.Context(), execlease.ExecutionRecord{Request: q, Authority: a}) if calls != 1 || (err == nil) != positive { t.Fatalf("kernel completeness calls=%d err=%v", calls, err) } }) } } type executionNoLaunchKernel struct { stopKernelFunc verify func(context.Context, hostguard.FenceTarget, StopInventory) (StopKernelEvidence, error) } func (k executionNoLaunchKernel) VerifyNoProcess(ctx context.Context, target hostguard.FenceTarget, inv StopInventory) (StopKernelEvidence, error) { return k.verify(ctx, target, inv) } func TestPlan14ExecutionRecoveryStartsCanonicalFence(t *testing.T) { for _, changed := range []bool{false, true} { t.Run(fmt.Sprint(changed), func(t *testing.T) { coordinator, req, _ := claimStopFixture(t) root := ClaimRootHTTP{Stop: ClaimStopHTTP{Coordinator: coordinator}} target := hostguard.FenceTarget{Host: req.Host, Incarnation: req.Incarnation, Stamp: req.InstallationStamp, Session: req.WriterSession, Box: req.Box, Enrollment: req.Enrollment, BootID: req.BootID, ControllerID: req.ControllerID, DirectoryRevision: req.ExpectedDirectoryRevision, ObligationID: req.ObligationID} if changed { target.Session++ } got, err := root.beginExecutionRecoveryFence(t.Context(), target) if changed { if err == nil { t.Fatal("fenced a different writer session") } return } if err != nil { t.Fatal(err) } if got.FenceID == "" || got.ClaimETag != req.ExpectedClaimETag || got.Op != req.OperationID || got.Phase != req.Phase { t.Fatalf("unbound fence: %+v", got) } if err := coordinator.Guard.ResumeFence(t.Context(), got); err != nil { t.Fatal(err) } }) } } func TestPlan14ExecutionFirstSessionTransition(t *testing.T) { t.Parallel() f, root, iq := plan14InstallRegistration(t, hostclaim.KindLanding, "checking") q := executionExpectation(f.read().Claim) before, err := root.executionAuthority(t.Context(), q, execlease.ControllerProcess{}) if err != nil { t.Fatal(err) } if _, err := root.commitRootInventory(t.Context(), iq); err != nil { t.Fatal(err) } if _, err := hostclaim.Update(t.Context(), f.claims, f.read(), func(c *hostclaim.Claim) error { c.Holder.Session++; return nil }); err != nil { t.Fatal(err) } nextQ := executionExpectation(f.read().Claim) after, err := root.executionAuthority(t.Context(), nextQ, execlease.ControllerProcess{}) if err != nil { t.Fatal(err) } before.Migration = after.Migration old := execlease.ExecutionRecord{Request: q, Authority: before} if err := root.transitionExecution(t.Context(), old, nextQ, after); err != nil { t.Fatal("first root-authorized session refused", err) } nextQ.Op = "other" if err := root.transitionExecution(t.Context(), old, nextQ, after); err == nil { t.Fatal("foreign operation admitted") } } func TestPlan14ExecutionEstablishedSessionTransitionNeedsCleanup(t *testing.T) { t.Parallel() for _, clean := range []bool{false, true} { t.Run(fmt.Sprint(clean), func(t *testing.T) { f, root, iq := plan14InstallRegistration(t, hostclaim.KindLanding, "checking") if _, err := root.commitRootInventory(t.Context(), iq); err != nil { t.Fatal(err) } q := executionExpectation(f.read().Claim) before, err := root.executionAuthority(t.Context(), q, execlease.ControllerProcess{}) if err != nil { t.Fatal(err) } if _, err := hostclaim.Update(t.Context(), f.claims, f.read(), func(c *hostclaim.Claim) error { c.Holder.Session++; return nil }); err != nil { t.Fatal(err) } nextQ := executionExpectation(f.read().Claim) after, err := root.executionAuthority(t.Context(), nextQ, execlease.ControllerProcess{}) if err != nil { t.Fatal(err) } calls := 0 root.Stop.Coordinator.Kernel = executionNoLaunchKernel{verify: func(context.Context, hostguard.FenceTarget, StopInventory) (StopKernelEvidence, error) { calls++ if !clean { return StopKernelEvidence{}, ErrClaimStopInventory } return StopKernelEvidence{}, nil }} err = root.transitionExecution(t.Context(), execlease.ExecutionRecord{Request: q, Authority: before}, nextQ, after) if (err == nil) != clean || calls != 1 { t.Fatalf("cleanup=%t calls=%d err=%v", clean, calls, err) } }) } }