Beispiel #1
0
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)
}
Beispiel #2
0
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)
	})
}
Beispiel #3
0
func runRetryTest(t *testing.T, f func(r *retryTest)) {
	r := &retryTest{}
	defer testutils.SetTimeout(t, time.Second)()
	r.setup()
	defer testutils.ResetSleepStub(&timeSleep)

	withSetup(t, func(hypCh *tchannel.Channel, hostPort string) {
		json.Register(hypCh, json.Handlers{"ad": r.adHandler}, nil)

		// Advertise failures cause warning log messages.
		opts := testutils.NewOpts().
			SetServiceName("my-client").
			AddLogFilter("Hyperbahn client registration failed", 10)
		serverCh := testutils.NewServer(t, opts)
		defer serverCh.Close()

		var err error
		r.ch = serverCh
		r.client, err = NewClient(serverCh, configFor(hostPort), &ClientOptions{
			Handler:      r,
			FailStrategy: FailStrategyIgnore,
		})
		require.NoError(t, err, "NewClient")
		defer r.client.Close()
		f(r)
		r.mock.AssertExpectations(t)
	})
}
Beispiel #4
0
func TestInitialAdvertiseFailedRetryTimeout(t *testing.T) {
	withSetup(t, func(hypCh *tchannel.Channel, hyperbahnHostPort string) {
		started := time.Now()
		count := 0
		adHandler := func(ctx json.Context, req *AdRequest) (*AdResponse, error) {
			count++

			deadline, ok := ctx.Deadline()
			if assert.True(t, ok, "context is missing Deadline") {
				assert.True(t, deadline.Sub(started) <= 2*time.Second,
					"Timeout per attempt should be 1 second. Started: %v Deadline: %v", started, deadline)
			}

			return nil, tchannel.NewSystemError(tchannel.ErrCodeUnexpected, "unexpected")
		}
		json.Register(hypCh, json.Handlers{"ad": adHandler}, nil)

		ch := testutils.NewServer(t, nil)
		client, err := NewClient(ch, configFor(hyperbahnHostPort), stubbedSleep())
		assert.NoError(t, err, "hyperbahn NewClient failed")
		defer client.Close()

		assert.Error(t, client.Advertise(), "Advertise should not succeed")
		// We expect 5 retries by TChannel and we attempt 5 to advertise 5 times.
		assert.Equal(t, 5*5, count, "adHandler not retried correct number of times")
	})
}
Beispiel #5
0
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")
}
Beispiel #6
0
func TestJSONTracingPropagation(t *testing.T) {
	suite := &PropagationTestSuite{
		Encoding: EncodingInfo{Format: tchannel.JSON, HeadersSupported: true},
		Register: func(t *testing.T, ch *tchannel.Channel) TracingCall {
			handler := &JSONHandler{TraceHandler: TraceHandler{Ch: ch}, t: t}
			json.Register(ch, json.Handlers{"call": handler.callJSON}, handler.onError)
			return handler.firstCall
		},
		TestCases: map[TracerType][]PropagationTestCase{
			Noop: {
				{ForwardCount: 2, TracingDisabled: true, ExpectedBaggage: "", ExpectedSpanCount: 0},
				{ForwardCount: 2, TracingDisabled: false, ExpectedBaggage: "", ExpectedSpanCount: 0},
			},
			Mock: {
				{ForwardCount: 2, TracingDisabled: true, ExpectedBaggage: BaggageValue, ExpectedSpanCount: 0},
				{ForwardCount: 2, TracingDisabled: false, ExpectedBaggage: BaggageValue, ExpectedSpanCount: 6},
			},
			Jaeger: {
				{ForwardCount: 2, TracingDisabled: true, ExpectedBaggage: BaggageValue, ExpectedSpanCount: 0},
				{ForwardCount: 2, TracingDisabled: false, ExpectedBaggage: BaggageValue, ExpectedSpanCount: 6},
			},
		},
	}
	suite.Run(t)
}
Beispiel #7
0
func (s *ForwarderTestSuite) registerPong(address string, channel *tchannel.Channel) {
	hmap := map[string]interface{}{
		"/ping": func(ctx json.Context, ping *Ping) (*Pong, error) {
			return &Pong{"Hello, world!", address}, nil
		},
		"/error": func(ctx json.Context, ping *Ping) (*Pong, error) {
			return nil, errors.New("remote error")
		},
	}
	s.Require().NoError(json.Register(channel, hmap, func(ctx context.Context, err error) {}))

	thriftHandler := &pingpong.MockTChanPingPong{}

	// successful request
	thriftHandler.On("Ping", mock.Anything, &pingpong.Ping{
		Key: "success",
	}).Return(&pingpong.Pong{
		Source: address,
	}, nil)

	// error request
	thriftHandler.On("Ping", mock.Anything, &pingpong.Ping{
		Key: "error",
	}).Return(nil, &pingpong.PingError{})

	server := thrift.NewServer(channel)
	server.Register(pingpong.NewTChanPingPongServer(thriftHandler))
}
Beispiel #8
0
func (w *worker) RegisterPong() error {
	hmap := map[string]interface{}{"/ping": w.PingHandler}

	return json.Register(w.channel, hmap, func(ctx context.Context, err error) {
		w.logger.Debug("error occured: %v", err)
	})
}
Beispiel #9
0
func (rp *Ringpop) registerHandlers() error {
	handlers := map[string]interface{}{
		"/health":       rp.health,
		"/admin/stats":  rp.adminStatsHandler,
		"/admin/lookup": rp.adminLookupHandler,
	}

	return json.Register(rp.channel, handlers, func(ctx context.Context, err error) {
		rp.log.WithField("error", err).Info("error occured")
	})
}
Beispiel #10
0
// Register the different endpoints of the test subject
func register(ch *tchannel.Channel) {
	ch.Register(raw.Wrap(echoRawHandler{}), "echo/raw")
	ch.Register(raw.Wrap(handlerTimeoutRawHandler{}), "handlertimeout/raw")

	json.Register(ch, json.Handlers{"echo": echoJSONHandler}, onError)

	tserver := thrift.NewServer(ch)
	tserver.Register(echo.NewTChanEchoServer(&echoThriftHandler{}))
	tserver.Register(gauntlet_tchannel.NewTChanThriftTestServer(&thriftTestHandler{}))
	tserver.Register(gauntlet_tchannel.NewTChanSecondServiceServer(&secondServiceHandler{}))
}
Beispiel #11
0
func (s *ReplicatorTestSuite) RegisterHandler(ch shared.SubChannel, address string) {
	handler := map[string]interface{}{
		"/ping": func(ctx json.Context, ping *Ping) (*Pong, error) {
			s.Equal(ping.From, "127.0.0.1:3001")
			return &Pong{"Hello, world!", address}, nil
		},
	}

	s.Require().NoError(json.Register(ch, handler, func(ctx context.Context, err error) {
		s.Fail("calls shouldn't fail")
	}))
}
Beispiel #12
0
func TestNotListeningChannel(t *testing.T) {
	withSetup(t, func(hypCh *tchannel.Channel, hyperbahnHostPort string) {
		adHandler := func(ctx json.Context, req *AdRequest) (*AdResponse, error) {
			return &AdResponse{1}, nil
		}
		json.Register(hypCh, json.Handlers{"ad": adHandler}, nil)

		ch := testutils.NewClient(t, nil)
		client, err := NewClient(ch, configFor(hyperbahnHostPort), nil)
		assert.NoError(t, err, "hyperbahn NewClient failed")
		defer client.Close()

		assert.Equal(t, errEphemeralPeer, client.Advertise(), "Advertise without Listen should fail")
	})
}
Beispiel #13
0
// New returns a mock Hyperbahn server that can be used for testing.
func New() (*Mock, error) {
	ch, err := tchannel.NewChannel("hyperbahn", nil)
	if err != nil {
		return nil, err
	}

	mh := &Mock{
		ch:     ch,
		respCh: make(chan int),
	}
	if err := json.Register(ch, json.Handlers{"ad": mh.adHandler}, nil); err != nil {
		return nil, err
	}

	return mh, ch.ListenAndServe("127.0.0.1:0")
}
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):
	}
}
Beispiel #15
0
func (n *Node) registerHandlers() error {
	handlers := map[string]interface{}{
		"/protocol/join":      n.joinHandler,
		"/protocol/ping":      n.pingHandler,
		"/protocol/ping-req":  n.pingRequestHandler,
		"/admin/debugSet":     notImplementedHandler,
		"/admin/debugClear":   notImplementedHandler,
		"/admin/gossip":       n.gossipHandler, // Deprecated
		"/admin/gossip/start": n.gossipHandlerStart,
		"/admin/gossip/stop":  n.gossipHandlerStop,
		"/admin/tick":         n.tickHandler, // Deprecated
		"/admin/gossip/tick":  n.tickHandler,
		"/admin/member/leave": n.adminLeaveHandler,
		"/admin/member/join":  n.adminJoinHandler,
	}

	return json.Register(n.channel, handlers, n.errorHandler)
}
Beispiel #16
0
func TestInitialAdvertiseFailedRetry(t *testing.T) {
	withSetup(t, func(hypCh *tchannel.Channel, hyperbahnHostPort string) {
		count := 0
		adHandler := func(ctx json.Context, req *AdRequest) (*AdResponse, error) {
			count++
			return nil, tchannel.NewSystemError(tchannel.ErrCodeUnexpected, "unexpected")
		}
		json.Register(hypCh, json.Handlers{"ad": adHandler}, nil)

		ch := testutils.NewServer(t, nil)
		client, err := NewClient(ch, configFor(hyperbahnHostPort), nil)
		assert.NoError(t, err, "hyperbahn NewClient failed")
		defer client.Close()

		assert.Error(t, client.Advertise(), "Advertise should not succeed")
		assert.Equal(t, 5, count, "adHandler not retried correct number of times")
	})
}
Beispiel #17
0
// New returns a mock Hyperbahn server that can be used for testing.
func New() (*Mock, error) {
	ch, err := tchannel.NewChannel("hyperbahn", nil)
	if err != nil {
		return nil, err
	}

	mh := &Mock{
		ch:              ch,
		respCh:          make(chan int),
		discoverResults: make(map[string][]string),
	}
	if err := json.Register(ch, json.Handlers{"ad": mh.adHandler}, nil); err != nil {
		return nil, err
	}

	thriftServer := thrift.NewServer(ch)
	thriftServer.Register(hthrift.NewTChanHyperbahnServer(mh))

	return mh, ch.ListenAndServe("127.0.0.1:0")
}
Beispiel #18
0
func (n *Node) registerHandlers() error {
	handlers := map[string]interface{}{
		"/protocol/join":      n.joinHandler,
		"/protocol/ping":      n.pingHandler,
		"/protocol/ping-req":  n.pingRequestHandler,
		"/admin/debugSet":     n.debugSetHandler,
		"/admin/debugClear":   n.debugClearHandler,
		"/admin/gossip":       n.gossipHandler, // Deprecated
		"/admin/gossip/start": n.gossipHandlerStart,
		"/admin/gossip/stop":  n.gossipHandlerStop,
		"/admin/tick":         n.tickHandler, // Deprecated
		"/admin/gossip/tick":  n.tickHandler,
		"/admin/member/leave": n.adminLeaveHandler,
		"/admin/member/join":  n.adminJoinHandler,
	}

	return json.Register(n.channel, handlers, func(ctx context.Context, err error) {
		n.log.WithField("error", err).Info("error occured")
	})

}
Beispiel #19
0
// 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")
	})
}
Beispiel #20
0
func runRetryTest(t *testing.T, f func(r *retryTest)) {
	r := &retryTest{}
	defer testutils.SetTimeout(t, time.Second)()
	r.setup()
	defer testutils.ResetSleepStub(&timeSleep)

	withSetup(t, func(serverCh *tchannel.Channel, hostPort string) {
		json.Register(serverCh, json.Handlers{"ad": r.adHandler}, nil)

		clientCh, err := testutils.NewServer(&testutils.ChannelOpts{ServiceName: "my-client"})
		require.NoError(t, err, "NewServer failed")
		defer clientCh.Close()

		r.ch = clientCh
		r.client, err = NewClient(clientCh, configFor(hostPort), &ClientOptions{
			Handler:      r,
			FailStrategy: FailStrategyIgnore,
		})
		require.NoError(t, err, "NewClient")
		defer r.client.Close()
		f(r)
		r.mock.AssertExpectations(t)
	})
}
Beispiel #21
0
// New returns a mock Hyperbahn server that can be used for testing.
func New() (*Mock, error) {
	table := &mockTable{}
	ch, err := tchannel.NewChannel("hyperbahn", &tchannel.ChannelOptions{
		RelayHosts:         table,
		RelayLocalHandlers: []string{"hyperbahn"},
	})
	if err != nil {
		return nil, err
	}
	table.ch = ch
	mh := &Mock{
		ch:              ch,
		respCh:          make(chan int),
		discoverResults: make(map[string][]string),
	}
	if err := json.Register(ch, json.Handlers{"ad": mh.adHandler}, nil); err != nil {
		return nil, err
	}

	thriftServer := thrift.NewServer(ch)
	thriftServer.Register(hthrift.NewTChanHyperbahnServer(mh))

	return mh, ch.ListenAndServe("127.0.0.1:0")
}
Beispiel #22
0
func (b *Behavior) registerJSON(ch *tchannel.Channel) {
	handler := &jsonHandler{b: b, ch: ch}
	json.Register(ch, json.Handlers{jsonEndpoint: handler.handleJSON}, handler.onError)
	b.jsonCall = handler.callDownstream
}
Beispiel #23
0
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"))
	})
}