func TestStatic(t *testing.T) { var ( instances = []string{"foo", "bar", "baz"} endpoints = map[string]endpoint.Endpoint{ "foo": func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, "bar": func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, "baz": func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, } factory = func(instance string) (endpoint.Endpoint, error) { if e, ok := endpoints[instance]; ok { return e, nil } return nil, fmt.Errorf("%s: not found", instance) } ) p := static.NewPublisher(instances, factory, log.NewNopLogger()) have, err := p.Endpoints() if err != nil { t.Fatal(err) } want := []endpoint.Endpoint{endpoints["foo"], endpoints["bar"], endpoints["baz"]} if fmt.Sprint(want) != fmt.Sprint(have) { t.Fatalf("want %v, have %v", want, have) } }
func TestRoundRobinDistribution(t *testing.T) { var ( ctx = context.Background() counts = []int{0, 0, 0} endpoints = []endpoint.Endpoint{ func(context.Context, interface{}) (interface{}, error) { counts[0]++; return struct{}{}, nil }, func(context.Context, interface{}) (interface{}, error) { counts[1]++; return struct{}{}, nil }, func(context.Context, interface{}) (interface{}, error) { counts[2]++; return struct{}{}, nil }, } ) lb := loadbalancer.NewRoundRobin(static.NewPublisher(endpoints)) for i, want := range [][]int{ {1, 0, 0}, {1, 1, 0}, {1, 1, 1}, {2, 1, 1}, {2, 2, 1}, {2, 2, 2}, {3, 2, 2}, } { e, err := lb.Endpoint() if err != nil { t.Fatal(err) } e(ctx, struct{}{}) if have := counts; !reflect.DeepEqual(want, have) { t.Fatalf("%d: want %v, have %v", i, want, have) } } }
func TestRandomNoEndpoints(t *testing.T) { lb := loadbalancer.NewRandom(static.NewPublisher([]endpoint.Endpoint{}), 123) _, have := lb.Endpoint() if want := loadbalancer.ErrNoEndpoints; want != have { t.Errorf("want %q, have %q", want, have) } }
func TestRandomDistribution(t *testing.T) { var ( n = 3 endpoints = make([]endpoint.Endpoint, n) counts = make([]int, n) seed = int64(123) ctx = context.Background() iterations = 100000 want = iterations / n tolerance = want / 100 // 1% ) for i := 0; i < n; i++ { i0 := i endpoints[i] = func(context.Context, interface{}) (interface{}, error) { counts[i0]++; return struct{}{}, nil } } lb := loadbalancer.NewRandom(static.NewPublisher(endpoints), seed) for i := 0; i < iterations; i++ { e, err := lb.Endpoint() if err != nil { t.Fatal(err) } e(ctx, struct{}{}) } for i, have := range counts { if math.Abs(float64(want-have)) > float64(tolerance) { t.Errorf("%d: want %d, have %d", i, want, have) } } }
func TestRetryMaxTotalFail(t *testing.T) { var ( endpoints = []endpoint.Endpoint{} // no endpoints p = static.NewPublisher(endpoints) lb = loadbalancer.NewRoundRobin(p) retry = loadbalancer.Retry(999, time.Second, lb) // lots of retries ctx = context.Background() ) if _, err := retry(ctx, struct{}{}); err == nil { t.Errorf("expected error, got none") // should fail } }
func TestStatic(t *testing.T) { var ( e1 = func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil } e2 = func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil } endpoints = []endpoint.Endpoint{e1, e2} ) p := static.NewPublisher(endpoints) have, err := p.Endpoints() if err != nil { t.Fatal(err) } if want := endpoints; !reflect.DeepEqual(want, have) { t.Fatalf("want %#+v, have %#+v", want, have) } }
func TestRetryMaxSuccess(t *testing.T) { var ( endpoints = []endpoint.Endpoint{ func(context.Context, interface{}) (interface{}, error) { return nil, errors.New("error one") }, func(context.Context, interface{}) (interface{}, error) { return nil, errors.New("error two") }, func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil /* OK */ }, } retries = len(endpoints) // exactly enough retries p = static.NewPublisher(endpoints) lb = loadbalancer.NewRoundRobin(p) ctx = context.Background() ) if _, err := loadbalancer.Retry(retries, time.Second, lb)(ctx, struct{}{}); err != nil { t.Error(err) } }
func TestRetryMaxPartialFail(t *testing.T) { var ( endpoints = []endpoint.Endpoint{ func(context.Context, interface{}) (interface{}, error) { return nil, errors.New("error one") }, func(context.Context, interface{}) (interface{}, error) { return nil, errors.New("error two") }, func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil /* OK */ }, } retries = len(endpoints) - 1 // not quite enough retries p = static.NewPublisher(endpoints) lb = loadbalancer.NewRoundRobin(p) ctx = context.Background() ) if _, err := loadbalancer.Retry(retries, time.Second, lb)(ctx, struct{}{}); err == nil { t.Errorf("expected error, got none") } }
func proxyingMiddleware(proxyList string, ctx context.Context, logger log.Logger) ServiceMiddleware { if proxyList == "" { _ = logger.Log("proxy_to", "none") return func(next StringService) StringService { return next } } proxies := split(proxyList) _ = logger.Log("proxy_to", fmt.Sprint(proxies)) return func(next StringService) StringService { var ( qps = 100 // max to each instance publisher = static.NewPublisher(proxies, factory(ctx, qps), logger) lb = loadbalancer.NewRoundRobin(publisher) maxAttempts = 3 maxTime = 100 * time.Millisecond endpoint = loadbalancer.Retry(maxAttempts, maxTime, lb) ) return proxymw{ctx, endpoint, next} } }
func TestStaticReplace(t *testing.T) { p := static.NewPublisher([]endpoint.Endpoint{ func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, }) have, err := p.Endpoints() if err != nil { t.Fatal(err) } if want, have := 1, len(have); want != have { t.Fatalf("want %d, have %d", want, have) } p.Replace([]endpoint.Endpoint{}) have, err = p.Endpoints() if err != nil { t.Fatal(err) } if want, have := 0, len(have); want != have { t.Fatalf("want %d, have %d", want, have) } }
func TestRetryTimeout(t *testing.T) { var ( step = make(chan struct{}) e = func(context.Context, interface{}) (interface{}, error) { <-step; return struct{}{}, nil } timeout = time.Millisecond retry = loadbalancer.Retry(999, timeout, loadbalancer.NewRoundRobin(static.NewPublisher([]endpoint.Endpoint{e}))) errs = make(chan error, 1) invoke = func() { _, err := retry(context.Background(), struct{}{}); errs <- err } ) go func() { step <- struct{}{} }() // queue up a flush of the endpoint invoke() // invoke the endpoint and trigger the flush if err := <-errs; err != nil { // that should succeed t.Error(err) } go func() { time.Sleep(10 * timeout); step <- struct{}{} }() // a delayed flush invoke() // invoke the endpoint if err := <-errs; err != context.DeadlineExceeded { // that should not succeed t.Errorf("wanted %v, got none", context.DeadlineExceeded) } }
func buildEndpoint(instances []string, factory loadbalancer.Factory, seed int64, logger log.Logger) endpoint.Endpoint { publisher := static.NewPublisher(instances, factory, logger) random := loadbalancer.NewRandom(publisher, seed) return loadbalancer.Retry(10, 10*time.Second, random) }
func buildEndpoint(tracer opentracing.Tracer, operationName string, instances []string, factory loadbalancer.Factory, seed int64, logger log.Logger) endpoint.Endpoint { publisher := static.NewPublisher(instances, factory, logger) random := loadbalancer.NewRandom(publisher, seed) endpoint := loadbalancer.Retry(10, 10*time.Second, random) return kitot.TraceClient(tracer, operationName)(endpoint) }