package host import ( "context" "errors" "sync" "github.com/carlosframework/platform/internal/activator" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/hostwrite" "github.com/carlosframework/platform/internal/lbhealth" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/schedule" ) // ClaimProviders contains deployment providers after identity, floor and // quarantine verification. Build must fail if any of those gates is unknown. type RegionalLoop interface { Loop(context.Context, <-chan struct{}, func(error)) error } type ClaimProviders struct { guard hostguard.Guard registerLanding func(context.Context, hostclaim.Observed) error moveTargetFence func(context.Context, hostclaim.Observed) (hostclaim.ReturnFence, error) moveParks *claimMoveParks Delivery schedule.DeliveryAdmission Selector hostwrite.Selector Wake *activator.ClaimRuntime Regional RegionalLoop Stop *ClaimStopCoordinator Provision func(*provision.ReconcileConfig) Close func(context.Context) error } type ClaimFactory func(context.Context) (ClaimProviders, error) type ClaimWiring struct { Writes hostwrite.Config Providers ClaimProviders cancel context.CancelFunc joined chan struct{} once sync.Once } func newClaimWiring(ctx context.Context, getenv func(string) string, build ClaimFactory) (*ClaimWiring, error) { w := &ClaimWiring{} cfg, err := hostbox.Startup(ctx, getenv, func(ctx context.Context) (hostwrite.Selector, error) { if build == nil { return nil, hostwrite.ErrUnavailable } providers, err := build(ctx) if err != nil { return nil, err } if providers.Selector == nil || providers.Wake == nil || providers.Regional == nil || providers.Stop == nil { return nil, hostwrite.ErrUnavailable } w.Providers = providers return providers.Selector, nil }) w.Writes = cfg return w, err } func (w *ClaimWiring) configure(cfg *activator.Config, runner *schedule.Runner, converger *schedule.Converger) error { if w == nil || cfg == nil || runner == nil || converger == nil { return hostwrite.ErrUnavailable } if !w.Writes.Enabled { return nil } if w.Writes.Selector == nil || w.Providers.Wake == nil { return hostwrite.ErrUnavailable } cfg.HostWrites = w.Writes cfg.Claims = w.Providers.Wake runner.HostWrites = w.Writes runner.StartDelivery = w.Providers.Delivery converger.HostWrites = w.Writes return nil } func (w *ClaimWiring) startRegional(ctx context.Context, serial <-chan struct{}, report func(error)) error { if w == nil { return hostwrite.ErrUnavailable } if !w.Writes.Enabled { return nil } if w.Providers.Regional == nil || w.joined != nil { return hostwrite.ErrUnavailable } workerCtx, cancel := context.WithCancel(ctx) w.cancel = cancel w.joined = make(chan struct{}) // The load balancer's hosting condition (region HA spec 2 §5, §6) is // this process's mode, as RunAgent's gate chose it (agentHosting): // the claim agent of an enrolled box with an armed guard. It is set // before the worker starts and cleared when the worker ends, so a box // whose claim worker has stopped never answers healthy. hosting := agentHosting.Load() lbhealth.Default.SetHosting(hosting) go func() { defer close(w.joined) defer lbhealth.Default.SetHosting(false) w.Providers.Regional.Loop(workerCtx, serial, report) }() return nil } func (w *ClaimWiring) close(ctx context.Context) error { if w == nil { return nil } w.once.Do(func() { if w.cancel != nil { w.cancel() } }) if w.joined == nil { if w.Providers.Close != nil { return w.Providers.Close(ctx) } return nil } select { case <-w.joined: if w.Providers.Close != nil { return w.Providers.Close(ctx) } return nil case <-ctx.Done(): return errors.Join(ctx.Err(), hostwrite.ErrUnavailable) } }