207 lines
7.8 KiB
Go
207 lines
7.8 KiB
Go
package mysql
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
|
|
"git.yuntuo.hk/k2safe/orbit/internal/fault"
|
|
core "git.yuntuo.hk/k2safe/orbit/model"
|
|
"git.yuntuo.hk/k2safe/orbit/platform/auth"
|
|
"git.yuntuo.hk/k2safe/orbit/pulse/service"
|
|
"git.yuntuo.hk/k2safe/orbit/weave/definition"
|
|
"git.yuntuo.hk/k2safe/orbit/weave/engine"
|
|
"git.yuntuo.hk/k2safe/orbit/weave/instance"
|
|
)
|
|
|
|
type workflowHistoryParent struct {
|
|
view instance.View
|
|
gate core.ID
|
|
version definition.VersionView
|
|
step instance.StepView
|
|
node definition.Step
|
|
}
|
|
|
|
func readWorkflowHistoryParent(ctx context.Context, tx *sql.Tx, ns, id core.ID, key string, p auth.Permissions) (workflowHistoryParent, error) {
|
|
var h workflowHistoryParent
|
|
var err error
|
|
h.view, h.gate, h.version, err = readWorkflowForQuery(ctx, tx, ns, id, p)
|
|
if err != nil {
|
|
return h, err
|
|
}
|
|
steps, err := readWorkflowStepViews(ctx, tx, ns, id, h.gate, h.version, key)
|
|
if err != nil {
|
|
return h, err
|
|
}
|
|
if len(steps) != 1 {
|
|
return h, fault.NotFound
|
|
}
|
|
h.step = steps[0]
|
|
program, err := engine.Compile(h.version.Specification)
|
|
if err != nil {
|
|
return h, err
|
|
}
|
|
h.node, _ = program.Step(key)
|
|
return h, nil
|
|
}
|
|
|
|
const activationHistorySelect = `SELECT a.id,a.step_id,a.sequence,a.state,a.revision,a.task_id,a.created_at,a.updated_at,a.completed_at,t.id,t.queue_id FROM weave_activations a LEFT JOIN pulse_tasks t ON t.namespace_id=a.namespace_id AND t.id=a.task_id AND t.owner_kind='workflow_activation' AND t.owner_id=a.id AND t.gate_id=? WHERE a.namespace_id=? AND a.workflow_id=? AND a.step_id=?`
|
|
|
|
func scanWorkflowActivation(row scanner, h workflowHistoryParent) (instance.ActivationSummary, error) {
|
|
var a instance.ActivationSummary
|
|
var task, queue *core.ID
|
|
err := row.Scan(&a.ID, &a.StepID, &a.Sequence, &a.State, &a.Revision, &a.TaskID, &a.CreatedAt, &a.UpdatedAt, &a.CompletedAt, &task, &queue)
|
|
if err != nil {
|
|
return a, err
|
|
}
|
|
if a.StepID != h.step.ID || a.Sequence == 0 || (h.node.Task != nil) != (a.TaskID != nil) {
|
|
return a, fault.Unavailable
|
|
}
|
|
if a.TaskID != nil && (task == nil || queue == nil || *task != *a.TaskID || *queue != h.node.Task.QueueID) {
|
|
return a, fault.Unavailable
|
|
}
|
|
return a, nil
|
|
}
|
|
func readHistoryActivation(ctx context.Context, tx *sql.Tx, h workflowHistoryParent, activation core.ID) (instance.ActivationSummary, error) {
|
|
a, err := scanWorkflowActivation(tx.QueryRowContext(ctx, activationHistorySelect+` AND a.id=?`, h.gate, h.view.NamespaceID, h.view.ID, h.step.ID, activation), h)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return a, fault.NotFound
|
|
}
|
|
return a, err
|
|
}
|
|
|
|
func (s *WorkflowInstanceStore) ListActivations(ctx context.Context, t auth.Token, id core.ID, key string, in service.HistoryPageInput) (instance.ActivationPage, error) {
|
|
out := instance.ActivationPage{InstanceID: id, Items: []instance.ActivationSummary{}}
|
|
err := s.transaction(ctx, t, instance.ReadAction, func(tx *sql.Tx, p auth.Permissions) error {
|
|
h, err := readWorkflowHistoryParent(ctx, tx, t.NamespaceID, id, key, p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Revision, out.StepID, out.CurrentActivationID = h.view.Revision, h.step.ID, h.step.ActivationID
|
|
q := activationHistorySelect
|
|
args := []any{h.gate, t.NamespaceID, id, h.step.ID}
|
|
if in.Cursor != "" {
|
|
c, err := decodeHistoryCursorKind(in.Cursor, t.NamespaceID, id, h.step.ID, "workflow-activations")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
q += ` AND a.sequence<?`
|
|
args = append(args, c.Epoch)
|
|
}
|
|
q += ` ORDER BY a.sequence DESC LIMIT ?`
|
|
args = append(args, in.Limit+1)
|
|
rows, err := tx.QueryContext(ctx, q, args...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
a, err := scanWorkflowActivation(rows, h)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Items = append(out.Items, a)
|
|
}
|
|
if err = rows.Err(); err != nil {
|
|
return err
|
|
}
|
|
if len(out.Items) > in.Limit {
|
|
out.Items = out.Items[:in.Limit]
|
|
last := out.Items[len(out.Items)-1]
|
|
out.NextCursor, err = encodeHistoryCursor(historyCursor{Version: 1, Kind: "workflow-activations", Namespace: t.NamespaceID, Task: id, Attempt: h.step.ID, Epoch: uint64(last.Sequence)})
|
|
}
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return instance.ActivationPage{}, err
|
|
}
|
|
return out, nil
|
|
}
|
|
func (s *WorkflowInstanceStore) GetActivation(ctx context.Context, t auth.Token, id core.ID, key string, activation core.ID) (instance.ActivationDetail, error) {
|
|
out := instance.ActivationDetail{InstanceID: id}
|
|
err := s.transaction(ctx, t, instance.ReadAction, func(tx *sql.Tx, p auth.Permissions) error {
|
|
h, err := readWorkflowHistoryParent(ctx, tx, t.NamespaceID, id, key, p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out.Revision, out.Step = h.view.Revision, h.step
|
|
out.Activation, err = readHistoryActivation(ctx, tx, h, activation)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var input, output []byte
|
|
if err = tx.QueryRowContext(ctx, `SELECT input,output FROM weave_activations WHERE namespace_id=? AND workflow_id=? AND step_id=? AND id=?`, t.NamespaceID, id, h.step.ID, activation).Scan(&input, &output); err != nil {
|
|
return err
|
|
}
|
|
out.Input, out.Output = input, output
|
|
out.Poll, out.Wait, err = readWorkflowActivationAttachments(ctx, tx, t.NamespaceID, id, h.gate, key, string(h.step.Kind), activation, out.Activation.Sequence, h.version)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return instance.ActivationDetail{}, err
|
|
}
|
|
return out, nil
|
|
}
|
|
func (s *WorkflowInstanceStore) ListPollRounds(ctx context.Context, t auth.Token, id core.ID, key string, activation core.ID, in service.HistoryPageInput) (instance.PollRoundPage, error) {
|
|
out := instance.PollRoundPage{InstanceID: id, ActivationID: activation, Items: []instance.PollRoundSummary{}}
|
|
err := s.transaction(ctx, t, instance.ReadAction, func(tx *sql.Tx, p auth.Permissions) error {
|
|
h, err := readWorkflowHistoryParent(ctx, tx, t.NamespaceID, id, key, p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a, err := readHistoryActivation(ctx, tx, h, activation)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if h.node.Poll == nil {
|
|
return fault.Invalid("round history requires a POLL activation")
|
|
}
|
|
out.Revision = h.view.Revision
|
|
poll, _, err := readWorkflowActivationAttachments(ctx, tx, t.NamespaceID, id, h.gate, key, string(h.step.Kind), activation, a.Sequence, h.version)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
q := "SELECT r.id,r.activation_id,r.`round`,r.task_id,r.created_at,t.id,t.queue_id FROM weave_poll_rounds r LEFT JOIN pulse_tasks t ON t.namespace_id=r.namespace_id AND t.id=r.task_id AND t.owner_kind='workflow_activation' AND t.owner_id=r.activation_id AND t.gate_id=? WHERE r.namespace_id=? AND r.workflow_id=? AND r.activation_id=?"
|
|
args := []any{h.gate, t.NamespaceID, id, activation}
|
|
if in.Cursor != "" {
|
|
c, err := decodeHistoryCursorKind(in.Cursor, t.NamespaceID, id, activation, "workflow-rounds")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
q += " AND r.`round`<?"
|
|
args = append(args, c.Epoch)
|
|
}
|
|
q += " ORDER BY r.`round` DESC LIMIT ?"
|
|
args = append(args, in.Limit+1)
|
|
rows, err := tx.QueryContext(ctx, q, args...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var round instance.PollRoundSummary
|
|
var task, queue *core.ID
|
|
if err = rows.Scan(&round.ID, &round.ActivationID, &round.Round, &round.TaskID, &round.CreatedAt, &task, &queue); err != nil {
|
|
return err
|
|
}
|
|
if task == nil || queue == nil || *task != round.TaskID || *queue != h.node.Poll.Task.QueueID || round.Round < poll.FirstRound || round.Round > poll.UsedRounds {
|
|
return fault.Unavailable
|
|
}
|
|
out.Items = append(out.Items, round)
|
|
}
|
|
if err = rows.Err(); err != nil {
|
|
return err
|
|
}
|
|
if len(out.Items) > in.Limit {
|
|
out.Items = out.Items[:in.Limit]
|
|
last := out.Items[len(out.Items)-1]
|
|
out.NextCursor, err = encodeHistoryCursor(historyCursor{Version: 1, Kind: "workflow-rounds", Namespace: t.NamespaceID, Task: id, Attempt: activation, Epoch: uint64(last.Round)})
|
|
}
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return instance.PollRoundPage{}, err
|
|
}
|
|
return out, nil
|
|
}
|