go.temporal.io/server@v1.23.0/common/sdk/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  //go:generate mockgen -copyright_file ../../LICENSE -package $GOPACKAGE -source $GOFILE -destination factory_mock.go
    26  
    27  package sdk
    28  
    29  import (
    30  	"context"
    31  	"crypto/tls"
    32  	"sync"
    33  
    34  	sdkclient "go.temporal.io/sdk/client"
    35  	sdklog "go.temporal.io/sdk/log"
    36  	sdkworker "go.temporal.io/sdk/worker"
    37  	"google.golang.org/grpc"
    38  	"google.golang.org/grpc/metadata"
    39  
    40  	"go.temporal.io/server/common"
    41  	"go.temporal.io/server/common/backoff"
    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/metrics"
    47  	"go.temporal.io/server/common/primitives"
    48  )
    49  
    50  type (
    51  	ClientFactory interface {
    52  		// options must include Namespace and should not include: HostPort, ConnectionOptions,
    53  		// MetricsHandler, or Logger (they will be overwritten)
    54  		NewClient(options sdkclient.Options) sdkclient.Client
    55  		GetSystemClient() sdkclient.Client
    56  		NewWorker(client sdkclient.Client, taskQueue string, options sdkworker.Options) sdkworker.Worker
    57  	}
    58  
    59  	clientFactory struct {
    60  		hostPort        string
    61  		tlsConfig       *tls.Config
    62  		metricsHandler  *MetricsHandler
    63  		logger          log.Logger
    64  		sdklogger       sdklog.Logger
    65  		systemSdkClient sdkclient.Client
    66  		stickyCacheSize dynamicconfig.IntPropertyFn
    67  		once            sync.Once
    68  	}
    69  )
    70  
    71  var (
    72  	_ ClientFactory = (*clientFactory)(nil)
    73  )
    74  
    75  func NewClientFactory(
    76  	hostPort string,
    77  	tlsConfig *tls.Config,
    78  	metricsHandler metrics.Handler,
    79  	logger log.Logger,
    80  	stickyCacheSize dynamicconfig.IntPropertyFn,
    81  ) *clientFactory {
    82  	return &clientFactory{
    83  		hostPort:        hostPort,
    84  		tlsConfig:       tlsConfig,
    85  		metricsHandler:  NewMetricsHandler(metricsHandler),
    86  		logger:          logger,
    87  		sdklogger:       log.NewSdkLogger(logger),
    88  		stickyCacheSize: stickyCacheSize,
    89  	}
    90  }
    91  
    92  func (f *clientFactory) options(options sdkclient.Options) sdkclient.Options {
    93  	options.HostPort = f.hostPort
    94  	options.MetricsHandler = f.metricsHandler
    95  	options.Logger = f.sdklogger
    96  	options.ConnectionOptions = sdkclient.ConnectionOptions{
    97  		TLS: f.tlsConfig,
    98  		DialOptions: []grpc.DialOption{
    99  			grpc.WithUnaryInterceptor(sdkClientNameHeadersInjectorInterceptor()),
   100  		},
   101  	}
   102  	return options
   103  }
   104  
   105  func (f *clientFactory) NewClient(options sdkclient.Options) sdkclient.Client {
   106  	// this shouldn't fail if the first client was created successfully
   107  	client, err := sdkclient.NewClientFromExisting(f.GetSystemClient(), f.options(options))
   108  	if err != nil {
   109  		f.logger.Fatal("error creating sdk client", tag.Error(err))
   110  	}
   111  	return client
   112  }
   113  
   114  func (f *clientFactory) GetSystemClient() sdkclient.Client {
   115  	f.once.Do(func() {
   116  		err := backoff.ThrottleRetry(func() error {
   117  			sdkClient, err := sdkclient.Dial(f.options(sdkclient.Options{
   118  				Namespace: primitives.SystemLocalNamespace,
   119  			}))
   120  			if err != nil {
   121  				f.logger.Warn("error creating sdk client", tag.Error(err))
   122  				return err
   123  			}
   124  			f.systemSdkClient = sdkClient
   125  			return nil
   126  		}, common.CreateSdkClientFactoryRetryPolicy(), common.IsContextDeadlineExceededErr)
   127  		if err != nil {
   128  			f.logger.Fatal("error creating sdk client", tag.Error(err))
   129  		}
   130  
   131  		if size := f.stickyCacheSize(); size > 0 {
   132  			f.logger.Info("setting sticky workflow cache size", tag.NewInt("size", size))
   133  			sdkworker.SetStickyWorkflowCacheSize(size)
   134  		}
   135  	})
   136  	return f.systemSdkClient
   137  }
   138  
   139  func (f *clientFactory) NewWorker(
   140  	client sdkclient.Client,
   141  	taskQueue string,
   142  	options sdkworker.Options,
   143  ) sdkworker.Worker {
   144  	return sdkworker.New(client, taskQueue, options)
   145  }
   146  
   147  // Overwrite the 'client-name' and 'client-version' headers on gRPC requests sent using the Go SDK
   148  // so they clearly indicate that the request is coming from the Temporal server.
   149  func sdkClientNameHeadersInjectorInterceptor() grpc.UnaryClientInterceptor {
   150  	return func(
   151  		ctx context.Context,
   152  		method string,
   153  		req, reply interface{},
   154  		cc *grpc.ClientConn,
   155  		invoker grpc.UnaryInvoker,
   156  		opts ...grpc.CallOption,
   157  	) error {
   158  		// Can't use headers.SetVersions() here because it is _appending_ headers to the context
   159  		// rather than _replacing_ them, which means Go SDK's default headers would still be present.
   160  		md, mdExist := metadata.FromOutgoingContext(ctx)
   161  		if !mdExist {
   162  			md = metadata.New(nil)
   163  		}
   164  		md.Set(headers.ClientNameHeaderName, headers.ClientNameServer)
   165  		md.Set(headers.ClientVersionHeaderName, headers.ServerVersion)
   166  		ctx = metadata.NewOutgoingContext(ctx, md)
   167  		return invoker(ctx, method, req, reply, cc, opts...)
   168  	}
   169  }