go.temporal.io/server@v1.23.0/common/cluster/metadata.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2021 Temporal Technologies Inc. All rights reservem. 4 // 5 // Copyright (c) 2021 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 metadata_mock.go 26 27 package cluster 28 29 import ( 30 "context" 31 "fmt" 32 "math" 33 "strconv" 34 "sync" 35 "sync/atomic" 36 "time" 37 38 "golang.org/x/exp/maps" 39 40 "go.temporal.io/server/common" 41 "go.temporal.io/server/common/collection" 42 "go.temporal.io/server/common/dynamicconfig" 43 "go.temporal.io/server/common/headers" 44 "go.temporal.io/server/common/log" 45 "go.temporal.io/server/common/log/tag" 46 "go.temporal.io/server/common/metrics" 47 "go.temporal.io/server/common/persistence" 48 "go.temporal.io/server/internal/goro" 49 ) 50 51 const ( 52 defaultClusterMetadataPageSize = 100 53 refreshInterval = time.Minute 54 55 unknownClusterNamePrefix = "unknown-cluster-" 56 ) 57 58 type ( 59 Metadata interface { 60 common.Pingable 61 62 // IsGlobalNamespaceEnabled whether the global namespace is enabled, 63 // this attr should be discarded when cross DC is made public 64 IsGlobalNamespaceEnabled() bool 65 // IsMasterCluster whether current cluster is master cluster 66 IsMasterCluster() bool 67 // GetClusterID return the cluster ID, which is also the initial failover version 68 GetClusterID() int64 69 // GetNextFailoverVersion return the next failover version for namespace failover 70 GetNextFailoverVersion(string, int64) int64 71 // IsVersionFromSameCluster return true if 2 version are used for the same cluster 72 IsVersionFromSameCluster(version1 int64, version2 int64) bool 73 // GetMasterClusterName return the master cluster name 74 GetMasterClusterName() string 75 // GetCurrentClusterName return the current cluster name 76 GetCurrentClusterName() string 77 // GetAllClusterInfo return the all cluster name -> corresponding info 78 GetAllClusterInfo() map[string]ClusterInformation 79 // ClusterNameForFailoverVersion return the corresponding cluster name for a given failover version 80 ClusterNameForFailoverVersion(isGlobalNamespace bool, failoverVersion int64) string 81 // GetFailoverVersionIncrement return the Failover version increment value 82 GetFailoverVersionIncrement() int64 83 RegisterMetadataChangeCallback(callbackId any, cb CallbackFn) 84 UnRegisterMetadataChangeCallback(callbackId any) 85 Start() 86 Stop() 87 } 88 89 CallbackFn func(oldClusterMetadata map[string]*ClusterInformation, newClusterMetadata map[string]*ClusterInformation) 90 91 // Config contains the all cluster which participated in cross DC 92 Config struct { 93 EnableGlobalNamespace bool `yaml:"enableGlobalNamespace"` 94 // FailoverVersionIncrement is the increment of each cluster version when failover happens 95 FailoverVersionIncrement int64 `yaml:"failoverVersionIncrement"` 96 // MasterClusterName is the master cluster name, only the master cluster can register / update namespace 97 // all clusters can do namespace failover 98 MasterClusterName string `yaml:"masterClusterName"` 99 // CurrentClusterName is the name of the current cluster 100 CurrentClusterName string `yaml:"currentClusterName"` 101 // ClusterInformation contains all cluster names to corresponding information about that cluster 102 ClusterInformation map[string]ClusterInformation `yaml:"clusterInformation"` 103 // Tag contains customized tag about the current cluster 104 Tags map[string]string `yaml:"tags"` 105 } 106 107 // ClusterInformation contains the information about each cluster which participated in cross DC 108 ClusterInformation struct { 109 Enabled bool `yaml:"enabled"` 110 InitialFailoverVersion int64 `yaml:"initialFailoverVersion"` 111 // Address indicate the remote service address(Host:Port). Host can be DNS name. 112 RPCAddress string `yaml:"rpcAddress"` 113 // Cluster ID allows to explicitly set the ID of the cluster. Optional. 114 ClusterID string `yaml:"-"` 115 ShardCount int32 `yaml:"-"` // Ignore this field when loading config. 116 Tags map[string]string `yaml:"-"` // Ignore this field. Use cluster.Config.Tags for customized tags. 117 // private field to track cluster information updates 118 version int64 119 } 120 121 metadataImpl struct { 122 status int32 123 clusterMetadataStore persistence.ClusterMetadataManager 124 refresher *goro.Handle 125 refreshDuration dynamicconfig.DurationPropertyFn 126 logger log.Logger 127 128 // Immutable fields 129 130 // EnableGlobalNamespace whether the global namespace is enabled, 131 enableGlobalNamespace bool 132 // all clusters can do namespace failover 133 masterClusterName string 134 // currentClusterName is the name of the current cluster 135 currentClusterName string 136 // failoverVersionIncrement is the increment of each cluster's version when failover happen 137 failoverVersionIncrement int64 138 139 // Mutable fields 140 141 clusterLock sync.RWMutex 142 // clusterInfo contains all cluster name -> corresponding information 143 clusterInfo map[string]ClusterInformation 144 // versionToClusterName contains all initial version -> corresponding cluster name 145 versionToClusterName map[int64]string 146 147 clusterCallbackLock sync.RWMutex 148 clusterChangeCallback map[any]CallbackFn 149 } 150 ) 151 152 func NewMetadata( 153 enableGlobalNamespace bool, 154 failoverVersionIncrement int64, 155 masterClusterName string, 156 currentClusterName string, 157 clusterInfo map[string]ClusterInformation, 158 clusterMetadataStore persistence.ClusterMetadataManager, 159 refreshDuration dynamicconfig.DurationPropertyFn, 160 logger log.Logger, 161 ) Metadata { 162 if len(clusterInfo) == 0 { 163 panic("Empty cluster information") 164 } else if len(masterClusterName) == 0 { 165 panic("Master cluster name is empty") 166 } else if len(currentClusterName) == 0 { 167 panic("Current cluster name is empty") 168 } else if failoverVersionIncrement == 0 || failoverVersionIncrement > math.MaxInt32 { 169 panic("Version increment <= 0 or > 2147483647") 170 } 171 172 versionToClusterName := updateVersionToClusterName(clusterInfo, failoverVersionIncrement) 173 if _, ok := clusterInfo[currentClusterName]; !ok { 174 panic("Current cluster is not specified in cluster info") 175 } 176 if _, ok := clusterInfo[masterClusterName]; !ok { 177 panic("Master cluster is not specified in cluster info") 178 } 179 if len(versionToClusterName) != len(clusterInfo) { 180 panic("Cluster info initial versions have duplicates") 181 } 182 183 copyClusterInfo := make(map[string]ClusterInformation) 184 for k, v := range clusterInfo { 185 copyClusterInfo[k] = v 186 } 187 if refreshDuration == nil { 188 refreshDuration = dynamicconfig.GetDurationPropertyFn(refreshInterval) 189 } 190 return &metadataImpl{ 191 status: common.DaemonStatusInitialized, 192 enableGlobalNamespace: enableGlobalNamespace, 193 failoverVersionIncrement: failoverVersionIncrement, 194 masterClusterName: masterClusterName, 195 currentClusterName: currentClusterName, 196 clusterInfo: copyClusterInfo, 197 versionToClusterName: versionToClusterName, 198 clusterChangeCallback: make(map[any]CallbackFn), 199 clusterMetadataStore: clusterMetadataStore, 200 logger: logger, 201 refreshDuration: refreshDuration, 202 } 203 } 204 205 func NewMetadataFromConfig( 206 config *Config, 207 clusterMetadataStore persistence.ClusterMetadataManager, 208 dynamicCollection *dynamicconfig.Collection, 209 logger log.Logger, 210 ) Metadata { 211 return NewMetadata( 212 config.EnableGlobalNamespace, 213 config.FailoverVersionIncrement, 214 config.MasterClusterName, 215 config.CurrentClusterName, 216 config.ClusterInformation, 217 clusterMetadataStore, 218 dynamicCollection.GetDurationProperty(dynamicconfig.ClusterMetadataRefreshInterval, refreshInterval), 219 logger, 220 ) 221 } 222 223 func NewMetadataForTest( 224 config *Config, 225 ) Metadata { 226 return NewMetadata( 227 config.EnableGlobalNamespace, 228 config.FailoverVersionIncrement, 229 config.MasterClusterName, 230 config.CurrentClusterName, 231 config.ClusterInformation, 232 nil, 233 nil, 234 log.NewNoopLogger(), 235 ) 236 } 237 238 func (m *metadataImpl) Start() { 239 if !atomic.CompareAndSwapInt32(&m.status, common.DaemonStatusInitialized, common.DaemonStatusStarted) { 240 return 241 } 242 243 // TODO: specify a timeout for the context 244 ctx := headers.SetCallerInfo( 245 context.TODO(), 246 headers.SystemBackgroundCallerInfo, 247 ) 248 err := m.refreshClusterMetadata(ctx) 249 if err != nil { 250 m.logger.Fatal("Unable to initialize cluster metadata cache", tag.Error(err)) 251 } 252 m.refresher = goro.NewHandle(ctx).Go(m.refreshLoop) 253 } 254 255 func (m *metadataImpl) Stop() { 256 if !atomic.CompareAndSwapInt32(&m.status, common.DaemonStatusStarted, common.DaemonStatusStopped) { 257 return 258 } 259 260 m.refresher.Cancel() 261 <-m.refresher.Done() 262 } 263 264 func (m *metadataImpl) GetPingChecks() []common.PingCheck { 265 return []common.PingCheck{ 266 { 267 Name: "cluster metadata lock", 268 // we don't do any persistence ops under clusterLock, use a short timeout 269 Timeout: 10 * time.Second, 270 Ping: func() []common.Pingable { 271 m.clusterLock.Lock() 272 //lint:ignore SA2001 just checking if we can acquire the lock 273 m.clusterLock.Unlock() 274 return nil 275 }, 276 MetricsName: metrics.DDClusterMetadataLockLatency.Name(), 277 }, 278 { 279 Name: "cluster metadata callback lock", 280 // listeners get called under clusterCallbackLock, they may do some more work, but 281 // not persistence ops. 282 Timeout: 10 * time.Second, 283 Ping: func() []common.Pingable { 284 m.clusterCallbackLock.Lock() 285 //lint:ignore SA2001 just checking if we can acquire the lock 286 m.clusterCallbackLock.Unlock() 287 return nil 288 }, 289 MetricsName: metrics.DDClusterMetadataCallbackLockLatency.Name(), 290 }, 291 } 292 } 293 294 func (m *metadataImpl) IsGlobalNamespaceEnabled() bool { 295 return m.enableGlobalNamespace 296 } 297 298 func (m *metadataImpl) IsMasterCluster() bool { 299 return m.masterClusterName == m.currentClusterName 300 } 301 302 func (m *metadataImpl) GetClusterID() int64 { 303 info, ok := m.clusterInfo[m.currentClusterName] 304 if !ok { 305 panic(fmt.Sprintf( 306 "Unknown cluster name: %v with given cluster initial failover version map: %v.", 307 m.currentClusterName, 308 m.clusterInfo, 309 )) 310 } 311 return info.InitialFailoverVersion 312 } 313 314 func (m *metadataImpl) GetNextFailoverVersion(clusterName string, currentFailoverVersion int64) int64 { 315 m.clusterLock.RLock() 316 defer m.clusterLock.RUnlock() 317 318 info, ok := m.clusterInfo[clusterName] 319 if !ok { 320 panic(fmt.Sprintf( 321 "Unknown cluster name: %v with given cluster initial failover version map: %v.", 322 clusterName, 323 m.clusterInfo, 324 )) 325 } 326 failoverVersion := currentFailoverVersion/m.failoverVersionIncrement*m.failoverVersionIncrement + info.InitialFailoverVersion 327 if failoverVersion < currentFailoverVersion { 328 return failoverVersion + m.failoverVersionIncrement 329 } 330 return failoverVersion 331 } 332 333 func (m *metadataImpl) IsVersionFromSameCluster(version1 int64, version2 int64) bool { 334 return (version1-version2)%m.failoverVersionIncrement == 0 335 } 336 337 func (m *metadataImpl) GetMasterClusterName() string { 338 return m.masterClusterName 339 } 340 341 func (m *metadataImpl) GetCurrentClusterName() string { 342 return m.currentClusterName 343 } 344 345 func (m *metadataImpl) GetAllClusterInfo() map[string]ClusterInformation { 346 m.clusterLock.RLock() 347 defer m.clusterLock.RUnlock() 348 349 result := make(map[string]ClusterInformation, len(m.clusterInfo)) 350 for k, v := range m.clusterInfo { 351 result[k] = v 352 } 353 return result 354 } 355 356 func (m *metadataImpl) ClusterNameForFailoverVersion(isGlobalNamespace bool, failoverVersion int64) string { 357 if failoverVersion == common.EmptyVersion { 358 // Local namespace uses EmptyVersion. But local namespace could be promoted to global namespace. Once promoted, 359 // workflows with EmptyVersion could be replicated to other clusters. The receiving cluster needs to know that 360 // those workflows are not from their current cluster. 361 if isGlobalNamespace { 362 return unknownClusterNamePrefix + strconv.Itoa(int(failoverVersion)) 363 } 364 return m.currentClusterName 365 } 366 367 if !isGlobalNamespace { 368 panic(fmt.Sprintf( 369 "ClusterMetadata encountered local namesapce with failover version %v", 370 failoverVersion, 371 )) 372 } 373 374 initialFailoverVersion := failoverVersion % m.failoverVersionIncrement 375 // Failover version starts with 1. Zero is an invalid value for failover version 376 if initialFailoverVersion == common.EmptyVersion { 377 initialFailoverVersion = m.failoverVersionIncrement 378 } 379 380 m.clusterLock.RLock() 381 defer m.clusterLock.RUnlock() 382 clusterName, ok := m.versionToClusterName[initialFailoverVersion] 383 if !ok { 384 m.logger.Warn(fmt.Sprintf( 385 "Unknown initial failover version %v with given cluster initial failover version map: %v and failover version increment %v.", 386 initialFailoverVersion, 387 m.clusterInfo, 388 m.failoverVersionIncrement, 389 )) 390 return unknownClusterNamePrefix + strconv.Itoa(int(initialFailoverVersion)) 391 } 392 return clusterName 393 } 394 395 func (m *metadataImpl) GetFailoverVersionIncrement() int64 { 396 return m.failoverVersionIncrement 397 } 398 399 func (m *metadataImpl) RegisterMetadataChangeCallback(callbackId any, cb CallbackFn) { 400 m.clusterCallbackLock.Lock() 401 m.clusterChangeCallback[callbackId] = cb 402 m.clusterCallbackLock.Unlock() 403 404 oldEntries := make(map[string]*ClusterInformation) 405 newEntries := make(map[string]*ClusterInformation) 406 m.clusterLock.RLock() 407 for clusterName, clusterInfo := range m.clusterInfo { 408 oldEntries[clusterName] = nil 409 newEntries[clusterName] = &ClusterInformation{ 410 Enabled: clusterInfo.Enabled, 411 InitialFailoverVersion: clusterInfo.InitialFailoverVersion, 412 RPCAddress: clusterInfo.RPCAddress, 413 ShardCount: clusterInfo.ShardCount, 414 version: clusterInfo.version, 415 } 416 } 417 m.clusterLock.RUnlock() 418 cb(oldEntries, newEntries) 419 } 420 421 func (m *metadataImpl) UnRegisterMetadataChangeCallback(callbackId any) { 422 m.clusterCallbackLock.Lock() 423 delete(m.clusterChangeCallback, callbackId) 424 m.clusterCallbackLock.Unlock() 425 } 426 427 func (m *metadataImpl) refreshLoop(ctx context.Context) error { 428 timer := time.NewTicker(m.refreshDuration()) 429 defer timer.Stop() 430 431 for { 432 select { 433 case <-ctx.Done(): 434 return nil 435 case <-timer.C: 436 for err := m.refreshClusterMetadata(ctx); err != nil; err = m.refreshClusterMetadata(ctx) { 437 m.logger.Error("Error refreshing remote cluster metadata", tag.Error(err)) 438 refreshTimer := time.NewTimer(m.refreshDuration() / 2) 439 440 select { 441 case <-refreshTimer.C: 442 case <-ctx.Done(): 443 refreshTimer.Stop() 444 return nil 445 } 446 } 447 } 448 } 449 } 450 451 func (m *metadataImpl) refreshClusterMetadata(ctx context.Context) error { 452 clusterMetadataMap, err := m.listAllClusterMetadataFromDB(ctx) 453 if err != nil { 454 return err 455 } 456 457 oldEntries := make(map[string]*ClusterInformation) 458 newEntries := make(map[string]*ClusterInformation) 459 460 clusterInfoMap := m.GetAllClusterInfo() 461 for clusterName, newClusterInfo := range clusterMetadataMap { 462 oldClusterInfo, ok := clusterInfoMap[clusterName] 463 if !ok { 464 // handle new cluster registry 465 oldEntries[clusterName] = nil 466 newEntries[clusterName] = &ClusterInformation{ 467 Enabled: newClusterInfo.Enabled, 468 InitialFailoverVersion: newClusterInfo.InitialFailoverVersion, 469 RPCAddress: newClusterInfo.RPCAddress, 470 ShardCount: newClusterInfo.ShardCount, 471 Tags: newClusterInfo.Tags, 472 version: newClusterInfo.version, 473 } 474 } else if newClusterInfo.version > oldClusterInfo.version { 475 if newClusterInfo.Enabled == oldClusterInfo.Enabled && 476 newClusterInfo.RPCAddress == oldClusterInfo.RPCAddress && 477 newClusterInfo.InitialFailoverVersion == oldClusterInfo.InitialFailoverVersion && 478 maps.Equal(newClusterInfo.Tags, oldClusterInfo.Tags) { 479 // key cluster info does not change 480 continue 481 } 482 // handle updated cluster registry 483 oldEntries[clusterName] = &ClusterInformation{ 484 Enabled: oldClusterInfo.Enabled, 485 InitialFailoverVersion: oldClusterInfo.InitialFailoverVersion, 486 RPCAddress: oldClusterInfo.RPCAddress, 487 ShardCount: oldClusterInfo.ShardCount, 488 Tags: oldClusterInfo.Tags, 489 version: oldClusterInfo.version, 490 } 491 newEntries[clusterName] = &ClusterInformation{ 492 Enabled: newClusterInfo.Enabled, 493 InitialFailoverVersion: newClusterInfo.InitialFailoverVersion, 494 RPCAddress: newClusterInfo.RPCAddress, 495 ShardCount: newClusterInfo.ShardCount, 496 Tags: newClusterInfo.Tags, 497 version: newClusterInfo.version, 498 } 499 } 500 } 501 for clusterName, oldClusterInfo := range clusterInfoMap { 502 if _, ok := clusterMetadataMap[clusterName]; !ok { 503 // removed cluster registry 504 oldEntries[clusterName] = &oldClusterInfo 505 newEntries[clusterName] = nil 506 } 507 } 508 509 if len(oldEntries) > 0 { 510 m.clusterLock.Lock() 511 m.updateClusterInfoLocked(oldEntries, newEntries) 512 m.updateFailoverVersionToClusterName() 513 m.clusterLock.Unlock() 514 515 m.clusterCallbackLock.RLock() 516 defer m.clusterCallbackLock.RUnlock() 517 for _, cb := range m.clusterChangeCallback { 518 cb(oldEntries, newEntries) 519 } 520 } 521 return nil 522 } 523 524 func (m *metadataImpl) updateClusterInfoLocked( 525 oldClusterMetadata map[string]*ClusterInformation, 526 newClusterMetadata map[string]*ClusterInformation, 527 ) { 528 for clusterName := range oldClusterMetadata { 529 if oldClusterMetadata[clusterName] != nil && newClusterMetadata[clusterName] == nil { 530 delete(m.clusterInfo, clusterName) 531 } else { 532 m.clusterInfo[clusterName] = *newClusterMetadata[clusterName] 533 } 534 } 535 } 536 537 func (m *metadataImpl) updateFailoverVersionToClusterName() { 538 m.versionToClusterName = updateVersionToClusterName(m.clusterInfo, m.failoverVersionIncrement) 539 } 540 541 func updateVersionToClusterName(clusterInfo map[string]ClusterInformation, failoverVersionIncrement int64) map[int64]string { 542 versionToClusterName := make(map[int64]string) 543 for clusterName, info := range clusterInfo { 544 if failoverVersionIncrement <= info.InitialFailoverVersion || info.InitialFailoverVersion <= 0 { 545 panic(fmt.Sprintf( 546 "Version increment %v is smaller than initial version: %v.", 547 failoverVersionIncrement, 548 clusterInfo, 549 )) 550 } 551 if len(clusterName) == 0 { 552 panic("Cluster name needs to be defined in Cluster Information") 553 } 554 versionToClusterName[info.InitialFailoverVersion] = clusterName 555 556 if info.Enabled && info.RPCAddress == "" { 557 panic(fmt.Sprintf("Cluster %v: RPCAddress is empty", clusterName)) 558 } 559 } 560 return versionToClusterName 561 } 562 563 func (m *metadataImpl) listAllClusterMetadataFromDB( 564 ctx context.Context, 565 ) (map[string]*ClusterInformation, error) { 566 result := make(map[string]*ClusterInformation) 567 if m.clusterMetadataStore == nil { 568 return result, nil 569 } 570 571 paginationFn := func(paginationToken []byte) ([]interface{}, []byte, error) { 572 resp, err := m.clusterMetadataStore.ListClusterMetadata( 573 ctx, 574 &persistence.ListClusterMetadataRequest{ 575 PageSize: defaultClusterMetadataPageSize, 576 NextPageToken: paginationToken, 577 }, 578 ) 579 if err != nil { 580 return nil, nil, err 581 } 582 var paginateItems []interface{} 583 for _, clusterInfo := range resp.ClusterMetadata { 584 paginateItems = append(paginateItems, clusterInfo) 585 } 586 return paginateItems, resp.NextPageToken, nil 587 } 588 589 iterator := collection.NewPagingIterator(paginationFn) 590 for iterator.HasNext() { 591 item, err := iterator.Next() 592 if err != nil { 593 return nil, err 594 } 595 getClusterResp := item.(*persistence.GetClusterMetadataResponse) 596 result[getClusterResp.GetClusterName()] = &ClusterInformation{ 597 Enabled: getClusterResp.GetIsConnectionEnabled(), 598 InitialFailoverVersion: getClusterResp.GetInitialFailoverVersion(), 599 RPCAddress: getClusterResp.GetClusterAddress(), 600 ShardCount: getClusterResp.GetHistoryShardCount(), 601 Tags: getClusterResp.GetTags(), 602 version: getClusterResp.Version, 603 } 604 } 605 return result, nil 606 }