| 1 | // An instance's environment binding and a seed's store (final review F3). |
| 2 | // A seed marks the store it makes (objstore.Declared.Seed), and its finish |
| 3 | // tears that store down while the mark stands and no other instance names |
| 4 | // the environment (seedfinish.go). An instance that comes to name the |
| 5 | // environment afterwards holds the store from then on, so the binding |
| 6 | // clears the mark under the store's walk lock, the lock the finish holds |
| 7 | // from its last check through the teardown. The lock is this process's: |
| 8 | // one console per deployment is a rollout invariant of the seeds series. |
| 9 | |
| 10 | package console |
| 11 | |
| 12 | import ( |
| 13 | "context" |
| 14 | "log" |
| 15 | "net/http" |
| 16 | |
| 17 | "github.com/carlosframework/platform/internal/objstore" |
| 18 | "github.com/carlosframework/platform/internal/provision" |
| 19 | ) |
| 20 | |
| 21 | // lockEnvironmentUpdate is the update door's binding: when the patch moves |
| 22 | // rec to another environment, it takes that environment's store walk lock |
| 23 | // and moves the store to rec (takeSeedStore). The caller holds unlock |
| 24 | // across its record write. A store that cannot be read or written answers |
| 25 | // main's update retry sentence, and ok is false. |
| 26 | func (s *Server) lockEnvironmentUpdate(ctx context.Context, w http.ResponseWriter, stored, rec provision.Record) (unlock func(), ok bool) { |
| 27 | if rec.Environment == stored.Environment { |
| 28 | return func() {}, true |
| 29 | } |
| 30 | unlock = s.storeWalks.lock(rec.Account, rec.App, rec.Environment) |
| 31 | if err := s.takeSeedStore(ctx, rec); err != nil { |
| 32 | unlock() |
| 33 | log.Printf("console: api instances update %s/%s/%s: environment %q: %v", rec.Account, rec.App, rec.Host, rec.Environment, err) |
| 34 | jsonError(w, http.StatusBadGateway, "could not update the instance — try again") |
| 35 | return nil, false |
| 36 | } |
| 37 | return unlock, true |
| 38 | } |
| 39 | |
| 40 | // bindEnvironmentCreate is the create door's binding for an instance that |
| 41 | // names an environment and provisions no store of its own (a private |
| 42 | // storage create's grant clears the mark itself, by GrantInFor's rule). It |
| 43 | // moves the store to rec under the lock. A failure answers main's create |
| 44 | // retry sentence, and the answer is false. |
| 45 | func (s *Server) bindEnvironmentCreate(ctx context.Context, w http.ResponseWriter, rec provision.Record) bool { |
| 46 | if rec.Environment == "" { |
| 47 | return true |
| 48 | } |
| 49 | defer s.storeWalks.lock(rec.Account, rec.App, rec.Environment)() |
| 50 | if err := s.takeSeedStore(ctx, rec); err != nil { |
| 51 | log.Printf("console: api instances create %s/%s/%s: environment %q: %v", rec.Account, rec.App, rec.Host, rec.Environment, err) |
| 52 | jsonError(w, http.StatusBadGateway, "could not create the instance — try again") |
| 53 | return false |
| 54 | } |
| 55 | return true |
| 56 | } |
| 57 | |
| 58 | // takeSeedStore clears the seed mark on the store of rec's environment |
| 59 | // when it is not rec's own seed's: GrantInFor's rule (objstore |
| 60 | // claimRawGrantIn) for a binding that grants nothing. Whoever binds the |
| 61 | // store now holds it, and the seed's finish must never tear it down; an |
| 62 | // aborted seed's bucket can leak that way, which is the safe direction. |
| 63 | // The caller holds the environment's store walk lock. |
| 64 | func (s *Server) takeSeedStore(ctx context.Context, rec provision.Record) error { |
| 65 | store, etag, err := objstore.GetIn(ctx, s.cfg.Releases, rec.Account, rec.App, rec.Environment) |
| 66 | if err != nil { |
| 67 | return err |
| 68 | } |
| 69 | mine := "" |
| 70 | if rec.Seed != nil { |
| 71 | mine = rec.Seed.ID |
| 72 | } |
| 73 | if store == nil || store.Declared == nil || store.Declared.Seed == "" || store.Declared.Seed == mine { |
| 74 | return nil |
| 75 | } |
| 76 | seed := store.Declared.Seed |
| 77 | store.Declared.Seed = "" |
| 78 | if _, err := objstore.PutIn(ctx, s.cfg.Releases, rec.Account, rec.App, rec.Environment, store, etag); err != nil { |
| 79 | return err |
| 80 | } |
| 81 | log.Printf("console: instance %s/%s/%s binds environment %q: the store seed %s made (bucket %s) is no longer that seed's, and its finish leaves it standing", |
| 82 | rec.Account, rec.App, rec.Host, rec.Environment, seed, storeBucketName(store)) |
| 83 | return nil |
| 84 | } |
| 85 | |