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, "/")...)...)
}