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 }