Sweep stranded pending claims at boot and on a Temporal schedule while preserving evidence-based abandonment semantics. Apply occupancy and name-policy checks to carves by operator-root owners without affecting direct operator placements.
95 lines
3.5 KiB
Go
95 lines
3.5 KiB
Go
package domains
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/workflows/queues"
|
|
"go.temporal.io/sdk/client"
|
|
)
|
|
|
|
const (
|
|
// ExpirySweepScheduleID is the unique identifier for the claim expiry
|
|
// sweep schedule.
|
|
ExpirySweepScheduleID = "domains-claim-expiry-sweep"
|
|
// ExpirySweepWorkflowID is the workflow ID used for sweep executions.
|
|
ExpirySweepWorkflowID = "domains-claim-expiry-sweep-workflow"
|
|
// DefaultExpirySweepInterval is the default interval between sweeps.
|
|
// Shorter than the billing sweep's hour: an unswept claim holds a name
|
|
// another workspace may be waiting on, and the candidate read is a single
|
|
// range scan of idx_claims_pending_expires_at.
|
|
DefaultExpirySweepInterval = 15 * time.Minute
|
|
)
|
|
|
|
// ScheduleManager manages Temporal schedules for domain-registry maintenance.
|
|
type ScheduleManager struct {
|
|
client client.Client
|
|
logger *slog.Logger
|
|
}
|
|
|
|
// NewScheduleManager creates a new ScheduleManager.
|
|
func NewScheduleManager(c client.Client, logger *slog.Logger) *ScheduleManager {
|
|
return &ScheduleManager{client: c, logger: logger}
|
|
}
|
|
|
|
// ExpirySweepScheduleConfig holds configuration for the expiry sweep schedule.
|
|
type ExpirySweepScheduleConfig struct {
|
|
// Interval is how often to run the sweep.
|
|
Interval time.Duration
|
|
// TriggerImmediately runs the sweep immediately when creating the
|
|
// schedule. The console already sweeps once per boot, so this defaults
|
|
// off — the boot pass is the immediate run.
|
|
TriggerImmediately bool
|
|
}
|
|
|
|
// EnsureExpirySweepSchedule creates or updates the claim expiry sweep
|
|
// schedule. If the schedule already exists, it updates the spec; otherwise it
|
|
// creates it.
|
|
func (m *ScheduleManager) EnsureExpirySweepSchedule(ctx context.Context, cfg ExpirySweepScheduleConfig) error {
|
|
if cfg.Interval == 0 {
|
|
cfg.Interval = DefaultExpirySweepInterval
|
|
}
|
|
|
|
scheduleClient := m.client.ScheduleClient()
|
|
|
|
handle := scheduleClient.GetHandle(ctx, ExpirySweepScheduleID)
|
|
if _, err := handle.Describe(ctx); err == nil {
|
|
m.logger.Info("updating existing domain claim expiry sweep schedule", slog.Duration("interval", cfg.Interval))
|
|
if err := handle.Update(ctx, client.ScheduleUpdateOptions{
|
|
DoUpdate: func(schedule client.ScheduleUpdateInput) (*client.ScheduleUpdate, error) {
|
|
schedule.Description.Schedule.Spec = &client.ScheduleSpec{
|
|
Intervals: []client.ScheduleIntervalSpec{{Every: cfg.Interval}},
|
|
}
|
|
schedule.Description.Schedule.Action = &client.ScheduleWorkflowAction{
|
|
ID: ExpirySweepWorkflowID,
|
|
Workflow: SweepExpiredClaimsWorkflow,
|
|
TaskQueue: queues.Main,
|
|
}
|
|
return &client.ScheduleUpdate{Schedule: &schedule.Description.Schedule}, nil
|
|
},
|
|
}); err != nil {
|
|
return fmt.Errorf("failed to update claim expiry sweep schedule: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
m.logger.Info("creating domain claim expiry sweep schedule",
|
|
slog.Duration("interval", cfg.Interval), slog.Bool("triggerImmediately", cfg.TriggerImmediately))
|
|
if _, err := scheduleClient.Create(ctx, client.ScheduleOptions{
|
|
ID: ExpirySweepScheduleID,
|
|
Spec: client.ScheduleSpec{Intervals: []client.ScheduleIntervalSpec{{Every: cfg.Interval}}},
|
|
Action: &client.ScheduleWorkflowAction{
|
|
ID: ExpirySweepWorkflowID,
|
|
Workflow: SweepExpiredClaimsWorkflow,
|
|
TaskQueue: queues.Main,
|
|
},
|
|
TriggerImmediately: cfg.TriggerImmediately,
|
|
}); err != nil {
|
|
return fmt.Errorf("failed to create claim expiry sweep schedule: %w", err)
|
|
}
|
|
m.logger.Info("domain claim expiry sweep schedule created", slog.String("scheduleID", ExpirySweepScheduleID))
|
|
return nil
|
|
}
|