/* * * 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 xdsrouting import ( "testing" "github.com/google/go-cmp/cmp" "google.golang.org/grpc/balancer" "google.golang.org/grpc/connectivity" "google.golang.org/grpc/xds/internal" "google.golang.org/grpc/xds/internal/testutils" ) var ( testPickers = []*testutils.TestConstPicker{ {SC: testutils.TestSubConns[0]}, {SC: testutils.TestSubConns[1]}, } ) func (s) TestRoutingPickerGroupPick(t *testing.T) { tests := []struct { name string routes []route pickers map[internal.LocalityID]*subBalancerState info balancer.PickInfo want balancer.PickResult wantErr error }{ { name: "empty", wantErr: errNoMatchedRouteFound, }, { name: "one route no match", routes: []route{ {m: newCompositeMatcher(newPathPrefixMatcher("/a/"), nil, nil), action: "action-0"}, }, pickers: map[internal.LocalityID]*subBalancerState{ makeLocalityFromName("action-0"): {state: balancer.State{ ConnectivityState: connectivity.Ready, Picker: testPickers[0], }}, }, info: balancer.PickInfo{FullMethodName: "/z/y"}, wantErr: errNoMatchedRouteFound, }, { name: "one route one match", routes: []route{ {m: newCompositeMatcher(newPathPrefixMatcher("/a/"), nil, nil), action: "action-0"}, }, pickers: map[internal.LocalityID]*subBalancerState{ makeLocalityFromName("action-0"): {state: balancer.State{ ConnectivityState: connectivity.Ready, Picker: testPickers[0], }}, }, info: balancer.PickInfo{FullMethodName: "/a/b"}, want: balancer.PickResult{SubConn: testutils.TestSubConns[0]}, }, { name: "two routes first match", routes: []route{ {m: newCompositeMatcher(newPathPrefixMatcher("/a/"), nil, nil), action: "action-0"}, {m: newCompositeMatcher(newPathPrefixMatcher("/z/"), nil, nil), action: "action-1"}, }, pickers: map[internal.LocalityID]*subBalancerState{ makeLocalityFromName("action-0"): {state: balancer.State{ ConnectivityState: connectivity.Ready, Picker: testPickers[0], }}, makeLocalityFromName("action-1"): {state: balancer.State{ ConnectivityState: connectivity.Ready, Picker: testPickers[1], }}, }, info: balancer.PickInfo{FullMethodName: "/a/b"}, want: balancer.PickResult{SubConn: testutils.TestSubConns[0]}, }, { name: "two routes second match", routes: []route{ {m: newCompositeMatcher(newPathPrefixMatcher("/a/"), nil, nil), action: "action-0"}, {m: newCompositeMatcher(newPathPrefixMatcher("/z/"), nil, nil), action: "action-1"}, }, pickers: map[internal.LocalityID]*subBalancerState{ makeLocalityFromName("action-0"): {state: balancer.State{ ConnectivityState: connectivity.Ready, Picker: testPickers[0], }}, makeLocalityFromName("action-1"): {state: balancer.State{ ConnectivityState: connectivity.Ready, Picker: testPickers[1], }}, }, info: balancer.PickInfo{FullMethodName: "/z/y"}, want: balancer.PickResult{SubConn: testutils.TestSubConns[1]}, }, { name: "two routes both match former more specific", routes: []route{ {m: newCompositeMatcher(newPathExactMatcher("/a/b"), nil, nil), action: "action-0"}, {m: newCompositeMatcher(newPathPrefixMatcher("/a/"), nil, nil), action: "action-1"}, }, pickers: map[internal.LocalityID]*subBalancerState{ makeLocalityFromName("action-0"): {state: balancer.State{ ConnectivityState: connectivity.Ready, Picker: testPickers[0], }}, makeLocalityFromName("action-1"): {state: balancer.State{ ConnectivityState: connectivity.Ready, Picker: testPickers[1], }}, }, info: balancer.PickInfo{FullMethodName: "/a/b"}, // First route is a match, so first action is picked. want: balancer.PickResult{SubConn: testutils.TestSubConns[0]}, }, { name: "tow routes both match latter more specific", routes: []route{ {m: newCompositeMatcher(newPathPrefixMatcher("/a/"), nil, nil), action: "action-0"}, {m: newCompositeMatcher(newPathExactMatcher("/a/b"), nil, nil), action: "action-1"}, }, pickers: map[internal.LocalityID]*subBalancerState{ makeLocalityFromName("action-0"): {state: balancer.State{ ConnectivityState: connectivity.Ready, Picker: testPickers[0], }}, makeLocalityFromName("action-1"): {state: balancer.State{ ConnectivityState: connectivity.Ready, Picker: testPickers[1], }}, }, info: balancer.PickInfo{FullMethodName: "/a/b"}, // First route is a match, so first action is picked, even though // second is an exact match. want: balancer.PickResult{SubConn: testutils.TestSubConns[0]}, }, } cmpOpts := []cmp.Option{cmp.AllowUnexported(testutils.TestSubConn{})} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pg := newPickerGroup(tt.routes, tt.pickers) got, err := pg.Pick(tt.info) t.Logf("Pick(%+v) = {%+v, %+v}", tt.info, got, err) if err != tt.wantErr { t.Errorf("Pick() error = %v, wantErr %v", err, tt.wantErr) return } if !cmp.Equal(got, tt.want, cmpOpts...) { t.Errorf("Pick() got = %v, want %v, diff %s", got, tt.want, cmp.Diff(got, tt.want, cmpOpts...)) } }) } }