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  }