carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "bytes"
5 "context"
6 "fmt"
7 "io"
8 "net"
9 "net/http"
10 "time"
11
12 "github.com/carlosframework/platform/internal/activator"
13 "github.com/carlosframework/platform/internal/hostbox"
14 "github.com/carlosframework/platform/internal/registry"
15)
16
17// ClaimRoleRootClient is the agent's client of POST /claim/launch on the
18// root instance-control socket. It implements activator.RootRoleLauncher.
19type ClaimRoleRootClient struct {
20 Socket string
21 // Dial is the dialer; nil dials Socket as a unix socket.
22 Dial func(context.Context, string) (net.Conn, error)
23}
24
25var _ activator.RootRoleLauncher = (*ClaimRoleRootClient)(nil)
26
27func NewClaimRoleRootClient(socket string) *ClaimRoleRootClient {
28 return &ClaimRoleRootClient{Socket: socket}
29}
30
31// claimLaunchDeadline bounds one call: a start includes the unit start and
32// root's checks around it.
33const claimLaunchDeadline = 90 * time.Second
34
35func (c *ClaimRoleRootClient) call(ctx context.Context, q hostbox.RoleLaunchRequest) (hostbox.RoleLaunchResponse, error) {
36 if c == nil || c.Socket == "" {
37 return hostbox.RoleLaunchResponse{}, activator.ErrClaimIsolationUnavailable
38 }
39 body, err := hostbox.EncodeRoleLaunchRequest(q)
40 if err != nil {
41 return hostbox.RoleLaunchResponse{}, err
42 }
43 ctx, cancel := context.WithTimeout(ctx, claimLaunchDeadline)
44 defer cancel()
45 dial := c.Dial
46 if dial == nil {
47 dial = func(ctx context.Context, path string) (net.Conn, error) {
48 return (&net.Dialer{}).DialContext(ctx, "unix", path)
49 }
50 }
51 transport := &http.Transport{DisableKeepAlives: true, DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { return dial(ctx, c.Socket) }}
52 defer transport.CloseIdleConnections()
53 req, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://control/claim/launch", bytes.NewReader(body))
54 if err != nil {
55 return hostbox.RoleLaunchResponse{}, err
56 }
57 req.Header.Set("Content-Type", "application/json")
58 resp, err := (&http.Client{Transport: transport}).Do(req)
59 if err != nil {
60 return hostbox.RoleLaunchResponse{}, fmt.Errorf("claim launch: %w", err)
61 }
62 defer resp.Body.Close()
63 switch resp.StatusCode {
64 case http.StatusOK:
65 return hostbox.DecodeRoleLaunchResponse(resp.Body)
66 case http.StatusBadRequest:
67 return hostbox.RoleLaunchResponse{}, hostbox.ErrRoleLaunchShape
68 case http.StatusForbidden:
69 return hostbox.RoleLaunchResponse{}, ErrClaimStopIdentity
70 case http.StatusConflict:
71 return hostbox.RoleLaunchResponse{}, hostbox.ErrChanged
72 case http.StatusServiceUnavailable, http.StatusNotFound, http.StatusMethodNotAllowed:
73 // An older root has no such route; a root without its providers
74 // refuses the same way.
75 return hostbox.RoleLaunchResponse{}, activator.ErrClaimIsolationUnavailable
76 }
77 _, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 4<<10))
78 return hostbox.RoleLaunchResponse{}, fmt.Errorf("claim launch: root answered %d", resp.StatusCode)
79}
80
81// Capability is the protocol root speaks; it grants nothing.
82func (c *ClaimRoleRootClient) Capability(ctx context.Context) (string, error) {
83 out, err := c.call(ctx, hostbox.RoleLaunchRequest{V: hostbox.RoleLaunchV, Op: hostbox.RoleLaunchCapability})
84 if err != nil {
85 return "", err
86 }
87 return out.Protocol, nil
88}
89
90// Launch asks root to run q's reservation and returns root's grant for
91// exactly that reservation.
92func (c *ClaimRoleRootClient) Launch(ctx context.Context, q hostbox.RoleLaunchRequest) (hostbox.RoleLaunchGrant, error) {
93 if q.Op != hostbox.RoleLaunchStart || q.Reservation == nil {
94 return hostbox.RoleLaunchGrant{}, hostbox.ErrRoleLaunchShape
95 }
96 out, err := c.call(ctx, q)
97 if err != nil {
98 return hostbox.RoleLaunchGrant{}, err
99 }
100 if out.Protocol != hostbox.RoleLaunchProtocol || out.Grant == nil {
101 return hostbox.RoleLaunchGrant{}, hostbox.ErrChanged
102 }
103 if err := out.Grant.Check(*q.Reservation); err != nil {
104 return hostbox.RoleLaunchGrant{}, err
105 }
106 return *out.Grant, nil
107}
108
109// Stop asks root to end q's reservation, or to spend it if root never saw it.
110func (c *ClaimRoleRootClient) Stop(ctx context.Context, q hostbox.RoleLaunchRequest) error {
111 if q.Op != hostbox.RoleLaunchStop || q.Reservation == nil {
112 return hostbox.ErrRoleLaunchShape
113 }
114 _, err := c.call(ctx, q)
115 return err
116}
117
118func (c *ClaimRoleRootClient) KindCapability(ctx context.Context, kind string) error {
119 out, err := c.call(ctx, hostbox.RoleLaunchRequest{V: hostbox.RoleLaunchV, Op: hostbox.RoleLaunchCapability, Kind: kind})
120 if err != nil {
121 return err
122 }
123 if out.Protocol != hostbox.RoleLaunchProtocol {
124 return hostbox.ErrChanged
125 }
126 return nil
127}
128
129func (c *ClaimRoleRootClient) UnitInputs(ctx context.Context, route registry.Route) (*hostbox.UnitLaunchInputs, error) {
130 out, err := c.call(ctx, hostbox.RoleLaunchRequest{V: hostbox.RoleLaunchV, Op: hostbox.RoleLaunchInputs, Route: &route})
131 if err != nil {
132 return nil, err
133 }
134 if out.Protocol != hostbox.RoleLaunchProtocol {
135 return nil, hostbox.ErrChanged
136 }
137 return out.Inputs, nil
138}
139