go.temporal.io/server@v1.23.0/common/membership/ringpop/factory.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 "crypto/tls" 30 "errors" 31 "fmt" 32 "net" 33 "sync" 34 "time" 35 36 "github.com/temporalio/ringpop-go" 37 "github.com/temporalio/tchannel-go" 38 "go.uber.org/fx" 39 40 "go.temporal.io/server/common/config" 41 "go.temporal.io/server/common/convert" 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/membership" 47 "go.temporal.io/server/common/persistence" 48 "go.temporal.io/server/common/primitives" 49 "go.temporal.io/server/common/rpc/encryption" 50 "go.temporal.io/server/environment" 51 ) 52 53 const ( 54 defaultMaxJoinDuration = 10 * time.Second 55 persistenceOperationTimeout = 10 * time.Second 56 ) 57 58 type factoryParams struct { 59 fx.In 60 61 Config *config.Membership 62 ServiceName primitives.ServiceName 63 ServicePortMap config.ServicePortMap 64 Logger log.Logger 65 MetadataManager persistence.ClusterMetadataManager 66 RPCConfig *config.RPC 67 TLSFactory encryption.TLSConfigProvider 68 DC *dynamicconfig.Collection 69 } 70 71 // factory provides ringpop based membership objects 72 type factory struct { 73 Config *config.Membership 74 ServiceName primitives.ServiceName 75 ServicePortMap config.ServicePortMap 76 Logger log.Logger 77 MetadataManager persistence.ClusterMetadataManager 78 RPCConfig *config.RPC 79 TLSFactory encryption.TLSConfigProvider 80 DC *dynamicconfig.Collection 81 82 channel *tchannel.Channel 83 monitor *monitor 84 chOnce sync.Once 85 monOnce sync.Once 86 } 87 88 var errMalformedBroadcastAddress = errors.New("ringpop config malformed `broadcastAddress` param") 89 90 // newFactory builds a ringpop factory 91 func newFactory(params factoryParams) (*factory, error) { 92 cfg := params.Config 93 if cfg.BroadcastAddress != "" && net.ParseIP(cfg.BroadcastAddress) == nil { 94 return nil, fmt.Errorf("%w: %s", errMalformedBroadcastAddress, cfg.BroadcastAddress) 95 } 96 97 if cfg.MaxJoinDuration == 0 { 98 cfg.MaxJoinDuration = defaultMaxJoinDuration 99 } 100 101 return &factory{ 102 Config: params.Config, 103 ServiceName: params.ServiceName, 104 ServicePortMap: params.ServicePortMap, 105 Logger: params.Logger, 106 MetadataManager: params.MetadataManager, 107 RPCConfig: params.RPCConfig, 108 TLSFactory: params.TLSFactory, 109 DC: params.DC, 110 }, nil 111 } 112 113 // getMonitor returns a membership monitor 114 func (factory *factory) getMonitor() *monitor { 115 factory.monOnce.Do(func() { 116 ctx, cancel := context.WithTimeout(context.Background(), persistenceOperationTimeout) 117 defer cancel() 118 119 ctx = headers.SetCallerInfo(ctx, headers.SystemBackgroundCallerInfo) 120 currentClusterMetadata, err := factory.MetadataManager.GetCurrentClusterMetadata(ctx) 121 if err != nil { 122 factory.Logger.Fatal("Failed to get current cluster ID", tag.Error(err)) 123 } 124 125 appName := "temporal" 126 if currentClusterMetadata.UseClusterIdMembership { 127 appName = fmt.Sprintf("temporal-%s", currentClusterMetadata.GetClusterId()) 128 } 129 rp, err := ringpop.New(appName, ringpop.Channel(factory.getTChannel()), ringpop.AddressResolverFunc(factory.broadcastAddressResolver)) 130 if err != nil { 131 factory.Logger.Fatal("Failed to get new ringpop", tag.Error(err)) 132 } 133 134 factory.monitor = newMonitor( 135 factory.ServiceName, 136 factory.ServicePortMap, 137 rp, 138 factory.Logger, 139 factory.MetadataManager, 140 factory.broadcastAddressResolver, 141 factory.Config.MaxJoinDuration, 142 ) 143 }) 144 145 return factory.monitor 146 } 147 148 func (factory *factory) broadcastAddressResolver() (string, error) { 149 return buildBroadcastHostPort(factory.getTChannel().PeerInfo(), factory.Config.BroadcastAddress) 150 } 151 152 func (factory *factory) getTChannel() *tchannel.Channel { 153 factory.chOnce.Do(func() { 154 ringpopServiceName := fmt.Sprintf("%v-ringpop", factory.ServiceName) 155 ringpopHostAddress := net.JoinHostPort(factory.getListenIP().String(), convert.IntToString(factory.RPCConfig.MembershipPort)) 156 enableTLS := factory.DC.GetBoolProperty(dynamicconfig.EnableRingpopTLS, false)() 157 158 var tChannel *tchannel.Channel 159 if enableTLS { 160 tChannel = factory.getTLSChannel(ringpopHostAddress, ringpopServiceName) 161 } else { 162 tChannel = factory.getTCPChannel(ringpopHostAddress, ringpopServiceName) 163 } 164 factory.channel = tChannel 165 }) 166 167 return factory.channel 168 } 169 170 func (factory *factory) getTCPChannel(ringpopHostAddress string, ringpopServiceName string) *tchannel.Channel { 171 listener, err := net.Listen("tcp", ringpopHostAddress) 172 if err != nil { 173 factory.Logger.Fatal("Failed to start ringpop listener", tag.Error(err), tag.Address(ringpopHostAddress)) 174 } 175 176 tChannel, err := tchannel.NewChannel(ringpopServiceName, &tchannel.ChannelOptions{}) 177 if err != nil { 178 factory.Logger.Fatal("Failed to create ringpop TChannel", tag.Error(err)) 179 } 180 181 if err := tChannel.Serve(listener); err != nil { 182 factory.Logger.Fatal("Failed to serve ringpop listener", tag.Error(err), tag.Address(ringpopHostAddress)) 183 } 184 return tChannel 185 } 186 187 func (factory *factory) getTLSChannel(ringpopHostAddress string, ringpopServiceName string) *tchannel.Channel { 188 clientTLSConfig, err := factory.TLSFactory.GetInternodeClientConfig() 189 if err != nil { 190 factory.Logger.Fatal("Failed to get internode TLS client config", tag.Error(err)) 191 } 192 193 serverTLSConfig, err := factory.TLSFactory.GetInternodeServerConfig() 194 if err != nil { 195 factory.Logger.Fatal("Failed to get internode TLS server config", tag.Error(err)) 196 } 197 198 listener, err := tls.Listen("tcp", ringpopHostAddress, serverTLSConfig) 199 if err != nil { 200 factory.Logger.Fatal("Failed to start ringpop TLS listener", tag.Error(err), tag.Address(ringpopHostAddress)) 201 } 202 203 dialer := tls.Dialer{Config: clientTLSConfig} 204 tChannel, err := tchannel.NewChannel(ringpopServiceName, &tchannel.ChannelOptions{Dialer: dialer.DialContext}) 205 if err != nil { 206 factory.Logger.Fatal("Failed to create ringpop TChannel", tag.Error(err)) 207 } 208 209 if err := tChannel.Serve(listener); err != nil { 210 factory.Logger.Fatal("Failed to serve ringpop listener", tag.Error(err), tag.Address(ringpopHostAddress)) 211 } 212 return tChannel 213 } 214 215 func (factory *factory) getListenIP() net.IP { 216 if factory.RPCConfig.BindOnLocalHost && len(factory.RPCConfig.BindOnIP) > 0 { 217 factory.Logger.Fatal("ListenIP failed, bindOnLocalHost and bindOnIP are mutually exclusive") 218 return nil 219 } 220 221 if factory.RPCConfig.BindOnLocalHost { 222 return net.ParseIP(environment.GetLocalhostIP()) 223 } 224 225 if len(factory.RPCConfig.BindOnIP) > 0 { 226 ip := net.ParseIP(factory.RPCConfig.BindOnIP) 227 if ip != nil { 228 return ip 229 } 230 231 factory.Logger.Fatal("ListenIP failed, unable to parse bindOnIP value", tag.Address(factory.RPCConfig.BindOnIP)) 232 return nil 233 } 234 235 ip, err := config.ListenIP() 236 if err != nil { 237 factory.Logger.Fatal("ListenIP failed", tag.Error(err)) 238 return nil 239 } 240 return ip 241 } 242 243 // closeTChannel allows fx Stop hook to close channel 244 func (factory *factory) closeTChannel() { 245 if factory.channel != nil { 246 factory.getTChannel().Close() 247 factory.channel = nil 248 } 249 } 250 251 func (factory *factory) getHostInfoProvider() (membership.HostInfoProvider, error) { 252 address, err := factory.broadcastAddressResolver() 253 if err != nil { 254 return nil, err 255 } 256 257 servicePort, ok := factory.ServicePortMap[factory.ServiceName] 258 if !ok { 259 return nil, membership.ErrUnknownService 260 } 261 262 // The broadcastAddressResolver returns the host:port used to listen for 263 // ringpop messages. We use a different port for the service, so we 264 // replace that portion. 265 serviceAddress, err := replaceServicePort(address, servicePort) 266 if err != nil { 267 return nil, err 268 } 269 270 hostInfo := membership.NewHostInfoFromAddress(serviceAddress) 271 return membership.NewHostInfoProvider(hostInfo), nil 272 }