codegen/template_bundle/template_files.go (4,464 lines of code) (raw):

// Code generated by go-bindata. // sources: // codegen/templates/augmented_mock.tmpl // codegen/templates/clientless-workflow.tmpl // codegen/templates/dependency_struct.tmpl // codegen/templates/endpoint.tmpl // codegen/templates/endpoint_collection.tmpl // codegen/templates/endpoint_test.tmpl // codegen/templates/endpoint_test_tchannel_client.tmpl // codegen/templates/fixture_types.tmpl // codegen/templates/grpc_client.tmpl // codegen/templates/http_client.tmpl // codegen/templates/http_client_test.tmpl // codegen/templates/main.tmpl // codegen/templates/main_test.tmpl // codegen/templates/middleware_http.tmpl // codegen/templates/middleware_tchannel.tmpl // codegen/templates/module_class_initializer.tmpl // codegen/templates/module_initializer.tmpl // codegen/templates/module_mock_initializer.tmpl // codegen/templates/service.tmpl // codegen/templates/service_mock.tmpl // codegen/templates/structs.tmpl // codegen/templates/tchannel_client.tmpl // codegen/templates/tchannel_client_test_server.tmpl // codegen/templates/tchannel_endpoint.tmpl // codegen/templates/workflow.tmpl // codegen/templates/workflow_mock.tmpl // codegen/templates/workflow_mock_clients_type.tmpl // DO NOT EDIT! package templates import ( "fmt" "io/ioutil" "os" "path/filepath" "strings" "time" ) type asset struct { bytes []byte info os.FileInfo } type bindataFileInfo struct { name string size int64 mode os.FileMode modTime time.Time } func (fi bindataFileInfo) Name() string { return fi.name } func (fi bindataFileInfo) Size() int64 { return fi.size } func (fi bindataFileInfo) Mode() os.FileMode { return fi.mode } func (fi bindataFileInfo) ModTime() time.Time { return fi.modTime } func (fi bindataFileInfo) IsDir() bool { return false } func (fi bindataFileInfo) Sys() interface{} { return nil } var _augmented_mockTmpl = []byte(`{{- /* template to render client mock code for custom client */ -}} {{- $methods := .Methods}} {{- $fixturePkg := .Fixture.ImportPath -}} {{- $scenarios := .Fixture.Scenarios -}} {{- $clientinterface := .ClientInterface}} package clientmock import ( "github.com/golang/mock/gomock" ) // Mock{{$clientinterface}}WithFixture is a mock of Client interface with preset fixture type Mock{{$clientinterface}}WithFixture struct { *Mock{{$clientinterface}} fixture *ClientFixture {{range $method := $methods}} {{$methodName := $method.Name -}} {{$methodMockType := printf "%sMock" $methodName -}} {{camel $methodMockType}} *{{$methodMockType}} {{- end}} } // Call is a thin wrapper around gomock.Call for exposing the methods that do not mutate the fixture related information // like Return(). type Call struct { call *gomock.Call } // MaxTimes marks a fixture as callable up to a maximum number of times. func (c Call) MaxTimes(max int) { c.call.MaxTimes(max) } // MinTimes marks a fixture as must be called a minimum number of times. func (c Call) MinTimes(min int) { c.call.MinTimes(min) } // New creates a new mock instance func New(ctrl *gomock.Controller, fixture *ClientFixture) *Mock{{$clientinterface}}WithFixture { return &Mock{{$clientinterface}}WithFixture{ Mock{{$clientinterface}}: NewMock{{$clientinterface}}(ctrl), fixture: fixture, } } // EXPECT shadows the EXPECT method on the underlying mock client. // It should not be called directly. func (m *Mock{{$clientinterface}}WithFixture) EXPECT() { panic("should not call EXPECT directly.") } {{range $method := $methods}} {{$methodName := $method.Name -}} {{$methodMockType := printf "%sMock" $methodName -}} {{$methodMockField := camel $methodMockType -}} {{$methodScenarios := index $scenarios $methodName -}} // {{$methodMockType}} mocks the {{$methodName}} method type {{$methodMockType}} struct { scenarios *{{$methodName}}Scenarios mockClient *Mock{{$clientinterface}} } {{$methodMockMethod := printf "Expect%s" $methodName -}} // {{$methodMockMethod}} returns an object that allows the caller to choose expected scenario for {{$methodName}} func (m *Mock{{$clientinterface}}WithFixture) {{$methodMockMethod}}() *{{$methodMockType}} { if m.{{$methodMockField}} == nil { m.{{$methodMockField}} = &{{$methodMockType}}{ scenarios: m.fixture.{{$methodName}}, mockClient: m.Mock{{$clientinterface}}, } } return m.{{$methodMockField}} } {{- range $scenario := $methodScenarios -}} {{$scenarioMethod := pascal $scenario}} // {{$scenarioMethod}} sets the expected scenario as defined in the concrete fixture package // {{$fixturePkg}} func (s *{{$methodMockType}}) {{$scenarioMethod}}() Call { f := s.scenarios.{{$scenarioMethod}} {{range $argName, $argType := $method.In}} var {{$argName}} interface{} {{$argName}} = f.{{title $argName}} if f.{{title $argName}}Any { {{$argName}} = gomock.Any() } {{- end}} {{- if $method.Variadic}} var {{$method.Variadic}} []interface{} if f.{{title $method.Variadic}} != nil { for _, v := range f.{{title $method.Variadic}} { {{$method.Variadic}} = append({{$method.Variadic}}, v) } } else if f.{{title $method.Variadic}}Any > 0 { for i := 0; i < f.{{title $method.Variadic}}Any; i++ { {{$method.Variadic}} = append({{$method.Variadic}}, gomock.Any()) } } {{- end}} {{range $retName, $retType := $method.Out}} {{$retName}} := f.{{title $retName}} {{- end}} return Call{call: s.mockClient.EXPECT().{{$methodName}}({{$method.InString}}{{if $method.Variadic}}, {{$method.Variadic}}...{{end}}).Return({{$method.OutString}})} } {{- end -}} {{- end -}} `) func augmented_mockTmplBytes() ([]byte, error) { return _augmented_mockTmpl, nil } func augmented_mockTmpl() (*asset, error) { bytes, err := augmented_mockTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "augmented_mock.tmpl", size: 3627, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _clientlessWorkflowTmpl = []byte(`{{/* template to render gateway workflow interface code */ -}} {{- $instance := .Instance }} package clientlessworkflow {{- $endpointType := .Spec.EndpointType }} {{- $reqHeaderMap := .ReqHeaders }} {{- $reqHeaderMapKeys := .ReqHeadersKeys }} {{- $reqHeaderRequiredKeys := .ReqRequiredHeadersKeys }} {{- $resHeaderMap := .ResHeaders }} {{- $resHeaderMapKeys := .ResHeadersKeys }} {{- $clientlessEndpoint := .IsClientlessEndpoint }} {{- $clientID := .ClientID }} {{- $clientName := title .ClientName }} {{- $clientType := .ClientType }} {{- $clientMethodName := title .ClientMethodName }} {{- $serviceMethod := printf "%s%s" (title .Method.ThriftService) (title .Method.Name) }} {{- $workflowInterface := printf "%sWorkflow" $serviceMethod }} {{- $workflowStruct := camel $workflowInterface }} {{- $method := .Method }} import ( "context" "net/textproto" "github.com/uber/zanzibar/config" zanzibar "github.com/uber/zanzibar/runtime" {{range $idx, $pkg := .IncludedPackages -}} {{$pkg.AliasName}} "{{$pkg.PackageName}}" {{end -}} {{if .Method.Downstream }} {{- range $idx, $pkg := .Method.Downstream.IncludedPackages -}} {{$file := basePath $pkg.PackageName -}} {{$pkg.AliasName}} "{{$pkg.PackageName}}" {{end}} {{- end}} "go.uber.org/zap" module "{{$instance.PackageInfo.ModulePackagePath}}" ) {{with .Method -}} // {{$workflowInterface}} defines the interface for {{$serviceMethod}} workflow type {{$workflowInterface}} interface { Handle( {{- if and (eq .RequestType "") (eq .ResponseType "") }} ctx context.Context, reqHeaders zanzibar.Header, ) (context.Context, zanzibar.Header, error) {{else if eq .RequestType "" }} ctx context.Context, reqHeaders zanzibar.Header, ) (context.Context, {{.ResponseType}}, zanzibar.Header, error) {{else if eq .ResponseType "" }} ctx context.Context, reqHeaders zanzibar.Header, r {{.RequestType}}, ) (context.Context, zanzibar.Header, error) {{else}} ctx context.Context, reqHeaders zanzibar.Header, r {{.RequestType}}, ) (context.Context, {{.ResponseType}}, zanzibar.Header, error) {{- end}} } {{end -}} {{- with .Method -}} {{- $methodName := title .Name }} // New{{$workflowInterface}} creates a workflow func New{{$workflowInterface}}(deps *module.Dependencies) {{$workflowInterface}} { return &{{$workflowStruct}}{ Logger: deps.Default.Logger, } } // {{$workflowStruct}} calls thrift client {{$clientName}}.{{$clientMethodName}} type {{$workflowStruct}} struct { Logger *zap.Logger } // Handle processes the request without a downstream func (w {{$workflowStruct}}) Handle( {{- if and (eq .RequestType "") (eq .ResponseType "") }} ctx context.Context, reqHeaders zanzibar.Header, ) (context.Context, zanzibar.Header, error) { {{else if eq .RequestType "" }} ctx context.Context, reqHeaders zanzibar.Header, ) (context.Context, {{.ResponseType}}, zanzibar.Header, error) { {{else if eq .ResponseType "" }} ctx context.Context, reqHeaders zanzibar.Header, r {{.RequestType}}, ) (context.Context, zanzibar.Header, error) { {{else}} ctx context.Context, reqHeaders zanzibar.Header, r {{.RequestType}}, ) (context.Context, {{.ResponseType}}, zanzibar.Header, error) { {{- end}} {{- if ne .ResponseType "" -}} response := convert{{$methodName}}DummyResponse(r) {{end}} clientlessHeaders := map[string]string{} {{if (ne (len $reqHeaderMapKeys) 0) }} var ok bool var h string {{- end -}} {{range $i, $k := $reqHeaderMapKeys}} h, ok = reqHeaders.Get("{{$k}}") if ok { {{- $typedHeader := index $reqHeaderMap $k -}} clientlessHeaders["{{$typedHeader.TransformTo}}"] = h } {{- end}} // Filter and map response headers from client to server response. {{if eq $endpointType "tchannel" -}} resHeaders := zanzibar.ServerTChannelHeader{} {{- else -}} resHeaders := zanzibar.ServerHTTPHeader{} {{- end -}} {{range $i, $k := $resHeaderMapKeys}} {{- $resHeaderVal := index $resHeaderMap $k}} h, ok = clientlessHeaders["{{$k}}"] if ok { resHeaders.Set("{{$resHeaderVal.TransformTo}}", h) } {{- end}} {{if eq .ResponseType "" -}} return ctx, resHeaders, nil {{- else -}} resHeaders.Set(zanzibar.ClientTypeKey, "{{$clientType}}") return ctx, response, resHeaders, nil {{end}} {{- end -}} } {{if eq $clientlessEndpoint true -}} {{ range $key, $line := $method.ConvertClientlessRequestGoStatements -}} {{$line}} {{ end }} {{end -}} `) func clientlessWorkflowTmplBytes() ([]byte, error) { return _clientlessWorkflowTmpl, nil } func clientlessWorkflowTmpl() (*asset, error) { bytes, err := clientlessWorkflowTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "clientless-workflow.tmpl", size: 4355, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _dependency_structTmpl = []byte(`{{$instance := . -}} package module import ( {{range $classType, $moduleInstances := $instance.ResolvedDependencies -}} {{range $idx, $moduleInstance := $moduleInstances -}} {{$moduleInstance.PackageInfo.ImportPackageAlias}} "{{$moduleInstance.PackageInfo.ImportPackagePath}}" {{end -}} {{end}} zanzibar "github.com/uber/zanzibar/runtime" ) // Dependencies contains dependencies for the {{$instance.InstanceName}} {{$instance.ClassName}} module type Dependencies struct { Default *zanzibar.DefaultDependencies {{range $classType, $moduleInstances := $instance.ResolvedDependencies -}} {{$classType | pascal}} *{{$classType | pascal}}Dependencies {{end -}} } {{range $classType, $moduleInstances := $instance.ResolvedDependencies -}} // {{$classType | pascal}}Dependencies contains {{$classType}} dependencies type {{$classType | pascal}}Dependencies struct { {{ range $idx, $dependency := $moduleInstances -}} {{- /* TODO: the dependency type should cover all types instead of just interface type */ -}} {{$dependency.PackageInfo.QualifiedInstanceName}} {{$dependency.PackageInfo.ImportPackageAlias}}.{{$dependency.PackageInfo.ExportType}} {{end -}} } {{end -}} `) func dependency_structTmplBytes() ([]byte, error) { return _dependency_structTmpl, nil } func dependency_structTmpl() (*asset, error) { bytes, err := dependency_structTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "dependency_struct.tmpl", size: 1180, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _endpointTmpl = []byte(`{{/* template to render gateway http endpoint code */ -}} {{- $instance := .Instance }} package {{$instance.PackageInfo.PackageName}} {{- $reqHeaderMap := .ReqHeaders }} {{- $reqHeaderMapKeys := .ReqHeadersKeys }} {{- $reqHeaderRequiredKeys := .ReqRequiredHeadersKeys }} {{- $resHeaderMap := .ResHeaders }} {{- $resHeaderMapKeys := .ResHeadersKeys }} {{- $resHeaderRequiredKeys := .ResRequiredHeadersKeys }} {{- $clientName := title .ClientName }} {{- $serviceMethod := printf "%s%s" (title .Method.ThriftService) (title .Method.Name) }} {{- $handlerName := printf "%sHandler" $serviceMethod }} {{- $clientMethodName := title .ClientMethodName }} {{- $endpointId := .Spec.EndpointID }} {{- $handleId := .Spec.HandleID }} {{- $middlewares := .Spec.Middlewares }} {{- $workflowPkg := .WorkflowPkg }} {{- $workflowInterface := printf "%sWorkflow" $serviceMethod }} {{- $traceKey := .TraceKey }} import ( "context" "runtime/debug" "encoding/json" "io/ioutil" "net/http" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "go.uber.org/thriftrw/ptr" "go.uber.org/zap" "go.uber.org/zap/zapcore" zanzibar "github.com/uber/zanzibar/runtime" {{range $idx, $pkg := .IncludedPackages -}} {{$pkg.AliasName}} "{{$pkg.PackageName}}" {{end -}} {{if .Method.Downstream }} {{- range $idx, $pkg := .Method.Downstream.IncludedPackages -}} {{$file := basePath $pkg.PackageName -}} {{$pkg.AliasName}} "{{$pkg.PackageName}}" {{end}} {{- end}} {{- if len $middlewares | ne 0 }} {{- range $idx, $middleware := $middlewares }} {{$middleware.Name | camel}} "{{$middleware.ImportPath}}" {{- end}} {{- end}} module "{{$instance.PackageInfo.ModulePackagePath}}" ) {{with .Method -}} // {{$handlerName}} is the handler for "{{.HTTPPath}}" type {{$handlerName}} struct { Dependencies *module.Dependencies endpoint *zanzibar.RouterEndpoint } // New{{$handlerName}} creates a handler func New{{$handlerName}}(deps *module.Dependencies) *{{$handlerName}} { handler := &{{$handlerName}}{ Dependencies: deps, } handler.endpoint = zanzibar.NewRouterEndpoint( deps.Default.ContextExtractor, deps.Default, "{{$endpointId}}", "{{$handleId}}", {{ if len $middlewares | ne 0 -}} zanzibar.NewStack([]zanzibar.MiddlewareHandle{ {{range $idx, $middleware := $middlewares -}} deps.Middleware.{{$middleware.Name | pascal}}.NewMiddlewareHandle( {{$middleware.Name | camel}}.Options{ {{range $key, $value := $middleware.PrettyOptions -}} {{$key}} : {{$value}}, {{end -}} }, ), {{end -}} }, handler.HandleRequest).Handle, {{- else -}} handler.HandleRequest, {{- end}} ) return handler } // Register adds the http handler to the gateway's http router func (h *{{$handlerName}}) Register(g *zanzibar.Gateway) error { return g.HTTPRouter.Handle( "{{.HTTPMethod}}", "{{.HTTPPath}}", http.HandlerFunc(h.endpoint.HandleRequest), ) } // HandleRequest handles "{{.HTTPPath}}". func (h *{{$handlerName}}) HandleRequest( ctx context.Context, req *zanzibar.ServerHTTPRequest, res *zanzibar.ServerHTTPResponse, ) context.Context { defer func() { if r := recover(); r != nil { stacktrace := string(debug.Stack()) e := errors.Errorf("enpoint panic: %v, stacktrace: %v", r, stacktrace) ctx = h.Dependencies.Default.ContextLogger.ErrorZ( ctx, "Endpoint failure: endpoint panic", zap.Error(e), zap.String("stacktrace", stacktrace)) h.Dependencies.Default.ContextMetrics.IncCounter(ctx, zanzibar.MetricEndpointPanics, 1) res.SendError(502, "Unexpected workflow panic, recovered at endpoint.", nil) } }() {{ if $reqHeaderRequiredKeys -}} if !req.CheckHeaders({{$reqHeaderRequiredKeys | printf "%#v" }}) { return ctx } {{- end -}} {{if ne .RequestType ""}} var requestBody {{unref .RequestType}} {{- if ne .HTTPMethod "GET"}} if ok := req.ReadAndUnmarshalBody(&requestBody); !ok { return ctx } {{end}} {{range $index, $line := .RequestParamGoStatements -}} {{$line}} {{end}} {{end}} {{range $index, $line := .ReqHeaderGoStatements -}} {{$line}} {{end}} {{range $index, $line := .ParseQueryParamGoStatements -}} {{$line}} {{end}} // log endpoint request to downstream services if ce := h.Dependencies.Default.ContextLogger.Check(zapcore.DebugLevel, "stub"); ce != nil { var zfields []zapcore.Field {{- if ne .RequestType ""}} zfields = append(zfields, zap.String("body", fmt.Sprintf("%s", req.GetRawBody()))) {{- end}} for _, k := range req.Header.Keys() { if val, ok := req.Header.Get(k); ok { zfields = append(zfields, zap.String(k, val)) } } ctx = h.Dependencies.Default.ContextLogger.DebugZ(ctx, "endpoint request to downstream", zfields...) } w := {{$workflowPkg}}.New{{$workflowInterface}}(h.Dependencies) if span := req.GetSpan(); span != nil { ctx = opentracing.ContextWithSpan(ctx, span) } {{if and (eq .RequestType "") (eq .ResponseType "")}} ctx, cliRespHeaders, err := w.Handle(ctx, req.Header) {{else if eq .RequestType ""}} ctx, response, cliRespHeaders, err := w.Handle(ctx, req.Header) {{else if eq .ResponseType ""}} ctx, cliRespHeaders, err := w.Handle(ctx, req.Header, &requestBody) {{else}} ctx, response, cliRespHeaders, err := w.Handle(ctx, req.Header, &requestBody) // log downstream response to endpoint if ce := h.Dependencies.Default.ContextLogger.Check(zapcore.DebugLevel, "stub"); ce != nil { var zfields []zapcore.Field {{- if ne .ResponseType ""}} if body, err := json.Marshal(response); err == nil { zfields = append(zfields, zap.String("body", fmt.Sprintf("%s", body))) } {{- end}} if cliRespHeaders != nil { for _, k := range cliRespHeaders.Keys() { if val, ok := cliRespHeaders.Get(k); ok { zfields = append(zfields, zap.String(k, val)) } } } if traceKey, ok := req.Header.Get("{{$traceKey}}"); ok { zfields = append(zfields, zap.String("{{$traceKey}}", traceKey)) } ctx = h.Dependencies.Default.ContextLogger.DebugZ(ctx, "downstream service response", zfields...) } {{end -}} // map useful client response headers to server response if cliRespHeaders != nil { if val, ok := cliRespHeaders.Get(zanzibar.ClientResponseDurationKey); ok { if duration, err := time.ParseDuration(val); err == nil { res.DownstreamFinishTime = duration } cliRespHeaders.Unset(zanzibar.ClientResponseDurationKey) } if val, ok := cliRespHeaders.Get(zanzibar.ClientTypeKey); ok { res.ClientType = val cliRespHeaders.Unset(zanzibar.ClientTypeKey) } } if err != nil { {{- if eq (len .Exceptions) 0 -}} res.SendError(500, "Unexpected server error", err) return ctx {{ else }} {{$val := false}} {{range $idx, $exception := .Exceptions}} {{if not $exception.IsBodyDisallowed}} {{$val = true}} {{ end}} {{end}} {{ if $val -}} switch errValue := err.(type) { {{else -}} switch err.(type) { {{end -}} {{range $idx, $exception := .Exceptions}} case *{{$exception.Type}}: {{if $exception.IsBodyDisallowed -}} res.WriteJSONBytes({{$exception.StatusCode.Code}}, cliRespHeaders, nil) {{else -}} res.WriteJSON( {{$exception.StatusCode.Code}}, cliRespHeaders, errValue, ) {{end -}} return ctx {{end}} default: res.SendError(500, "Unexpected server error", err) return ctx } {{ end }} } {{if eq .ResponseType "" -}} res.WriteJSONBytes({{.OKStatusCode.Code}}, cliRespHeaders, nil) {{- else if eq .ResponseType "string" -}} bytes, err := json.Marshal(response) if err != nil { res.SendError(500, "Unexpected server error", errors.Wrap(err, "Unable to marshal resp json")) return ctx } res.WriteJSONBytes({{.OKStatusCode.Code}}, cliRespHeaders, bytes) {{- else -}} res.WriteJSON({{.OKStatusCode.Code}}, cliRespHeaders, response) {{- end }} return ctx } {{end -}} `) func endpointTmplBytes() ([]byte, error) { return _endpointTmpl, nil } func endpointTmpl() (*asset, error) { bytes, err := endpointTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "endpoint.tmpl", size: 7798, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _endpoint_collectionTmpl = []byte(`{{- $instance := .Instance -}} {{- $endpointMeta := .EndpointMeta -}} package {{$instance.PackageInfo.PackageName}} import ( module "{{$instance.PackageInfo.ModulePackagePath}}" zanzibar "github.com/uber/zanzibar/runtime" ) // Endpoint registers a request handler on a gateway type Endpoint interface{ Register(*zanzibar.Gateway) error } // NewEndpoint returns a collection of endpoints that can be registered on // a gateway func NewEndpoint(deps *module.Dependencies) Endpoint { return &EndpointHandlers{ {{- range $idx, $meta := $endpointMeta }} {{$serviceMethod := printf "%s%s" (title .Method.ThriftService) (title .Method.Name) -}} {{$handlerName := printf "%sHandler" $serviceMethod -}} {{$handlerName}}: New{{$handlerName}}(deps), {{- end}} } } // EndpointHandlers is a collection of individual endpoint handlers type EndpointHandlers struct { {{- range $idx, $meta := $endpointMeta }} {{$serviceMethod := printf "%s%s" (title $meta.Method.ThriftService) (title $meta.Method.Name) -}} {{$handlerName := printf "%sHandler" $serviceMethod -}} {{$handlerName}} *{{$handlerName}} {{- end}} } // Register registers the endpoint handlers with the gateway func (handlers *EndpointHandlers) Register(gateway *zanzibar.Gateway) error { {{- range $idx, $meta := $endpointMeta }} {{$serviceMethod := printf "%s%s" (title .Method.ThriftService) (title .Method.Name) -}} {{$handlerName := printf "%sHandler" $serviceMethod -}} err{{$idx}} := handlers.{{$handlerName}}.Register(gateway) if err{{$idx}} != nil { return err{{$idx}} } {{- end}} return nil } `) func endpoint_collectionTmplBytes() ([]byte, error) { return _endpoint_collectionTmpl, nil } func endpoint_collectionTmpl() (*asset, error) { bytes, err := endpoint_collectionTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "endpoint_collection.tmpl", size: 1591, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _endpoint_testTmpl = []byte(`{{/* template to render gateway http endpoint tests */ -}} {{- $instance := .Instance }} package {{$instance.PackageInfo.PackageName}} import ( "bytes" "path/filepath" "net/http" "testing" "runtime" "github.com/stretchr/testify/assert" "github.com/uber/zanzibar/test/lib/bench_gateway" testBackend "github.com/uber/zanzibar/test/lib/test_backend" testGateway "github.com/uber/zanzibar/test/lib/test_gateway" ) {{- $clientID := .ClientID }} {{- $relativePathToRoot := .RelativePathToRoot}} {{- $headers := .ReqHeaders }} {{with .Method -}} {{- $clientPackage := .Downstream.PackageName -}} {{- $clientMethod := .DownstreamMethod -}} {{- $clientMethodName := $clientMethod.Name | title -}} {{- $clientMethodRequestType := fullTypeName ($clientMethod).RequestType ($clientPackage) -}} {{- $clientMethodResponseType := fullTypeName ($clientMethod).ResponseType ($clientPackage) -}} {{range $testName, $testFixture := $.TestFixtures}} func getDirName{{$testFixture.HandleID | title}}{{$testFixture.TestName | title}}() string { _, file, _, _ := runtime.Caller(0) return filepath.Dir(file) } func Test{{$testFixture.HandleID | title}}{{$testFixture.TestName | title}}OKResponse(t *testing.T) { var counter int gateway, err := testGateway.CreateGateway(t, nil, &testGateway.Options{ KnownHTTPBackends: []string{"{{$clientID}}"}, TestBinary: filepath.Join( getDirName{{$testFixture.HandleID | title}}{{$testFixture.TestName | title}}(), "{{$relativePathToRoot}}", "build", "services", "{{$testFixture.TestServiceName}}", "main", "main.go", ), ConfigFiles: []string{ filepath.Join( getDirName{{$testFixture.HandleID | title}}{{$testFixture.TestName | title}}(), "{{$relativePathToRoot}}", "config", "test.yaml", ), filepath.Join( getDirName{{$testFixture.HandleID | title}}{{$testFixture.TestName | title}}(), "{{$relativePathToRoot}}", "config", "{{$testFixture.TestServiceName}}", "test.yaml", ), }, }) if !assert.NoError(t, err, "got bootstrap err") { return } defer gateway.Close() {{range $clientCallName, $clientCallFixture := $testFixture.ClientTestFixtures}} fake{{$clientCallFixture.ClientMethod | title}} := func(w http.ResponseWriter, r *http.Request) { {{range $k, $v := $clientCallFixture.ClientReqHeaders -}} assert.Equal( t, "{{$v}}", r.Header.Get("{{$k}}")) {{end}} {{range $k, $v := $clientCallFixture.ClientResHeaders -}} w.Header().Set("{{$k}}", "{{$v}}") {{end}} w.WriteHeader({{$.Method.OKStatusCode.Code}}) {{ if $clientCallFixture.ClientResponse.Body -}} payload := []byte({{printf "` + "`" + `%s` + "`" + `" $clientCallFixture.ClientResponse.Body}}) {{else}} var payload []byte {{- end}} // TODO(zw): generate client response. if _, err := w.Write(payload); err != nil { t.Fatal("can't write fake response") } counter++ } gateway.HTTPBackends()["{{$clientID}}"].HandleFunc( "{{$clientMethod.HTTPMethod}}", "{{$clientMethod.HTTPPath}}", fake{{$clientCallFixture.ClientMethod | title}}, ) {{end -}} headers := map[string]string{} {{ if $headers -}} {{range $k, $v := $testFixture.EndpointReqHeaders -}} headers["{{$k}}"] = "{{$v}}" {{end}} {{- end}} {{ if $testFixture.EndpointRequest.Body -}} endpointRequest := []byte({{printf "` + "`" + `%s` + "`" + `" $testFixture.EndpointRequest.Body}}) {{else}} endpointRequest := []byte(` + "`" + `{}` + "`" + `) {{- end}} res, err := gateway.MakeRequest( "{{$.Method.HTTPMethod}}", "{{$.Method.HTTPPath}}", headers, bytes.NewReader(endpointRequest), ) if !assert.NoError(t, err, "got http error") { return } assert.Equal(t, {{$.Method.OKStatusCode.Code}}, res.StatusCode) {{range $k, $v := $testFixture.EndpointResHeaders -}} assert.Equal( t, "{{$v}}", res.Header.Get("{{$k}}")) {{end}} assert.Equal(t, 1, counter) } {{end -}} {{end -}} `) func endpoint_testTmplBytes() ([]byte, error) { return _endpoint_testTmpl, nil } func endpoint_testTmpl() (*asset, error) { bytes, err := endpoint_testTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "endpoint_test.tmpl", size: 3820, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _endpoint_test_tchannel_clientTmpl = []byte(`{{/* template to render gateway http endpoint tests */}} {{- $instance := .Instance }} package {{$instance.PackageInfo.PackageName}} import ( "bytes" "context" {{if .Method.DownstreamMethod.ResponseType -}} "encoding/json" {{end -}} "path/filepath" "testing" "strconv" {{if ne .Method.ResponseType "" -}} "io/ioutil" {{end}} "github.com/stretchr/testify/assert" testGateway "github.com/uber/zanzibar/test/lib/test_gateway" "github.com/uber/zanzibar/test/lib/util" "github.com/uber/zanzibar/config" testbackend "github.com/uber/zanzibar/test/lib/test_backend" {{range $idx, $pkg := .IncludedPackages -}} {{$pkg.AliasName}} "{{$pkg.PackageName}}" {{end}} ) {{- $clientName := camel .ClientName -}} {{- $headers := .ReqHeaders }} {{with .Method -}} {{- $responseType := .ResponseType -}} {{- $clientPackage := .Downstream.PackageName -}} {{- $thriftService := .DownstreamMethod.ThriftService -}} {{- $clientMethod := .DownstreamMethod -}} {{- $clientMethodName := $clientMethod.Name -}} {{- $clientMethodRequestType := fullTypeName ($clientMethod).RequestType ($clientPackage) -}} {{- $clientMethodResponseType := fullTypeName ($clientMethod).ResponseType ($clientPackage) -}} {{range $testName, $testFixture := $.TestFixtures}} func Test{{title $testFixture.HandleID}}{{title $testFixture.TestName}}OKResponse(t *testing.T) { confFiles := util.DefaultConfigFiles("{{$testFixture.TestServiceName}}") staticConf := config.NewRuntimeConfigOrDie(confFiles, map[string]interface{}{}) var alternateServiceDetail config.AlternateServiceDetail if staticConf.ContainsKey("clients.{{$clientName}}.alternates") { staticConf.MustGetStruct("clients.{{$clientName}}.alternates", &alternateServiceDetail) } var backends []*testbackend.TestTChannelBackend for serviceName := range alternateServiceDetail.ServicesDetailMap { if serviceName == "nomatch" { continue } backend, err := testbackend.CreateTChannelBackend(int32(0), serviceName) assert.NoError(t, err) err = backend.Bootstrap() assert.NoError(t, err) backends = append(backends, backend) } gateway, err := testGateway.CreateGateway(t, map[string]interface{}{ {{/* the serviceName here is service discovery name, therefore is ok to be arbitrary */ -}} "clients.{{$clientName}}.serviceName": "{{$clientName}}Service", }, &testGateway.Options{ KnownTChannelBackends: []string{"{{$clientName}}"}, TestBinary: util.DefaultMainFile("{{$testFixture.TestServiceName}}"), ConfigFiles: confFiles, Backends: backends, }) if !assert.NoError(t, err, "got bootstrap err") { return } defer gateway.Close() {{range $clientCallName, $clientCallFixture := $testFixture.ClientTestFixtures}} {{$clientFunc := printf "fake%s" (title $clientCallFixture.ClientMethod) -}} {{$clientFunc}} := func( ctx context.Context, reqHeaders map[string]string, {{if $clientMethod.RequestType -}} args {{$clientMethodRequestType}}, {{end -}} ) ({{- if $clientMethod.ResponseType -}}{{$clientMethodResponseType}}, {{- end -}}map[string]string, error) { {{range $k, $v := $clientCallFixture.ClientReqHeaders -}} assert.Equal( t, "{{$v}}", reqHeaders["{{$k}}"]) {{end -}} var resHeaders map[string]string {{if (len $clientCallFixture.ClientResHeaders) -}} resHeaders = map[string]string{} {{end -}} {{range $k, $v := $clientCallFixture.ClientResHeaders -}} resHeaders["{{$k}}"] = "{{$v}}" {{end}} {{if $clientMethod.ResponseType -}} var res {{unref $clientMethod.ResponseType}} clientResponse := []byte({{printf "` + "`" + `%s` + "`" + `" $clientCallFixture.ClientResponse.Body}}) err := json.Unmarshal(clientResponse, &res) if err!= nil { t.Fatal("cant't unmarshal client response json to client response struct") return nil, resHeaders, err } return &res, resHeaders, nil {{else -}} return resHeaders, nil {{end -}} } headers := map[string]string{} err = gateway.TChannelBackends()["{{$clientName}}"].Register( "{{$testFixture.EndpointID}}", "{{$testFixture.HandleID}}", "{{$thriftService}}::{{$clientMethodName}}", {{$clientPackage}}.New{{$thriftService}}{{title $clientMethodName}}Handler({{$clientFunc}}), ) assert.NoError(t, err) makeRequestAndValidate{{title $testFixture.HandleID}}{{title $testFixture.TestName}}(t, gateway, headers) isSet := true i := 1 for serviceName := range alternateServiceDetail.ServicesDetailMap { headers := map[string]string{} if serviceName == "nomatch" { headers["x-container"] = "randomstr" headers["x-test-Env"] = "randomstr" } else { if isSet { headers["x-container"] = "sandbox" isSet = false } else { headers["x-test-Env"] = "test1" } err = gateway.TChannelBackends()["{{$clientName}}:"+strconv.Itoa(i)].Register( "{{$testFixture.EndpointID}}", "{{$testFixture.HandleID}}", "{{$thriftService}}::{{$clientMethodName}}", {{$clientPackage}}.New{{$thriftService}}{{title $clientMethodName}}Handler({{$clientFunc}}), ) assert.NoError(t, err) i++ } makeRequestAndValidate{{title $testFixture.HandleID}}{{title $testFixture.TestName}}(t, gateway, headers) } {{end}} } func makeRequestAndValidate{{title $testFixture.HandleID}}{{title $testFixture.TestName}}(t *testing.T, gateway testGateway.TestGateway, headers map[string]string) { {{ if $headers -}} {{range $k, $v := $testFixture.EndpointReqHeaders -}} headers["{{$k}}"] = "{{$v}}" {{end}} {{- end}} {{ if $testFixture.EndpointRequest.Body -}} endpointRequest := []byte({{printf "` + "`" + `%s` + "`" + `" $testFixture.EndpointRequest.Body}}) {{else}} endpointRequest := []byte(` + "`" + `{}` + "`" + `) {{- end}} res, err := gateway.MakeRequest( "{{$.Method.HTTPMethod}}", "{{$.Method.HTTPPath}}", headers, bytes.NewReader(endpointRequest), ) if !assert.NoError(t, err, "got http error") { return } {{if $responseType -}} defer func() { _ = res.Body.Close() }() data, err := io.ReadAll(res.Body) if !assert.NoError(t, err, "failed to read response body") { return } {{end}} assert.Equal(t, {{$.Method.OKStatusCode.Code}}, res.StatusCode) {{range $k, $v := $testFixture.EndpointResHeaders -}} assert.Equal( t, "{{$v}}", res.Header.Get("{{$k}}")) {{end -}} {{if $responseType -}} assert.JSONEq(t, ` + "`" + `{{$testFixture.EndpointResponse.Body}}` + "`" + `, string(data)) {{end -}} } {{end -}} {{end -}} `) func endpoint_test_tchannel_clientTmplBytes() ([]byte, error) { return _endpoint_test_tchannel_clientTmpl, nil } func endpoint_test_tchannel_clientTmpl() (*asset, error) { bytes, err := endpoint_test_tchannel_clientTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "endpoint_test_tchannel_client.tmpl", size: 6329, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _fixture_typesTmpl = []byte(`{{- /* template to render fixture type code for custom client */ -}} {{- $imports := .Imports}} {{- $methods := .Methods}} {{- $scenariosMap := .Fixture.Scenarios}} package clientmock {{if (len $imports)}} import ( {{- range $path, $alias := $imports}} {{$alias}} "{{$path}}" {{- end}} ) {{end}} // ClientFixture defines the client fixture type type ClientFixture struct { {{- range $method := $methods}} {{$methodName := $method.Name -}} {{$methodName}} *{{$methodName}}Scenarios {{- end -}} } {{range $method := $methods}} {{$methodName := $method.Name -}} {{$scenarios := index $scenariosMap $methodName -}} // {{$methodName}}Scenarios defines all fixture scenarios for {{$methodName}} type {{$methodName}}Scenarios struct { {{range $scenario := $scenarios -}} {{pascal $scenario}} *{{$methodName}}Fixture ` + "`" + `scenario:"{{$scenario}}"` + "`" + ` {{end -}} } {{end -}} {{- range $method := $methods}} {{$methodName := $method.Name -}} // {{$methodName}}Fixture defines the fixture type for {{$methodName}} type {{$methodName}}Fixture struct { {{- range $argName, $argType := $method.In}} {{title $argName}} {{$argType}} {{- end}} {{- if $method.Variadic}} {{title $method.Variadic}} []{{$method.VariadicType}} {{- end}} // Arg{n}Any indicates the nth argument could be gomock.Any {{- range $argName, $argType := $method.In}} {{title $argName}}Any bool {{- end}} {{- if $method.Variadic}} // {{title $method.Variadic}}Any indicates the variadic argument is a number of gomock.Any {{title $method.Variadic}}Any int {{- end}} {{range $retName, $retType := $method.Out}} {{title $retName}} {{$retType}} {{- end}} } {{- end}} `) func fixture_typesTmplBytes() ([]byte, error) { return _fixture_typesTmpl, nil } func fixture_typesTmpl() (*asset, error) { bytes, err := fixture_typesTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "fixture_types.tmpl", size: 1640, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _grpc_clientTmpl = []byte(`{{- /* template to render gateway gRPC client code */ -}} {{- $instance := .Instance }} {{- $services := .Services }} package {{$instance.PackageInfo.PackageName}} import ( "context" "github.com/afex/hystrix-go/hystrix" "go.uber.org/yarpc" module "{{$instance.PackageInfo.ModulePackagePath}}" {{range $idx, $pkg := .IncludedPackages -}} {{$pkg.AliasName}} "{{$pkg.PackageName}}" {{end}} zanzibar "github.com/uber/zanzibar/runtime" ) {{$clientID := $instance.InstanceName -}} {{$exposedMethods := .ExposedMethods -}} {{$QPSLevels := .QPSLevels -}} {{- $clientName := printf "%sClient" (camel $clientID) }} {{- $exportName := .ExportName}} // CircuitBreakerConfigKey is key value for qps level to circuit breaker parameters mapping const CircuitBreakerConfigKey = "circuitbreaking-configurations" // Client defines {{$clientID}} client interface. type Client interface { {{range $i, $svc := .ProtoServices -}} {{range $j, $method := $svc.RPC}} {{$serviceMethod := printf "%s::%s" $svc.Name .Name -}} {{$methodName := (title (index $exposedMethods $serviceMethod)) -}} {{- if $methodName -}} {{$methodName}} ( ctx context.Context, request *gen.{{$method.Request.Name}}, opts ...yarpc.CallOption, ) (context.Context, *gen.{{$method.Response.Name}}, error) {{ end -}} {{ end -}} {{ end -}} } // {{$clientName}} is the gRPC client for downstream service. type {{$clientName}} struct { {{range $i, $s := $services -}} {{camel $s.Name}}Client gen.{{pascal $s.Name}}YARPCClient {{ end -}} opts *zanzibar.GRPCClientOpts } // NewClient returns a new gRPC client for service {{$clientID}} func {{$exportName}}(deps *module.Dependencies) Client { oc := deps.Default.GRPCClientDispatcher.MustOutboundConfig("{{$clientID}}") var routingKey string if deps.Default.Config.ContainsKey("clients.{{$clientID}}.routingKey") { routingKey = deps.Default.Config.MustGetString("clients.{{$clientID}}.routingKey") } var requestUUIDHeaderKey string if deps.Default.Config.ContainsKey("clients.{{$clientID}}.requestUUIDHeaderKey") { requestUUIDHeaderKey = deps.Default.Config.MustGetString("clients.{{$clientID}}.requestUUIDHeaderKey") } timeoutInMS := int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.timeout")) methodNames := map[string]string{ {{range $i, $svc := .ProtoServices -}} {{range $j, $method := $svc.RPC -}} {{$serviceMethod := printf "%s::%s" $svc.Name .Name -}} {{$methodName := (title (index $exposedMethods $serviceMethod)) -}} {{- if $methodName -}} "{{$serviceMethod}}": "{{$methodName}}", {{ end -}} {{- end -}} {{- end}} } qpsLevels := map[string]string{ {{range $methodName, $qpsLevel := $QPSLevels -}} "{{$methodName}}": "{{$qpsLevel}}", {{end}} } // circuitBreakerDisabled sets whether circuit-breaker should be disabled circuitBreakerDisabled := false if deps.Default.Config.ContainsKey("clients.{{$clientID}}.circuitBreakerDisabled") { circuitBreakerDisabled = deps.Default.Config.MustGetBoolean("clients.{{$clientID}}.circuitBreakerDisabled") } if !circuitBreakerDisabled { for _, methodName := range methodNames { circuitBreakerName := "{{$clientID}}" + "-" + methodName qpsLevel := "default" if level, ok := qpsLevels[circuitBreakerName]; ok { qpsLevel = level } configureCircuitBreaker(deps, timeoutInMS, circuitBreakerName, qpsLevel) } } return &{{$clientName}}{ {{range $i, $s := $services -}} {{camel $s.Name}}Client: gen.New{{pascal $s.Name}}YARPCClient(oc), {{ end -}} opts: zanzibar.NewGRPCClientOpts( deps.Default.ContextLogger, deps.Default.ContextMetrics, deps.Default.ContextExtractor, methodNames, "{{$clientID}}", routingKey, requestUUIDHeaderKey, circuitBreakerDisabled, timeoutInMS, ), } } // CircuitBreakerConfig is used for storing the circuit breaker parameters for each qps level type CircuitBreakerConfig struct { Parameters map[string]map[string]int } func configureCircuitBreaker(deps *module.Dependencies, timeoutVal int, circuitBreakerName string, qpsLevel string) { // sleepWindowInMilliseconds sets the amount of time, after tripping the circuit, // to reject requests before allowing attempts again to determine if the circuit should again be closed sleepWindowInMilliseconds := 5000 // maxConcurrentRequests sets how many requests can be run at the same time, beyond which requests are rejected maxConcurrentRequests := 20 // errorPercentThreshold sets the error percentage at or above which the circuit should trip open errorPercentThreshold := 20 // requestVolumeThreshold sets a minimum number of requests that will trip the circuit in a rolling window of 10s // For example, if the value is 20, then if only 19 requests are received in the rolling window of 10 seconds // the circuit will not trip open even if all 19 failed. requestVolumeThreshold := 20 // parses circuit breaker configurations if deps.Default.Config.ContainsKey(CircuitBreakerConfigKey) { var config CircuitBreakerConfig deps.Default.Config.MustGetStruct(CircuitBreakerConfigKey, &config) parameters := config.Parameters // first checks if level exists in configurations then assigns parameters // if "default" qps level assigns default parameters from circuit breaker configurations if settings, ok := parameters[qpsLevel]; ok { if sleep, ok := settings["sleepWindowInMilliseconds"]; ok { sleepWindowInMilliseconds = sleep } if max, ok := settings["maxConcurrentRequests"]; ok { maxConcurrentRequests = max } if errorPercent, ok := settings["errorPercentThreshold"]; ok { errorPercentThreshold = errorPercent } if reqVolThreshold, ok := settings["requestVolumeThreshold"]; ok { requestVolumeThreshold = reqVolThreshold } } } // client settings override parameters if deps.Default.Config.ContainsKey("clients.{{$clientID}}.sleepWindowInMilliseconds") { sleepWindowInMilliseconds = int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.sleepWindowInMilliseconds")) } if deps.Default.Config.ContainsKey("clients.{{$clientID}}.maxConcurrentRequests") { maxConcurrentRequests = int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.maxConcurrentRequests")) } if deps.Default.Config.ContainsKey("clients.{{$clientID}}.errorPercentThreshold") { errorPercentThreshold = int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.errorPercentThreshold")) } if deps.Default.Config.ContainsKey("clients.{{$clientID}}.requestVolumeThreshold") { requestVolumeThreshold = int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.requestVolumeThreshold")) } hystrix.ConfigureCommand(circuitBreakerName, hystrix.CommandConfig{ MaxConcurrentRequests: maxConcurrentRequests, ErrorPercentThreshold: errorPercentThreshold, SleepWindow: sleepWindowInMilliseconds, RequestVolumeThreshold: requestVolumeThreshold, Timeout: timeoutVal, }) } {{range $i, $svc := .ProtoServices -}} {{range $j, $method := $svc.RPC -}} {{$serviceMethod := printf "%s::%s" $svc.Name .Name -}} {{$methodName := (title (index $exposedMethods $serviceMethod)) -}} {{if $methodName -}} // {{$methodName}} is a client RPC call for method {{printf "%s::%s" $svc.Name $method.Name}}. func (e *{{$clientName}}) {{$methodName}}( ctx context.Context, request *gen.{{$method.Request.Name}}, opts ...yarpc.CallOption, ) (context.Context, *gen.{{$method.Response.Name}}, error) { var result *gen.{{$method.Response.Name}} var err error ctx, callHelper := zanzibar.NewGRPCClientCallHelper(ctx, "{{printf "%s::%s" $svc.Name $method.Name}}", e.opts) if e.opts.RoutingKey != "" { opts = append(opts, yarpc.WithRoutingKey(e.opts.RoutingKey)) } if e.opts.RequestUUIDHeaderKey != "" { reqUUID := zanzibar.RequestUUIDFromCtx(ctx) if reqUUID != "" { opts = append(opts, yarpc.WithHeader(e.opts.RequestUUIDHeaderKey, reqUUID)) } } // Creating a new child context with timeout for the yarpc call as this gets cancelled as soon as call is returned // from this client or deadline exceeded after timeout ctxWithTimeout, cancel := context.WithTimeout(ctx, e.opts.Timeout) defer cancel() runFunc := e.{{camel $svc.Name}}Client.{{$method.Name}} callHelper.Start() if e.opts.CircuitBreakerDisabled { result, err = runFunc(ctxWithTimeout, request, opts...) } else { circuitBreakerName := "{{$clientID}}" + "-" + "{{$methodName}}" err = hystrix.DoC(ctxWithTimeout, circuitBreakerName, func(ctx context.Context) error { result, err = runFunc(ctx, request, opts...) return err }, nil) } callHelper.Finish(ctx, err) return ctx, result, err } {{end -}} {{end -}} {{end}} `) func grpc_clientTmplBytes() ([]byte, error) { return _grpc_clientTmpl, nil } func grpc_clientTmpl() (*asset, error) { bytes, err := grpc_clientTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "grpc_client.tmpl", size: 8644, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _http_clientTmpl = []byte(`{{- /* template to render edge gateway http client code */ -}} {{- $instance := .Instance }} package {{$instance.PackageInfo.PackageName}} import ( "context" "fmt" "net/textproto" "github.com/afex/hystrix-go/hystrix" "strconv" "time" "github.com/pkg/errors" "github.com/uber/zanzibar/config" zanzibar "github.com/uber/zanzibar/runtime" "github.com/uber/zanzibar/runtime/jsonwrapper" module "{{$instance.PackageInfo.ModulePackagePath}}" {{range $idx, $pkg := .IncludedPackages -}} {{$pkg.AliasName}} "{{$pkg.PackageName}}" {{end}} ) {{- $clientID := .ClientID -}} {{$exposedMethods := .ExposedMethods -}} {{$QPSLevels := .QPSLevels -}} {{- $clientName := printf "%sClient" (camel $clientID) }} {{- $exportName := .ExportName}} {{- $sidecarRouter := .SidecarRouter}} // CircuitBreakerConfigKey is key value for qps level to circuit breaker parameters mapping const CircuitBreakerConfigKey = "circuitbreaking-configurations" // Client defines {{$clientID}} client interface. type Client interface { HTTPClient() *zanzibar.HTTPClient {{- range $svc := .Services -}} {{range .Methods}} {{$serviceMethod := printf "%s::%s" $svc.Name .Name -}} {{$methodName := (title (index $exposedMethods $serviceMethod)) -}} {{- if $methodName -}} {{$methodName}}( ctx context.Context, reqHeaders map[string]string, {{if ne .RequestType "" -}} args {{.RequestType}}, {{end -}} ) (context.Context, {{- if ne .ResponseType "" -}} {{.ResponseType}}, {{- end -}}map[string]string, error) {{- end -}} {{- end -}} {{- end -}} } // {{$clientName}} is the http client. type {{$clientName}} struct { clientID string httpClient *zanzibar.HTTPClient jsonWrapper jsonwrapper.JSONWrapper circuitBreakerDisabled bool requestUUIDHeaderKey string requestProcedureHeaderKey string {{if $sidecarRouter -}} calleeHeader string callerHeader string callerName string calleeName string altRoutingMap map[string]map[string]string {{end -}} } // {{$exportName}} returns a new http client. func {{$exportName}}(deps *module.Dependencies) Client { {{if $sidecarRouter -}} ip := deps.Default.Config.MustGetString("sidecarRouter.{{$sidecarRouter}}.http.ip") port := deps.Default.Config.MustGetInt("sidecarRouter.{{$sidecarRouter}}.http.port") callerHeader := deps.Default.Config.MustGetString("sidecarRouter.{{$sidecarRouter}}.http.callerHeader") calleeHeader := deps.Default.Config.MustGetString("sidecarRouter.{{$sidecarRouter}}.http.calleeHeader") callerName := deps.Default.Config.MustGetString("serviceName") calleeName := deps.Default.Config.MustGetString("clients.{{$clientID}}.serviceName") var altServiceDetail = config.AlternateServiceDetail{} if deps.Default.Config.ContainsKey("clients.{{$clientID}}.alternates") { deps.Default.Config.MustGetStruct("clients.{{$clientID}}.alternates", &altServiceDetail) } {{else -}} ip := deps.Default.Config.MustGetString("clients.{{$clientID}}.ip") port := deps.Default.Config.MustGetInt("clients.{{$clientID}}.port") {{end -}} baseURL := fmt.Sprintf("http://%s:%d", ip, port) timeoutVal := int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.timeout")) timeout := time.Millisecond * time.Duration( timeoutVal, ) defaultHeaders := make(map[string]string) if deps.Default.Config.ContainsKey("http.defaultHeaders") { deps.Default.Config.MustGetStruct("http.defaultHeaders", &defaultHeaders) } if deps.Default.Config.ContainsKey("clients.{{$clientID}}.defaultHeaders") { deps.Default.Config.MustGetStruct("clients.{{$clientID}}.defaultHeaders", &defaultHeaders) } var requestUUIDHeaderKey string if deps.Default.Config.ContainsKey("http.clients.requestUUIDHeaderKey") { requestUUIDHeaderKey = deps.Default.Config.MustGetString("http.clients.requestUUIDHeaderKey") } var requestProcedureHeaderKey string if deps.Default.Config.ContainsKey("http.clients.requestProcedureHeaderKey"){ requestProcedureHeaderKey = deps.Default.Config.MustGetString("http.clients.requestProcedureHeaderKey") } followRedirect := true if deps.Default.Config.ContainsKey("clients.{{$clientID}}.followRedirect") { followRedirect = deps.Default.Config.MustGetBoolean("clients.{{$clientID}}.followRedirect") } methodNames := map[string]string{ {{range $serviceMethod, $methodName := $exposedMethods -}} "{{$methodName}}": "{{$serviceMethod}}", {{end}} } // circuitBreakerDisabled sets whether circuit-breaker should be disabled circuitBreakerDisabled := false if deps.Default.Config.ContainsKey("clients.{{$clientID}}.circuitBreakerDisabled") { circuitBreakerDisabled = deps.Default.Config.MustGetBoolean("clients.{{$clientID}}.circuitBreakerDisabled") } //get mapping of client method and it's timeout //if mapping is not provided, use client's timeout for all methods clientMethodTimeoutMapping := make(map[string]int64) if deps.Default.Config.ContainsKey("clients.{{$clientID}}.methodTimeoutMapping") { deps.Default.Config.MustGetStruct("clients.{{$clientID}}.methodTimeoutMapping", &clientMethodTimeoutMapping) } else { //override the client overall-timeout with the client's method level timeout for methodName := range methodNames { clientMethodTimeoutMapping[methodName] = int64(timeoutVal) } } qpsLevels := map[string]string{ {{range $methodName, $qpsLevel := $QPSLevels -}} "{{$methodName}}": "{{$qpsLevel}}", {{end}} } if !circuitBreakerDisabled { for methodName, methodTimeout := range clientMethodTimeoutMapping{ circuitBreakerName := "{{$clientID}}" + "-" + methodName qpsLevel := "default" if level, ok := qpsLevels[circuitBreakerName]; ok { qpsLevel = level } configureCircuitBreaker(deps, int(methodTimeout), circuitBreakerName, qpsLevel) } } return &{{$clientName}}{ clientID: "{{$clientID}}", {{if $sidecarRouter -}} callerHeader: callerHeader, calleeHeader: calleeHeader, callerName: callerName, calleeName: calleeName, altRoutingMap: initializeAltRoutingMap(altServiceDetail), {{end -}} httpClient: zanzibar.NewHTTPClientContext( deps.Default.ContextLogger, deps.Default.ContextMetrics, deps.Default.JSONWrapper, "{{$clientID}}", methodNames, baseURL, defaultHeaders, timeout, followRedirect, ), circuitBreakerDisabled: circuitBreakerDisabled, requestUUIDHeaderKey: requestUUIDHeaderKey, requestProcedureHeaderKey: requestProcedureHeaderKey, } } {{if $sidecarRouter -}} func initializeAltRoutingMap(altServiceDetail config.AlternateServiceDetail) map[string]map[string]string { // The goal is to support for each header key, multiple values that point to different services routingMap := make(map[string]map[string]string) for _, alt := range altServiceDetail.RoutingConfigs { if headerValueToServiceMap, ok := routingMap[textproto.CanonicalMIMEHeaderKey(alt.HeaderName)]; ok { headerValueToServiceMap[alt.HeaderValue] = alt.ServiceName } else { routingMap[textproto.CanonicalMIMEHeaderKey(alt.HeaderName)] = map[string]string{alt.HeaderValue:alt.ServiceName} } } return routingMap } {{end -}} // CircuitBreakerConfig is used for storing the circuit breaker parameters for each qps level type CircuitBreakerConfig struct { Parameters map[string]map[string]int } func configureCircuitBreaker(deps *module.Dependencies, timeoutVal int, circuitBreakerName string, qpsLevel string) { // sleepWindowInMilliseconds sets the amount of time, after tripping the circuit, // to reject requests before allowing attempts again to determine if the circuit should again be closed sleepWindowInMilliseconds := 5000 // maxConcurrentRequests sets how many requests can be run at the same time, beyond which requests are rejected maxConcurrentRequests := 20 // errorPercentThreshold sets the error percentage at or above which the circuit should trip open errorPercentThreshold := 20 // requestVolumeThreshold sets a minimum number of requests that will trip the circuit in a rolling window of 10s // For example, if the value is 20, then if only 19 requests are received in the rolling window of 10 seconds // the circuit will not trip open even if all 19 failed. requestVolumeThreshold := 20 // parses circuit breaker configurations if deps.Default.Config.ContainsKey(CircuitBreakerConfigKey) { var config CircuitBreakerConfig deps.Default.Config.MustGetStruct(CircuitBreakerConfigKey, &config) parameters := config.Parameters // first checks if level exists in configurations then assigns parameters // if "default" qps level assigns default parameters from circuit breaker configurations if settings, ok := parameters[qpsLevel]; ok { if sleep, ok := settings["sleepWindowInMilliseconds"]; ok { sleepWindowInMilliseconds = sleep } if max, ok := settings["maxConcurrentRequests"]; ok { maxConcurrentRequests = max } if errorPercent, ok := settings["errorPercentThreshold"]; ok { errorPercentThreshold = errorPercent } if reqVolThreshold, ok := settings["requestVolumeThreshold"]; ok { requestVolumeThreshold = reqVolThreshold } } } // client settings override parameters if deps.Default.Config.ContainsKey("clients.{{$clientID}}.sleepWindowInMilliseconds") { sleepWindowInMilliseconds = int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.sleepWindowInMilliseconds")) } if deps.Default.Config.ContainsKey("clients.{{$clientID}}.maxConcurrentRequests") { maxConcurrentRequests = int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.maxConcurrentRequests")) } if deps.Default.Config.ContainsKey("clients.{{$clientID}}.errorPercentThreshold") { errorPercentThreshold = int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.errorPercentThreshold")) } if deps.Default.Config.ContainsKey("clients.{{$clientID}}.requestVolumeThreshold") { requestVolumeThreshold = int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.requestVolumeThreshold")) } hystrix.ConfigureCommand(circuitBreakerName, hystrix.CommandConfig{ MaxConcurrentRequests: maxConcurrentRequests, ErrorPercentThreshold: errorPercentThreshold, SleepWindow: sleepWindowInMilliseconds, RequestVolumeThreshold: requestVolumeThreshold, Timeout: timeoutVal, }) } // HTTPClient returns the underlying HTTP client, should only be // used for internal testing. func (c *{{$clientName}}) HTTPClient() *zanzibar.HTTPClient { return c.httpClient } {{range $svc := .Services}} {{range .Methods}} {{$serviceMethod := printf "%s::%s" $svc.Name .Name -}} {{$methodName := (title (index $exposedMethods $serviceMethod)) -}} {{if $methodName -}} // {{$methodName}} calls "{{.HTTPPath}}" endpoint. func (c *{{$clientName}}) {{$methodName}}( ctx context.Context, headers map[string]string, {{if ne .RequestType "" -}} r {{.RequestType}}, {{end -}} ) (context.Context, {{- if ne .ResponseType "" -}} {{.ResponseType}}, {{- end -}}map[string]string, error) { reqUUID := zanzibar.RequestUUIDFromCtx(ctx) if headers == nil { headers = make(map[string]string) } if reqUUID != "" { headers[c.requestUUIDHeaderKey] = reqUUID } if c.requestProcedureHeaderKey != "" { headers[c.requestProcedureHeaderKey] = "{{$serviceMethod}}" } {{if .ResponseType -}} var defaultRes {{.ResponseType}} {{end -}} req := zanzibar.NewClientHTTPRequest(ctx, c.clientID, "{{$methodName}}", "{{$serviceMethod}}", c.httpClient) {{if .ReqHeaderGoStatements }} {{range $index, $line := .ReqClientHeaderGoStatements -}} {{$line}} {{end -}} {{- end}} {{if $sidecarRouter -}} headers[c.callerHeader] = c.callerName // Set the service name if dynamic routing header is present for routeHeaderKey, routeMap := range c.altRoutingMap { if headerVal, ok := headers[routeHeaderKey]; ok { for routeRegex, altServiceName := range routeMap { //if headerVal matches routeRegex regex, set the alternative service name if matchFound, _ := regexp.MatchString(routeRegex, headerVal); matchFound { headers[c.calleeHeader] = altServiceName break } } } } // If serviceName was not set in the dynamic routing section above, set as the default if _, ok := headers[c.calleeHeader]; !ok { headers[c.calleeHeader] = c.calleeName } {{end}} // Generate full URL. fullURL := c.httpClient.BaseURL {{- range $k, $segment := .PathSegments -}} {{- if eq $segment.Type "static" -}}+"/{{$segment.Text}}" {{- else -}}+"/"+string({{- if not $segment.Required }} * {{- end -}}r{{$segment.BodyIdentifier | title}}) {{- end -}} {{- end}} {{range $index, $line := .WriteQueryParamGoStatements -}} {{$line}} {{end}} {{if (and (ne .RequestType "") (ne .HTTPMethod "GET"))}} {{if and (.RequestBoxed) (eq .BoxedRequestType "[]byte")}} err := req.WriteBytes("{{.HTTPMethod}}", fullURL, headers, r.{{.BoxedRequestName}}) {{else}} err := req.WriteJSON("{{.HTTPMethod}}", fullURL, headers, {{if .RequestBoxed -}}r.{{.BoxedRequestName}}{{- else -}}r{{- end -}}) {{end -}} {{else}} err := req.WriteJSON("{{.HTTPMethod}}", fullURL, headers, nil) {{end}} {{- /* <if .RequestType ne ""> */ -}} if err != nil { return {{if eq .ResponseType ""}}ctx, nil, err{{else}}ctx, defaultRes, nil, err{{end}} } {{if .ReqHeaders }} headerErr := req.CheckHeaders({{.ReqHeaders | printf "%#v"}}) if headerErr != nil { return {{ if eq .ResponseType "" -}} ctx, nil, headerErr {{- else -}} ctx, defaultRes, nil, headerErr {{- end}} } {{- end}} var res *zanzibar.ClientHTTPResponse if (c.circuitBreakerDisabled) { res, err = req.Do() } else { // We want hystrix ckt-breaker to count errors only for system issues var clientErr error circuitBreakerName := "{{$clientID}}" + "-" + "{{$methodName}}" err = hystrix.DoC(ctx, circuitBreakerName, func(ctx context.Context) error { res, clientErr = req.Do() if res != nil { // This is not a system error/issue. Downstream responded return nil } return clientErr }, nil) if err == nil { // ckt-breaker was ok, bubble up client error if set err = clientErr } } if err != nil { return ctx, {{if eq .ResponseType ""}}nil, err{{else}}defaultRes, nil, err{{end}} } respHeaders := make(map[string]string) for k := range res.Header { respHeaders[k] = res.Header.Get(k) } defer func() { respHeaders[zanzibar.ClientResponseDurationKey] = res.Duration.String() }() {{- if .ResHeaders }} // TODO(jakev): verify mandatory response headers {{- end}} res.CheckOKResponse([]int{ {{- range $index, $code := .ValidStatusCodes -}} {{if $index}},{{end}}{{$code}} {{- end -}} }) {{if and (eq .ResponseType "") (eq (len .Exceptions) 0)}} switch res.StatusCode { case {{.OKStatusCode.Code}}: {{- if and (ne (.OKStatusCode.Code) 204) (ne (.OKStatusCode.Code) 304) -}} _, err = res.ReadAll() if err != nil { return ctx, respHeaders, err } {{- end}} return ctx, respHeaders, nil default: _, err = res.ReadAll() if err != nil { return ctx, respHeaders, err } } {{else if eq (len .Exceptions) 0}} switch res.StatusCode { case {{.OKStatusCode.Code}}: {{- if or (eq (.OKStatusCode.Code) 204) (eq (.OKStatusCode.Code) 304) -}} return ctx, {{if isPointerType .ResponseType}}&{{end}}{{unref .ResponseType}}{}, respHeaders, nil {{- else }} {{- if eq .ResponseType "[]byte"}} responseBody, err := res.ReadAll() if err != nil { return ctx, defaultRes, respHeaders, err } return ctx, responseBody, respHeaders, nil {{ else }} var responseBody {{unref .ResponseType}} rawBody, err := res.ReadAll() if err != nil { return ctx, defaultRes, respHeaders, err } err = res.UnmarshalBody(&responseBody, rawBody) if err != nil { return ctx, defaultRes, respHeaders, err } {{- if .ResHeaderFields }} // TODO(jakev): read response headers and put them in body {{- end}} return ctx, {{if isPointerType .ResponseType}}&{{end}}responseBody, respHeaders, nil {{end -}} {{end -}} default: _, err = res.ReadAll() if err != nil { return ctx, defaultRes, respHeaders, err } } {{else if eq .ResponseType ""}} switch res.StatusCode { case {{.OKStatusCode.Code}}: {{- if and (ne (.OKStatusCode.Code) 204) (ne (.OKStatusCode.Code) 304) -}} _, err = res.ReadAll() if err != nil { return ctx, respHeaders, err } {{- end}} return ctx, respHeaders, nil {{range $code, $exceptions := .ExceptionsByStatusCode -}} case {{$code}}: {{- if or (eq $code 204) (eq $code 304) }} {{/* If multiple exceptions have 204/304 status code mapped, we aren't able to distinguish between them */}} {{/* so we'll just return the first exception that has 204/304 status code set. */}} {{$val := index $exceptions 0}} return ctx, respHeaders, &{{$val.Type}}{} {{ else if and (eq (len $exceptions) 1) (eq (index $exceptions 0).IsBodyDisallowed true) -}} {{$val := index $exceptions 0}} return ctx, respHeaders, &{{$val.Type}}{} {{else}} allOptions := []interface{}{ {{range $idx, $exception := $exceptions -}} &{{$exception.Type}}{}, {{- end}} } v, err := res.ReadAndUnmarshalBodyMultipleOptions(allOptions) if err != nil { return ctx, respHeaders, err } return ctx, respHeaders, v.(error) {{end}} {{- end}} default: _, err = res.ReadAll() if err != nil { return ctx, respHeaders, err } } {{else}} switch res.StatusCode { case {{.OKStatusCode.Code}}: {{- if or (eq (.OKStatusCode.Code) 204) (eq (.OKStatusCode.Code) 304) }} return ctx, {{if isPointerType .ResponseType}}&{{end}}{{unref .ResponseType}}{}, respHeaders, nil {{- else }} {{- if eq .ResponseType "[]byte"}} responseBody, err := res.ReadAll() if err != nil { return ctx, defaultRes, respHeaders, err } return ctx, responseBody, respHeaders, nil {{ else }} var responseBody {{unref .ResponseType}} rawBody, err := res.ReadAll() if err != nil { return ctx, defaultRes, respHeaders, err } err = res.UnmarshalBody(&responseBody, rawBody) if err != nil { return ctx, defaultRes, respHeaders, err } {{- if .ResHeaderFields }} // TODO(jakev): read response headers and put them in body {{- end}} return ctx, {{if isPointerType .ResponseType}}&{{end}}responseBody, respHeaders, nil {{end -}} {{end}} {{range $code, $exceptions := .ExceptionsByStatusCode -}} case {{$code}}: {{- if or (eq $code 204) (eq $code 304) }} {{/* If multiple exceptions have 204/304 status code mapped, we aren't able to distinguish between them */}} {{/* so we'll just return the first exception that has 204/304 status code set. */}} {{$val := index $exceptions 0}} return ctx, defaultRes, respHeaders, &{{$val.Type}}{} {{ else if and (eq (len $exceptions) 1) (eq (index $exceptions 0).IsBodyDisallowed true) -}} {{$val := index $exceptions 0}} return ctx, defaultRes, respHeaders, &{{$val.Type}}{} {{else}} allOptions := []interface{}{ {{range $idx, $exception := $exceptions -}} &{{$exception.Type}}{}, {{- end}} } v, err := res.ReadAndUnmarshalBodyMultipleOptions(allOptions) if err != nil { return ctx, defaultRes, respHeaders, err } return ctx, defaultRes, respHeaders, v.(error) {{end}} {{- end}} default: _, err = res.ReadAll() if err != nil { return ctx, defaultRes, respHeaders, err } } {{end}} return ctx, {{if ne .ResponseType ""}}defaultRes, {{end}}respHeaders, &zanzibar.UnexpectedHTTPError{ StatusCode: res.StatusCode, RawBody: res.GetRawBody(), } } {{end}} {{end}} {{end}} `) func http_clientTmplBytes() ([]byte, error) { return _http_clientTmpl, nil } func http_clientTmpl() (*asset, error) { bytes, err := http_clientTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "http_client.tmpl", size: 19491, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _http_client_testTmpl = []byte(`{{- /* template to render edge gateway http client code */ -}} {{- $instance := .Instance }} package {{$instance.PackageInfo.PackageName}} import ( "context" "fmt" "net/textproto" "github.com/afex/hystrix-go/hystrix" "strconv" "time" "github.com/pkg/errors" "github.com/uber/zanzibar/config" zanzibar "github.com/uber/zanzibar/runtime" "github.com/uber/zanzibar/runtime/jsonwrapper" module "{{$instance.PackageInfo.ModulePackagePath}}" {{range $idx, $pkg := .IncludedPackages -}} {{$pkg.AliasName}} "{{$pkg.PackageName}}" {{end}} ) {{- $clientID := .ClientID -}} {{$exposedMethods := .ExposedMethods -}} {{$QPSLevels := .QPSLevels -}} {{- $clientName := printf "%sClient" (camel $clientID) }} {{- $exportName := .ExportName}} {{- $sidecarRouter := .SidecarRouter}} // CustomTemplateTesting generated for testing of custom template feature const CustomTemplateTesting = "test" // CircuitBreakerConfigKey is key value for qps level to circuit breaker parameters mapping const CircuitBreakerConfigKey = "circuitbreaking-configurations" // Client defines {{$clientID}} client interface. type Client interface { HTTPClient() *zanzibar.HTTPClient {{- range $svc := .Services -}} {{range .Methods}} {{$serviceMethod := printf "%s::%s" $svc.Name .Name -}} {{$methodName := (title (index $exposedMethods $serviceMethod)) -}} {{- if $methodName -}} {{$methodName}}( ctx context.Context, reqHeaders map[string]string, {{if ne .RequestType "" -}} args {{.RequestType}}, {{end -}} ) (context.Context, {{- if ne .ResponseType "" -}} {{.ResponseType}}, {{- end -}}map[string]string, error) {{- end -}} {{- end -}} {{- end -}} } // {{$clientName}} is the http client. type {{$clientName}} struct { clientID string httpClient *zanzibar.HTTPClient jsonWrapper jsonwrapper.JSONWrapper circuitBreakerDisabled bool requestUUIDHeaderKey string requestProcedureHeaderKey string {{if $sidecarRouter -}} calleeHeader string callerHeader string callerName string calleeName string altRoutingMap map[string]map[string]string {{end -}} } // {{$exportName}} returns a new http client. func {{$exportName}}(deps *module.Dependencies) Client { {{if $sidecarRouter -}} ip := deps.Default.Config.MustGetString("sidecarRouter.{{$sidecarRouter}}.http.ip") port := deps.Default.Config.MustGetInt("sidecarRouter.{{$sidecarRouter}}.http.port") callerHeader := deps.Default.Config.MustGetString("sidecarRouter.{{$sidecarRouter}}.http.callerHeader") calleeHeader := deps.Default.Config.MustGetString("sidecarRouter.{{$sidecarRouter}}.http.calleeHeader") callerName := deps.Default.Config.MustGetString("serviceName") calleeName := deps.Default.Config.MustGetString("clients.{{$clientID}}.serviceName") var altServiceDetail = config.AlternateServiceDetail{} if deps.Default.Config.ContainsKey("clients.{{$clientID}}.alternates") { deps.Default.Config.MustGetStruct("clients.{{$clientID}}.alternates", &altServiceDetail) } {{else -}} ip := deps.Default.Config.MustGetString("clients.{{$clientID}}.ip") port := deps.Default.Config.MustGetInt("clients.{{$clientID}}.port") {{end -}} baseURL := fmt.Sprintf("http://%s:%d", ip, port) timeoutVal := int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.timeout")) timeout := time.Millisecond * time.Duration( timeoutVal, ) defaultHeaders := make(map[string]string) if deps.Default.Config.ContainsKey("http.defaultHeaders") { deps.Default.Config.MustGetStruct("http.defaultHeaders", &defaultHeaders) } if deps.Default.Config.ContainsKey("clients.{{$clientID}}.defaultHeaders") { deps.Default.Config.MustGetStruct("clients.{{$clientID}}.defaultHeaders", &defaultHeaders) } var requestUUIDHeaderKey string if deps.Default.Config.ContainsKey("http.clients.requestUUIDHeaderKey") { requestUUIDHeaderKey = deps.Default.Config.MustGetString("http.clients.requestUUIDHeaderKey") } var requestProcedureHeaderKey string if deps.Default.Config.ContainsKey("http.clients.requestProcedureHeaderKey"){ requestProcedureHeaderKey = deps.Default.Config.MustGetString("http.clients.requestProcedureHeaderKey") } followRedirect := true if deps.Default.Config.ContainsKey("clients.{{$clientID}}.followRedirect") { followRedirect = deps.Default.Config.MustGetBoolean("clients.{{$clientID}}.followRedirect") } methodNames := map[string]string{ {{range $serviceMethod, $methodName := $exposedMethods -}} "{{$methodName}}": "{{$serviceMethod}}", {{end}} } // circuitBreakerDisabled sets whether circuit-breaker should be disabled circuitBreakerDisabled := false if deps.Default.Config.ContainsKey("clients.{{$clientID}}.circuitBreakerDisabled") { circuitBreakerDisabled = deps.Default.Config.MustGetBoolean("clients.{{$clientID}}.circuitBreakerDisabled") } //get mapping of client method and it's timeout //if mapping is not provided, use client's timeout for all methods clientMethodTimeoutMapping := make(map[string]int64) if deps.Default.Config.ContainsKey("clients.{{$clientID}}.methodTimeoutMapping") { deps.Default.Config.MustGetStruct("clients.{{$clientID}}.methodTimeoutMapping", &clientMethodTimeoutMapping) } else { //override the client overall-timeout with the client's method level timeout for methodName := range methodNames { clientMethodTimeoutMapping[methodName] = int64(timeoutVal) } } qpsLevels := map[string]string{ {{range $methodName, $qpsLevel := $QPSLevels -}} "{{$methodName}}": "{{$qpsLevel}}", {{end}} } if !circuitBreakerDisabled { for methodName, methodTimeout := range clientMethodTimeoutMapping{ circuitBreakerName := "{{$clientID}}" + "-" + methodName qpsLevel := "default" if level, ok := qpsLevels[circuitBreakerName]; ok { qpsLevel = level } configureCircuitBreaker(deps, int(methodTimeout), circuitBreakerName, qpsLevel) } } return &{{$clientName}}{ clientID: "{{$clientID}}", {{if $sidecarRouter -}} callerHeader: callerHeader, calleeHeader: calleeHeader, callerName: callerName, calleeName: calleeName, altRoutingMap: initializeAltRoutingMap(altServiceDetail), {{end -}} httpClient: zanzibar.NewHTTPClientContext( deps.Default.ContextLogger, deps.Default.ContextMetrics, deps.Default.JSONWrapper, "{{$clientID}}", methodNames, baseURL, defaultHeaders, timeout, followRedirect, ), circuitBreakerDisabled: circuitBreakerDisabled, requestUUIDHeaderKey: requestUUIDHeaderKey, requestProcedureHeaderKey: requestProcedureHeaderKey, } } {{if $sidecarRouter -}} func initializeAltRoutingMap(altServiceDetail config.AlternateServiceDetail) map[string]map[string]string { // The goal is to support for each header key, multiple values that point to different services routingMap := make(map[string]map[string]string) for _, alt := range altServiceDetail.RoutingConfigs { if headerValueToServiceMap, ok := routingMap[textproto.CanonicalMIMEHeaderKey(alt.HeaderName)]; ok { headerValueToServiceMap[alt.HeaderValue] = alt.ServiceName } else { routingMap[textproto.CanonicalMIMEHeaderKey(alt.HeaderName)] = map[string]string{alt.HeaderValue:alt.ServiceName} } } return routingMap } {{end -}} // CircuitBreakerConfig is used for storing the circuit breaker parameters for each qps level type CircuitBreakerConfig struct { Parameters map[string]map[string]int } func configureCircuitBreaker(deps *module.Dependencies, timeoutVal int, circuitBreakerName string, qpsLevel string) { // sleepWindowInMilliseconds sets the amount of time, after tripping the circuit, // to reject requests before allowing attempts again to determine if the circuit should again be closed sleepWindowInMilliseconds := 5000 // maxConcurrentRequests sets how many requests can be run at the same time, beyond which requests are rejected maxConcurrentRequests := 20 // errorPercentThreshold sets the error percentage at or above which the circuit should trip open errorPercentThreshold := 20 // requestVolumeThreshold sets a minimum number of requests that will trip the circuit in a rolling window of 10s // For example, if the value is 20, then if only 19 requests are received in the rolling window of 10 seconds // the circuit will not trip open even if all 19 failed. requestVolumeThreshold := 20 // parses circuit breaker configurations if deps.Default.Config.ContainsKey(CircuitBreakerConfigKey) { var config CircuitBreakerConfig deps.Default.Config.MustGetStruct(CircuitBreakerConfigKey, &config) parameters := config.Parameters // first checks if level exists in configurations then assigns parameters // if "default" qps level assigns default parameters from circuit breaker configurations if settings, ok := parameters[qpsLevel]; ok { if sleep, ok := settings["sleepWindowInMilliseconds"]; ok { sleepWindowInMilliseconds = sleep } if max, ok := settings["maxConcurrentRequests"]; ok { maxConcurrentRequests = max } if errorPercent, ok := settings["errorPercentThreshold"]; ok { errorPercentThreshold = errorPercent } if reqVolThreshold, ok := settings["requestVolumeThreshold"]; ok { requestVolumeThreshold = reqVolThreshold } } } // client settings override parameters if deps.Default.Config.ContainsKey("clients.{{$clientID}}.sleepWindowInMilliseconds") { sleepWindowInMilliseconds = int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.sleepWindowInMilliseconds")) } if deps.Default.Config.ContainsKey("clients.{{$clientID}}.maxConcurrentRequests") { maxConcurrentRequests = int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.maxConcurrentRequests")) } if deps.Default.Config.ContainsKey("clients.{{$clientID}}.errorPercentThreshold") { errorPercentThreshold = int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.errorPercentThreshold")) } if deps.Default.Config.ContainsKey("clients.{{$clientID}}.requestVolumeThreshold") { requestVolumeThreshold = int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.requestVolumeThreshold")) } hystrix.ConfigureCommand(circuitBreakerName, hystrix.CommandConfig{ MaxConcurrentRequests: maxConcurrentRequests, ErrorPercentThreshold: errorPercentThreshold, SleepWindow: sleepWindowInMilliseconds, RequestVolumeThreshold: requestVolumeThreshold, Timeout: timeoutVal, }) } // HTTPClient returns the underlying HTTP client, should only be // used for internal testing. func (c *{{$clientName}}) HTTPClient() *zanzibar.HTTPClient { return c.httpClient } {{range $svc := .Services}} {{range .Methods}} {{$serviceMethod := printf "%s::%s" $svc.Name .Name -}} {{$methodName := (title (index $exposedMethods $serviceMethod)) -}} {{if $methodName -}} // {{$methodName}} calls "{{.HTTPPath}}" endpoint. func (c *{{$clientName}}) {{$methodName}}( ctx context.Context, headers map[string]string, {{if ne .RequestType "" -}} r {{.RequestType}}, {{end -}} ) (context.Context, {{- if ne .ResponseType "" -}} {{.ResponseType}}, {{- end -}}map[string]string, error) { reqUUID := zanzibar.RequestUUIDFromCtx(ctx) if headers == nil { headers = make(map[string]string) } if reqUUID != "" { headers[c.requestUUIDHeaderKey] = reqUUID } if c.requestProcedureHeaderKey != "" { headers[c.requestProcedureHeaderKey] = "{{$serviceMethod}}" } {{if .ResponseType -}} var defaultRes {{.ResponseType}} {{end -}} req := zanzibar.NewClientHTTPRequest(ctx, c.clientID, "{{$methodName}}", "{{$serviceMethod}}", c.httpClient) {{if .ReqHeaderGoStatements }} {{range $index, $line := .ReqClientHeaderGoStatements -}} {{$line}} {{end -}} {{- end}} {{if $sidecarRouter -}} headers[c.callerHeader] = c.callerName // Set the service name if dynamic routing header is present for routeHeaderKey, routeMap := range c.altRoutingMap { if headerVal, ok := headers[routeHeaderKey]; ok { for routeRegex, altServiceName := range routeMap { //if headerVal matches routeRegex regex, set the alternative service name if matchFound, _ := regexp.MatchString(routeRegex, headerVal); matchFound { headers[c.calleeHeader] = altServiceName break } } } } // If serviceName was not set in the dynamic routing section above, set as the default if _, ok := headers[c.calleeHeader]; !ok { headers[c.calleeHeader] = c.calleeName } {{end}} // Generate full URL. fullURL := c.httpClient.BaseURL {{- range $k, $segment := .PathSegments -}} {{- if eq $segment.Type "static" -}}+"/{{$segment.Text}}" {{- else -}}+"/"+string({{- if not $segment.Required }} * {{- end -}}r{{$segment.BodyIdentifier | title}}) {{- end -}} {{- end}} {{range $index, $line := .WriteQueryParamGoStatements -}} {{$line}} {{end}} {{if (and (ne .RequestType "") (ne .HTTPMethod "GET"))}} {{if and (.RequestBoxed) (eq .BoxedRequestType "[]byte")}} err := req.WriteBytes("{{.HTTPMethod}}", fullURL, headers, r.{{.BoxedRequestName}}) {{else}} err := req.WriteJSON("{{.HTTPMethod}}", fullURL, headers, {{if .RequestBoxed -}}r.{{.BoxedRequestName}}{{- else -}}r{{- end -}}) {{end -}} {{else}} err := req.WriteJSON("{{.HTTPMethod}}", fullURL, headers, nil) {{end}} {{- /* <if .RequestType ne ""> */ -}} if err != nil { return {{if eq .ResponseType ""}}ctx, nil, err{{else}}ctx, defaultRes, nil, err{{end}} } {{if .ReqHeaders }} headerErr := req.CheckHeaders({{.ReqHeaders | printf "%#v"}}) if headerErr != nil { return {{ if eq .ResponseType "" -}} ctx, nil, headerErr {{- else -}} ctx, defaultRes, nil, headerErr {{- end}} } {{- end}} var res *zanzibar.ClientHTTPResponse if (c.circuitBreakerDisabled) { res, err = req.Do() } else { // We want hystrix ckt-breaker to count errors only for system issues var clientErr error circuitBreakerName := "{{$clientID}}" + "-" + "{{$methodName}}" err = hystrix.DoC(ctx, circuitBreakerName, func(ctx context.Context) error { res, clientErr = req.Do() if res != nil { // This is not a system error/issue. Downstream responded return nil } return clientErr }, nil) if err == nil { // ckt-breaker was ok, bubble up client error if set err = clientErr } } if err != nil { return ctx, {{if eq .ResponseType ""}}nil, err{{else}}defaultRes, nil, err{{end}} } respHeaders := make(map[string]string) for k := range res.Header { respHeaders[k] = res.Header.Get(k) } defer func() { respHeaders[zanzibar.ClientResponseDurationKey] = res.Duration.String() }() {{- if .ResHeaders }} // TODO(jakev): verify mandatory response headers {{- end}} res.CheckOKResponse([]int{ {{- range $index, $code := .ValidStatusCodes -}} {{if $index}},{{end}}{{$code}} {{- end -}} }) {{if and (eq .ResponseType "") (eq (len .Exceptions) 0)}} switch res.StatusCode { case {{.OKStatusCode.Code}}: {{- if and (ne (.OKStatusCode.Code) 204) (ne (.OKStatusCode.Code) 304) -}} _, err = res.ReadAll() if err != nil { return ctx, respHeaders, err } {{- end}} return ctx, respHeaders, nil default: _, err = res.ReadAll() if err != nil { return ctx, respHeaders, err } } {{else if eq (len .Exceptions) 0}} switch res.StatusCode { case {{.OKStatusCode.Code}}: {{- if or (eq (.OKStatusCode.Code) 204) (eq (.OKStatusCode.Code) 304) -}} return ctx, {{if isPointerType .ResponseType}}&{{end}}{{unref .ResponseType}}{}, respHeaders, nil {{- else }} {{- if eq .ResponseType "[]byte"}} responseBody, err := res.ReadAll() if err != nil { return ctx, defaultRes, respHeaders, err } return ctx, responseBody, respHeaders, nil {{ else }} var responseBody {{unref .ResponseType}} rawBody, err := res.ReadAll() if err != nil { return ctx, defaultRes, respHeaders, err } err = res.UnmarshalBody(&responseBody, rawBody) if err != nil { return ctx, defaultRes, respHeaders, err } {{- if .ResHeaderFields }} // TODO(jakev): read response headers and put them in body {{- end}} return ctx, {{if isPointerType .ResponseType}}&{{end}}responseBody, respHeaders, nil {{end -}} {{end -}} default: _, err = res.ReadAll() if err != nil { return ctx, defaultRes, respHeaders, err } } {{else if eq .ResponseType ""}} switch res.StatusCode { case {{.OKStatusCode.Code}}: {{- if and (ne (.OKStatusCode.Code) 204) (ne (.OKStatusCode.Code) 304) -}} _, err = res.ReadAll() if err != nil { return ctx, respHeaders, err } {{- end}} return ctx, respHeaders, nil {{range $code, $exceptions := .ExceptionsByStatusCode -}} case {{$code}}: {{- if or (eq $code 204) (eq $code 304) }} {{/* If multiple exceptions have 204/304 status code mapped, we aren't able to distinguish between them */}} {{/* so we'll just return the first exception that has 204/304 status code set. */}} {{$val := index $exceptions 0}} return ctx, respHeaders, &{{$val.Type}}{} {{ else if and (eq (len $exceptions) 1) (eq (index $exceptions 0).IsBodyDisallowed true) -}} {{$val := index $exceptions 0}} return ctx, respHeaders, &{{$val.Type}}{} {{else}} allOptions := []interface{}{ {{range $idx, $exception := $exceptions -}} &{{$exception.Type}}{}, {{- end}} } v, err := res.ReadAndUnmarshalBodyMultipleOptions(allOptions) if err != nil { return ctx, respHeaders, err } return ctx, respHeaders, v.(error) {{end}} {{- end}} default: _, err = res.ReadAll() if err != nil { return ctx, respHeaders, err } } {{else}} switch res.StatusCode { case {{.OKStatusCode.Code}}: {{- if or (eq (.OKStatusCode.Code) 204) (eq (.OKStatusCode.Code) 304) }} return ctx, {{if isPointerType .ResponseType}}&{{end}}{{unref .ResponseType}}{}, respHeaders, nil {{- else }} {{- if eq .ResponseType "[]byte"}} responseBody, err := res.ReadAll() if err != nil { return ctx, defaultRes, respHeaders, err } return ctx, responseBody, respHeaders, nil {{ else }} var responseBody {{unref .ResponseType}} rawBody, err := res.ReadAll() if err != nil { return ctx, defaultRes, respHeaders, err } err = res.UnmarshalBody(&responseBody, rawBody) if err != nil { return ctx, defaultRes, respHeaders, err } {{- if .ResHeaderFields }} // TODO(jakev): read response headers and put them in body {{- end}} return ctx, {{if isPointerType .ResponseType}}&{{end}}responseBody, respHeaders, nil {{end -}} {{end}} {{range $code, $exceptions := .ExceptionsByStatusCode -}} case {{$code}}: {{- if or (eq $code 204) (eq $code 304) }} {{/* If multiple exceptions have 204/304 status code mapped, we aren't able to distinguish between them */}} {{/* so we'll just return the first exception that has 204/304 status code set. */}} {{$val := index $exceptions 0}} return ctx, defaultRes, respHeaders, &{{$val.Type}}{} {{ else if and (eq (len $exceptions) 1) (eq (index $exceptions 0).IsBodyDisallowed true) -}} {{$val := index $exceptions 0}} return ctx, defaultRes, respHeaders, &{{$val.Type}}{} {{else}} allOptions := []interface{}{ {{range $idx, $exception := $exceptions -}} &{{$exception.Type}}{}, {{- end}} } v, err := res.ReadAndUnmarshalBodyMultipleOptions(allOptions) if err != nil { return ctx, defaultRes, respHeaders, err } return ctx, defaultRes, respHeaders, v.(error) {{end}} {{- end}} default: _, err = res.ReadAll() if err != nil { return ctx, defaultRes, respHeaders, err } } {{end}} return ctx, {{if ne .ResponseType ""}}defaultRes, {{end}}respHeaders, &zanzibar.UnexpectedHTTPError{ StatusCode: res.StatusCode, RawBody: res.GetRawBody(), } } {{end}} {{end}} {{end}} `) func http_client_testTmplBytes() ([]byte, error) { return _http_client_testTmpl, nil } func http_client_testTmpl() (*asset, error) { bytes, err := http_client_testTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "http_client_test.tmpl", size: 19602, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _mainTmpl = []byte(`{{- /* template to render gateway main.go */ -}} {{- $instance := . -}} package main import ( "bytes" "context" "flag" "strings" "github.com/uber/zanzibar/config" "github.com/pkg/errors" _ "go.uber.org/automaxprocs" "go.uber.org/fx" "go.uber.org/zap" zanzibar "github.com/uber/zanzibar/runtime" app "{{$instance.PackageInfo.PackageRoot}}" service "{{$instance.PackageInfo.GeneratedPackagePath}}" module "{{$instance.PackageInfo.ModulePackagePath}}" uberconfig "go.uber.org/config" ) var configFiles *string // Module defines the Zanzibar application module for {{$instance.InstanceName | pascal}} var Module = fx.Options( fx.Provide(New), fx.Invoke(run), ) func opts() fx.Option { return fx.Options( append( []fx.Option{Module}, app.GetOverrideFxOptions()..., )..., ) } // Params defines the dependencies of the New module. type Params struct { fx.In Lifecycle fx.Lifecycle } // Result defines the objects that the New module provides type Result struct { fx.Out // Gateway corresponds to the fully built server gateway Gateway *zanzibar.Gateway // Provider is an abstraction over the Zanzibar config store Provider uberconfig.Provider ` + "`" + `name:"zanzibarConfig"` + "`" + ` // Deps is a reference to the dependency tree inside zanzibar gateway Deps *service.DependenciesTree } func main() { fx.New(opts()).Run() } // run is the main entry point for {{$instance.InstanceName | pascal}} func run(gateway *zanzibar.Gateway) { gateway.Logger.Info("Started {{$instance.InstanceName | pascal}}", zap.String("realHTTPAddr", gateway.RealHTTPAddr), zap.String("realTChannelAddr", gateway.RealTChannelAddr), zap.Any("config", gateway.InspectOrDie()), ) } // New exports functionality similar to Module, but allows the caller to wrap // or modify Result. Most users should use Module instead. func New(p Params) (Result, error) { readFlags() gateway, deps, err := createGateway() if err != nil { return Result{}, errors.Wrap(err, "failed to create gateway server") } // Represent the zanzibar config in YAML that will be used to expose a config provider yamlCfg, err := gateway.Config.AsYaml() if err != nil { return Result{}, errors.Wrap(err, "unable to marshal Zanzibar config to YAML") } provider, err := uberconfig.NewYAML( []uberconfig.YAMLOption{ uberconfig.Source(bytes.NewReader(yamlCfg)), }..., ) if err != nil { return Result{}, errors.Wrap(err, "unable to provide a YAML view from Zanzibar config") } p.Lifecycle.Append(fx.Hook{ OnStart: func(ctx context.Context) error { err = gateway.Bootstrap() if err != nil { panic(errors.Wrap(err, "failed to bootstrap gateway server")) } return nil }, OnStop: func(ctx context.Context) error { gateway.Logger.Info("fx OnStop() hook activated") gateway.WaitGroup.Add(1) gateway.Shutdown() gateway.WaitGroup.Done() return nil }, }) return Result{ Gateway: gateway, Provider: provider, Deps: deps, }, nil } func createGateway() (*zanzibar.Gateway, *service.DependenciesTree, error) { cfg := getConfig() gateway, deps, err := service.CreateGateway(cfg, app.AppOptions) if err != nil { return nil, nil, err } return gateway, deps.(*service.DependenciesTree), nil } func getConfig() *zanzibar.StaticConfig { var files []string if configFiles == nil { files = []string{} } else { files = strings.Split(*configFiles, ";") } return config.NewRuntimeConfigOrDie(files, nil) } func readFlags() { configFiles = flag.String( "config", "", "an ordered, semi-colon separated list of configuration files to use", ) flag.Parse() } `) func mainTmplBytes() ([]byte, error) { return _mainTmpl, nil } func mainTmpl() (*asset, error) { bytes, err := mainTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "main.tmpl", size: 3608, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _main_testTmpl = []byte(`{{- /* template to render gateway main_test.go This template is the test entrypoint for spawning a gateway as a child process using the test coverage features etc. */ -}} {{- $instance := . -}} package main import ( "flag" "os" "os/signal" "syscall" "testing" "github.com/stretchr/testify/assert" zanzibar "github.com/uber/zanzibar/runtime" "go.uber.org/zap" "go.uber.org/zap/zapcore" module "{{$instance.PackageInfo.ModulePackagePath}}" ) var cachedServer *zanzibar.Gateway func TestMain(m *testing.M) { readFlags() if os.Getenv("GATEWAY_RUN_CHILD_PROCESS_TEST") != "" { listenOnSignals() code := m.Run() os.Exit(code) } else { os.Exit(0) } } func listenOnSignals() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGUSR2) go func() { <-sigs if cachedServer != nil { cachedServer.Close() } }() } func TestStartGateway(t *testing.T) { testLogger := zap.New( zapcore.NewCore( zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), os.Stderr, zap.DebugLevel, ), ) gateway, deps, err := createGateway() if err != nil { testLogger.Error( "Failed to CreateGateway in TestStartGateway()", zap.Error(err), ) return } assert.NotNil(t, deps) cachedServer = gateway err = gateway.Bootstrap() if err != nil { testLogger.Error( "Failed to Bootstrap in TestStartGateway()", zap.Error(err), ) return } logAndWait(gateway) } func logAndWait(server *zanzibar.Gateway) { server.Logger.Info("Started {{$instance.InstanceName | pascal}}", zap.String("realHTTPAddr", server.RealHTTPAddr), zap.String("realTChannelAddr", server.RealTChannelAddr), zap.Any("config", server.InspectOrDie()), ) go func(){ sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) <-sig server.WaitGroup.Add(1) server.Shutdown() server.WaitGroup.Done() }() server.Wait() } `) func main_testTmplBytes() ([]byte, error) { return _main_testTmpl, nil } func main_testTmpl() (*asset, error) { bytes, err := main_testTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "main_test.tmpl", size: 1896, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _middleware_httpTmpl = []byte(`{{$instance := . -}} package {{$instance.PackageInfo.PackageName}} import ( zanzibar "github.com/uber/zanzibar/runtime" module "{{$instance.PackageInfo.ModulePackagePath}}" handle "{{index .Config "path"}}" ) // Middleware is a container for module.Deps and factory for MiddlewareHandle type Middleware struct { Deps *module.Dependencies } // NewMiddleware is a factory method for the struct func NewMiddleware(deps *module.Dependencies) Middleware { return Middleware { Deps: deps, } } // NewMiddlewareHandle calls back to the custom middleware to build a MiddlewareHandle func (m *Middleware) NewMiddlewareHandle(o handle.Options) zanzibar.MiddlewareHandle { return handle.NewMiddleware(m.Deps, o) } `) func middleware_httpTmplBytes() ([]byte, error) { return _middleware_httpTmpl, nil } func middleware_httpTmpl() (*asset, error) { bytes, err := middleware_httpTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "middleware_http.tmpl", size: 718, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _middleware_tchannelTmpl = []byte(`{{$instance := . -}} package {{$instance.PackageInfo.PackageName}} import ( zanzibar "github.com/uber/zanzibar/runtime" module "{{$instance.PackageInfo.ModulePackagePath}}" handle "{{index .Config "path"}}" ) // Middleware is a container for module.Deps and factory for MiddlewareHandle type Middleware struct { Deps *module.Dependencies } // NewMiddleware is a factory method for the struct func NewMiddleware(deps *module.Dependencies) Middleware { return Middleware { Deps: deps, } } // NewMiddlewareHandle calls back to the custom middleware to build a MiddlewareHandle func (m *Middleware) NewMiddlewareHandle(o handle.Options) zanzibar.MiddlewareTchannelHandle { return handle.NewMiddleware(m.Deps, o) } `) func middleware_tchannelTmplBytes() ([]byte, error) { return _middleware_tchannelTmpl, nil } func middleware_tchannelTmpl() (*asset, error) { bytes, err := middleware_tchannelTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "middleware_tchannel.tmpl", size: 726, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _module_class_initializerTmpl = []byte(`{{- $className := index . 0 }} {{- $instance := index . 1 }} {{- $moduleInstances := (index $instance.RecursiveDependencies $className)}} {{- $initializedDeps := printf "initialized%sDependencies" (title $className) }} {{- if eq (len .) 3 }} {{$initializedDeps}} := &{{index . 2}}.{{$className | title}}DependenciesNodes{} {{- else }} {{$initializedDeps}} := &{{$className | title}}DependenciesNodes{} {{- end }} tree.{{$className | title}} = {{$initializedDeps}} {{- range $idx, $dependency := $moduleInstances}} {{- $pkgInfo := $dependency.PackageInfo }} {{$initializedDeps}}.{{$pkgInfo.QualifiedInstanceName}} = {{$pkgInfo.ImportPackageAlias}}.{{$pkgInfo.ExportName}}(&{{$pkgInfo.ModulePackageAlias}}.Dependencies{ Default: initializedDefaultDependencies, {{- range $className, $moduleInstances := $dependency.ResolvedDependencies}} {{$className | pascal}}: &{{$pkgInfo.ModulePackageAlias}}.{{$className | pascal}}Dependencies{ {{- range $idy, $subDependency := $moduleInstances}} {{$subDependency.PackageInfo.QualifiedInstanceName}}: initialized{{$className | pascal}}Dependencies.{{$subDependency.PackageInfo.QualifiedInstanceName}}, {{- end}} }, {{- end}} }) {{- end}} `) func module_class_initializerTmplBytes() ([]byte, error) { return _module_class_initializerTmpl, nil } func module_class_initializerTmpl() (*asset, error) { bytes, err := module_class_initializerTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "module_class_initializer.tmpl", size: 1191, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _module_initializerTmpl = []byte(`{{$instance := . -}} package module import ( {{range $classType, $moduleInstances := $instance.RecursiveDependencies -}} {{range $idx, $moduleInstance := $moduleInstances -}} {{$moduleInstance.PackageInfo.ImportPackageAlias}} "{{$moduleInstance.PackageInfo.ImportPackagePath}}" {{$moduleInstance.PackageInfo.ModulePackageAlias}} "{{$moduleInstance.PackageInfo.ModulePackagePath}}" {{end -}} {{end}} zanzibar "github.com/uber/zanzibar/runtime" ) // DependenciesTree contains all deps for this service. type DependenciesTree struct { {{range $idx, $className := $instance.DependencyOrder -}} {{$className | title}} *{{$className | title}}DependenciesNodes {{end -}} } {{range $idx, $className := $instance.DependencyOrder -}} {{$moduleInstances := (index $instance.RecursiveDependencies $className) -}} // {{$className | title}}DependenciesNodes contains {{$className}} dependencies type {{$className | title}}DependenciesNodes struct { {{ range $idx, $dependency := $moduleInstances -}} {{$dependency.PackageInfo.QualifiedInstanceName}} {{$dependency.PackageInfo.ImportPackageAlias}}.{{$dependency.PackageInfo.ExportType}} {{end -}} } {{end -}} // InitializeDependencies fully initializes all dependencies in the dep tree // for the {{$instance.InstanceName}} {{$instance.ClassName}} func InitializeDependencies( g *zanzibar.Gateway, ) (*DependenciesTree, *Dependencies) { tree := &DependenciesTree{} initializedDefaultDependencies := &zanzibar.DefaultDependencies{ Logger: g.Logger, ContextExtractor: g.ContextExtractor, ContextLogger: g.ContextLogger, ContextMetrics: zanzibar.NewContextMetrics(g.RootScope), Scope: g.RootScope, Tracer: g.Tracer, Config: g.Config, ServerTChannel: g.ServerTChannel, Gateway: g, GRPCClientDispatcher: g.GRPCClientDispatcher, JSONWrapper: g.JSONWrapper, } {{range $idx, $className := $instance.DependencyOrder}} {{template "module_class_initializer.tmpl" args $className $instance}} {{end}} dependencies := &Dependencies{ Default: initializedDefaultDependencies, {{- range $className, $moduleInstances := $instance.ResolvedDependencies}} {{$className | pascal}}: &{{$className | pascal}}Dependencies{ {{- range $idy, $subDependency := $moduleInstances}} {{$subDependency.PackageInfo.QualifiedInstanceName}}: initialized{{$className | pascal}}Dependencies.{{$subDependency.PackageInfo.QualifiedInstanceName}}, {{- end}} }, {{- end}} } return tree, dependencies } `) func module_initializerTmplBytes() ([]byte, error) { return _module_initializerTmpl, nil } func module_initializerTmpl() (*asset, error) { bytes, err := module_initializerTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "module_initializer.tmpl", size: 2564, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _module_mock_initializerTmpl = []byte(`{{$instance := .Instance -}} {{$leafWithFixture := .LeafWithFixture -}} {{$leafClass := firstIsClientOrEmpty $instance.DependencyOrder -}} {{$mockDeps := printf "Mock%sNodes" (title $leafClass) -}} {{$classPkg := "module" -}} package {{$instance.PackageInfo.GeneratedPackageAlias}}mock import ( "testing" "github.com/golang/mock/gomock" zanzibar "github.com/uber/zanzibar/runtime" {{$classPkg}} "{{$instance.PackageInfo.ModulePackagePath}}" {{range $classType, $moduleInstances := $instance.RecursiveDependencies -}} {{range $idx, $moduleInstance := $moduleInstances -}} {{if eq $classType $leafClass -}} {{$moduleInstance.PackageInfo.GeneratedPackageAlias}} "{{$moduleInstance.PackageInfo.GeneratedPackagePath}}/mock-client" {{if (index $leafWithFixture $moduleInstance.InstanceName) -}} fixture{{$moduleInstance.PackageInfo.ImportPackageAlias}} "{{index $leafWithFixture $moduleInstance.InstanceName}}" {{end -}} {{else -}} {{$moduleInstance.PackageInfo.ImportPackageAlias}} "{{$moduleInstance.PackageInfo.ImportPackagePath}}" {{$moduleInstance.PackageInfo.ModulePackageAlias}} "{{$moduleInstance.PackageInfo.ModulePackagePath}}" {{end -}} {{end -}} {{end}} ) {{$moduleInstances := (index $instance.RecursiveDependencies $leafClass) -}} // {{$mockDeps}} contains mock {{$leafClass}} dependencies type {{$mockDeps}} struct { {{ range $idx, $dependency := $moduleInstances -}} {{- if (index $leafWithFixture $dependency.InstanceName) }} {{$dependency.PackageInfo.QualifiedInstanceName}} *{{$dependency.PackageInfo.GeneratedPackageAlias}}.Mock{{$dependency.PackageInfo.ExportType}}WithFixture {{- else }} {{$dependency.PackageInfo.QualifiedInstanceName}} *{{$dependency.PackageInfo.GeneratedPackageAlias}}.Mock{{$dependency.PackageInfo.ExportType}} {{- end }} {{- end}} } // InitializeDependenciesMock fully initializes all dependencies in the dep tree // for the {{$instance.InstanceName}} {{$instance.ClassName}} with leaf nodes being mocks func InitializeDependenciesMock( g *zanzibar.Gateway, ctrl *gomock.Controller, ) (*{{$classPkg}}.DependenciesTree, *{{$classPkg}}.Dependencies, *{{$mockDeps}}) { tree := &{{$classPkg}}.DependenciesTree{} {{ if eq $leafClass "" -}} {{camel $mockDeps}} := &{{$mockDeps}}{} {{ end -}} initializedDefaultDependencies := &zanzibar.DefaultDependencies{ ContextExtractor: g.ContextExtractor, ContextMetrics: g.ContextMetrics, ContextLogger: g.ContextLogger, Logger: g.Logger, Scope: g.RootScope, Config: g.Config, ServerTChannel: g.ServerTChannel, Tracer: g.Tracer, GRPCClientDispatcher: g.GRPCClientDispatcher, JSONWrapper: g.JSONWrapper, } {{range $idx, $className := $instance.DependencyOrder}} {{if eq $className $leafClass -}} {{- $moduleInstances := (index $instance.RecursiveDependencies $className)}} {{camel $mockDeps}} := &{{$mockDeps}}{ {{- range $idx, $dependency := $moduleInstances}} {{- $pkgInfo := $dependency.PackageInfo }} {{- if (index $leafWithFixture $dependency.InstanceName) }} {{$pkgInfo.QualifiedInstanceName}}: {{$pkgInfo.GeneratedPackageAlias}}.New(ctrl, fixture{{$pkgInfo.ImportPackageAlias}}.Fixture), {{- else }} {{$pkgInfo.QualifiedInstanceName}}: {{$pkgInfo.GeneratedPackageAlias}}.NewMock{{title $className}}(ctrl), {{- end }} {{- end }} } {{- $initializedDeps := printf "initialized%sDependencies" (title $className) }} {{$initializedDeps}} := &{{$classPkg}}.{{$className | title}}DependenciesNodes{} tree.{{$className | title}} = {{$initializedDeps}} {{- range $idx, $dependency := $moduleInstances}} {{- $pkgInfo := $dependency.PackageInfo }} {{$initializedDeps}}.{{$pkgInfo.QualifiedInstanceName}} = {{camel $mockDeps}}.{{$pkgInfo.QualifiedInstanceName}} {{- end }} {{else -}} {{template "module_class_initializer.tmpl" args $className $instance $classPkg}} {{end}} {{end}} dependencies := &{{$classPkg}}.Dependencies{ Default: initializedDefaultDependencies, {{- range $className, $moduleInstances := $instance.ResolvedDependencies}} {{$className | pascal}}: &{{$classPkg}}.{{$className | pascal}}Dependencies{ {{- range $idy, $subDependency := $moduleInstances}} {{$subDependency.PackageInfo.QualifiedInstanceName}}: initialized{{$className | pascal}}Dependencies.{{$subDependency.PackageInfo.QualifiedInstanceName}}, {{- end}} }, {{- end}} } return tree, dependencies, {{camel $mockDeps}} } `) func module_mock_initializerTmplBytes() ([]byte, error) { return _module_mock_initializerTmpl, nil } func module_mock_initializerTmpl() (*asset, error) { bytes, err := module_mock_initializerTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "module_mock_initializer.tmpl", size: 4471, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _serviceTmpl = []byte(`{{- /* template to render gateway main.go */ -}} {{- $instance := . -}} package {{$instance.PackageInfo.GeneratedPackageAlias}} import ( "os" "path/filepath" "go.uber.org/zap" zanzibar "github.com/uber/zanzibar/runtime" module "{{$instance.PackageInfo.ModulePackagePath}}" ) // DependenciesTree re-exported for convenience. type DependenciesTree module.DependenciesTree // CreateGateway creates a new instances of the {{$instance.InstanceName}} // service with the specified config func CreateGateway( config *zanzibar.StaticConfig, opts *zanzibar.Options, ) (*zanzibar.Gateway, interface{}, error) { gateway, err := zanzibar.CreateGateway(config, opts) if err != nil { return nil, nil, err } tree, dependencies := module.InitializeDependencies(gateway) registerErr := RegisterDeps(gateway, dependencies) if registerErr != nil { return nil, nil, registerErr } return gateway, (*DependenciesTree)(tree), nil } // RegisterDeps registers direct dependencies of the service func RegisterDeps(g *zanzibar.Gateway, deps *module.Dependencies) error { //lint:ignore S1021 allow less concise variable declaration for ease of code generation var err error {{- range $class, $instances := $instance.ResolvedDependencies }} {{- range $idx, $instance := $instances }} err = deps.{{title $class}}.{{$instance.PackageInfo.QualifiedInstanceName}}.Register(g) if err != nil { return err } {{- end}} {{- end}} return nil } `) func serviceTmplBytes() ([]byte, error) { return _serviceTmpl, nil } func serviceTmpl() (*asset, error) { bytes, err := serviceTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "service.tmpl", size: 1445, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _service_mockTmpl = []byte(`{{- $instance := .Instance -}} {{- $defaultTestConfigPath := .TestConfigPath -}} {{- $leafClass := firstIsClientOrEmpty $instance.DependencyOrder -}} {{- $mockType := printf "Mock%sNodes" (title $leafClass) -}} {{- $mock := printf "Mock%ss" (title $leafClass) -}} package {{$instance.PackageInfo.GeneratedPackageAlias}}mock import ( "context" "errors" "io" "net/http" "os" "path/filepath" "testing" "time" "github.com/golang/mock/gomock" "github.com/uber/zanzibar/config" zanzibar "github.com/uber/zanzibar/runtime" service "{{$instance.PackageInfo.GeneratedPackagePath}}" ) // MockService interface type MockService interface { MakeHTTPRequest( method string, url string, headers map[string]string, body io.Reader, ) (*http.Response, error) MakeTChannelRequest( ctx context.Context, thriftService string, method string, headers map[string]string, req, resp zanzibar.RWTStruct, ) (bool, map[string]string, error) {{$mock}}() *{{$mockType}} Server() *zanzibar.Gateway Start() Stop() } type mockService struct { started bool server *zanzibar.Gateway ctrl *gomock.Controller {{camel $mock}} *{{$mockType}} httpClient *http.Client tChannelClient zanzibar.TChannelCaller } // MustCreateTestService creates a new MockService, panics if it fails doing so. // Optional testConfigPaths specifies runtime config files used in tests, it // should be paths that are relative to "$GOPATH/src". // If testConfigPaths is absent, a default test config file is used. // The default test config file is chosen base on existence in order below: // - "../../config/test.yaml" where current dir is the dir of service-config.yaml for the mocked service // - "config/test.yaml" where current dir is the project root func MustCreateTestService(t *testing.T, testConfigPaths ...string) MockService { if len(testConfigPaths) == 0 { configPath := filepath.Join("src", "{{$defaultTestConfigPath}}") defaultPath := filepath.Join(os.Getenv("GOPATH"), configPath) // This is a temporary solution for running tests using bazel // see https://docs.bazel.build/versions/master/test-encyclopedia.html for relevant env vars // TODO: need long term solution to avoid hardcoding bazel specifics bazelPath := filepath.Join(os.Getenv("TEST_SRCDIR"), os.Getenv("TEST_WORKSPACE"), configPath) testConfigPaths = append( testConfigPaths, defaultPath, bazelPath, ) } c := config.NewRuntimeConfigOrDie(testConfigPaths, nil) server, err := zanzibar.CreateGateway(c, nil) if err != nil { panic(err) } ctrl := gomock.NewController(t) _, dependencies, mockNodes := InitializeDependenciesMock(server, ctrl) registerErr := service.RegisterDeps(server, dependencies) if registerErr != nil { panic(registerErr) } httpClient := &http.Client{ Transport: &http.Transport{ DisableKeepAlives: false, MaxIdleConns: 500, MaxIdleConnsPerHost: 500, }, } // TChannel clients must have a timeout defined, so defining a long timeout that should not realistically // be hit in tests. timeout := time.Duration(5) * time.Minute timeoutPerAttempt := time.Duration(1) * time.Minute tchannelClient := zanzibar.NewRawTChannelClient( server.ServerTChannel, server.ContextLogger, server.RootScope, &zanzibar.TChannelClientOption{ ServiceName: server.ServiceName, ClientID: "TestClient", Timeout: timeout, TimeoutPerAttempt: timeoutPerAttempt, }, ) return &mockService{ server: server, ctrl: ctrl, {{camel $mock}}: mockNodes, httpClient: httpClient, tChannelClient: tchannelClient, } } // Server returns the mock server func (m *mockService) Server() *zanzibar.Gateway { return m.server } // Start starts the mock server, panics if fails doing so func (m *mockService) Start() { if err := m.server.Bootstrap(); err != nil { panic(err) } m.started = true } // Stop stops the mock server func (m *mockService) Stop() { // m.ctrl.Finish() calls runtime.Goexit() on errors // put it in defer so cleanup is always done defer func(){ m.server.Shutdown() m.started = false }() m.ctrl.Finish() } // {{$mock}} returns the mock {{$leafClass}}s func (m *mockService) {{$mock}}() *{{$mockType}} { return m.{{camel $mock}} } // MakeHTTPRequest makes a HTTP request to the mock server func (m *mockService) MakeHTTPRequest( method string, url string, headers map[string]string, body io.Reader, ) (*http.Response, error) { if !m.started { return nil, errors.New("mock server is not started") } client := m.httpClient fullURL := "http://" + m.server.RealHTTPAddr + url req, err := http.NewRequest(method, fullURL, body) for headerName, headerValue := range headers { req.Header.Set(headerName, headerValue) } if err != nil { return nil, err } return client.Do(req) } // MakeTChannelRequest makes a TChannel request to the mock server func (m *mockService) MakeTChannelRequest( ctx context.Context, thriftService string, method string, headers map[string]string, req, res zanzibar.RWTStruct, ) (bool, map[string]string, error) { if !m.started { return false, nil, errors.New("mock server is not started") } sc := m.server.ServerTChannel.GetSubChannel(m.server.ServiceName) sc.Peers().Add(m.server.RealTChannelAddr) return m.tChannelClient.Call(ctx, thriftService, method, headers, req, res) } `) func service_mockTmplBytes() ([]byte, error) { return _service_mockTmpl, nil } func service_mockTmpl() (*asset, error) { bytes, err := service_mockTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "service_mock.tmpl", size: 5424, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _structsTmpl = []byte(`{{- /* template to render edge gateway http client code */ -}} {{- $instance := .Instance }} package {{$instance.PackageInfo.PackageName}} import ( "runtime" "path/filepath" zanzibar "github.com/uber/zanzibar/runtime" {{range $idx, $pkg := .Spec.IncludedPackages -}} {{$pkg.AliasName}} "{{$pkg.PackageName}}" {{end}} ) func getDirName() string { _, file, _, _ := runtime.Caller(0) return zanzibar.GetDirnameFromRuntimeCaller(file) } `) func structsTmplBytes() ([]byte, error) { return _structsTmpl, nil } func structsTmpl() (*asset, error) { bytes, err := structsTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "structs.tmpl", size: 445, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _tchannel_clientTmpl = []byte(`{{- /* template to render edge gateway tchannel client code */ -}} {{- $instance := .Instance }} package {{$instance.PackageInfo.PackageName}} import ( "context" "errors" "strconv" "strings" "time" "net/textproto" "github.com/afex/hystrix-go/hystrix" "github.com/uber/tchannel-go" zanzibar "github.com/uber/zanzibar/runtime" "github.com/uber/tchannel-go" "github.com/uber/zanzibar/config" "github.com/uber/zanzibar/runtime/ruleengine" "go.uber.org/zap" module "{{$instance.PackageInfo.ModulePackagePath}}" {{range $idx, $pkg := .IncludedPackages -}} {{$pkg.AliasName}} "{{$pkg.PackageName}}" {{end}} ) {{$clientID := .ClientID -}} {{$exposedMethods := .ExposedMethods -}} {{$QPSLevels := .QPSLevels -}} {{- $clientName := printf "%sClient" (camel $clientID) }} {{- $exportName := .ExportName}} {{- $sidecarRouter := .SidecarRouter}} // CircuitBreakerConfigKey is key value for qps level to circuit breaker parameters mapping const CircuitBreakerConfigKey = "circuitbreaking-configurations" var logFieldErrLocation = zanzibar.LogFieldErrorLocation("client::{{$instance.InstanceName}}") // Client defines {{$clientID}} client interface. type Client interface { {{range $svc := .Services -}} {{range .Methods}} {{$serviceMethod := printf "%s::%s" $svc.Name .Name -}} {{$methodName := (title (index $exposedMethods $serviceMethod)) -}} {{- if $methodName -}} {{$methodName}}( ctx context.Context, reqHeaders map[string]string, {{if ne .RequestType "" -}} args {{.RequestType}}, {{end -}} ) (context.Context, {{- if ne .ResponseType "" -}} {{.ResponseType}}, {{- end -}}map[string]string, error) {{- end -}} {{- end -}} {{- end -}} } // NewClient returns a new TChannel client for service {{$clientID}}. func {{$exportName}}(deps *module.Dependencies) Client { {{- /* this is the service discovery service name */}} serviceName := deps.Default.Config.MustGetString("clients.{{$clientID}}.serviceName") var routingKey string if deps.Default.Config.ContainsKey("clients.{{$clientID}}.routingKey") { routingKey = deps.Default.Config.MustGetString("clients.{{$clientID}}.routingKey") } var requestUUIDHeaderKey string if deps.Default.Config.ContainsKey("tchannel.clients.requestUUIDHeaderKey") { requestUUIDHeaderKey = deps.Default.Config.MustGetString("tchannel.clients.requestUUIDHeaderKey") } {{if $sidecarRouter -}} ip := deps.Default.Config.MustGetString("sidecarRouter.{{$sidecarRouter}}.tchannel.ip") port := deps.Default.Config.MustGetInt("sidecarRouter.{{$sidecarRouter}}.tchannel.port") {{else -}} ip := deps.Default.Config.MustGetString("clients.{{$clientID}}.ip") port := deps.Default.Config.MustGetInt("clients.{{$clientID}}.port") {{end -}} gateway := deps.Default.Gateway var channel *tchannel.Channel // If dedicated.tchannel.client : true, each tchannel client will create a // dedicated connection with local sidecar, else it will use a shared connection if deps.Default.Config.ContainsKey("dedicated.tchannel.client") && deps.Default.Config.MustGetBoolean("dedicated.tchannel.client") { channel = gateway.SetupClientTChannel(deps.Default.Config, serviceName) channel.Peers().Add(ip + ":" + strconv.Itoa(int(port))) } else { channel = gateway.ServerTChannel channel.GetSubChannel(serviceName, tchannel.Isolated).Peers().Add(ip + ":" + strconv.Itoa(int(port))) } /*Ex: { "clients.rider-presentation.alternates": { "routingConfigs": [ { "headerName": "x-test-env", "headerValue": "*", "serviceName": "testservice" }, { "headerName": "x-container", "headerValue": "container*", "serviceName": "relayer" } ], "servicesDetail": { "testservice": { "ip": "127.0.0.1", "port": 5000 }, "relayer": { "ip": "127.0.0.1", "port": 12000 } } } }*/ var re ruleengine.RuleEngine var headerPatterns []string altChannelMap := make(map[string]*tchannel.SubChannel) headerPatterns, re = initializeDynamicChannel(channel, deps, headerPatterns, altChannelMap, re) timeoutVal := int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.timeout")) timeout := time.Millisecond * time.Duration( timeoutVal, ) timeoutPerAttempt := time.Millisecond * time.Duration( deps.Default.Config.MustGetInt("clients.{{$clientID}}.timeoutPerAttempt"), ) methodNames := map[string]string{ {{range $svc := .Services -}} {{range .Methods -}} {{$serviceMethod := printf "%s::%s" $svc.Name .Name -}} {{$methodName := (title (index $exposedMethods $serviceMethod)) -}} {{if $methodName -}} "{{$serviceMethod}}": "{{$methodName}}", {{end -}} {{ end -}} {{ end -}} } //get mapping of client method and it's timeout //if mapping is not provided, use client's timeout for all the methods clientMethodTimeoutMapping := make(map[string]int64) if deps.Default.Config.ContainsKey("clients.{{$clientID}}.methodTimeoutMapping") { deps.Default.Config.MustGetStruct("clients.{{$clientID}}.methodTimeoutMapping", &clientMethodTimeoutMapping) } else { for _, methodName := range methodNames { clientMethodTimeoutMapping[methodName] = int64(timeoutVal) } } qpsLevels := map[string]string{ {{range $methodName, $qpsLevel := $QPSLevels -}} "{{$methodName}}": "{{$qpsLevel}}", {{end}} } // circuitBreakerDisabled sets whether circuit-breaker should be disabled circuitBreakerDisabled := false if deps.Default.Config.ContainsKey("clients.{{$clientID}}.circuitBreakerDisabled") { circuitBreakerDisabled = deps.Default.Config.MustGetBoolean("clients.{{$clientID}}.circuitBreakerDisabled") } if !circuitBreakerDisabled { for methodName, methodTimeoutVal := range clientMethodTimeoutMapping{ circuitBreakerName := "{{$clientID}}" + "-" + methodName qpsLevel := "default" if level, ok := qpsLevels[circuitBreakerName]; ok { qpsLevel = level } configureCircuitBreaker(deps, int(methodTimeoutVal), circuitBreakerName, qpsLevel) } } var client *zanzibar.TChannelClient if deps.Default.Config.ContainsKey("tchannelclients.retryCount.feature.enabled") && deps.Default.Config.MustGetBoolean("tchannelclients.retryCount.feature.enabled") && deps.Default.Config.ContainsKey("clients.{{$clientID}}.retryCount") && int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.retryCount")) > 0{ maxAttempts := int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.retryCount")) client = zanzibar.NewTChannelClientContext( channel, deps.Default.ContextLogger, deps.Default.ContextMetrics, deps.Default.ContextExtractor, &zanzibar.TChannelClientOption{ ServiceName: serviceName, ClientID: "{{$clientID}}", MethodNames: methodNames, Timeout: timeout, TimeoutPerAttempt: timeoutPerAttempt, RoutingKey: &routingKey, RuleEngine: re, HeaderPatterns: headerPatterns, RequestUUIDHeaderKey: requestUUIDHeaderKey, AltChannelMap: altChannelMap, MaxAttempts: maxAttempts, }, ) }else{ client = zanzibar.NewTChannelClientContext( channel, deps.Default.ContextLogger, deps.Default.ContextMetrics, deps.Default.ContextExtractor, &zanzibar.TChannelClientOption{ ServiceName: serviceName, ClientID: "{{$clientID}}", MethodNames: methodNames, Timeout: timeout, TimeoutPerAttempt: timeoutPerAttempt, RoutingKey: &routingKey, RuleEngine: re, HeaderPatterns: headerPatterns, RequestUUIDHeaderKey: requestUUIDHeaderKey, AltChannelMap: altChannelMap, }, ) } return &{{$clientName}}{ client: client, circuitBreakerDisabled: circuitBreakerDisabled, defaultDeps: deps.Default, } } func initializeDynamicChannel(channel *tchannel.Channel, deps *module.Dependencies, headerPatterns []string, altChannelMap map[string]*tchannel.SubChannel, re ruleengine.RuleEngine) ([]string, ruleengine.RuleEngine) { if deps.Default.Config.ContainsKey("clients.{{$clientID}}.alternates") { var alternateServiceDetail config.AlternateServiceDetail deps.Default.Config.MustGetStruct("clients.{{$clientID}}.alternates", &alternateServiceDetail) ruleWrapper := ruleengine.RuleWrapper{} for _, routingConfig := range alternateServiceDetail.RoutingConfigs { ruleValue := []string{routingConfig.ServiceName} rd := routingConfig.RoutingDelegate if rd != nil { ruleValue = append(ruleValue, *rd) } rawRule := ruleengine.RawRule{Patterns: []string{textproto.CanonicalMIMEHeaderKey(routingConfig.HeaderName), strings.ToLower(routingConfig.HeaderValue)}, Value: ruleValue} headerPatterns = append(headerPatterns, textproto.CanonicalMIMEHeaderKey(routingConfig.HeaderName)) ruleWrapper.Rules = append(ruleWrapper.Rules, rawRule) scAlt := channel.GetSubChannel(routingConfig.ServiceName, tchannel.Isolated) serviceRouting, ok := alternateServiceDetail.ServicesDetailMap[routingConfig.ServiceName] if !ok { panic("service routing mapping incorrect for service: " + routingConfig.ServiceName) } scAlt.Peers().Add(serviceRouting.IP + ":" + strconv.Itoa(serviceRouting.Port)) altChannelMap[routingConfig.ServiceName] = scAlt } re = ruleengine.NewRuleEngine(ruleWrapper) } return headerPatterns, re } // CircuitBreakerConfig is used for storing the circuit breaker parameters for each qps level type CircuitBreakerConfig struct { Parameters map[string]map[string]int } func configureCircuitBreaker(deps *module.Dependencies, timeoutVal int, circuitBreakerName string, qpsLevel string) { // sleepWindowInMilliseconds sets the amount of time, after tripping the circuit, // to reject requests before allowing attempts again to determine if the circuit should again be closed sleepWindowInMilliseconds := 5000 // maxConcurrentRequests sets how many requests can be run at the same time, beyond which requests are rejected maxConcurrentRequests := 20 // errorPercentThreshold sets the error percentage at or above which the circuit should trip open errorPercentThreshold := 20 // requestVolumeThreshold sets a minimum number of requests that will trip the circuit in a rolling window of 10s // For example, if the value is 20, then if only 19 requests are received in the rolling window of 10 seconds // the circuit will not trip open even if all 19 failed. requestVolumeThreshold := 20 // parses circuit breaker configurations if deps.Default.Config.ContainsKey(CircuitBreakerConfigKey) { var config CircuitBreakerConfig deps.Default.Config.MustGetStruct(CircuitBreakerConfigKey, &config) parameters := config.Parameters // first checks if level exists in configurations then assigns parameters // if "default" qps level assigns default parameters from circuit breaker configurations if settings, ok := parameters[qpsLevel]; ok { if sleep, ok := settings["sleepWindowInMilliseconds"]; ok { sleepWindowInMilliseconds = sleep } if max, ok := settings["maxConcurrentRequests"]; ok { maxConcurrentRequests = max } if errorPercent, ok := settings["errorPercentThreshold"]; ok { errorPercentThreshold = errorPercent } if reqVolThreshold, ok := settings["requestVolumeThreshold"]; ok { requestVolumeThreshold = reqVolThreshold } } } // client settings override parameters if deps.Default.Config.ContainsKey("clients.{{$clientID}}.sleepWindowInMilliseconds") { sleepWindowInMilliseconds = int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.sleepWindowInMilliseconds")) } if deps.Default.Config.ContainsKey("clients.{{$clientID}}.maxConcurrentRequests") { maxConcurrentRequests = int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.maxConcurrentRequests")) } if deps.Default.Config.ContainsKey("clients.{{$clientID}}.errorPercentThreshold") { errorPercentThreshold = int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.errorPercentThreshold")) } if deps.Default.Config.ContainsKey("clients.{{$clientID}}.requestVolumeThreshold") { requestVolumeThreshold = int(deps.Default.Config.MustGetInt("clients.{{$clientID}}.requestVolumeThreshold")) } hystrix.ConfigureCommand(circuitBreakerName, hystrix.CommandConfig{ MaxConcurrentRequests: maxConcurrentRequests, ErrorPercentThreshold: errorPercentThreshold, SleepWindow: sleepWindowInMilliseconds, RequestVolumeThreshold: requestVolumeThreshold, Timeout: timeoutVal, }) } // {{$clientName}} is the TChannel client for downstream service. type {{$clientName}} struct { client *zanzibar.TChannelClient circuitBreakerDisabled bool defaultDeps *zanzibar.DefaultDependencies } {{range $svc := .Services}} {{range .Methods}} {{$serviceMethod := printf "%s::%s" $svc.Name .Name -}} {{$methodName := (title (index $exposedMethods $serviceMethod)) -}} {{if $methodName -}} // {{$methodName}} is a client RPC call for method "{{$serviceMethod}}" func (c *{{$clientName}}) {{$methodName}}( ctx context.Context, reqHeaders map[string]string, {{if ne .RequestType "" -}} args {{.RequestType}}, {{end -}} ) (context.Context, {{- if ne .ResponseType "" -}} {{.ResponseType}}, {{- end -}}map[string]string, error) { var result {{.GenCodePkgName}}.{{title $svc.Name}}_{{title .Name}}_Result {{if .ResponseType -}} var resp {{.ResponseType}} {{end}} logger := c.client.ContextLogger {{if eq .RequestType "" -}} args := &{{.GenCodePkgName}}.{{title $svc.Name}}_{{title .Name}}_Args{} {{end -}} var success bool respHeaders := make(map[string]string) var err error if (c.circuitBreakerDisabled) { success, respHeaders, err = c.client.Call( ctx, "{{$svc.Name}}", "{{.Name}}", reqHeaders, args, &result) } else { // We want hystrix ckt-breaker to count errors only for system issues var clientErr error scope := c.defaultDeps.Scope.Tagged(map[string]string{ "client" : "{{$clientID}}", "methodName" : "{{$methodName}}", }) start := time.Now() circuitBreakerName := "{{$clientID}}" + "-" + "{{$methodName}}" err = hystrix.DoC(ctx, circuitBreakerName, func(ctx context.Context) error { elapsed := time.Now().Sub(start) scope.Timer("hystrix-timer").Record(elapsed) success, respHeaders, clientErr = c.client.Call( ctx, "{{$svc.Name}}", "{{.Name}}", reqHeaders, args, &result) if _, isSysErr := clientErr.(tchannel.SystemError); !isSysErr { // Declare ok if it is not a system-error return nil } return clientErr }, nil) if err == nil { // ckt-breaker was ok, bubble up client error if set err = clientErr } } if err != nil { zanzibar.AppendLogFieldsToContext(ctx, zap.String("error", fmt.Sprintf("error making tchannel call: %s", err)), logFieldErrLocation) } if err == nil && !success { switch { {{range .Exceptions -}} case result.{{title .Name}} != nil: err = result.{{title .Name}} zanzibar.AppendLogFieldsToContext(ctx, zap.Error(err), zanzibar.LogFieldErrTypeClientException, logFieldErrLocation) {{end -}} {{if ne .ResponseType "" -}} case result.Success != nil: ctx = logger.WarnZ(ctx, "Internal error. Success flag is not set for {{title .Name}}. Overriding") success = true {{end -}} default: err = errors.New("{{$clientName}} received no result or unknown exception for {{title .Name}}") zanzibar.AppendLogFieldsToContext(ctx, zap.Error(err), logFieldErrLocation) } } if err != nil { ctx = logger.WarnZ(ctx, "Client failure: TChannel client call returned error") {{if eq .ResponseType "" -}} return ctx, respHeaders, err {{else -}} return ctx, resp, respHeaders, err {{end -}} } {{if eq .ResponseType "" -}} return ctx, respHeaders, err {{else -}} resp, err = {{.GenCodePkgName}}.{{title $svc.Name}}_{{title .Name}}_Helper.UnwrapResponse(&result) if err != nil { zanzibar.AppendLogFieldsToContext(ctx, zap.Error(err), logFieldErrLocation) ctx = logger.WarnZ(ctx, "Client failure: unable to unwrap client response") } return ctx, resp, respHeaders, err {{end -}} } {{end -}} {{end -}} {{end}} `) func tchannel_clientTmplBytes() ([]byte, error) { return _tchannel_clientTmpl, nil } func tchannel_clientTmpl() (*asset, error) { bytes, err := tchannel_clientTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "tchannel_client.tmpl", size: 16209, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _tchannel_client_test_serverTmpl = []byte(`{{- /* template to render edge gateway tchannel server code */ -}} {{- $instance := .Instance }} package {{$instance.PackageInfo.PackageName}} import ( "context" "errors" "time" zanzibar "github.com/uber/zanzibar/runtime" "go.uber.org/thriftrw/wire" {{range $idx, $pkg := .IncludedPackages -}} {{$pkg.AliasName}} "{{$pkg.PackageName}}" {{end}} ) {{$exposedMethods := .ExposedMethods -}} {{range $svc := .Services}} {{range .Methods}} {{$serviceMethod := printf "%s::%s" $svc.Name .Name -}} {{$methodName := index $exposedMethods $serviceMethod -}} {{if $methodName -}} {{$privateName := lower .Name -}} {{$genCodePkg := .GenCodePkgName -}} {{$func := printf "%s%sFunc" $svc.Name (lintAcronym .Name) -}} {{$handler := printf "%s%sHandler" $svc.Name (lintAcronym .Name) -}} // {{$func}} is the handler function for "{{.Name}}" method of thrift service "{{$svc.Name}}". type {{$func}} func ( ctx context.Context, reqHeaders map[string]string, {{if ne .RequestType "" -}} args {{.RequestType}}, {{end -}} ) ({{- if ne .ResponseType "" -}}{{.ResponseType}}, {{- end -}}map[string]string, error) // New{{$handler}} wraps a handler function so it can be registered with a thrift server. func New{{$handler}}(f {{$func}}) zanzibar.TChannelHandler { return &{{$handler}}{f} } // {{$handler}} handles the "{{.Name}}" method call of thrift service "{{$svc.Name}}". type {{$handler}} struct { {{$privateName}} {{$func}} } // Handle parses request from wire value and calls corresponding handler function. func (h *{{$handler}}) Handle( ctx context.Context, reqHeaders map[string]string, wireValue *wire.Value, ) (context.Context, bool, zanzibar.RWTStruct, map[string]string, error) { var req {{$genCodePkg}}.{{title $svc.Name}}_{{title .Name}}_Args var res {{$genCodePkg}}.{{title $svc.Name}}_{{title .Name}}_Result if err := req.FromWire(*wireValue); err != nil { return ctx, false, nil, nil, err } {{- if and (eq .RequestType "") (eq .ResponseType "")}} respHeaders, err := h.{{$privateName}}(ctx, reqHeaders) {{- else if eq .RequestType ""}} r, respHeaders, err := h.{{$privateName}}(ctx, reqHeaders) {{- else if eq .ResponseType ""}} respHeaders, err := h.{{$privateName}}(ctx, reqHeaders, &req) {{- else}} r, respHeaders, err := h.{{$privateName}}(ctx, reqHeaders, &req) {{- end}} {{if eq (len .Exceptions) 0 -}} if err != nil { return ctx, false, nil, nil, err } {{if .ResponseType -}} res.Success = {{.RefResponse "r"}} {{end -}} {{else -}} if err != nil { switch v := err.(type) { {{$method := .Name -}} {{range .Exceptions -}} case *{{.Type}}: if v == nil { return ctx, false, nil, nil, errors.New( "Handler for {{$method}} returned non-nil error type *{{title .Name}} but nil value", ) } res.{{title .Name}} = v {{end -}} default: return ctx, false, nil, nil, err } } {{if .ResponseType -}} else { res.Success = {{.RefResponse "r"}} } {{end -}} {{end}} return ctx, err == nil, &res, respHeaders, nil } {{end -}} {{end -}} {{end}} `) func tchannel_client_test_serverTmplBytes() ([]byte, error) { return _tchannel_client_test_serverTmpl, nil } func tchannel_client_test_serverTmpl() (*asset, error) { bytes, err := tchannel_client_test_serverTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "tchannel_client_test_server.tmpl", size: 3065, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _tchannel_endpointTmpl = []byte(`{{- /* template to render edge gateway tchannel server code */ -}} {{- $instance := .Instance }} {{- $spec := .Spec }} package {{$instance.PackageInfo.PackageName}} {{- $middlewares := .Spec.Middlewares }} import ( "context" "runtime/debug" "time" "github.com/pkg/errors" "go.uber.org/thriftrw/wire" "go.uber.org/zap" tchannel "github.com/uber/tchannel-go" zanzibar "github.com/uber/zanzibar/runtime" {{range $idx, $pkg := .IncludedPackages -}} {{$pkg.AliasName}} "{{$pkg.PackageName}}" {{end -}} {{- if len $middlewares | ne 0 }} {{- range $idx, $middleware := $middlewares }} {{$middleware.Name | camel}} "{{$middleware.ImportPath}}" {{- end}} {{- end}} module "{{$instance.PackageInfo.ModulePackagePath}}" ) {{- $serviceMethod := printf "%s%s" (title .Method.ThriftService) (title .Method.Name) }} {{- $methodName := .Method.Name }} {{- $handlerName := printf "%sHandler" $serviceMethod }} {{- $genCodePkg := .Method.GenCodePkgName }} {{- $workflowPkg := .WorkflowPkg }} {{- $workflowInterface := printf "%sWorkflow" $serviceMethod }} {{- $deputyReqHeader := .DeputyReqHeader}} {{- $clientID := .ClientID }} {{with .Method -}} // New{{$handlerName}} creates a handler to be registered with a thrift server. func New{{$handlerName}}(deps *module.Dependencies) *{{$handlerName}} { handler := &{{$handlerName}}{ Deps: deps, } handler.endpoint = zanzibar.NewTChannelEndpoint( "{{$spec.EndpointID}}", "{{$spec.HandleID}}", "{{.ThriftService}}::{{.Name}}", {{ if len $middlewares | ne 0 -}} zanzibar.NewTchannelStack([]zanzibar.MiddlewareTchannelHandle{ {{range $idx, $middleware := $middlewares -}} deps.Middleware.{{$middleware.Name | pascal}}.NewMiddlewareHandle( {{$middleware.Name | camel}}.Options{ {{range $key, $value := $middleware.PrettyOptions -}} {{$key}} : {{$value}}, {{end -}} }, ), {{end -}} }, handler), {{- else -}} handler, {{- end}} ) return handler } // {{$handlerName}} is the handler for "{{.ThriftService}}::{{.Name}}". type {{$handlerName}} struct { Deps *module.Dependencies endpoint *zanzibar.TChannelEndpoint } // Register adds the tchannel handler to the gateway's tchannel router func (h *{{$handlerName}}) Register(g *zanzibar.Gateway) error { return g.ServerTChannelRouter.Register(h.endpoint) } // Handle handles RPC call of "{{.ThriftService}}::{{.Name}}". func (h *{{$handlerName}}) Handle( ctx context.Context, reqHeaders map[string]string, wireValue *wire.Value, ) (ctxRes context.Context, isSuccessful bool, response zanzibar.RWTStruct, headers map[string]string, e error) { defer func() { if r := recover(); r != nil { stacktrace := string(debug.Stack()) e = errors.Errorf("enpoint panic: %v, stacktrace: %v", r, stacktrace) ctx = h.Deps.Default.ContextLogger.ErrorZ( ctx, "Endpoint failure: endpoint panic", zap.Error(e), zap.String("stacktrace", stacktrace)) h.Deps.Default.ContextMetrics.IncCounter(ctx, zanzibar.MetricEndpointPanics, 1) isSuccessful = false response = nil headers = nil } }() wfReqHeaders := zanzibar.ServerTChannelHeader(reqHeaders) {{if .ReqHeaders -}} if err := wfReqHeaders.EnsureContext(ctx, {{.ReqHeaders | printf "%#v" }}, h.Deps.Default.ContextLogger); err != nil { return ctx, false, nil, nil, errors.Wrapf( err, "%s.%s (%s) missing request headers", h.endpoint.EndpointID, h.endpoint.HandlerID, h.endpoint.Method, ) } {{- end}} var res {{$genCodePkg}}.{{title .ThriftService}}_{{title .Name}}_Result {{if ne .RequestType "" -}} var req {{unref .RequestType}} if err := req.FromWire(*wireValue); err != nil { ctx = h.Deps.Default.ContextLogger.ErrorZ(ctx, "Endpoint failure: error converting request from wire", zap.Error(err)) return ctx, false, nil, nil, errors.Wrapf( err, "Error converting %s.%s (%s) request from wire", h.endpoint.EndpointID, h.endpoint.HandlerID, h.endpoint.Method, ) } if hostPort, ok := reqHeaders["{{$deputyReqHeader}}"]; ok { if hostPort != "" { return h.redirectToDeputy(ctx, reqHeaders, hostPort, &req, &res) } } {{end -}} workflow := {{if $workflowPkg}}{{$workflowPkg}}.{{end}}New{{$workflowInterface}}(h.Deps) {{if and (eq .RequestType "") (eq .ResponseType "")}} ctx, wfResHeaders, err := workflow.Handle(ctx, wfReqHeaders) {{else if eq .RequestType ""}} ctx, r, wfResHeaders, err := workflow.Handle(ctx, wfReqHeaders) {{else if eq .ResponseType ""}} ctx, wfResHeaders, err := workflow.Handle(ctx, wfReqHeaders, &req) {{else}} ctx, r, wfResHeaders, err := workflow.Handle(ctx, wfReqHeaders, &req) {{end}} resHeaders := map[string]string{} if wfResHeaders != nil { for _, key := range wfResHeaders.Keys() { resHeaders[key], _ = wfResHeaders.Get(key) } } {{if eq (len .Exceptions) 0 -}} if err != nil { ctx = h.Deps.Default.ContextLogger.ErrorZ(ctx, "Endpoint failure: handler returned error", zap.Error(err)) return ctx, false, nil, resHeaders, err } res.Success = {{.RefResponse "r"}} {{else -}} if err != nil { switch v := err.(type) { {{$method := .Name -}} {{range .Exceptions -}} case *{{.Type}}: ctxWithError := zanzibar.WithScopeTagsDefault(ctx, map[string]string{ "app-error": "{{.Type}}", }, h.Deps.Default.ContextMetrics.Scope()) h.Deps.Default.ContextMetrics.IncCounter(ctxWithError, zanzibar.MetricEndpointAppErrors, 1) if v == nil { ctx = h.Deps.Default.ContextLogger.ErrorZ( ctx, "Endpoint failure: handler returned non-nil error type *{{.Type}} but nil value", zap.Error(err), ) return ctx, false, nil, resHeaders, errors.Errorf( "%s.%s (%s) handler returned non-nil error type *{{.Type}} but nil value", h.endpoint.EndpointID, h.endpoint.HandlerID, h.endpoint.Method, ) } res.{{title .Name}} = v {{end -}} default: ctxWithError := zanzibar.WithScopeTagsDefault(ctx, map[string]string{ "app-error": "unknown", }, h.Deps.Default.ContextMetrics.Scope()) h.Deps.Default.ContextMetrics.IncCounter(ctxWithError, zanzibar.MetricEndpointAppErrors, 1) ctx = h.Deps.Default.ContextLogger.ErrorZ(ctx, "Endpoint failure: handler returned error", zap.Error(err)) return ctx, false, nil, resHeaders, errors.Wrapf( err, "%s.%s (%s) handler returned error", h.endpoint.EndpointID, h.endpoint.HandlerID, h.endpoint.Method, ) } } {{if ne .ResponseType "" -}} else { res.Success = {{.RefResponse "r"}} } {{end -}} {{end}} {{- if .ResHeaders}} if wfResHeaders == nil { return ctx, false, nil, nil, errors.Wrapf( errors.Errorf( "Missing mandatory headers: %s", strings.Join({{.ResHeaders | printf "%#v" }}, ", "), ), "%s.%s (%s) missing response headers", h.endpoint.EndpointID, h.endpoint.HandlerID, h.endpoint.Method, ) } if err := wfResHeaders.EnsureContext(ctx, {{.ResHeaders | printf "%#v" }}, h.Deps.Default.ContextLogger); err != nil { return ctx, false, nil, nil, errors.Wrapf( err, "%s.%s (%s) missing response headers", h.endpoint.EndpointID, h.endpoint.HandlerID, h.endpoint.Method, ) } {{- end}} return ctx, err == nil, &res, resHeaders, nil } {{if ne .RequestType "" -}} // redirectToDeputy sends the request to deputy hostPort func (h *{{$handlerName}}) redirectToDeputy( ctx context.Context, reqHeaders map[string]string, hostPort string, req *{{unref .RequestType}}, res *{{$genCodePkg}}.{{title .ThriftService}}_{{title .Name}}_Result, ) (context.Context, bool, zanzibar.RWTStruct, map[string]string, error) { var routingKey string if h.Deps.Default.Config.ContainsKey("tchannel.routingKey") { routingKey = h.Deps.Default.Config.MustGetString("tchannel.routingKey") } serviceName := h.Deps.Default.Config.MustGetString("tchannel.serviceName") timeout := time.Millisecond * time.Duration( h.Deps.Default.Config.MustGetInt("tchannel.deputy.timeout"), ) timeoutPerAttemptConf := int(h.Deps.Default.Config.MustGetInt("tchannel.deputy.timeoutPerAttempt")) timeoutPerAttempt := time.Millisecond * time.Duration(timeoutPerAttemptConf) maxAttempts := int(h.Deps.Default.Config.MustGetInt("clients.{{$clientID}}.retryCount")) methodNames := map[string]string{ "{{.ThriftService}}::{{.Name}}": "{{$methodName}}", } deputyChannel, err := tchannel.NewChannel(serviceName, nil) if err != nil { ctx = h.Deps.Default.ContextLogger.ErrorZ(ctx, "Deputy Failure", zap.Error(err)) } defer deputyChannel.Close() deputyChannel.Peers().Add(hostPort) client := zanzibar.NewTChannelClientContext( deputyChannel, h.Deps.Default.ContextLogger, h.Deps.Default.ContextMetrics, h.Deps.Default.ContextExtractor, &zanzibar.TChannelClientOption{ ServiceName: serviceName, ClientID: "{{$clientID}}", MethodNames: methodNames, Timeout: timeout, TimeoutPerAttempt: timeoutPerAttempt, RoutingKey: &routingKey, }, ) timeoutAndRetryConfig := zanzibar.BuildTimeoutAndRetryConfig(timeoutPerAttemptConf, zanzibar.DefaultBackOffTimeAcrossRetriesConf, maxAttempts, zanzibar.DefaultScaleFactor) ctx = zanzibar.WithTimeAndRetryOptions(ctx, timeoutAndRetryConfig) success, respHeaders, err := client.Call(ctx, "{{.ThriftService}}", "{{$methodName}}", reqHeaders, req, res) return ctx, success, res, respHeaders, err } {{end -}} {{end -}} `) func tchannel_endpointTmplBytes() ([]byte, error) { return _tchannel_endpointTmpl, nil } func tchannel_endpointTmpl() (*asset, error) { bytes, err := tchannel_endpointTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "tchannel_endpoint.tmpl", size: 9383, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _workflowTmpl = []byte(`{{/* template to render gateway workflow interface code */ -}} {{- $instance := .Instance }} package workflow {{- $endpointType := .Spec.EndpointType }} {{- $reqHeaderMap := .ReqHeaders }} {{- $reqHeaderMapKeys := .ReqHeadersKeys }} {{- $defaultHeaders := .DefaultHeaders }} {{- $reqHeaderRequiredKeys := .ReqRequiredHeadersKeys }} {{- $resHeaderMap := .ResHeaders }} {{- $resHeaderMapKeys := .ResHeadersKeys }} {{- $clientID := .ClientID }} {{- $clientName := title .ClientName }} {{- $clientType := .ClientType }} {{- $clientMethodName := title .ClientMethodName }} {{- $serviceMethod := printf "%s%s" (title .Method.ThriftService) (title .Method.Name) }} {{- $workflowInterface := printf "%sWorkflow" $serviceMethod }} {{- $workflowStruct := camel $workflowInterface }} {{- $endpointId := .Spec.EndpointID }} {{- $handleId := .Spec.HandleID }} {{- $handleIdDotEndpointIdFmt := printf "%s.%s" ($endpointId) ($handleId) }} import ( "context" "net/textproto" "github.com/uber/zanzibar/config" zanzibar "github.com/uber/zanzibar/runtime" {{range $idx, $pkg := .IncludedPackages -}} {{$pkg.AliasName}} "{{$pkg.PackageName}}" {{end -}} {{if .Method.Downstream }} {{- range $idx, $pkg := .Method.Downstream.IncludedPackages -}} {{$file := basePath $pkg.PackageName -}} {{$pkg.AliasName}} "{{$pkg.PackageName}}" {{end}} {{- end}} module "{{$instance.PackageInfo.ModulePackagePath}}" "go.uber.org/zap" ) {{with .Method -}} // {{$workflowInterface}} defines the interface for {{$serviceMethod}} workflow type {{$workflowInterface}} interface { Handle( {{- if and (eq .RequestType "") (eq .ResponseType "") }} ctx context.Context, reqHeaders zanzibar.Header, ) (context.Context, zanzibar.Header, error) {{else if eq .RequestType "" }} ctx context.Context, reqHeaders zanzibar.Header, ) (context.Context, {{.ResponseType}}, zanzibar.Header, error) {{else if eq .ResponseType "" }} ctx context.Context, reqHeaders zanzibar.Header, r {{.RequestType}}, ) (context.Context, zanzibar.Header, error) {{else}} ctx context.Context, reqHeaders zanzibar.Header, r {{.RequestType}}, ) (context.Context, {{.ResponseType}}, zanzibar.Header, error) {{- end}} } {{end -}} {{- if .Method.Downstream }} {{- $method := .Method -}} {{- with .Method -}} {{- $methodName := title .Name }} {{- $clientPackage := .Downstream.PackageName -}} {{- $clientMethod := .DownstreamMethod -}} {{- $clientReqType := fullTypeName ($clientMethod).RequestType ($clientPackage) -}} {{- $clientResType := fullTypeName ($clientMethod).ResponseType ($clientPackage) -}} {{- $clientExceptions := .DownstreamMethod.Exceptions -}} // New{{$workflowInterface}} creates a workflow func New{{$workflowInterface}}(deps *module.Dependencies) {{$workflowInterface}} { var whitelistedDynamicHeaders []string if deps.Default.Config.ContainsKey("clients.{{$clientID}}.alternates") { var alternateServiceDetail config.AlternateServiceDetail deps.Default.Config.MustGetStruct("clients.{{$clientID}}.alternates", &alternateServiceDetail) for _, routingConfig := range alternateServiceDetail.RoutingConfigs { whitelistedDynamicHeaders = append( whitelistedDynamicHeaders, textproto.CanonicalMIMEHeaderKey(routingConfig.HeaderName)) } } return &{{$workflowStruct}}{ Clients: deps.Client, Logger: deps.Default.Logger, whitelistedDynamicHeaders: whitelistedDynamicHeaders, defaultDeps: deps.Default, } } // {{$workflowStruct}} calls thrift client {{$clientName}}.{{$clientMethodName}} type {{$workflowStruct}} struct { Clients *module.ClientDependencies Logger *zap.Logger whitelistedDynamicHeaders []string defaultDeps *zanzibar.DefaultDependencies } // Handle calls thrift client. func (w {{$workflowStruct}}) Handle( {{- if and (eq .RequestType "") (eq .ResponseType "") }} ctx context.Context, reqHeaders zanzibar.Header, ) (context.Context, zanzibar.Header, error) { {{else if eq .RequestType "" }} ctx context.Context, reqHeaders zanzibar.Header, ) (context.Context, {{.ResponseType}}, zanzibar.Header, error) { {{else if eq .ResponseType "" }} ctx context.Context, reqHeaders zanzibar.Header, r {{.RequestType}}, ) (context.Context, zanzibar.Header, error) { {{else}} ctx context.Context, reqHeaders zanzibar.Header, r {{.RequestType}}, ) (context.Context, {{.ResponseType}}, zanzibar.Header, error) { {{- end}} {{- if ne .RequestType "" -}} clientRequest := convertTo{{title .Name}}ClientRequest(r) {{end}} {{- if len $method.PropagateHeadersGoStatements | ne 0 }} {{- if ne .RequestType "" -}} clientRequest = propagateHeaders{{title .Name}}ClientRequests(clientRequest, reqHeaders) {{- else -}} clientRequest := propagateHeaders{{title .Name}}ClientRequests(nil, reqHeaders) {{end}} {{end}} clientHeaders := map[string]string{} {{if (ne (len $defaultHeaders) 0) }} var ok bool var h string var k string {{range $i, $k := $defaultHeaders}} k = textproto.CanonicalMIMEHeaderKey("{{$k}}") h, ok = reqHeaders.Get(k) if ok { clientHeaders[k] = h } {{- end -}} {{- end -}} {{if (ne (len $reqHeaderMapKeys) 0) }} {{if (eq (len $defaultHeaders) 0) }} var ok bool var h string {{- end -}} {{- end -}} {{range $i, $k := $reqHeaderMapKeys}} h, ok = reqHeaders.Get("{{$k}}") if ok { {{- $typedHeader := index $reqHeaderMap $k -}} clientHeaders["{{$typedHeader.TransformTo}}"] = h } {{- end}} for _, whitelistedHeader := range w.whitelistedDynamicHeaders { headerVal, ok := reqHeaders.Get(whitelistedHeader) if ok { clientHeaders[whitelistedHeader] = headerVal } } //when maxRetry is 0, timeout per client level is used & one attempt is made, and timoutPerAttempt is not used var timeoutAndRetryConfig *zanzibar.TimeoutAndRetryOptions //when endpoint level timeout information is available, override it with client level config if w.defaultDeps.Config.ContainsKey("endpoints.{{$handleIdDotEndpointIdFmt}}.timeoutPerAttempt") { scaleFactor := w.defaultDeps.Config.MustGetFloat("endpoints.{{$handleIdDotEndpointIdFmt}}.scaleFactor") maxRetry := int(w.defaultDeps.Config.MustGetInt("endpoints.{{$handleIdDotEndpointIdFmt}}.retryCount")) backOffTimeAcrossRetriesCfg := int(w.defaultDeps.Config.MustGetInt("endpoints.{{$handleIdDotEndpointIdFmt}}.backOffTimeAcrossRetries")) timeoutPerAttemptConf := int(w.defaultDeps.Config.MustGetInt("endpoints.{{$handleIdDotEndpointIdFmt}}.timeoutPerAttempt")) timeoutAndRetryConfig = zanzibar.BuildTimeoutAndRetryConfig(int(timeoutPerAttemptConf), backOffTimeAcrossRetriesCfg, maxRetry, scaleFactor) ctx = zanzibar.WithTimeAndRetryOptions(ctx, timeoutAndRetryConfig) } {{if and (eq $clientReqType "") (eq $clientResType "")}} {{if (eq (len $resHeaderMap) 0) -}} ctx, _, err := w.Clients.{{$clientName}}.{{$clientMethodName}}(ctx, clientHeaders) {{else}} ctx, cliRespHeaders, err := w.Clients.{{$clientName}}.{{$clientMethodName}}(ctx, clientHeaders) {{- end }} {{else if eq $clientReqType ""}} {{if (eq (len $resHeaderMap) 0) -}} ctx, clientRespBody, cliRespHeaders, err := w.Clients.{{$clientName}}.{{$clientMethodName}}( ctx, clientHeaders, ) {{else}} ctx, clientRespBody, cliRespHeaders, err := w.Clients.{{$clientName}}.{{$clientMethodName}}( ctx, clientHeaders, ) {{- end }} {{else if eq $clientResType ""}} {{if (eq (len $resHeaderMap) 0) -}} ctx, _, err := w.Clients.{{$clientName}}.{{$clientMethodName}}( ctx, clientHeaders, clientRequest, ) {{else}} ctx, cliRespHeaders, err := w.Clients.{{$clientName}}.{{$clientMethodName}}( ctx, clientHeaders, clientRequest, ) {{- end }} {{else}} {{if (eq (len $resHeaderMap) 0) -}} ctx, clientRespBody, cliRespHeaders, err := w.Clients.{{$clientName}}.{{$clientMethodName}}( ctx, clientHeaders, clientRequest, ) {{else}} ctx, clientRespBody, cliRespHeaders, err := w.Clients.{{$clientName}}.{{$clientMethodName}}( ctx, clientHeaders, clientRequest, ) {{- end }} {{end -}} {{- $responseType := .ResponseType }} if err != nil { switch errValue := err.(type) { {{range $idx, $cException := $clientExceptions}} case *{{$cException.Type}}: serverErr := convert{{$methodName}}{{title $cException.Name}}( errValue, ) {{if eq $responseType ""}} return ctx, nil, serverErr {{else if eq $responseType "string" }} return ctx, "", nil, serverErr {{else}} return ctx, nil, nil, serverErr {{end}} {{end}} default: w.Logger.Warn("Client failure: could not make client request", zap.Error(errValue), zap.String("client", "{{$clientName}}"), ) {{if eq $responseType ""}} return ctx, nil, err {{else if eq $responseType "string" }} return ctx, "", nil, err {{else}} return ctx, nil, nil, err {{end}} } } // Filter and map response headers from client to server response. {{if eq $endpointType "tchannel" -}} resHeaders := zanzibar.ServerTChannelHeader{} {{- else -}} resHeaders := zanzibar.ServerHTTPHeader{} {{- end -}} {{range $i, $k := $resHeaderMapKeys}} {{- $resHeaderVal := index $resHeaderMap $k}} if cliRespHeaders != nil { resHeaders.Set("{{$resHeaderVal.TransformTo}}", cliRespHeaders["{{$k}}"]) } {{- end}} {{if eq .ResponseType "" -}} return ctx, resHeaders, nil {{- else -}} response := convert{{.DownstreamService}}{{title .Name}}ClientResponse(clientRespBody) if val, ok := cliRespHeaders[zanzibar.ClientResponseDurationKey]; ok { resHeaders.Set(zanzibar.ClientResponseDurationKey, val) } resHeaders.Set(zanzibar.ClientTypeKey, "{{$clientType}}") return ctx, response, resHeaders, nil {{- end -}} } {{if and (ne .RequestType "") (ne $clientReqType "") -}} {{ range $key, $line := $method.ConvertRequestGoStatements -}} {{$line}} {{ end }} {{end -}} {{- $exceptionIndex := .ExceptionsIndex }} {{range $idx, $cException := $clientExceptions}} {{- $sException := index $exceptionIndex $cException.Name -}} func convert{{$methodName}}{{title $cException.Name}}( clientError *{{$cException.Type}}, ) *{{$sException.Type}} { // TODO: Add error fields mapping here. serverError := &{{$sException.Type}}{} return serverError } {{end}} {{if and (ne .ResponseType "") (ne $clientResType "") -}} {{ range $key, $line := $method.ConvertResponseGoStatements -}} {{$line}} {{ end }} {{end -}} {{- if len $method.PropagateHeadersGoStatements | ne 0 }} {{ range $key, $line := $method.PropagateHeadersGoStatements -}} {{$line}} {{ end }} {{end -}} {{end -}} {{end -}} `) func workflowTmplBytes() ([]byte, error) { return _workflowTmpl, nil } func workflowTmpl() (*asset, error) { bytes, err := workflowTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "workflow.tmpl", size: 10454, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _workflow_mockTmpl = []byte(`{{$instance := .Instance -}} {{$espec := .EndpointSpec -}} {{$clientsWithFixture := .ClientsWithFixture -}} {{$serviceMethod := printf "%s%s" (title $espec.ThriftServiceName) (title $espec.ThriftMethodName) -}} {{$workflowInterface := printf "%sWorkflow" $serviceMethod -}} {{$leafWithFixture := .ClientsWithFixture -}} {{$leafClass := firstIsClientOrEmpty $instance.DependencyOrder -}} {{$mockType := printf "Mock%sNodes" (title $leafClass) -}} {{$classPkg := "module" -}} package mock{{lower (camel $instance.InstanceName)}}workflow import ( "testing" "github.com/golang/mock/gomock" "github.com/uber-go/tally" "go.uber.org/zap" zanzibar "github.com/uber/zanzibar/runtime" {{range $classType, $moduleInstances := $instance.RecursiveDependencies -}} {{range $idx, $moduleInstance := $moduleInstances -}} {{if eq $classType $leafClass -}} {{$moduleInstance.PackageInfo.GeneratedPackageAlias}}mock "{{$moduleInstance.PackageInfo.GeneratedPackagePath}}/mock-client" {{if (index $leafWithFixture $moduleInstance.InstanceName) -}} fixture{{$moduleInstance.PackageInfo.ImportPackageAlias}} "{{index $leafWithFixture $moduleInstance.InstanceName}}" {{end -}} {{else -}} {{$moduleInstance.PackageInfo.ImportPackageAlias}} "{{$moduleInstance.PackageInfo.ImportPackagePath}}" {{$moduleInstance.PackageInfo.ModulePackageAlias}} "{{$moduleInstance.PackageInfo.ModulePackagePath}}" {{end -}} {{end -}} {{end -}} {{$instance.PackageInfo.PackageAlias}} "{{$espec.WorkflowImportPath}}" module "{{$instance.PackageInfo.ModulePackagePath}}" workflow "{{$instance.PackageInfo.GeneratedPackagePath}}/workflow" ) // New{{$workflowInterface}}Mock creates a workflow with mock clients func New{{$workflowInterface}}Mock(t *testing.T) (workflow.{{$workflowInterface}}, *{{$mockType}}) { {{ if (len $instance.DependencyOrder) -}} ctrl := gomock.NewController(t) {{ else -}} {{camel $mockType}} := &{{$mockType}}{} {{ end }} initializedDefaultDependencies := &zanzibar.DefaultDependencies { Logger: zap.NewNop(), Scope: tally.NewTestScope("", make(map[string]string)), } initializedDefaultDependencies.ContextLogger = zanzibar.NewContextLogger(initializedDefaultDependencies.Logger) initializedDefaultDependencies.ContextExtractor = &zanzibar.ContextExtractors{} {{range $idx, $className := $instance.DependencyOrder}} {{- $moduleInstances := (index $instance.RecursiveDependencies $className)}} {{- $initializedDeps := printf "initialized%sDependencies" (title $className) }} {{$initializedDeps}} := &{{$className}}DependenciesNodes{} {{if eq $className $leafClass -}} {{camel $mockType}} := &{{$mockType}}{ {{- range $idx, $dependency := $moduleInstances}} {{- $pkgInfo := $dependency.PackageInfo }} {{- if (index $leafWithFixture $dependency.InstanceName) }} {{$pkgInfo.QualifiedInstanceName}}: {{$pkgInfo.GeneratedPackageAlias}}mock.New(ctrl, fixture{{$pkgInfo.ImportPackageAlias}}.Fixture), {{- else }} {{$pkgInfo.QualifiedInstanceName}}: {{$pkgInfo.GeneratedPackageAlias}}mock.NewMock{{title $className}}(ctrl), {{- end }} {{- end }} } {{- range $idx, $dependency := $moduleInstances}} {{- $pkgInfo := $dependency.PackageInfo }} {{$initializedDeps}}.{{$pkgInfo.QualifiedInstanceName}} = {{camel $mockType}}.{{$pkgInfo.QualifiedInstanceName}} {{- end }} {{else -}} {{- range $idx, $dependency := $moduleInstances}} {{- $pkgInfo := $dependency.PackageInfo }} {{$initializedDeps}}.{{$pkgInfo.QualifiedInstanceName}} = {{$pkgInfo.ImportPackageAlias}}.{{$pkgInfo.ExportName}}(&{{$pkgInfo.ModulePackageAlias}}.Dependencies{ Default: initializedDefaultDependencies, {{- range $className, $moduleInstances := $dependency.ResolvedDependencies}} {{$className | pascal}}: &{{$pkgInfo.ModulePackageAlias}}.{{$className | pascal}}Dependencies{ {{- range $idy, $subDependency := $moduleInstances}} {{$subDependency.PackageInfo.QualifiedInstanceName}}: initialized{{$className | pascal}}Dependencies.{{$subDependency.PackageInfo.QualifiedInstanceName}}, {{- end}} }, {{- end}} }) {{- end}} {{end}} {{end}} w := {{$instance.PackageInfo.PackageAlias}}.New{{$workflowInterface}}( &{{$classPkg}}.Dependencies{ Default: initializedDefaultDependencies, {{- range $className, $moduleInstances := $instance.ResolvedDependencies}} {{$className | pascal}}: &{{$classPkg}}.{{$className | pascal}}Dependencies{ {{- range $idy, $subDependency := $moduleInstances}} {{$subDependency.PackageInfo.QualifiedInstanceName}}: initialized{{$className | pascal}}Dependencies.{{$subDependency.PackageInfo.QualifiedInstanceName}}, {{- end}} }, {{- end}} }, ) return w, {{camel $mockType}} }`) func workflow_mockTmplBytes() ([]byte, error) { return _workflow_mockTmpl, nil } func workflow_mockTmpl() (*asset, error) { bytes, err := workflow_mockTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "workflow_mock.tmpl", size: 4653, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } var _workflow_mock_clients_typeTmpl = []byte(`{{$instance := .Instance -}} {{$leafWithFixture := .ClientsWithFixture -}} {{$leafClass := firstIsClientOrEmpty $instance.DependencyOrder -}} {{$typeName := printf "Mock%sNodes" (title $leafClass) -}} {{$mockDeps := index $instance.RecursiveDependencies $leafClass -}} package mock{{lower (camel $instance.InstanceName)}}workflow import ( {{range $classType, $moduleInstances := $instance.RecursiveDependencies -}} {{range $idx, $moduleInstance := $moduleInstances -}} {{if eq $classType $leafClass -}} {{$moduleInstance.PackageInfo.ImportPackageAlias}} "{{$moduleInstance.PackageInfo.ImportPackagePath}}" {{$moduleInstance.PackageInfo.GeneratedPackageAlias}}mock "{{$moduleInstance.PackageInfo.GeneratedPackagePath}}/mock-{{$leafClass}}" {{else -}} {{$moduleInstance.PackageInfo.ImportPackageAlias}} "{{$moduleInstance.PackageInfo.ImportPackagePath}}" {{end -}} {{end -}} {{end -}} ) // {{$typeName}} contains mock {{$leafClass}} dependencies for the {{$instance.InstanceName}} {{$instance.ClassName}} module type {{$typeName}} struct { {{range $idx, $moduleInstance := $mockDeps -}} {{- $pkgInfo := $moduleInstance.PackageInfo }} {{- if (index $leafWithFixture $moduleInstance.InstanceName) }} {{$pkgInfo.QualifiedInstanceName}} *{{$pkgInfo.GeneratedPackageAlias}}mock.Mock{{$pkgInfo.ExportType}}WithFixture {{- else }} {{$pkgInfo.QualifiedInstanceName}} *{{$pkgInfo.GeneratedPackageAlias}}mock.Mock{{$pkgInfo.ExportType}} {{- end }} {{- end }} } {{range $idx, $className := $instance.DependencyOrder -}} {{$moduleInstances := (index $instance.RecursiveDependencies $className) -}} // {{$className}}DependenciesNodes contains {{$className}} dependencies type {{$className}}DependenciesNodes struct { {{ range $idx, $dependency := $moduleInstances -}} {{$dependency.PackageInfo.QualifiedInstanceName}} {{$dependency.PackageInfo.ImportPackageAlias}}.{{$dependency.PackageInfo.ExportType}} {{end -}} } {{end -}} `) func workflow_mock_clients_typeTmplBytes() ([]byte, error) { return _workflow_mock_clients_typeTmpl, nil } func workflow_mock_clients_typeTmpl() (*asset, error) { bytes, err := workflow_mock_clients_typeTmplBytes() if err != nil { return nil, err } info := bindataFileInfo{name: "workflow_mock_clients_type.tmpl", size: 1935, mode: os.FileMode(420), modTime: time.Unix(1, 0)} a := &asset{bytes: bytes, info: info} return a, nil } // Asset loads and returns the asset for the given name. // It returns an error if the asset could not be found or // could not be loaded. func Asset(name string) ([]byte, error) { cannonicalName := strings.Replace(name, "\\", "/", -1) if f, ok := _bindata[cannonicalName]; ok { a, err := f() if err != nil { return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) } return a.bytes, nil } return nil, fmt.Errorf("Asset %s not found", name) } // MustAsset is like Asset but panics when Asset would return an error. // It simplifies safe initialization of global variables. func MustAsset(name string) []byte { a, err := Asset(name) if err != nil { panic("asset: Asset(" + name + "): " + err.Error()) } return a } // AssetInfo loads and returns the asset info for the given name. // It returns an error if the asset could not be found or // could not be loaded. func AssetInfo(name string) (os.FileInfo, error) { cannonicalName := strings.Replace(name, "\\", "/", -1) if f, ok := _bindata[cannonicalName]; ok { a, err := f() if err != nil { return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) } return a.info, nil } return nil, fmt.Errorf("AssetInfo %s not found", name) } // AssetNames returns the names of the assets. func AssetNames() []string { names := make([]string, 0, len(_bindata)) for name := range _bindata { names = append(names, name) } return names } // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ "augmented_mock.tmpl": augmented_mockTmpl, "clientless-workflow.tmpl": clientlessWorkflowTmpl, "dependency_struct.tmpl": dependency_structTmpl, "endpoint.tmpl": endpointTmpl, "endpoint_collection.tmpl": endpoint_collectionTmpl, "endpoint_test.tmpl": endpoint_testTmpl, "endpoint_test_tchannel_client.tmpl": endpoint_test_tchannel_clientTmpl, "fixture_types.tmpl": fixture_typesTmpl, "grpc_client.tmpl": grpc_clientTmpl, "http_client.tmpl": http_clientTmpl, "http_client_test.tmpl": http_client_testTmpl, "main.tmpl": mainTmpl, "main_test.tmpl": main_testTmpl, "middleware_http.tmpl": middleware_httpTmpl, "middleware_tchannel.tmpl": middleware_tchannelTmpl, "module_class_initializer.tmpl": module_class_initializerTmpl, "module_initializer.tmpl": module_initializerTmpl, "module_mock_initializer.tmpl": module_mock_initializerTmpl, "service.tmpl": serviceTmpl, "service_mock.tmpl": service_mockTmpl, "structs.tmpl": structsTmpl, "tchannel_client.tmpl": tchannel_clientTmpl, "tchannel_client_test_server.tmpl": tchannel_client_test_serverTmpl, "tchannel_endpoint.tmpl": tchannel_endpointTmpl, "workflow.tmpl": workflowTmpl, "workflow_mock.tmpl": workflow_mockTmpl, "workflow_mock_clients_type.tmpl": workflow_mock_clients_typeTmpl, } // AssetDir returns the file names below a certain // directory embedded in the file by go-bindata. // For example if you run go-bindata on data/... and data contains the // following hierarchy: // // data/ // foo.txt // img/ // a.png // b.png // // then AssetDir("data") would return []string{"foo.txt", "img"} // AssetDir("data/img") would return []string{"a.png", "b.png"} // AssetDir("foo.txt") and AssetDir("notexist") would return an error // AssetDir("") will return []string{"data"}. func AssetDir(name string) ([]string, error) { node := _bintree if len(name) != 0 { cannonicalName := strings.Replace(name, "\\", "/", -1) pathList := strings.Split(cannonicalName, "/") for _, p := range pathList { node = node.Children[p] if node == nil { return nil, fmt.Errorf("Asset %s not found", name) } } } if node.Func != nil { return nil, fmt.Errorf("Asset %s not found", name) } rv := make([]string, 0, len(node.Children)) for childName := range node.Children { rv = append(rv, childName) } return rv, nil } type bintree struct { Func func() (*asset, error) Children map[string]*bintree } var _bintree = &bintree{nil, map[string]*bintree{ "augmented_mock.tmpl": &bintree{augmented_mockTmpl, map[string]*bintree{}}, "clientless-workflow.tmpl": &bintree{clientlessWorkflowTmpl, map[string]*bintree{}}, "dependency_struct.tmpl": &bintree{dependency_structTmpl, map[string]*bintree{}}, "endpoint.tmpl": &bintree{endpointTmpl, map[string]*bintree{}}, "endpoint_collection.tmpl": &bintree{endpoint_collectionTmpl, map[string]*bintree{}}, "endpoint_test.tmpl": &bintree{endpoint_testTmpl, map[string]*bintree{}}, "endpoint_test_tchannel_client.tmpl": &bintree{endpoint_test_tchannel_clientTmpl, map[string]*bintree{}}, "fixture_types.tmpl": &bintree{fixture_typesTmpl, map[string]*bintree{}}, "grpc_client.tmpl": &bintree{grpc_clientTmpl, map[string]*bintree{}}, "http_client.tmpl": &bintree{http_clientTmpl, map[string]*bintree{}}, "http_client_test.tmpl": &bintree{http_client_testTmpl, map[string]*bintree{}}, "main.tmpl": &bintree{mainTmpl, map[string]*bintree{}}, "main_test.tmpl": &bintree{main_testTmpl, map[string]*bintree{}}, "middleware_http.tmpl": &bintree{middleware_httpTmpl, map[string]*bintree{}}, "middleware_tchannel.tmpl": &bintree{middleware_tchannelTmpl, map[string]*bintree{}}, "module_class_initializer.tmpl": &bintree{module_class_initializerTmpl, map[string]*bintree{}}, "module_initializer.tmpl": &bintree{module_initializerTmpl, map[string]*bintree{}}, "module_mock_initializer.tmpl": &bintree{module_mock_initializerTmpl, map[string]*bintree{}}, "service.tmpl": &bintree{serviceTmpl, map[string]*bintree{}}, "service_mock.tmpl": &bintree{service_mockTmpl, map[string]*bintree{}}, "structs.tmpl": &bintree{structsTmpl, map[string]*bintree{}}, "tchannel_client.tmpl": &bintree{tchannel_clientTmpl, map[string]*bintree{}}, "tchannel_client_test_server.tmpl": &bintree{tchannel_client_test_serverTmpl, map[string]*bintree{}}, "tchannel_endpoint.tmpl": &bintree{tchannel_endpointTmpl, map[string]*bintree{}}, "workflow.tmpl": &bintree{workflowTmpl, map[string]*bintree{}}, "workflow_mock.tmpl": &bintree{workflow_mockTmpl, map[string]*bintree{}}, "workflow_mock_clients_type.tmpl": &bintree{workflow_mock_clients_typeTmpl, map[string]*bintree{}}, }} // RestoreAsset restores an asset under the given directory func RestoreAsset(dir, name string) error { data, err := Asset(name) if err != nil { return err } info, err := AssetInfo(name) if err != nil { return err } err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) if err != nil { return err } err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) if err != nil { return err } err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) if err != nil { return err } return nil } // RestoreAssets restores an asset under the given directory recursively func RestoreAssets(dir, name string) error { children, err := AssetDir(name) // File if err != nil { return RestoreAsset(dir, name) } // Dir for _, child := range children { err = RestoreAssets(dir, filepath.Join(name, child)) if err != nil { return err } } return nil } func _filePath(dir, name string) string { cannonicalName := strings.Replace(name, "\\", "/", -1) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) }