go.temporal.io/server@v1.23.0/common/membership/ringpop/test_cluster.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  package ringpop
    26  
    27  import (
    28  	"context"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/golang/mock/gomock"
    33  	"github.com/pborman/uuid"
    34  	"github.com/temporalio/ringpop-go"
    35  	"github.com/temporalio/tchannel-go"
    36  
    37  	"go.temporal.io/server/common/config"
    38  	"go.temporal.io/server/common/log"
    39  	"go.temporal.io/server/common/log/tag"
    40  	"go.temporal.io/server/common/membership"
    41  	"go.temporal.io/server/common/persistence"
    42  	"go.temporal.io/server/common/primitives"
    43  )
    44  
    45  // testCluster is a type that represents a test ringpop cluster
    46  type testCluster struct {
    47  	hostUUIDs    []string
    48  	hostAddrs    []string
    49  	hostInfoList []membership.HostInfo
    50  	rings        []*monitor
    51  	channels     []*tchannel.Channel
    52  	seedNode     string
    53  }
    54  
    55  // newTestCluster creates a new test cluster with the given name and cluster size
    56  // All the nodes in the test cluster will register themselves in Ringpop
    57  // with the specified name. This is only intended for unit tests.
    58  func newTestCluster(
    59  	t *testing.T,
    60  	ringPopApp string,
    61  	size int,
    62  	listenIPAddr string,
    63  	seed string,
    64  	serviceName primitives.ServiceName,
    65  	broadcastAddress string,
    66  ) *testCluster {
    67  	logger := log.NewTestLogger()
    68  	ctrl := gomock.NewController(t)
    69  	defer ctrl.Finish()
    70  
    71  	mockMgr := persistence.NewMockClusterMetadataManager(ctrl)
    72  	mockMgr.EXPECT().PruneClusterMembership(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
    73  	mockMgr.EXPECT().UpsertClusterMembership(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
    74  
    75  	cluster := &testCluster{
    76  		hostUUIDs:    make([]string, size),
    77  		hostAddrs:    make([]string, size),
    78  		hostInfoList: make([]membership.HostInfo, size),
    79  		rings:        make([]*monitor, size),
    80  		channels:     make([]*tchannel.Channel, size),
    81  		seedNode:     seed,
    82  	}
    83  
    84  	for i := 0; i < size; i++ {
    85  		var err error
    86  		cluster.channels[i], err = tchannel.NewChannel(ringPopApp, nil)
    87  		if err != nil {
    88  			logger.Error("Failed to create tchannel", tag.Error(err))
    89  			return nil
    90  		}
    91  		listenAddr := listenIPAddr + ":0"
    92  		err = cluster.channels[i].ListenAndServe(listenAddr)
    93  		if err != nil {
    94  			logger.Error("tchannel listen failed", tag.Error(err))
    95  			return nil
    96  		}
    97  		cluster.hostUUIDs[i] = uuid.New()
    98  		cluster.hostAddrs[i], err = buildBroadcastHostPort(cluster.channels[i].PeerInfo(), broadcastAddress)
    99  		if err != nil {
   100  			logger.Error("Failed to build broadcast hostport", tag.Error(err))
   101  			return nil
   102  		}
   103  		cluster.hostInfoList[i] = newHostInfo(cluster.hostAddrs[i], nil)
   104  	}
   105  
   106  	// if seed node is already supplied, use it; if not, set it
   107  	if cluster.seedNode == "" {
   108  		cluster.seedNode = cluster.hostAddrs[0]
   109  	}
   110  	logger.Info("seedNode", tag.Name(cluster.seedNode))
   111  
   112  	seedAddress, seedPort, err := splitHostPortTyped(cluster.seedNode)
   113  	if err != nil {
   114  		logger.Error("unable to split host port", tag.Error(err))
   115  		return nil
   116  	}
   117  	seedMember := &persistence.ClusterMember{
   118  		HostID:        uuid.NewUUID(),
   119  		RPCAddress:    seedAddress,
   120  		RPCPort:       seedPort,
   121  		SessionStart:  time.Now().UTC(),
   122  		LastHeartbeat: time.Now().UTC(),
   123  	}
   124  
   125  	firstGetClusterMemberCall := true
   126  	mockMgr.EXPECT().GetClusterMembers(gomock.Any(), gomock.Any()).DoAndReturn(
   127  		func(_ context.Context, _ *persistence.GetClusterMembersRequest) (*persistence.GetClusterMembersResponse, error) {
   128  			res := &persistence.GetClusterMembersResponse{ActiveMembers: []*persistence.ClusterMember{seedMember}}
   129  
   130  			if firstGetClusterMemberCall {
   131  				// The first time GetClusterMembers is invoked, we simulate returning a stale/bad heartbeat.
   132  				// All subsequent calls only return the single "good" seed member
   133  				// This ensures that we exercise the retry path in bootstrap properly.
   134  				badSeedMember := &persistence.ClusterMember{
   135  					HostID:        uuid.NewUUID(),
   136  					RPCAddress:    seedAddress,
   137  					RPCPort:       seedPort + 1,
   138  					SessionStart:  time.Now().UTC(),
   139  					LastHeartbeat: time.Now().UTC(),
   140  				}
   141  				res = &persistence.GetClusterMembersResponse{ActiveMembers: []*persistence.ClusterMember{seedMember, badSeedMember}}
   142  			}
   143  
   144  			firstGetClusterMemberCall = false
   145  			return res, nil
   146  		}).AnyTimes()
   147  
   148  	for i := 0; i < size; i++ {
   149  		node := i
   150  		resolver := func() (string, error) {
   151  			return buildBroadcastHostPort(cluster.channels[node].PeerInfo(), broadcastAddress)
   152  		}
   153  
   154  		ringPop, err := ringpop.New(ringPopApp, ringpop.Channel(cluster.channels[i]), ringpop.AddressResolverFunc(resolver))
   155  		if err != nil {
   156  			logger.Error("failed to create ringpop instance", tag.Error(err))
   157  			return nil
   158  		}
   159  		_, port, _ := splitHostPortTyped(cluster.hostAddrs[i])
   160  		cluster.rings[i] = newMonitor(
   161  			serviceName,
   162  			config.ServicePortMap{serviceName: int(port)}, // use same port for "grpc" port
   163  			ringPop,
   164  			logger,
   165  			mockMgr,
   166  			resolver,
   167  			2*time.Second,
   168  		)
   169  		cluster.rings[i].Start()
   170  	}
   171  	return cluster
   172  }
   173  
   174  // GetSeedNode returns the seedNode for this cluster
   175  func (c *testCluster) GetSeedNode() string {
   176  	return c.seedNode
   177  }
   178  
   179  // KillHost kills the given host within the cluster
   180  func (c *testCluster) KillHost(hostID string) {
   181  	for i := 0; i < len(c.hostUUIDs); i++ {
   182  		if c.hostUUIDs[i] == hostID {
   183  			c.rings[i].Stop()
   184  			c.channels[i].Close()
   185  			c.rings[i] = nil
   186  			c.channels[i] = nil
   187  		}
   188  	}
   189  }
   190  
   191  // Stop stops the cluster
   192  func (c *testCluster) Stop() {
   193  	for i := 0; i < len(c.hostAddrs); i++ {
   194  		if c.rings[i] != nil {
   195  			c.rings[i].Stop()
   196  			c.channels[i].Close()
   197  		}
   198  	}
   199  }
   200  
   201  // GetHostInfoList returns the list of all hosts within the cluster
   202  func (c *testCluster) GetHostInfoList() []membership.HostInfo {
   203  	return c.hostInfoList
   204  }
   205  
   206  // GetHostAddrs returns all host addrs within the cluster
   207  func (c *testCluster) GetHostAddrs() []string {
   208  	return c.hostAddrs
   209  }
   210  
   211  // FindHostByAddr returns the host info corresponding to
   212  // the given addr, if it exists
   213  func (c *testCluster) FindHostByAddr(addr string) (membership.HostInfo, bool) {
   214  	for _, hi := range c.hostInfoList {
   215  		if hi.GetAddress() == addr {
   216  			return hi, true
   217  		}
   218  	}
   219  	return nil, false
   220  }