| /* |
| * |
| * Copyright 2020 gRPC authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package rls |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "testing" |
| "time" |
| |
| "github.com/golang/protobuf/proto" |
| "github.com/google/go-cmp/cmp" |
| "google.golang.org/grpc" |
| rlspb "google.golang.org/grpc/balancer/rls/internal/proto/grpc_lookup_v1" |
| "google.golang.org/grpc/balancer/rls/internal/testutils/fakeserver" |
| "google.golang.org/grpc/codes" |
| "google.golang.org/grpc/internal/testutils" |
| "google.golang.org/grpc/status" |
| ) |
| |
| const ( |
| defaultDialTarget = "dummy" |
| defaultRPCTimeout = 5 * time.Second |
| ) |
| |
| func setup(t *testing.T) (*fakeserver.Server, *grpc.ClientConn, func()) { |
| t.Helper() |
| |
| server, sCleanup, err := fakeserver.Start(nil) |
| if err != nil { |
| t.Fatalf("Failed to start fake RLS server: %v", err) |
| } |
| |
| cc, cCleanup, err := server.ClientConn() |
| if err != nil { |
| sCleanup() |
| t.Fatalf("Failed to get a ClientConn to the RLS server: %v", err) |
| } |
| |
| return server, cc, func() { |
| sCleanup() |
| cCleanup() |
| } |
| } |
| |
| // TestLookupFailure verifies the case where the RLS server returns an error. |
| func (s) TestLookupFailure(t *testing.T) { |
| server, cc, cleanup := setup(t) |
| defer cleanup() |
| |
| // We setup the fake server to return an error. |
| server.ResponseChan <- fakeserver.Response{Err: errors.New("rls failure")} |
| |
| rlsClient := newRLSClient(cc, defaultDialTarget, defaultRPCTimeout) |
| |
| errCh := testutils.NewChannel() |
| rlsClient.lookup("", nil, func(targets []string, headerData string, err error) { |
| if err == nil { |
| errCh.Send(errors.New("rlsClient.lookup() succeeded, should have failed")) |
| return |
| } |
| if len(targets) != 0 || headerData != "" { |
| errCh.Send(fmt.Errorf("rlsClient.lookup() = (%v, %s), want (nil, \"\")", targets, headerData)) |
| return |
| } |
| errCh.Send(nil) |
| }) |
| |
| ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) |
| defer cancel() |
| if e, err := errCh.Receive(ctx); err != nil || e != nil { |
| t.Fatalf("lookup error: %v, error receiving from channel: %v", e, err) |
| } |
| } |
| |
| // TestLookupDeadlineExceeded tests the case where the RPC deadline associated |
| // with the lookup expires. |
| func (s) TestLookupDeadlineExceeded(t *testing.T) { |
| _, cc, cleanup := setup(t) |
| defer cleanup() |
| |
| // Give the Lookup RPC a small deadline, but don't setup the fake server to |
| // return anything. So the Lookup call will block and eventually expire. |
| rlsClient := newRLSClient(cc, defaultDialTarget, 100*time.Millisecond) |
| |
| errCh := testutils.NewChannel() |
| rlsClient.lookup("", nil, func(_ []string, _ string, err error) { |
| if st, ok := status.FromError(err); !ok || st.Code() != codes.DeadlineExceeded { |
| errCh.Send(fmt.Errorf("rlsClient.lookup() returned error: %v, want %v", err, codes.DeadlineExceeded)) |
| return |
| } |
| errCh.Send(nil) |
| }) |
| |
| ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) |
| defer cancel() |
| if e, err := errCh.Receive(ctx); err != nil || e != nil { |
| t.Fatalf("lookup error: %v, error receiving from channel: %v", e, err) |
| } |
| } |
| |
| // TestLookupSuccess verifies the successful Lookup API case. |
| func (s) TestLookupSuccess(t *testing.T) { |
| server, cc, cleanup := setup(t) |
| defer cleanup() |
| |
| const ( |
| rlsReqPath = "/service/method" |
| wantHeaderData = "headerData" |
| ) |
| |
| rlsReqKeyMap := map[string]string{ |
| "k1": "v1", |
| "k2": "v2", |
| } |
| wantLookupRequest := &rlspb.RouteLookupRequest{ |
| Server: defaultDialTarget, |
| Path: rlsReqPath, |
| TargetType: "grpc", |
| KeyMap: rlsReqKeyMap, |
| } |
| wantRespTargets := []string{"us_east_1.firestore.googleapis.com"} |
| |
| rlsClient := newRLSClient(cc, defaultDialTarget, defaultRPCTimeout) |
| |
| errCh := testutils.NewChannel() |
| rlsClient.lookup(rlsReqPath, rlsReqKeyMap, func(targets []string, hd string, err error) { |
| if err != nil { |
| errCh.Send(fmt.Errorf("rlsClient.Lookup() failed: %v", err)) |
| return |
| } |
| if !cmp.Equal(targets, wantRespTargets) || hd != wantHeaderData { |
| errCh.Send(fmt.Errorf("rlsClient.lookup() = (%v, %s), want (%v, %s)", targets, hd, wantRespTargets, wantHeaderData)) |
| return |
| } |
| errCh.Send(nil) |
| }) |
| |
| // Make sure that the fake server received the expected RouteLookupRequest |
| // proto. |
| ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) |
| defer cancel() |
| req, err := server.RequestChan.Receive(ctx) |
| if err != nil { |
| t.Fatalf("Timed out wile waiting for a RouteLookupRequest") |
| } |
| gotLookupRequest := req.(*rlspb.RouteLookupRequest) |
| if diff := cmp.Diff(wantLookupRequest, gotLookupRequest, cmp.Comparer(proto.Equal)); diff != "" { |
| t.Fatalf("RouteLookupRequest diff (-want, +got):\n%s", diff) |
| } |
| |
| // We setup the fake server to return this response when it receives a |
| // request. |
| server.ResponseChan <- fakeserver.Response{ |
| Resp: &rlspb.RouteLookupResponse{ |
| Targets: wantRespTargets, |
| HeaderData: wantHeaderData, |
| }, |
| } |
| |
| if e, err := errCh.Receive(ctx); err != nil || e != nil { |
| t.Fatalf("lookup error: %v, error receiving from channel: %v", e, err) |
| } |
| } |