carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostbox
2
3import (
4 "context"
5 "encoding/hex"
6 "reflect"
7 "time"
8)
9
10// Parking selectors carry identities only. Root derives all paths, receipts,
11// sizes and deadlines independently.
12type ServingArtifactIdentity struct {
13 Host, Box, Region, Incarnation, Location, Generation string
14 Stamp, Session uint64
15}
16
17type ServingArtifactDirectory struct {
18 Device, Inode, Mount uint64
19}
20
21type ServingLegacyPredecessor struct {
22 Session uint64
23 Generation string
24 Empty bool
25}
26
27type ServingParkRequest struct {
28 Successor ServingArtifactIdentity
29 Predecessor ServingArtifactIdentity
30 Legacy ServingLegacyPredecessor
31 Retirement string
32}
33
34type ServingParkResult struct {
35 TagsFrom uint64
36 ID string
37 Legacy bool
38 Predecessor ServingArtifactIdentity
39 OriginalSuccessor ServingArtifactIdentity
40 Successor ServingArtifactIdentity
41 Directory ServingArtifactDirectory
42}
43
44// Keep this tuple frozen when the public wire structs grow.
45func journalServingParkV4(p *ServingParkRequest) any {
46 if p == nil {
47 return nil
48 }
49 identity := func(s ServingArtifactIdentity) any {
50 return []any{s.Host, s.Box, s.Region, s.Incarnation, s.Location, s.Generation, s.Stamp, s.Session}
51 }
52 return []any{identity(p.Successor), identity(p.Predecessor), p.Legacy.Session, p.Legacy.Generation, p.Legacy.Empty, p.Retirement}
53}
54
55func ServingParkOperation(op string) bool {
56 return op == "serving-park" || op == "serving-park-legacy" || op == "serving-park-complete" || op == "serving-park-pressure"
57}
58
59func validateServingParkRequest(q JournalRequest) error {
60 if q.V != 4 || q.ServingPark == nil || !ServingParkOperation(q.Op) {
61 return ErrTypedJournal
62 }
63 // Reject every field belonging to a different journal operation, including
64 // fields omitted from v4's hash. Only these selectors are remotely writable.
65 canonical := JournalRequest{V: q.V, ID: q.ID, Op: q.Op, Nonce: q.Nonce, IssuedAt: q.IssuedAt, ServingPark: q.ServingPark}
66 if !reflect.DeepEqual(q, canonical) {
67 return ErrTypedJournal
68 }
69 nonce, err := hex.DecodeString(q.Nonce)
70 if err != nil || len(nonce) != 16 || q.IssuedAt <= 0 {
71 return ErrTypedJournal
72 }
73 if q.ID != journalRequestID(q) {
74 return ErrChanged
75 }
76 p := *q.ServingPark
77 s := p.Successor
78 if s.Host == "" || s.Box == "" || s.Region == "" || s.Incarnation == "" || s.Location == "" || s.Generation == "" || s.Stamp == 0 || s.Session != s.Stamp {
79 return ErrTypedJournal
80 }
81 switch q.Op {
82 case "serving-park":
83 if p.Legacy != (ServingLegacyPredecessor{}) || p.Retirement != "" {
84 return ErrTypedJournal
85 }
86 case "serving-park-legacy":
87 if p.Predecessor != (ServingArtifactIdentity{}) || p.Retirement != "" || (p.Legacy.Session == 0) != (p.Legacy.Generation == "") || p.Legacy.Session >= s.Session || (p.Legacy.Empty && p.Legacy.Session != 0) {
88 return ErrTypedJournal
89 }
90 case "serving-park-complete":
91 id, err := hex.DecodeString(p.Retirement)
92 if err != nil || len(id) != 32 || p.Predecessor != (ServingArtifactIdentity{}) || p.Legacy != (ServingLegacyPredecessor{}) {
93 return ErrTypedJournal
94 }
95 case "serving-park-pressure":
96 // Pressure also needs a preadmission observed-claim selector. It remains
97 // unavailable until the independent sizing and relief mechanism exists.
98 return ErrUnsupported
99 }
100 return nil
101}
102
103type ServingParkClient struct{ Call JournalCall }
104
105func (c ServingParkClient) request(ctx context.Context, op string, p ServingParkRequest) (ServingParkResult, error) {
106 if c.Call == nil {
107 return ServingParkResult{}, ErrUnsupported
108 }
109 q, err := journalPrepare(JournalRequest{Op: op, ServingPark: &p}, time.Now())
110 if err != nil {
111 return ServingParkResult{}, err
112 }
113 out, err := journalSend(ctx, c.Call, q, false)
114 if err != nil {
115 return ServingParkResult{}, err
116 }
117 if err = servingParkResponse(q, out); err != nil {
118 return ServingParkResult{}, err
119 }
120 return *out.ServingPark, nil
121}
122
123func (c ServingParkClient) Park(ctx context.Context, successor, predecessor ServingArtifactIdentity) (ServingParkResult, error) {
124 return c.request(ctx, "serving-park", ServingParkRequest{Successor: successor, Predecessor: predecessor})
125}
126
127func (c ServingParkClient) ParkLegacy(ctx context.Context, successor ServingArtifactIdentity, predecessor ServingLegacyPredecessor) (ServingParkResult, error) {
128 return c.request(ctx, "serving-park-legacy", ServingParkRequest{Successor: successor, Legacy: predecessor})
129}
130
131func (c ServingParkClient) Complete(ctx context.Context, id string, successor ServingArtifactIdentity) error {
132 _, err := c.request(ctx, "serving-park-complete", ServingParkRequest{Successor: successor, Retirement: id})
133 return err
134}
135
136func (c ServingParkClient) ReplayUncertain(ctx context.Context, uncertain *JournalUncertainError) (JournalResponse, error) {
137 if c.Call == nil || uncertain == nil || validateServingParkRequest(uncertain.Request) != nil {
138 return JournalResponse{}, ErrUnsupported
139 }
140 out, err := journalSend(ctx, c.Call, uncertain.Request, true)
141 if err == nil {
142 err = servingParkResponse(uncertain.Request, out)
143 }
144 return out, err
145}
146
147func servingParkResponse(q JournalRequest, out JournalResponse) error {
148 p := q.ServingPark
149 if p == nil || out.ServingPark == nil || out.ServingPark.Successor != p.Successor || out.ServingPark.ID == "" || (p.Retirement != "" && out.ServingPark.ID != p.Retirement) {
150 return &JournalUncertainError{Request: q, Cause: ErrJournalUncertain}
151 }
152 return nil
153}
154