mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
## What changed? Added support for Nexus workflow update completion callbacks via CHASM. This allows a Nexus caller to be notified when a workflow update completes by attaching completion callbacks to the update request. ## Why? Nexus operations that target workflow updates need a way to receive completion notifications. Without this, a Nexus caller that sends an update has no async mechanism to learn when the update finishes. Completion callbacks enable the same async notification pattern that already exists for workflow-level Nexus operations. ## How did you test it? - [ ] built - [x] run locally and tested manually - [ ] covered by existing tests - [x] added new unit test(s) - [x] added new functional test(s) ## Potential risks Touches speculative workflow updates, they are always hard to reason about. Tried to compensate with lots of test coverage. Note: Needs this API PR https://github.com/temporalio/api/pull/742/changes --- > [!NOTE] > **High Risk** > Touches workflow update state machine and mutable state event handling to persist/trigger per-update callbacks, including close/retry/reset paths, which is complex and can affect correctness of update outcomes and callback delivery. > > **Overview** > Adds **workflow update completion callbacks** via CHASM so Nexus callers can register callbacks on `UpdateWorkflowExecution` and have them fired on update completion or workflow close. > > This introduces a `WorkflowUpdate` CHASM component with new `UpdateState` protobuf (including persisted `rejection_failure`), stores update callbacks under `Workflow.Updates`, and extends callback processing to handle *update-level* callbacks on update completion, rejection (including reset/reapply), and on run transitions (retry/timeout/continue-as-new) where update callbacks must fire even if workflow-level callbacks are inherited. > > It also adds dynamic config gates/limits (`EnableWorkflowUpdateCallbacks`, `MaxCallbacksPerUpdateID`), updates `DescribeWorkflow` to surface update callback triggers, extends mutable state/history builder APIs to carry per-update callback options in `WorkflowExecutionOptionsUpdated`, and adds `Update.AttachCallbacks` logic to persist/flush callbacks (including buffering while `stateSent`, request-id dedup, and stricter validation requiring `request_id` when callbacks are present). > > <sup>Reviewed by [Cursor Bugbot]() for commit 4484fee104d73344ef5370c468008440c7df1c6a. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> --------- Co-authored-by: long-nt-tran <[email protected]>
1201 lines
41 KiB
Go
1201 lines
41 KiB
Go
//go:generate mockgen -package $GOPACKAGE -source $GOFILE -destination namespace_handler_mock.go
|
|
|
|
package frontend
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
enumspb "go.temporal.io/api/enums/v1"
|
|
namespacepb "go.temporal.io/api/namespace/v1"
|
|
replicationpb "go.temporal.io/api/replication/v1"
|
|
rulespb "go.temporal.io/api/rules/v1"
|
|
"go.temporal.io/api/serviceerror"
|
|
"go.temporal.io/api/workflowservice/v1"
|
|
enumsspb "go.temporal.io/server/api/enums/v1"
|
|
persistencespb "go.temporal.io/server/api/persistence/v1"
|
|
"go.temporal.io/server/common"
|
|
"go.temporal.io/server/common/archiver"
|
|
"go.temporal.io/server/common/archiver/provider"
|
|
"go.temporal.io/server/common/clock"
|
|
"go.temporal.io/server/common/cluster"
|
|
"go.temporal.io/server/common/log"
|
|
"go.temporal.io/server/common/log/tag"
|
|
"go.temporal.io/server/common/namespace"
|
|
"go.temporal.io/server/common/namespace/nsmanager"
|
|
"go.temporal.io/server/common/namespace/nsreplication"
|
|
"go.temporal.io/server/common/persistence"
|
|
"go.temporal.io/server/common/primitives/timestamp"
|
|
"go.temporal.io/server/common/util"
|
|
"google.golang.org/protobuf/types/known/durationpb"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
)
|
|
|
|
type (
|
|
// namespaceHandler implements the namespace-related APIs specified by the [workflowservice] package,
|
|
// such as registering, updating, and querying namespaces.
|
|
namespaceHandler struct {
|
|
logger log.Logger
|
|
metadataMgr persistence.MetadataManager
|
|
namespaceRegistry namespace.Registry
|
|
clusterMetadata cluster.Metadata
|
|
namespaceReplicator nsreplication.Replicator
|
|
namespaceAttrValidator *nsmanager.Validator
|
|
archivalMetadata archiver.ArchivalMetadata
|
|
archiverProvider provider.ArchiverProvider
|
|
timeSource clock.TimeSource
|
|
config *Config
|
|
}
|
|
)
|
|
|
|
const (
|
|
maxReplicationHistorySize = 10
|
|
)
|
|
|
|
var (
|
|
// err indicating that this cluster is not the master, so cannot do namespace registration or update
|
|
errNotMasterCluster = serviceerror.NewInvalidArgument("Cluster is not master cluster, cannot do namespace registration or namespace update.")
|
|
errCannotDoNamespaceFailoverAndUpdate = serviceerror.NewInvalidArgument("Cannot set active cluster to current cluster when other parameters are set.")
|
|
errInvalidRetentionPeriod = serviceerror.NewInvalidArgument("A valid retention period is not set on request.")
|
|
errInvalidNamespaceStateUpdate = serviceerror.NewInvalidArgument("Invalid namespace state update.")
|
|
|
|
errCustomSearchAttributeFieldAlreadyAllocated = serviceerror.NewInvalidArgument("Custom search attribute field name already allocated.")
|
|
)
|
|
|
|
// newNamespaceHandler create a new namespace handler
|
|
func newNamespaceHandler(
|
|
logger log.Logger,
|
|
metadataMgr persistence.MetadataManager,
|
|
namespaceRegistry namespace.Registry,
|
|
clusterMetadata cluster.Metadata,
|
|
namespaceReplicator nsreplication.Replicator,
|
|
archivalMetadata archiver.ArchivalMetadata,
|
|
archiverProvider provider.ArchiverProvider,
|
|
timeSource clock.TimeSource,
|
|
config *Config,
|
|
) *namespaceHandler {
|
|
return &namespaceHandler{
|
|
logger: logger,
|
|
metadataMgr: metadataMgr,
|
|
namespaceRegistry: namespaceRegistry,
|
|
clusterMetadata: clusterMetadata,
|
|
namespaceReplicator: namespaceReplicator,
|
|
namespaceAttrValidator: nsmanager.NewValidator(clusterMetadata),
|
|
archivalMetadata: archivalMetadata,
|
|
archiverProvider: archiverProvider,
|
|
timeSource: timeSource,
|
|
config: config,
|
|
}
|
|
}
|
|
|
|
// RegisterNamespace register a new namespace
|
|
//
|
|
//nolint:revive // cognitive complexity grandfathered
|
|
func (d *namespaceHandler) RegisterNamespace(
|
|
ctx context.Context,
|
|
registerRequest *workflowservice.RegisterNamespaceRequest,
|
|
) (*workflowservice.RegisterNamespaceResponse, error) {
|
|
|
|
if !d.clusterMetadata.IsGlobalNamespaceEnabled() {
|
|
if registerRequest.GetIsGlobalNamespace() {
|
|
return nil, serviceerror.NewInvalidArgument("Cannot register global namespace when not enabled")
|
|
}
|
|
|
|
registerRequest.IsGlobalNamespace = false
|
|
} else {
|
|
// cluster global namespace enabled
|
|
if !d.clusterMetadata.IsMasterCluster() && registerRequest.GetIsGlobalNamespace() {
|
|
return nil, errNotMasterCluster
|
|
}
|
|
}
|
|
|
|
if err := d.validateRetentionDuration(
|
|
registerRequest.WorkflowExecutionRetentionPeriod,
|
|
registerRequest.IsGlobalNamespace,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// first check if the name is already registered as the local namespace
|
|
_, err := d.metadataMgr.GetNamespace(ctx, &persistence.GetNamespaceRequest{Name: registerRequest.GetNamespace()})
|
|
switch err.(type) {
|
|
case nil:
|
|
// namespace already exists, cannot proceed
|
|
return nil, serviceerror.NewNamespaceAlreadyExistsf("Namespace %q already exists", registerRequest.GetNamespace())
|
|
case *serviceerror.NamespaceNotFound:
|
|
// namespace does not exists, proceeds
|
|
default:
|
|
// other err
|
|
return nil, err
|
|
}
|
|
|
|
var activeClusterName string
|
|
// input validation on cluster names
|
|
if registerRequest.GetActiveClusterName() != "" {
|
|
activeClusterName = registerRequest.GetActiveClusterName()
|
|
} else {
|
|
activeClusterName = d.clusterMetadata.GetCurrentClusterName()
|
|
}
|
|
var clusters []string
|
|
for _, clusterConfig := range registerRequest.Clusters {
|
|
clusterName := clusterConfig.GetClusterName()
|
|
clusters = append(clusters, clusterName)
|
|
}
|
|
clusters = persistence.GetOrUseDefaultClusters(activeClusterName, clusters)
|
|
|
|
currentHistoryArchivalState := namespace.NeverEnabledState()
|
|
nextHistoryArchivalState := currentHistoryArchivalState
|
|
clusterHistoryArchivalConfig := d.archivalMetadata.GetHistoryConfig()
|
|
if clusterHistoryArchivalConfig.ClusterConfiguredForArchival() {
|
|
archivalEvent, err := d.toArchivalRegisterEvent(
|
|
registerRequest.HistoryArchivalState,
|
|
registerRequest.GetHistoryArchivalUri(),
|
|
clusterHistoryArchivalConfig.GetNamespaceDefaultState(),
|
|
clusterHistoryArchivalConfig.GetNamespaceDefaultURI(),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nextHistoryArchivalState, _, err = currentHistoryArchivalState.GetNextState(archivalEvent, d.validateHistoryArchivalURI)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
currentVisibilityArchivalState := namespace.NeverEnabledState()
|
|
nextVisibilityArchivalState := currentVisibilityArchivalState
|
|
clusterVisibilityArchivalConfig := d.archivalMetadata.GetVisibilityConfig()
|
|
if clusterVisibilityArchivalConfig.ClusterConfiguredForArchival() {
|
|
archivalEvent, err := d.toArchivalRegisterEvent(
|
|
registerRequest.VisibilityArchivalState,
|
|
registerRequest.GetVisibilityArchivalUri(),
|
|
clusterVisibilityArchivalConfig.GetNamespaceDefaultState(),
|
|
clusterVisibilityArchivalConfig.GetNamespaceDefaultURI(),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nextVisibilityArchivalState, _, err = currentVisibilityArchivalState.GetNextState(archivalEvent, d.validateVisibilityArchivalURI)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
info := &persistencespb.NamespaceInfo{
|
|
Id: uuid.NewString(),
|
|
Name: registerRequest.GetNamespace(),
|
|
State: enumspb.NAMESPACE_STATE_REGISTERED,
|
|
Owner: registerRequest.GetOwnerEmail(),
|
|
Description: registerRequest.GetDescription(),
|
|
Data: registerRequest.Data,
|
|
}
|
|
config := &persistencespb.NamespaceConfig{
|
|
Retention: registerRequest.GetWorkflowExecutionRetentionPeriod(),
|
|
HistoryArchivalState: nextHistoryArchivalState.State,
|
|
HistoryArchivalUri: nextHistoryArchivalState.URI,
|
|
VisibilityArchivalState: nextVisibilityArchivalState.State,
|
|
VisibilityArchivalUri: nextVisibilityArchivalState.URI,
|
|
BadBinaries: &namespacepb.BadBinaries{Binaries: map[string]*namespacepb.BadBinaryInfo{}},
|
|
CustomSearchAttributeAliases: nil,
|
|
}
|
|
replicationConfig := &persistencespb.NamespaceReplicationConfig{
|
|
ActiveClusterName: activeClusterName,
|
|
Clusters: clusters,
|
|
State: enumspb.REPLICATION_STATE_NORMAL,
|
|
}
|
|
isGlobalNamespace := registerRequest.GetIsGlobalNamespace()
|
|
|
|
if err := d.namespaceAttrValidator.ValidateNamespaceConfig(config); err != nil {
|
|
return nil, err
|
|
}
|
|
if isGlobalNamespace {
|
|
if err := d.namespaceAttrValidator.ValidateNamespaceReplicationConfigForGlobalNamespace(
|
|
replicationConfig,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
if err := d.namespaceAttrValidator.ValidateNamespaceReplicationConfigForLocalNamespace(
|
|
replicationConfig,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
failoverVersion := common.EmptyVersion
|
|
if registerRequest.GetIsGlobalNamespace() {
|
|
failoverVersion = d.clusterMetadata.GetNextFailoverVersion(activeClusterName, 0)
|
|
}
|
|
|
|
namespaceRequest := &persistence.CreateNamespaceRequest{
|
|
Namespace: &persistencespb.NamespaceDetail{
|
|
Info: info,
|
|
Config: config,
|
|
ReplicationConfig: replicationConfig,
|
|
ConfigVersion: 0,
|
|
FailoverVersion: failoverVersion,
|
|
},
|
|
IsGlobalNamespace: isGlobalNamespace,
|
|
}
|
|
|
|
namespaceResponse, err := d.metadataMgr.CreateNamespace(ctx, namespaceRequest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = d.namespaceReplicator.HandleTransmissionTask(
|
|
ctx,
|
|
enumsspb.NAMESPACE_OPERATION_CREATE,
|
|
namespaceRequest.Namespace.Info,
|
|
namespaceRequest.Namespace.Config,
|
|
namespaceRequest.Namespace.ReplicationConfig,
|
|
false,
|
|
namespaceRequest.Namespace.ConfigVersion,
|
|
namespaceRequest.Namespace.FailoverVersion,
|
|
namespaceRequest.IsGlobalNamespace,
|
|
nil,
|
|
false, // forceReplicate
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
d.logger.Info("Register namespace succeeded",
|
|
tag.WorkflowNamespace(registerRequest.GetNamespace()),
|
|
tag.WorkflowNamespaceID(namespaceResponse.ID),
|
|
)
|
|
|
|
return &workflowservice.RegisterNamespaceResponse{}, nil
|
|
}
|
|
|
|
// ListNamespaces list all namespaces
|
|
func (d *namespaceHandler) ListNamespaces(
|
|
ctx context.Context,
|
|
listRequest *workflowservice.ListNamespacesRequest,
|
|
) (*workflowservice.ListNamespacesResponse, error) {
|
|
|
|
pageSize := 100
|
|
if listRequest.GetPageSize() != 0 {
|
|
pageSize = int(listRequest.GetPageSize())
|
|
}
|
|
|
|
resp, err := d.metadataMgr.ListNamespaces(ctx, &persistence.ListNamespacesRequest{
|
|
PageSize: pageSize,
|
|
NextPageToken: listRequest.NextPageToken,
|
|
IncludeDeleted: listRequest.GetNamespaceFilter().GetIncludeDeleted(),
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var namespaces []*workflowservice.DescribeNamespaceResponse
|
|
for _, namespace := range resp.Namespaces {
|
|
desc := &workflowservice.DescribeNamespaceResponse{
|
|
IsGlobalNamespace: namespace.IsGlobalNamespace,
|
|
FailoverVersion: namespace.Namespace.FailoverVersion,
|
|
}
|
|
desc.NamespaceInfo, desc.Config, desc.ReplicationConfig, desc.FailoverHistory =
|
|
d.createResponse(
|
|
namespace.Namespace.Info,
|
|
namespace.Namespace.Config,
|
|
namespace.Namespace.ReplicationConfig)
|
|
namespaces = append(namespaces, desc)
|
|
}
|
|
|
|
response := &workflowservice.ListNamespacesResponse{
|
|
Namespaces: namespaces,
|
|
NextPageToken: resp.NextPageToken,
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
// DescribeNamespace describe the namespace
|
|
func (d *namespaceHandler) DescribeNamespace(
|
|
ctx context.Context,
|
|
describeRequest *workflowservice.DescribeNamespaceRequest,
|
|
) (*workflowservice.DescribeNamespaceResponse, error) {
|
|
|
|
if describeRequest.GetWeakConsistency() {
|
|
return d.describeNamespaceFromRegistry(describeRequest)
|
|
}
|
|
|
|
// TODO, we should migrate the non global namespace to new table, see #773
|
|
req := &persistence.GetNamespaceRequest{
|
|
Name: describeRequest.GetNamespace(),
|
|
ID: describeRequest.GetId(),
|
|
}
|
|
resp, err := d.metadataMgr.GetNamespace(ctx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
response := &workflowservice.DescribeNamespaceResponse{
|
|
IsGlobalNamespace: resp.IsGlobalNamespace,
|
|
FailoverVersion: resp.Namespace.FailoverVersion,
|
|
}
|
|
response.NamespaceInfo, response.Config, response.ReplicationConfig, response.FailoverHistory =
|
|
d.createResponse(resp.Namespace.Info, resp.Namespace.Config, resp.Namespace.ReplicationConfig)
|
|
return response, nil
|
|
}
|
|
|
|
// describeNamespaceFromRegistry serves DescribeNamespace from the in-memory namespace registry. The response may be stale,
|
|
// and may return NamespaceNotFound for namespaces created within the registry's refresh window.
|
|
func (d *namespaceHandler) describeNamespaceFromRegistry(request *workflowservice.DescribeNamespaceRequest) (*workflowservice.DescribeNamespaceResponse, error) {
|
|
opts := namespace.GetNamespaceOptions{DisableReadthrough: true}
|
|
var ns *namespace.Namespace
|
|
var err error
|
|
switch {
|
|
case request.GetId() != "" && request.GetNamespace() != "":
|
|
return nil, serviceerror.NewInvalidArgument("GetNamespace operation failed. Both ID and Name specified in request.")
|
|
case request.GetId() != "":
|
|
ns, err = d.namespaceRegistry.GetNamespaceByIDWithOptions(namespace.ID(request.GetId()), opts)
|
|
case request.GetNamespace() != "":
|
|
ns, err = d.namespaceRegistry.GetNamespaceWithOptions(namespace.Name(request.GetNamespace()), opts)
|
|
default:
|
|
return nil, errNamespaceNotSet
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
response := &workflowservice.DescribeNamespaceResponse{
|
|
IsGlobalNamespace: ns.IsGlobalNamespace(),
|
|
FailoverVersion: ns.FailoverVersion(namespace.EmptyBusinessID),
|
|
}
|
|
response.NamespaceInfo, response.Config, response.ReplicationConfig, response.FailoverHistory =
|
|
d.createResponse(ns.Info(), ns.Config(), ns.ReplicationConfig())
|
|
return response, nil
|
|
}
|
|
|
|
// UpdateNamespace update the namespace
|
|
//
|
|
//nolint:revive // cognitive complexity grandfathered
|
|
func (d *namespaceHandler) UpdateNamespace(
|
|
ctx context.Context,
|
|
updateRequest *workflowservice.UpdateNamespaceRequest,
|
|
) (*workflowservice.UpdateNamespaceResponse, error) {
|
|
|
|
// must get the metadata (notificationVersion) first
|
|
// this version can be regarded as the lock on the v2 namespace table
|
|
// and since we do not know which table will return the namespace afterwards
|
|
// this call has to be made
|
|
metadata, err := d.metadataMgr.GetMetadata(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
notificationVersion := metadata.NotificationVersion
|
|
getResponse, err := d.metadataMgr.GetNamespace(ctx, &persistence.GetNamespaceRequest{Name: updateRequest.GetNamespace()})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
info := getResponse.Namespace.Info
|
|
config := getResponse.Namespace.Config
|
|
replicationConfig := getResponse.Namespace.ReplicationConfig
|
|
failoverHistory := getResponse.Namespace.ReplicationConfig.FailoverHistory
|
|
configVersion := getResponse.Namespace.ConfigVersion
|
|
failoverVersion := getResponse.Namespace.FailoverVersion
|
|
failoverNotificationVersion := getResponse.Namespace.FailoverNotificationVersion
|
|
isGlobalNamespace := getResponse.IsGlobalNamespace || updateRequest.PromoteNamespace
|
|
needsNamespacePromotion := !getResponse.IsGlobalNamespace && updateRequest.PromoteNamespace
|
|
|
|
currentHistoryArchivalState := &namespace.ArchivalConfigState{
|
|
State: config.HistoryArchivalState,
|
|
URI: config.HistoryArchivalUri,
|
|
}
|
|
nextHistoryArchivalState := currentHistoryArchivalState
|
|
historyArchivalConfigChanged := false
|
|
clusterHistoryArchivalConfig := d.archivalMetadata.GetHistoryConfig()
|
|
if updateRequest.Config != nil && clusterHistoryArchivalConfig.ClusterConfiguredForArchival() {
|
|
cfg := updateRequest.GetConfig()
|
|
archivalEvent, err := d.toArchivalUpdateEvent(cfg.HistoryArchivalState, cfg.GetHistoryArchivalUri(), clusterHistoryArchivalConfig.GetNamespaceDefaultURI())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nextHistoryArchivalState, historyArchivalConfigChanged, err = currentHistoryArchivalState.GetNextState(archivalEvent, d.validateHistoryArchivalURI)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
currentVisibilityArchivalState := &namespace.ArchivalConfigState{
|
|
State: config.VisibilityArchivalState,
|
|
URI: config.VisibilityArchivalUri,
|
|
}
|
|
nextVisibilityArchivalState := currentVisibilityArchivalState
|
|
visibilityArchivalConfigChanged := false
|
|
clusterVisibilityArchivalConfig := d.archivalMetadata.GetVisibilityConfig()
|
|
if updateRequest.Config != nil && clusterVisibilityArchivalConfig.ClusterConfiguredForArchival() {
|
|
cfg := updateRequest.GetConfig()
|
|
archivalEvent, err := d.toArchivalUpdateEvent(cfg.VisibilityArchivalState, cfg.GetVisibilityArchivalUri(), clusterVisibilityArchivalConfig.GetNamespaceDefaultURI())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nextVisibilityArchivalState, visibilityArchivalConfigChanged, err = currentVisibilityArchivalState.GetNextState(archivalEvent, d.validateVisibilityArchivalURI)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// whether active cluster is changed
|
|
activeClusterChanged := false
|
|
// whether anything other than active cluster is changed
|
|
configurationChanged := false
|
|
// whether replication cluster list is changed
|
|
clusterListChanged := false
|
|
|
|
if updateRequest.UpdateInfo != nil {
|
|
updatedInfo := updateRequest.UpdateInfo
|
|
if updatedInfo.GetDescription() != "" {
|
|
configurationChanged = true
|
|
info.Description = updatedInfo.GetDescription()
|
|
}
|
|
if updatedInfo.GetOwnerEmail() != "" {
|
|
configurationChanged = true
|
|
info.Owner = updatedInfo.GetOwnerEmail()
|
|
}
|
|
if updatedInfo.Data != nil {
|
|
configurationChanged = true
|
|
// only do merging
|
|
info.Data = d.mergeNamespaceData(info.Data, updatedInfo.Data)
|
|
}
|
|
if updatedInfo.State != enumspb.NAMESPACE_STATE_UNSPECIFIED && info.State != updatedInfo.State {
|
|
configurationChanged = true
|
|
if err := validateStateUpdate(getResponse, updateRequest); err != nil {
|
|
return nil, err
|
|
}
|
|
info.State = updatedInfo.State
|
|
}
|
|
}
|
|
if updateRequest.Config != nil {
|
|
updatedConfig := updateRequest.Config
|
|
if updatedConfig.GetWorkflowExecutionRetentionTtl() != nil {
|
|
configurationChanged = true
|
|
|
|
config.Retention = updatedConfig.GetWorkflowExecutionRetentionTtl()
|
|
if err := d.validateRetentionDuration(
|
|
config.Retention,
|
|
isGlobalNamespace,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if historyArchivalConfigChanged {
|
|
configurationChanged = true
|
|
config.HistoryArchivalState = nextHistoryArchivalState.State
|
|
config.HistoryArchivalUri = nextHistoryArchivalState.URI
|
|
}
|
|
if visibilityArchivalConfigChanged {
|
|
configurationChanged = true
|
|
config.VisibilityArchivalState = nextVisibilityArchivalState.State
|
|
config.VisibilityArchivalUri = nextVisibilityArchivalState.URI
|
|
}
|
|
if updatedConfig.BadBinaries != nil {
|
|
maxLength := d.config.MaxBadBinaries(updateRequest.GetNamespace())
|
|
// only do merging
|
|
bb := d.mergeBadBinaries(config.BadBinaries.Binaries, updatedConfig.BadBinaries.Binaries, time.Now().UTC())
|
|
config.BadBinaries = &bb
|
|
if len(config.BadBinaries.Binaries) > maxLength {
|
|
return nil, serviceerror.NewInvalidArgumentf("Total resetBinaries cannot exceed the max limit: %v", maxLength)
|
|
}
|
|
}
|
|
if len(updatedConfig.CustomSearchAttributeAliases) > 0 {
|
|
configurationChanged = true
|
|
csaAliases, err := d.upsertCustomSearchAttributesAliases(
|
|
config.CustomSearchAttributeAliases,
|
|
updatedConfig.CustomSearchAttributeAliases,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
config.CustomSearchAttributeAliases = csaAliases
|
|
}
|
|
}
|
|
|
|
if updateRequest.GetDeleteBadBinary() != "" {
|
|
binChecksum := updateRequest.GetDeleteBadBinary()
|
|
_, ok := config.BadBinaries.Binaries[binChecksum]
|
|
if !ok {
|
|
return nil, serviceerror.NewInvalidArgumentf("Bad binary checksum %v doesn't exists.", binChecksum)
|
|
}
|
|
configurationChanged = true
|
|
delete(config.BadBinaries.Binaries, binChecksum)
|
|
}
|
|
|
|
if updateRequest.ReplicationConfig != nil {
|
|
updateReplicationConfig := updateRequest.ReplicationConfig
|
|
if len(updateReplicationConfig.Clusters) != 0 {
|
|
configurationChanged = true
|
|
clusterListChanged = true
|
|
var clustersNew []string
|
|
for _, clusterConfig := range updateReplicationConfig.Clusters {
|
|
clustersNew = append(clustersNew, clusterConfig.GetClusterName())
|
|
}
|
|
replicationConfig.Clusters = clustersNew
|
|
}
|
|
if updateReplicationConfig.State != enumspb.REPLICATION_STATE_UNSPECIFIED &&
|
|
updateReplicationConfig.State != replicationConfig.State {
|
|
if err := validateReplicationStateUpdate(getResponse, updateRequest); err != nil {
|
|
return nil, err
|
|
}
|
|
configurationChanged = true
|
|
replicationConfig.State = updateReplicationConfig.State
|
|
}
|
|
|
|
if updateReplicationConfig.GetActiveClusterName() != "" {
|
|
activeClusterChanged = true
|
|
replicationConfig.ActiveClusterName = updateReplicationConfig.GetActiveClusterName()
|
|
replicationConfig.State = enumspb.REPLICATION_STATE_NORMAL
|
|
}
|
|
}
|
|
|
|
if err := d.namespaceAttrValidator.ValidateNamespaceConfig(config); err != nil {
|
|
return nil, err
|
|
}
|
|
if isGlobalNamespace {
|
|
if err := d.namespaceAttrValidator.ValidateNamespaceReplicationConfigForGlobalNamespace(
|
|
replicationConfig,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
if !d.clusterMetadata.IsGlobalNamespaceEnabled() {
|
|
return nil, serviceerror.NewInvalidArgumentf("global namespace is not enabled on this "+
|
|
"cluster, cannot update global namespace or promote local namespace: %v", updateRequest.Namespace)
|
|
}
|
|
} else {
|
|
if err := d.namespaceAttrValidator.ValidateNamespaceReplicationConfigForLocalNamespace(
|
|
replicationConfig,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if configurationChanged && activeClusterChanged && isGlobalNamespace {
|
|
return nil, errCannotDoNamespaceFailoverAndUpdate
|
|
} else if configurationChanged || activeClusterChanged || needsNamespacePromotion {
|
|
if (needsNamespacePromotion || activeClusterChanged) && isGlobalNamespace {
|
|
failoverVersion = d.clusterMetadata.GetNextFailoverVersion(
|
|
replicationConfig.ActiveClusterName,
|
|
failoverVersion,
|
|
)
|
|
failoverNotificationVersion = notificationVersion
|
|
}
|
|
// set the versions
|
|
if configurationChanged {
|
|
configVersion++
|
|
}
|
|
|
|
if (configurationChanged || activeClusterChanged) && isGlobalNamespace {
|
|
// N.B., it should be sufficient to check only for activeClusterChanged. In order to be defensive, we also
|
|
// check for configurationChanged. If nothing needs to be updated this will be a no-op.
|
|
failoverHistory = d.maybeUpdateFailoverHistory(
|
|
failoverHistory,
|
|
updateRequest.ReplicationConfig,
|
|
getResponse.Namespace,
|
|
failoverVersion,
|
|
)
|
|
}
|
|
|
|
replicationConfig.FailoverHistory = failoverHistory
|
|
updateReq := &persistence.UpdateNamespaceRequest{
|
|
Namespace: &persistencespb.NamespaceDetail{
|
|
Info: info,
|
|
Config: config,
|
|
ReplicationConfig: replicationConfig,
|
|
ConfigVersion: configVersion,
|
|
FailoverVersion: failoverVersion,
|
|
FailoverNotificationVersion: failoverNotificationVersion,
|
|
},
|
|
IsGlobalNamespace: isGlobalNamespace,
|
|
NotificationVersion: notificationVersion,
|
|
}
|
|
err = d.metadataMgr.UpdateNamespace(ctx, updateReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
err = d.namespaceReplicator.HandleTransmissionTask(
|
|
ctx,
|
|
enumsspb.NAMESPACE_OPERATION_UPDATE,
|
|
info,
|
|
config,
|
|
replicationConfig,
|
|
clusterListChanged,
|
|
configVersion,
|
|
failoverVersion,
|
|
isGlobalNamespace,
|
|
failoverHistory,
|
|
false, // forceReplicate
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
response := &workflowservice.UpdateNamespaceResponse{
|
|
IsGlobalNamespace: isGlobalNamespace,
|
|
FailoverVersion: failoverVersion,
|
|
}
|
|
response.NamespaceInfo, response.Config, response.ReplicationConfig, _ = d.createResponse(info, config, replicationConfig)
|
|
|
|
d.logger.Info("Update namespace succeeded",
|
|
tag.WorkflowNamespace(info.Name),
|
|
tag.WorkflowNamespaceID(info.Id),
|
|
)
|
|
return response, nil
|
|
}
|
|
|
|
// DeprecateNamespace deprecates a namespace
|
|
// Deprecated.
|
|
func (d *namespaceHandler) DeprecateNamespace(
|
|
ctx context.Context,
|
|
deprecateRequest *workflowservice.DeprecateNamespaceRequest,
|
|
) (*workflowservice.DeprecateNamespaceResponse, error) {
|
|
|
|
clusterMetadata := d.clusterMetadata
|
|
// TODO remove the IsGlobalNamespaceEnabled check once cross DC is public
|
|
if clusterMetadata.IsGlobalNamespaceEnabled() && !clusterMetadata.IsMasterCluster() {
|
|
return nil, errNotMasterCluster
|
|
}
|
|
|
|
// must get the metadata (notificationVersion) first
|
|
// this version can be regarded as the lock on the v2 namespace table
|
|
// and since we do not know which table will return the namespace afterwards
|
|
// this call has to be made
|
|
metadata, err := d.metadataMgr.GetMetadata(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
notificationVersion := metadata.NotificationVersion
|
|
getResponse, err := d.metadataMgr.GetNamespace(ctx, &persistence.GetNamespaceRequest{Name: deprecateRequest.GetNamespace()})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
getResponse.Namespace.ConfigVersion = getResponse.Namespace.ConfigVersion + 1
|
|
getResponse.Namespace.Info.State = enumspb.NAMESPACE_STATE_DEPRECATED
|
|
updateReq := &persistence.UpdateNamespaceRequest{
|
|
Namespace: &persistencespb.NamespaceDetail{
|
|
Info: getResponse.Namespace.Info,
|
|
Config: getResponse.Namespace.Config,
|
|
ReplicationConfig: getResponse.Namespace.ReplicationConfig,
|
|
ConfigVersion: getResponse.Namespace.ConfigVersion,
|
|
FailoverVersion: getResponse.Namespace.FailoverVersion,
|
|
FailoverNotificationVersion: getResponse.Namespace.FailoverNotificationVersion,
|
|
},
|
|
NotificationVersion: notificationVersion,
|
|
IsGlobalNamespace: getResponse.IsGlobalNamespace,
|
|
}
|
|
err = d.metadataMgr.UpdateNamespace(ctx, updateReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (d *namespaceHandler) CreateWorkflowRule(
|
|
ctx context.Context,
|
|
ruleSpec *rulespb.WorkflowRuleSpec,
|
|
createdByIdentity string,
|
|
description string,
|
|
nsName string,
|
|
) (*rulespb.WorkflowRule, error) {
|
|
|
|
if ruleSpec.GetId() == "" {
|
|
return nil, serviceerror.NewInvalidArgument("Workflow Rule ID is not set.")
|
|
}
|
|
|
|
metadata, err := d.metadataMgr.GetMetadata(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
getNamespaceResponse, err := d.metadataMgr.GetNamespace(ctx, &persistence.GetNamespaceRequest{Name: nsName})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
existingNamespace := getNamespaceResponse.Namespace
|
|
config := getNamespaceResponse.Namespace.Config
|
|
|
|
if config.WorkflowRules == nil {
|
|
config.WorkflowRules = make(map[string]*rulespb.WorkflowRule)
|
|
} else {
|
|
maxRules := d.config.MaxWorkflowRulesPerNamespace(nsName)
|
|
if len(config.WorkflowRules) >= maxRules {
|
|
d.removeOldestExpiredWorkflowRule(nsName, config.WorkflowRules)
|
|
}
|
|
if len(config.WorkflowRules) >= maxRules {
|
|
return nil, serviceerror.NewInvalidArgumentf("Workflow Rule limit exceeded. Max: %v", maxRules)
|
|
}
|
|
}
|
|
|
|
_, ok := config.WorkflowRules[ruleSpec.GetId()]
|
|
if ok {
|
|
return nil, serviceerror.NewInvalidArgument("Workflow Rule with this ID already exists.")
|
|
}
|
|
|
|
workflowRule := &rulespb.WorkflowRule{
|
|
Spec: ruleSpec,
|
|
CreateTime: timestamppb.New(d.timeSource.Now()),
|
|
CreatedByIdentity: createdByIdentity,
|
|
Description: description,
|
|
}
|
|
config.WorkflowRules[ruleSpec.GetId()] = workflowRule
|
|
|
|
updateReq := &persistence.UpdateNamespaceRequest{
|
|
Namespace: &persistencespb.NamespaceDetail{
|
|
Info: existingNamespace.Info,
|
|
Config: config,
|
|
ReplicationConfig: existingNamespace.ReplicationConfig,
|
|
ConfigVersion: existingNamespace.ConfigVersion + 1,
|
|
FailoverVersion: existingNamespace.FailoverVersion,
|
|
FailoverNotificationVersion: existingNamespace.FailoverNotificationVersion,
|
|
},
|
|
IsGlobalNamespace: getNamespaceResponse.IsGlobalNamespace,
|
|
NotificationVersion: metadata.NotificationVersion,
|
|
}
|
|
err = d.metadataMgr.UpdateNamespace(ctx, updateReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return workflowRule, nil
|
|
}
|
|
|
|
func (d *namespaceHandler) removeOldestExpiredWorkflowRule(nsName string, rules map[string]*rulespb.WorkflowRule) {
|
|
oldestTime := d.timeSource.Now()
|
|
var oldestKey string
|
|
found := false
|
|
|
|
for key, rule := range rules {
|
|
expirationTime := rule.GetSpec().GetExpirationTime()
|
|
if expirationTime == nil {
|
|
continue
|
|
}
|
|
if !found || expirationTime.AsTime().Before(oldestTime) {
|
|
oldestTime = expirationTime.AsTime()
|
|
oldestKey = key
|
|
found = true
|
|
}
|
|
}
|
|
|
|
if found {
|
|
d.logger.Info(
|
|
"Removed expired workflow rule",
|
|
tag.WorkflowRuleID(oldestKey),
|
|
tag.WorkflowNamespace(nsName),
|
|
)
|
|
delete(rules, oldestKey)
|
|
}
|
|
}
|
|
|
|
func (d *namespaceHandler) DescribeWorkflowRule(
|
|
ctx context.Context, ruleID string, nsName string,
|
|
) (*rulespb.WorkflowRule, error) {
|
|
getNamespaceResponse, err := d.metadataMgr.GetNamespace(ctx, &persistence.GetNamespaceRequest{Name: nsName})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if getNamespaceResponse.Namespace.Config.WorkflowRules == nil {
|
|
return nil, serviceerror.NewInvalidArgument("Workflow Rule with this ID not Found.")
|
|
}
|
|
|
|
rule, ok := getNamespaceResponse.Namespace.Config.WorkflowRules[ruleID]
|
|
if !ok {
|
|
return nil, serviceerror.NewInvalidArgument("Workflow Rule with this ID not Found.")
|
|
}
|
|
|
|
return rule, nil
|
|
}
|
|
|
|
func (d *namespaceHandler) DeleteWorkflowRule(
|
|
ctx context.Context, ruleID string, nsName string,
|
|
) error {
|
|
if ruleID == "" {
|
|
return serviceerror.NewInvalidArgument("Workflow Rule ID is not set.")
|
|
}
|
|
|
|
metadata, err := d.metadataMgr.GetMetadata(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
getNamespaceResponse, err := d.metadataMgr.GetNamespace(ctx, &persistence.GetNamespaceRequest{Name: nsName})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
existingNamespace := getNamespaceResponse.Namespace
|
|
config := getNamespaceResponse.Namespace.Config
|
|
if config.WorkflowRules == nil {
|
|
return serviceerror.NewInvalidArgument("Workflow Rule with this ID not Found.")
|
|
}
|
|
_, ok := config.WorkflowRules[ruleID]
|
|
if !ok {
|
|
return serviceerror.NewInvalidArgument("Workflow Rule with this ID not Found.")
|
|
}
|
|
|
|
delete(config.WorkflowRules, ruleID)
|
|
|
|
updateReq := &persistence.UpdateNamespaceRequest{
|
|
Namespace: &persistencespb.NamespaceDetail{
|
|
Info: existingNamespace.Info,
|
|
Config: config,
|
|
ReplicationConfig: existingNamespace.ReplicationConfig,
|
|
ConfigVersion: existingNamespace.ConfigVersion + 1,
|
|
FailoverVersion: existingNamespace.FailoverVersion,
|
|
FailoverNotificationVersion: existingNamespace.FailoverNotificationVersion,
|
|
},
|
|
IsGlobalNamespace: getNamespaceResponse.IsGlobalNamespace,
|
|
NotificationVersion: metadata.NotificationVersion,
|
|
}
|
|
return d.metadataMgr.UpdateNamespace(ctx, updateReq)
|
|
}
|
|
|
|
func (d *namespaceHandler) ListWorkflowRules(
|
|
ctx context.Context, nsName string,
|
|
) ([]*rulespb.WorkflowRule, error) {
|
|
getNamespaceResponse, err := d.metadataMgr.GetNamespace(ctx, &persistence.GetNamespaceRequest{Name: nsName})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
workflowRulesMap := getNamespaceResponse.Namespace.Config.WorkflowRules
|
|
if workflowRulesMap == nil {
|
|
return []*rulespb.WorkflowRule{}, nil
|
|
}
|
|
|
|
workflowRules := make([]*rulespb.WorkflowRule, 0, len(workflowRulesMap))
|
|
for _, rule := range workflowRulesMap {
|
|
workflowRules = append(workflowRules, rule)
|
|
}
|
|
return workflowRules, nil
|
|
}
|
|
|
|
func (d *namespaceHandler) createResponse(
|
|
info *persistencespb.NamespaceInfo,
|
|
config *persistencespb.NamespaceConfig,
|
|
replicationConfig *persistencespb.NamespaceReplicationConfig,
|
|
) (*namespacepb.NamespaceInfo, *namespacepb.NamespaceConfig, *replicationpb.NamespaceReplicationConfig, []*replicationpb.FailoverStatus) {
|
|
|
|
numConsecutiveWorkflowTaskProblemsToTriggerSearchAttribute := d.config.NumConsecutiveWorkflowTaskProblemsToTriggerSearchAttribute(info.Name)
|
|
|
|
infoResult := &namespacepb.NamespaceInfo{
|
|
Name: info.Name,
|
|
State: info.State,
|
|
Description: info.Description,
|
|
OwnerEmail: info.Owner,
|
|
Data: info.Data,
|
|
Id: info.Id,
|
|
|
|
Capabilities: &namespacepb.NamespaceInfo_Capabilities{
|
|
EagerWorkflowStart: d.config.EnableEagerWorkflowStart(info.Name),
|
|
SyncUpdate: d.config.EnableUpdateWorkflowExecution(info.Name),
|
|
AsyncUpdate: d.config.EnableUpdateWorkflowExecutionAsyncAccepted(info.Name),
|
|
ReportedProblemsSearchAttribute: numConsecutiveWorkflowTaskProblemsToTriggerSearchAttribute > 0,
|
|
WorkerHeartbeats: d.config.WorkerHeartbeatsEnabled(info.Name),
|
|
WorkflowPause: d.config.WorkflowPauseEnabled(info.Name),
|
|
StandaloneActivities: d.config.Activity.Enabled(info.Name),
|
|
StandaloneNexusOperation: d.config.EnableChasm(info.Name) && d.config.StandaloneNexusOperationsEnabled(info.Name),
|
|
WorkerPollCompleteOnShutdown: d.config.EnableCancelWorkerPollsOnShutdown(info.Name),
|
|
WorkerCommands: d.config.WorkerCommandsEnabled(info.Name),
|
|
WorkflowUpdateCallbacks: d.config.EnableWorkflowUpdateCallbacks(info.Name),
|
|
PollerAutoscaling: true,
|
|
},
|
|
Limits: &namespacepb.NamespaceInfo_Limits{
|
|
BlobSizeLimitError: int64(d.config.BlobSizeLimitError(info.Name)),
|
|
MemoSizeLimitError: int64(d.config.MemoSizeLimitError(info.Name)),
|
|
},
|
|
SupportsSchedules: d.config.EnableSchedules(info.Name),
|
|
}
|
|
|
|
configResult := &namespacepb.NamespaceConfig{
|
|
WorkflowExecutionRetentionTtl: config.Retention,
|
|
HistoryArchivalState: config.HistoryArchivalState,
|
|
HistoryArchivalUri: config.HistoryArchivalUri,
|
|
VisibilityArchivalState: config.VisibilityArchivalState,
|
|
VisibilityArchivalUri: config.VisibilityArchivalUri,
|
|
BadBinaries: config.BadBinaries,
|
|
CustomSearchAttributeAliases: config.CustomSearchAttributeAliases,
|
|
}
|
|
|
|
var clusters []*replicationpb.ClusterReplicationConfig
|
|
for _, cluster := range replicationConfig.Clusters {
|
|
clusters = append(clusters, &replicationpb.ClusterReplicationConfig{
|
|
ClusterName: cluster,
|
|
})
|
|
}
|
|
replicationConfigResult := &replicationpb.NamespaceReplicationConfig{
|
|
ActiveClusterName: replicationConfig.ActiveClusterName,
|
|
Clusters: clusters,
|
|
State: replicationConfig.GetState(),
|
|
}
|
|
|
|
var failoverHistory []*replicationpb.FailoverStatus
|
|
for _, entry := range replicationConfig.GetFailoverHistory() {
|
|
failoverHistory = append(failoverHistory, &replicationpb.FailoverStatus{
|
|
FailoverTime: entry.GetFailoverTime(),
|
|
FailoverVersion: entry.GetFailoverVersion(),
|
|
})
|
|
}
|
|
|
|
return infoResult, configResult, replicationConfigResult, failoverHistory
|
|
}
|
|
|
|
func (d *namespaceHandler) mergeBadBinaries(
|
|
old map[string]*namespacepb.BadBinaryInfo,
|
|
new map[string]*namespacepb.BadBinaryInfo,
|
|
createTime time.Time,
|
|
) namespacepb.BadBinaries {
|
|
|
|
if old == nil {
|
|
old = map[string]*namespacepb.BadBinaryInfo{}
|
|
}
|
|
for k, v := range new {
|
|
v.CreateTime = timestamppb.New(createTime)
|
|
old[k] = v
|
|
}
|
|
return namespacepb.BadBinaries{
|
|
Binaries: old,
|
|
}
|
|
}
|
|
|
|
func (d *namespaceHandler) mergeNamespaceData(
|
|
old map[string]string,
|
|
new map[string]string,
|
|
) map[string]string {
|
|
|
|
if old == nil {
|
|
old = map[string]string{}
|
|
}
|
|
for k, v := range new {
|
|
old[k] = v
|
|
}
|
|
return old
|
|
}
|
|
|
|
func (d *namespaceHandler) upsertCustomSearchAttributesAliases(
|
|
current map[string]string,
|
|
upsert map[string]string,
|
|
) (map[string]string, error) {
|
|
result := util.CloneMapNonNil(current)
|
|
currentAliasToField := util.InverseMap(current)
|
|
for key, value := range upsert {
|
|
if value == "" {
|
|
delete(result, key)
|
|
} else if _, aliasExists := currentAliasToField[value]; aliasExists {
|
|
d.logger.Warn(
|
|
fmt.Sprintf(errSearchAttributeAlreadyExistsMessage, value),
|
|
tag.String("visibility-search-attribute", value),
|
|
)
|
|
} else if _, ok := current[key]; ok {
|
|
return nil, errCustomSearchAttributeFieldAlreadyAllocated
|
|
} else {
|
|
result[key] = value
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (d *namespaceHandler) toArchivalRegisterEvent(
|
|
state enumspb.ArchivalState,
|
|
URI string,
|
|
defaultState enumspb.ArchivalState,
|
|
defaultURI string,
|
|
) (*namespace.ArchivalConfigEvent, error) {
|
|
|
|
event := &namespace.ArchivalConfigEvent{
|
|
State: state,
|
|
URI: URI,
|
|
DefaultURI: defaultURI,
|
|
}
|
|
if event.State == enumspb.ARCHIVAL_STATE_UNSPECIFIED {
|
|
event.State = defaultState
|
|
}
|
|
if err := event.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
return event, nil
|
|
}
|
|
|
|
func (d *namespaceHandler) toArchivalUpdateEvent(
|
|
state enumspb.ArchivalState,
|
|
URI string,
|
|
defaultURI string,
|
|
) (*namespace.ArchivalConfigEvent, error) {
|
|
|
|
event := &namespace.ArchivalConfigEvent{
|
|
State: state,
|
|
URI: URI,
|
|
DefaultURI: defaultURI,
|
|
}
|
|
if err := event.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
return event, nil
|
|
}
|
|
|
|
func (d *namespaceHandler) validateHistoryArchivalURI(URIString string) error {
|
|
URI, err := archiver.NewURI(URIString)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
a, err := d.archiverProvider.GetHistoryArchiver(URI.Scheme())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return a.ValidateURI(URI)
|
|
}
|
|
|
|
func (d *namespaceHandler) validateVisibilityArchivalURI(URIString string) error {
|
|
URI, err := archiver.NewURI(URIString)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
a, err := d.archiverProvider.GetVisibilityArchiver(URI.Scheme())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return a.ValidateURI(URI)
|
|
}
|
|
|
|
// maybeUpdateFailoverHistory adds an entry if the Namespace is becoming active in a new cluster.
|
|
func (d *namespaceHandler) maybeUpdateFailoverHistory(
|
|
failoverHistory []*persistencespb.FailoverStatus,
|
|
updateReplicationConfig *replicationpb.NamespaceReplicationConfig,
|
|
namespaceDetail *persistencespb.NamespaceDetail,
|
|
newFailoverVersion int64,
|
|
) []*persistencespb.FailoverStatus {
|
|
d.logger.Debug(
|
|
"maybeUpdateFailoverHistory",
|
|
tag.Any("failoverHistory", failoverHistory),
|
|
tag.Any("updateReplConfig", updateReplicationConfig),
|
|
tag.Any("namespaceDetail", namespaceDetail),
|
|
)
|
|
if updateReplicationConfig == nil {
|
|
d.logger.Debug("updateReplicationConfig was nil")
|
|
return failoverHistory
|
|
}
|
|
|
|
lastFailoverVersion := int64(-1)
|
|
if l := len(namespaceDetail.ReplicationConfig.FailoverHistory); l > 0 {
|
|
lastFailoverVersion = namespaceDetail.ReplicationConfig.FailoverHistory[l-1].FailoverVersion
|
|
}
|
|
if lastFailoverVersion != newFailoverVersion {
|
|
now := d.timeSource.Now()
|
|
failoverHistory = append(
|
|
failoverHistory, &persistencespb.FailoverStatus{
|
|
FailoverTime: timestamppb.New(now),
|
|
FailoverVersion: newFailoverVersion,
|
|
},
|
|
)
|
|
}
|
|
if l := len(failoverHistory); l > maxReplicationHistorySize {
|
|
failoverHistory = failoverHistory[l-maxReplicationHistorySize : l]
|
|
}
|
|
return failoverHistory
|
|
}
|
|
|
|
// validateRetentionDuration ensures that retention duration can't be set below a sane minimum.
|
|
func (d *namespaceHandler) validateRetentionDuration(retention *durationpb.Duration, isGlobalNamespace bool) error {
|
|
if err := timestamp.ValidateAndCapProtoDuration(retention); err != nil {
|
|
return errInvalidRetentionPeriod
|
|
}
|
|
|
|
var minRetention time.Duration
|
|
if isGlobalNamespace {
|
|
minRetention = d.config.NamespaceMinRetentionGlobal()
|
|
} else {
|
|
minRetention = d.config.NamespaceMinRetentionLocal()
|
|
}
|
|
|
|
if timestamp.DurationValue(retention) < minRetention {
|
|
return errInvalidRetentionPeriod
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateReplicationStateUpdate(existingNamespace *persistence.GetNamespaceResponse, nsUpdateRequest *workflowservice.UpdateNamespaceRequest) error {
|
|
if nsUpdateRequest.ReplicationConfig == nil ||
|
|
nsUpdateRequest.ReplicationConfig.State == enumspb.REPLICATION_STATE_UNSPECIFIED ||
|
|
nsUpdateRequest.ReplicationConfig.State == existingNamespace.Namespace.ReplicationConfig.State {
|
|
return nil // no change
|
|
}
|
|
|
|
if existingNamespace.Namespace.Info.State != enumspb.NAMESPACE_STATE_REGISTERED {
|
|
return serviceerror.NewInvalidArgumentf(
|
|
"update ReplicationState is only supported when namespace is in %s state, current state: %s",
|
|
enumspb.NAMESPACE_STATE_REGISTERED,
|
|
existingNamespace.Namespace.Info.State,
|
|
)
|
|
}
|
|
|
|
if nsUpdateRequest.ReplicationConfig.State == enumspb.REPLICATION_STATE_HANDOVER {
|
|
if !existingNamespace.IsGlobalNamespace {
|
|
return serviceerror.NewInvalidArgumentf(
|
|
"%s can only be set for global namespace",
|
|
enumspb.REPLICATION_STATE_HANDOVER,
|
|
)
|
|
}
|
|
// verify namespace has more than 1 replication clusters
|
|
replicationClusterCount := len(existingNamespace.Namespace.ReplicationConfig.Clusters)
|
|
if len(nsUpdateRequest.ReplicationConfig.Clusters) > 0 {
|
|
replicationClusterCount = len(nsUpdateRequest.ReplicationConfig.Clusters)
|
|
}
|
|
if replicationClusterCount < 2 {
|
|
return serviceerror.NewInvalidArgumentf("%s require more than one replication clusters", enumspb.REPLICATION_STATE_HANDOVER)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateStateUpdate(existingNamespace *persistence.GetNamespaceResponse, nsUpdateRequest *workflowservice.UpdateNamespaceRequest) error {
|
|
if nsUpdateRequest.UpdateInfo == nil {
|
|
return nil // no change
|
|
}
|
|
oldState := existingNamespace.Namespace.Info.State
|
|
newState := nsUpdateRequest.UpdateInfo.State
|
|
if newState == enumspb.NAMESPACE_STATE_UNSPECIFIED || oldState == newState {
|
|
return nil // no change
|
|
}
|
|
|
|
if existingNamespace.Namespace.ReplicationConfig != nil &&
|
|
existingNamespace.Namespace.ReplicationConfig.State == enumspb.REPLICATION_STATE_HANDOVER {
|
|
return serviceerror.NewInvalidArgument("cannot update namespace state while its replication state in REPLICATION_STATE_HANDOVER")
|
|
}
|
|
|
|
switch oldState {
|
|
case enumspb.NAMESPACE_STATE_REGISTERED:
|
|
switch newState {
|
|
case enumspb.NAMESPACE_STATE_DELETED, enumspb.NAMESPACE_STATE_DEPRECATED:
|
|
return nil
|
|
default:
|
|
return errInvalidNamespaceStateUpdate
|
|
}
|
|
|
|
case enumspb.NAMESPACE_STATE_DEPRECATED:
|
|
switch newState {
|
|
case enumspb.NAMESPACE_STATE_DELETED:
|
|
return nil
|
|
default:
|
|
return errInvalidNamespaceStateUpdate
|
|
}
|
|
|
|
case enumspb.NAMESPACE_STATE_DELETED:
|
|
return errInvalidNamespaceStateUpdate
|
|
default:
|
|
return errInvalidNamespaceStateUpdate
|
|
}
|
|
}
|