feat: expose workflow runtime through HTTP and Go SDK
This commit is contained in:
@@ -17,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
// WorkflowInstanceStore 以应用额度锁串行受理,在一个事务内建立全部归属及初始工作。
|
||||
// 当前为内部存储适配层,公共HTTP和SDK在消费运行器接通后开放。
|
||||
// 公共HTTP与内部运行器复用同一套固定版本、幂等与事务规则。
|
||||
type WorkflowInstanceStore struct{ *TaskStore }
|
||||
|
||||
func NewWorkflowInstanceStore(db *sql.DB) *WorkflowInstanceStore {
|
||||
@@ -226,67 +226,77 @@ func (s *WorkflowInstanceStore) Get(ctx context.Context, t auth.Token, id core.I
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
|
||||
// readWorkflowForQuery 固定定义授权全部队列后再锁实例,保持与运行器相同的父子锁顺序。
|
||||
func readWorkflowForQuery(ctx context.Context, tx *sql.Tx, ns, id core.ID, p auth.Permissions) (instance.View, core.ID, definition.VersionView, error) {
|
||||
v, gate, err := readWorkflowInstance(ctx, tx, ns, id, false)
|
||||
var version definition.VersionView
|
||||
if err != nil {
|
||||
return v, gate, version, err
|
||||
}
|
||||
version, err = instanceVersion(ctx, tx, v)
|
||||
if err != nil {
|
||||
return v, gate, version, err
|
||||
}
|
||||
if err = authorizeWorkflowQueues(ctx, tx, ns, p, version.Analysis.Queues); err != nil {
|
||||
return v, gate, version, err
|
||||
}
|
||||
v, gate, err = readWorkflowInstance(ctx, tx, ns, id, true)
|
||||
return v, gate, version, err
|
||||
}
|
||||
func (s *WorkflowInstanceStore) Steps(ctx context.Context, t auth.Token, id core.ID) (instance.StepsView, error) {
|
||||
out := instance.StepsView{InstanceID: id, Steps: []instance.StepView{}}
|
||||
err := s.transaction(ctx, t, instance.ReadAction, func(tx *sql.Tx, p auth.Permissions) error {
|
||||
// 先核对队列,后锁实例;未来运行器遵守应用→队列→实例,读取不得逆序锁队列。
|
||||
v, _, err := readWorkflowInstance(ctx, tx, t.NamespaceID, id, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
version, err := instanceVersion(ctx, tx, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := authorizeWorkflowQueues(ctx, tx, t.NamespaceID, p, version.Analysis.Queues); err != nil {
|
||||
return err
|
||||
}
|
||||
var gate core.ID
|
||||
v, gate, err = readWorkflowInstance(ctx, tx, t.NamespaceID, id, true)
|
||||
v, gate, version, err := readWorkflowForQuery(ctx, tx, t.NamespaceID, id, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out.Revision = v.Revision
|
||||
program, err := engine.Compile(version.Specification)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows, err := tx.QueryContext(ctx, "SELECT s.id,s.`key`,s.kind,s.state,s.revision,s.current_activation_id,a.task_id,a.id,a.state,task.id,task.queue_id FROM weave_steps s LEFT JOIN weave_activations a ON a.namespace_id=s.namespace_id AND a.id=s.current_activation_id AND a.workflow_id=s.workflow_id AND a.step_id=s.id LEFT JOIN pulse_tasks task ON task.namespace_id=s.namespace_id AND task.id=a.task_id AND task.owner_kind='workflow_activation' AND task.owner_id=a.id AND task.gate_id=? WHERE s.namespace_id=? AND s.workflow_id=? AND s.`key`<>? ORDER BY s.`key` LIMIT 129", gate, t.NamespaceID, id, workflowRootKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var step instance.StepView
|
||||
var activation *core.ID
|
||||
var task, queue *core.ID
|
||||
var state sql.NullString
|
||||
if err := rows.Scan(&step.ID, &step.Key, &step.Kind, &step.State, &step.Revision, &step.ActivationID, &step.TaskID, &activation, &state, &task, &queue); err != nil {
|
||||
return err
|
||||
}
|
||||
node, ok := program.Step(step.Key)
|
||||
if !ok || node.Kind != string(step.Kind) {
|
||||
return errors.New("workflow step definition mismatch")
|
||||
}
|
||||
inactive := step.State == "BLOCKED" || step.State == "SKIPPED"
|
||||
if inactive && step.ActivationID != nil || !inactive && (step.ActivationID == nil || activation == nil || *activation != *step.ActivationID || state.String != string(step.State)) {
|
||||
return errors.New("workflow current activation mismatch")
|
||||
}
|
||||
if !inactive && (step.Kind == "TASK") != (step.TaskID != nil) {
|
||||
return errors.New("workflow direct task ownership mismatch")
|
||||
}
|
||||
if step.TaskID != nil && (task == nil || queue == nil || *task != *step.TaskID || node.Task == nil || *queue != node.Task.QueueID) {
|
||||
return errors.New("workflow task association invalid")
|
||||
}
|
||||
out.Steps = append(out.Steps, step)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(out.Steps) != int(version.Analysis.Steps) {
|
||||
return errors.New("workflow step set incomplete")
|
||||
}
|
||||
return nil
|
||||
out.Steps, err = readWorkflowStepViews(ctx, tx, t.NamespaceID, id, gate, version, "")
|
||||
return err
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
func readWorkflowStepViews(ctx context.Context, tx *sql.Tx, ns, id, gate core.ID, version definition.VersionView, key string) ([]instance.StepView, error) {
|
||||
items := []instance.StepView{}
|
||||
program, err := engine.Compile(version.Specification)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, err := tx.QueryContext(ctx, "SELECT s.id,s.`key`,s.kind,s.state,s.revision,s.current_activation_id,a.task_id,COALESCE(a.sequence,0),a.id,a.state,task.id,task.queue_id FROM weave_steps s LEFT JOIN weave_activations a ON a.namespace_id=s.namespace_id AND a.id=s.current_activation_id AND a.workflow_id=s.workflow_id AND a.step_id=s.id LEFT JOIN pulse_tasks task ON task.namespace_id=s.namespace_id AND task.id=a.task_id AND task.owner_kind='workflow_activation' AND task.owner_id=a.id AND task.gate_id=? WHERE s.namespace_id=? AND s.workflow_id=? AND s.`key`<>? AND (?='' OR s.`key`=?) ORDER BY s.`key` LIMIT 129", gate, ns, id, workflowRootKey, key, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var step instance.StepView
|
||||
var activation *core.ID
|
||||
var task, queue *core.ID
|
||||
var state sql.NullString
|
||||
if err := rows.Scan(&step.ID, &step.Key, &step.Kind, &step.State, &step.Revision, &step.ActivationID, &step.TaskID, &step.ActivationSequence, &activation, &state, &task, &queue); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, ok := program.Step(step.Key)
|
||||
if !ok || node.Kind != string(step.Kind) {
|
||||
return nil, errors.New("workflow step definition mismatch")
|
||||
}
|
||||
inactive := step.State == "BLOCKED" || step.State == "SKIPPED"
|
||||
if inactive && step.ActivationID != nil || !inactive && (step.ActivationSequence == 0 || step.ActivationID == nil || activation == nil || *activation != *step.ActivationID || state.String != string(step.State)) {
|
||||
return nil, errors.New("workflow current activation mismatch")
|
||||
}
|
||||
if !inactive && (step.Kind == "TASK") != (step.TaskID != nil) {
|
||||
return nil, errors.New("workflow direct task ownership mismatch")
|
||||
}
|
||||
if step.TaskID != nil && (task == nil || queue == nil || *task != *step.TaskID || node.Task == nil || *queue != node.Task.QueueID) {
|
||||
return nil, errors.New("workflow task association invalid")
|
||||
}
|
||||
items = append(items, step)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key == "" && len(items) != int(version.Analysis.Steps) {
|
||||
return nil, errors.New("workflow step set incomplete")
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user