// JSON starts an http run using JSON encoding func JSON(t crossdock.T, dispatcher yarpc.Dispatcher) { fatals := crossdock.Fatals(t) client := json.New(dispatcher.Channel("oneway-test")) token := getRandomID() ack, err := client.CallOneway( context.Background(), yarpc.NewReqMeta().Procedure("echo/json"), &jsonToken{Token: token}, ) // ensure channel hasn't been filled yet select { case <-serverCalledBack: fatals.FailNow("oneway json test failed", "client waited for server to fill channel") default: } fatals.NoError(err, "call to oneway/json failed: %v", err) fatals.NotNil(ack, "ack is nil") serverToken := <-serverCalledBack fatals.Equal(token, string(serverToken), "Client/Server token mismatch") }
func jsonCall(dispatcher yarpc.Dispatcher, headers yarpc.Headers, token string) (string, yarpc.CallResMeta, error) { client := json.New(dispatcher.Channel(serverName)) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() reqMeta := yarpc.NewReqMeta().Procedure("echo").Headers(headers) reqBody := &jsonEcho{Token: token} var resBody jsonEcho resMeta, err := client.Call(ctx, reqMeta, reqBody, &resBody) return resBody.Token, resMeta, err }
// Phone implements the phone procedure func Phone(ctx context.Context, reqMeta yarpc.ReqMeta, body *PhoneRequest) (*PhoneResponse, yarpc.ResMeta, error) { var outbound transport.UnaryOutbound switch { case body.Transport.HTTP != nil: t := body.Transport.HTTP url := fmt.Sprintf("http://%s:%d", t.Host, t.Port) outbound = ht.NewOutbound(url) case body.Transport.TChannel != nil: t := body.Transport.TChannel hostport := fmt.Sprintf("%s:%d", t.Host, t.Port) ch, err := tchannel.NewChannel("yarpc-test-client", nil) if err != nil { return nil, nil, fmt.Errorf("failed to build TChannel: %v", err) } outbound = tch.NewOutbound(ch, tch.HostPort(hostport)) default: return nil, nil, fmt.Errorf("unconfigured transport") } if err := outbound.Start(transport.NoDeps); err != nil { return nil, nil, err } defer outbound.Stop() // TODO use reqMeta.Service for caller client := json.New(channel.MultiOutbound("yarpc-test", body.Service, transport.Outbounds{ Unary: outbound, })) resBody := PhoneResponse{ Service: "yarpc-test", // TODO use reqMeta.Service Procedure: reqMeta.Procedure(), } ctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond) defer cancel() _, err := client.Call( ctx, yarpc.NewReqMeta().Procedure(body.Procedure), body.Body, &resBody.Body) if err != nil { return nil, nil, err } return &resBody, nil, nil }
func TestTChannelTracerDepth2(t *testing.T) { tracer := mocktracer.New() dispatcher := createTChannelDispatcher(tracer, t) client := json.New(dispatcher.Channel("yarpc-test")) handler := handler{client: client, t: t} handler.register(dispatcher) require.NoError(t, dispatcher.Start()) defer dispatcher.Stop() ctx, cancel := handler.createContextWithBaggage(tracer) defer cancel() err := handler.echoEcho(ctx) assert.NoError(t, err) AssertDepth2Spans(t, tracer) }
// JSON implements the 'json' behavior. func JSON(t crossdock.T) { t = createEchoT("json", t) fatals := crossdock.Fatals(t) dispatcher := disp.Create(t) fatals.NoError(dispatcher.Start(), "could not start Dispatcher") defer dispatcher.Stop() client := json.New(dispatcher.Channel("yarpc-test")) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() var response jsonEcho token := random.String(5) _, err := client.Call( ctx, yarpc.NewReqMeta().Procedure("echo"), &jsonEcho{Token: token}, &response, ) crossdock.Fatals(t).NoError(err, "call to echo failed: %v", err) crossdock.Assert(t).Equal(token, response.Token, "server said: %v", response.Token) }
// Run runs the headers behavior func Run(t crossdock.T) { t = createHeadersT(t) fatals := crossdock.Fatals(t) assert := crossdock.Assert(t) checks := crossdock.Checks(t) dispatcher := disp.Create(t) fatals.NoError(dispatcher.Start(), "could not start Dispatcher") defer dispatcher.Stop() var caller headerCaller encoding := t.Param(params.Encoding) switch encoding { case "raw": caller = rawCaller{raw.New(dispatcher.Channel("yarpc-test"))} case "json": caller = jsonCaller{json.New(dispatcher.Channel("yarpc-test"))} case "thrift": caller = thriftCaller{echoclient.New(dispatcher.Channel("yarpc-test"))} default: fatals.Fail("", "unknown encoding %q", encoding) } token1 := random.String(10) token2 := random.String(10) tests := []struct { desc string give yarpc.Headers want yarpc.Headers }{ { "valid headers", yarpc.NewHeaders().With("token1", token1).With("token2", token2), yarpc.NewHeaders().With("token1", token1).With("token2", token2), }, { "non-string values", yarpc.NewHeaders().With("token", "42"), yarpc.NewHeaders().With("token", "42"), }, { "empty strings", yarpc.NewHeaders().With("token", ""), yarpc.NewHeaders().With("token", ""), }, { "no headers", yarpc.Headers{}, yarpc.NewHeaders(), }, { "empty map", yarpc.NewHeaders(), yarpc.NewHeaders(), }, { "varying casing", yarpc.NewHeaders().With("ToKeN1", token1).With("tOkEn2", token2), yarpc.NewHeaders().With("token1", token1).With("token2", token2), }, { "http header conflict", yarpc.NewHeaders().With("Rpc-Procedure", "does not exist"), yarpc.NewHeaders().With("rpc-procedure", "does not exist"), }, { "mixed case value", yarpc.NewHeaders().With("token", "MIXED case Value"), yarpc.NewHeaders().With("token", "MIXED case Value"), }, } for _, tt := range tests { got, err := caller.Call(tt.give) if checks.NoError(err, "%v: call failed", tt.desc) { gotHeaders := internal.RemoveVariableHeaderKeys(got) assert.Equal(tt.want, gotHeaders, "%v: returns valid headers", tt.desc) } } }
func TestInjectClientSuccess(t *testing.T) { type unknownClient interface{} type knownClient interface{} clear := yarpc.RegisterClientBuilder( func(transport.Channel) knownClient { return knownClient(struct{}{}) }) defer clear() mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() tests := []struct { name string target interface{} // list of services for which Channel() should return successfully knownServices []string // list of field names in target we expect to be nil or non-nil wantNil []string wantNonNil []string }{ { name: "empty", target: &struct{}{}, }, { name: "unknown service non-nil", target: &struct { Client json.Client `service:"foo"` }{ Client: json.New(channel.MultiOutbound( "foo", "bar", transport.Outbounds{ Unary: transporttest.NewMockUnaryOutbound(mockCtrl), })), }, wantNonNil: []string{"Client"}, }, { name: "unknown type untagged", target: &struct { Client unknownClient `notservice:"foo"` }{}, wantNil: []string{"Client"}, }, { name: "unknown type non-nil", target: &struct { Client unknownClient `service:"foo"` }{Client: unknownClient(struct{}{})}, wantNonNil: []string{"Client"}, }, { name: "known type", knownServices: []string{"foo"}, target: &struct { Client knownClient `service:"foo"` }{}, wantNonNil: []string{"Client"}, }, { name: "default encodings", knownServices: []string{"jsontest", "rawtest"}, target: &struct { JSON json.Client `service:"jsontest"` Raw raw.Client `service:"rawtest"` }{}, wantNonNil: []string{"JSON", "Raw"}, }, { name: "unexported field", target: &struct { rawClient raw.Client `service:"rawtest"` }{}, wantNil: []string{"rawClient"}, }, } for _, tt := range tests { cp := newMockChannelProvier(mockCtrl, tt.knownServices...) assert.NotPanics(t, func() { yarpc.InjectClients(cp, tt.target) }, tt.name) for _, fieldName := range tt.wantNil { field := reflect.ValueOf(tt.target).Elem().FieldByName(fieldName) assert.True(t, field.IsNil(), "expected %q to be nil", fieldName) } for _, fieldName := range tt.wantNonNil { field := reflect.ValueOf(tt.target).Elem().FieldByName(fieldName) assert.False(t, field.IsNil(), "expected %q to be non-nil", fieldName) } } }
// Run verifies that opentracing context is propagated across multiple hops. // // Behavior parameters: // // - ctxclient: Address of this client. // - ctxserver: Address of the crossdock test subject server. // - transport: The transport to make requests to the test subject with. // // This behavior sets up a server in-process which the Phone procedure on the // test subject is responsible for calling. // // Outgoing calls to the Phone procedure will be made using the transport // specified as a parameter, and incoming calls from the Phone procedure will // be received over a different transport. func Run(t crossdock.T) { checks := crossdock.Checks(t) assert := crossdock.Assert(t) fatals := crossdock.Fatals(t) tests := []struct { desc string initCtx context.Context handlers map[string]handler procedure string }{ { desc: "no baggage", handlers: map[string]handler{ "hello": &singleHopHandler{ t: t, wantBaggage: map[string]string{}, }, }, }, { desc: "existing baggage", initCtx: func() context.Context { span := opentracing.GlobalTracer().StartSpan("existing baggage") span.SetBaggageItem("token", "42") return opentracing.ContextWithSpan(context.Background(), span) }(), handlers: map[string]handler{ "hello": &singleHopHandler{ t: t, wantBaggage: map[string]string{"token": "42"}, }, }, }, { desc: "add baggage", procedure: "one", handlers: map[string]handler{ "one": &multiHopHandler{ t: t, phoneCallTo: "two", addBaggage: map[string]string{"x": "1"}, wantBaggage: map[string]string{}, }, "two": &multiHopHandler{ t: t, phoneCallTo: "three", addBaggage: map[string]string{"y": "2"}, wantBaggage: map[string]string{"x": "1"}, }, "three": &singleHopHandler{ t: t, wantBaggage: map[string]string{"x": "1", "y": "2"}, }, }, }, { desc: "add baggage: existing baggage", initCtx: func() context.Context { span := opentracing.GlobalTracer().StartSpan("existing baggage") span.SetBaggageItem("token", "123") return opentracing.ContextWithSpan(context.Background(), span) }(), procedure: "one", handlers: map[string]handler{ "one": &multiHopHandler{ t: t, phoneCallTo: "two", addBaggage: map[string]string{"hello": "world"}, wantBaggage: map[string]string{"token": "123"}, }, "two": &singleHopHandler{ t: t, wantBaggage: map[string]string{"token": "123", "hello": "world"}, }, }, }, { desc: "overwrite baggage", initCtx: func() context.Context { span := opentracing.GlobalTracer().StartSpan("existing baggage") span.SetBaggageItem("x", "1") return opentracing.ContextWithSpan(context.Background(), span) }(), procedure: "one", handlers: map[string]handler{ "one": &multiHopHandler{ t: t, phoneCallTo: "two", addBaggage: map[string]string{"x": "2", "y": "3"}, wantBaggage: map[string]string{"x": "1"}, }, "two": &multiHopHandler{ t: t, phoneCallTo: "three", addBaggage: map[string]string{"y": "4"}, wantBaggage: map[string]string{"x": "2", "y": "3"}, }, "three": &singleHopHandler{ t: t, wantBaggage: map[string]string{"x": "2", "y": "4"}, }, }, }, } for _, tt := range tests { func() { procedure := tt.procedure if procedure == "" { if !assert.Len(tt.handlers, 1, "%v: invalid test: starting procedure must be provided", tt.desc) { return } for k := range tt.handlers { procedure = k } } dispatcher, tconfig := buildDispatcher(t) fatals.NoError(dispatcher.Start(), "%v: Dispatcher failed to start", tt.desc) defer dispatcher.Stop() jsonClient := json.New(dispatcher.Channel("yarpc-test")) for name, handler := range tt.handlers { handler.SetClient(jsonClient) handler.SetTransport(tconfig) dispatcher.Register(json.Procedure(name, handler.Handle)) } ctx := context.Background() if tt.initCtx != nil { ctx = tt.initCtx } ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() var resp js.RawMessage _, err := jsonClient.Call( ctx, yarpc.NewReqMeta().Procedure("phone"), &server.PhoneRequest{ Service: "ctxclient", Procedure: procedure, Transport: tconfig, Body: &js.RawMessage{'{', '}'}, }, &resp) checks.NoError(err, "%v: request failed", tt.desc) }() } }
func main() { outboundName := "" flag.StringVar( &outboundName, "outbound", "", "name of the outbound to use (http/tchannel)", ) flag.Parse() var outbound transport.UnaryOutbound switch strings.ToLower(outboundName) { case "http": outbound = http.NewOutbound("http://localhost:24034") case "tchannel": channel, err := tchannel.NewChannel("keyvalue-client", nil) if err != nil { log.Fatalln(err) } outbound = tch.NewOutbound(channel, tch.HostPort("localhost:28941")) default: log.Fatalf("invalid outbound: %q\n", outboundName) } dispatcher := yarpc.NewDispatcher(yarpc.Config{ Name: "keyvalue-client", Outbounds: yarpc.Outbounds{ "keyvalue": { Unary: outbound, }, }, Filter: yarpc.Filters(requestLogFilter{}), }) if err := dispatcher.Start(); err != nil { log.Fatalf("failed to start Dispatcher: %v", err) } defer dispatcher.Stop() client := json.New(dispatcher.Channel("keyvalue")) scanner := bufio.NewScanner(os.Stdin) rootCtx := context.Background() for scanner.Scan() { line := scanner.Text() args := strings.Split(line, " ") if len(args) < 1 || len(args[0]) < 3 { continue } cmd := args[0] args = args[1:] switch cmd { case "get": if len(args) != 1 { fmt.Println("usage: get key") continue } key := args[0] ctx, cancel := context.WithTimeout(rootCtx, 100*time.Millisecond) defer cancel() if value, err := get(ctx, client, key); err != nil { fmt.Printf("get %q failed: %s\n", key, err) } else { fmt.Println(key, "=", value) } continue case "set": if len(args) != 2 { fmt.Println("usage: set key value") continue } key, value := args[0], args[1] ctx, cancel := context.WithTimeout(rootCtx, 100*time.Millisecond) defer cancel() if err := set(ctx, client, key, value); err != nil { fmt.Printf("set %q = %q failed: %v\n", key, value, err.Error()) } continue case "exit": return default: fmt.Println("invalid command", cmd) fmt.Println("valid commansd are: get, set, exit") } } if err := scanner.Err(); err != nil { fmt.Println("error:", err.Error()) } }