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 linux
2
3package host
4
5import (
6 "bytes"
7 "context"
8 "encoding/json"
9 "errors"
10 "net/http"
11 "net/http/httptest"
12 "os"
13 "path/filepath"
14 "syscall"
15 "testing"
16
17 "github.com/carlosframework/platform/internal/hostbox"
18)
19
20func TestR2InventoryNamesForeignArtifact(t *testing.T) {
21 t.Parallel()
22 for _, rootName := range []string{"opt", "units", "data"} {
23 t.Run(rootName, func(t *testing.T) {
24 root, target, reg, base := plan9Fix1RootRetire(t)
25 row, err := reg.Get(target.Host)
26 if err != nil {
27 t.Fatal(err)
28 }
29 row.DBPath = filepath.Join(base, "instances", target.Host, target.Host+".db")
30 if err = reg.Upsert(*row); err != nil {
31 t.Fatal(err)
32 }
33 root.Roots[rootName] = base
34 name := "carlos"
35 if rootName == "units" {
36 name = "carlos-edge.service.d/guard.conf"
37 }
38 if rootName == "data" {
39 name = "instances/another-host/secret"
40 }
41 victim := filepath.Join(base, name)
42 if err = os.MkdirAll(filepath.Dir(victim), 0700); err != nil {
43 t.Fatal(err)
44 }
45 if err = os.WriteFile(victim, []byte("root-only"), 0600); err != nil {
46 t.Fatal(err)
47 }
48 var st syscall.Stat_t
49 if err = syscall.Lstat(victim, &st); err != nil {
50 t.Fatal(err)
51 }
52 in, err := root.Reconcile.Inventory.ReadExactStopInventory(t.Context(), target)
53 if err != nil {
54 t.Fatal(err)
55 }
56 in.Entries = append(in.Entries, hostbox.StopInventoryEntry{Identity: inventoryIdentityFixture(t, base, name), Kind: "path", ArtifactID: "foreign", ManagedRoot: rootName, RelativePath: name, Ownership: row.ClaimOwner, Materialization: "materialized", Device: uint64(st.Dev), Inode: st.Ino, Type: st.Mode & syscall.S_IFMT})
57 // Simulate a durable pre-fix or migrated agent inventory.
58 if err = root.Reconcile.Inventory.WriteStopInventory(t.Context(), in); err != nil {
59 t.Fatal(err)
60 }
61 err = root.retire(t.Context(), target)
62 if !errors.Is(err, hostbox.ErrTypedInventory) {
63 t.Errorf("foreign inventory not refused at retire: %v", err)
64 }
65 if raw, e := os.ReadFile(victim); e != nil || string(raw) != "root-only" {
66 t.Errorf("root moved foreign file: %v", e)
67 }
68
69 j, e := hostbox.OpenLaunchJournal(filepath.Join(base, "service.db"))
70 if e != nil {
71 t.Fatal(e)
72 }
73 defer j.Close()
74 root.Journals, e = hostbox.NewJournalService(j)
75 if e != nil {
76 t.Fatal(e)
77 }
78 root.Reconcile.Box, root.Reconcile.Enrollment = target.Box, target.Enrollment
79 f := newRecoveryFixture(t)
80 c := f.read().Claim
81 root.Stop.Coordinator.Evidence = f.claims
82 in.Host, in.Incarnation, in.InstallationStamp = c.Host, c.Holder.Incarnation, c.Holder.Stamp
83 row.Host, row.ClaimIncarnation, row.ClaimStamp = in.Host, in.Incarnation, in.InstallationStamp
84 row.DBPath = filepath.Join(base, "instances", in.Host, in.Host+".db")
85 in.Entries[0].RelativePath = in.Host
86 if e = reg.Upsert(*row); e != nil {
87 t.Fatal(e)
88 }
89 target.Host, target.Incarnation, target.Stamp = in.Host, in.Incarnation, in.InstallationStamp
90 var q hostbox.JournalRequest
91 _, remote := hostbox.RemoteJournals(func(_ context.Context, request hostbox.JournalRequest) (hostbox.JournalResponse, error) {
92 q = request
93 return hostbox.JournalResponse{V: 1, ID: request.ID}, nil
94 })
95 if e = remote.WriteStopInventory(t.Context(), in); e != nil {
96 t.Fatal(e)
97 }
98 body, _ := json.Marshal(q)
99 response := httptest.NewRecorder()
100 root.serveJournal(response, httptest.NewRequest(http.MethodPost, "/claim/journal", bytes.NewReader(body)), StopPeer{})
101 var out hostbox.JournalResponse
102 _ = json.Unmarshal(response.Body.Bytes(), &out)
103 if response.Code == http.StatusOK && out.Error == "" {
104 t.Error("foreign inventory accepted at write")
105 }
106 if _, e = root.Journals.Inventory.ReadExactStopInventory(t.Context(), target); e == nil {
107 t.Error("foreign inventory became durable")
108 }
109 in.Entries = in.Entries[:1]
110 in.Entries = append(in.Entries, hostbox.StopInventoryEntry{Kind: "path", ManagedRoot: "data", RelativePath: filepath.Join("instances", in.Host, in.Host+".db"), ArtifactID: "state", Ownership: row.ClaimOwner, Materialization: "intended"})
111 if e = remote.WriteStopInventory(t.Context(), in); e != nil {
112 t.Fatal(e)
113 }
114 body, _ = json.Marshal(q)
115 response = httptest.NewRecorder()
116 root.serveJournal(response, httptest.NewRequest(http.MethodPost, "/claim/journal", bytes.NewReader(body)), StopPeer{})
117 out = hostbox.JournalResponse{}
118 if e = json.Unmarshal(response.Body.Bytes(), &out); e != nil || response.Code != http.StatusOK || out.Error != "" {
119 t.Fatalf("own inventory refused: %d %+v %v", response.Code, out, e)
120 }
121 if _, e = root.Journals.Inventory.ReadExactStopInventory(t.Context(), target); e != nil {
122 t.Fatalf("own inventory not durable: %v", e)
123 }
124 })
125 }
126}
127
128func TestPlan9Fix2RetireOwnStateAndRecoverArchivedRoute(t *testing.T) {
129 root, target, reg, base := plan9Fix1RootRetire(t)
130 row, err := reg.Get(target.Host)
131 if err != nil {
132 t.Fatal(err)
133 }
134 state := filepath.Join(base, "instances", target.Host)
135 row.DBPath = filepath.Join(state, target.Host+".db")
136 if err = reg.Upsert(*row); err != nil {
137 t.Fatal(err)
138 }
139 if err = os.MkdirAll(state, 0700); err != nil {
140 t.Fatal(err)
141 }
142 if err = os.WriteFile(row.DBPath, []byte("own-state"), 0600); err != nil {
143 t.Fatal(err)
144 }
145 var st syscall.Stat_t
146 if err = syscall.Lstat(row.DBPath, &st); err != nil {
147 t.Fatal(err)
148 }
149 in, err := root.Reconcile.Inventory.ReadExactStopInventory(t.Context(), target)
150 if err != nil {
151 t.Fatal(err)
152 }
153 in.Entries = append(in.Entries, hostbox.StopInventoryEntry{Identity: inventoryIdentityFixture(t, base, filepath.Join("instances", target.Host, target.Host+".db")), Kind: "path", ArtifactID: "state", ManagedRoot: "data", RelativePath: filepath.Join("instances", target.Host, target.Host+".db"), Ownership: row.ClaimOwner, Materialization: "materialized", Device: uint64(st.Dev), Inode: st.Ino, Type: st.Mode & syscall.S_IFMT})
154 if err = root.Reconcile.Inventory.WriteStopInventory(t.Context(), in); err != nil {
155 t.Fatal(err)
156 }
157 // This fixture deliberately fails only the final remote acknowledgement.
158 first := root.retire(t.Context(), target)
159 if !errors.Is(first, ErrClaimStopIdentity) {
160 t.Fatalf("own-state retirement: %v", first)
161 }
162 if _, err = os.Stat(row.DBPath); !errors.Is(err, os.ErrNotExist) {
163 t.Fatalf("own state not parked: %v", err)
164 }
165 if row, err = reg.Get(target.Host); err != nil || row != nil {
166 t.Fatalf("route not removed: %+v %v", row, err)
167 }
168 // Recovery gets its DB path from root's archive after the live row is gone.
169 if err = root.retire(t.Context(), target); !errors.Is(err, ErrClaimStopIdentity) {
170 t.Fatalf("archive recovery: %v", err)
171 }
172}
173