go.temporal.io/server@v1.23.0/common/namespace/replication_task_executor.go (about)

     1  // The MIT License
     2  //
     3  // Copyright (c) 2020 Temporal Technologies Inc.  All rights reserved.
     4  //
     5  // Copyright (c) 2020 Uber Technologies, Inc.
     6  //
     7  // Permission is hereby granted, free of charge, to any person obtaining a copy
     8  // of this software and associated documentation files (the "Software"), to deal
     9  // in the Software without restriction, including without limitation the rights
    10  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    11  // copies of the Software, and to permit persons to whom the Software is
    12  // furnished to do so, subject to the following conditions:
    13  //
    14  // The above copyright notice and this permission notice shall be included in
    15  // all copies or substantial portions of the Software.
    16  //
    17  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    18  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    19  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    20  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    21  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    22  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    23  // THE SOFTWARE.
    24  
    25  //go:generate mockgen -copyright_file ../../LICENSE -package $GOPACKAGE -source $GOFILE -destination replication_task_handler_mock.go
    26  
    27  package namespace
    28  
    29  import (
    30  	"context"
    31  
    32  	enumspb "go.temporal.io/api/enums/v1"
    33  	replicationpb "go.temporal.io/api/replication/v1"
    34  	"go.temporal.io/api/serviceerror"
    35  
    36  	enumsspb "go.temporal.io/server/api/enums/v1"
    37  	persistencespb "go.temporal.io/server/api/persistence/v1"
    38  	replicationspb "go.temporal.io/server/api/replication/v1"
    39  	"go.temporal.io/server/common/log"
    40  	"go.temporal.io/server/common/persistence"
    41  )
    42  
    43  var (
    44  	// ErrEmptyNamespaceReplicationTask is the error to indicate empty replication task
    45  	ErrEmptyNamespaceReplicationTask = serviceerror.NewInvalidArgument("empty namespace replication task")
    46  	// ErrInvalidNamespaceOperation is the error to indicate empty namespace operation attribute
    47  	ErrInvalidNamespaceOperation = serviceerror.NewInvalidArgument("invalid namespace operation attribute")
    48  	// ErrInvalidNamespaceID is the error to indicate empty rID attribute
    49  	ErrInvalidNamespaceID = serviceerror.NewInvalidArgument("invalid namespace ID attribute")
    50  	// ErrInvalidNamespaceInfo is the error to indicate empty info attribute
    51  	ErrInvalidNamespaceInfo = serviceerror.NewInvalidArgument("invalid namespace info attribute")
    52  	// ErrInvalidNamespaceConfig is the error to indicate empty config attribute
    53  	ErrInvalidNamespaceConfig = serviceerror.NewInvalidArgument("invalid namespace config attribute")
    54  	// ErrInvalidNamespaceReplicationConfig is the error to indicate empty replication config attribute
    55  	ErrInvalidNamespaceReplicationConfig = serviceerror.NewInvalidArgument("invalid namespace replication config attribute")
    56  	// ErrInvalidNamespaceConfigVersion is the error to indicate empty config version attribute
    57  	ErrInvalidNamespaceConfigVersion = serviceerror.NewInvalidArgument("invalid namespace config version attribute")
    58  	// ErrInvalidNamespaceFailoverVersion is the error to indicate empty failover version attribute
    59  	ErrInvalidNamespaceFailoverVersion = serviceerror.NewInvalidArgument("invalid namespace failover version attribute")
    60  	// ErrInvalidNamespaceState is the error to indicate invalid namespace state
    61  	ErrInvalidNamespaceState = serviceerror.NewInvalidArgument("invalid namespace state attribute")
    62  	// ErrNameUUIDCollision is the error to indicate namespace name / UUID collision
    63  	ErrNameUUIDCollision = serviceerror.NewInvalidArgument("namespace replication encountered name / UUID collision")
    64  )
    65  
    66  // NOTE: the counterpart of namespace replication transmission logic is in service/fropntend package
    67  
    68  type (
    69  	// ReplicationTaskExecutor is the interface which is to execute namespace replication task
    70  	ReplicationTaskExecutor interface {
    71  		Execute(ctx context.Context, task *replicationspb.NamespaceTaskAttributes) error
    72  	}
    73  
    74  	namespaceReplicationTaskExecutorImpl struct {
    75  		currentCluster  string
    76  		metadataManager persistence.MetadataManager
    77  		logger          log.Logger
    78  	}
    79  )
    80  
    81  // NewReplicationTaskExecutor create a new instance of namespace replicator
    82  func NewReplicationTaskExecutor(
    83  	currentCluster string,
    84  	metadataManagerV2 persistence.MetadataManager,
    85  	logger log.Logger,
    86  ) ReplicationTaskExecutor {
    87  
    88  	return &namespaceReplicationTaskExecutorImpl{
    89  		currentCluster:  currentCluster,
    90  		metadataManager: metadataManagerV2,
    91  		logger:          logger,
    92  	}
    93  }
    94  
    95  // Execute handles receiving of the namespace replication task
    96  func (h *namespaceReplicationTaskExecutorImpl) Execute(
    97  	ctx context.Context,
    98  	task *replicationspb.NamespaceTaskAttributes,
    99  ) error {
   100  	if err := h.validateNamespaceReplicationTask(task); err != nil {
   101  		return err
   102  	}
   103  	if shouldProcess, err := h.shouldProcessTask(ctx, task); !shouldProcess || err != nil {
   104  		return err
   105  	}
   106  
   107  	switch task.GetNamespaceOperation() {
   108  	case enumsspb.NAMESPACE_OPERATION_CREATE:
   109  		return h.handleNamespaceCreationReplicationTask(ctx, task)
   110  	case enumsspb.NAMESPACE_OPERATION_UPDATE:
   111  		return h.handleNamespaceUpdateReplicationTask(ctx, task)
   112  	default:
   113  		return ErrInvalidNamespaceOperation
   114  	}
   115  }
   116  
   117  func checkClusterIncludedInReplicationConfig(clusterName string, repCfg []*replicationpb.ClusterReplicationConfig) bool {
   118  	for _, cluster := range repCfg {
   119  		if clusterName == cluster.ClusterName {
   120  			return true
   121  		}
   122  	}
   123  	return false
   124  }
   125  
   126  func (h *namespaceReplicationTaskExecutorImpl) shouldProcessTask(ctx context.Context, task *replicationspb.NamespaceTaskAttributes) (bool, error) {
   127  	resp, err := h.metadataManager.GetNamespace(ctx, &persistence.GetNamespaceRequest{
   128  		Name: task.Info.GetName(),
   129  	})
   130  	switch err.(type) {
   131  	case nil:
   132  		if resp.Namespace.Info.Id != task.GetId() {
   133  			return false, ErrNameUUIDCollision
   134  		}
   135  
   136  		return true, nil
   137  	case *serviceerror.NamespaceNotFound:
   138  		return checkClusterIncludedInReplicationConfig(h.currentCluster, task.ReplicationConfig.Clusters), nil
   139  	default:
   140  		// return the original err
   141  		return false, err
   142  	}
   143  }
   144  
   145  // handleNamespaceCreationReplicationTask handles the namespace creation replication task
   146  func (h *namespaceReplicationTaskExecutorImpl) handleNamespaceCreationReplicationTask(
   147  	ctx context.Context,
   148  	task *replicationspb.NamespaceTaskAttributes,
   149  ) error {
   150  	// task already validated
   151  	err := h.validateNamespaceStatus(task.Info.State)
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	request := &persistence.CreateNamespaceRequest{
   157  		Namespace: &persistencespb.NamespaceDetail{
   158  			Info: &persistencespb.NamespaceInfo{
   159  				Id:          task.GetId(),
   160  				Name:        task.Info.GetName(),
   161  				State:       task.Info.GetState(),
   162  				Description: task.Info.GetDescription(),
   163  				Owner:       task.Info.GetOwnerEmail(),
   164  				Data:        task.Info.Data,
   165  			},
   166  			Config: &persistencespb.NamespaceConfig{
   167  				Retention:                    task.Config.GetWorkflowExecutionRetentionTtl(),
   168  				HistoryArchivalState:         task.Config.GetHistoryArchivalState(),
   169  				HistoryArchivalUri:           task.Config.GetHistoryArchivalUri(),
   170  				VisibilityArchivalState:      task.Config.GetVisibilityArchivalState(),
   171  				VisibilityArchivalUri:        task.Config.GetVisibilityArchivalUri(),
   172  				CustomSearchAttributeAliases: task.Config.GetCustomSearchAttributeAliases(),
   173  			},
   174  			ReplicationConfig: &persistencespb.NamespaceReplicationConfig{
   175  				ActiveClusterName: task.ReplicationConfig.GetActiveClusterName(),
   176  				Clusters:          ConvertClusterReplicationConfigFromProto(task.ReplicationConfig.Clusters),
   177  			},
   178  			ConfigVersion:   task.GetConfigVersion(),
   179  			FailoverVersion: task.GetFailoverVersion(),
   180  		},
   181  		IsGlobalNamespace: true, // local namespace will not be replicated
   182  	}
   183  
   184  	_, err = h.metadataManager.CreateNamespace(ctx, request)
   185  	if err != nil {
   186  		// SQL and Cassandra handle namespace UUID collision differently
   187  		// here, whenever seeing a error replicating a namespace
   188  		// do a check if there is a name / UUID collision
   189  
   190  		recordExists := true
   191  		resp, getErr := h.metadataManager.GetNamespace(ctx, &persistence.GetNamespaceRequest{
   192  			Name: task.Info.GetName(),
   193  		})
   194  		switch getErr.(type) {
   195  		case nil:
   196  			if resp.Namespace.Info.Id != task.GetId() {
   197  				return ErrNameUUIDCollision
   198  			}
   199  		case *serviceerror.NamespaceNotFound:
   200  			// no check is necessary
   201  			recordExists = false
   202  		default:
   203  			// return the original err
   204  			return err
   205  		}
   206  
   207  		resp, getErr = h.metadataManager.GetNamespace(ctx, &persistence.GetNamespaceRequest{
   208  			ID: task.GetId(),
   209  		})
   210  		switch getErr.(type) {
   211  		case nil:
   212  			if resp.Namespace.Info.Name != task.Info.GetName() {
   213  				return ErrNameUUIDCollision
   214  			}
   215  		case *serviceerror.NamespaceNotFound:
   216  			// no check is necessary
   217  			recordExists = false
   218  		default:
   219  			// return the original err
   220  			return err
   221  		}
   222  
   223  		if recordExists {
   224  			// name -> id & id -> name check pass, this is duplication request
   225  			return nil
   226  		}
   227  		return err
   228  	}
   229  
   230  	return err
   231  }
   232  
   233  // handleNamespaceUpdateReplicationTask handles the namespace update replication task
   234  func (h *namespaceReplicationTaskExecutorImpl) handleNamespaceUpdateReplicationTask(
   235  	ctx context.Context,
   236  	task *replicationspb.NamespaceTaskAttributes,
   237  ) error {
   238  	// task already validated
   239  	err := h.validateNamespaceStatus(task.Info.State)
   240  	if err != nil {
   241  		return err
   242  	}
   243  
   244  	// first we need to get the current notification version since we need to it for conditional update
   245  	metadata, err := h.metadataManager.GetMetadata(ctx)
   246  	if err != nil {
   247  		return err
   248  	}
   249  	notificationVersion := metadata.NotificationVersion
   250  
   251  	// plus, we need to check whether the config version is <= the config version set in the input
   252  	// plus, we need to check whether the failover version is <= the failover version set in the input
   253  	resp, err := h.metadataManager.GetNamespace(ctx, &persistence.GetNamespaceRequest{
   254  		Name: task.Info.GetName(),
   255  	})
   256  	if err != nil {
   257  		if _, isNotFound := err.(*serviceerror.NamespaceNotFound); isNotFound {
   258  			// this can happen if the create namespace replication task is to processed.
   259  			// e.g. new cluster which does not have anything
   260  			return h.handleNamespaceCreationReplicationTask(ctx, task)
   261  		}
   262  		return err
   263  	}
   264  
   265  	recordUpdated := false
   266  	request := &persistence.UpdateNamespaceRequest{
   267  		Namespace:           resp.Namespace,
   268  		NotificationVersion: notificationVersion,
   269  		IsGlobalNamespace:   resp.IsGlobalNamespace,
   270  	}
   271  
   272  	if resp.Namespace.ConfigVersion < task.GetConfigVersion() {
   273  		recordUpdated = true
   274  		request.Namespace.Info = &persistencespb.NamespaceInfo{
   275  			Id:          task.GetId(),
   276  			Name:        task.Info.GetName(),
   277  			State:       task.Info.GetState(),
   278  			Description: task.Info.GetDescription(),
   279  			Owner:       task.Info.GetOwnerEmail(),
   280  			Data:        task.Info.Data,
   281  		}
   282  		request.Namespace.Config = &persistencespb.NamespaceConfig{
   283  			Retention:                    task.Config.GetWorkflowExecutionRetentionTtl(),
   284  			HistoryArchivalState:         task.Config.GetHistoryArchivalState(),
   285  			HistoryArchivalUri:           task.Config.GetHistoryArchivalUri(),
   286  			VisibilityArchivalState:      task.Config.GetVisibilityArchivalState(),
   287  			VisibilityArchivalUri:        task.Config.GetVisibilityArchivalUri(),
   288  			CustomSearchAttributeAliases: task.Config.GetCustomSearchAttributeAliases(),
   289  		}
   290  		if task.Config.GetBadBinaries() != nil {
   291  			request.Namespace.Config.BadBinaries = task.Config.GetBadBinaries()
   292  		}
   293  		request.Namespace.ReplicationConfig.Clusters = ConvertClusterReplicationConfigFromProto(task.ReplicationConfig.Clusters)
   294  		request.Namespace.ConfigVersion = task.GetConfigVersion()
   295  	}
   296  	if resp.Namespace.FailoverVersion < task.GetFailoverVersion() {
   297  		recordUpdated = true
   298  		request.Namespace.ReplicationConfig.ActiveClusterName = task.ReplicationConfig.GetActiveClusterName()
   299  		request.Namespace.FailoverVersion = task.GetFailoverVersion()
   300  		request.Namespace.FailoverNotificationVersion = notificationVersion
   301  		request.Namespace.ReplicationConfig.FailoverHistory = convertFailoverHistoryToPersistenceProto(task.GetFailoverHistory())
   302  	}
   303  
   304  	if !recordUpdated {
   305  		return nil
   306  	}
   307  
   308  	return h.metadataManager.UpdateNamespace(ctx, request)
   309  }
   310  
   311  func (h *namespaceReplicationTaskExecutorImpl) validateNamespaceReplicationTask(task *replicationspb.NamespaceTaskAttributes) error {
   312  	if task == nil {
   313  		return ErrEmptyNamespaceReplicationTask
   314  	}
   315  
   316  	if task.Id == "" {
   317  		return ErrInvalidNamespaceID
   318  	} else if task.Info == nil {
   319  		return ErrInvalidNamespaceInfo
   320  	} else if task.Config == nil {
   321  		return ErrInvalidNamespaceConfig
   322  	} else if task.ReplicationConfig == nil {
   323  		return ErrInvalidNamespaceReplicationConfig
   324  	}
   325  	return nil
   326  }
   327  
   328  func ConvertClusterReplicationConfigFromProto(
   329  	input []*replicationpb.ClusterReplicationConfig,
   330  ) []string {
   331  	var output []string
   332  	for _, cluster := range input {
   333  		clusterName := cluster.GetClusterName()
   334  		output = append(output, clusterName)
   335  	}
   336  	return output
   337  }
   338  
   339  func convertFailoverHistoryToPersistenceProto(failoverHistory []*replicationpb.FailoverStatus) []*persistencespb.FailoverStatus {
   340  	var persistencePb []*persistencespb.FailoverStatus
   341  	for _, status := range failoverHistory {
   342  		persistencePb = append(persistencePb, &persistencespb.FailoverStatus{
   343  			FailoverTime:    status.GetFailoverTime(),
   344  			FailoverVersion: status.GetFailoverVersion(),
   345  		})
   346  	}
   347  	return persistencePb
   348  }
   349  
   350  func (h *namespaceReplicationTaskExecutorImpl) validateNamespaceStatus(input enumspb.NamespaceState) error {
   351  	switch input {
   352  	case enumspb.NAMESPACE_STATE_REGISTERED, enumspb.NAMESPACE_STATE_DEPRECATED:
   353  		return nil
   354  	default:
   355  		return ErrInvalidNamespaceState
   356  	}
   357  }