carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostexec
2
3import (
4 "bytes"
5 "encoding/json"
6 "errors"
7 "io"
8 "strings"
9 "time"
10
11 "github.com/carlosframework/platform/internal/controlname"
12 "github.com/carlosframework/platform/internal/rootparticipation"
13)
14
15const TTL = 90 * time.Second
16const Renewal = 20 * time.Second
17const Margin = 5 * time.Second
18
19var ErrControllerBusy = errors.New("hostexec: controller already holds lifetime lock")
20var ErrLost = errors.New("hostexec: lease lost")
21var ErrInactive = errors.New("hostexec: no active participation")
22var ErrState = errors.New("hostexec: invalid local state")
23var ErrUnsupported = errors.New("hostexec: suspend-aware clock unavailable")
24
25// Record is the complete remote wire object. It grants no claim authority.
26type Record struct {
27 Op string `json:"op"`
28 Expires time.Time `json:"expires"`
29}
30
31type Binding struct {
32 Box string `json:"box"`
33 Enrollment string `json:"enrollment"`
34 Host string `json:"host"`
35 Incarnation string `json:"incarnation"`
36 Op string `json:"op"`
37 BootID string `json:"boot_id"`
38 ControllerID string `json:"controller_id"`
39}
40
41type ControllerProcess = rootparticipation.ControllerProcess
42
43type State struct {
44 Process ControllerProcess `json:"process,omitzero"`
45 V int `json:"v"`
46 Binding
47 LeaseETag string `json:"lease_etag"`
48 Expires time.Time `json:"expires"`
49 SafeUntil int64 `json:"safe_until_boottime_ns"`
50 State string `json:"state"`
51}
52
53func validID(s string) bool {
54 return s != "" && s != "." && s != ".." && !strings.ContainsAny(s, "/\\\x00\r\n\t ")
55}
56func (b Binding) valid() bool {
57 return controlname.ValidHostShape(b.Host) && validID(b.Box) && validID(b.Enrollment) &&
58 validID(b.Incarnation) && validID(b.Op) && validID(b.BootID) && validID(b.ControllerID)
59}
60func (s State) validate() error {
61 if s.V != 1 || !s.Binding.valid() || s.SafeUntil <= 0 || s.Expires.IsZero() {
62 return ErrState
63 }
64 switch s.State {
65 case "pending", "lost":
66 case "active":
67 if s.LeaseETag == "" {
68 return ErrState
69 }
70 default:
71 return ErrState
72 }
73 return nil
74}
75func decode(b []byte, dst any) error {
76 d := json.NewDecoder(bytes.NewReader(b))
77 d.DisallowUnknownFields()
78 if err := d.Decode(dst); err != nil {
79 return err
80 }
81 var extra any
82 if err := d.Decode(&extra); err != io.EOF {
83 return ErrState
84 }
85 return nil
86}
87func Key(host, box string) (string, error) {
88 if !controlname.ValidHostShape(host) || !validID(box) {
89 return "", ErrState
90 }
91 return "control/hosts/" + host + "/exec/" + box + ".json", nil
92}
93
94// BootNanos includes time spent suspended. Wall time is used only on the wire.
95type Clock interface {
96 Now() (wall time.Time, bootNanos int64, err error)
97 After(time.Duration) <-chan time.Time
98}
99