Skip to content

Commit 97175b5

Browse files
authored
fix(active-active): Update CLI describe workflow output to show ActiveClusterSelectionPolicy (#7461)
<!-- Describe what has changed in this PR --> **What changed?** Update CLI describe workflow output to show ActiveClusterSelectionPolicy <!-- Tell your future self why have you made these changes --> **Why?** <!-- How have you verified this change? Tested locally? Added a unit test? Checked in staging env? --> **How did you test it?** unit test <!-- Assuming the worst case, what can be broken when deploying this change to production? --> **Potential risks** <!-- Is it notable for release? e.g. schema updates, configuration or data migration required? If so, please mention it, and also update CHANGELOG.md --> **Release notes** <!-- Is there any documentation updates should be made for config, https://cadenceworkflow.io/docs/operation-guide/setup/ ? If so, please open an PR in https://github.com/cadence-workflow/cadence-docs --> **Documentation Changes**
1 parent 0efbfef commit 97175b5

File tree

2 files changed

+270
-47
lines changed

2 files changed

+270
-47
lines changed

tools/cli/workflow_commands.go

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,19 +1148,20 @@ type describeWorkflowExecutionResponse struct {
11481148

11491149
// workflowExecutionInfo has same fields as types.WorkflowExecutionInfo, but has datetime instead of raw time
11501150
type workflowExecutionInfo struct {
1151-
Execution *types.WorkflowExecution
1152-
Type *types.WorkflowType
1153-
StartTime *string // change from *int64
1154-
CloseTime *string // change from *int64
1155-
CloseStatus *types.WorkflowExecutionCloseStatus
1156-
HistoryLength int64
1157-
ParentDomainID *string
1158-
ParentExecution *types.WorkflowExecution
1159-
Memo *types.Memo
1160-
SearchAttributes map[string]interface{}
1161-
AutoResetPoints *types.ResetPoints
1162-
PartitionConfig map[string]string
1163-
CronOverlapPolicy *types.CronOverlapPolicy
1151+
Execution *types.WorkflowExecution
1152+
Type *types.WorkflowType
1153+
StartTime *string // change from *int64
1154+
CloseTime *string // change from *int64
1155+
CloseStatus *types.WorkflowExecutionCloseStatus
1156+
HistoryLength int64
1157+
ParentDomainID *string
1158+
ParentExecution *types.WorkflowExecution
1159+
Memo *types.Memo
1160+
SearchAttributes map[string]interface{}
1161+
AutoResetPoints *types.ResetPoints
1162+
PartitionConfig map[string]string
1163+
CronOverlapPolicy *types.CronOverlapPolicy
1164+
ActiveClusterSelectionPolicy *types.ActiveClusterSelectionPolicy
11641165
}
11651166

11661167
// pendingActivityInfo has same fields as types.PendingActivityInfo, but different field type for better display
@@ -1199,19 +1200,20 @@ func convertDescribeWorkflowExecutionResponse(resp *types.DescribeWorkflowExecut
11991200
return nil, fmt.Errorf("error converting search attributes: %w", err)
12001201
}
12011202
executionInfo := workflowExecutionInfo{
1202-
Execution: info.Execution,
1203-
Type: info.Type,
1204-
StartTime: common.StringPtr(timestampToString(info.GetStartTime(), false)),
1205-
CloseTime: common.StringPtr(timestampToString(info.GetCloseTime(), false)),
1206-
CloseStatus: info.CloseStatus,
1207-
HistoryLength: info.HistoryLength,
1208-
ParentDomainID: info.ParentDomainID,
1209-
ParentExecution: info.ParentExecution,
1210-
Memo: info.Memo,
1211-
SearchAttributes: searchattributes,
1212-
AutoResetPoints: info.AutoResetPoints,
1213-
PartitionConfig: info.PartitionConfig,
1214-
CronOverlapPolicy: info.CronOverlapPolicy,
1203+
Execution: info.Execution,
1204+
Type: info.Type,
1205+
StartTime: common.StringPtr(timestampToString(info.GetStartTime(), false)),
1206+
CloseTime: common.StringPtr(timestampToString(info.GetCloseTime(), false)),
1207+
CloseStatus: info.CloseStatus,
1208+
HistoryLength: info.HistoryLength,
1209+
ParentDomainID: info.ParentDomainID,
1210+
ParentExecution: info.ParentExecution,
1211+
Memo: info.Memo,
1212+
SearchAttributes: searchattributes,
1213+
AutoResetPoints: info.AutoResetPoints,
1214+
PartitionConfig: info.PartitionConfig,
1215+
CronOverlapPolicy: info.CronOverlapPolicy,
1216+
ActiveClusterSelectionPolicy: info.ActiveClusterSelectionPolicy,
12151217
}
12161218

12171219
var pendingActs []*pendingActivityInfo

tools/cli/workflow_commands_test.go

Lines changed: 242 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -426,31 +426,252 @@ func Test_GetWorkflowStatus(t *testing.T) {
426426
func Test_ConvertDescribeWorkflowExecutionResponse(t *testing.T) {
427427
mockCtrl := gomock.NewController(t)
428428
serverFrontendClient := frontend.NewMockClient(mockCtrl)
429-
mockResp := &types.DescribeWorkflowExecutionResponse{
430-
WorkflowExecutionInfo: &types.WorkflowExecutionInfo{
431-
Execution: &types.WorkflowExecution{
432-
WorkflowID: "test-workflow-id",
433-
RunID: "test-run-id",
429+
430+
t.Run("basic conversion", func(t *testing.T) {
431+
mockResp := &types.DescribeWorkflowExecutionResponse{
432+
WorkflowExecutionInfo: &types.WorkflowExecutionInfo{
433+
Execution: &types.WorkflowExecution{
434+
WorkflowID: "test-workflow-id",
435+
RunID: "test-run-id",
436+
},
434437
},
435-
},
436-
PendingActivities: []*types.PendingActivityInfo{
437-
{
438-
ActivityID: "test-activity-id",
439-
ActivityType: &types.ActivityType{
440-
Name: "test-activity-type",
438+
PendingActivities: []*types.PendingActivityInfo{
439+
{
440+
ActivityID: "test-activity-id",
441+
ActivityType: &types.ActivityType{
442+
Name: "test-activity-type",
443+
},
444+
HeartbeatDetails: []byte("test-heartbeat-details"),
445+
LastFailureDetails: []byte("test-failure-details"),
441446
},
442-
HeartbeatDetails: []byte("test-heartbeat-details"),
443-
LastFailureDetails: []byte("test-failure-details"),
444447
},
445-
},
446-
PendingDecision: &types.PendingDecisionInfo{
447-
State: nil,
448-
},
449-
}
448+
PendingDecision: &types.PendingDecisionInfo{
449+
State: nil,
450+
},
451+
}
450452

451-
resp, err := convertDescribeWorkflowExecutionResponse(mockResp, serverFrontendClient, nil)
452-
assert.NoError(t, err)
453-
assert.Equal(t, "test-workflow-id", resp.WorkflowExecutionInfo.Execution.WorkflowID)
453+
resp, err := convertDescribeWorkflowExecutionResponse(mockResp, serverFrontendClient, nil)
454+
assert.NoError(t, err)
455+
assert.Equal(t, "test-workflow-id", resp.WorkflowExecutionInfo.Execution.WorkflowID)
456+
assert.Equal(t, 1, len(resp.PendingActivities))
457+
assert.NotNil(t, resp.PendingDecision)
458+
})
459+
460+
t.Run("with execution configuration", func(t *testing.T) {
461+
mockResp := &types.DescribeWorkflowExecutionResponse{
462+
ExecutionConfiguration: &types.WorkflowExecutionConfiguration{
463+
TaskList: &types.TaskList{Name: "test-task-list"},
464+
},
465+
WorkflowExecutionInfo: &types.WorkflowExecutionInfo{
466+
Execution: &types.WorkflowExecution{
467+
WorkflowID: "test-wf-id",
468+
RunID: "test-run-id",
469+
},
470+
Type: &types.WorkflowType{
471+
Name: "test-workflow-type",
472+
},
473+
HistoryLength: 100,
474+
},
475+
PendingActivities: []*types.PendingActivityInfo{},
476+
PendingChildren: []*types.PendingChildExecutionInfo{},
477+
}
478+
479+
resp, err := convertDescribeWorkflowExecutionResponse(mockResp, serverFrontendClient, nil)
480+
assert.NoError(t, err)
481+
assert.NotNil(t, resp.ExecutionConfiguration)
482+
assert.Equal(t, "test-task-list", resp.ExecutionConfiguration.TaskList.Name)
483+
assert.Equal(t, int64(100), resp.WorkflowExecutionInfo.HistoryLength)
484+
})
485+
486+
t.Run("with pending activities and conversion of timestamps", func(t *testing.T) {
487+
now := time.Now().UnixNano()
488+
mockResp := &types.DescribeWorkflowExecutionResponse{
489+
WorkflowExecutionInfo: &types.WorkflowExecutionInfo{
490+
Execution: &types.WorkflowExecution{
491+
WorkflowID: "test-wf",
492+
RunID: "test-run",
493+
},
494+
StartTime: common.Int64Ptr(now),
495+
CloseTime: common.Int64Ptr(now + 1000),
496+
},
497+
PendingActivities: []*types.PendingActivityInfo{
498+
{
499+
ActivityID: "activity-1",
500+
ActivityType: &types.ActivityType{
501+
Name: "ActivityType1",
502+
},
503+
State: types.PendingActivityStateScheduled.Ptr(),
504+
ScheduledTimestamp: common.Int64Ptr(now),
505+
LastStartedTimestamp: common.Int64Ptr(now + 500),
506+
LastHeartbeatTimestamp: common.Int64Ptr(now + 600),
507+
ExpirationTimestamp: common.Int64Ptr(now + 2000),
508+
Attempt: 1,
509+
MaximumAttempts: 3,
510+
LastFailureReason: common.StringPtr("test-failure"),
511+
LastWorkerIdentity: "worker-1",
512+
ScheduleID: 1,
513+
},
514+
{
515+
ActivityID: "activity-2",
516+
ActivityType: &types.ActivityType{
517+
Name: "ActivityType2",
518+
},
519+
HeartbeatDetails: []byte("heartbeat-data"),
520+
LastFailureDetails: []byte("failure-data"),
521+
},
522+
},
523+
}
524+
525+
resp, err := convertDescribeWorkflowExecutionResponse(mockResp, serverFrontendClient, nil)
526+
assert.NoError(t, err)
527+
assert.Equal(t, 2, len(resp.PendingActivities))
528+
529+
// Check first activity has timestamps converted
530+
assert.NotNil(t, resp.PendingActivities[0].ScheduledTimestamp)
531+
assert.NotNil(t, resp.PendingActivities[0].LastStartedTimestamp)
532+
assert.NotNil(t, resp.PendingActivities[0].LastHeartbeatTimestamp)
533+
assert.NotNil(t, resp.PendingActivities[0].ExpirationTimestamp)
534+
assert.Equal(t, int32(1), resp.PendingActivities[0].Attempt)
535+
assert.Equal(t, int32(3), resp.PendingActivities[0].MaximumAttempts)
536+
assert.Equal(t, "test-failure", *resp.PendingActivities[0].LastFailureReason)
537+
538+
// Check second activity has byte fields converted
539+
assert.NotNil(t, resp.PendingActivities[1].HeartbeatDetails)
540+
assert.Equal(t, "heartbeat-data", *resp.PendingActivities[1].HeartbeatDetails)
541+
assert.NotNil(t, resp.PendingActivities[1].LastFailureDetails)
542+
assert.Equal(t, "failure-data", *resp.PendingActivities[1].LastFailureDetails)
543+
})
544+
545+
t.Run("with pending decision and timestamps", func(t *testing.T) {
546+
now := time.Now().UnixNano()
547+
mockResp := &types.DescribeWorkflowExecutionResponse{
548+
WorkflowExecutionInfo: &types.WorkflowExecutionInfo{
549+
Execution: &types.WorkflowExecution{
550+
WorkflowID: "test-wf",
551+
RunID: "test-run",
552+
},
553+
},
554+
PendingDecision: &types.PendingDecisionInfo{
555+
State: types.PendingDecisionStateScheduled.Ptr(),
556+
ScheduledTimestamp: common.Int64Ptr(now),
557+
StartedTimestamp: common.Int64Ptr(now + 100),
558+
Attempt: 5,
559+
ScheduleID: 42,
560+
},
561+
}
562+
563+
resp, err := convertDescribeWorkflowExecutionResponse(mockResp, serverFrontendClient, nil)
564+
assert.NoError(t, err)
565+
assert.NotNil(t, resp.PendingDecision)
566+
assert.NotNil(t, resp.PendingDecision.ScheduledTimestamp)
567+
assert.NotNil(t, resp.PendingDecision.StartedTimestamp)
568+
assert.Equal(t, int64(5), resp.PendingDecision.Attempt)
569+
assert.Equal(t, int64(42), resp.PendingDecision.ScheduleID)
570+
})
571+
572+
t.Run("with pending children", func(t *testing.T) {
573+
mockResp := &types.DescribeWorkflowExecutionResponse{
574+
WorkflowExecutionInfo: &types.WorkflowExecutionInfo{
575+
Execution: &types.WorkflowExecution{
576+
WorkflowID: "parent-wf",
577+
RunID: "parent-run",
578+
},
579+
},
580+
PendingChildren: []*types.PendingChildExecutionInfo{
581+
{
582+
WorkflowID: "child-wf-1",
583+
RunID: "child-run-1",
584+
WorkflowTypeName: "ChildWorkflowType",
585+
InitiatedID: 10,
586+
ParentClosePolicy: types.ParentClosePolicyTerminate.Ptr(),
587+
},
588+
{
589+
WorkflowID: "child-wf-2",
590+
RunID: "child-run-2",
591+
WorkflowTypeName: "ChildWorkflowType2",
592+
InitiatedID: 20,
593+
ParentClosePolicy: types.ParentClosePolicyAbandon.Ptr(),
594+
},
595+
},
596+
}
597+
598+
resp, err := convertDescribeWorkflowExecutionResponse(mockResp, serverFrontendClient, nil)
599+
assert.NoError(t, err)
600+
assert.Equal(t, 2, len(resp.PendingChildren))
601+
assert.Equal(t, "child-wf-1", resp.PendingChildren[0].WorkflowID)
602+
assert.Equal(t, "child-wf-2", resp.PendingChildren[1].WorkflowID)
603+
})
604+
605+
t.Run("with workflow execution info fields", func(t *testing.T) {
606+
now := time.Now().UnixNano()
607+
mockResp := &types.DescribeWorkflowExecutionResponse{
608+
WorkflowExecutionInfo: &types.WorkflowExecutionInfo{
609+
Execution: &types.WorkflowExecution{
610+
WorkflowID: "wf-id",
611+
RunID: "run-id",
612+
},
613+
Type: &types.WorkflowType{
614+
Name: "WorkflowType",
615+
},
616+
StartTime: common.Int64Ptr(now),
617+
CloseTime: common.Int64Ptr(now + 1000),
618+
CloseStatus: types.WorkflowExecutionCloseStatusCompleted.Ptr(),
619+
HistoryLength: 50,
620+
ParentDomainID: common.StringPtr("parent-domain"),
621+
ParentExecution: &types.WorkflowExecution{
622+
WorkflowID: "parent-wf",
623+
RunID: "parent-run",
624+
},
625+
Memo: &types.Memo{
626+
Fields: map[string][]byte{
627+
"key1": []byte("value1"),
628+
},
629+
},
630+
PartitionConfig: map[string]string{
631+
"config1": "value1",
632+
},
633+
CronOverlapPolicy: types.CronOverlapPolicySkipped.Ptr(),
634+
ActiveClusterSelectionPolicy: &types.ActiveClusterSelectionPolicy{
635+
ClusterAttribute: &types.ClusterAttribute{
636+
Scope: "region",
637+
Name: "us-west-1",
638+
},
639+
},
640+
},
641+
}
642+
643+
resp, err := convertDescribeWorkflowExecutionResponse(mockResp, serverFrontendClient, nil)
644+
assert.NoError(t, err)
645+
assert.NotNil(t, resp.WorkflowExecutionInfo.StartTime)
646+
assert.NotNil(t, resp.WorkflowExecutionInfo.CloseTime)
647+
assert.Equal(t, types.WorkflowExecutionCloseStatusCompleted, *resp.WorkflowExecutionInfo.CloseStatus)
648+
assert.Equal(t, int64(50), resp.WorkflowExecutionInfo.HistoryLength)
649+
assert.NotNil(t, resp.WorkflowExecutionInfo.ParentDomainID)
650+
assert.NotNil(t, resp.WorkflowExecutionInfo.ParentExecution)
651+
assert.NotNil(t, resp.WorkflowExecutionInfo.Memo)
652+
assert.Equal(t, map[string]string{"config1": "value1"}, resp.WorkflowExecutionInfo.PartitionConfig)
653+
assert.Equal(t, types.CronOverlapPolicySkipped, *resp.WorkflowExecutionInfo.CronOverlapPolicy)
654+
assert.NotNil(t, resp.WorkflowExecutionInfo.ActiveClusterSelectionPolicy)
655+
assert.NotNil(t, resp.WorkflowExecutionInfo.ActiveClusterSelectionPolicy.ClusterAttribute)
656+
assert.Equal(t, "region", resp.WorkflowExecutionInfo.ActiveClusterSelectionPolicy.ClusterAttribute.Scope)
657+
assert.Equal(t, "us-west-1", resp.WorkflowExecutionInfo.ActiveClusterSelectionPolicy.ClusterAttribute.Name)
658+
})
659+
660+
t.Run("with no pending activities or decision", func(t *testing.T) {
661+
mockResp := &types.DescribeWorkflowExecutionResponse{
662+
WorkflowExecutionInfo: &types.WorkflowExecutionInfo{
663+
Execution: &types.WorkflowExecution{
664+
WorkflowID: "wf-id",
665+
RunID: "run-id",
666+
},
667+
},
668+
}
669+
670+
resp, err := convertDescribeWorkflowExecutionResponse(mockResp, serverFrontendClient, nil)
671+
assert.NoError(t, err)
672+
assert.Nil(t, resp.PendingDecision)
673+
assert.Equal(t, 0, len(resp.PendingActivities))
674+
})
454675
}
455676

456677
func Test_PrintRunStatus(t *testing.T) {

0 commit comments

Comments
 (0)