mirror of
https://github.com/oarkflow/mq.git
synced 2025-10-05 07:57:00 +08:00
update
This commit is contained in:
403
dag/migration_utils.go
Normal file
403
dag/migration_utils.go
Normal file
@@ -0,0 +1,403 @@
|
||||
package dag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MigrationUtility provides utilities to convert existing DAG configurations to workflow definitions
|
||||
type MigrationUtility struct {
|
||||
dag *DAG
|
||||
}
|
||||
|
||||
// NewMigrationUtility creates a new migration utility
|
||||
func NewMigrationUtility(dag *DAG) *MigrationUtility {
|
||||
return &MigrationUtility{
|
||||
dag: dag,
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertDAGToWorkflow converts an existing DAG to a workflow definition
|
||||
func (m *MigrationUtility) ConvertDAGToWorkflow(workflowID, workflowName, version string) (*WorkflowDefinition, error) {
|
||||
if m.dag == nil {
|
||||
return nil, fmt.Errorf("DAG is nil")
|
||||
}
|
||||
|
||||
workflow := &WorkflowDefinition{
|
||||
ID: workflowID,
|
||||
Name: workflowName,
|
||||
Description: fmt.Sprintf("Migrated from DAG: %s", m.dag.name),
|
||||
Version: version,
|
||||
Status: WorkflowStatusActive,
|
||||
Tags: []string{"migrated", "dag"},
|
||||
Category: "migrated",
|
||||
Owner: "system",
|
||||
Nodes: []WorkflowNode{},
|
||||
Edges: []WorkflowEdge{},
|
||||
Variables: make(map[string]Variable),
|
||||
Config: WorkflowConfig{
|
||||
Priority: PriorityMedium,
|
||||
Concurrency: 1,
|
||||
EnableAudit: true,
|
||||
EnableMetrics: true,
|
||||
},
|
||||
Metadata: make(map[string]interface{}),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedBy: "migration-utility",
|
||||
UpdatedBy: "migration-utility",
|
||||
}
|
||||
|
||||
// Convert DAG nodes to workflow nodes
|
||||
nodeMap := make(map[string]bool) // Track processed nodes
|
||||
|
||||
m.dag.nodes.ForEach(func(nodeID string, node *Node) bool {
|
||||
workflowNode := m.convertDAGNodeToWorkflowNode(node)
|
||||
workflow.Nodes = append(workflow.Nodes, workflowNode)
|
||||
nodeMap[nodeID] = true
|
||||
return true
|
||||
})
|
||||
|
||||
// Convert DAG edges to workflow edges
|
||||
edgeID := 1
|
||||
m.dag.nodes.ForEach(func(nodeID string, node *Node) bool {
|
||||
for _, edge := range node.Edges {
|
||||
workflowEdge := WorkflowEdge{
|
||||
ID: fmt.Sprintf("edge_%d", edgeID),
|
||||
FromNode: edge.From.ID,
|
||||
ToNode: edge.To.ID,
|
||||
Label: edge.Label,
|
||||
Priority: 1,
|
||||
Metadata: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
// Add condition for conditional edges
|
||||
if edge.Type == Iterator {
|
||||
workflowEdge.Condition = "iterator_condition"
|
||||
workflowEdge.Metadata["original_type"] = "iterator"
|
||||
}
|
||||
|
||||
workflow.Edges = append(workflow.Edges, workflowEdge)
|
||||
edgeID++
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// Add metadata about the original DAG
|
||||
workflow.Metadata["original_dag_name"] = m.dag.name
|
||||
workflow.Metadata["original_dag_key"] = m.dag.key
|
||||
workflow.Metadata["migration_timestamp"] = time.Now()
|
||||
workflow.Metadata["migration_version"] = "1.0"
|
||||
|
||||
return workflow, nil
|
||||
}
|
||||
|
||||
// convertDAGNodeToWorkflowNode converts a DAG node to a workflow node
|
||||
func (m *MigrationUtility) convertDAGNodeToWorkflowNode(dagNode *Node) WorkflowNode {
|
||||
workflowNode := WorkflowNode{
|
||||
ID: dagNode.ID,
|
||||
Name: dagNode.Label,
|
||||
Description: fmt.Sprintf("Migrated DAG node: %s", dagNode.Label),
|
||||
Position: Position{
|
||||
X: 0, // Default position - will need to be set by UI
|
||||
Y: 0,
|
||||
},
|
||||
Metadata: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
// Convert node type
|
||||
workflowNode.Type = m.convertDAGNodeType(dagNode.NodeType)
|
||||
|
||||
// Set timeout if specified
|
||||
if dagNode.Timeout > 0 {
|
||||
workflowNode.Timeout = &dagNode.Timeout
|
||||
}
|
||||
|
||||
// Create basic configuration
|
||||
workflowNode.Config = WorkflowNodeConfig{
|
||||
Variables: make(map[string]string),
|
||||
Custom: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
// Add original DAG node information to metadata
|
||||
workflowNode.Metadata["original_node_type"] = dagNode.NodeType.String()
|
||||
workflowNode.Metadata["is_ready"] = dagNode.isReady
|
||||
workflowNode.Metadata["debug"] = dagNode.Debug
|
||||
workflowNode.Metadata["is_first"] = dagNode.IsFirst
|
||||
workflowNode.Metadata["is_last"] = dagNode.IsLast
|
||||
|
||||
// Set default retry policy
|
||||
workflowNode.RetryPolicy = &RetryPolicy{
|
||||
MaxAttempts: 3,
|
||||
BackoffMs: 1000,
|
||||
Jitter: true,
|
||||
Timeout: time.Minute * 5,
|
||||
}
|
||||
|
||||
return workflowNode
|
||||
}
|
||||
|
||||
// convertDAGNodeType converts DAG node type to workflow node type
|
||||
func (m *MigrationUtility) convertDAGNodeType(dagNodeType NodeType) WorkflowNodeType {
|
||||
switch dagNodeType {
|
||||
case Function:
|
||||
return WorkflowNodeTypeTask
|
||||
case Page:
|
||||
return WorkflowNodeTypeHTML
|
||||
default:
|
||||
return WorkflowNodeTypeTask
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertWorkflowToDAG converts a workflow definition back to DAG structure
|
||||
func (m *MigrationUtility) ConvertWorkflowToDAG(workflow *WorkflowDefinition) (*DAG, error) {
|
||||
// Create new DAG
|
||||
dag := NewDAG(workflow.Name, workflow.ID, nil)
|
||||
|
||||
// Convert workflow nodes to DAG nodes
|
||||
for _, workflowNode := range workflow.Nodes {
|
||||
dagNode := m.convertWorkflowNodeToDAGNode(&workflowNode)
|
||||
dag.nodes.Set(dagNode.ID, dagNode)
|
||||
}
|
||||
|
||||
// Convert workflow edges to DAG edges
|
||||
for _, workflowEdge := range workflow.Edges {
|
||||
fromNode, fromExists := dag.nodes.Get(workflowEdge.FromNode)
|
||||
toNode, toExists := dag.nodes.Get(workflowEdge.ToNode)
|
||||
|
||||
if !fromExists || !toExists {
|
||||
continue
|
||||
}
|
||||
|
||||
edge := Edge{
|
||||
From: fromNode,
|
||||
FromSource: workflowEdge.FromNode,
|
||||
To: toNode,
|
||||
Label: workflowEdge.Label,
|
||||
Type: m.convertWorkflowEdgeType(workflowEdge),
|
||||
}
|
||||
|
||||
fromNode.Edges = append(fromNode.Edges, edge)
|
||||
}
|
||||
|
||||
return dag, nil
|
||||
}
|
||||
|
||||
// convertWorkflowNodeToDAGNode converts a workflow node to a DAG node
|
||||
func (m *MigrationUtility) convertWorkflowNodeToDAGNode(workflowNode *WorkflowNode) *Node {
|
||||
dagNode := &Node{
|
||||
ID: workflowNode.ID,
|
||||
Label: workflowNode.Name,
|
||||
NodeType: m.convertWorkflowNodeTypeToDAG(workflowNode.Type),
|
||||
Edges: []Edge{},
|
||||
isReady: true,
|
||||
}
|
||||
|
||||
// Set timeout if specified
|
||||
if workflowNode.Timeout != nil {
|
||||
dagNode.Timeout = *workflowNode.Timeout
|
||||
}
|
||||
|
||||
// Extract metadata
|
||||
if workflowNode.Metadata != nil {
|
||||
if debug, ok := workflowNode.Metadata["debug"].(bool); ok {
|
||||
dagNode.Debug = debug
|
||||
}
|
||||
if isFirst, ok := workflowNode.Metadata["is_first"].(bool); ok {
|
||||
dagNode.IsFirst = isFirst
|
||||
}
|
||||
if isLast, ok := workflowNode.Metadata["is_last"].(bool); ok {
|
||||
dagNode.IsLast = isLast
|
||||
}
|
||||
}
|
||||
|
||||
// Create a basic processor (this would need to be enhanced based on node type)
|
||||
dagNode.processor = &workflowNodeProcessor{
|
||||
node: workflowNode,
|
||||
}
|
||||
|
||||
return dagNode
|
||||
}
|
||||
|
||||
// convertWorkflowNodeTypeToDAG converts workflow node type to DAG node type
|
||||
func (m *MigrationUtility) convertWorkflowNodeTypeToDAG(workflowNodeType WorkflowNodeType) NodeType {
|
||||
switch workflowNodeType {
|
||||
case WorkflowNodeTypeHTML:
|
||||
return Page
|
||||
case WorkflowNodeTypeTask:
|
||||
return Function
|
||||
default:
|
||||
return Function
|
||||
}
|
||||
}
|
||||
|
||||
// convertWorkflowEdgeType converts workflow edge to DAG edge type
|
||||
func (m *MigrationUtility) convertWorkflowEdgeType(workflowEdge WorkflowEdge) EdgeType {
|
||||
// Check metadata for original type
|
||||
if workflowEdge.Metadata != nil {
|
||||
if originalType, ok := workflowEdge.Metadata["original_type"].(string); ok {
|
||||
if originalType == "iterator" {
|
||||
return Iterator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for conditions to determine edge type
|
||||
if workflowEdge.Condition != "" {
|
||||
return Iterator
|
||||
}
|
||||
|
||||
return Simple
|
||||
}
|
||||
|
||||
// ValidateWorkflowDefinition validates a workflow definition for common issues
|
||||
func (m *MigrationUtility) ValidateWorkflowDefinition(workflow *WorkflowDefinition) []string {
|
||||
var issues []string
|
||||
|
||||
// Check required fields
|
||||
if workflow.ID == "" {
|
||||
issues = append(issues, "Workflow ID is required")
|
||||
}
|
||||
if workflow.Name == "" {
|
||||
issues = append(issues, "Workflow name is required")
|
||||
}
|
||||
if workflow.Version == "" {
|
||||
issues = append(issues, "Workflow version is required")
|
||||
}
|
||||
|
||||
// Check nodes
|
||||
if len(workflow.Nodes) == 0 {
|
||||
issues = append(issues, "Workflow must have at least one node")
|
||||
}
|
||||
|
||||
// Check for duplicate node IDs
|
||||
nodeIDs := make(map[string]bool)
|
||||
for _, node := range workflow.Nodes {
|
||||
if node.ID == "" {
|
||||
issues = append(issues, "Node ID is required")
|
||||
continue
|
||||
}
|
||||
if nodeIDs[node.ID] {
|
||||
issues = append(issues, fmt.Sprintf("Duplicate node ID: %s", node.ID))
|
||||
}
|
||||
nodeIDs[node.ID] = true
|
||||
}
|
||||
|
||||
// Validate edges
|
||||
for _, edge := range workflow.Edges {
|
||||
if !nodeIDs[edge.FromNode] {
|
||||
issues = append(issues, fmt.Sprintf("Edge references non-existent from node: %s", edge.FromNode))
|
||||
}
|
||||
if !nodeIDs[edge.ToNode] {
|
||||
issues = append(issues, fmt.Sprintf("Edge references non-existent to node: %s", edge.ToNode))
|
||||
}
|
||||
}
|
||||
|
||||
// Check for cycles (simplified check)
|
||||
if m.hasSimpleCycle(workflow) {
|
||||
issues = append(issues, "Workflow contains cycles which may cause infinite loops")
|
||||
}
|
||||
|
||||
return issues
|
||||
}
|
||||
|
||||
// hasSimpleCycle performs a simple cycle detection
|
||||
func (m *MigrationUtility) hasSimpleCycle(workflow *WorkflowDefinition) bool {
|
||||
// Build adjacency list
|
||||
adj := make(map[string][]string)
|
||||
for _, edge := range workflow.Edges {
|
||||
adj[edge.FromNode] = append(adj[edge.FromNode], edge.ToNode)
|
||||
}
|
||||
|
||||
// Track visited nodes
|
||||
visited := make(map[string]bool)
|
||||
recStack := make(map[string]bool)
|
||||
|
||||
// Check each node for cycles
|
||||
for _, node := range workflow.Nodes {
|
||||
if !visited[node.ID] {
|
||||
if m.hasCycleDFS(node.ID, adj, visited, recStack) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// hasCycleDFS performs DFS-based cycle detection
|
||||
func (m *MigrationUtility) hasCycleDFS(nodeID string, adj map[string][]string, visited, recStack map[string]bool) bool {
|
||||
visited[nodeID] = true
|
||||
recStack[nodeID] = true
|
||||
|
||||
// Visit all adjacent nodes
|
||||
for _, neighbor := range adj[nodeID] {
|
||||
if !visited[neighbor] {
|
||||
if m.hasCycleDFS(neighbor, adj, visited, recStack) {
|
||||
return true
|
||||
}
|
||||
} else if recStack[neighbor] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
recStack[nodeID] = false
|
||||
return false
|
||||
}
|
||||
|
||||
// GenerateWorkflowTemplate creates a basic workflow template
|
||||
func (m *MigrationUtility) GenerateWorkflowTemplate(name, id string) *WorkflowDefinition {
|
||||
return &WorkflowDefinition{
|
||||
ID: id,
|
||||
Name: name,
|
||||
Description: "Generated workflow template",
|
||||
Version: "1.0.0",
|
||||
Status: WorkflowStatusDraft,
|
||||
Tags: []string{"template"},
|
||||
Category: "template",
|
||||
Owner: "system",
|
||||
Nodes: []WorkflowNode{
|
||||
{
|
||||
ID: "start_node",
|
||||
Name: "Start",
|
||||
Type: WorkflowNodeTypeTask,
|
||||
Description: "Starting node",
|
||||
Position: Position{X: 100, Y: 100},
|
||||
Config: WorkflowNodeConfig{
|
||||
Script: "echo 'Workflow started'",
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "end_node",
|
||||
Name: "End",
|
||||
Type: WorkflowNodeTypeTask,
|
||||
Description: "Ending node",
|
||||
Position: Position{X: 300, Y: 100},
|
||||
Config: WorkflowNodeConfig{
|
||||
Script: "echo 'Workflow completed'",
|
||||
},
|
||||
},
|
||||
},
|
||||
Edges: []WorkflowEdge{
|
||||
{
|
||||
ID: "edge_1",
|
||||
FromNode: "start_node",
|
||||
ToNode: "end_node",
|
||||
Label: "Proceed",
|
||||
Priority: 1,
|
||||
},
|
||||
},
|
||||
Variables: make(map[string]Variable),
|
||||
Config: WorkflowConfig{
|
||||
Priority: PriorityMedium,
|
||||
Concurrency: 1,
|
||||
EnableAudit: true,
|
||||
EnableMetrics: true,
|
||||
},
|
||||
Metadata: make(map[string]interface{}),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedBy: "migration-utility",
|
||||
UpdatedBy: "migration-utility",
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user