// 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 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 (j *joinSender) MakeCall(ctx json.Context, node string, res *joinResponse) <-chan error { errC := make(chan error) go func() { defer close(errC) peer := j.node.channel.Peers().GetOrAdd(node) req := joinRequest{ App: j.node.app, Source: j.node.address, Incarnation: j.node.Incarnation(), Timeout: j.timeout, } err := json.CallPeer(ctx, peer, j.node.service, "/protocol/join", req, res) if err != nil { j.node.log.WithFields(log.Fields{ "error": err, }).Debug("could not complete join") errC <- err return } errC <- nil }() return errC }
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 (p *pingRequestSender) MakeCall(ctx json.Context, res *pingResponse) <-chan error { errC := make(chan error) go func() { defer close(errC) changes, bumpPiggybackCounters := p.node.disseminator.IssueAsSender() req := &pingRequest{ Source: p.node.Address(), SourceIncarnation: p.node.Incarnation(), Checksum: p.node.memberlist.Checksum(), Changes: changes, Target: p.target, } peer := p.node.channel.Peers().GetOrAdd(p.peer) err := json.CallPeer(ctx, peer, p.node.service, "/protocol/ping-req", req, &res) if err != nil { bumpPiggybackCounters() errC <- err return } errC <- nil }() return errC }
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 (h *JSONHandler) firstCall(ctx context.Context, req *TracingRequest) (*TracingResponse, error) { jctx := json.Wrap(ctx) response := new(TracingResponse) peer := h.Ch.Peers().GetOrAdd(h.Ch.PeerInfo().HostPort) if err := json.CallPeer(jctx, peer, h.Ch.PeerInfo().ServiceName, "call", req, response); err != nil { return nil, err } return response, nil }
func (p *pingSender) MakeCall(ctx json.Context, res *ping) <-chan error { errC := make(chan error) go func() { defer close(errC) peer := p.node.channel.Peers().GetOrAdd(p.target) changes, bumpPiggybackCounters := p.node.disseminator.IssueAsSender() req := ping{ Checksum: p.node.memberlist.Checksum(), Changes: changes, Source: p.node.Address(), SourceIncarnation: p.node.Incarnation(), } p.node.emit(PingSendEvent{ Local: p.node.Address(), Remote: p.target, Changes: req.Changes, }) p.logger.WithFields(log.Fields{ "remote": p.target, "changes": req.Changes, }).Debug("ping send") var startTime = time.Now() err := json.CallPeer(ctx, peer, p.node.service, "/protocol/ping", req, res) if err != nil { p.logger.WithFields(log.Fields{ "remote": p.target, "error": err, }).Debug("ping failed") errC <- err return } // when ping was successful bumpPiggybackCounters() p.node.emit(PingSendCompleteEvent{ Local: p.node.Address(), Remote: p.target, Changes: req.Changes, Duration: time.Now().Sub(startTime), }) errC <- nil }() return errC }
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 (h *JSONHandler) callJSON(ctx json.Context, req *TracingRequest) (*TracingResponse, error) { return h.HandleCall(ctx, req, func(ctx context.Context, req *TracingRequest) (*TracingResponse, error) { jctx := ctx.(json.Context) peer := h.Ch.Peers().GetOrAdd(h.Ch.PeerInfo().HostPort) childResp := new(TracingResponse) if err := json.CallPeer(jctx, peer, h.Ch.PeerInfo().ServiceName, "call", req, childResp); err != nil { return nil, err } return childResp, nil }) }
func (h *jsonHandler) callDownstream(ctx context.Context, target *Downstream) (*Response, error) { req := &Request{ ServerRole: target.ServerRole, Downstream: target.Downstream, } jctx := json.Wrap(ctx) response := new(Response) log.Printf("Calling JSON service %s (%s)", target.ServiceName, target.HostPort) peer := h.ch.Peers().GetOrAdd(target.HostPort) if err := json.CallPeer(jctx, peer, target.ServiceName, jsonEndpoint, req, response); err != nil { return nil, err } return response, nil }
func (h *traceHandler) call(ctx json.Context, req *TracingRequest) (*TracingResponse, error) { span := CurrentSpan(ctx) if span == nil { return nil, fmt.Errorf("tracing not found") } var childResp *TracingResponse if req.ForwardCount > 0 { sc := h.ch.Peers().GetOrAdd(h.ch.PeerInfo().HostPort) childResp = new(TracingResponse) require.NoError(h.t, json.CallPeer(ctx, sc, h.ch.PeerInfo().ServiceName, "call", nil, childResp)) } return &TracingResponse{ TraceID: span.TraceID(), SpanID: span.SpanID(), ParentID: span.ParentID(), TracingEnabled: span.TracingEnabled(), Child: childResp, }, nil }
// Per https://github.com/uber/tchannel-go/issues/505, concurrent client calls // made with the same shared map used as headers were causing panic due to // concurrent writes to the map when injecting tracing headers. func TestReusableHeaders(t *testing.T) { opts := &testutils.ChannelOpts{ ChannelOptions: ChannelOptions{Tracer: mocktracer.New()}, } WithVerifiedServer(t, opts, func(ch *Channel, hostPort string) { jsonHandler := &JSONHandler{TraceHandler: testtracing.TraceHandler{Ch: ch}, t: t} json.Register(ch, json.Handlers{"call": jsonHandler.callJSON}, jsonHandler.onError) span := ch.Tracer().StartSpan("client") traceID := span.(*mocktracer.MockSpan).SpanContext.TraceID // for validation ctx := opentracing.ContextWithSpan(context.Background(), span) sharedHeaders := map[string]string{"life": "42"} ctx, cancel := NewContextBuilder(2 * time.Second). SetHeaders(sharedHeaders). SetParentContext(ctx). Build() defer cancel() peer := ch.Peers().GetOrAdd(ch.PeerInfo().HostPort) var wg sync.WaitGroup for i := 0; i < 42; i++ { wg.Add(1) go func() { defer wg.Done() var response testtracing.TracingResponse err := json.CallPeer(json.Wrap(ctx), peer, ch.ServiceName(), "call", &testtracing.TracingRequest{}, &response) assert.NoError(t, err, "json.Call failed") assert.EqualValues(t, traceID, response.TraceID, "traceID must match") }() } wg.Wait() assert.Equal(t, map[string]string{"life": "42"}, sharedHeaders, "headers unchanged") }) }
func TestTracingSpanAttributes(t *testing.T) { tracer := mocktracer.New() opts := &testutils.ChannelOpts{ ChannelOptions: ChannelOptions{Tracer: tracer}, DisableRelay: true, } WithVerifiedServer(t, opts, func(ch *Channel, hostPort string) { // Register JSON handler jsonHandler := &JSONHandler{TraceHandler: testtracing.TraceHandler{Ch: ch}, t: t} json.Register(ch, json.Handlers{"call": jsonHandler.callJSON}, jsonHandler.onError) span := ch.Tracer().StartSpan("client") span.SetBaggageItem(testtracing.BaggageKey, testtracing.BaggageValue) ctx := opentracing.ContextWithSpan(context.Background(), span) root := new(testtracing.TracingResponse).ObserveSpan(ctx) ctx, cancel := NewContextBuilder(2 * time.Second).SetParentContext(ctx).Build() defer cancel() peer := ch.Peers().GetOrAdd(ch.PeerInfo().HostPort) var response testtracing.TracingResponse require.NoError(t, json.CallPeer(json.Wrap(ctx), peer, ch.PeerInfo().ServiceName, "call", &testtracing.TracingRequest{}, &response)) // Spans are finished in inbound.doneSending() or outbound.doneReading(), // which are called on different go-routines and may execute *after* the // response has been received by the client. Give them a chance to finish. for i := 0; i < 1000; i++ { if spanCount := len(testtracing.MockTracerSampledSpans(tracer)); spanCount == 2 { break } time.Sleep(testutils.Timeout(time.Millisecond)) } spans := testtracing.MockTracerSampledSpans(tracer) spanCount := len(spans) ch.Logger().Debugf("end span count: %d", spanCount) // finish span after taking count of recorded spans span.Finish() require.Equal(t, 2, spanCount, "Wrong span count") assert.Equal(t, root.TraceID, response.TraceID, "Trace ID must match root span") assert.Equal(t, testtracing.BaggageValue, response.Luggage, "Baggage must match") var parent, child *mocktracer.MockSpan for _, s := range spans { if s.Tag("span.kind") == ext.SpanKindRPCClientEnum { parent = s ch.Logger().Debugf("Found parent span: %+v", s) } else if s.Tag("span.kind") == ext.SpanKindRPCServerEnum { child = s ch.Logger().Debugf("Found child span: %+v", s) } } require.NotNil(t, parent) require.NotNil(t, child) traceID := func(s opentracing.Span) int { return s.Context().(mocktracer.MockSpanContext).TraceID } spanID := func(s *mocktracer.MockSpan) int { return s.Context().(mocktracer.MockSpanContext).SpanID } sampled := func(s *mocktracer.MockSpan) bool { return s.Context().(mocktracer.MockSpanContext).Sampled } require.Equal(t, traceID(span), traceID(parent), "parent must be found") require.Equal(t, traceID(span), traceID(child), "child must be found") assert.Equal(t, traceID(parent), traceID(child)) assert.Equal(t, spanID(parent), child.ParentID) assert.True(t, sampled(parent), "should be sampled") assert.True(t, sampled(child), "should be sampled") assert.Equal(t, "call", parent.OperationName) assert.Equal(t, "call", child.OperationName) assert.Equal(t, "testService", parent.Tag("peer.service")) assert.Equal(t, "testService", child.Tag("peer.service")) assert.Equal(t, "json", parent.Tag("as")) assert.Equal(t, "json", child.Tag("as")) assert.NotNil(t, parent.Tag("peer.ipv4")) assert.NotNil(t, child.Tag("peer.ipv4")) assert.NotNil(t, parent.Tag("peer.port")) assert.NotNil(t, child.Tag("peer.port")) }) }