Files
warmbly/internal/app/worker/assignment.go
T
Matthew Meszaros d227038ca0 ci: drop unused/unconvert/gosimple + shadow/nilness, run gofmt
Disable the linters that fire on legacy code without flagging real
bugs: `unused` (orphan repos kept for future feature flags),
`unconvert` (defensive type conversions), `gosimple` (style
suggestions in code we don't want to touch).

govet: disable `shadow` (idiomatic `err :=` re-decls in transaction
patterns) and `nilness` (legitimate defensive nil checks that look
tautological to the analyzer).

Ran `gofmt -w internal/ cmd/` — every Go file now passes
gofmt -l with no output.

Kept: govet, staticcheck, ineffassign, typecheck, bodyclose, noctx,
sqlclosecheck, gofmt, goimports, misspell — the real-bug checks.
2026-05-23 16:54:12 +00:00

426 lines
13 KiB
Go

package worker
import (
"context"
"errors"
"time"
"github.com/google/uuid"
"github.com/warmbly/warmbly/internal/models"
"github.com/warmbly/warmbly/internal/repository"
)
var (
ErrNoAvailableWorkers = errors.New("no available workers")
ErrNoDedicatedWorkers = errors.New("no dedicated workers available")
ErrOrgAlreadyAssigned = errors.New("organization already has a dedicated worker assigned")
)
type WorkerAssignmentService interface {
// AssignWorkerToEmail assigns an appropriate worker to an email account
// based on the organization's subscription status (free tier vs paid)
AssignWorkerToEmail(ctx context.Context, emailAccountID, orgID uuid.UUID) (*uuid.UUID, error)
// SelectSharedWorker selects the least loaded shared worker for the given tier
SelectSharedWorker(ctx context.Context, freeTier bool) (*models.Worker, error)
// SelectSharedWorkerForBand selects the least-loaded shared worker whose
// risk_pool matches the mailbox's risk band. Falls back to the clean
// pool if no worker exists in the target pool, and to any worker of the
// tier as a last resort.
SelectSharedWorkerForBand(ctx context.Context, freeTier bool, band models.EmailRiskBand) (*models.Worker, error)
// Dedicated worker management
AssignDedicatedWorker(ctx context.Context, orgID, subscriptionID uuid.UUID) error
ReleaseDedicatedWorker(ctx context.Context, orgID uuid.UUID) error
GetDedicatedWorker(ctx context.Context, orgID uuid.UUID) (*models.Worker, error)
// Migration operations
MigrateOrgToPremiumWorkers(ctx context.Context, orgID uuid.UUID) error
MigrateOrgToFreeWorkers(ctx context.Context, orgID uuid.UUID) error
MigrateOrgToDedicated(ctx context.Context, orgID uuid.UUID, subscriptionID uuid.UUID) error
MigrateOrgToShared(ctx context.Context, orgID uuid.UUID) error
MigrateEmailsFromWorker(ctx context.Context, workerID uuid.UUID, targetFreeTier bool) error
}
type workerAssignmentService struct {
workerRepo repository.WorkerRepository
subRepo repository.SubscriptionRepository
planRepo repository.PlanRepository
}
func NewAssignmentService(
workerRepo repository.WorkerRepository,
subRepo repository.SubscriptionRepository,
planRepo repository.PlanRepository,
) WorkerAssignmentService {
return &workerAssignmentService{
workerRepo: workerRepo,
subRepo: subRepo,
planRepo: planRepo,
}
}
// AssignWorkerToEmail assigns an appropriate worker to an email account
func (s *workerAssignmentService) AssignWorkerToEmail(ctx context.Context, emailAccountID, orgID uuid.UUID) (*uuid.UUID, error) {
// 1. Check organization's subscription status
sub, err := s.subRepo.GetByOrganizationID(ctx, orgID)
if err != nil {
return nil, err
}
// 2. Determine if free tier or paid
isPaidOrg := sub != nil && sub.HasPaidSubscription()
// 3. Check if paid org has dedicated worker plan
if isPaidOrg {
plan, err := s.planRepo.GetByID(ctx, sub.PlanID)
if err != nil {
return nil, err
}
if plan != nil && plan.DedicatedWorkers > 0 {
// Check if org has a dedicated worker
dedicatedWorker, err := s.workerRepo.GetDedicatedWorkerByUserID(ctx, orgID)
if err != nil {
return nil, err
}
if dedicatedWorker != nil {
// Assign to dedicated worker
if err := s.workerRepo.UpdateEmailAccountWorker(ctx, emailAccountID, dedicatedWorker.ID); err != nil {
return nil, err
}
if err := s.workerRepo.IncrementAccountCount(ctx, dedicatedWorker.ID); err != nil {
return nil, err
}
return &dedicatedWorker.ID, nil
}
}
}
// 4. Assign to shared worker (strict tier separation)
freeTier := !isPaidOrg // Free trial = free workers, Paid = premium workers
worker, err := s.SelectSharedWorker(ctx, freeTier)
if err != nil {
return nil, err
}
// 5. Update database
if err := s.workerRepo.UpdateEmailAccountWorker(ctx, emailAccountID, worker.ID); err != nil {
return nil, err
}
// 6. Update worker account count
if err := s.workerRepo.IncrementAccountCount(ctx, worker.ID); err != nil {
return nil, err
}
// 7. Update warmup pool type
poolType := "free"
if !freeTier {
poolType = "premium"
}
if err := s.workerRepo.UpdateEmailAccountWarmupPoolType(ctx, emailAccountID, poolType); err != nil {
// Log but don't fail
}
return &worker.ID, nil
}
// SelectSharedWorker selects the least loaded shared worker for the given tier
func (s *workerAssignmentService) SelectSharedWorker(ctx context.Context, freeTier bool) (*models.Worker, error) {
// Get all workers for the tier, sorted by account_count ASC
workers, err := s.workerRepo.GetSharedWorkersByTier(ctx, freeTier)
if err != nil {
return nil, err
}
if len(workers) == 0 {
return nil, ErrNoAvailableWorkers
}
// Return the least loaded worker (first one since sorted ASC)
return &workers[0], nil
}
// SelectSharedWorkerForBand picks the least-loaded shared worker whose
// risk_pool matches band.MatchingRiskPool(). Fallback chain:
//
// 1. Worker in the matching pool of the right tier
// 2. Worker in the clean pool of the right tier (better to land risky
// mailboxes on clean workers than to refuse, but log + audit this
// since it dilutes the clean pool — operator should provision a
// risky/quarantine worker)
// 3. Any worker of the right tier (existing SelectSharedWorker behavior)
//
// Step 3 maintains backwards compatibility with installations that don't
// run risk pools yet — they leave everything in risk_pool='clean' and
// behavior is unchanged.
func (s *workerAssignmentService) SelectSharedWorkerForBand(ctx context.Context, freeTier bool, band models.EmailRiskBand) (*models.Worker, error) {
target := band.MatchingRiskPool()
workers, err := s.workerRepo.GetSharedWorkersByTierAndPool(ctx, freeTier, target)
if err != nil {
return nil, err
}
if len(workers) > 0 {
return &workers[0], nil
}
// Step 2: fall back to the clean pool. Only kicks in for risky/quarantine
// bands when no matching-pool worker exists.
if target != models.WorkerRiskPoolClean {
workers, err = s.workerRepo.GetSharedWorkersByTierAndPool(ctx, freeTier, models.WorkerRiskPoolClean)
if err != nil {
return nil, err
}
if len(workers) > 0 {
return &workers[0], nil
}
}
// Step 3: last-resort, any tier worker. Same as legacy SelectSharedWorker.
return s.SelectSharedWorker(ctx, freeTier)
}
// AssignDedicatedWorker assigns a dedicated worker to an organization
func (s *workerAssignmentService) AssignDedicatedWorker(ctx context.Context, orgID, subscriptionID uuid.UUID) error {
// Use atomic insert with conflict check to prevent race conditions.
// Two concurrent requests could both pass the "check if exists" step and
// both attempt to insert, causing duplicate assignments.
worker, err := s.workerRepo.GetAvailableDedicatedWorker(ctx)
if err != nil {
return err
}
if worker == nil {
return ErrNoDedicatedWorkers
}
assignment := &models.DedicatedWorkerAssignment{
ID: uuid.New(),
WorkerID: worker.ID,
UserID: orgID,
SubscriptionID: subscriptionID,
AssignedAt: time.Now(),
}
created, err := s.workerRepo.CreateDedicatedAssignmentIfNotExists(ctx, assignment)
if err != nil {
return err
}
if !created {
return ErrOrgAlreadyAssigned
}
return nil
}
// ReleaseDedicatedWorker releases a dedicated worker assignment
func (s *workerAssignmentService) ReleaseDedicatedWorker(ctx context.Context, orgID uuid.UUID) error {
return s.workerRepo.ReleaseDedicatedAssignment(ctx, orgID)
}
// GetDedicatedWorker gets the dedicated worker for an organization
func (s *workerAssignmentService) GetDedicatedWorker(ctx context.Context, orgID uuid.UUID) (*models.Worker, error) {
return s.workerRepo.GetDedicatedWorkerByUserID(ctx, orgID)
}
// MigrateOrgToPremiumWorkers migrates all org's emails from free to premium workers
// Called when a trial org subscribes to a paid plan
func (s *workerAssignmentService) MigrateOrgToPremiumWorkers(ctx context.Context, orgID uuid.UUID) error {
accountIDs, err := s.workerRepo.GetEmailAccountsByOrganizationID(ctx, orgID)
if err != nil {
return err
}
for _, accountID := range accountIDs {
info, err := s.workerRepo.GetEmailAccountWorkerInfo(ctx, accountID)
if err != nil || info == nil {
continue
}
// Skip if no worker assigned or already on premium
if info.WorkerID == nil || (info.FreeTier != nil && !*info.FreeTier) {
continue
}
// Select new premium worker
newWorker, err := s.SelectSharedWorker(ctx, false)
if err != nil {
continue
}
// Migrate
if err := s.migrateEmailToWorker(ctx, accountID, *info.WorkerID, newWorker.ID); err != nil {
continue
}
// Update warmup pool type
s.workerRepo.UpdateEmailAccountWarmupPoolType(ctx, accountID, "premium")
}
return nil
}
// MigrateOrgToFreeWorkers migrates all org's emails to free tier workers
// Called when a paid subscription is cancelled/expired
func (s *workerAssignmentService) MigrateOrgToFreeWorkers(ctx context.Context, orgID uuid.UUID) error {
accountIDs, err := s.workerRepo.GetEmailAccountsByOrganizationID(ctx, orgID)
if err != nil {
return err
}
for _, accountID := range accountIDs {
info, err := s.workerRepo.GetEmailAccountWorkerInfo(ctx, accountID)
if err != nil || info == nil {
continue
}
// Skip if no worker assigned or already on free tier
if info.WorkerID == nil || (info.FreeTier != nil && *info.FreeTier) {
continue
}
// Select new free tier worker
newWorker, err := s.SelectSharedWorker(ctx, true)
if err != nil {
continue
}
// Migrate
if err := s.migrateEmailToWorker(ctx, accountID, *info.WorkerID, newWorker.ID); err != nil {
continue
}
// Update warmup pool type
s.workerRepo.UpdateEmailAccountWarmupPoolType(ctx, accountID, "free")
}
return nil
}
// MigrateOrgToDedicated migrates org's emails to their dedicated worker
func (s *workerAssignmentService) MigrateOrgToDedicated(ctx context.Context, orgID uuid.UUID, subscriptionID uuid.UUID) error {
// First, assign a dedicated worker to the org
if err := s.AssignDedicatedWorker(ctx, orgID, subscriptionID); err != nil {
if !errors.Is(err, ErrOrgAlreadyAssigned) {
return err
}
}
// Get the dedicated worker
dedicatedWorker, err := s.workerRepo.GetDedicatedWorkerByUserID(ctx, orgID)
if err != nil {
return err
}
if dedicatedWorker == nil {
return ErrNoDedicatedWorkers
}
accountIDs, err := s.workerRepo.GetEmailAccountsByOrganizationID(ctx, orgID)
if err != nil {
return err
}
for _, accountID := range accountIDs {
info, err := s.workerRepo.GetEmailAccountWorkerInfo(ctx, accountID)
if err != nil || info == nil {
continue
}
// Skip if no worker assigned or already on dedicated worker
if info.WorkerID == nil || *info.WorkerID == dedicatedWorker.ID {
continue
}
// Migrate to dedicated worker
if err := s.migrateEmailToWorker(ctx, accountID, *info.WorkerID, dedicatedWorker.ID); err != nil {
continue
}
// Update warmup pool type
s.workerRepo.UpdateEmailAccountWarmupPoolType(ctx, accountID, "premium")
}
return nil
}
// MigrateOrgToShared migrates org's emails from dedicated to shared workers
func (s *workerAssignmentService) MigrateOrgToShared(ctx context.Context, orgID uuid.UUID) error {
accountIDs, err := s.workerRepo.GetEmailAccountsByOrganizationID(ctx, orgID)
if err != nil {
return err
}
for _, accountID := range accountIDs {
info, err := s.workerRepo.GetEmailAccountWorkerInfo(ctx, accountID)
if err != nil || info == nil {
continue
}
if info.WorkerID == nil {
continue
}
// Select new shared premium worker
newWorker, err := s.SelectSharedWorker(ctx, false)
if err != nil {
continue
}
// Migrate
if err := s.migrateEmailToWorker(ctx, accountID, *info.WorkerID, newWorker.ID); err != nil {
continue
}
}
// Release the dedicated worker
if err := s.ReleaseDedicatedWorker(ctx, orgID); err != nil {
// Log but don't fail
}
return nil
}
// MigrateEmailsFromWorker migrates all emails from a worker to other workers
func (s *workerAssignmentService) MigrateEmailsFromWorker(ctx context.Context, workerID uuid.UUID, targetFreeTier bool) error {
// Get all email accounts on this worker
accountIDs, err := s.workerRepo.GetEmailAccountsByWorkerID(ctx, workerID)
if err != nil {
return err
}
for _, accountID := range accountIDs {
// Select new worker
newWorker, err := s.SelectSharedWorker(ctx, targetFreeTier)
if err != nil {
continue
}
// Migrate
if err := s.migrateEmailToWorker(ctx, accountID, workerID, newWorker.ID); err != nil {
continue
}
}
return nil
}
// migrateEmailToWorker handles the actual migration of an email account
func (s *workerAssignmentService) migrateEmailToWorker(ctx context.Context, emailAccountID, oldWorkerID, newWorkerID uuid.UUID) error {
// 1. Update database
if err := s.workerRepo.UpdateEmailAccountWorker(ctx, emailAccountID, newWorkerID); err != nil {
return err
}
// 2. Decrement old worker count
if err := s.workerRepo.DecrementAccountCount(ctx, oldWorkerID); err != nil {
// Log but don't fail
}
// 3. Increment new worker count
if err := s.workerRepo.IncrementAccountCount(ctx, newWorkerID); err != nil {
// Log but don't fail
}
return nil
}