go与python的最佳实践
This commit is contained in:
parent
d78dc1fb95
commit
55681ef5c4
19
server/activities/activities.go
Normal file
19
server/activities/activities.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package activities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddSuffixActivity appends a fixed suffix to the input data.
|
||||||
|
func AddSuffixActivity(ctx context.Context, data string) (string, error) {
|
||||||
|
suffixes := []string{
|
||||||
|
"-one", "-two", "-three", "-four", "-five",
|
||||||
|
"-six", "-seven", "-eight", "-nine", "-ten",
|
||||||
|
}
|
||||||
|
suffix := suffixes[rand.Intn(len(suffixes))]
|
||||||
|
result := fmt.Sprintf("%s%s", data, suffix)
|
||||||
|
fmt.Println("Go Activity: Modified data to:", result)
|
||||||
|
return result, nil
|
||||||
|
}
|
@ -1,23 +0,0 @@
|
|||||||
package activity
|
|
||||||
|
|
||||||
// 定义 Temporal Activity 接口
|
|
||||||
import (
|
|
||||||
"beacon/server/gen/pb" // 替换为你的模块路径
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 定义活动接口,Python Worker 将实现这些接口
|
|
||||||
// Temporal Go SDK 会在编译时通过 go-temporal 插件自动生成这些接口的实现桩
|
|
||||||
// 使得你可以直接调用这些接口,而实际执行在 Python Worker 中。
|
|
||||||
|
|
||||||
// RunApiTest 是执行接口测试的活动
|
|
||||||
func RunApiTest(ctx context.Context, req *pb.ApiTestRequest) (*pb.ApiTestResult, error) {
|
|
||||||
// 实际调用会被转发到 Python Worker
|
|
||||||
return nil, nil // Go 侧不需要实现,由 Temporal SDK 代理
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunUiTest 是执行 UI 测试的活动
|
|
||||||
func RunUiTest(ctx context.Context, req *pb.UiTestRequest) (*pb.UiTestResult, error) {
|
|
||||||
// 实际调用会被转发到 Python Worker
|
|
||||||
return nil, nil // Go 侧不需要实现,由 Temporal SDK 代理
|
|
||||||
}
|
|
49
server/client/main.go
Normal file
49
server/client/main.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"beacon/server/workflows"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"go.temporal.io/sdk/client"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
log.Fatalln("Usage: go run main.go <data>")
|
||||||
|
}
|
||||||
|
inputData := os.Args[1]
|
||||||
|
|
||||||
|
// Create a Temporal client using the new Dial method.
|
||||||
|
c, err := client.Dial(client.Options{HostPort: "temporal.newai.day:17233"})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Unable to create Temporal client", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
// Generate a unique Workflow ID.
|
||||||
|
workflowID := "data-processing-workflow-" + uuid.New().String()
|
||||||
|
|
||||||
|
// Set up Workflow start options.
|
||||||
|
workflowOptions := client.StartWorkflowOptions{
|
||||||
|
ID: workflowID,
|
||||||
|
TaskQueue: "data-processing-task-queue",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the Workflow.
|
||||||
|
we, err := c.ExecuteWorkflow(context.Background(), workflowOptions, workflows.TestRunWorkflow, inputData)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Unable to execute Workflow", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the Workflow to complete and get the result.
|
||||||
|
var result string
|
||||||
|
err = we.Get(context.Background(), &result)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Unable to get Workflow result", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Processed Data:", result)
|
||||||
|
}
|
@ -1,61 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
// Go 服务端入口,触发 Workflow
|
|
||||||
import (
|
|
||||||
"beacon/server/gen/pb"
|
|
||||||
"beacon/server/workflow"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"go.temporal.io/sdk/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// 创建 Temporal 客户端
|
|
||||||
c, err := client.Dial(client.Options{
|
|
||||||
HostPort: "temporal.newai.day:17233", // 根据你的 Temporal Server 配置
|
|
||||||
Namespace: "default",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Unable to create Temporal client: %v", err)
|
|
||||||
}
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
// 模拟一个触发测试的事件 (例如来自 Web UI 或 CI/CD)
|
|
||||||
runID := uuid.New().String()
|
|
||||||
testInput := &pb.TestRunInput{
|
|
||||||
RunId: runID,
|
|
||||||
EnvironmentUrl: "https://example.com",
|
|
||||||
Tags: []string{"smoke", "critical"},
|
|
||||||
RunApiTests: true,
|
|
||||||
RunUiTests: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
workflowOptions := client.StartWorkflowOptions{
|
|
||||||
ID: "test_workflow_" + runID,
|
|
||||||
TaskQueue: "test-task-queue", // 保持与 Python Worker 一致
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Starting TestRunWorkflow for run ID: %s\n", runID)
|
|
||||||
we, err := c.ExecuteWorkflow(context.Background(), workflowOptions, workflow.TestRunWorkflow, testInput)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Unable to execute workflow: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Workflow started. Workflow ID: %s, Run ID: %s\n", we.GetID(), we.GetRunID())
|
|
||||||
|
|
||||||
// 等待 Workflow 完成并获取结果
|
|
||||||
var result pb.TestRunOutput
|
|
||||||
err = we.Get(context.Background(), &result)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Unable to get workflow result: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Workflow finished. Overall Success: %t, Message: %s\n", result.OverallSuccess, result.CompletionMessage)
|
|
||||||
fmt.Printf("API Test Results: %+v\n", result.ApiResults)
|
|
||||||
fmt.Printf("UI Test Results: %+v\n", result.UiResults)
|
|
||||||
|
|
||||||
// 后续可以根据 result 生成报告、发送通知等
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
package workflow
|
|
||||||
|
|
||||||
// 定义 Temporal Workflow
|
|
||||||
import (
|
|
||||||
"beacon/server/activity"
|
|
||||||
"beacon/server/gen/pb"
|
|
||||||
"fmt"
|
|
||||||
"go.temporal.io/sdk/temporal"
|
|
||||||
"go.temporal.io/sdk/workflow"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestRunWorkflow 定义了整个测试执行的工作流
|
|
||||||
func TestRunWorkflow(ctx workflow.Context, input *pb.TestRunInput) (*pb.TestRunOutput, error) {
|
|
||||||
logger := workflow.GetLogger(ctx)
|
|
||||||
logger.Info("TestRunWorkflow started", "runID", input.RunId)
|
|
||||||
|
|
||||||
ao := workflow.ActivityOptions{
|
|
||||||
StartToCloseTimeout: 10 * time.Minute, // Activity 执行超时时间
|
|
||||||
HeartbeatTimeout: 30 * time.Second, // Heartbeat 防止 Worker 假死
|
|
||||||
RetryPolicy: &temporal.RetryPolicy{ // Activity 级别的重试策略
|
|
||||||
InitialInterval: time.Second,
|
|
||||||
BackoffCoefficient: 2.0,
|
|
||||||
MaximumInterval: time.Minute,
|
|
||||||
MaximumAttempts: 3,
|
|
||||||
NonRetryableErrorTypes: []string{"NonRetryableErrorType"}, // 自定义不可重试的错误
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ctx = workflow.WithActivityOptions(ctx, ao)
|
|
||||||
|
|
||||||
var (
|
|
||||||
apiResults []*pb.ApiTestResult
|
|
||||||
uiResults []*pb.UiTestResult
|
|
||||||
overallSuccess = true
|
|
||||||
completionMessage = "Test run completed successfully."
|
|
||||||
)
|
|
||||||
|
|
||||||
// 执行 API 测试 Activity
|
|
||||||
if input.RunApiTests {
|
|
||||||
apiTestInput := &pb.ApiTestRequest{
|
|
||||||
TestCaseId: "api-example-1",
|
|
||||||
Endpoint: "/api/v1/data",
|
|
||||||
HttpMethod: "GET",
|
|
||||||
Headers: map[string]string{"Authorization": "Bearer token123"},
|
|
||||||
ExpectedStatusCode: 200,
|
|
||||||
}
|
|
||||||
var apiRes pb.ApiTestResult
|
|
||||||
err := workflow.ExecuteActivity(ctx, activity.RunApiTest, apiTestInput).Get(ctx, &apiRes)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("API test activity failed", "error", err)
|
|
||||||
// 可以选择标记为失败,或者继续执行UI测试
|
|
||||||
overallSuccess = false
|
|
||||||
apiRes.BaseResult.Success = false
|
|
||||||
apiRes.BaseResult.Message = fmt.Sprintf("API Test Failed: %v", err)
|
|
||||||
}
|
|
||||||
apiResults = append(apiResults, &apiRes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行 UI 测试 Activity
|
|
||||||
if input.RunUiTests {
|
|
||||||
uiTestInput := &pb.UiTestRequest{
|
|
||||||
TestCaseId: "ui-example-1",
|
|
||||||
UrlPath: "/dashboard",
|
|
||||||
BrowserType: "chromium",
|
|
||||||
Headless: true,
|
|
||||||
UserData: map[string]string{"user": "test", "pass": "password"},
|
|
||||||
}
|
|
||||||
var uiRes pb.UiTestResult
|
|
||||||
err := workflow.ExecuteActivity(ctx, activity.RunUiTest, uiTestInput).Get(ctx, &uiRes)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("UI test activity failed", "error", err)
|
|
||||||
overallSuccess = false
|
|
||||||
uiRes.BaseResult.Success = false
|
|
||||||
uiRes.BaseResult.Message = fmt.Sprintf("UI Test Failed: %v", err)
|
|
||||||
}
|
|
||||||
uiResults = append(uiResults, &uiRes)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !overallSuccess {
|
|
||||||
completionMessage = "Test run completed with failures."
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("TestRunWorkflow completed", "overallSuccess", overallSuccess)
|
|
||||||
return &pb.TestRunOutput{
|
|
||||||
RunId: input.RunId,
|
|
||||||
OverallSuccess: overallSuccess,
|
|
||||||
CompletionMessage: completionMessage,
|
|
||||||
ApiResults: apiResults,
|
|
||||||
UiResults: uiResults,
|
|
||||||
}, nil
|
|
||||||
}
|
|
52
server/workflows/workflow.go
Normal file
52
server/workflows/workflow.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package workflows
|
||||||
|
|
||||||
|
// 定义 Temporal Workflow
|
||||||
|
import (
|
||||||
|
"beacon/server/activities"
|
||||||
|
"fmt"
|
||||||
|
"go.temporal.io/sdk/temporal"
|
||||||
|
"go.temporal.io/sdk/workflow"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestRunWorkflow 定义了整个测试执行的工作流
|
||||||
|
func TestRunWorkflow(ctx workflow.Context, data string) (string, error) {
|
||||||
|
logger := workflow.GetLogger(ctx)
|
||||||
|
retryPolicy := &temporal.RetryPolicy{
|
||||||
|
InitialInterval: time.Second, // First retry after 1 second
|
||||||
|
BackoffCoefficient: 2.0, // Double the wait time on each retry (1s → 2s → 4s → 8s, etc.)
|
||||||
|
MaximumInterval: 100 * time.Second, // Cap wait time at 100 seconds
|
||||||
|
MaximumAttempts: 50, // Retry up to 5 times before giving up
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Add a prefix(Python)
|
||||||
|
pythonCtx := workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
|
||||||
|
TaskQueue: "python-task-queue",
|
||||||
|
StartToCloseTimeout: time.Minute,
|
||||||
|
RetryPolicy: retryPolicy,
|
||||||
|
})
|
||||||
|
|
||||||
|
var prefixed string
|
||||||
|
if err := workflow.ExecuteActivity(pythonCtx, "PythonAddRandomPrefixActivity", data).Get(pythonCtx, &prefixed); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to add prefix: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
goCtx := workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
|
||||||
|
StartToCloseTimeout: time.Minute, //activity must complete within 1 minute
|
||||||
|
RetryPolicy: retryPolicy,
|
||||||
|
})
|
||||||
|
|
||||||
|
var suffixed string
|
||||||
|
err := workflow.ExecuteActivity(goCtx, activities.AddSuffixActivity, prefixed).Get(goCtx, &suffixed)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to add suffix: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var processed string
|
||||||
|
// Demo Activity 3: Simulate uppercasing the data.
|
||||||
|
logger.Info("Demo Activity 3: Pretending to uppercase the data:", data)
|
||||||
|
processed = strings.ToUpper(suffixed)
|
||||||
|
|
||||||
|
return processed, nil
|
||||||
|
}
|
34
workers/go/main.go
Normal file
34
workers/go/main.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"beacon/server/activities"
|
||||||
|
"beacon/server/workflows"
|
||||||
|
"go.temporal.io/sdk/client"
|
||||||
|
"go.temporal.io/sdk/worker"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create a Temporal client with the default options.
|
||||||
|
c, err := client.Dial(client.Options{HostPort: "temporal.newai.day:17233"})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Unable to create Temporal client", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
taskQueue := "data-processing-task-queue"
|
||||||
|
|
||||||
|
// Create a Worker that listens on the specified Task Queue.
|
||||||
|
w := worker.New(c, taskQueue, worker.Options{})
|
||||||
|
|
||||||
|
// Register the Workflow and the real Go suffix Activity with the Worker.
|
||||||
|
w.RegisterWorkflow(workflows.TestRunWorkflow)
|
||||||
|
w.RegisterActivity(activities.AddSuffixActivity)
|
||||||
|
//(for later) w.RegisterActivity(activities.AddSuffixActivity)
|
||||||
|
|
||||||
|
// Start the Worker. This call blocks until the Worker is interrupted.
|
||||||
|
err = w.Run(worker.InterruptCh())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Unable to start Worker", err)
|
||||||
|
}
|
||||||
|
}
|
0
workers/python/__init__.py
Normal file
0
workers/python/__init__.py
Normal file
@ -2,6 +2,7 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import random
|
||||||
|
|
||||||
from temporalio import activity
|
from temporalio import activity
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ from utils import upload_file_to_s3, scalar_map_to_dict
|
|||||||
|
|
||||||
|
|
||||||
class TestActivities:
|
class TestActivities:
|
||||||
@activity.defn
|
@activity.defn(name="run_api_test")
|
||||||
async def run_api_test(self,req: pb.ApiTestRequest) -> pb.ApiTestResult:
|
async def run_api_test(self,req: pb.ApiTestRequest) -> pb.ApiTestResult:
|
||||||
"""执行API测试的Temporal Activity实现"""
|
"""执行API测试的Temporal Activity实现"""
|
||||||
activity.logger.info(f"Received API Test Request: {req.test_case_id}")
|
activity.logger.info(f"Received API Test Request: {req.test_case_id}")
|
||||||
@ -46,7 +47,7 @@ class TestActivities:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@activity.defn
|
@activity.defn(name="run_ui_test")
|
||||||
async def run_ui_test(self,req: pb.UiTestRequest) -> pb.UiTestResult:
|
async def run_ui_test(self,req: pb.UiTestRequest) -> pb.UiTestResult:
|
||||||
"""执行UI测试的Temporal Activity实现"""
|
"""执行UI测试的Temporal Activity实现"""
|
||||||
activity.logger.info(f"Received UI Test Request: {req.test_case_id}")
|
activity.logger.info(f"Received UI Test Request: {req.test_case_id}")
|
||||||
@ -79,4 +80,13 @@ class TestActivities:
|
|||||||
result.base_result.error_details = str(e)
|
result.base_result.error_details = str(e)
|
||||||
|
|
||||||
result.base_result.duration_seconds = time.time() - start_time
|
result.base_result.duration_seconds = time.time() - start_time
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@activity.defn(name="PythonAddRandomPrefixActivity")
|
||||||
|
async def python_add_random_prefix_activity(self,data: str) -> str:
|
||||||
|
prefixes = [
|
||||||
|
"alpha-", "beta-", "gamma-", "delta-", "epsilon-",
|
||||||
|
"zeta-", "eta-", "theta-", "iota-", "kappa-",
|
||||||
|
]
|
||||||
|
prefix = random.choice(prefixes)
|
||||||
|
return f"{prefix}{data}"
|
@ -14,15 +14,16 @@ from activities import TestActivities # 导入定义的 Activity
|
|||||||
async def main():
|
async def main():
|
||||||
# 连接 Temporal Server
|
# 连接 Temporal Server
|
||||||
client = await Client.connect("temporal.newai.day:17233", namespace="default") # 根据你的 Temporal Server 配置
|
client = await Client.connect("temporal.newai.day:17233", namespace="default") # 根据你的 Temporal Server 配置
|
||||||
|
print("Python Worker: Successfully connected to Temporal Server!")
|
||||||
activities = TestActivities()
|
activities = TestActivities()
|
||||||
|
task_queue = "python-task-queue"
|
||||||
# 创建 Worker
|
# 创建 Worker
|
||||||
worker = Worker(
|
worker = Worker(
|
||||||
client,
|
client,
|
||||||
task_queue="test-task-queue", # 保持与 Go Client 一致
|
task_queue=task_queue, # 保持与 Go Client 一致
|
||||||
activities=[activities.run_ui_test, activities.run_api_test]
|
activities=[activities.run_api_test,activities.python_add_random_prefix_activity]
|
||||||
)
|
)
|
||||||
print("Starting Python Temporal Worker...")
|
print("Starting Python Temporal Worker...",task_queue)
|
||||||
await worker.run()
|
await worker.run()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
Loading…
Reference in New Issue
Block a user