carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1// The host-retry log: where `carlos instances retry <host>` grants ride
2// (zero-downtime spec §8.4, §11; readiness round 2, Task 3).
3//
4// Bucket layout addition:
5//
6// apps/<acct>/<app>/host-retry.json CAS-guarded, schema-1 request log
7//
8// It has the restart-scope log's JSON shape and append (a log of requests,
9// oldest first, each with a random id, at most restartScopeKeep kept), and
10// every entry is a RestartScopeRetry whose Signed is the console's
11// root-signed HostRetryGrant. Grants used to ride the restart-scope log,
12// and that is why they no longer do: a console built before
13// RestartScope.Signed re-marshals that whole log on every restart it
14// appends, which strips the signature from every pending retry in it, and
15// the member's retry silently clears nothing. No released binary reads or
16// writes this key, so no older console can rewrite it, and no older box
17// ever sees a grant.
18//
19// The box reads it on the serial move, as it reads the restart-scope log
20// (internal/host hostRetryPass), and applies every grant still inside its
21// window that it has not handled in this process, an agent restart
22// included: a grant spent or made stale since is a no-op under the failure
23// book's generation rule (rollout.FailureBook.RetryHostAt).
24package release
25
26import (
27 "context"
28 "errors"
29 "fmt"
30 "os"
31)
32
33// HostRetryKey is the bucket key of app's host-retry log, derived from
34// AppPrefix like every other per-app key.
35func HostRetryKey(acct, app string) string { return AppPrefix(acct, app) + "host-retry.json" }
36
37// LoadHostRetries reads app's host-retry log. found=false means the object
38// is absent, the ordinary state of almost every app. A newer schema is an
39// error, as in LoadRestartScopes: a box that cannot be sure what an entry
40// means must do nothing with it.
41func LoadHostRetries(ctx context.Context, s Store, acct, app string) (*ScopedRestartGeneration, bool, error) {
42 key := HostRetryKey(acct, app)
43 raw, _, err := s.Get(ctx, key)
44 if errors.Is(err, os.ErrNotExist) {
45 return &ScopedRestartGeneration{Schema: 1}, false, nil
46 }
47 if err != nil {
48 return nil, false, err
49 }
50 g, err := parseRestartScopes(key, raw)
51 if err != nil {
52 return nil, false, err
53 }
54 return g, true, nil
55}
56
57// claimRawAppendHostRetry records one signed retry of host in app's
58// host-retry log and returns it. signed is the console's root-signed grant
59// (SignHostRetry); an entry without one is refused here, since every box
60// would drop it. The append, its CAS retries and its bound are the
61// restart-scope log's (claimRawAppendRequestLog).
62func claimRawAppendHostRetry(ctx context.Context, s Store, acct, app, host, signed string) (RestartRequest, error) {
63 if !ValidAccount(acct) {
64 return RestartRequest{}, fmt.Errorf("retry: invalid account %q", acct)
65 }
66 if !ValidApp(app) {
67 return RestartRequest{}, fmt.Errorf("retry: invalid app %q", app)
68 }
69 if err := validScopeValue(RestartScopeRetry, host); err != nil {
70 return RestartRequest{}, err
71 }
72 switch {
73 case signed == "":
74 return RestartRequest{}, errors.New("retry: no signed grant; every box would drop it")
75 case len(signed) > MaxRootEnvelopeBytes:
76 return RestartRequest{}, errors.New("retry: the signed grant is too large")
77 }
78 return claimRawAppendRequestLog(ctx, s, HostRetryKey(acct, app),
79 RestartScope{Kind: RestartScopeRetry, Value: host, Signed: signed}, "retry")
80}
81
82// AppendHostRetry is claimRawAppendHostRetry under the app's mutation
83// claim.
84func AppendHostRetry(ctx context.Context, s Store, acct, app, host, signed string) (claimResult0 RestartRequest, claimResult1 error) {
85 claimResult1 = WithAppMutation(ctx, s, acct, app, "release.AppendHostRetry", func(claimContext context.Context) error {
86 claimResult0, claimResult1 = claimRawAppendHostRetry(claimContext, s, acct, app, host, signed)
87 return claimResult1
88 })
89 return
90}
91