Files

91 lines
4.7 KiB
Go

package main
import (
"context"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
// verifiedTask 保存已核验分支的任务和尝试,最终与本地调用逐个关联。
type verifiedTask struct {
sequence int // sequence 实例请求序号。
taskID string // taskID 分支唯一任务身份。
attemptID string // attemptID 唯一成功尝试身份。
}
func verifySteps(ctx context.Context, cfg config, api workflowAPI, instance orbit.WorkflowInstance, runID string, sequence int, steps orbit.WorkflowSteps) ([]verifiedTask, string) {
if steps.InstanceID != instance.ID || steps.Revision != instance.Revision || len(steps.Steps) != 3 {
return nil, "STEP_COUNT_OR_PARENT_MISMATCH"
}
keys, ids, tasks, activations := map[string]bool{}, map[string]bool{}, map[string]bool{}, map[string]bool{}
for _, s := range steps.Steps {
kind := map[string]string{"pair": "PARALLEL", "left": "TASK", "right": "TASK"}[s.Key]
if kind == "" || s.Kind != kind || s.State != "SUCCEEDED" || !validID(s.ID) || s.Revision == 0 || ids[s.ID] || keys[s.Key] || s.ActivationSequence != 1 || s.ActivationID == nil || !validID(*s.ActivationID) || activations[*s.ActivationID] {
return nil, "STEP_MISMATCH"
}
keys[s.Key] = true
ids[s.ID] = true
activations[*s.ActivationID] = true
if kind == "PARALLEL" {
if s.TaskID != nil {
return nil, "JOIN_TASK_MISMATCH"
}
continue
}
if s.TaskID == nil || !validID(*s.TaskID) || tasks[*s.TaskID] {
return nil, "DUPLICATE_OR_INVALID_TASK"
}
tasks[*s.TaskID] = true
}
proofs := make([]verifiedTask, 0, 2)
for _, step := range steps.Steps {
if step.Kind != "TASK" {
continue
}
call, stop := context.WithTimeout(ctx, cfg.Timeout)
task, err := api.GetTask(call, *step.TaskID)
stop()
if err != nil {
return nil, "TASK_" + classify(err)
}
result, ok := decodePayload(task.Result)
if task.ID != *step.TaskID || !matchesTask(task, cfg, runID, sequence) || task.NamespaceID != instance.NamespaceID || task.State != orbit.TaskSucceeded || task.EffectUncertain || task.AttemptCount != 1 || task.CompletedAt == nil || task.CancelRequestedAt != nil || !ok || result != (workflowPayload{RunID: runID, Sequence: sequence}) {
return nil, "TASK_MISMATCH"
}
if task.CreatedAt.Before(instance.CreatedAt) || !task.AvailableAt.Equal(task.CreatedAt) || !task.DeadlineAt.Equal(instance.DeadlineAt) || task.CompletedAt.Before(task.CreatedAt) || instance.CompletedAt == nil || task.CompletedAt.After(*instance.CompletedAt) {
return nil, "TASK_TIMESTAMP_MISMATCH"
}
call, stop = context.WithTimeout(ctx, cfg.Timeout)
history, err := api.ListTaskAttempts(call, task.ID, orbit.HistoryPageRequest{Limit: 2})
stop()
if err != nil {
return nil, "HISTORY_" + classify(err)
}
if len(history.Items) != 1 || history.NextCursor != "" {
return nil, "ATTEMPT_COUNT_MISMATCH"
}
a := history.Items[0]
if !validID(a.ID) || a.TaskID != task.ID || a.State != orbit.AttemptSucceeded || a.Epoch != 1 || a.AuthorizedAt == nil || a.EndedAt == nil || a.ErrorCode != "" || a.Retryable || a.ConcurrencyToken != 0 {
return nil, "ATTEMPT_MISMATCH"
}
if a.CreatedAt.Before(task.AvailableAt) || a.AuthorizedAt.Before(a.CreatedAt) || a.EndedAt.Before(*a.AuthorizedAt) || !a.EndedAt.Equal(*task.CompletedAt) {
return nil, "ATTEMPT_TIMESTAMP_MISMATCH"
}
call, stop = context.WithTimeout(ctx, cfg.Timeout)
source, err := api.GetTaskSource(call, task.ID)
stop()
if err != nil {
return nil, "SOURCE_" + classify(err)
}
s := source.Workflow
if source.TaskID != task.ID || source.Kind != "WORKFLOW" || source.RetryOf != nil || source.Schedule != nil || s == nil || s.WorkflowID != instance.ID || s.DefinitionID != instance.DefinitionID || s.DefinitionVersionID != instance.DefinitionVersionID || s.Version != instance.Version || s.StepID != step.ID || s.StepKey != step.Key || s.StepKind != "TASK" || s.ActivationID != *step.ActivationID || s.ActivationSequence != 1 || s.PollRound != 0 || s.PollRoundID != nil || s.RetryOf != nil {
return nil, "SOURCE_MISMATCH"
}
proofs = append(proofs, verifiedTask{sequence: sequence, taskID: task.ID, attemptID: a.ID})
}
return proofs, ""
}
func matchesTask(t orbit.Task, c config, runID string, sequence int) bool {
input, ok := decodePayload(t.Payload)
return validID(t.ID) && t.QueueID == c.QueueID && t.Handler == handlerName(runID) && t.HandlerVersion == "v1" && t.PayloadVersion == "v1" && t.BusinessKey == "" && t.ConcurrencyKey == "" && t.Priority == 0 && t.RetryOf == nil && t.StartDeadlineAt == nil && t.RecoverySafety == orbit.RecoveryReconcileOnly && t.Retry == (orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: uint64(c.RunTimeout.Milliseconds())}) && ok && input == (workflowPayload{RunID: runID, Sequence: sequence})
}