carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostrelocate
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "slices"
8
9 "github.com/carlosframework/platform/internal/hostclaim"
10 "github.com/carlosframework/platform/internal/hostops"
11 "github.com/carlosframework/platform/internal/release"
12)
13
14// RegionChooser gives the worker its Choose and Eligible from create's
15// chooser (hostops.ExecutorChooser), so a relocation's target comes from
16// the same complete eligible set a create's executor does (spec 1 §2,
17// §5.2). Like ProductionAuthority.SelectExecutor it chooses within claim
18// Plan 5's scope, set on every call: Namespace, and the pool of the host's
19// authoritative record. The placement is Region, the worker's: a
20// relocation never leaves it, and Worker.Step acts only on its own
21// region's operations.
22type RegionChooser struct {
23 Executors hostops.ExecutorChooser
24 // Records is the deployment (control) bucket holding the host's
25 // authoritative record at holder.key.
26 Records release.Store
27 // Namespace is the roster namespace of the box the worker runs on. An
28 // empty one refuses: the chooser would choose nothing.
29 Namespace string
30 Region string
31}
32
33// Choose names a target for obs's host, never one in exclude.
34// ExecutorChooser already leaves out the claim's lost boxes and every box
35// owing a park-local chore for the host; exclude adds the worker's own
36// (the source, the current target, the targets this operation replaced).
37// They are passed as the lost list of a copy of the observation: that list
38// is the only exclusion the chooser reads, and the copy is never written.
39func (r RegionChooser) Choose(ctx context.Context, obs hostclaim.Observed, exclude []string) (hostclaim.Target, error) {
40 x, rec, err := r.scoped(ctx, obs)
41 if err != nil {
42 return hostclaim.Target{}, err
43 }
44 c := *obs.Claim
45 c.Lost = append(slices.Clone(c.Lost), exclude...)
46 obs.Claim = &c
47 sel, err := x.Choose(ctx, obs, rec)
48 if err != nil {
49 return hostclaim.Target{}, err
50 }
51 return hostclaim.Target{Box: sel.Box, Enrollment: sel.Enrollment}, nil
52}
53
54// Eligible refuses box unless it is in the complete eligible set for obs's
55// host now (ExecutorChooser.Permits within the same scope).
56func (r RegionChooser) Eligible(ctx context.Context, obs hostclaim.Observed, box string) error {
57 x, rec, err := r.scoped(ctx, obs)
58 if err != nil {
59 return err
60 }
61 return x.Permits(ctx, obs, rec, box)
62}
63
64// scoped reads the host's authoritative record and returns the chooser
65// scoped to Namespace and the record's pool, with the record placed in
66// Region.
67func (r RegionChooser) scoped(ctx context.Context, obs hostclaim.Observed) (hostops.ExecutorChooser, hostops.Record, error) {
68 if r.Records == nil || r.Namespace == "" || r.Region == "" || obs.Absent || obs.Claim == nil || obs.Claim.Holder == nil {
69 return hostops.ExecutorChooser{}, hostops.Record{}, ErrUnsupported
70 }
71 rec, err := hostops.ReadRecord(ctx, r.Records, obs.Claim.Holder.Key)
72 if err != nil {
73 return hostops.ExecutorChooser{}, hostops.Record{}, err
74 }
75 if rec.Holder.Host != obs.Host || rec.Holder.Family != obs.Claim.Holder.Family {
76 return hostops.ExecutorChooser{}, hostops.Record{}, fmt.Errorf("%w: record %s is not %s's", ErrRefused, obs.Claim.Holder.Key, obs.Host)
77 }
78 var shape struct {
79 Pool string `json:"pool"`
80 }
81 if err := json.Unmarshal(rec.Body, &shape); err != nil {
82 return hostops.ExecutorChooser{}, hostops.Record{}, err
83 }
84 rec.Holder.Region = r.Region
85 x := r.Executors
86 x.Namespace, x.Pool = r.Namespace, shape.Pool
87 return x, rec, nil
88}
89