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 "context"
5 "log"
6 "net/http"
7 "os"
8 "path/filepath"
9 "time"
10
11 "github.com/carlosframework/platform/internal/hostwrite"
12 "github.com/carlosframework/platform/internal/instancectl"
13 "github.com/carlosframework/platform/internal/moves"
14 "github.com/carlosframework/platform/internal/registry"
15 "github.com/carlosframework/platform/internal/release"
16 "github.com/carlosframework/platform/internal/rootparticipation"
17)
18
19func RunInstanceControl(args []string) {
20 ctx, cancel := context.WithCancel(context.Background())
21 defer cancel()
22 if err := registerProductionLaunchVerifier(); err != nil {
23 log.Fatal(err)
24 }
25 if hostwrite.Enabled(os.Getenv) {
26 if err := claimRecoveryReady(rootparticipation.OpenForUpdate); err != nil {
27 log.Print(&ClaimDependencyError{Contracts: []string{"census", "registered verifier", "flag must stay off until Part 3 (G)"}})
28 return
29 }
30 }
31 ctx, err := claimEntryContext(ctx, os.Getenv, openDeployStore)
32 if err != nil {
33 log.Fatal(err)
34 }
35 if !hostwrite.Enabled(claimEntryEnv(ctx)) {
36 instancectl.RunWithRegistry(args, instanceRegistryHostApp(nil))
37 return
38 }
39 reg, err := openServiceRegistry(filepath.Join(dataDir(), "registry.db"))
40 if err != nil {
41 log.Fatal(err)
42 }
43 defer reg.Close()
44 ctx = withClaimRuntimeResources(ctx, ClaimRuntimeResources{Mode: "instance-control", Registry: reg, DataDir: dataDir(), OptDir: release.OptDir(), UnitsDir: "/etc/systemd/system"})
45 ctx, err = ensureProductionClaimAssembly(ctx)
46 if err != nil {
47 log.Fatal(err)
48 }
49 entry, _ := ctx.Value(claimEntryKey{}).(claimEntry)
50 defer func() {
51 cleanup, cancel := context.WithTimeout(context.Background(), 30*time.Second)
52 defer cancel()
53 if err := entry.Assembly.Close(cleanup); err != nil {
54 log.Printf("instance-control: host claims shutdown: %v", err)
55 }
56 }()
57 startup, finishStartup := context.WithTimeout(ctx, 2*time.Minute)
58 defer finishStartup()
59 handler, err := NewProductionRootClaimStop(startup)
60 if err != nil {
61 log.Fatal(err)
62 }
63 execution := handler.(ClaimRootHTTP).executionRuntime
64 defer func() {
65 cleanup, cancel := context.WithTimeout(context.Background(), 30*time.Second)
66 defer cancel()
67 if err := execution.Close(cleanup); err != nil {
68 log.Printf("instance-control: host claims shutdown: %v", err)
69 }
70 }()
71 handler, err = WithProductionClaimLaunch(startup, handler)
72 if err != nil {
73 log.Fatal(err)
74 }
75 // moves Task 7b: root's held release, only with CARLOS_MOVES_PHASE1 on.
76 handler, err = WithProductionMovesHeldRelease(startup, handler, reg, claimEntryEnv(ctx))
77 if err != nil {
78 log.Fatal(err)
79 }
80 if err := activateClaimRoot(ctx, startup, handler); err != nil {
81 log.Fatal(err)
82 }
83 finishStartup()
84 fenceDone := make(chan struct{})
85 go func() { defer close(fenceDone); runHostFencePass(ctx) }()
86 defer func() { cancel(); <-fenceDone }()
87 instancectl.RunWithClaim(args, handler, heldReleaseRouteHandler(handler, claimEntryEnv(ctx)), rootInstallationHostApp(entry.Assembly.Guard, entry.Assembly.Claims))
88}
89
90func activateClaimRoot(ctx, startup context.Context, handler http.Handler) error {
91 recovery, ok := handler.(interface{ RecoverPending(context.Context) error })
92 if !ok {
93 return ErrClaimStopUnavailable
94 }
95 if err := recovery.RecoverPending(startup); err != nil {
96 log.Print(err)
97 }
98 if err := claimActivationReady(startup, rootparticipation.OpenForUpdate); err != nil {
99 return err
100 }
101 lifecycle, ok := handler.(interface{ Start(context.Context) })
102 if !ok {
103 return ErrClaimStopUnavailable
104 }
105 lifecycle.Start(ctx)
106 return nil
107}
108
109// heldReleaseRouteHandler is the handler instancectl registers for the moves
110// held-release route: the claim root (which answers the path itself,
111// refusing while no held release is wired) only with both CARLOS_HOST_CLAIM
112// and CARLOS_MOVES_PHASE1 on; otherwise nil, so the route is not registered
113// and main's 404 stands (whole-branch fixes, item 6).
114func heldReleaseRouteHandler(handler http.Handler, getenv func(string) string) http.Handler {
115 if handler == nil || getenv == nil || !hostwrite.Enabled(getenv) || !moves.Enabled(getenv) {
116 return nil
117 }
118 return handler
119}
120
121func instanceRegistryHostApp(shared *registry.Registry) func(string) (string, string, error) {
122 return func(host string) (string, string, error) {
123 reg := shared
124 if reg == nil {
125 var err error
126 reg, err = openServiceRegistryReadOnly(filepath.Join(dataDir(), "registry.db"))
127 if err != nil {
128 return "", "", err
129 }
130 defer reg.Close()
131 }
132 row, err := reg.Get(host)
133 if err != nil {
134 return "", "", err
135 }
136 if row == nil {
137 return "", "", os.ErrNotExist
138 }
139 return row.Account, row.App, nil
140 }
141}
142
143func claimActivationReady(ctx context.Context, open func() (rootparticipation.Ledger, bool, error)) error {
144 l, required, err := open()
145 if l.State != nil {
146 defer l.State.Close()
147 }
148 if err != nil {
149 return err
150 }
151 if !required {
152 return rootparticipation.ErrUnavailable
153 }
154 ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
155 defer cancel()
156 return l.WithLaunchExclusion(ctx, l.ActivationReady)
157}
158
159func claimRecoveryReady(open func() (rootparticipation.Ledger, bool, error)) error {
160 l, required, err := open()
161 if l.State != nil {
162 defer l.State.Close()
163 }
164 if err != nil {
165 return err
166 }
167 if !required {
168 return rootparticipation.ErrUnavailable
169 }
170 _, err = l.RecoveryCensus()
171 return err
172}
173