package hostguard import ( "context" "github.com/carlosframework/platform/internal/hostclaim" ) func (g Guard) BeginColdHandover(ctx context.Context, prior, barrier FenceTarget, check func(*hostclaim.Claim) error) error { return g.beginColdHandover(ctx, prior, barrier, nil, check) } func (g Guard) BeginServingHandover(ctx context.Context, prior, barrier FenceTarget, installation RootInstallation, check func(*hostclaim.Claim) error) error { if g.Root == nil || prior.Op != "" || prior.Phase != "" || prior.ObligationID != "" || barrier.Op != "" || barrier.Phase != "" || barrier.ObligationID != "" || installation.Empty || installation.Host != prior.Host || installation.Incarnation != prior.Incarnation || installation.Stamp != prior.Stamp || installation.Box != prior.Box || installation.Enrollment != prior.Enrollment || installation.InventoryRevision == 0 { return ErrInvalidInput } return g.beginColdHandover(ctx, prior, barrier, &installation, check) } func (g Guard) beginColdHandover(ctx context.Context, prior, barrier FenceTarget, installation *RootInstallation, check func(*hostclaim.Claim) error) error { return g.locked(ctx, prior.Host, func(d *recordDir) error { if ((installation == nil || prior.Session != 0) && prior.validate(prior.Host) != nil) || barrier.validate(prior.Host) != nil || prior.Host != barrier.Host || prior.Incarnation != barrier.Incarnation || barrier.Stamp <= prior.Stamp || barrier.Session <= prior.Session || barrier.Stamp != barrier.Session || check == nil { return ErrInvalidInput } claim, err := g.fresh(ctx, prior.Host) if err != nil { return err } if err = check(claim); err != nil { return err } if !barrier.matchesSession(claim, Captured{Incarnation: barrier.Incarnation, Stamp: barrier.Stamp}) { return ErrFenceTarget } r, err := d.read(prior.Host) if err != nil { return err } if installation != nil && (r.installation != *installation || r.quarantined || r.pendingInstallation != nil) { return ErrRecordIncarnation } found := false for _, receipt := range r.FenceJournal { if receipt.Target == prior { found = true } } if !found && (installation == nil || prior.Session != 0) { return ErrNoFenceReceipt } if installation != nil && prior.Session == 0 && (prior.FenceID != "" || (r.ActiveFence != nil && *r.ActiveFence != barrier)) { return ErrFenceTarget } if r.ActiveFence != nil && *r.ActiveFence == barrier { return d.sync(prior.Host) } if r.Incarnation != prior.Incarnation || (r.ActiveStamp != 0 && r.ActiveStamp != prior.Stamp) { return ErrStampMismatch } if r.ActiveFence != nil && *r.ActiveFence != prior { return ErrFenceActive } copied := barrier r.ActiveFence = &copied if r.root { r.barrier = &copied } return d.write(r) }) } func (g Guard) CompleteColdHandover(ctx context.Context, barrier FenceTarget, alreadyComplete bool, check func(*hostclaim.Claim) error, fn func() error) error { return g.locked(ctx, barrier.Host, func(d *recordDir) error { claim, err := g.fresh(ctx, barrier.Host) if err != nil { return err } if check == nil || fn == nil { return ErrInvalidInput } if err = check(claim); err != nil { return err } r, err := d.read(barrier.Host) if err != nil { return err } if alreadyComplete && r.Incarnation == barrier.Incarnation && r.ActiveStamp == barrier.Stamp && r.ActiveFence == nil { if g.Root == nil { return d.sync(barrier.Host) } if err := g.refreshRootInventory(ctx, &r); err != nil { return err } return d.write(r) } if r.ActiveFence == nil || *r.ActiveFence != barrier || r.Incarnation != barrier.Incarnation { return ErrFenceTarget } if err = fn(); err != nil { return err } if r.ActiveStamp != barrier.Stamp { r.retire() r.ActiveStamp = barrier.Stamp } r.ActiveFence = nil r.barrier = nil if err := g.refreshRootInventory(ctx, &r); err != nil { return err } return d.write(r) }) } func (g Guard) CompletedColdPredecessor(ctx context.Context, host string, captured Captured, session uint64, op, phase string) (FenceReceipt, error) { var found FenceReceipt err := g.fenceLocked(ctx, host, captured, func(d *recordDir) error { r, err := d.read(host) if err != nil { return err } if r.root && r.ActiveFence != nil { t := *r.ActiveFence if t.Host == host && t.Incarnation == captured.Incarnation && t.Stamp == captured.Stamp && t.Session == session && t.Op == op && t.Phase == phase { for _, receipt := range r.FenceJournal { if receipt.Target == t { found = receipt return nil } } return ErrNoFenceReceipt } } for _, receipt := range r.FenceJournal { t := receipt.Target if t.Host == host && t.Incarnation == captured.Incarnation && t.Stamp == captured.Stamp && t.Session == session && t.Op == op && t.Phase == phase { if found.Target.FenceID != "" && found.Target != t { return ErrFenceTarget } found = receipt } } if found.Target.FenceID == "" { return ErrNoFenceReceipt } return nil }) return found, err } func (g Guard) AbandonColdHandover(ctx context.Context, barrier FenceTarget) error { if g.Root == nil { return ErrUnsupported } return g.readLocked(ctx, barrier.Host, func(d *recordDir) error { c, err := g.fresh(ctx, barrier.Host) if err != nil { return err } if c.Holder.Incarnation == barrier.Incarnation && (c.Holder.Stamp < barrier.Stamp || c.Holder.Session <= barrier.Session) { return ErrFenced } r, err := d.read(barrier.Host) if err != nil { return err } if r.barrier == nil { return nil } if *r.barrier != barrier { return ErrFenceTarget } r.rootRetired = append(r.rootRetired, Captured{Incarnation: barrier.Incarnation, Stamp: barrier.Stamp}) r.barrier = nil if r.ActiveFence != nil && *r.ActiveFence == barrier { r.ActiveFence = nil } return d.write(r) }) } // SettleObsoleteColdHandover settles an interrupted cold handover from prior // whose journaled successor root found obsolete at startup: stamped under a // box binding other than the one in root's startup roster read (roster // binding B3). It is fenced: it runs under the host lock every handover step // takes, and only for a prior whose completed fence receipt this record // holds. It needs no claim session advance, unlike AbandonColdHandover: the // trigger is the binding change, decided by root from its own roster read. // // It undoes BeginColdHandover: the barrier is cleared and the deny slot it // took goes back to the prior's completed fence, the state the prior's stop // left. It records the successor's stamp as retired so it is never // installed or reused, and then runs record, which persists the settlement // in the handover's own journal. The prior keeps its historical cleanup // authority: its stamp stays the active one, its completed fence and // receipt stay, so its cleanup stop and retirement still complete. Settling // again, with the barrier already cleared, only re-runs record. func (g Guard) SettleObsoleteColdHandover(ctx context.Context, prior, barrier FenceTarget, record func() error) error { if g.Root == nil { return ErrUnsupported } if record == nil || prior.validate(prior.Host) != nil || barrier.validate(prior.Host) != nil || prior.Host != barrier.Host || prior.Incarnation != barrier.Incarnation || barrier.Stamp <= prior.Stamp || barrier.Session <= prior.Session || barrier.Stamp != barrier.Session { return ErrInvalidInput } return g.locked(ctx, prior.Host, func(d *recordDir) error { r, err := d.read(prior.Host) if err != nil { return err } successor := Captured{Incarnation: barrier.Incarnation, Stamp: barrier.Stamp} retired := false for _, old := range r.rootRetired { if old == successor { retired = true } } // Already settled: only the journal's record may be missing. Whatever // happened to the host since (a new successor, a new stop) stands. if retired && r.barrier == nil && (r.ActiveFence == nil || *r.ActiveFence != barrier) { return record() } found := false for _, receipt := range r.FenceJournal { if receipt.Target == prior { found = true } } if !found { return ErrNoFenceReceipt } if r.Incarnation != prior.Incarnation || r.ActiveStamp != prior.Stamp { return ErrStampMismatch } if r.barrier != nil && *r.barrier != barrier || r.ActiveFence != nil && *r.ActiveFence != barrier && *r.ActiveFence != prior { return ErrFenceTarget } if !retired { r.rootRetired = append(r.rootRetired, successor) } r.barrier = nil if r.ActiveFence != nil && *r.ActiveFence == barrier { restored := prior r.ActiveFence = &restored } if err := d.write(r); err != nil { return err } return record() }) }