carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostresolve
2
3import (
4 "encoding/json"
5 "fmt"
6 "reflect"
7 "slices"
8 "testing"
9
10 "github.com/carlosframework/platform/internal/hostclaim"
11 "github.com/carlosframework/platform/internal/hostfloor"
12 "github.com/carlosframework/platform/internal/hostquarantine"
13 "github.com/carlosframework/platform/internal/status"
14)
15
16func TestReadmitClearsEveryCoveredHostRevision(t *testing.T) {
17 for _, fault := range []string{"none", "uncovered-revision", "missing-loss-reference", "future-revision"} {
18 t.Run(fault, func(t *testing.T) {
19 ctx := t.Context()
20 s, store, now, first := resolveFixture(t)
21 hosts := []string{first, "zzz.bab.oncarlos.com"}
22 other, err := hostclaim.Read(ctx, store, hosts[1])
23 if err != nil {
24 t.Fatal(err)
25 }
26 n := 0
27 _, err = hostclaim.Update(ctx, store, other, hostclaim.Adopt(hostclaim.NewHolder{Family: "instance", Key: "apps/bab/jam/instances/" + hosts[1] + ".json", Account: "bab", App: "jam", Host: hosts[1], Region: "ie", RecordRegion: "ie"}, "live", nil, []string{"dead"}, "discovery", func() string { n++; return fmt.Sprintf("other-%d", n) }))
28 if err != nil {
29 t.Fatal(err)
30 }
31 var losses []hostclaim.LossRecord
32 for _, host := range hosts {
33 in := submitted(t, s, now, host)
34 if _, err := s.Declare(ctx, "operator", host, in); err != nil {
35 t.Fatal(err)
36 }
37 loss, err := hostclaim.ReadLossRecord(ctx, store, s.Directory.PublicKey, in.Confirmation)
38 if err != nil {
39 t.Fatal(err)
40 }
41 losses = append(losses, loss.Record)
42 }
43 prepareReadmission(t, s, *now, losses[1])
44 key, err := hostquarantine.RetirementKey("dead", losses[1].Enrollment, 2)
45 if err != nil {
46 t.Fatal(err)
47 }
48 raw, etag, err := store.Get(ctx, key)
49 if err != nil {
50 t.Fatal(err)
51 }
52 var receipt hostquarantine.RetirementCompletion
53 if err := json.Unmarshal(raw, &receipt); err != nil {
54 t.Fatal(err)
55 }
56 receipt.Entries = append(receipt.Entries, hostquarantine.RetirementEntry{Host: first, Incarnation: losses[0].HolderIncarnation, Stamp: losses[0].HolderStamp, RetirementID: "earlier-retirement", JournalSHA256: receipt.Entries[0].JournalSHA256})
57 raw, err = json.Marshal(receipt)
58 if err != nil {
59 t.Fatal(err)
60 }
61 if _, err = store.Put(ctx, key, raw, etag); err != nil {
62 t.Fatal(err)
63 }
64 digest := hostfloor.Digest(raw)
65 raw, etag, err = store.Get(ctx, "status/boxes/dead.json")
66 if err != nil {
67 t.Fatal(err)
68 }
69 var report status.Box
70 if err := json.Unmarshal(raw, &report); err != nil {
71 t.Fatal(err)
72 }
73 report.Metrics.RetirementReceiptSHA256 = digest
74 raw, err = json.Marshal(report)
75 if err != nil {
76 t.Fatal(err)
77 }
78 if _, err = store.Put(ctx, "status/boxes/dead.json", raw, etag); err != nil {
79 t.Fatal(err)
80 }
81 if fault != "none" {
82 other, err = hostclaim.Read(ctx, store, hosts[1])
83 if err != nil {
84 t.Fatal(err)
85 }
86 _, err = hostclaim.Update(ctx, store, other, func(c *hostclaim.Claim) error {
87 switch fault {
88 case "uncovered-revision":
89 c.LostRevisions["dead"] = 1
90 case "missing-loss-reference":
91 c.LossRefs = nil
92 case "future-revision":
93 c.LostRevisions["dead"] = 3
94 }
95 return nil
96 })
97 if err != nil {
98 t.Fatal(err)
99 }
100 }
101 before := make([]hostclaim.Observed, len(hosts))
102 for i, host := range hosts {
103 before[i], err = hostclaim.Read(ctx, store, host)
104 if err != nil {
105 t.Fatal(err)
106 }
107 }
108 roster, err := s.Directory.Read(ctx)
109 if err != nil {
110 t.Fatal(err)
111 }
112 box := roster.Roster.Boxes["dead"]
113 if box.QuarantineRevision != 2 {
114 t.Fatal("expected two retained revisions", box)
115 }
116 err = s.Readmit(ctx, "dead", box.Enrollment, box.QuarantineRevision, box.LossIDs)
117 if (err == nil) != (fault == "none") {
118 t.Fatalf("Readmit = %v, fault = %s", err, fault)
119 }
120 for i, host := range hosts {
121 after, err := hostclaim.Read(ctx, store, host)
122 if err != nil {
123 t.Fatal(err)
124 }
125 if fault != "none" {
126 if after.ETag != before[i].ETag {
127 t.Fatal("invalid inventory changed a claim", host)
128 }
129 continue
130 }
131 if slices.Contains(after.Claim.Lost, "dead") || after.Claim.LostRevisions["dead"] != 0 {
132 t.Errorf("readmission stranded %s at revision %d", host, after.Claim.LostRevisions["dead"])
133 }
134 if !reflect.DeepEqual(after.Claim.LossRefs, before[i].Claim.LossRefs) {
135 t.Fatal("retained loss references changed", host)
136 }
137 }
138 after, err := s.Directory.Read(ctx)
139 if err != nil {
140 t.Fatal(err)
141 }
142 if fault != "none" && after.ETag != roster.ETag {
143 t.Fatal("invalid inventory cleared global quarantine")
144 }
145 if fault == "none" && after.Roster.Boxes["dead"].State != "active" {
146 t.Fatal("box was not readmitted")
147 }
148 })
149 }
150}
151