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 "encoding/base64"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "net/http"
10 "strings"
11 "sync"
12 "time"
13
14 "github.com/carlosframework/platform/internal/hostactors"
15 "github.com/carlosframework/platform/internal/hostenv"
16 "github.com/carlosframework/platform/internal/registry"
17 "github.com/carlosframework/platform/internal/release"
18 "github.com/carlosframework/platform/internal/status"
19)
20
21// A box its region's group launched is referenced by its group reference
22// (census ruling region-ha-2b): hostactors.GroupBoxReference of its account
23// and instance, "g-" and 20 hex digits. Its host.env says so:
24// carlos system host-env write adds CARLOS_BOX_REFERENCE=group. Status
25// labels come from 1,600 values and identity records are never deleted, so
26// as a group replaces its boxes, labels collide and a box whose label
27// another record holds can never publish its own; group references don't
28// collide, and are a pseudonym, so the public status feed shows no
29// instance id. Every other box, fleet boxes included, keeps its label and
30// its status key exactly as before.
31
32// groupReferenced reports whether this box takes its group reference.
33func groupReferenced(getenv func(string) string) bool {
34 return strings.TrimSpace(getenv(hostenv.BoxReference)) == hostenv.GroupReference
35}
36
37// groupIdentity reads this box's account and instance from its signed
38// identity document (IMDS); a seam for tests.
39var groupIdentity = func() (account, instance string, err error) {
40 rec, err := readIdentityRecord(&http.Client{Timeout: 2 * time.Second}, imdsBase, "", time.Now())
41 if err != nil {
42 return "", "", err
43 }
44 doc, err := base64.StdEncoding.DecodeString(rec.IdentityDocument)
45 if err != nil {
46 return "", "", err
47 }
48 var d struct {
49 AccountID string `json:"accountId"`
50 InstanceID string `json:"instanceId"`
51 }
52 if err := json.Unmarshal(doc, &d); err != nil {
53 return "", "", err
54 }
55 if d.AccountID == "" || d.InstanceID == "" {
56 return "", "", errors.New("the identity document names no account or instance")
57 }
58 return d.AccountID, d.InstanceID, nil
59}
60
61var groupRef struct {
62 sync.Mutex
63 ref string
64}
65
66// groupBoxReference is this box's group reference, read once and kept. ""
67// while IMDS has not answered: never a label in its place.
68func groupBoxReference() string {
69 ref, _ := resolveGroupBoxReference()
70 return ref
71}
72
73// resolveGroupBoxReference is groupBoxReference with the reason it has
74// none: the IMDS read's error, kept so the caller can say why. Only a
75// success is kept; a failure is asked again next time.
76func resolveGroupBoxReference() (string, error) {
77 groupRef.Lock()
78 defer groupRef.Unlock()
79 if groupRef.ref != "" {
80 return groupRef.ref, nil
81 }
82 account, instance, err := groupIdentity()
83 if err != nil {
84 return "", err
85 }
86 groupRef.ref = hostactors.GroupBoxReference(account, instance)
87 return groupRef.ref, nil
88}
89
90// errNoGroupReference is a tick of a group box whose reference has not
91// resolved: it publishes nothing.
92var errNoGroupReference = errors.New("this group box's reference could not be read, so it publishes no status report this tick")
93
94// groupStatusTick is statusLoop's tick (census ruling region-ha-2b). A box
95// whose host.env carries the group marker publishes its status report
96// only under its group reference, the key the console derives, resolved
97// afresh on every tick until it resolves (groupBoxReference keeps only a
98// success). Until then it publishes nothing at all: never under
99// status.Label, which may be another box's key, since WriteBox replaces
100// whatever is there. That holds whatever the claim and region HA switches
101// say. Every other box's tick is statusTick's with statusLoop's label,
102// exactly as before.
103func groupStatusTick(ctx context.Context, s release.Store, reg *registry.Registry, p status.Probes, publish bool, label string, getenv func(string) string) error {
104 if groupReferenced(getenv) {
105 ref, err := resolveGroupBoxReference()
106 if err != nil {
107 return fmt.Errorf("%w: %v", errNoGroupReference, err)
108 }
109 label = ref
110 }
111 return statusTick(ctx, s, reg, p, publish, label)
112}
113