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 }