Skip to content
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

GetDeploymentReachability API #6859

Merged
merged 26 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
670232c
first sketch of reachability
carlydf Nov 21, 2024
7899a50
Merge branch 'versioning-3' of github.com:temporalio/temporal into cd…
carlydf Nov 22, 2024
13fb4c7
add lastUpdatedTime to cache, add search attribute defs
carlydf Nov 22, 2024
12992a5
add and search for reachabilityBuildIdSearchAttribute
carlydf Nov 22, 2024
7a00f7e
address comments
carlydf Nov 22, 2024
97d4584
add check for nil current deployment
carlydf Nov 22, 2024
0349b73
fix compile error
carlydf Nov 22, 2024
ec84921
add super basic unit test'
carlydf Nov 22, 2024
ad0dbc0
changes to make server work locally
Shivs11 Nov 21, 2024
2f4a81f
add reachability unit tests
carlydf Nov 22, 2024
079c710
require non-nil deployment for reachability
carlydf Nov 23, 2024
a986591
update reachability SA after workflow task completion
carlydf Nov 23, 2024
aff94cc
reduce diff
carlydf Nov 23, 2024
51095f9
merge in versioning-3 (breaks make start)
carlydf Nov 23, 2024
2b41bc7
remove print
carlydf Nov 23, 2024
215efc9
update reachability SA if override changes effective deployment or be…
carlydf Nov 23, 2024
6de1893
escape reachability search attribute
carlydf Nov 23, 2024
e613d0c
handle nil deployment case
carlydf Nov 23, 2024
6341511
add comment explaining when we update the reachability search attribute
carlydf Nov 23, 2024
e972d44
fix mismatched args
carlydf Nov 23, 2024
0714b00
handle error
carlydf Nov 23, 2024
96f47f9
fix lint
carlydf Nov 23, 2024
e53ceef
goimports
carlydf Nov 23, 2024
9143dca
remove getcurrent call
carlydf Nov 23, 2024
a3f7999
address comments
carlydf Nov 23, 2024
ee4a890
merge in versioning-3:
carlydf Nov 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion common/searchattribute/defs.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ const (
StateTransitionCount = "StateTransitionCount"
TemporalChangeVersion = "TemporalChangeVersion"
BinaryChecksums = "BinaryChecksums"
BuildIds = "BuildIds"
BatcherNamespace = "BatcherNamespace"
BatcherUser = "BatcherUser"
HistorySizeBytes = "HistorySizeBytes"
Expand Down Expand Up @@ -81,6 +80,9 @@ const (
// define a custom ScheduleId search attribute, in which case the query using the ScheduleId would operate just like
// any other custom search attribute.
ScheduleID = "ScheduleId"

// Used for Worker Versioning
BuildIds = "BuildIds"
)

var (
Expand Down
41 changes: 36 additions & 5 deletions common/worker_versioning/worker_versioning.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/temporalio/sqlparser"
commonpb "go.temporal.io/api/common/v1"
deploymentpb "go.temporal.io/api/deployment/v1"
enumspb "go.temporal.io/api/enums/v1"
persistencespb "go.temporal.io/server/api/persistence/v1"
taskqueuespb "go.temporal.io/server/api/taskqueue/v1"
"go.temporal.io/server/common/namespace"
Expand All @@ -41,14 +42,44 @@ import (
)

const (
buildIdSearchAttributePrefixAssigned = "assigned"
buildIdSearchAttributePrefixVersioned = "versioned"
buildIdSearchAttributePrefixUnversioned = "unversioned"
BuildIdSearchAttributeDelimiter = ":"
buildIdSearchAttributePrefixReachability = "reachability"
buildIdSearchAttributePrefixAssigned = "assigned"
buildIdSearchAttributePrefixVersioned = "versioned"
buildIdSearchAttributePrefixUnversioned = "unversioned"
BuildIdSearchAttributeDelimiter = ":"
// UnversionedSearchAttribute is the sentinel value used to mark all unversioned workflows
UnversionedSearchAttribute = buildIdSearchAttributePrefixUnversioned
)

// TODO (carly): fix delimiter
// escapeBuildIdSearchAttributeDelimiter is a helper which escapes the BuildIdSearchAttributeDelimiter character in the input string
func escapeBuildIdSearchAttributeDelimiter(s string) string {
s = strings.Replace(s, BuildIdSearchAttributeDelimiter, `|`+BuildIdSearchAttributeDelimiter, -1)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apparently escaping is harder than we think. see #6864 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a todo

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here is the correct escape func. Maybe we should bring the function here and use it in all places. probably we want to rename this folder to common/deployment after we clean up old stuff.

return s
}

// ReachabilityBuildIdSearchAttribute returns the search attribute value for the currently assigned build ID in the form
// 'reachability:<behavior>:<deployment_series_name>:<deployment_build_id>'
func ReachabilityBuildIdSearchAttribute(behavior enumspb.VersioningBehavior, deployment *deploymentpb.Deployment) string {
var escapedDeployment string
if deployment == nil {
escapedDeployment = "UNVERSIONED"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm... behavior is not meaningful in this case, do we need to add a reachability:.. value in this case? (an unversioned values is already added in BuildIds)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah honestly I think I never call this with a nil deployment, I was just not thinking / being paranoid about nil pointer error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can remove the nil-check

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting, in the general case, it is possible that the behavior is unpinned but deployment is nil (unpinned override of a new/unversioned wf).

So this is another evidence that we should not make it generic, and keep it specific to pinned when the deployment cannot be null.

} else {
escapedDeployment = fmt.Sprintf("%s%s%s",
escapeBuildIdSearchAttributeDelimiter(deployment.GetSeriesName()),
BuildIdSearchAttributeDelimiter,
escapeBuildIdSearchAttributeDelimiter(deployment.GetBuildId()),
)
}
return sqlparser.String(sqlparser.NewStrVal([]byte(fmt.Sprintf("%s%s%s%s%s",
buildIdSearchAttributePrefixReachability,
BuildIdSearchAttributeDelimiter,
escapeBuildIdSearchAttributeDelimiter(behavior.String()),
carlydf marked this conversation as resolved.
Show resolved Hide resolved
BuildIdSearchAttributeDelimiter,
escapedDeployment,
))))
}
carlydf marked this conversation as resolved.
Show resolved Hide resolved

// AssignedBuildIdSearchAttribute returns the search attribute value for the currently assigned build ID
func AssignedBuildIdSearchAttribute(buildId string) string {
return buildIdSearchAttributePrefixAssigned + BuildIdSearchAttributeDelimiter + buildId
Expand Down Expand Up @@ -144,7 +175,7 @@ func DeploymentToString(deployment *deploymentpb.Deployment) string {
if deployment == nil {
return "UNVERSIONED"
}
return deployment.SeriesName + ":" + deployment.GetBuildId()
return deployment.GetSeriesName() + ":" + deployment.GetBuildId()
}

// MakeDirectiveForWorkflowTask returns a versioning directive based on the following parameters:
Expand Down
35 changes: 33 additions & 2 deletions service/frontend/workflow_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3231,8 +3231,39 @@ func (wh *WorkflowHandler) decodeDeploymentMemo(memo *commonpb.Memo) *deployspb.
return &workflowMemo
}

func (wh *WorkflowHandler) GetDeploymentReachability(context.Context, *workflowservice.GetDeploymentReachabilityRequest) (*workflowservice.GetDeploymentReachabilityResponse, error) {
panic("Not implemented *yet*")
func (wh *WorkflowHandler) GetDeploymentReachability(
ctx context.Context,
request *workflowservice.GetDeploymentReachabilityRequest,
) (_ *workflowservice.GetDeploymentReachabilityResponse, retError error) {
defer log.CapturePanic(wh.logger, &retError)

if request == nil {
return nil, errRequestNotSet
}

if len(request.Namespace) == 0 {
return nil, errNamespaceNotSet
}

if !wh.config.EnableDeployments(request.Namespace) {
return nil, errDeploymentsNotAllowed
}

if request.GetDeployment() == nil {
return nil, serviceerror.NewInvalidArgument("deployment is required")
}

namespaceEntry, err := wh.namespaceRegistry.GetNamespace(namespace.Name(request.GetNamespace()))
if err != nil {
return nil, err
}
resp, err := wh.deploymentStoreClient.GetDeploymentReachability(ctx, namespaceEntry, request.Deployment.SeriesName, request.Deployment.BuildId)
if err != nil {
wh.logger.Error("Error during GetDeploymentReachability", tag.Error(err))
return nil, err
}

return resp, nil
}

func (wh *WorkflowHandler) SetCurrentDeployment(context.Context, *workflowservice.SetCurrentDeploymentRequest) (*workflowservice.SetCurrentDeploymentResponse, error) {
Expand Down
81 changes: 57 additions & 24 deletions service/history/workflow/mutable_state_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"context"
"errors"
"fmt"
"math"
"math/rand"
"reflect"
"slices"
Expand Down Expand Up @@ -2639,6 +2640,12 @@ func (ms *MutableStateImpl) UpdateBuildIdAssignment(buildId string) error {
return ms.updateBuildIdsSearchAttribute(&commonpb.WorkerVersionStamp{UseVersioning: true, BuildId: buildId}, limit)
}

// For v3 versioned workflows (ms.GetEffectiveVersioningBehavior() != UNSPECIFIED), this will append a tag formed as
// reachability:<effective_behavior>:<deployment_series_name>:<deployment_build_id> to the BuildIds search attribute,
Comment on lines +2643 to +2644
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trying to remember what was the use for the reachability:... for unpinned wfs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm.. I think the reason to get reachability of unpinned would be to be able to further segment the "reachable" value into "reachable by unpinned only", which would mean you can feel more comfortable decommissioning a deployment, if it is only "reachable by unpinned"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but now, we just treat "reachable by unpinned only" as equivalent to "unreachable"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This plus my previous comment makes me thing maybe instead of a general reachability:... value, we need a pinned:<deployment_series_name>:<deployment_build_id> only for pinned wfs.

(later we can add incompatible:<deployment_series_name>:<deployment_build_id> for failed transitions)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah you're right, that was our reason. But we abandoned that thinking. but why spend costly visibility space for something we don't need? If we want to add it in the future, I think we can start adding this SA to unpinned as well (as unpinned:...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait actually, I think we want <behavior>:<deployment_series_name>:<deployment_build_id>, because when a deployment changes from pinned to unpinned, we need to update the SA to not say "pinned" anymore

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or, to save storage, we can remove the previous pinned SA (I guess I can just remove the string from the string list that I read, and then write the new list?)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah should at most be one pinned:... value in the list. Even if we keep reachability:... there should at most be one of those values in the list.

// if it does not already exist there. The deployment will be execution_info.deployment, or the override deployment if
// a pinned override is set.
// For all other workflows (ms.GetEffectiveVersioningBehavior() == UNSPECIFIED), this will append a tag based on the
// workflow's versioning status.
func (ms *MutableStateImpl) updateBuildIdsSearchAttribute(stamp *commonpb.WorkerVersionStamp, maxSearchAttributeValueSize int) error {
changed, err := ms.addBuildIdToSearchAttributesWithNoVisibilityTask(stamp, maxSearchAttributeValueSize)
if err != nil {
Expand Down Expand Up @@ -2671,34 +2678,55 @@ func (ms *MutableStateImpl) loadBuildIds() ([]string, error) {
return searchAttributeValues, nil
}

// getReachabilityDeployment ignores DeploymentTransition.Deployment. If there is a pinned override,
// it returns the override deployment. If there is an unpinned override or no override, it returns
// execution_info.deployment, which is the last deployment that this workflow completed a task on.
func (ms *MutableStateImpl) getReachabilityDeployment() *deploymentpb.Deployment {
versioningInfo := ms.GetExecutionInfo().GetVersioningInfo()
if override := versioningInfo.GetVersioningOverride(); override != nil {
if override.GetBehavior() == enumspb.VERSIONING_BEHAVIOR_PINNED {
return override.GetDeployment()
}
}
return versioningInfo.GetDeployment()
}

// Takes a list of loaded build IDs from a search attribute and adds a new build ID to it. Also makes sure that the
// resulting SA list begins with either "unversioned" or "assigned:<bld>" based on workflow's Build ID assignment status.
// Returns a potentially modified list.
// [cleanup-old-wv] old versioning does not add "assigned:<bld>" value to the SA.
// [cleanup-versioning-2] versioning-2 adds "assigned:<bld>" which is no longer used in versioning-3
func (ms *MutableStateImpl) addBuildIdToLoadedSearchAttribute(
existingValues []string,
stamp *commonpb.WorkerVersionStamp,
) []string {
var newValues []string
if !stamp.GetUseVersioning() {
if !stamp.GetUseVersioning() && ms.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_UNSPECIFIED { // unversioned workflows may still have non-nil deployment, so we don't check deployment
newValues = append(newValues, worker_versioning.UnversionedSearchAttribute)
} else if ms.GetEffectiveVersioningBehavior() != enumspb.VERSIONING_BEHAVIOR_UNSPECIFIED {
newValues = append(newValues, worker_versioning.ReachabilityBuildIdSearchAttribute(
ms.GetEffectiveVersioningBehavior(),
ms.getReachabilityDeployment(),
))
} else if ms.GetAssignedBuildId() != "" {
newValues = append(newValues, worker_versioning.AssignedBuildIdSearchAttribute(ms.GetAssignedBuildId()))
}

buildId := worker_versioning.VersionStampToBuildIdSearchAttribute(stamp)
found := slices.Contains(newValues, buildId)
for _, existingValue := range existingValues {
if existingValue == buildId {
found = true
if ms.GetEffectiveVersioningBehavior() == enumspb.VERSIONING_BEHAVIOR_UNSPECIFIED {
buildId := worker_versioning.VersionStampToBuildIdSearchAttribute(stamp)
found := slices.Contains(newValues, buildId)
for _, existingValue := range existingValues {
if existingValue == buildId {
found = true
}
if !worker_versioning.IsUnversionedOrAssignedBuildIdSearchAttribute(existingValue) {
newValues = append(newValues, existingValue)
}
}
if !worker_versioning.IsUnversionedOrAssignedBuildIdSearchAttribute(existingValue) {
newValues = append(newValues, existingValue)
if !found {
newValues = append(newValues, buildId)
}
}
if !found {
newValues = append(newValues, buildId)
}
return newValues
}

Expand Down Expand Up @@ -4251,23 +4279,16 @@ func (ms *MutableStateImpl) AddWorkflowExecutionOptionsUpdatedEvent(

func (ms *MutableStateImpl) ApplyWorkflowExecutionOptionsUpdatedEvent(event *historypb.HistoryEvent) error {
override := event.GetWorkflowExecutionOptionsUpdatedEventAttributes().GetVersioningOverride()
previousCurrentDeployment := ms.GetEffectiveDeployment()
previousEffectiveDeployment := ms.GetEffectiveDeployment()
previousEffectiveVersioningBehavior := ms.GetEffectiveVersioningBehavior()
if ms.GetExecutionInfo().GetVersioningInfo() == nil {
ms.GetExecutionInfo().VersioningInfo = &workflowpb.WorkflowExecutionVersioningInfo{}
}
ms.GetExecutionInfo().VersioningInfo.VersioningOverride = override

// If effective deployment or behavior change, we need to reschedule any pending tasks, because History will reject
// the task's start request if the task is being started by a poller that is not from the workflow's effective
// deployment according to History. Therefore, it is important for matching to match tasks with the correct pollers.
// Even if the effective deployment does not change, we still need to reschedule tasks into the appropriate
// default/unpinned queue or the pinned queue, because the two queues will be handled differently if the task queue's
// Current Deployment changes between now and when the task is started.
//
// We choose to let any started WFT that is running on the old deployment finish running, instead of forcing it to fail.
if !proto.Equal(ms.GetEffectiveDeployment(), previousCurrentDeployment) ||
if !proto.Equal(ms.GetEffectiveDeployment(), previousEffectiveDeployment) ||
ms.GetEffectiveVersioningBehavior() != previousEffectiveVersioningBehavior {
// TODO (carly) part 2: if safe mode, do replay test on new deployment if deployment changed, if fail, revert changes and abort
// If there is an ongoing transition, we remove it so that tasks from this workflow (including the pending WFT
// that initiated the transition) can run on our override deployment as soon as possible.
//
Expand All @@ -4291,21 +4312,33 @@ func (ms *MutableStateImpl) ApplyWorkflowExecutionOptionsUpdatedEvent(event *his
// and it will start the same transition in the workflow. So removing the transition would not make a difference
// and would in fact add some extra work for the server.
ms.executionInfo.GetVersioningInfo().DeploymentTransition = nil
// TODO (carly) part 2: if safe mode, do replay test on new deployment if deployment changed, if fail, revert changes and abort

// If effective deployment or behavior change, we need to reschedule any pending tasks, because History will reject
// the task's start request if the task is being started by a poller that is not from the workflow's effective
// deployment according to History. Therefore, it is important for matching to match tasks with the correct pollers.
// Even if the effective deployment does not change, we still need to reschedule tasks into the appropriate
// default/unpinned queue or the pinned queue, because the two queues will be handled differently if the task queue's
// Current Deployment changes between now and when the task is started.
//
// We choose to let any started WFT that is running on the old deployment finish running, instead of forcing it to fail.
ms.ClearStickyTaskQueue()
if ms.HasPendingWorkflowTask() && !ms.HasStartedWorkflowTask() &&
// Speculative WFT is directly (without transfer task) added to matching when scheduled.
// It is protected by timeout on both normal and sticky task queues.
// If there is no poller for previous deployment, it will time out,
// and will be rescheduled as normal WFT.
ms.GetPendingWorkflowTask().Type != enumsspb.WORKFLOW_TASK_TYPE_SPECULATIVE {
// sticky queue was just cleared, so the following call only generates
// a WorkflowTask, not a WorkflowTaskTimeoutTask.
// Sticky queue was just cleared, so the following call only generates a WorkflowTask, not a WorkflowTaskTimeoutTask.
err := ms.taskGenerator.GenerateScheduleWorkflowTaskTasks(ms.GetPendingWorkflowTask().ScheduledEventID)
if err != nil {
return err
}
}
// For v3 versioned workflows (ms.GetEffectiveVersioningBehavior() != UNSPECIFIED), this will update the reachability
// search attribute based on the execution_info.deployment and/or override deployment if one exists.
if err := ms.updateBuildIdsSearchAttribute(nil, math.MaxInt32); err != nil {
return err
}
}
return ms.reschedulePendingActivities()
}
Expand Down
7 changes: 5 additions & 2 deletions service/history/workflow/workflow_task_state_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -1098,8 +1098,11 @@ func (m *workflowTaskStateMachine) afterAddWorkflowTaskCompletedEvent(
event.GetEventId(),
limits.MaxResetPoints,
)
// For versioned workflows the search attributes should be already up-to-date based on the task started events.
// This is still useful for unversioned workers.
// For v3 versioned workflows (ms.GetEffectiveVersioningBehavior() != UNSPECIFIED), this will update the reachability
// search attribute based on the execution_info.deployment and/or override deployment if one exists. We must update the
// search attribute here because the reachability deployment may have just been changed by CompleteDeploymentTransition.
// This is also useful for unversioned workers.
// For v1 and v2 versioned workflows the search attributes should be already up-to-date based on the task started events.
if err := m.ms.updateBuildIdsSearchAttribute(attrs.GetWorkerVersion(), limits.MaxSearchAttributeValueSize); err != nil {
return err
}
Expand Down
42 changes: 41 additions & 1 deletion service/worker/deployment/deployment_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ type DeploymentStoreClient interface {
seriesName string,
NextPageToken []byte,
) ([]*deploymentpb.DeploymentListInfo, []byte, error)

GetDeploymentReachability(
ctx context.Context,
namespaceEntry *namespace.Namespace,
seriesName string,
buildID string,
) (*workflowservice.GetDeploymentReachabilityResponse, error)
}

// implements DeploymentStoreClient
Expand All @@ -86,6 +93,8 @@ type DeploymentClientImpl struct {
VisibilityManager manager.VisibilityManager
MaxIDLengthLimit dynamicconfig.IntPropertyFn
VisibilityMaxPageSize dynamicconfig.IntPropertyFnWithNamespaceFilter

reachabilityCache reachabilityCache
}

var _ DeploymentStoreClient = (*DeploymentClientImpl)(nil)
Expand Down Expand Up @@ -238,8 +247,39 @@ func (d *DeploymentClientImpl) DescribeDeployment(ctx context.Context, namespace
}, nil
}

func (d *DeploymentClientImpl) GetCurrentDeployment(ctx context.Context, namespaceEntry *namespace.Namespace, seriesName string) (*deploymentpb.DeploymentInfo, error) {
// TODO (carly): pass deployment instead of seriesName + buildId in all these APIs -- separate PR
func (d *DeploymentClientImpl) GetDeploymentReachability(
ctx context.Context,
namespaceEntry *namespace.Namespace,
seriesName string,
buildID string,
carlydf marked this conversation as resolved.
Show resolved Hide resolved
) (*workflowservice.GetDeploymentReachabilityResponse, error) {
deployInfo, err := d.DescribeDeployment(ctx, namespaceEntry, seriesName, buildID)
if err != nil {
return nil, err
}
reachability, lastUpdateTime, err := getDeploymentReachability(
ctx,
namespaceEntry.ID().String(),
namespaceEntry.Name().String(),
seriesName,
buildID,
deployInfo.GetIsCurrent(),
d.reachabilityCache,
)

if err != nil {
return nil, err
}

return &workflowservice.GetDeploymentReachabilityResponse{
DeploymentInfo: deployInfo,
Reachability: reachability,
LastUpdateTime: timestamppb.New(lastUpdateTime),
}, nil
}

func (d *DeploymentClientImpl) GetCurrentDeployment(ctx context.Context, namespaceEntry *namespace.Namespace, seriesName string) (*deploymentpb.DeploymentInfo, error) {
// Validating params
err := ValidateDeploymentWfParams(SeriesFieldName, seriesName, d.MaxIDLengthLimit())
if err != nil {
Expand Down
Loading
Loading