carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "crypto/sha256"
6 "encoding/hex"
7 "fmt"
8 "testing"
9
10 "github.com/carlosframework/platform/internal/hostbox"
11 "github.com/carlosframework/platform/internal/hostclaim"
12 "github.com/carlosframework/platform/internal/hostguard"
13 "github.com/carlosframework/platform/internal/provision"
14 "github.com/carlosframework/platform/internal/registry"
15)
16
17func TestPlan14RootTimerInventory(t *testing.T) {
18 for _, attack := range []string{"unrecorded", "intended", "missing", "omitted-timer", "row identity", "owner", "path", "root", "shape"} {
19 t.Run(attack, func(t *testing.T) {
20 t.Setenv("CARLOS_HOST_CLAIM", "1")
21 f := newRecoveryFixture(t)
22 o := f.read()
23 h := o.Claim.Holder
24 plan14PutRecord(t, f.claims, provision.Record{Schema: 1, Host: o.Host, Account: h.Account, App: h.App, Channel: "stable", CreatedAt: f.now})
25 _, etag, _ := f.claims.Get(t.Context(), h.Key)
26 journals, err := hostbox.NewJournalService(f.journal)
27 if err != nil {
28 t.Fatal(err)
29 }
30 reg := restartTestRegistry(t)
31 sum := sha256.Sum256([]byte(fmt.Sprintf("%s\x00%s\x00%d\x00%s", o.Host, h.Incarnation, h.Stamp, "box-1")))
32 owner := registry.ClaimOwnership{ClaimIncarnation: h.Incarnation, ClaimStamp: h.Stamp, ClaimOwner: hex.EncodeToString(sum[:]), ClaimRowID: "route"}
33 row := registry.Route{Host: o.Host, Account: h.Account, App: h.App, DBPath: provision.NestedDBPath(f.guard.DataDir, o.Host), ClaimOwnership: owner}
34 if err = reg.Upsert(row); err != nil {
35 t.Fatal(err)
36 }
37 if err = reg.UpsertTimer(registry.Timer{Host: o.Host, Name: "daily", Kind: "cron", Spec: "0 0 * * *", Path: "/daily", ClaimOwnership: owner}); err != nil {
38 t.Fatal(err)
39 }
40 timer, err := reg.GetTimer(o.Host, "daily")
41 if err != nil {
42 t.Fatal(err)
43 }
44 if !timer.ClaimOwnership.Valid() {
45 t.Fatal("timer fixture has no ownership")
46 }
47 in := hostbox.StopInventory{Complete: true, Host: o.Host, Incarnation: h.Incarnation, InstallationStamp: h.Stamp, Box: "box-1", Enrollment: "en", DirectoryRevision: "1", Entries: []hostbox.StopInventoryEntry{{Kind: "route", ArtifactID: "route", ManagedRoot: "registry", RelativePath: o.Host, RowIdentity: owner.ClaimRowID, Ownership: owner.ClaimOwner, Materialization: "materialized"}}}
48 if err = journals.Inventory.WriteStopInventory(t.Context(), in); err != nil {
49 t.Fatal(err)
50 }
51 g := f.guard
52 g.Root = hostguard.NewRootAuthority(&rollbackDisk{dir: t.TempDir()})
53 if err = g.EstablishRoot(t.Context(), hostguard.RootInstallation{Artifacts: hostguard.InstallationArtifacts{Schema: 1, StateDirectory: true}, Host: o.Host, Incarnation: h.Incarnation, Stamp: h.Stamp, Account: h.Account, App: h.App, Record: h.Key, Box: "box-1", Enrollment: "en", Migration: 1, InventoryRevision: 1}, func(*hostclaim.Claim) error { return nil }); err != nil {
54 t.Fatal(err)
55 }
56 actor := hostbox.Actor{Box: "box-1", Enrollment: "en", Revision: 1, Binding: "1"}
57 root := ClaimRootHTTP{Stop: ClaimStopHTTP{Coordinator: ClaimStopCoordinator{Guard: g, Evidence: f.claims}}, Journals: journals, InventoryActor: func(context.Context) (hostbox.Actor, error) { return actor, nil }, Roots: map[string]string{"data": g.DataDir}, Reconcile: hostbox.ReconcileAdapter{Registry: reg, Census: &hostbox.EmptyInstallationCensus{}}}
58 e := hostbox.StopInventoryEntry{Kind: "timer", ArtifactID: "timer", ManagedRoot: "registry", RelativePath: o.Host + "/daily", RowIdentity: timer.ClaimRowID, Ownership: owner.ClaimOwner, Materialization: "materialized"}
59 switch attack {
60 case "missing", "intended":
61 if err = reg.DeleteTimer(o.Host, "daily"); err != nil {
62 t.Fatal(err)
63 }
64 if attack == "intended" {
65 e.Materialization = "intended"
66 }
67 case "row identity":
68 e.RowIdentity = "other"
69 case "owner":
70 e.Ownership = "other"
71 case "path":
72 e.RelativePath = "other.example/daily"
73 case "root":
74 e.ManagedRoot = "data"
75 case "shape":
76 e.Inode = 1
77 }
78 if attack != "omitted-timer" {
79 in.Entries = append(in.Entries, e)
80 }
81 _, client := hostbox.RemoteJournals(root.commitRootInventory)
82 _, err = client.WriteExpected(t.Context(), in, hostbox.InventoryExpectation{Revision: 1, RecordKey: h.Key, RecordETag: etag, Target: hostguard.FenceTarget{Host: o.Host, Incarnation: h.Incarnation, Stamp: h.Stamp, Session: h.Session, Box: actor.Box, Enrollment: actor.Enrollment, ClaimETag: o.ETag, Op: o.Claim.Op.ID, Phase: o.Claim.Op.Phase}})
83 valid := attack == "intended"
84 if valid && err != nil || !valid && err == nil {
85 t.Fatal(attack, err)
86 }
87 _, revision, _ := journals.Inventory.Snapshot(t.Context(), o.Host, hostguard.Captured{Incarnation: h.Incarnation, Stamp: h.Stamp})
88 want := uint64(1)
89 if valid {
90 want = 2
91 }
92 if valid {
93 views, readErr := g.Root.GenerationInstallations(t.Context())
94 if readErr != nil || len(views) != 1 || views[0].Installation.Artifacts.Timers != "daily" {
95 t.Fatalf("timer obligation not retained: %+v %v", views, readErr)
96 }
97 }
98 if revision != want {
99 t.Fatal(revision, want)
100 }
101 })
102 }
103}
104