func TestInboundExistingMethods(t *testing.T) { // Create a channel with an existing "echo" method. ch, err := tchannel.NewChannel("foo", nil) require.NoError(t, err) json.Register(ch, json.Handlers{ "echo": func(ctx json.Context, req map[string]string) (map[string]string, error) { return req, nil }, }, nil) i := NewInbound(ch) service := transport.ServiceDetail{Name: "derp", Registry: new(transporttest.MockRegistry)} require.NoError(t, i.Start(service, transport.NoDeps)) defer i.Stop() // Make a call to the "echo" method which should call our pre-registered method. ctx, cancel := json.NewContext(time.Second) defer cancel() var resp map[string]string arg := map[string]string{"k": "v"} svc := ch.ServiceName() peer := ch.Peers().GetOrAdd(ch.PeerInfo().HostPort) err = json.CallPeer(ctx, peer, svc, "echo", arg, &resp) require.NoError(t, err, "Call failed") assert.Equal(t, arg, resp, "Response mismatch") }
func TestTracingPropagates(t *testing.T) { WithVerifiedServer(t, nil, func(ch *Channel, hostPort string) { handler := &traceHandler{t: t, ch: ch} json.Register(ch, json.Handlers{ "call": handler.call, }, handler.onError) ctx, cancel := json.NewContext(time.Second) defer cancel() peer := ch.Peers().GetOrAdd(ch.PeerInfo().HostPort) var response TracingResponse require.NoError(t, json.CallPeer(ctx, peer, ch.PeerInfo().ServiceName, "call", &TracingRequest{ ForwardCount: 1, }, &response)) clientSpan := CurrentSpan(ctx) require.NotNil(t, clientSpan) assert.Equal(t, uint64(0), clientSpan.ParentID()) assert.NotEqual(t, uint64(0), clientSpan.TraceID()) assert.True(t, clientSpan.TracingEnabled(), "Tracing should be enabled") assert.Equal(t, clientSpan.TraceID(), response.TraceID) assert.Equal(t, clientSpan.SpanID(), response.ParentID) assert.True(t, response.TracingEnabled, "Tracing should be enabled") assert.Equal(t, response.TraceID, response.SpanID, "traceID = spanID for root span") nestedResponse := response.Child require.NotNil(t, nestedResponse) assert.Equal(t, clientSpan.TraceID(), nestedResponse.TraceID) assert.Equal(t, response.SpanID, nestedResponse.ParentID) assert.True(t, response.TracingEnabled, "Tracing should be enabled") assert.NotEqual(t, response.SpanID, nestedResponse.SpanID) }) }
func (s *JoinSenderTestSuite) TestJoinSelf() { // Set up a real listening channel s.tnode = newChannelNode(s.T()) s.node = s.tnode.node defer s.tnode.Destroy() bootstrapNodes(s.T(), s.tnode) joiner, err := newJoinSender(s.node, &joinOpts{ discoverProvider: &StaticHostList{fakeHostPorts(1, 1, 1, 1)}, }) s.Require().NoError(err, "cannot have an error") s.Require().NotNil(joiner, "joiner cannot be nil") ctx, cancel := json.NewContext(joiner.timeout) defer cancel() var res joinResponse select { case err := <-joiner.MakeCall(ctx, s.node.Address(), &res): s.Error(err, "expected join to fail for different apps") case <-ctx.Done(): s.Fail("expected join to not timeout") } }
// Purpose of this test is to ensure introspection doesn't cause any panics // and we have coverage of the introspection code. func TestIntrospection(t *testing.T) { testutils.WithTestServer(t, nil, func(ts *testutils.TestServer) { client := testutils.NewClient(t, nil) defer client.Close() ctx, cancel := json.NewContext(time.Second) defer cancel() var resp map[string]interface{} peer := client.Peers().GetOrAdd(ts.HostPort()) err := json.CallPeer(ctx, peer, ts.ServiceName(), "_gometa_introspect", map[string]interface{}{ "includeExchanges": true, "includeEmptyPeers": true, "includeTombstones": true, }, &resp) require.NoError(t, err, "Call _gometa_introspect failed") err = json.CallPeer(ctx, peer, ts.ServiceName(), "_gometa_runtime", map[string]interface{}{ "includeGoStacks": true, }, &resp) require.NoError(t, err, "Call _gometa_runtime failed") if !ts.HasRelay() { // Try making the call on the "tchannel" service which is where meta handlers // are registered. This will only work when we call it directly as the relay // will not forward the tchannel service. err = json.CallPeer(ctx, peer, "tchannel", "_gometa_runtime", map[string]interface{}{ "includeGoStacks": true, }, &resp) require.NoError(t, err, "Call _gometa_runtime failed") } }) }
func jsonCall(call call, headers map[string]string, token string) (jsonResp, map[string]string, error) { peer := call.Channel.Peers().Add(call.ServerHostPort) ctx, cancel := json.NewContext(time.Second) ctx = json.WithHeaders(ctx, headers) defer cancel() var response jsonResp err := json.CallPeer(ctx, peer, serverName, "echo", &jsonResp{Token: token}, &response) return response, ctx.ResponseHeaders(), err }
func (p *pingSender) SendPing() (*ping, error) { ctx, cancel := json.NewContext(p.timeout) defer cancel() var res ping select { case err := <-p.MakeCall(ctx, &res): return &res, err case <-ctx.Done(): // ping timed out return nil, errors.New("ping timed out") } }
func main() { // Create a new TChannel for handling requests ch, err := tchannel.NewChannel("PingService", &tchannel.ChannelOptions{Logger: tchannel.SimpleLogger}) if err != nil { log.WithFields(tchannel.ErrField(err)).Fatal("Couldn't create new channel.") } // Register a handler for the ping message on the PingService json.Register(ch, json.Handlers{ "ping": pingHandler, }, onError) // Listen for incoming requests listenAndHandle(ch, "127.0.0.1:10500") // Create a new TChannel for sending requests. client, err := tchannel.NewChannel("ping-client", nil) if err != nil { log.WithFields(tchannel.ErrField(err)).Fatal("Couldn't create new client channel.") } // Make a call to ourselves, with a timeout of 10s ctx, cancel := json.NewContext(time.Second * 10) defer cancel() peer := client.Peers().Add(ch.PeerInfo().HostPort) var pong Pong if err := json.CallPeer(ctx, peer, "PingService", "ping", &Ping{"Hello World"}, &pong); err != nil { log.WithFields(tchannel.ErrField(err)).Fatal("json.Call failed.") } log.Infof("Received pong: %s", pong.Message) // Create a new subchannel for the top-level channel subCh := ch.GetSubChannel("PingServiceOther") // Register a handler on the subchannel json.Register(subCh, json.Handlers{ "pingOther": pingOtherHandler, }, onError) // Try to send a message to the Service:Method pair for the subchannel if err := json.CallPeer(ctx, peer, "PingServiceOther", "pingOther", &Ping{"Hello Other World"}, &pong); err != nil { log.WithFields(tchannel.ErrField(err)).Fatal("json.Call failed.") } log.Infof("Received pong: %s", pong.Message) }
func TestZipkinTraceReporterFactory(t *testing.T) { ch, err := tchannel.NewChannel("svc", &tchannel.ChannelOptions{ TraceReporterFactory: ZipkinTraceReporterFactory, }) assert.NoError(t, err) // Create a TCollector channel, and add it as a peer to ch so Report works. mockServer := new(mocks.TChanTCollector) tcollectorCh, err := setupServer(mockServer) require.NoError(t, err, "setupServer failed") ch.Peers().Add(tcollectorCh.PeerInfo().HostPort) called := make(chan int) ret := &gen.Response{Ok: true} mockServer.On("Submit", mock.Anything, mock.Anything).Return(ret, nil).Run(func(args mock.Arguments) { called <- 1 }) // Make some calls, and validate that the trace reporter is not called recursively. require.NoError(t, json.Register(tcollectorCh, json.Handlers{"op": func(ctx json.Context, arg map[string]interface{}) (map[string]interface{}, error) { return arg, nil }}, nil), "Register failed") ctx, cancel := json.NewContext(time.Second) defer cancel() for i := 0; i < 5; i++ { var res map[string]string assert.NoError(t, json.CallSC(ctx, ch.GetSubChannel(tcollectorServiceName), "op", nil, &res), "call failed") } // Verify the spans being reported. for i := 0; i < 5; i++ { select { case <-called: case <-time.After(time.Second): t.Errorf("Expected submit for call %v", i) } } // Verify that no other spans are reported. select { case <-called: t.Errorf("Too many spans reported") case <-time.After(time.Millisecond): } }
func (s *JoinSenderTestSuite) TestJoinSelf() { bootstrapNodes(s.T(), s.tnode) joiner, err := newJoinSender(s.node, nil) s.Require().NoError(err, "cannot have an error") s.Require().NotNil(joiner, "joiner cannot be nil") ctx, cancel := json.NewContext(joiner.timeout) defer cancel() var res joinResponse select { case err := <-joiner.MakeCall(ctx, s.node.Address(), &res): s.Error(err, "expected join to fail for different apps") case <-ctx.Done(): s.Fail("expected join to not timeout") } }
func (p *pingRequestSender) SendPingRequest() (*pingResponse, error) { p.node.log.WithFields(log.Fields{ "peer": p.peer, "target": p.target, }).Debug("ping request send") ctx, cancel := json.NewContext(p.timeout) defer cancel() var res pingResponse select { case err := <-p.MakeCall(ctx, &res): return &res, err case <-ctx.Done(): // call timed out return nil, errors.New("ping request timed out") } }
func (c *Client) sendAdvertise() error { // Cannot advertise from an ephemeral peer. if c.tchan.PeerInfo().IsEphemeral() { return errEphemeralPeer } ctx, cancel := json.NewContext(c.opts.Timeout) defer cancel() // Disable tracing on Hyperbahn advertise messages to avoid cascading failures (see #790). tchannel.CurrentSpan(ctx).EnableTracing(false) sc := c.tchan.GetSubChannel(hyperbahnServiceName) arg := c.createRequest() var resp AdResponse c.opts.Handler.On(SendAdvertise) if err := json.CallSC(ctx, sc, "ad", arg, &resp); err != nil { return err } return nil }
func (j *joinSender) JoinGroup(nodesJoined []string) ([]string, []string) { group := j.SelectGroup(nodesJoined) var responses struct { successes []string failures []string sync.Mutex } var numNodesLeft = j.size - len(nodesJoined) var startTime = time.Now() var wg sync.WaitGroup for _, node := range group { wg.Add(1) go func(n string) { ctx, cancel := json.NewContext(j.timeout) defer cancel() var res joinResponse var failed bool select { case err := <-j.MakeCall(ctx, n, &res): if err != nil { j.node.log.WithFields(log.Fields{ "remote": n, "timeout": j.timeout, }).Debug("attempt to join node failed") failed = true break } j.node.memberlist.Update(res.Membership) case <-ctx.Done(): j.node.log.WithFields(log.Fields{ "remote": n, "timeout": j.timeout, }).Debug("attempt to join node timed out") failed = true } if !failed { responses.Lock() responses.successes = append(responses.successes, n) responses.Unlock() } else { responses.Lock() responses.failures = append(responses.failures, n) responses.Unlock() } wg.Done() }(node) } // wait for joins to complete wg.Wait() // don't need to lock successes/failures since we're finished writing to them j.node.log.WithFields(log.Fields{ "groupSize": len(group), "joinSize": j.size, "joinTime": time.Now().Sub(startTime), "numNodesLeft": numNodesLeft, "numFailures": len(responses.failures), "failures": responses.failures, "numSuccesses": len(responses.successes), "successes": responses.successes, }).Debug("join group complete") return responses.successes, responses.failures }
// Run exercises a YARPC server with outbound TChannel requests from a rigged // client and validates behavior that might only be visible to a TChannel // client without the YARPC abstraction interposed, typically errors. func Run(t crossdock.T) { fatals := crossdock.Fatals(t) assert := crossdock.Assert(t) tests := []test{ { name: "happy path", procedure: "echo", body: []byte("{}"), headers: []byte("{}"), validate: func(res3 []byte, isAppErr bool, err error) { assert.NoError(err, "is not error") assert.False(isAppErr, "malformed body must not be application error") }, }, { name: "malformed body", procedure: "echo", body: []byte(""), headers: []byte("{}"), validate: func(res3 []byte, isAppErr bool, err error) { assert.Error(err, "is error") assert.False(isAppErr, "malformed body must not be application error") err, ok := err.(tchannel.SystemError) assert.True(ok, "malformed body must produce system error") if !ok { return } code := tchannel.GetSystemErrorCode(err) assert.Contains(err.Error(), `failed to decode "json"`, "must mention failing to decode JSON in error message") assert.Equal(tchannel.ErrCodeBadRequest, code, "must produce bad request error") }, }, // TODO test invalid headers { name: "missing procedure", procedure: "", body: []byte{}, headers: []byte("{}"), validate: func(res3 []byte, isAppErr bool, err error) { assert.Error(err, "is error") assert.False(isAppErr, "missing procedure must not produce an application error") err, ok := err.(tchannel.SystemError) assert.True(ok, "missing procedure must produce system error") if !ok { return } code := tchannel.GetSystemErrorCode(err) assert.Equal(tchannel.ErrCodeBadRequest, code, "missing procedure must produce bad request error") assert.Contains(err.Error(), "missing procedure", "must mention missing procedure in error message") }, }, { name: "invalid procedure", procedure: "no-such-procedure", body: []byte{}, headers: []byte("{}"), validate: func(res3 []byte, isAppErr bool, err error) { assert.Error(err, "is error") assert.False(isAppErr, "no-such-procedure must not produce application error") err, ok := err.(tchannel.SystemError) assert.True(ok, "no-such-procedure must produce system error") if !ok { return } code := tchannel.GetSystemErrorCode(err) assert.Equal(tchannel.ErrCodeBadRequest, code, "must produce bad request error") assert.Contains(err.Error(), `unrecognized procedure "no-such-procedure"`, "must mention unrecongized procedure in error message") }, }, { name: "bad response", procedure: "bad-response", body: []byte("{}"), headers: []byte("{}"), validate: func(res3 []byte, isAppErr bool, err error) { assert.Error(err, "is error") assert.False(isAppErr, "bad-response must not produce an application error") err, ok := err.(tchannel.SystemError) assert.True(ok, "bad-response must produce system error") if !ok { return } code := tchannel.GetSystemErrorCode(err) assert.Equal(tchannel.ErrCodeUnexpected, code, "bad-response must produce unexpected error") assert.Contains(err.Error(), `failed to encode "json"`, "must mention failure to encode JSON in error message") }, }, { name: "unexpected error", procedure: "unexpected-error", body: []byte("{}"), headers: []byte("{}"), validate: func(res3 []byte, isAppErr bool, err error) { assert.Error(err, "is error") assert.False(isAppErr, "unexpected-error procedure must not produce application error") err, ok := err.(tchannel.SystemError) assert.True(ok, "unexpected-error procedure must produce system error") code := tchannel.GetSystemErrorCode(err) assert.Equal(tchannel.ErrCodeUnexpected, code, "must produce transport error") }, }, } server := t.Param(params.Server) serverHostPort := fmt.Sprintf("%v:%v", server, serverPort) ch, err := tchannel.NewChannel(serviceName, nil) fatals.NoError(err, "could not create channel") peer := ch.Peers().Add(serverHostPort) for _, tt := range tests { var res2, res3 []byte var headers map[string]string t.Tag("case", tt.name) t.Tag("procedure", tt.procedure) ctx, cancel := json.NewContext(time.Second) defer cancel() ctx = json.WithHeaders(ctx, headers) encoding := "json" if tt.encoding != "" { encoding = tt.encoding } call, err := peer.BeginCall( ctx, serviceName, tt.procedure, &tchannel.CallOptions{Format: tchannel.Format(encoding)}, ) fatals.NoError(err, "could not begin call") err = tchannel.NewArgWriter(call.Arg2Writer()).Write(tt.headers) fatals.NoError(err, "could not write request headers") err = tchannel.NewArgWriter(call.Arg3Writer()).Write(tt.body) fatals.NoError(err, "could not write request body") err = tchannel.NewArgReader(call.Response().Arg2Reader()).Read(&res2) isAppErr := call.Response().ApplicationError() if err == nil { err = tchannel.NewArgReader(call.Response().Arg3Reader()).Read(&res3) } tt.validate(res3, isAppErr, err) } }