| index 5eace5b84..a7e70bf89 100644 |
| --- a/internal/sim/engines/witness_test.go |
| +++ b/internal/sim/engines/witness_test.go |
| @@ -67,6 +67,8 @@ var witnesses = []witness{ |
| "R2-2: the engine runs a declaring build whose live manifest cannot be read; the upgrade leaves it NULL; an edge that serves NULL like 0 admits a public request into the engine", wClassifyNullRefused}, |
| {"classify-staged", "classify-staged", "INV-I1+INV-P", |
| "R8-1: an awake engine is repointed to a plain build without a restart, so the declaring build still runs; an upgrade that reads only the live manifest classifies the group 0, under the running engine (INV-I1) and a public request reaches it (INV-P)", wClassifyStaged}, |
| + {"classify-staged", "classify-staged", "INV-I1+INV-P", |
| + "R8-1, app scope: two instances of one app; the declaring build is staged once, by the other instance; this one runs it from that copy, is repointed to a plain build without a restart and upgraded; read per group, or without the staged clause, it classifies 0 under the running engine", wClassifyStagedApp}, |
| {"minted-follow-owner", "minted-follow-owner", "INV-P", |
| "R5-1: an app minted a platform row while public; adoption of a declaring build marks and fences the app; a minted row that is its own group stays 0 and a public request by the minted host reaches the engine", wMintedFollowOwner}, |
| {"grant-marks-group", "grant-marks-group", "INV-P", |
| @@ -586,6 +588,34 @@ func wClassifyStaged(t *testing.T, w *world) { |
| w.check() |
| } |
| |
| +// wClassifyStagedApp (R8-1, app scope): tcp-a stages the declaring build |
| +// first; tcp-b, another instance of the app, adopts the same build (staged |
| +// for the app already, so recorded under tcp-a only), is repointed to a |
| +// plain build without a restart, and the agent upgrades. |
| +func wClassifyStagedApp(t *testing.T, w *world) { |
| + b := w.boxes[0] |
| + if !w.provision(b, tcpA, tcpApp, "10.0.0.7:9101", vEngine) || !w.provision(b, tcpB, tcpApp, "10.0.0.7:9102", vPlain) { |
| + t.Fatalf("witness: provisioning refused: %s", w.boxesState()) |
| + } |
| + if !w.adopt(b, tcpB, vEngine, nil) { |
| + t.Fatalf("witness: adoption of the declaring build refused: %s", w.boxesState()) |
| + } |
| + w.check() |
| + for _, v := range b.Staged[tcpB] { |
| + if v == vEngine { |
| + t.Fatalf("witness: the declaring build is recorded under %s too; the scenario needs it staged for the other instance only", tcpB) |
| + } |
| + } |
| + if !w.repointAwake(b, tcpB, vPlain) { |
| + t.Fatalf("witness: the awake repoint was refused: %s", w.boxesState()) |
| + } |
| + w.check() |
| + w.upgrade(b) |
| + w.check() |
| + w.requestBy(b, tcpB) |
| + w.check() |
| +} |
| + |
| const ( |
| tcpApp = "acct/tcp" |
| tcpA = "tcp-a" |
| @@ -789,6 +819,72 @@ func TestGrantRefusedOnUnresolvedMintedRow(t *testing.T) { |
| } |
| } |
| |
| +// TestRevokeRunsTheBarrier: --grant on a minted row times out while the |
| +// edge's reload fails (1 written, not fenced); a public request is admitted |
| +// and held; --revoke through the minted row also times out and the mark stays |
| +// 1; adoption of a declaring build is refused for want of the fence. Once |
| +// the reload recovers, the fence drains the held request, the declaring |
| +// build starts, and nothing public reaches it. With the reload working, |
| +// --revoke returns once the edge has loaded it, and the mark is still 1. |
| +func TestRevokeRunsTheBarrier(t *testing.T) { |
| + w := newWitness() |
| + b := w.boxes[0] |
| + w.tcpPublic(t, b) |
| + b.ReloadFails = true |
| + if w.grant(b, mintedB) { |
| + t.Fatal("--grant returned loaded while the edge's reload fails") |
| + } |
| + if m := w.groupMark(b, tcpB); m != markInternal { |
| + t.Fatalf("after the timed-out grant the group is %v, want internal", m) |
| + } |
| + held := w.admitPublic(b, tcpB, "/") |
| + if held == nil { |
| + t.Fatalf("the edge (still serving 0) refused a public request: %s", w.boxesState()) |
| + } |
| + held.Held = true |
| + if w.revoke(b, mintedB) { |
| + t.Fatal("--revoke returned loaded while the edge's reload fails") |
| + } |
| + if b.Rows[mintedB].Grant { |
| + t.Fatal("--revoke left the grant recorded") |
| + } |
| + if m := w.groupMark(b, tcpB); m != markInternal { |
| + t.Fatalf("--revoke changed the mark to %v; only --public writes 0", m) |
| + } |
| + if w.adopt(b, tcpB, vEngine, nil) { |
| + t.Fatalf("adoption of a declaring build passed the gate without a fence: %s", w.boxesState()) |
| + } |
| + w.check() |
| + b.ReloadFails = false |
| + if !w.adopt(b, tcpB, vEngine, nil) { |
| + t.Fatalf("adoption refused after the reload recovered: %s", w.boxesState()) |
| + } |
| + w.check() |
| + w.deliver(held) |
| + if held.Reached != 0 { |
| + t.Fatalf("the request admitted before the fence reached process %d", held.Reached) |
| + } |
| + for _, h := range []string{tcpB, mintedB} { |
| + if r := w.admitPublic(b, h, "/"); r != nil { |
| + t.Fatalf("a public request to %s was admitted after the fence", h) |
| + } |
| + } |
| + // with the reload working, revoke waits for the edge and returns loaded |
| + w.grant(b, tcpB) |
| + if !w.revoke(b, tcpB) || b.Fenced < b.Wrote { |
| + t.Fatalf("--revoke with a working reload did not return loaded: %s", w.boxesState()) |
| + } |
| + if m := w.groupMark(b, tcpB); m != markInternal || !w.fenced(b, tcpB) { |
| + t.Fatalf("after --revoke the group is %v (fenced %v), want internal and fenced", m, w.fenced(b, tcpB)) |
| + } |
| + if len(w.violations) > 0 { |
| + t.Fatalf("violations: %v", w.violations) |
| + } |
| + if t.Failed() { |
| + dump(t, w) |
| + } |
| +} |
| + |
| // wPublicHoldsLock: the engine was adopted to a plain build (the mark stays |
| // 1); the operator runs --public; after its checks pass and before it stops |
| // the group, adoption of a declaring build runs; then a public request |