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 "fmt"
6
7 "github.com/carlosframework/platform/internal/hostclaim"
8 "github.com/carlosframework/platform/internal/hostguard"
9 "github.com/carlosframework/platform/internal/registry"
10)
11
12// AdmissionFenceClosed is Reconciler.FenceClosed over this box's host
13// guard (region HA §5.3, R5-3): whether the host's admission is closed
14// for the installation the guard record has active, on the claim
15// observed, read exactly as With reads it (hostguard.Guard.AdmissionFenced).
16// Root writes that fence durably when it begins the source's stop
17// (hostguard.Guard.BeginFence, before any privileged stop work), so it
18// survives an agent restart and a completed stop still reads closed. It
19// is not the activator's in-process fence (FenceAndStopAs), which a
20// restart empties. A record that cannot be read, or a claim for another
21// host or incarnation, is an error, never "open": absence is never
22// evidence, and the Reconciler then keeps every row and mints none. It
23// takes no lock: Install asks it outside the host lock and holdOnly
24// under it, and the record is replaced atomically.
25func AdmissionFenceClosed(g hostguard.Guard) func(context.Context, hostclaim.Observed) (bool, error) {
26 return func(ctx context.Context, o hostclaim.Observed) (bool, error) {
27 if err := ctx.Err(); err != nil {
28 return false, err
29 }
30 captured, err := g.Capture(o.Host)
31 if err != nil {
32 return false, err
33 }
34 return g.AdmissionFenced(o.Host, o.Claim, captured)
35 }
36}
37
38// RegistryHoldRow is Reconciler.HoldRow over this box's registry (R5-3,
39// and the completed relocation's catch-up hold, L18 finding 3):
40// it keeps the host's existing row held for op in "relocating", a status
41// the edge never serves (registry.HoldStatuses), with registry.SetHoldIf
42// expecting the row unheld, the conditional write moves' holdExisting
43// makes. A row already held for op is a replay and is left exactly as it
44// is: nothing is written. A row another op holds is never taken, and
45// neither is one whose hold changed between the read and the write
46// (SetHoldIf matches only an unheld row): both are ErrChanged with
47// nothing written, and the next pass judges afresh. No row is ErrChanged
48// and nothing is minted (ruling 17); a row that cannot be read is its
49// error. Neither is ever "held". The write sets status and hold_op only,
50// so the row's claim ownership stays as it was and the park-local's
51// retirement, which deletes by ownership whatever the hold, still finds
52// it.
53func RegistryHoldRow(reg *registry.Registry) func(context.Context, string, string) error {
54 return func(ctx context.Context, host, op string) error {
55 if reg == nil || host == "" || op == "" {
56 return ErrUnsupported
57 }
58 if err := ctx.Err(); err != nil {
59 return err
60 }
61 row, err := reg.Get(host)
62 if err != nil {
63 return err
64 }
65 switch {
66 case row == nil:
67 return fmt.Errorf("%w: %s has no row to hold for %s", ErrChanged, host, op)
68 case row.HoldOp == op && registry.IsHold(row.Status):
69 return nil
70 case row.HoldOp != "":
71 return fmt.Errorf("%w: %s's row is held by %q, not %s", ErrChanged, host, row.HoldOp, op)
72 }
73 held, err := reg.SetHoldIf(host, "relocating", op, "")
74 if err != nil {
75 return err
76 }
77 if !held {
78 return fmt.Errorf("%w: %s's row changed before it was held for %s", ErrChanged, host, op)
79 }
80 return nil
81 }
82}
83