go.temporal.io/server/common/rpc/rpc.go
413 LOC · 198 covered · 215 uncovered · 54 ranges · 82 concepts · 18 introducers · 19 tests
File neighbourhood
The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.
Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file
In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.
Graph controls are ready.
Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.
package rpc
import (
"cmp"
"context"
"crypto/tls"
"fmt"
"math"
"math/rand"
"net"
"net/http"
"net/url"
"sync"
"time"
"go.temporal.io/api/serviceerror"
"go.temporal.io/server/common"
"go.temporal.io/server/common/config"
"go.temporal.io/server/common/convert"
"go.temporal.io/server/common/log"
"go.temporal.io/server/common/log/tag"
"go.temporal.io/server/common/membership"
"go.temporal.io/server/common/metrics"
"go.temporal.io/server/common/primitives"
"go.temporal.io/server/common/rpc/auth"
"go.temporal.io/server/common/rpc/encryption"
"go.temporal.io/server/temporal/environment"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/status"
)
var _ common.RPCFactory = (*RPCFactory)(nil)
// RPCFactory is an implementation of common.RPCFactory interface
type RPCFactory struct {
config *config.Config
serviceName primitives.ServiceName
logger log.Logger
metricsHandler metrics.Handler
frontendURL string
frontendHTTPURL string
frontendHTTPPort int
frontendTLSConfig *tls.Config
grpcListener func() net.Listener
tlsFactory encryption.TLSConfigProvider
commonDialOptions []grpc.DialOption
perServiceDialOptions map[primitives.ServiceName][]grpc.DialOption
tokenProvider auth.TokenProvider
authHeaderName string
requireRemoteClusterAuth bool
monitor membership.Monitor
// A OnceValues wrapper for createLocalFrontendHTTPClient.
localFrontendClient func() (*common.FrontendHTTPClient, error)
// TODO: Remove these flags once the keepalive settings are rolled out
EnableInternodeServerKeepalive bool
EnableInternodeClientKeepalive bool
}
// NewFactory builds a new RPCFactory
// conforming to the underlying configuration
func NewFactory(
cfg *config.Config,
sName primitives.ServiceName,
logger log.Logger,
metricsHandler metrics.Handler,
tlsProvider encryption.TLSConfigProvider,
frontendURL string,
frontendHTTPURL string,
frontendHTTPPort int,
frontendTLSConfig *tls.Config,
commonDialOptions []grpc.DialOption,
perServiceDialOptions map[primitives.ServiceName][]grpc.DialOption,
monitor membership.Monitor,
tokenProvider auth.TokenProvider,
authHeaderName := "authorization"
requireRemoteClusterAuth := false
if cfg != nil {
requireRemoteClusterAuth = cfg.Global.Authorization.RemoteClusterAuth.Require
}
config: cfg,
serviceName: sName,
logger: logger,
metricsHandler: metricsHandler,
frontendURL: frontendURL,
frontendHTTPURL: frontendHTTPURL,
frontendHTTPPort: frontendHTTPPort,
frontendTLSConfig: frontendTLSConfig,
tlsFactory: tlsProvider,
commonDialOptions: commonDialOptions,
perServiceDialOptions: perServiceDialOptions,
tokenProvider: tokenProvider,
authHeaderName: authHeaderName,
requireRemoteClusterAuth: requireRemoteClusterAuth,
monitor: monitor,
}
f.grpcListener = sync.OnceValue(f.createGRPCListener)
f.localFrontendClient = sync.OnceValues(f.createLocalFrontendHTTPClient)
return f
}
var opts []grpc.ServerOption
if d.tlsFactory != nil {
if err != nil {
return nil, err
}
return opts, nil
}
opts = append(opts, grpc.Creds(credentials.NewTLS(serverConfig)))
}
}
func (d *RPCFactory) GetFrontendClientTlsConfig() (*tls.Config, error) {
if d.tlsFactory != nil {
return d.tlsFactory.GetFrontendClientConfig()
}
return nil, nil
}
func (d *RPCFactory) GetRemoteClusterClientConfig(hostname string) (*tls.Config, error) {
if d.tlsFactory != nil {
return d.tlsFactory.GetRemoteClusterClientConfig(hostname)
}
return nil, nil
}
var opts []grpc.ServerOption
if d.EnableInternodeServerKeepalive {
rpcConfig := d.config.Services[string(d.serviceName)].RPC
kep := rpcConfig.KeepAliveServerConfig.GetKeepAliveEnforcementPolicy()
kp := rpcConfig.KeepAliveServerConfig.GetKeepAliveServerParameters()
opts = append(opts, grpc.KeepaliveEnforcementPolicy(kep), grpc.KeepaliveParams(kp))
}
if err != nil {
return nil, err
}
return opts, nil
}
opts = append(opts, grpc.Creds(credentials.NewTLS(serverConfig)))
}
}
func (d *RPCFactory) GetInternodeClientTlsConfig() (*tls.Config, error) {
if d.tlsFactory != nil {
return d.tlsFactory.GetInternodeClientConfig()
}
return nil, nil
}
// GetGRPCListener returns cached dispatcher for gRPC inbound or creates one
return d.grpcListener()
}
rpcConfig := d.config.Services[string(d.serviceName)].RPC
hostAddress := net.JoinHostPort(getListenIP(&rpcConfig, d.logger).String(), convert.IntToString(rpcConfig.GRPCPort))
grpcListener, err := net.Listen("tcp", hostAddress)
if err != nil || grpcListener == nil || grpcListener.Addr() == nil {
d.logger.Fatal("Failed to start gRPC listener", tag.Error(err), tag.Service(d.serviceName), tag.Address(hostAddress))
}
d.logger.Info("Created gRPC listener", tag.Service(d.serviceName), tag.Address(hostAddress))
fx.go ×44
return grpcListener
}
if cfg.BindOnLocalHost && len(cfg.BindOnIP) > 0 {
logger.Fatal("ListenIP failed, bindOnLocalHost and bindOnIP are mutually exclusive")
return nil
}
}
ip := net.ParseIP(cfg.BindOnIP)
if ip != nil {
return ip
}
logger.Fatal("ListenIP failed, unable to parse bindOnIP value", tag.Address(cfg.BindOnIP))
return nil
}
ip, err := config.ListenIP()
if err != nil {
logger.Fatal("ListenIP failed", tag.Error(err))
return nil
}
return ip
}
// CreateRemoteFrontendGRPCConnection creates a gRPC connection for cross-cluster calls.
func (d *RPCFactory) CreateRemoteFrontendGRPCConnection(rpcAddress string) *grpc.ClientConn {
rpc.go ×4
var tlsClientConfig *tls.Config
var err error
if d.tlsFactory != nil {
hostname, _, err2 := net.SplitHostPort(rpcAddress)
if err2 != nil {
d.logger.Fatal("Invalid rpcAddress for remote cluster", tag.Error(err2))
}
if err != nil {
d.logger.Fatal("Failed to create tls config for gRPC connection", tag.Error(err))
return nil
}
}
additionalDialOptions := append([]grpc.DialOption{}, d.perServiceDialOptions[primitives.FrontendService]...)
// requireRemoteClusterAuth is defense-in-depth: temporal/fx.go boot validation
// rejects (require=true, tokenProvider=nil), but RPCFactory is also constructed in
// tests where the boot path doesn't run.
if d.tokenProvider != nil || d.requireRemoteClusterAuth {
var token string
if d.tokenProvider != nil {
t, err := d.tokenProvider.GetToken(ctx, rpcAddress)
if err != nil {
return "", err
}
}
return "", status.Error(codes.Unauthenticated, "no auth token available for outbound remote-cluster RPC")
rpc.go ×1
}
}
additionalDialOptions = append(additionalDialOptions, grpc.WithPerRPCCredentials(creds))
}
return d.dial(rpcAddress, tlsClientConfig, append(additionalDialOptions, keepAliveOption)...)
rpc.go ×4
}
// CreateLocalFrontendGRPCConnection creates connection for internal frontend calls
additionalDialOptions := append([]grpc.DialOption{}, d.perServiceDialOptions[primitives.InternalFrontendService]...)
return d.dial(d.frontendURL, d.frontendTLSConfig, additionalDialOptions...)
}
// createInternodeGRPCConnection creates connection for gRPC calls
func (d *RPCFactory) createInternodeGRPCConnection(hostName string, serviceName primitives.ServiceName) *grpc.ClientConn {
service_grpc.pb.go ×20
var tlsClientConfig *tls.Config
var err error
if d.tlsFactory != nil {
if err != nil {
d.logger.Fatal("Failed to create tls config for gRPC connection", tag.Error(err))
return nil
}
}
additionalDialOptions := append([]grpc.DialOption{}, d.perServiceDialOptions[serviceName]...)
service_grpc.pb.go ×20
return d.dial(hostName, tlsClientConfig, append(additionalDialOptions, d.getClientKeepAliveConfig(serviceName))...)
}
func (d *RPCFactory) CreateHistoryGRPCConnection(rpcAddress string) *grpc.ClientConn {
handler.go ×25
return d.createInternodeGRPCConnection(rpcAddress, primitives.HistoryService)
}
func (d *RPCFactory) CreateMatchingGRPCConnection(rpcAddress string) *grpc.ClientConn {
service_grpc.pb.go ×20
return d.createInternodeGRPCConnection(rpcAddress, primitives.MatchingService)
}
func (d *RPCFactory) dial(hostName string, tlsClientConfig *tls.Config, dialOptions ...grpc.DialOption) *grpc.ClientConn {
grpc.go ×4
dialOptions = append(d.commonDialOptions, dialOptions...)
connection, err := Dial(hostName, tlsClientConfig, d.logger, d.metricsHandler, dialOptions...)
if err != nil {
d.logger.Fatal("Failed to create gRPC connection", tag.Error(err))
return nil
}
}
func (d *RPCFactory) getClientKeepAliveConfig(serviceName primitives.ServiceName) grpc.DialOption {
grpc.go ×3
// default keepalive settings for clients
params := keepalive.ClientParameters{
Time: time.Duration(math.MaxInt64),
Timeout: 20 * time.Second,
PermitWithoutStream: false,
}
if d.EnableInternodeClientKeepalive {
serviceConfig := d.config.Services[string(serviceName)]
params = serviceConfig.RPC.ClientConnectionConfig.GetKeepAliveClientParameters()
}
}
func (d *RPCFactory) GetTLSConfigProvider() encryption.TLSConfigProvider {
return d.tlsFactory
}
// CreateLocalFrontendHTTPClient gets or creates a cached frontend client.
func (d *RPCFactory) CreateLocalFrontendHTTPClient() (*common.FrontendHTTPClient, error) {
rpc.go ×6
return d.localFrontendClient()
}
// createLocalFrontendHTTPClient creates an HTTP client for communicating with the frontend.
// It uses either the provided frontendURL or membership to resolve the frontend address.
func (d *RPCFactory) createLocalFrontendHTTPClient() (*common.FrontendHTTPClient, error) {
rpc.go ×6
// dialer and transport field values copied from http.DefaultTransport.
dialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dialer.DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := http.Client{}
// Default to http unless TLS is configured.
scheme := "http"
if d.frontendTLSConfig != nil {
scheme = "https"
}
if r := serviceResolverFromGRPCURL(d.frontendHTTPURL); r != nil {
resolver: r,
underlying: transport,
httpPort: d.frontendHTTPPort,
}
address = "internal" // This will be replaced by the roundTripper
client.Transport = transport
address = d.frontendHTTPURL
}
Client: client,
Address: address,
Scheme: scheme,
}, nil
}
type roundTripper struct {
resolver membership.ServiceResolver
underlying http.RoundTripper
httpPort int
}
// Pick a frontend host at random.
members := rt.resolver.AvailableMembers()
if len(members) == 0 {
return nil, serviceerror.NewUnavailable("no frontend host to route request to")
}
member := members[idx]
// Replace port with the HTTP port.
host, _, err := net.SplitHostPort(member.Identity())
if err != nil {
return nil, fmt.Errorf("failed to extract port from frontend member: %w", err)
}
// Replace request's host.
req.URL.Host = address
req.Host = address
return rt.underlying.RoundTrip(req)
}
// serviceResolverFromGRPCURL returns a ServiceResolver if ustr corresponds to a
// membership url, otherwise nil.
u, err := url.Parse(ustr)
if err != nil {
}
if err != nil {
return nil
}
}