@@ -29,6 +29,7 @@ import (
2929 "github.com/google/go-cmp/cmp"
3030 "github.com/google/go-cmp/cmp/cmpopts"
3131 "google.golang.org/grpc/internal/testutils"
32+ "google.golang.org/grpc/internal/xds/env"
3233 "google.golang.org/grpc/xds/internal/httpfilter"
3334 "google.golang.org/grpc/xds/internal/version"
3435 "google.golang.org/protobuf/types/known/durationpb"
@@ -1153,6 +1154,61 @@ func (s) TestRoutesProtoToSlice(t *testing.T) {
11531154 }},
11541155 wantErr : false ,
11551156 },
1157+ {
1158+ name : "good-with-channel-id-hash-policy" ,
1159+ routes : []* v3routepb.Route {
1160+ {
1161+ Match : & v3routepb.RouteMatch {
1162+ PathSpecifier : & v3routepb.RouteMatch_Prefix {Prefix : "/a/" },
1163+ Headers : []* v3routepb.HeaderMatcher {
1164+ {
1165+ Name : "th" ,
1166+ HeaderMatchSpecifier : & v3routepb.HeaderMatcher_PrefixMatch {
1167+ PrefixMatch : "tv" ,
1168+ },
1169+ InvertMatch : true ,
1170+ },
1171+ },
1172+ RuntimeFraction : & v3corepb.RuntimeFractionalPercent {
1173+ DefaultValue : & v3typepb.FractionalPercent {
1174+ Numerator : 1 ,
1175+ Denominator : v3typepb .FractionalPercent_HUNDRED ,
1176+ },
1177+ },
1178+ },
1179+ Action : & v3routepb.Route_Route {
1180+ Route : & v3routepb.RouteAction {
1181+ ClusterSpecifier : & v3routepb.RouteAction_WeightedClusters {
1182+ WeightedClusters : & v3routepb.WeightedCluster {
1183+ Clusters : []* v3routepb.WeightedCluster_ClusterWeight {
1184+ {Name : "B" , Weight : & wrapperspb.UInt32Value {Value : 60 }},
1185+ {Name : "A" , Weight : & wrapperspb.UInt32Value {Value : 40 }},
1186+ },
1187+ TotalWeight : & wrapperspb.UInt32Value {Value : 100 },
1188+ }},
1189+ HashPolicy : []* v3routepb.RouteAction_HashPolicy {
1190+ {PolicySpecifier : & v3routepb.RouteAction_HashPolicy_FilterState_ {FilterState : & v3routepb.RouteAction_HashPolicy_FilterState {Key : "io.grpc.channel_id" }}},
1191+ },
1192+ }},
1193+ },
1194+ },
1195+ wantRoutes : []* Route {{
1196+ Prefix : newStringP ("/a/" ),
1197+ Headers : []* HeaderMatcher {
1198+ {
1199+ Name : "th" ,
1200+ InvertMatch : newBoolP (true ),
1201+ PrefixMatch : newStringP ("tv" ),
1202+ },
1203+ },
1204+ Fraction : newUInt32P (10000 ),
1205+ WeightedClusters : map [string ]WeightedCluster {"A" : {Weight : 40 }, "B" : {Weight : 60 }},
1206+ HashPolicies : []* HashPolicy {
1207+ {HashPolicyType : HashPolicyTypeChannelID },
1208+ },
1209+ }},
1210+ wantErr : false ,
1211+ },
11561212 {
11571213 name : "with custom HTTP filter config" ,
11581214 routes : goodRouteWithFilterConfigs (map [string ]* anypb.Any {"foo" : customFilterConfig }),
@@ -1197,7 +1253,9 @@ func (s) TestRoutesProtoToSlice(t *testing.T) {
11971253 return fmt .Sprint (fc )
11981254 }),
11991255 }
1200-
1256+ oldRingHashSupport := env .RingHashSupport
1257+ env .RingHashSupport = true
1258+ defer func () { env .RingHashSupport = oldRingHashSupport }()
12011259 for _ , tt := range tests {
12021260 t .Run (tt .name , func (t * testing.T ) {
12031261 got , err := routesProtoToSlice (tt .routes , nil , false )
@@ -1211,6 +1269,119 @@ func (s) TestRoutesProtoToSlice(t *testing.T) {
12111269 }
12121270}
12131271
1272+ func (s ) TestHashPoliciesProtoToSlice (t * testing.T ) {
1273+ tests := []struct {
1274+ name string
1275+ hashPolicies []* v3routepb.RouteAction_HashPolicy
1276+ wantHashPolicies []* HashPolicy
1277+ wantErr bool
1278+ }{
1279+ // header-hash-policy tests a basic hash policy that specifies to hash a
1280+ // certain header.
1281+ {
1282+ name : "header-hash-policy" ,
1283+ hashPolicies : []* v3routepb.RouteAction_HashPolicy {
1284+ {
1285+ PolicySpecifier : & v3routepb.RouteAction_HashPolicy_Header_ {
1286+ Header : & v3routepb.RouteAction_HashPolicy_Header {
1287+ HeaderName : ":path" ,
1288+ RegexRewrite : & v3matcherpb.RegexMatchAndSubstitute {
1289+ Pattern : & v3matcherpb.RegexMatcher {Regex : "/products" },
1290+ Substitution : "/products" ,
1291+ },
1292+ },
1293+ },
1294+ },
1295+ },
1296+ wantHashPolicies : []* HashPolicy {
1297+ {
1298+ HashPolicyType : HashPolicyTypeHeader ,
1299+ HeaderName : ":path" ,
1300+ Regex : func () * regexp.Regexp { return regexp .MustCompile ("/products" ) }(),
1301+ RegexSubstitution : "/products" ,
1302+ },
1303+ },
1304+ },
1305+ // channel-id-hash-policy tests a basic hash policy that specifies to
1306+ // hash a unique identifier of the channel.
1307+ {
1308+ name : "channel-id-hash-policy" ,
1309+ hashPolicies : []* v3routepb.RouteAction_HashPolicy {
1310+ {PolicySpecifier : & v3routepb.RouteAction_HashPolicy_FilterState_ {FilterState : & v3routepb.RouteAction_HashPolicy_FilterState {Key : "io.grpc.channel_id" }}},
1311+ },
1312+ wantHashPolicies : []* HashPolicy {
1313+ {HashPolicyType : HashPolicyTypeChannelID },
1314+ },
1315+ },
1316+ // unsupported-filter-state-key tests that an unsupported key in the
1317+ // filter state hash policy are treated as a no-op.
1318+ {
1319+ name : "wrong-filter-state-key" ,
1320+ hashPolicies : []* v3routepb.RouteAction_HashPolicy {
1321+ {PolicySpecifier : & v3routepb.RouteAction_HashPolicy_FilterState_ {FilterState : & v3routepb.RouteAction_HashPolicy_FilterState {Key : "unsupported key" }}},
1322+ },
1323+ },
1324+ // no-op-hash-policy tests that hash policies that are not supported by
1325+ // grpc are treated as a no-op.
1326+ {
1327+ name : "no-op-hash-policy" ,
1328+ hashPolicies : []* v3routepb.RouteAction_HashPolicy {
1329+ {PolicySpecifier : & v3routepb.RouteAction_HashPolicy_FilterState_ {}},
1330+ },
1331+ },
1332+ // header-and-channel-id-hash-policy test that a list of header and
1333+ // channel id hash policies are successfully converted to an internal
1334+ // struct.
1335+ {
1336+ name : "header-and-channel-id-hash-policy" ,
1337+ hashPolicies : []* v3routepb.RouteAction_HashPolicy {
1338+ {
1339+ PolicySpecifier : & v3routepb.RouteAction_HashPolicy_Header_ {
1340+ Header : & v3routepb.RouteAction_HashPolicy_Header {
1341+ HeaderName : ":path" ,
1342+ RegexRewrite : & v3matcherpb.RegexMatchAndSubstitute {
1343+ Pattern : & v3matcherpb.RegexMatcher {Regex : "/products" },
1344+ Substitution : "/products" ,
1345+ },
1346+ },
1347+ },
1348+ },
1349+ {
1350+ PolicySpecifier : & v3routepb.RouteAction_HashPolicy_FilterState_ {FilterState : & v3routepb.RouteAction_HashPolicy_FilterState {Key : "io.grpc.channel_id" }},
1351+ Terminal : true ,
1352+ },
1353+ },
1354+ wantHashPolicies : []* HashPolicy {
1355+ {
1356+ HashPolicyType : HashPolicyTypeHeader ,
1357+ HeaderName : ":path" ,
1358+ Regex : func () * regexp.Regexp { return regexp .MustCompile ("/products" ) }(),
1359+ RegexSubstitution : "/products" ,
1360+ },
1361+ {
1362+ HashPolicyType : HashPolicyTypeChannelID ,
1363+ Terminal : true ,
1364+ },
1365+ },
1366+ },
1367+ }
1368+
1369+ oldRingHashSupport := env .RingHashSupport
1370+ env .RingHashSupport = true
1371+ defer func () { env .RingHashSupport = oldRingHashSupport }()
1372+ for _ , tt := range tests {
1373+ t .Run (tt .name , func (t * testing.T ) {
1374+ got , err := hashPoliciesProtoToSlice (tt .hashPolicies , nil )
1375+ if (err != nil ) != tt .wantErr {
1376+ t .Fatalf ("hashPoliciesProtoToSlice() error = %v, wantErr %v" , err , tt .wantErr )
1377+ }
1378+ if diff := cmp .Diff (got , tt .wantHashPolicies , cmp .AllowUnexported (regexp.Regexp {})); diff != "" {
1379+ t .Fatalf ("hashPoliciesProtoToSlice() returned unexpected diff (-got +want):\n %s" , diff )
1380+ }
1381+ })
1382+ }
1383+ }
1384+
12141385func newStringP (s string ) * string {
12151386 return & s
12161387}
0 commit comments