//go:build linux package host import ( "context" "errors" "fmt" "os" "path/filepath" "strings" "testing" "time" "github.com/carlosframework/platform/internal/cgroup" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" execlease "github.com/carlosframework/platform/internal/hostexec" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/hostwrite" "github.com/carlosframework/platform/internal/instancectl" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/registry" ) // L2, launch authorize (claimlaunch_authority.go): root's launch authority // reads the production identity fresh. A reservation whose target carries the // box binding is authorized before and after six roster writes; a target // stamped with the roster numeral is refused, and after a quarantine and // readmission the same reservation is refused. func TestClaimBindingLaunchAuthorizeSurvivesRosterWrites(t *testing.T) { ctx := t.Context() roster := newBindingRosterFixture(t, "dst", "ie") identity := roster.identity() actor, err := identity.Verify(ctx) if err != nil { t.Fatal(err) } dataDir := t.TempDir() f := newROFixture(t, roOptions{}) f.inputs.Launch = func(context.Context, registry.Route) (ClaimServingLaunch, error) { return ClaimServingLaunch{Identity: hostwrite.Identity{Config: "sha256:config", Release: "sha256:release"}, Executable: "/opt/carlos/versions/v1/app", ExecutableSHA256: strings.Repeat("b", 64), Version: "v1", Args: []string{"--socket", filepath.Join(provision.InstanceRunDir(roHost), "app.sock"), "--db", provision.NestedDBPath(dataDir, roHost)}, Env: []string{"CARLOS_ADMIN_TOKEN=test"}}, nil } in, err := f.inputs.Select(ctx, f.route, f.read(), roProbe) if err != nil { t.Fatal(err) } admitted := f.admit(f.read(), in) h := admitted.Claim.Holder boot, err := execlease.BootID() if err != nil { t.Fatal(err) } start, err := claimConstructionProcessStart() if err != nil { t.Fatal(err) } peer := StopPeer{UID: 1001, PID: os.Getpid(), ProcessStart: start, BootID: boot, ControllerID: "controller-1"} target := hostguard.FenceTarget{Region: "ie", Host: roHost, Incarnation: h.Incarnation, Stamp: h.Stamp, Session: h.Session, Op: roOp, Phase: hostclaim.PhaseRelocatingProbing, Box: actor.Box, Enrollment: actor.Enrollment, BootID: peer.BootID, ControllerID: peer.ControllerID, ClaimETag: admitted.ETag, DirectoryRevision: actor.Binding} attempts := &relocationProbeAttempts{Claims: f.claims, DataDir: dataDir, Box: actor.Box, NewID: f.inputs.NewID, Now: f.inputs.Now} s := OperationStart{Admitted: admitted, Policy: roProbe, Inputs: in, Owner: target} if err := attempts.Reserve(ctx, s, 0); err != nil { t.Fatal(err) } a := attempts.CurrentAttempt(s) exe := hostbox.LaunchFile{Device: 5, Inode: 6} kernel := &launchKernel{path: in.Executable, want: exe, digest: in.ExecutableSHA256} root := &ClaimLaunchRoot{ValidateExecutable: func(string, string, string) error { return nil }, Local: &rev2Local{}, Clock: &rev2Clock{wall: time.Now(), boot: 100}, DataRoot: dataDir, ExecLockDir: t.TempDir(), Attempts: map[string]claimLaunchAttemptReader{hostclaim.KindRelocating: relocationAttemptReader{DataDir: dataDir}}, Guard: hostguard.Guard{DataDir: dataDir, LockDir: t.TempDir(), Claims: hostguard.StoreReader{Store: f.claims}}, Actor: identity.Verify, Kernel: kernel} configureLaunchParticipation(t, &launchFixture{root: root, peer: peer, target: target, claims: &launchClaims{obs: admitted}}) spec := &hostbox.LaunchSpec{Identity: in.Identity, Executable: in.Executable, Digest: in.ExecutableSHA256, Args: in.Args, Env: in.Env} reservation := hostbox.LaunchReservation{ID: a.ID + ":main", Role: "main", InvocationID: a.InvocationID, Inputs: a.LaunchInputs, Target: target, Group: hostbox.LaunchGroup{RelativePath: instancectl.ClaimUnitControlGroup(roHost)}, Executable: exe} q := hostbox.RoleLaunchRequest{V: hostbox.RoleLaunchV, Op: hostbox.RoleLaunchStart, Spec: spec, Reservation: &reservation, Executable: in.Executable, MemoryMax: cgroup.TierDefault.MemoryMax} if err := root.authorize(ctx, peer, q, admitted.Claim); err != nil { t.Fatal("authorize at the install's binding:", err) } roster.sixWrites() if err := root.authorize(ctx, peer, q, admitted.Claim); err != nil { t.Fatal("authorize after six roster writes:", err) } numeral := reservation numeral.Target.DirectoryRevision = fmt.Sprint(roster.read().Roster.Revision) q.Reservation = &numeral if err := root.authorize(ctx, peer, q, admitted.Claim); !errors.Is(err, hostbox.ErrChanged) { t.Fatalf("numeral-stamped target: %v, want ErrChanged", err) } q.Reservation = &reservation roster.quarantine("loss_1") roster.readmit() if err := root.authorize(ctx, peer, q, admitted.Claim); !errors.Is(err, hostbox.ErrChanged) { t.Fatalf("after readmission: %v, want ErrChanged", err) } } // L2, serving handover (claimservinghandover.go validateServingHandover): the // prior installation and the successor are both stamped with the binding; // after six roster writes the handover runs. A successor stamped with the // roster numeral is refused before any successor state. func TestClaimBindingServingHandoverSurvivesRosterWrites(t *testing.T) { ctx := t.Context() roster := newBindingRosterFixture(t, "box-1", "ie") identity := roster.identity() actor, err := identity.Verify(ctx) if err != nil { t.Fatal(err) } f := newServingRootFixtureFor(t, true, actor) f.p.root.InventoryActor = identity.Verify root := servingHandoverRoot(t, f) roster.sixWrites() before := f.f.read() next := hostbox.ColdSuccessor{Host: f.current.Host, Incarnation: f.current.Incarnation, Box: actor.Box, Enrollment: actor.Enrollment, DirectoryRevision: fmt.Sprint(roster.read().Roster.Revision), ETag: before.ETag, Generation: f.current.Generation, Stamp: f.current.Stamp, Session: f.current.Session} if err := root.servingHandover().Run(ctx, f.receipt.Target, next); !errors.Is(err, hostbox.ErrChanged) { t.Fatalf("numeral successor: %v, want ErrChanged", err) } if _, err := f.f.guard.Capture(f.current.Host); err != nil { t.Fatal(err) } next.DirectoryRevision = actor.Binding if err := root.servingHandover().Run(ctx, f.receipt.Target, next); err != nil { t.Fatal("serving handover after six roster writes:", err) } installed, err := f.f.guard.Capture(f.current.Host) if err != nil || installed != (hostguard.Captured{Incarnation: f.current.Incarnation, Stamp: f.current.Stamp}) { t.Fatalf("successor installation: %+v %v", installed, err) } in, _, err := root.Reconcile.Inventory.Snapshot(ctx, f.current.Host, installed) if err != nil || in.DirectoryRevision != actor.Binding { t.Fatalf("successor inventory stamp %q: %v", in.DirectoryRevision, err) } }