package host import ( "context" "errors" "fmt" "slices" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" execlease "github.com/carlosframework/platform/internal/hostexec" "github.com/carlosframework/platform/internal/hostguard" ) func (h ClaimRootHTTP) settleExecution(ctx context.Context, r execlease.ExecutionRecord) error { o, err := hostclaim.Read(ctx, h.Stop.Coordinator.Evidence, r.Request.Host) if err != nil { return err } if o.Absent || o.Claim == nil || o.Claim.Host != r.Request.Host || o.Claim.Validate() != nil { return execlease.ErrState } c := o.Claim if !c.Free() && c.Op.Kind != hostclaim.KindServing && slices.Contains(c.Op.Participants, r.Authority.Box) { return execlease.ErrInactive } if !c.Free() && c.Holder.Incarnation != r.Request.Incarnation && !slices.Contains(c.RetiredIncarnations, r.Request.Incarnation) { return execlease.ErrState } return nil } func (h ClaimRootHTTP) cleanupExecution(ctx context.Context, r execlease.ExecutionRecord) error { q, a := r.Request, r.Authority guard := h.Stop.Coordinator.Guard if h.Launches == nil || guard.Root == nil || h.Reconcile.Census == nil || h.Stop.Coordinator.Kernel == nil { return execlease.ErrState } all, err := h.Launches.HostLaunches(ctx, q.Host) if err != nil { return err } var launches []hostbox.LaunchInventory captured := hostguard.Captured{Incarnation: q.Incarnation, Stamp: q.Stamp} if a.ParticipationOnly && a.Installation != "" { if a.Kind == hostclaim.KindDeleting || a.Kind == hostclaim.KindParking { view, viewErr := guard.Root.View(ctx, q.Host, captured) if viewErr != nil { return viewErr } in := view.Installation if view.Barrier != nil || in.Incarnation != captured.Incarnation || in.Stamp != captured.Stamp || in.Box != a.Box || in.Enrollment != a.Enrollment || in.Account != a.Account || in.App != a.App || in.Migration != a.Migration { return execlease.ErrLost } } else { captured, err = guard.Capture(q.Host) if err != nil { return err } } if captured.Incarnation != q.Incarnation || fmt.Sprintf("%s/%d/%s/%s", captured.Incarnation, captured.Stamp, a.Box, a.Enrollment) != a.Installation { return execlease.ErrLost } } for _, launch := range all { target := launch.Target if target.Incarnation == captured.Incarnation && target.Stamp == captured.Stamp && target.Box == a.Box && target.Enrollment == a.Enrollment { launches = append(launches, launch) } } if len(launches) == 0 { if a.Installation != "" { view, err := guard.Root.View(ctx, q.Host, captured) if err != nil { return err } in := view.Installation if view.Barrier != nil || in.Box != a.Box || in.Enrollment != a.Enrollment || in.Account != a.Account || in.App != a.App || in.Migration != a.Migration || fmt.Sprintf("%s/%d/%s/%s", in.Incarnation, in.Stamp, in.Box, in.Enrollment) != a.Installation { return ErrClaimStopInventory } inventory, revision, err := h.Journals.Inventory.Snapshot(ctx, q.Host, captured) if err != nil { return err } if !inventory.Complete || revision == 0 || revision != in.InventoryRevision { return ErrClaimStopInventory } kernel, ok := h.Stop.Coordinator.Kernel.(stopNoProcessKernel) if !ok { return ErrClaimStopUnavailable } boot, err := execlease.BootID() if err != nil { return err } target := hostguard.FenceTarget{Host: q.Host, Incarnation: captured.Incarnation, Stamp: captured.Stamp, Box: a.Box, Enrollment: a.Enrollment, BootID: boot, DirectoryRevision: inventory.DirectoryRevision} _, err = kernel.VerifyNoProcess(ctx, target, inventory) return err } if !a.ParticipationOnly { return ErrClaimStopInventory } if _, err := guard.Capture(q.Host); !errors.Is(err, hostguard.ErrNoRecord) { return ErrClaimStopInventory } o, err := hostclaim.Read(ctx, h.Stop.Coordinator.Evidence, q.Host) if err != nil { return err } return h.rootEmptyCensus().CheckLocal(ctx, o, hostbox.RecordSelection{Host: q.Host, Incarnation: q.Incarnation}) } view, err := guard.Root.View(ctx, q.Host, captured) if err != nil { return err } for _, launch := range launches { var receipt *hostguard.FenceReceipt for i := range view.Receipts { if hostbox.SameLaunchOwner(view.Receipts[i].Target, launch.Target) { receipt = &view.Receipts[i] break } } if receipt == nil { pending := view.Pending if pending == nil || !hostbox.SameLaunchOwner(*pending, launch.Target) { next, err := h.beginExecutionRecoveryFence(ctx, launch.Target) if err != nil { return err } pending = &next } if _, err := h.Stop.Coordinator.RecoverStopped(ctx, *pending); err != nil { return err } durable, err := guard.FenceReceiptFor(ctx, *pending) if err != nil { return err } receipt = &durable } if err := verifyRetirementReceipt(ctx, *receipt); err != nil { return err } reader, ok := h.Stop.Coordinator.Inventory.(exactStopInventory) if !ok { return ErrClaimStopInventory } inventory, err := reader.ReadExactStopInventory(ctx, receipt.Target) if err != nil { return err } launch, err = h.Launches.Read(ctx, receipt.Target) if err != nil { return err } if retired, err := retiredExecutionLaunch(view, launch); retired || err != nil { if err != nil { return err } continue } ack := DrainClaimAcknowledgement(receipt.Target.FenceID, launch.Target.ControllerID, launch.ProcessStart, launch.ProducerIDs, launch.Completed) if _, err := h.Stop.Coordinator.Kernel.VerifyStopped(ctx, receipt.Target, inventory, ack); err != nil { return err } } return nil } func retiredExecutionLaunch(view hostguard.FenceView, launch hostbox.LaunchInventory) (bool, error) { t := launch.Target if !slices.Contains(view.Retired, hostguard.Captured{Incarnation: t.Incarnation, Stamp: t.Stamp}) { return false, nil } if view.Host != t.Host || view.Barrier != nil || !launch.WritersClosed || !launch.Completed.Valid() { return true, ErrClaimStopInventory } for _, r := range launch.Reservations { if !hostbox.SameLaunchOwner(r.Target, t) || (hostbox.IsUnitReservation(r) && r.State != hostbox.UnitSettled) || (!hostbox.IsUnitReservation(r) && r.State != "settled") { return true, ErrClaimStopInventory } } return true, nil } func (h ClaimRootHTTP) transitionExecution(ctx context.Context, old execlease.ExecutionRecord, q execlease.ExecutionRequest, next execlease.ExecutionAuthority) error { a := old.Authority if old.Request.Host != q.Host || old.Request.Incarnation != q.Incarnation || old.Request.Op != q.Op || a.Box != next.Box || a.Enrollment != next.Enrollment || a.Account != next.Account || a.App != next.App || a.Migration != next.Migration { return execlease.ErrLost } if old.Request.Stamp == q.Stamp && old.Request.Session == q.Session { if a.Installation == next.Installation && a.ParticipationOnly == next.ParticipationOnly { return nil } if a.ParticipationOnly && next.Installation != "" { return nil } return execlease.ErrLost } if next.ParticipationOnly && a.Installation == next.Installation && q.Stamp >= old.Request.Stamp && q.Session >= old.Request.Session { return nil } if next.ParticipationOnly || next.Installation == "" || q.Stamp < old.Request.Stamp || q.Session <= old.Request.Session { return execlease.ErrLost } view, err := h.Stop.Coordinator.Guard.Root.View(ctx, q.Host, hostguard.Captured{Incarnation: q.Incarnation, Stamp: q.Stamp}) if err != nil { return err } if view.Installation.Stamp != q.Stamp || view.Installation.Box != next.Box || view.Installation.Enrollment != next.Enrollment { return execlease.ErrLost } if a.ParticipationOnly && (a.Installation == "" || a.Installation == next.Installation) { return nil } if q.Stamp == old.Request.Stamp { if a.Installation != next.Installation { return execlease.ErrLost } return h.cleanupExecution(ctx, old) } if !slices.Contains(view.Retired, hostguard.Captured{Incarnation: q.Incarnation, Stamp: old.Request.Stamp}) { return execlease.ErrLost } return nil } func (h ClaimRootHTTP) beginExecutionRecoveryFence(ctx context.Context, target hostguard.FenceTarget) (hostguard.FenceTarget, error) { o, err := h.Stop.Coordinator.Claims.Read(ctx, target.Host) if err != nil { return hostguard.FenceTarget{}, err } if o.Absent || o.Claim == nil || o.Claim.Holder == nil || h.Stop.Coordinator.NewID == nil { return hostguard.FenceTarget{}, ErrClaimStopInventory } c := o.Claim target.ClaimETag, target.ClaimGen = o.ETag, c.Gen target.Op, target.Phase = c.Op.ID, c.Op.Phase if c.Op.Kind == hostclaim.KindServing { target.ObligationID = "" } else if target.ObligationID == "" { var ok bool switch c.Op.Kind { case hostclaim.KindLanding: target.ObligationID, ok = hostguard.LandingWriterStop(c, target.Box, target.Region) case hostclaim.KindRelocating: target.ObligationID, ok = hostguard.RelocationWriterStop(c, target.Box, target.Region) case hostclaim.KindRestoring, hostclaim.KindReverting: target.ObligationID, ok = hostguard.RecoveryWriterStop(c, target.Box, target.Region) } if !ok { return hostguard.FenceTarget{}, ErrClaimStopInventory } } if !hostguard.StopAuthorityMatches(c, target) || !stopOperationTuple(target.Op, target.Phase, target.ObligationID) { return hostguard.FenceTarget{}, ErrClaimStopInventory } target.FenceID = h.Stop.Coordinator.NewID() return h.Stop.Coordinator.Guard.BeginFence(ctx, target) }