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 }