diff --git a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancerProvider.java b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancerProvider.java index bb4f8de5a5f..f265b1ba001 100644 --- a/xds/src/main/java/io/grpc/xds/RingHashLoadBalancerProvider.java +++ b/xds/src/main/java/io/grpc/xds/RingHashLoadBalancerProvider.java @@ -22,6 +22,7 @@ import io.grpc.LoadBalancer; import io.grpc.LoadBalancer.Helper; import io.grpc.LoadBalancerProvider; +import io.grpc.Metadata; import io.grpc.NameResolver.ConfigOrError; import io.grpc.Status; import io.grpc.internal.GrpcUtil; @@ -106,6 +107,16 @@ private ConfigOrError parseLoadBalancingPolicyConfigInternal( return ConfigOrError.fromError(Status.UNAVAILABLE.withDescription( "Invalid 'minRingSize'/'maxRingSize'")); } + if (!requestHashHeader.isEmpty()) { + // gRFC A76: reject header names that are invalid or refer to a binary (-bin) header. + try { + requestHashHeader = + Metadata.Key.of(requestHashHeader, Metadata.ASCII_STRING_MARSHALLER).name(); + } catch (IllegalArgumentException e) { + return ConfigOrError.fromError(Status.UNAVAILABLE.withDescription( + "Invalid 'requestHashHeader': " + requestHashHeader).withCause(e)); + } + } return ConfigOrError.fromConfig( new RingHashConfig(minRingSize, maxRingSize, requestHashHeader)); } diff --git a/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerProviderTest.java b/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerProviderTest.java index 66c9c5c537e..adc26b982da 100644 --- a/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerProviderTest.java +++ b/xds/src/test/java/io/grpc/xds/RingHashLoadBalancerProviderTest.java @@ -281,6 +281,56 @@ public void parseLoadBalancingConfig_requestHashHeaderUnsetWhenEnvVarSet_useDefa } } + @Test + public void parseLoadBalancingConfig_requestHashHeaderWithBinSuffix_rejected() + throws IOException { + System.setProperty(GRPC_EXPERIMENTAL_RING_HASH_SET_REQUEST_HASH_KEY, "true"); + try { + String lbConfig = "{\"requestHashHeader\" : \"dummy-hash-bin\"}"; + ConfigOrError configOrError = + provider.parseLoadBalancingPolicyConfig(parseJsonObject(lbConfig)); + assertThat(configOrError.getError()).isNotNull(); + assertThat(configOrError.getError().getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(configOrError.getError().getDescription()) + .isEqualTo("Invalid 'requestHashHeader': dummy-hash-bin"); + } finally { + System.clearProperty(GRPC_EXPERIMENTAL_RING_HASH_SET_REQUEST_HASH_KEY); + } + } + + @Test + public void parseLoadBalancingConfig_requestHashHeaderWithInvalidCharacters_rejected() + throws IOException { + System.setProperty(GRPC_EXPERIMENTAL_RING_HASH_SET_REQUEST_HASH_KEY, "true"); + try { + String lbConfig = "{\"requestHashHeader\" : \"dummy!hash\"}"; + ConfigOrError configOrError = + provider.parseLoadBalancingPolicyConfig(parseJsonObject(lbConfig)); + assertThat(configOrError.getError()).isNotNull(); + assertThat(configOrError.getError().getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(configOrError.getError().getDescription()) + .isEqualTo("Invalid 'requestHashHeader': dummy!hash"); + } finally { + System.clearProperty(GRPC_EXPERIMENTAL_RING_HASH_SET_REQUEST_HASH_KEY); + } + } + + @Test + public void parseLoadBalancingConfig_requestHashHeaderMixedCase_lowerCased() + throws IOException { + System.setProperty(GRPC_EXPERIMENTAL_RING_HASH_SET_REQUEST_HASH_KEY, "true"); + try { + String lbConfig = "{\"requestHashHeader\" : \"Dummy-Hash\"}"; + ConfigOrError configOrError = + provider.parseLoadBalancingPolicyConfig(parseJsonObject(lbConfig)); + assertThat(configOrError.getConfig()).isNotNull(); + RingHashConfig config = (RingHashConfig) configOrError.getConfig(); + assertThat(config.requestHashHeader).isEqualTo("dummy-hash"); + } finally { + System.clearProperty(GRPC_EXPERIMENTAL_RING_HASH_SET_REQUEST_HASH_KEY); + } + } + @SuppressWarnings("unchecked") private static Map parseJsonObject(String json) throws IOException { return (Map) JsonParser.parse(json);