carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostops
2
3import (
4 "fmt"
5 "sort"
6
7 "github.com/carlosframework/platform/internal/provision"
8 "github.com/carlosframework/platform/internal/registry"
9)
10
11// Claim activation and seeded instances (seeded-instances spec §3.4, §5.6;
12// merge review S2).
13//
14// A claim console refuses every import door, runs no seed sweep and
15// finishes no seed, and a claim-armed box arms neither the swap nor the
16// rollback. So a seed that is unfinished when claims come on is stranded:
17// an import stays gated with nothing able to commit it, put its old route
18// back or clean it up. Activation therefore refuses while any unfinished
19// seed state exists, and the claim's own seed algorithm (#250) can lift
20// this once it is built.
21//
22// Publish makes the check, under the collecting manifest, which is the
23// record-writer freeze. Three things count, and each is read from evidence
24// activation already authenticates:
25//
26// - an unfinished seed on a record, from the census's own record read
27// (provision.SeedSource.Unfinished): every seed but a placed --from
28// copy and a committed import;
29// - an import journal row on a box;
30// - a gated route on a box.
31//
32// The last two are box-local: a committed import's journal row and gate
33// stay until the box's next pass clears them, and a rollback's journal row
34// outlives its record. So each box says them in its signed census report
35// (CensusReport.Seeds). A report that does not carry them is not read as
36// "none": activation refuses, as it does for a report that is incomplete.
37
38// CensusSeeds is a box's own seed state at its census: the hosts its import
39// journal holds a row for, in any state, and the hosts whose route carries
40// an import gate, open or closed. Both sorted. Empty lists are a box that
41// has none; a nil *CensusSeeds on the report is a box that did not say.
42type CensusSeeds struct {
43 Journaled []string `json:"journaled"`
44 Gated []string `json:"gated"`
45}
46
47// SeedStateReader is what a box's census collector reads its seed state
48// from: the box's registry. Both reads are served by a registry opened
49// through root's registry child as well as one opened directly.
50type SeedStateReader interface {
51 List() ([]registry.Route, error)
52 ImportJournalHosts() ([]string, error)
53}
54
55// CollectCensusSeeds reads a box's seed state for its census report. An
56// error is a box that cannot say: the collector then leaves
57// CensusReport.Seeds nil, and activation refuses.
58func CollectCensusSeeds(r SeedStateReader) (*CensusSeeds, error) {
59 journaled, err := r.ImportJournalHosts()
60 if err != nil {
61 return nil, err
62 }
63 routes, err := r.List()
64 if err != nil {
65 return nil, err
66 }
67 out := &CensusSeeds{Journaled: append([]string{}, journaled...), Gated: []string{}}
68 for _, rt := range routes {
69 if rt.ImportGate != "" {
70 out.Gated = append(out.Gated, rt.Host)
71 }
72 }
73 sort.Strings(out.Journaled)
74 sort.Strings(out.Gated)
75 return out, nil
76}
77
78// SeedActivationError is an activation refused over seed state. It is
79// ErrPending when a seed is unfinished (finish or abandon it, then activate
80// again) and ErrIdentity when a box's report carries no seed state (the
81// evidence is incomplete, as for any other incomplete report).
82//
83// DRAFT COPY: the four sentences below are operator-facing and have not
84// been through copy review. They are listed, with this site, in the merge
85// report's review-fixes section.
86type SeedActivationError struct {
87 base error
88 text string
89}
90
91func (e *SeedActivationError) Error() string { return e.base.Error() + ": " + e.text }
92func (e *SeedActivationError) Unwrap() error { return e.base }
93
94func seedEvidenceMissing(box string) error {
95 return &SeedActivationError{ErrIdentity, fmt.Sprintf("box %s reported no seed state, so an import in flight there cannot be ruled out", box)}
96}
97
98func seedJournaled(box, host string) error {
99 return &SeedActivationError{ErrPending, fmt.Sprintf("box %s still holds an import journal row for %s", box, host)}
100}
101
102func seedGated(box, host string) error {
103 return &SeedActivationError{ErrPending, fmt.Sprintf("box %s still gates %s for an import", box, host)}
104}
105
106func seedUnfinished(host string, seed *provision.SeedSource) error {
107 return &SeedActivationError{ErrPending, fmt.Sprintf("%s has an unfinished seed (%s, %s)", host, seed.Kind, seed.State)}
108}
109
110// activationSeedRefusal is Publish's seed check over one census: nil when
111// every box reported its seed state and no box and no record holds any
112// that is unfinished. Boxes and hosts are judged in name order, so the
113// answer for one census is always the same one.
114func activationSeedRefusal(c Census) error {
115 boxes := make([]string, 0, len(c.Reports))
116 for box := range c.Reports {
117 boxes = append(boxes, box)
118 }
119 sort.Strings(boxes)
120 // Evidence first: with any box unknown, nothing else can clear it.
121 for _, box := range boxes {
122 if c.Reports[box].Seeds == nil {
123 return seedEvidenceMissing(box)
124 }
125 }
126 for _, box := range boxes {
127 seeds := c.Reports[box].Seeds
128 for _, host := range sortedHosts(seeds.Journaled) {
129 return seedJournaled(box, host)
130 }
131 for _, host := range sortedHosts(seeds.Gated) {
132 return seedGated(box, host)
133 }
134 }
135 records := append([]Record(nil), c.Records...)
136 sort.Slice(records, func(i, j int) bool { return records[i].Holder.Host < records[j].Holder.Host })
137 for _, r := range records {
138 if r.Holder.Family != "instance" {
139 continue
140 }
141 var rec provision.Record
142 if err := decode(r.Body, &rec); err != nil {
143 return ErrIdentity
144 }
145 if rec.Seed.Unfinished() {
146 return seedUnfinished(r.Holder.Host, rec.Seed)
147 }
148 }
149 return nil
150}
151
152func sortedHosts(hosts []string) []string {
153 out := append([]string(nil), hosts...)
154 sort.Strings(out)
155 return out
156}
157