feat: trace task sources to fixed workflow activations and rounds
This commit is contained in:
99
internal/storage/mysql/task_workflow_source.go
Normal file
99
internal/storage/mysql/task_workflow_source.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"git.yuntuo.hk/k2safe/orbit/internal/fault"
|
||||
core "git.yuntuo.hk/k2safe/orbit/model"
|
||||
"git.yuntuo.hk/k2safe/orbit/pulse/service"
|
||||
"git.yuntuo.hk/k2safe/orbit/weave/engine"
|
||||
)
|
||||
|
||||
// readTaskWorkflowSource 只核验创建时固定的来源,不按当前激活替换历史,也不取得运行器锁。
|
||||
// 这里只向已授权任务暴露有限引用;完整流程及定义继续使用各自权限。
|
||||
func readTaskWorkflowSource(ctx context.Context, tx *sql.Tx, ns, task, queue, activation, gate core.ID, direct bool) (service.TaskWorkflowSource, error) {
|
||||
var out service.TaskWorkflowSource
|
||||
var activationTask *core.ID
|
||||
var policyRaw []byte
|
||||
var handler, handlerVersion, payloadVersion string
|
||||
err := tx.QueryRowContext(ctx, "SELECT a.workflow_id,i.definition_id,i.definition_version_id,v.version,s.id,s.`key`,s.kind,a.id,a.sequence,a.task_id,i.policy_snapshot,t.retry_of,t.handler,t.handler_version,t.payload_version FROM weave_activations a JOIN weave_steps s ON s.namespace_id=a.namespace_id AND s.workflow_id=a.workflow_id AND s.id=a.step_id JOIN weave_instances i ON i.namespace_id=a.namespace_id AND i.id=a.workflow_id AND i.gate_id=? JOIN pulse_execution_gates g ON g.namespace_id=i.namespace_id AND g.id=i.gate_id JOIN weave_definitions d ON d.namespace_id=i.namespace_id AND d.id=i.definition_id JOIN weave_definition_versions v ON v.namespace_id=i.namespace_id AND v.definition_id=i.definition_id AND v.id=i.definition_version_id JOIN pulse_tasks t ON t.namespace_id=a.namespace_id AND t.id=? AND t.owner_kind='workflow_activation' AND t.owner_id=a.id AND t.gate_id=i.gate_id AND t.queue_id=? JOIN pulse_queues q ON q.namespace_id=t.namespace_id AND q.id=t.queue_id WHERE a.namespace_id=? AND a.id=?", gate, task, queue, ns, activation).Scan(&out.WorkflowID, &out.DefinitionID, &out.DefinitionVersionID, &out.Version, &out.StepID, &out.StepKey, &out.StepKind, &out.ActivationID, &out.ActivationSequence, &activationTask, &policyRaw, &out.RetryOf, &handler, &handlerVersion, &payloadVersion)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return out, fault.Unavailable
|
||||
}
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
version, err := readWorkflowVersion(ctx, tx, ns, out.DefinitionID, out.Version)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
var policy workflowPolicy
|
||||
if version.ID != out.DefinitionVersionID || json.Unmarshal(policyRaw, &policy) != nil || policy != (workflowPolicy{1, version.ID, version.ContentDigest, version.Specification.FailurePolicy, version.Analysis.MaxTaskAttempts}) {
|
||||
return out, fault.Unavailable
|
||||
}
|
||||
program, err := engine.Compile(version.Specification)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
node, ok := program.Step(out.StepKey)
|
||||
if !ok || node.Kind != out.StepKind || out.ActivationSequence == 0 {
|
||||
return out, fault.Unavailable
|
||||
}
|
||||
contract := node.Task
|
||||
wantsParent := out.ActivationSequence > 1
|
||||
switch out.StepKind {
|
||||
case "TASK":
|
||||
if !direct || activationTask == nil || *activationTask != task || contract == nil || out.ActivationSequence > contract.Retry.MaxAttempts {
|
||||
return out, fault.Unavailable
|
||||
}
|
||||
case "POLL":
|
||||
if direct || activationTask != nil || node.Poll == nil {
|
||||
return out, fault.Unavailable
|
||||
}
|
||||
contract = &node.Poll.Task
|
||||
var first, used, maximum uint32
|
||||
var roundID core.ID
|
||||
err = tx.QueryRowContext(ctx, "SELECT r.id,r.`round`,p.first_round,p.used_rounds,p.max_rounds FROM weave_poll_rounds r JOIN weave_poll_states p ON p.namespace_id=r.namespace_id AND p.workflow_id=r.workflow_id AND p.activation_id=r.activation_id WHERE r.namespace_id=? AND r.workflow_id=? AND r.activation_id=? AND r.task_id=?", ns, out.WorkflowID, activation, task).Scan(&roundID, &out.PollRound, &first, &used, &maximum)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return out, fault.Unavailable
|
||||
}
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
if first == 0 || first > out.PollRound || out.PollRound > used || used > maximum || maximum != node.Poll.MaxRounds || out.ActivationSequence == 1 && first != 1 || out.ActivationSequence > maximum*contract.Retry.MaxAttempts {
|
||||
return out, fault.Unavailable
|
||||
}
|
||||
out.PollRoundID = &roundID
|
||||
wantsParent = wantsParent && out.PollRound == first
|
||||
default:
|
||||
return out, fault.Unavailable
|
||||
}
|
||||
if contract.QueueID != queue || contract.Handler != handler || contract.HandlerVersion != handlerVersion || contract.PayloadVersion != payloadVersion || wantsParent != (out.RetryOf != nil) {
|
||||
return out, fault.Unavailable
|
||||
}
|
||||
if wantsParent {
|
||||
if *out.RetryOf == task {
|
||||
return out, fault.Unavailable
|
||||
}
|
||||
// 只读直接父任务,使用步骤/代次唯一索引;不扫描或递归整个恢复历史。
|
||||
q := `SELECT COUNT(*) FROM weave_activations a JOIN pulse_tasks t ON t.namespace_id=a.namespace_id AND t.owner_kind='workflow_activation' AND t.owner_id=a.id AND t.gate_id=? AND t.queue_id=? AND t.id=? WHERE a.namespace_id=? AND a.workflow_id=? AND a.step_id=? AND a.sequence=? AND t.effect_uncertain=FALSE`
|
||||
args := []any{gate, queue, *out.RetryOf, ns, out.WorkflowID, out.StepID, out.ActivationSequence - 1}
|
||||
if out.StepKind == "TASK" {
|
||||
q += ` AND a.task_id=t.id AND t.state IN ('FAILED','CANCELLED')`
|
||||
} else {
|
||||
q += " AND a.task_id IS NULL AND t.state IN ('SUCCEEDED','FAILED','CANCELLED') AND EXISTS(SELECT 1 FROM weave_poll_rounds r JOIN weave_poll_states p ON p.namespace_id=r.namespace_id AND p.workflow_id=r.workflow_id AND p.activation_id=r.activation_id WHERE r.namespace_id=a.namespace_id AND r.workflow_id=a.workflow_id AND r.activation_id=a.id AND r.task_id=t.id AND r.`round`=? AND p.used_rounds=r.`round` AND p.first_round<=r.`round`)"
|
||||
args = append(args, out.PollRound)
|
||||
}
|
||||
var n int
|
||||
if err = tx.QueryRowContext(ctx, q, args...).Scan(&n); err != nil {
|
||||
return out, err
|
||||
}
|
||||
if n != 1 {
|
||||
return out, fault.Unavailable
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user