| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "testing" |
| 7 | |
| 8 | "github.com/carlosframework/platform/internal/hostbox" |
| 9 | "github.com/carlosframework/platform/internal/hostclaim" |
| 10 | "github.com/carlosframework/platform/internal/hostwrite" |
| 11 | "github.com/carlosframework/platform/internal/provision" |
| 12 | ) |
| 13 | |
| 14 | // mpLaunch is the probe's launch identity (Plan 3 H4): the pinned config |
| 15 | // and the pinned release it launches with. |
| 16 | func mpLaunch() hostwrite.Identity { |
| 17 | p := mpPins() |
| 18 | return hostwrite.Identity{Config: HeldConfigIdentity(p.BundleETags, p.OverlayETag).Config, Release: p.Release} |
| 19 | } |
| 20 | |
| 21 | func mpProbingRecord() *provision.Record { |
| 22 | rec := mpRecord() |
| 23 | rec.Move.LandingPhase = provision.LandProbing |
| 24 | return rec |
| 25 | } |
| 26 | |
| 27 | // The probe's rows (Plan 3 Task 5, H4): at probing the held render, the |
| 28 | // held release and the spawn are granted, the spawn only with the launch |
| 29 | // identity bound to the pins, through hostbox.Policy.Check with the hold the |
| 30 | // landing has. probed grants nothing to a fresh selection. |
| 31 | func TestMovesClaimPolicyProbingRows(t *testing.T) { |
| 32 | ctx := t.Context() |
| 33 | moves := MovesClaimPolicy((&mpReader{rec: mpProbingRecord()}).read) |
| 34 | policy := hostbox.Policy{Moves: moves, Held: func(context.Context, string) (string, error) { return mpOp, nil }} |
| 35 | probing := mpClaim(hostclaim.KindLanding, hostclaim.PhaseLandingProbing) |
| 36 | for action, id := range map[string]hostwrite.Identity{ |
| 37 | "held-config": mpIdentity("held-config"), |
| 38 | "held-release": mpIdentity("held-release"), |
| 39 | "spawn": mpLaunch(), |
| 40 | } { |
| 41 | if err := policy.Check(ctx, probing, mpSelection(probing, action), id); err != nil { |
| 42 | t.Fatalf("probing/%s: %v", action, err) |
| 43 | } |
| 44 | } |
| 45 | for _, action := range []string{"held-timers", "restore", "seed", "writer", "install"} { |
| 46 | if err := policy.Check(ctx, probing, mpSelection(probing, action), hostwrite.Identity{}); err == nil { |
| 47 | t.Fatalf("probing/%s was granted", action) |
| 48 | } |
| 49 | } |
| 50 | refused := func(err error, reason string) { |
| 51 | t.Helper() |
| 52 | var r *MovesPolicyRefusal |
| 53 | if !errors.As(err, &r) || r.Reason != reason { |
| 54 | t.Fatalf("got %v, want the refusal %q", err, reason) |
| 55 | } |
| 56 | } |
| 57 | s := mpSelection(probing, "spawn") |
| 58 | refused(moves(ctx, probing, s, hostwrite.Identity{}), "the identity does not fit the action") |
| 59 | refused(moves(ctx, probing, s, hostwrite.Identity{Config: mpLaunch().Config}), "the identity does not fit the action") |
| 60 | refused(moves(ctx, probing, s, hostwrite.Identity{Config: mpLaunch().Config, Release: "v9"}), "the launch is not the pinned one") |
| 61 | refused(moves(ctx, probing, s, hostwrite.Identity{Config: HeldConfigIdentity(nil, "x").Config, Release: mpPins().Release}), "the launch is not the pinned one") |
| 62 | noPins := mpProbingRecord() |
| 63 | noPins.Move.Inputs = nil |
| 64 | refused(MovesClaimPolicy((&mpReader{rec: noPins}).read)(ctx, probing, s, mpLaunch()), "no pinned inputs") |
| 65 | marked := mpProbingRecord() |
| 66 | marked.Move.Reason = provision.RollbackProbeAttempts |
| 67 | refused(MovesClaimPolicy((&mpReader{rec: marked}).read)(ctx, probing, s, mpLaunch()), "the record is not this landing") |
| 68 | |
| 69 | probed := mpClaim(hostclaim.KindLanding, hostclaim.PhaseLandingProbed) |
| 70 | for _, action := range mpActions { |
| 71 | if err := policy.Check(ctx, probed, mpSelection(probed, action), mpLaunch()); err == nil { |
| 72 | t.Fatalf("a fresh probed/%s selection was granted", action) |
| 73 | } |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | // The admitted probe session survives the landing's forward steps (H3): its |
| 78 | // spawn selection, made at probing, is still granted once the claim is at |
| 79 | // probed and then committed, with the same session; a held render selected |
| 80 | // at probing is not, and no backward or skipped step is. |
| 81 | func TestMovesClaimPolicyProbeSessionContinues(t *testing.T) { |
| 82 | ctx := t.Context() |
| 83 | reader := &mpReader{rec: mpProbingRecord()} |
| 84 | moves := MovesClaimPolicy(reader.read) |
| 85 | policy := hostbox.Policy{Moves: moves, Held: func(context.Context, string) (string, error) { return mpOp, nil }} |
| 86 | selected := mpSelection(mpClaim(hostclaim.KindLanding, hostclaim.PhaseLandingProbing), "spawn") |
| 87 | for phase, state := range map[string][2]string{ |
| 88 | hostclaim.PhaseLandingProbed: {provision.MoveLanding, provision.LandProbed}, |
| 89 | hostclaim.PhaseLandingCommitted: {provision.MoveCommitted, ""}, |
| 90 | } { |
| 91 | c := mpClaim(hostclaim.KindLanding, phase) |
| 92 | reader.rec = mpProbingRecord() |
| 93 | reader.rec.Move.State, reader.rec.Move.LandingPhase = state[0], state[1] |
| 94 | if err := policy.Check(ctx, c, selected, mpLaunch()); err != nil { |
| 95 | t.Fatalf("the probe's spawn at %s: %v", phase, err) |
| 96 | } |
| 97 | } |
| 98 | reader.rec = mpProbingRecord() |
| 99 | held := mpSelection(mpClaim(hostclaim.KindLanding, hostclaim.PhaseLandingProbing), "held-config") |
| 100 | if err := policy.Check(ctx, mpClaim(hostclaim.KindLanding, hostclaim.PhaseLandingProbed), held, mpIdentity("held-config")); err == nil { |
| 101 | t.Fatal("a held render selected at probing was granted at probed") |
| 102 | } |
| 103 | restored := mpSelection(mpClaim(hostclaim.KindLanding, provision.LandRestored), "spawn") |
| 104 | if err := policy.Check(ctx, mpClaim(hostclaim.KindLanding, hostclaim.PhaseLandingProbing), restored, mpLaunch()); err == nil { |
| 105 | t.Fatal("a spawn selected at restored was granted at probing") |
| 106 | } |
| 107 | back := mpSelection(mpClaim(hostclaim.KindLanding, hostclaim.PhaseLandingProbed), "spawn") |
| 108 | if err := policy.Check(ctx, mpClaim(hostclaim.KindLanding, hostclaim.PhaseLandingProbing), back, mpLaunch()); err == nil { |
| 109 | t.Fatal("a backward step was granted") |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | // Task 7, the committed wake: at landing/committed a fresh selection's spawn |
| 114 | // is granted only with the ordinary launch identity the committed input |
| 115 | // check accepts, against a committed record of this move. Without the check |
| 116 | // (production until the claim binds one) it is refused. |
| 117 | func TestMovesClaimPolicyCommittedSpawn(t *testing.T) { |
| 118 | ctx := t.Context() |
| 119 | committed := mpRecord() |
| 120 | committed.Move.State, committed.Move.LandingPhase = provision.MoveCommitted, "" |
| 121 | reader := &mpReader{rec: committed} |
| 122 | checked := 0 |
| 123 | ordinary := hostwrite.Identity{Config: "sha256:env", Release: "sha256:bytes"} |
| 124 | inputs := func(_ context.Context, c *hostclaim.Claim, s hostbox.Selection, id hostwrite.Identity) error { |
| 125 | checked++ |
| 126 | if id != ordinary { |
| 127 | return errors.New("not the launch") |
| 128 | } |
| 129 | return nil |
| 130 | } |
| 131 | policy := hostbox.Policy{Moves: MovesClaimPolicyWithInputs(reader.read, inputs), Held: func(context.Context, string) (string, error) { return "", nil }} |
| 132 | c := mpClaim(hostclaim.KindLanding, hostclaim.PhaseLandingCommitted) |
| 133 | s := mpSelection(c, "spawn") |
| 134 | if err := policy.Check(ctx, c, s, ordinary); err != nil || checked != 1 { |
| 135 | t.Fatalf("the committed wake's spawn: %v checked=%d", err, checked) |
| 136 | } |
| 137 | if err := policy.Check(ctx, c, s, hostwrite.Identity{Config: "x", Release: "y"}); err == nil { |
| 138 | t.Fatal("another launch was granted") |
| 139 | } |
| 140 | for _, action := range []string{"held-config", "held-release", "restore", "install"} { |
| 141 | if err := policy.Check(ctx, c, mpSelection(c, action), ordinary); err == nil { |
| 142 | t.Fatalf("committed/%s was granted", action) |
| 143 | } |
| 144 | } |
| 145 | reader.rec = mpProbingRecord() |
| 146 | if err := policy.Check(ctx, c, s, ordinary); err == nil { |
| 147 | t.Fatal("a committed spawn over a record that is not committed was granted") |
| 148 | } |
| 149 | reader.rec = committed |
| 150 | unchecked := hostbox.Policy{Moves: MovesClaimPolicy(reader.read), Held: func(context.Context, string) (string, error) { return "", nil }} |
| 151 | if err := unchecked.Check(ctx, c, s, ordinary); err == nil { |
| 152 | t.Fatal("a committed spawn without an input check was granted") |
| 153 | } |
| 154 | } |
| 155 | |