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  }