New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for display archived workflow on web ui from s3 archival store #5117
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -491,6 +491,12 @@ func (wh *WorkflowHandler) GetWorkflowExecutionHistoryReverse(ctx context.Contex | |
request.MaximumPageSize = common.GetHistoryMaxPageSize | ||
} | ||
|
||
enableArchivalRead := wh.archivalMetadata.GetHistoryConfig().ReadEnabled() | ||
workflowExecutionArchived := wh.workflowExecutionArchived(ctx, request.Execution, namespaceID) | ||
if enableArchivalRead && workflowExecutionArchived { | ||
return wh.getArchivedHistoryReverse(ctx, request, namespaceID) | ||
} | ||
|
||
if dynamicconfig.AccessHistory(wh.config.AccessHistoryFraction, wh.metricsScope(ctx).WithTags(metrics.OperationTag(metrics.FrontendGetWorkflowExecutionHistoryReverseTag))) { | ||
response, err := wh.historyClient.GetWorkflowExecutionHistoryReverse(ctx, | ||
&historyservice.GetWorkflowExecutionHistoryReverseRequest{ | ||
|
@@ -2356,6 +2362,12 @@ func (wh *WorkflowHandler) DescribeWorkflowExecution(ctx context.Context, reques | |
return nil, err | ||
} | ||
|
||
enableArchivalRead := wh.archivalMetadata.GetVisibilityConfig().ReadEnabled() | ||
workflowArchived := wh.workflowExecutionArchived(ctx, request.Execution, namespaceID) | ||
if enableArchivalRead && workflowArchived { | ||
return wh.describeArchivedWorkflowExecution(ctx, request) | ||
} | ||
|
||
response, err := wh.historyClient.DescribeWorkflowExecution(ctx, &historyservice.DescribeWorkflowExecutionRequest{ | ||
NamespaceId: namespaceID.String(), | ||
Request: request, | ||
|
@@ -3830,26 +3842,66 @@ func (wh *WorkflowHandler) validateBuildIdCompatibilityUpdate( | |
} | ||
|
||
func (wh *WorkflowHandler) historyArchived(ctx context.Context, request *workflowservice.GetWorkflowExecutionHistoryRequest, namespaceID namespace.ID) bool { | ||
if request.GetExecution() == nil || request.GetExecution().GetRunId() == "" { | ||
return wh.workflowExecutionArchived(ctx, request.Execution, namespaceID) | ||
} | ||
|
||
func (wh *WorkflowHandler) workflowExecutionArchived(ctx context.Context, execution *commonpb.WorkflowExecution, namespaceID namespace.ID) bool { | ||
if execution == nil || execution.GetRunId() == "" { | ||
return false | ||
} | ||
getMutableStateRequest := &historyservice.GetMutableStateRequest{ | ||
NamespaceId: namespaceID.String(), | ||
Execution: request.Execution, | ||
Execution: execution, | ||
} | ||
_, err := wh.historyClient.GetMutableState(ctx, getMutableStateRequest) | ||
if err == nil { | ||
return false | ||
} | ||
switch err.(type) { | ||
case *serviceerror.NotFound: | ||
// the only case in which history is assumed to be archived is if getting mutable state returns entity not found error | ||
// the only case in which workflow or workflow's event history is assumed to be archived is if getting mutable state returns entity not found error | ||
return true | ||
} | ||
|
||
return false | ||
} | ||
|
||
func (wh *WorkflowHandler) describeArchivedWorkflowExecution(ctx context.Context, request *workflowservice.DescribeWorkflowExecutionRequest) (*workflowservice.DescribeWorkflowExecutionResponse, error) { | ||
r := &workflowservice.ListArchivedWorkflowExecutionsRequest{ | ||
Namespace: request.Namespace, | ||
PageSize: 1, | ||
Query: fmt.Sprintf( | ||
"WorkflowId = '%s' and RunId = '%s'", | ||
request.Execution.GetWorkflowId(), | ||
request.Execution.GetRunId(), | ||
), | ||
} | ||
archivedWorkflowExecutionsResponse, err := wh.ListArchivedWorkflowExecutions(ctx, r) | ||
if err != nil { | ||
return nil, serviceerror.NewNotFound(err.Error()) | ||
} | ||
if len(archivedWorkflowExecutionsResponse.Executions) == 0 { | ||
return nil, serviceerror.NewNotFound(fmt.Sprintf(errUnableToListArchivedWorkflowExecutionMessage, request.Execution.GetWorkflowId(), request.Execution.GetRunId())) | ||
} | ||
//get execution info | ||
executionInfo := archivedWorkflowExecutionsResponse.Executions[0] | ||
if executionInfo.TaskQueue == "" { | ||
//todo: support display task queue | ||
executionInfo.TaskQueue = "Null" | ||
} | ||
result := &workflowservice.DescribeWorkflowExecutionResponse{ | ||
ExecutionConfig: &workflowpb.WorkflowExecutionConfig{ | ||
TaskQueue: &taskqueuepb.TaskQueue{ | ||
Name: executionInfo.TaskQueue, | ||
Kind: enumspb.TASK_QUEUE_KIND_NORMAL, | ||
}, | ||
}, | ||
WorkflowExecutionInfo: executionInfo, | ||
} | ||
|
||
return result, nil | ||
} | ||
|
||
func (wh *WorkflowHandler) getArchivedHistory( | ||
ctx context.Context, | ||
request *workflowservice.GetWorkflowExecutionHistoryRequest, | ||
|
@@ -3900,6 +3952,59 @@ func (wh *WorkflowHandler) getArchivedHistory( | |
}, nil | ||
} | ||
|
||
func (wh *WorkflowHandler) getArchivedHistoryReverse( | ||
ctx context.Context, | ||
request *workflowservice.GetWorkflowExecutionHistoryReverseRequest, | ||
namespaceID namespace.ID, | ||
) (*workflowservice.GetWorkflowExecutionHistoryReverseResponse, error) { | ||
entry, err := wh.namespaceRegistry.GetNamespaceByID(namespaceID) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
URIString := entry.HistoryArchivalState().URI | ||
if URIString == "" { | ||
// if URI is empty, it means the namespace has never enabled for archival. | ||
// the error is not "workflow has passed retention period", because | ||
// we have no way to tell if the requested workflow exists or not. | ||
return nil, errHistoryNotFound | ||
} | ||
|
||
URI, err := archiver.NewURI(URIString) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
historyArchiver, err := wh.archiverProvider.GetHistoryArchiver(URI.Scheme(), string(primitives.FrontendService)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
resp, err := historyArchiver.Get(ctx, URI, &archiver.GetHistoryRequest{ | ||
NamespaceID: namespaceID.String(), | ||
WorkflowID: request.GetExecution().GetWorkflowId(), | ||
RunID: request.GetExecution().GetRunId(), | ||
NextPageToken: request.GetNextPageToken(), | ||
PageSize: int(request.GetMaximumPageSize()), | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
history := &historypb.History{} | ||
for _, batch := range resp.HistoryBatches { | ||
history.Events = append(history.Events, batch.Events...) | ||
} | ||
// reverse the events | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am a bit confused here. Looks like this is only reversing events within a page. When there are multiple pages, the returned history will look like something like: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep.There will be a mistake. Mark it on my todo list. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure how you plan to resolve this though. It seems like a n^2 alg. if the underlying storage doesn't support reading backward. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure whether if the underlying storage support it. I'll check it out. If not, maybe there's no smarter way. |
||
for i, j := 0, len(history.Events)-1; i < j; i, j = i+1, j-1 { | ||
history.Events[i], history.Events[j] = history.Events[j], history.Events[i] | ||
} | ||
return &workflowservice.GetWorkflowExecutionHistoryReverseResponse{ | ||
History: history, | ||
NextPageToken: resp.NextPageToken, | ||
}, nil | ||
} | ||
|
||
// cancelOutstandingPoll cancel outstanding poll if context was canceled and returns true. Otherwise returns false. | ||
func (wh *WorkflowHandler) cancelOutstandingPoll(ctx context.Context, namespaceID namespace.ID, taskQueueType enumspb.TaskQueueType, | ||
taskQueue *taskqueuepb.TaskQueue, pollerID string) bool { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think archived workflow can be described based on visibility records. Fields like pending activities or pending children can't be populated by vis records.
If we really want to do it, the right way should be rebuild workflow mutable state from history events, then describe the rebuilt mutable states so that all fields in the describe response can be populated.
Alternatively, a separate API can be created for describing archived workflow and defined a new response that returns only limited information.
cc @yiminc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems a little weird if a archived workflow still has these pending things. Should we consider a archived wf is totally completed(without any changeable things)?
I'm not familiar with the internals of mutable state.. According to the previous experience issue, an archived workflow which has past the retention is considered deleted from mutable states. If we want to describe it based on mutable state, there maybe a lot of change to do.
A separate API is good. We need more discuss about the limited information of an archived wf and how to get it..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
workflow can still have pending things even after closed, for example when workflow is force terminated before activity finishes. Archived or not is a concept of where the data is stored, so they are orthogonal.