carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostops
2
3import (
4 "context"
5 "errors"
6 "testing"
7 "time"
8
9 "github.com/carlosframework/platform/internal/hostclaim"
10 "github.com/carlosframework/platform/internal/lease"
11 "github.com/carlosframework/platform/internal/lease/leasetest"
12 "github.com/carlosframework/platform/internal/release"
13)
14
15func TestPlan9B8VersionedRecordWritesCannotReopenSequence(t *testing.T) {
16 r := newResolveFixture(t, false)
17 ctx := context.Background()
18 if got, err := ResolveMoveStart(ctx, r.h, r.moving, moveStartID); err != nil || got != OutcomeNotApplied {
19 t.Fatalf("resolve: %q %v", got, err)
20 }
21 versions := leasetest.NewVersionedBucket()
22 versions.AddBucket("control")
23 old := versions.Put("control", r.raw.recordKey, r.before)
24 guarded, err := NewVersionedBoundary(true, versions, r.raw, r.w, "control", nil, nil)
25 if err != nil {
26 t.Fatal(err)
27 }
28 issued, settle, err := r.h.Runner(r.w.now).Issue(ctx, Effect{ID: "versions", Resource: r.raw.recordKey, Action: "test-versions"})
29 if err != nil {
30 t.Fatal(err)
31 }
32 defer func() { _ = settle("", ErrNotApplied) }()
33 if _, err = guarded.PutIfAbsent(issued, "control", r.raw.recordKey, r.before); !errors.Is(err, ErrContract) {
34 t.Fatalf("PutIfAbsent: %v", err)
35 }
36 if _, _, err = guarded.CopyVersion(issued, "control", r.raw.recordKey, old, "control", r.raw.recordKey, false); !errors.Is(err, ErrContract) {
37 t.Fatalf("CopyVersion: %v", err)
38 }
39 if err = guarded.DeleteVersion(issued, "control", r.raw.recordKey, old); !errors.Is(err, ErrContract) {
40 t.Fatalf("DeleteVersion: %v", err)
41 }
42 retained, err := versions.GetVersion(ctx, "control", r.raw.recordKey, old)
43 if err != nil || string(retained) != string(r.before) {
44 t.Fatalf("old version changed: %v", err)
45 }
46 // Even the private sweep scope cannot delete a live record's version.
47 sweep := context.WithValue(issued, retiredRecordSweepKey{}, r.raw.recordKey)
48 if err = guarded.DeleteVersion(sweep, "control", r.raw.recordKey, old); !errors.Is(err, ErrPending) {
49 t.Fatalf("live sweep: %v", err)
50 }
51}
52
53func TestPlan9B8PurgeSweepRechecksRetiredRecord(t *testing.T) {
54 ctx := context.Background()
55 control := release.NewDirStore(t.TempDir())
56 versions := leasetest.NewVersionedBucket()
57 versions.AddBucket("control")
58 key := "apps/bab/demo/instances/shop.example.com.json"
59 old := versions.Put("control", key, []byte(`{"record_seq":1}`))
60 journal := PurgeJournal{V: 1, Purge: Purge{ID: "p", Phase: "retired"}, AppID: "old", Account: "bab", App: "demo", Hosts: map[string]PurgeHost{}}
61 w := seedPurge(t, ctx, control, time.Now(), journal)
62 if err := PreparePurgeSweep(ctx, control, w, versions, "control", "bab", "demo", "p", "old"); err != nil {
63 t.Fatal(err)
64 }
65 tag, err := control.Put(ctx, key, []byte(`{"record_seq":2}`), "")
66 if err != nil {
67 t.Fatal(err)
68 }
69 if err := DeletePurgeSweep(ctx, control, w, versions, "control", "bab", "demo", "p", "old"); !errors.Is(err, ErrPending) {
70 t.Fatalf("late live record: %v", err)
71 }
72 if _, err := versions.GetVersion(ctx, "control", key, old); err != nil {
73 t.Fatalf("live version removed: %v", err)
74 }
75 if err := release.DeleteIfMatch(ctx, control, key, tag); err != nil {
76 t.Fatal(err)
77 }
78 if err := DeletePurgeSweep(ctx, control, w, versions, "control", "bab", "demo", "p", "old"); err != nil {
79 t.Fatal(err)
80 }
81 if _, err := versions.GetVersion(ctx, "control", key, old); !errors.Is(err, lease.ErrMirrorNotFound) {
82 t.Fatalf("retired version not swept: %v", err)
83 }
84}
85
86func TestPlan9B8FenceRefusesChangedClaim(t *testing.T) {
87 r := newResolveFixture(t, false)
88 ctx := context.Background()
89 if _, err := hostclaim.Update(r.h.Lifecycle(ctx), r.data, r.moving, hostclaim.ReleaseMove(moveStartID, "ie-box")); err != nil {
90 t.Fatal(err)
91 }
92 if got, err := ResolveMoveStart(ctx, r.h, r.moving, moveStartID); got != OutcomeUncertain || !errors.Is(err, hostclaim.ErrConflict) {
93 t.Fatalf("stale fence: %q %v", got, err)
94 }
95 if r.control.fences != 0 || r.effect(t).State != "uncertain" {
96 t.Fatal("stale observation fenced or settled")
97 }
98}
99
100type sweepWithoutResurrection struct {
101 *leasetest.VersionedBucket
102 t *testing.T
103 key string
104}
105
106func (v sweepWithoutResurrection) DeleteVersion(ctx context.Context, bucket, key, id string) error {
107 if err := v.VersionedBucket.DeleteVersion(ctx, bucket, key, id); err != nil {
108 return err
109 }
110 if _, live := v.Current(bucket, v.key); live {
111 v.t.Fatal("sweep resurrected a record body")
112 }
113 return nil
114}
115func TestPlan9B8PurgeRemovesRecordMarkersLast(t *testing.T) {
116 ctx := context.Background()
117 control := release.NewDirStore(t.TempDir())
118 versions := leasetest.NewVersionedBucket()
119 versions.AddBucket("control")
120 key := "apps/bab/demo/instances/shop.example.com.json"
121 versions.Put("control", key, []byte(`{"record_seq":1}`))
122 versions.BareDelete("control", key)
123 versions.Put("control", key, []byte(`{"record_seq":2}`))
124 marker := versions.BareDelete("control", key)
125 if err := retiredRecordVersion(ctx, control, versions, "control", key, marker); !errors.Is(err, ErrPending) {
126 t.Fatalf("marker could expose old body: %v", err)
127 }
128 journal := PurgeJournal{V: 1, Purge: Purge{ID: "p", Phase: "retired"}, AppID: "old", Account: "bab", App: "demo", Hosts: map[string]PurgeHost{}}
129 w := seedPurge(t, ctx, control, time.Now(), journal)
130 guarded := sweepWithoutResurrection{versions, t, key}
131 if err := PreparePurgeSweep(ctx, control, w, guarded, "control", "bab", "demo", "p", "old"); err != nil {
132 t.Fatal(err)
133 }
134 if err := DeletePurgeSweep(ctx, control, w, guarded, "control", "bab", "demo", "p", "old"); err != nil {
135 t.Fatal(err)
136 }
137 all, err := versions.ListVersions(ctx, "control", key)
138 if err != nil || len(all) != 0 {
139 t.Fatalf("retained versions: %+v %v", all, err)
140 }
141}
142