Beispiel #1
0
// 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")
}
Beispiel #2
0
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
}
Beispiel #3
0
// 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
}
Beispiel #4
0
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)
}
Beispiel #5
0
// 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)
}
Beispiel #6
0
// 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)
		}
	}
}
Beispiel #7
0
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)
		}
	}
}
Beispiel #8
0
// 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)
		}()
	}
}
Beispiel #9
0
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())
	}
}