carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostactors
2
3import (
4 "context"
5 "github.com/carlosframework/platform/internal/hostclaim"
6 "github.com/carlosframework/platform/internal/hostfloor"
7 "github.com/carlosframework/platform/internal/release"
8 "reflect"
9 "strings"
10 "time"
11)
12
13// Start attaches the reservation in the same claim CAS as the start intent.
14func (d Directory) Start(ctx context.Context, o hostclaim.Observed, id string, tr func(*hostclaim.Claim) error) (hostclaim.Observed, error) {
15 roster, err := d.Read(ctx)
16 if err != nil {
17 return hostclaim.Observed{}, err
18 }
19 h, ok := roster.Roster.Holds[id]
20 if !ok || h.Host != o.Host || h.ExpectedClaimETag != o.ETag || roster.Roster.Publication != "published" || roster.Roster.FloorSHA256 != h.FloorSHA256 {
21 return hostclaim.Observed{}, ErrRoster
22 }
23 floor, err := hostfloor.Read(ctx, d.Store, d.PublicKey, d.Bucket)
24 if err != nil || floor.Digest != h.FloorSHA256 {
25 return hostclaim.Observed{}, ErrRoster
26 }
27 for _, report := range h.Releases {
28 if report.GeneratedAt.IsZero() || time.Since(report.GeneratedAt) > 15*time.Minute || report.GeneratedAt.After(time.Now().Add(5*time.Second)) {
29 return hostclaim.Observed{}, ErrRoster
30 }
31 }
32 // Reservations pin identity, but never preserve an actor's eligibility.
33 for actor, enrollment := range h.Actors {
34 ref, boxActor := strings.CutPrefix(actor, "box:")
35 if !boxActor {
36 id, consoleActor := strings.CutPrefix(actor, "console:")
37 console, exists := roster.Roster.Consoles[id]
38 if !consoleActor || !exists || console.Enrollment != enrollment {
39 return hostclaim.Observed{}, ErrRoster
40 }
41 if console.Compute == nil {
42 continue
43 }
44 ref, enrollment = console.Compute.Box, console.Compute.Enrollment
45 }
46 box, exists := roster.Roster.Boxes[ref]
47 if !exists || box.Enrollment != enrollment || box.State != "active" {
48 return hostclaim.Observed{}, ErrRoster
49 }
50 fenced, err := d.TerminationFence(ctx, roster, ref, time.Now())
51 if err != nil || fenced {
52 return hostclaim.Observed{}, ErrRoster
53 }
54 }
55 return hostclaim.Update(ctx, d.Store, o, func(c *hostclaim.Claim) error {
56 if err := tr(c); err != nil {
57 return err
58 }
59 if c.Op.ID != h.OpID || c.Op.Intent == nil {
60 return ErrRoster
61 }
62 c.Op.Intent["actors_hold"] = id
63 return nil
64 })
65}
66
67// CancelHold invalidates the claim ETag before releasing a never-started hold.
68// If a delayed start won its CAS, the reread leaves its reservation held.
69func (d Directory) CancelHold(ctx context.Context, sg release.Signer, id string) error {
70 o, err := d.Read(ctx)
71 if err != nil {
72 return err
73 }
74 h, ok := o.Roster.Holds[id]
75 if !ok {
76 return nil
77 }
78 claim, err := hostclaim.Read(ctx, d.Store, h.Host)
79 if err != nil {
80 return err
81 }
82 if !claim.Absent && claim.Claim.Op.Intent["actors_hold"] == id {
83 return ErrRoster
84 }
85 if claim.ETag == h.ExpectedClaimETag {
86 _, err = hostclaim.Update(ctx, d.Store, claim, func(*hostclaim.Claim) error { return nil })
87 if err != nil {
88 return err
89 }
90 }
91 // Reread both records after the invalidating CAS. No timeout releases a hold.
92 fresh, err := d.Read(ctx)
93 if err != nil {
94 return err
95 }
96 if !reflect.DeepEqual(fresh.Roster.Holds[id], h) {
97 return ErrRoster
98 }
99 claim, err = hostclaim.Read(ctx, d.Store, h.Host)
100 if err != nil {
101 return err
102 }
103 if !claim.Absent && claim.Claim.Op.Intent["actors_hold"] == id {
104 return ErrRoster
105 }
106 r := Clone(fresh.Roster)
107 delete(r.Holds, id)
108 _, err = d.Replace(ctx, sg, fresh, r)
109 return err
110}
111func (d Directory) CheckHold(ctx context.Context, c *hostclaim.Claim, box, enrollment string) error {
112 if c == nil {
113 return ErrRoster
114 }
115 o, err := d.Read(ctx)
116 if err != nil {
117 return err
118 }
119 h, ok := o.Roster.Holds[c.Op.Intent["actors_hold"]]
120 if !ok || h.Host != c.Host || h.OpID != c.Op.ID || h.Actors["box:"+box] != enrollment {
121 return ErrRoster
122 }
123 return nil
124}
125