package hostguard import "context" type FenceView struct { MigrationDenies []MigrationDeny Schema int Host string Captured Captured Installation RootInstallation Retired []Captured Pending *FenceTarget Receipts []FenceReceipt Barrier *FenceTarget // Settled is set only in the migration census's views: the // installation is quarantined by an obsolete-intent settlement. Settled bool `json:",omitempty"` } type FenceViewProvider func(context.Context, string, Captured) (FenceView, error) // Installation reads one atomic root record without acquiring the host lock. // A caller using it to authorize an effect must hold Guard.With's exclusion. // It does not confer admission or replace the guard's fresh claim checks. func (a *RootAuthority) Installation(ctx context.Context, host string, captured Captured) (RootInstallation, error) { if err := ctx.Err(); err != nil { return RootInstallation{}, err } v, err := a.viewLocked(host, captured) return v.Installation, err } func (a *RootAuthority) View(ctx context.Context, host string, captured Captured) (FenceView, error) { if err := ctx.Err(); err != nil { return FenceView{}, err } if a == nil || a.store == nil || validateFileHost(host) != nil { return FenceView{}, ErrInvalidInput } unlock, err := a.store.Lock(ctx, host+".lock") if err != nil { return FenceView{}, err } defer unlock() return a.viewLocked(host, captured) } func (a *RootAuthority) viewLocked(host string, captured Captured) (FenceView, error) { return a.readView(host, captured, false) } // readView is viewLocked's read. A quarantined record is refused, except // that the refusal-only migration census (census) inspects a settled one // (SettleObsoleteRootInventory): it still names its installation and // inventory revision for cleanup and migration startup, and its view says // Settled. A pending intent is refused either way. func (a *RootAuthority) readView(host string, captured Captured, census bool) (FenceView, error) { r, err := a.read(host) if err != nil { return FenceView{}, err } if r.pendingInstallation != nil || (r.quarantined && !(census && r.settled)) { return FenceView{}, ErrFenced } known := r.Incarnation == captured.Incarnation && r.ActiveStamp == captured.Stamp && captured.Stamp != 0 for _, old := range r.rootRetired { if old == captured { known = true } } if !known { return FenceView{}, ErrStampMismatch } 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 } func (v FenceView) validate(host string, c Captured) error { if v.Schema != 1 || v.Host != host || v.Installation.Host != host || v.Installation.Migration == 0 || v.Captured != c { return ErrMalformedRecord } if v.Pending != nil && v.Pending.validate(host) != nil { return ErrMalformedRecord } if v.Barrier != nil && v.Barrier.validate(host) != nil { return ErrMalformedRecord } for _, r := range v.Receipts { if r.validate(host) != nil { return ErrMalformedRecord } } return nil } func (v FenceView) admit(host string, c Captured, claimSession uint64) error { if err := v.validate(host, c); err != nil { return err } if v.Installation.Incarnation != c.Incarnation || v.Installation.Stamp != c.Stamp || c.Stamp == 0 { return ErrInactive } for _, retired := range v.Retired { if retired == c { return ErrRetiredStamp } } if migrationAdmissionDenied(v.MigrationDenies, v.Receipts, c, claimSession) { return ErrFenced } if v.Barrier != nil { return ErrFenced } denied := func(t FenceTarget) bool { return t.Host == host && t.Incarnation == c.Incarnation && t.Stamp == c.Stamp && t.Session == claimSession } if v.Pending != nil && denied(*v.Pending) { return ErrFenced } for _, r := range v.Receipts { if denied(r.Target) { return ErrFenced } } return nil } func (g Guard) fenceLocked(ctx context.Context, host string, c Captured, fn func(*recordDir) error) error { if g.FenceView == nil { return g.readLocked(ctx, host, fn) } view, err := g.FenceView(ctx, host, c) if err != nil { return err } if err := view.validate(host, c); err != nil { return err } 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} return fn(&recordDir{snapshot: &r}) }