Files
go-sip/internal/dispatcher/control_worker.go
T

84 lines
3.1 KiB
Go

package dispatcher
import (
"context"
"errors"
"fmt"
"log/slog"
"time"
agentv1 "git.ipao.vip/rogee/go-sip/gen/agent/v1"
)
type TaskController interface {
Control(context.Context, string, *agentv1.ExecutionBinding, agentv1.ControlAction, agentv1.ActiveCallPolicy) (*agentv1.ApplyTaskControlResponse, error)
}
// ProcessTaskControls retries only the original control, never an execution or
// originate. Uncertain acknowledgements leave the durable target pending.
func (d *Dispatcher) ProcessTaskControls(ctx context.Context, controller TaskController, limit int) (int, error) {
if controller == nil {
return 0, errors.New("task controller is required")
}
ctx, cancelBatch := context.WithTimeout(ctx, 10*time.Second)
defer cancelBatch()
// ponytail: one Agent per P1 Dispatcher; serialization orders control against
// last execution admission. Revisit granularity only if this scope expands.
d.executionMu.Lock()
defer d.executionMu.Unlock()
targets, err := d.store.PendingTaskControls(limit)
if err != nil {
return 0, err
}
completed := 0
var failures []error
for _, target := range targets {
if err := ctx.Err(); err != nil {
return completed, errors.Join(append(failures, err)...)
}
if target.Binding == nil || target.AgentID == "" {
failures = append(failures, fmt.Errorf("control %s execution %s has no durable Agent assignment", target.CommandID, target.ExecutionID))
continue
}
var action agentv1.ControlAction
switch target.Action {
case "pause":
action = agentv1.ControlAction_CONTROL_ACTION_PAUSE
case "resume":
action = agentv1.ControlAction_CONTROL_ACTION_RESUME
case "stop":
action = agentv1.ControlAction_CONTROL_ACTION_STOP
default:
failures = append(failures, errors.New("invalid persisted control action"))
continue
}
var policy agentv1.ActiveCallPolicy
switch target.Policy {
case "drain":
policy = agentv1.ActiveCallPolicy_ACTIVE_CALL_POLICY_DRAIN
case "hangup":
policy = agentv1.ActiveCallPolicy_ACTIVE_CALL_POLICY_HANGUP
default:
failures = append(failures, errors.New("invalid persisted active-call policy"))
continue
}
attemptCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
response, err := controller.Control(attemptCtx, target.AgentID, target.Binding, action, policy)
cancel()
if err == nil && (response.GetReceipt().GetResult() != agentv1.ResultCode_RESULT_CODE_APPLIED || response.GetAppliedTaskRevision() != target.ExpectedRevision+1) {
err = errors.New("Agent has not confirmed the requested applied revision")
}
if err == nil {
err = d.store.CompleteTaskControl(target, response.AppliedTaskRevision)
}
if err != nil {
slog.Error("task control remains pending", "command_id", target.CommandID, "execution_id", target.ExecutionID, "error", err)
failures = append(failures, fmt.Errorf("control %s: %w", target.CommandID, err))
continue
}
slog.Info("Agent task control application persisted", "command_id", target.CommandID, "execution_id", target.ExecutionID, "revision", response.AppliedTaskRevision)
completed++
}
return completed, errors.Join(failures...)
}