| 1 | package hostguard |
| 2 | |
| 3 | import "context" |
| 4 | |
| 5 | type FenceView struct { |
| 6 | MigrationDenies []MigrationDeny |
| 7 | Schema int |
| 8 | Host string |
| 9 | Captured Captured |
| 10 | Installation RootInstallation |
| 11 | Retired []Captured |
| 12 | Pending *FenceTarget |
| 13 | Receipts []FenceReceipt |
| 14 | Barrier *FenceTarget |
| 15 | // Settled is set only in the migration census's views: the |
| 16 | // installation is quarantined by an obsolete-intent settlement. |
| 17 | Settled bool `json:",omitempty"` |
| 18 | } |
| 19 | type FenceViewProvider func(context.Context, string, Captured) (FenceView, error) |
| 20 | |
| 21 | // Installation reads one atomic root record without acquiring the host lock. |
| 22 | // A caller using it to authorize an effect must hold Guard.With's exclusion. |
| 23 | // It does not confer admission or replace the guard's fresh claim checks. |
| 24 | func (a *RootAuthority) Installation(ctx context.Context, host string, captured Captured) (RootInstallation, error) { |
| 25 | if err := ctx.Err(); err != nil { |
| 26 | return RootInstallation{}, err |
| 27 | } |
| 28 | v, err := a.viewLocked(host, captured) |
| 29 | return v.Installation, err |
| 30 | } |
| 31 | |
| 32 | func (a *RootAuthority) View(ctx context.Context, host string, captured Captured) (FenceView, error) { |
| 33 | if err := ctx.Err(); err != nil { |
| 34 | return FenceView{}, err |
| 35 | } |
| 36 | if a == nil || a.store == nil || validateFileHost(host) != nil { |
| 37 | return FenceView{}, ErrInvalidInput |
| 38 | } |
| 39 | unlock, err := a.store.Lock(ctx, host+".lock") |
| 40 | if err != nil { |
| 41 | return FenceView{}, err |
| 42 | } |
| 43 | defer unlock() |
| 44 | return a.viewLocked(host, captured) |
| 45 | } |
| 46 | |
| 47 | func (a *RootAuthority) viewLocked(host string, captured Captured) (FenceView, error) { |
| 48 | return a.readView(host, captured, false) |
| 49 | } |
| 50 | |
| 51 | // readView is viewLocked's read. A quarantined record is refused, except |
| 52 | // that the refusal-only migration census (census) inspects a settled one |
| 53 | // (SettleObsoleteRootInventory): it still names its installation and |
| 54 | // inventory revision for cleanup and migration startup, and its view says |
| 55 | // Settled. A pending intent is refused either way. |
| 56 | func (a *RootAuthority) readView(host string, captured Captured, census bool) (FenceView, error) { |
| 57 | r, err := a.read(host) |
| 58 | if err != nil { |
| 59 | return FenceView{}, err |
| 60 | } |
| 61 | if r.pendingInstallation != nil || (r.quarantined && !(census && r.settled)) { |
| 62 | return FenceView{}, ErrFenced |
| 63 | } |
| 64 | known := r.Incarnation == captured.Incarnation && r.ActiveStamp == captured.Stamp && captured.Stamp != 0 |
| 65 | for _, old := range r.rootRetired { |
| 66 | if old == captured { |
| 67 | known = true |
| 68 | } |
| 69 | } |
| 70 | if !known { |
| 71 | return FenceView{}, ErrStampMismatch |
| 72 | } |
| 73 | return FenceView{MigrationDenies: r.migrationDenies, Schema: 1, Host: host, Captured: captured, Installation: r.installation, Retired: r.rootRetired, Pending: r.ActiveFence, Receipts: r.FenceJournal, Barrier: r.barrier, Settled: r.quarantined && r.settled}, nil |
| 74 | } |
| 75 | func (v FenceView) validate(host string, c Captured) error { |
| 76 | if v.Schema != 1 || v.Host != host || v.Installation.Host != host || v.Installation.Migration == 0 || v.Captured != c { |
| 77 | return ErrMalformedRecord |
| 78 | } |
| 79 | if v.Pending != nil && v.Pending.validate(host) != nil { |
| 80 | return ErrMalformedRecord |
| 81 | } |
| 82 | if v.Barrier != nil && v.Barrier.validate(host) != nil { |
| 83 | return ErrMalformedRecord |
| 84 | } |
| 85 | for _, r := range v.Receipts { |
| 86 | if r.validate(host) != nil { |
| 87 | return ErrMalformedRecord |
| 88 | } |
| 89 | } |
| 90 | return nil |
| 91 | } |
| 92 | func (v FenceView) admit(host string, c Captured, claimSession uint64) error { |
| 93 | if err := v.validate(host, c); err != nil { |
| 94 | return err |
| 95 | } |
| 96 | if v.Installation.Incarnation != c.Incarnation || v.Installation.Stamp != c.Stamp || c.Stamp == 0 { |
| 97 | return ErrInactive |
| 98 | } |
| 99 | for _, retired := range v.Retired { |
| 100 | if retired == c { |
| 101 | return ErrRetiredStamp |
| 102 | } |
| 103 | } |
| 104 | if migrationAdmissionDenied(v.MigrationDenies, v.Receipts, c, claimSession) { |
| 105 | return ErrFenced |
| 106 | } |
| 107 | if v.Barrier != nil { |
| 108 | return ErrFenced |
| 109 | } |
| 110 | denied := func(t FenceTarget) bool { |
| 111 | return t.Host == host && t.Incarnation == c.Incarnation && t.Stamp == c.Stamp && t.Session == claimSession |
| 112 | } |
| 113 | if v.Pending != nil && denied(*v.Pending) { |
| 114 | return ErrFenced |
| 115 | } |
| 116 | for _, r := range v.Receipts { |
| 117 | if denied(r.Target) { |
| 118 | return ErrFenced |
| 119 | } |
| 120 | } |
| 121 | return nil |
| 122 | } |
| 123 | func (g Guard) fenceLocked(ctx context.Context, host string, c Captured, fn func(*recordDir) error) error { |
| 124 | if g.FenceView == nil { |
| 125 | return g.readLocked(ctx, host, fn) |
| 126 | } |
| 127 | view, err := g.FenceView(ctx, host, c) |
| 128 | if err != nil { |
| 129 | return err |
| 130 | } |
| 131 | if err := view.validate(host, c); err != nil { |
| 132 | return err |
| 133 | } |
| 134 | r := record{migrationDenies: view.MigrationDenies, V: 1, Host: host, Incarnation: view.Installation.Incarnation, ActiveStamp: view.Installation.Stamp, ActiveFence: view.Pending, FenceJournal: view.Receipts, root: true, installation: view.Installation, rootRetired: view.Retired, barrier: view.Barrier} |
| 135 | return fn(&recordDir{snapshot: &r}) |
| 136 | } |
| 137 | |