carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build unix
2
3package hostguard
4
5import (
6 "bytes"
7 "context"
8 "errors"
9 "testing"
10 "time"
11
12 "github.com/carlosframework/platform/internal/hostclaim"
13)
14
15// B3 (roster binding): an obsolete cold handover barrier is settled without
16// a claim session advance (the live successor admission still holds the
17// claim), under the host lock, only for a prior with a completed receipt.
18// Settlement undoes BeginColdHandover (the deny slot back to the prior's
19// completed fence), retires the successor's stamp so it is never installed,
20// runs record after the guard write, and is idempotent.
21func TestSettleObsoleteColdHandover(t *testing.T) {
22 f, prior, captured := fenceFixture(t)
23 f.obs.Claim.Holder.Account, f.obs.Claim.Holder.App = "bab", "console"
24 store := &rootTestStore{dir: t.TempDir()}
25 f.g.Root = NewRootAuthority(store)
26 in := RootInstallation{Host: prior.Host, Incarnation: captured.Incarnation, Stamp: captured.Stamp, Account: "bab", App: "console", Record: "record", Box: prior.Box, Enrollment: prior.Enrollment, Migration: 1}
27 if err := f.g.EstablishRoot(t.Context(), in, allow); err != nil {
28 t.Fatal(err)
29 }
30 f.obs.Claim.Op.Kind = hostclaim.KindMoving
31 if _, err := f.g.BeginFence(t.Context(), prior); err != nil {
32 t.Fatal(err)
33 }
34 if err := f.g.CompleteFence(t.Context(), prior, []byte("receipt"), time.Now(), func(context.Context, FenceTarget, []byte, time.Time) error { return nil }); err != nil {
35 t.Fatal(err)
36 }
37 barrier := prior
38 barrier.FenceID, barrier.Stamp, barrier.Session, barrier.DirectoryRevision = "barrier", 17, 17, "obsolete-binding"
39 f.obs.Claim.Holder.Stamp, f.obs.Claim.Holder.Session = 17, 17
40 if err := f.g.BeginColdHandover(t.Context(), prior, barrier, allow); err != nil {
41 t.Fatal(err)
42 }
43 if err := f.g.AbandonColdHandover(t.Context(), barrier); !errors.Is(err, ErrFenced) {
44 t.Fatal("the session-advance abandonment applies here:", err)
45 }
46 records := 0
47 record := func() error { records++; return nil }
48
49 other := barrier
50 other.FenceID = "other"
51 if err := f.g.SettleObsoleteColdHandover(t.Context(), prior, other, record); !errors.Is(err, ErrFenceTarget) {
52 t.Fatalf("another barrier: %v", err)
53 }
54 unfenced := prior
55 unfenced.FenceID = "never-completed"
56 if err := f.g.SettleObsoleteColdHandover(t.Context(), unfenced, barrier, record); !errors.Is(err, ErrNoFenceReceipt) {
57 t.Fatalf("a prior without a completed receipt: %v", err)
58 }
59 if records != 0 {
60 t.Fatal("a refused settlement recorded")
61 }
62
63 // A failed record leaves the guard settled; the retry only records.
64 failed := errors.New("journal write failed")
65 if err := f.g.SettleObsoleteColdHandover(t.Context(), prior, barrier, func() error { return failed }); !errors.Is(err, failed) {
66 t.Fatal(err)
67 }
68 r, err := f.g.Root.read(prior.Host)
69 if err != nil || r.barrier != nil || r.ActiveFence == nil || *r.ActiveFence != prior || r.ActiveStamp != prior.Stamp || len(r.FenceJournal) != 1 || r.FenceJournal[0].Target != prior {
70 t.Fatalf("after settlement: %+v %v", r, err)
71 }
72 if len(r.rootRetired) != 1 || r.rootRetired[0] != (Captured{Incarnation: barrier.Incarnation, Stamp: barrier.Stamp}) {
73 t.Fatalf("successor stamp not retired: %v", r.rootRetired)
74 }
75 before, _ := store.Read(prior.Host + ".json")
76 if err := f.g.SettleObsoleteColdHandover(t.Context(), prior, barrier, record); err != nil || records != 1 {
77 t.Fatalf("idempotent settlement: %v, records %d", err, records)
78 }
79 if after, _ := store.Read(prior.Host + ".json"); !bytes.Equal(before, after) {
80 t.Fatal("a repeated settlement rewrote the record")
81 }
82
83 // The successor is never installed; the prior stays cleanable.
84 successor := in
85 successor.Stamp = barrier.Stamp
86 if err := f.g.EstablishRoot(t.Context(), successor, allow); !errors.Is(err, ErrRetiredStamp) {
87 t.Fatalf("settled successor installed: %v", err)
88 }
89 if err := f.g.CleanupWith(t.Context(), prior, func(context.Context, FenceReceipt) error { return nil }, func() error { return nil }); err != nil {
90 t.Fatal("prior cleanup after settlement:", err)
91 }
92}
93