blob: 80cd2a8046ecf041977b4e60e3891c46c6d6a70e [file] [log] [blame]
/*
* Copyright 2021 The 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 io.grpc.xds;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.protobuf.Any;
import com.google.protobuf.BoolValue;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.StringValue;
import com.google.protobuf.UInt32Value;
import com.google.protobuf.UInt64Value;
import com.google.protobuf.util.Durations;
import com.google.re2j.Pattern;
import io.envoyproxy.envoy.config.cluster.v3.Cluster;
import io.envoyproxy.envoy.config.cluster.v3.Cluster.DiscoveryType;
import io.envoyproxy.envoy.config.cluster.v3.Cluster.EdsClusterConfig;
import io.envoyproxy.envoy.config.cluster.v3.Cluster.LbPolicy;
import io.envoyproxy.envoy.config.cluster.v3.Cluster.RingHashLbConfig;
import io.envoyproxy.envoy.config.cluster.v3.Cluster.RingHashLbConfig.HashFunction;
import io.envoyproxy.envoy.config.core.v3.Address;
import io.envoyproxy.envoy.config.core.v3.AggregatedConfigSource;
import io.envoyproxy.envoy.config.core.v3.CidrRange;
import io.envoyproxy.envoy.config.core.v3.ConfigSource;
import io.envoyproxy.envoy.config.core.v3.DataSource;
import io.envoyproxy.envoy.config.core.v3.HttpProtocolOptions;
import io.envoyproxy.envoy.config.core.v3.Locality;
import io.envoyproxy.envoy.config.core.v3.RuntimeFractionalPercent;
import io.envoyproxy.envoy.config.core.v3.SocketAddress;
import io.envoyproxy.envoy.config.core.v3.TrafficDirection;
import io.envoyproxy.envoy.config.core.v3.TransportSocket;
import io.envoyproxy.envoy.config.core.v3.TypedExtensionConfig;
import io.envoyproxy.envoy.config.endpoint.v3.Endpoint;
import io.envoyproxy.envoy.config.listener.v3.Filter;
import io.envoyproxy.envoy.config.listener.v3.FilterChain;
import io.envoyproxy.envoy.config.listener.v3.FilterChainMatch;
import io.envoyproxy.envoy.config.listener.v3.Listener;
import io.envoyproxy.envoy.config.listener.v3.ListenerFilter;
import io.envoyproxy.envoy.config.rbac.v3.Permission;
import io.envoyproxy.envoy.config.rbac.v3.Policy;
import io.envoyproxy.envoy.config.rbac.v3.Principal;
import io.envoyproxy.envoy.config.rbac.v3.RBAC;
import io.envoyproxy.envoy.config.rbac.v3.RBAC.Action;
import io.envoyproxy.envoy.config.route.v3.DirectResponseAction;
import io.envoyproxy.envoy.config.route.v3.FilterAction;
import io.envoyproxy.envoy.config.route.v3.NonForwardingAction;
import io.envoyproxy.envoy.config.route.v3.RedirectAction;
import io.envoyproxy.envoy.config.route.v3.RetryPolicy;
import io.envoyproxy.envoy.config.route.v3.RetryPolicy.RetryBackOff;
import io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.ConnectionProperties;
import io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.FilterState;
import io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.Header;
import io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.QueryParameter;
import io.envoyproxy.envoy.config.route.v3.RouteAction.MaxStreamDuration;
import io.envoyproxy.envoy.config.route.v3.WeightedCluster;
import io.envoyproxy.envoy.extensions.filters.common.fault.v3.FaultDelay;
import io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort;
import io.envoyproxy.envoy.extensions.filters.http.fault.v3.HTTPFault;
import io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBACPerRoute;
import io.envoyproxy.envoy.extensions.filters.http.router.v3.Router;
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager;
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter;
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateProviderPluginInstance;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CertificateProviderInstance;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CombinedCertificateValidationContext;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.SdsSecretConfig;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsCertificate;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsParameters;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext;
import io.envoyproxy.envoy.type.matcher.v3.RegexMatchAndSubstitute;
import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher;
import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher.GoogleRE2;
import io.envoyproxy.envoy.type.matcher.v3.StringMatcher;
import io.envoyproxy.envoy.type.v3.FractionalPercent;
import io.envoyproxy.envoy.type.v3.FractionalPercent.DenominatorType;
import io.envoyproxy.envoy.type.v3.Int64Range;
import io.grpc.Status.Code;
import io.grpc.xds.ClientXdsClient.ResourceInvalidException;
import io.grpc.xds.ClientXdsClient.StructOrError;
import io.grpc.xds.Endpoints.LbEndpoint;
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
import io.grpc.xds.Filter.FilterConfig;
import io.grpc.xds.VirtualHost.Route;
import io.grpc.xds.VirtualHost.Route.RouteAction;
import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight;
import io.grpc.xds.VirtualHost.Route.RouteAction.HashPolicy;
import io.grpc.xds.VirtualHost.Route.RouteMatch;
import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher;
import io.grpc.xds.XdsClient.CdsUpdate;
import io.grpc.xds.internal.Matchers.FractionMatcher;
import io.grpc.xds.internal.Matchers.HeaderMatcher;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class ClientXdsClientDataTest {
@SuppressWarnings("deprecation") // https://github.com/grpc/grpc-java/issues/7467
@Rule
public final ExpectedException thrown = ExpectedException.none();
private final FilterRegistry filterRegistry = FilterRegistry.newRegistry();
private boolean originalEnableRetry;
@Before
public void setUp() {
originalEnableRetry = ClientXdsClient.enableRetry;
assertThat(originalEnableRetry).isTrue();
}
@After
public void tearDown() {
ClientXdsClient.enableRetry = originalEnableRetry;
}
@Test
public void parseRoute_withRouteAction() {
io.envoyproxy.envoy.config.route.v3.Route proto =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setName("route-blade")
.setMatch(
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPath("/service/method"))
.setRoute(
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo"))
.build();
StructOrError<Route> struct = ClientXdsClient.parseRoute(proto, filterRegistry, false);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct())
.isEqualTo(
Route.forAction(
RouteMatch.create(PathMatcher.fromPath("/service/method", false),
Collections.<HeaderMatcher>emptyList(), null),
RouteAction.forCluster(
"cluster-foo", Collections.<HashPolicy>emptyList(), null, null),
ImmutableMap.<String, FilterConfig>of()));
}
@Test
public void parseRoute_withNonForwardingAction() {
io.envoyproxy.envoy.config.route.v3.Route proto =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setName("route-blade")
.setMatch(
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPath("/service/method"))
.setNonForwardingAction(NonForwardingAction.getDefaultInstance())
.build();
StructOrError<Route> struct = ClientXdsClient.parseRoute(proto, filterRegistry, false);
assertThat(struct.getStruct())
.isEqualTo(
Route.forNonForwardingAction(
RouteMatch.create(PathMatcher.fromPath("/service/method", false),
Collections.<HeaderMatcher>emptyList(), null),
ImmutableMap.<String, FilterConfig>of()));
}
@Test
public void parseRoute_withUnsupportedActionTypes() {
StructOrError<Route> res;
io.envoyproxy.envoy.config.route.v3.Route redirectRoute =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setName("route-blade")
.setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPath(""))
.setRedirect(RedirectAction.getDefaultInstance())
.build();
res = ClientXdsClient.parseRoute(redirectRoute, filterRegistry, false);
assertThat(res.getStruct()).isNull();
assertThat(res.getErrorDetail())
.isEqualTo("Route [route-blade] with unknown action type: REDIRECT");
io.envoyproxy.envoy.config.route.v3.Route directResponseRoute =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setName("route-blade")
.setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPath(""))
.setDirectResponse(DirectResponseAction.getDefaultInstance())
.build();
res = ClientXdsClient.parseRoute(directResponseRoute, filterRegistry, false);
assertThat(res.getStruct()).isNull();
assertThat(res.getErrorDetail())
.isEqualTo("Route [route-blade] with unknown action type: DIRECT_RESPONSE");
io.envoyproxy.envoy.config.route.v3.Route filterRoute =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setName("route-blade")
.setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPath(""))
.setFilterAction(FilterAction.getDefaultInstance())
.build();
res = ClientXdsClient.parseRoute(filterRoute, filterRegistry, false);
assertThat(res.getStruct()).isNull();
assertThat(res.getErrorDetail())
.isEqualTo("Route [route-blade] with unknown action type: FILTER_ACTION");
}
@Test
public void parseRoute_skipRouteWithUnsupportedMatcher() {
io.envoyproxy.envoy.config.route.v3.Route proto =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setName("ignore me")
.setMatch(
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPath("/service/method")
.addQueryParameters(
io.envoyproxy.envoy.config.route.v3.QueryParameterMatcher
.getDefaultInstance())) // query parameter not supported
.setRoute(
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo"))
.build();
assertThat(ClientXdsClient.parseRoute(proto, filterRegistry, false)).isNull();
}
@Test
public void parseRoute_skipRouteWithUnsupportedAction() {
io.envoyproxy.envoy.config.route.v3.Route proto =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setName("ignore me")
.setMatch(
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPath("/service/method"))
.setRoute(
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setClusterHeader("cluster header")) // cluster_header action not supported
.build();
assertThat(ClientXdsClient.parseRoute(proto, filterRegistry, false)).isNull();
}
@Test
@SuppressWarnings("deprecation")
public void parseRouteMatch_withHeaderMatcher() {
io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPrefix("")
.addHeaders(
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName(":scheme")
.setPrefixMatch("http"))
.addHeaders(
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName(":method")
.setExactMatch("PUT"))
.build();
StructOrError<RouteMatch> struct = ClientXdsClient.parseRouteMatch(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct())
.isEqualTo(
RouteMatch.create(
PathMatcher.fromPrefix("", false),
Arrays.asList(
HeaderMatcher.forPrefix(":scheme", "http", false),
HeaderMatcher.forExactValue(":method", "PUT", false)),
null));
}
@Test
public void parseRouteMatch_withRuntimeFractionMatcher() {
io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPrefix("")
.setRuntimeFraction(
RuntimeFractionalPercent.newBuilder()
.setDefaultValue(
FractionalPercent.newBuilder()
.setNumerator(30)
.setDenominator(FractionalPercent.DenominatorType.HUNDRED)))
.build();
StructOrError<RouteMatch> struct = ClientXdsClient.parseRouteMatch(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct())
.isEqualTo(
RouteMatch.create(
PathMatcher.fromPrefix( "", false), Collections.<HeaderMatcher>emptyList(),
FractionMatcher.create(30, 100)));
}
@Test
public void parsePathMatcher_withFullPath() {
io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPath("/service/method")
.build();
StructOrError<PathMatcher> struct = ClientXdsClient.parsePathMatcher(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct()).isEqualTo(
PathMatcher.fromPath("/service/method", false));
}
@Test
public void parsePathMatcher_withPrefix() {
io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPrefix("/").build();
StructOrError<PathMatcher> struct = ClientXdsClient.parsePathMatcher(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct()).isEqualTo(
PathMatcher.fromPrefix("/", false));
}
@Test
public void parsePathMatcher_withSafeRegEx() {
io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setSafeRegex(RegexMatcher.newBuilder().setRegex("."))
.build();
StructOrError<PathMatcher> struct = ClientXdsClient.parsePathMatcher(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct()).isEqualTo(PathMatcher.fromRegEx(Pattern.compile(".")));
}
@Test
@SuppressWarnings("deprecation")
public void parseHeaderMatcher_withExactMatch() {
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName(":method")
.setExactMatch("PUT")
.build();
StructOrError<HeaderMatcher> struct1 = ClientXdsClient.parseHeaderMatcher(proto);
assertThat(struct1.getErrorDetail()).isNull();
assertThat(struct1.getStruct()).isEqualTo(
HeaderMatcher.forExactValue(":method", "PUT", false));
}
@Test
@SuppressWarnings("deprecation")
public void parseHeaderMatcher_withSafeRegExMatch() {
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName(":method")
.setSafeRegexMatch(RegexMatcher.newBuilder().setRegex("P*"))
.build();
StructOrError<HeaderMatcher> struct3 = ClientXdsClient.parseHeaderMatcher(proto);
assertThat(struct3.getErrorDetail()).isNull();
assertThat(struct3.getStruct()).isEqualTo(
HeaderMatcher.forSafeRegEx(":method", Pattern.compile("P*"), false));
}
@Test
public void parseHeaderMatcher_withRangeMatch() {
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName("timeout")
.setRangeMatch(Int64Range.newBuilder().setStart(10L).setEnd(20L))
.build();
StructOrError<HeaderMatcher> struct4 = ClientXdsClient.parseHeaderMatcher(proto);
assertThat(struct4.getErrorDetail()).isNull();
assertThat(struct4.getStruct()).isEqualTo(
HeaderMatcher.forRange("timeout", HeaderMatcher.Range.create(10L, 20L), false));
}
@Test
public void parseHeaderMatcher_withPresentMatch() {
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName("user-agent")
.setPresentMatch(true)
.build();
StructOrError<HeaderMatcher> struct5 = ClientXdsClient.parseHeaderMatcher(proto);
assertThat(struct5.getErrorDetail()).isNull();
assertThat(struct5.getStruct()).isEqualTo(
HeaderMatcher.forPresent("user-agent", true, false));
}
@Test
@SuppressWarnings("deprecation")
public void parseHeaderMatcher_withPrefixMatch() {
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName("authority")
.setPrefixMatch("service-foo")
.build();
StructOrError<HeaderMatcher> struct6 = ClientXdsClient.parseHeaderMatcher(proto);
assertThat(struct6.getErrorDetail()).isNull();
assertThat(struct6.getStruct()).isEqualTo(
HeaderMatcher.forPrefix("authority", "service-foo", false));
}
@Test
@SuppressWarnings("deprecation")
public void parseHeaderMatcher_withSuffixMatch() {
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName("authority")
.setSuffixMatch("googleapis.com")
.build();
StructOrError<HeaderMatcher> struct7 = ClientXdsClient.parseHeaderMatcher(proto);
assertThat(struct7.getErrorDetail()).isNull();
assertThat(struct7.getStruct()).isEqualTo(
HeaderMatcher.forSuffix("authority", "googleapis.com", false));
}
@Test
@SuppressWarnings("deprecation")
public void parseHeaderMatcher_malformedRegExPattern() {
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName(":method")
.setSafeRegexMatch(RegexMatcher.newBuilder().setRegex("["))
.build();
StructOrError<HeaderMatcher> struct = ClientXdsClient.parseHeaderMatcher(proto);
assertThat(struct.getErrorDetail()).isNotNull();
assertThat(struct.getStruct()).isNull();
}
@Test
public void parseRouteAction_withCluster() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.build();
StructOrError<RouteAction> struct =
ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct().cluster()).isEqualTo("cluster-foo");
assertThat(struct.getStruct().weightedClusters()).isNull();
}
@Test
public void parseRouteAction_withWeightedCluster() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setWeightedClusters(
WeightedCluster.newBuilder()
.addClusters(
WeightedCluster.ClusterWeight
.newBuilder()
.setName("cluster-foo")
.setWeight(UInt32Value.newBuilder().setValue(30)))
.addClusters(WeightedCluster.ClusterWeight
.newBuilder()
.setName("cluster-bar")
.setWeight(UInt32Value.newBuilder().setValue(70))))
.build();
StructOrError<RouteAction> struct =
ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct().cluster()).isNull();
assertThat(struct.getStruct().weightedClusters()).containsExactly(
ClusterWeight.create("cluster-foo", 30, ImmutableMap.<String, FilterConfig>of()),
ClusterWeight.create("cluster-bar", 70, ImmutableMap.<String, FilterConfig>of()));
}
@Test
public void parseRouteAction_withTimeoutByGrpcTimeoutHeaderMax() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setMaxStreamDuration(
MaxStreamDuration.newBuilder()
.setGrpcTimeoutHeaderMax(Durations.fromSeconds(5L))
.setMaxStreamDuration(Durations.fromMillis(20L)))
.build();
StructOrError<RouteAction> struct =
ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
assertThat(struct.getStruct().timeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L));
}
@Test
public void parseRouteAction_withTimeoutByMaxStreamDuration() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setMaxStreamDuration(
MaxStreamDuration.newBuilder()
.setMaxStreamDuration(Durations.fromSeconds(5L)))
.build();
StructOrError<RouteAction> struct =
ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
assertThat(struct.getStruct().timeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L));
}
@Test
public void parseRouteAction_withTimeoutUnset() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.build();
StructOrError<RouteAction> struct =
ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
assertThat(struct.getStruct().timeoutNano()).isNull();
}
@Test
public void parseRouteAction_withRetryPolicy() {
ClientXdsClient.enableRetry = true;
RetryPolicy.Builder builder = RetryPolicy.newBuilder()
.setNumRetries(UInt32Value.of(3))
.setRetryBackOff(
RetryBackOff.newBuilder()
.setBaseInterval(Durations.fromMillis(500))
.setMaxInterval(Durations.fromMillis(600)))
.setPerTryTimeout(Durations.fromMillis(300))
.setRetryOn(
"cancelled,deadline-exceeded,internal,resource-exhausted,unavailable");
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setRetryPolicy(builder.build())
.build();
StructOrError<RouteAction> struct =
ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
RouteAction.RetryPolicy retryPolicy = struct.getStruct().retryPolicy();
assertThat(retryPolicy.maxAttempts()).isEqualTo(4);
assertThat(retryPolicy.initialBackoff()).isEqualTo(Durations.fromMillis(500));
assertThat(retryPolicy.maxBackoff()).isEqualTo(Durations.fromMillis(600));
// Not supporting per_try_timeout yet.
assertThat(retryPolicy.perAttemptRecvTimeout()).isEqualTo(null);
assertThat(retryPolicy.retryableStatusCodes()).containsExactly(
Code.CANCELLED, Code.DEADLINE_EXCEEDED, Code.INTERNAL, Code.RESOURCE_EXHAUSTED,
Code.UNAVAILABLE);
// empty retry_on
builder = RetryPolicy.newBuilder()
.setNumRetries(UInt32Value.of(3))
.setRetryBackOff(
RetryBackOff.newBuilder()
.setBaseInterval(Durations.fromMillis(500))
.setMaxInterval(Durations.fromMillis(600)))
.setPerTryTimeout(Durations.fromMillis(300)); // Not supporting per_try_timeout yet.
proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setRetryPolicy(builder.build())
.build();
struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
assertThat(struct.getStruct().retryPolicy()).isNull();
// base_interval unset
builder
.setRetryOn("cancelled")
.setRetryBackOff(RetryBackOff.newBuilder().setMaxInterval(Durations.fromMillis(600)));
proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setRetryPolicy(builder)
.build();
struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
assertThat(struct.getErrorDetail()).isEqualTo("No base_interval specified in retry_backoff");
// max_interval unset
builder.setRetryBackOff(RetryBackOff.newBuilder().setBaseInterval(Durations.fromMillis(500)));
proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setRetryPolicy(builder)
.build();
struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
retryPolicy = struct.getStruct().retryPolicy();
assertThat(retryPolicy.maxBackoff()).isEqualTo(Durations.fromMillis(500 * 10));
// base_interval < 0
builder.setRetryBackOff(RetryBackOff.newBuilder().setBaseInterval(Durations.fromMillis(-1)));
proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setRetryPolicy(builder)
.build();
struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
assertThat(struct.getErrorDetail())
.isEqualTo("base_interval in retry_backoff must be positive");
// base_interval > max_interval > 1ms
builder.setRetryBackOff(
RetryBackOff.newBuilder()
.setBaseInterval(Durations.fromMillis(200)).setMaxInterval(Durations.fromMillis(100)));
proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setRetryPolicy(builder)
.build();
struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
assertThat(struct.getErrorDetail())
.isEqualTo("max_interval in retry_backoff cannot be less than base_interval");
// 1ms > base_interval > max_interval
builder.setRetryBackOff(
RetryBackOff.newBuilder()
.setBaseInterval(Durations.fromNanos(200)).setMaxInterval(Durations.fromNanos(100)));
proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setRetryPolicy(builder)
.build();
struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
assertThat(struct.getErrorDetail())
.isEqualTo("max_interval in retry_backoff cannot be less than base_interval");
// 1ms > max_interval > base_interval
builder.setRetryBackOff(
RetryBackOff.newBuilder()
.setBaseInterval(Durations.fromNanos(100)).setMaxInterval(Durations.fromNanos(200)));
proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setRetryPolicy(builder)
.build();
struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
assertThat(struct.getStruct().retryPolicy().initialBackoff())
.isEqualTo(Durations.fromMillis(1));
assertThat(struct.getStruct().retryPolicy().maxBackoff())
.isEqualTo(Durations.fromMillis(1));
// retry_backoff unset
builder = RetryPolicy.newBuilder()
.setNumRetries(UInt32Value.of(3))
.setPerTryTimeout(Durations.fromMillis(300))
.setRetryOn("cancelled");
proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setRetryPolicy(builder)
.build();
struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
retryPolicy = struct.getStruct().retryPolicy();
assertThat(retryPolicy.initialBackoff()).isEqualTo(Durations.fromMillis(25));
assertThat(retryPolicy.maxBackoff()).isEqualTo(Durations.fromMillis(250));
// unsupported retry_on value
builder = RetryPolicy.newBuilder()
.setNumRetries(UInt32Value.of(3))
.setRetryBackOff(
RetryBackOff.newBuilder()
.setBaseInterval(Durations.fromMillis(500))
.setMaxInterval(Durations.fromMillis(600)))
.setPerTryTimeout(Durations.fromMillis(300))
.setRetryOn("cancelled,unsupported-foo");
proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setRetryPolicy(builder)
.build();
struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
assertThat(struct.getStruct().retryPolicy().retryableStatusCodes())
.containsExactly(Code.CANCELLED);
// unsupported retry_on code
builder = RetryPolicy.newBuilder()
.setNumRetries(UInt32Value.of(3))
.setRetryBackOff(
RetryBackOff.newBuilder()
.setBaseInterval(Durations.fromMillis(500))
.setMaxInterval(Durations.fromMillis(600)))
.setPerTryTimeout(Durations.fromMillis(300))
.setRetryOn("cancelled,abort");
proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setRetryPolicy(builder)
.build();
struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
assertThat(struct.getStruct().retryPolicy().retryableStatusCodes())
.containsExactly(Code.CANCELLED);
// whitespace in retry_on
builder = RetryPolicy.newBuilder()
.setNumRetries(UInt32Value.of(3))
.setRetryBackOff(
RetryBackOff.newBuilder()
.setBaseInterval(Durations.fromMillis(500))
.setMaxInterval(Durations.fromMillis(600)))
.setPerTryTimeout(Durations.fromMillis(300))
.setRetryOn("abort, , cancelled , ");
proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setRetryPolicy(builder)
.build();
struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
assertThat(struct.getStruct().retryPolicy().retryableStatusCodes())
.containsExactly(Code.CANCELLED);
}
@Test
public void parseRouteAction_withHashPolicies() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.addHashPolicy(
io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.newBuilder()
.setHeader(
Header.newBuilder()
.setHeaderName("user-agent")
.setRegexRewrite(
RegexMatchAndSubstitute.newBuilder()
.setPattern(
RegexMatcher.newBuilder()
.setGoogleRe2(GoogleRE2.getDefaultInstance())
.setRegex("grpc.*"))
.setSubstitution("gRPC"))))
.addHashPolicy(
io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.newBuilder()
.setConnectionProperties(ConnectionProperties.newBuilder().setSourceIp(true))
.setTerminal(true)) // unsupported
.addHashPolicy(
io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.newBuilder()
.setFilterState(
FilterState.newBuilder()
.setKey(ClientXdsClient.HASH_POLICY_FILTER_STATE_KEY)))
.addHashPolicy(
io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.newBuilder()
.setQueryParameter(
QueryParameter.newBuilder().setName("param"))) // unsupported
.build();
StructOrError<RouteAction> struct =
ClientXdsClient.parseRouteAction(proto, filterRegistry, false);
List<HashPolicy> policies = struct.getStruct().hashPolicies();
assertThat(policies).hasSize(2);
assertThat(policies.get(0).type()).isEqualTo(HashPolicy.Type.HEADER);
assertThat(policies.get(0).headerName()).isEqualTo("user-agent");
assertThat(policies.get(0).isTerminal()).isFalse();
assertThat(policies.get(0).regEx().pattern()).isEqualTo("grpc.*");
assertThat(policies.get(0).regExSubstitution()).isEqualTo("gRPC");
assertThat(policies.get(1).type()).isEqualTo(HashPolicy.Type.CHANNEL_ID);
assertThat(policies.get(1).isTerminal()).isFalse();
}
@Test
public void parseClusterWeight() {
io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight proto =
io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight.newBuilder()
.setName("cluster-foo")
.setWeight(UInt32Value.newBuilder().setValue(30))
.build();
ClusterWeight clusterWeight =
ClientXdsClient.parseClusterWeight(proto, filterRegistry, false).getStruct();
assertThat(clusterWeight.name()).isEqualTo("cluster-foo");
assertThat(clusterWeight.weight()).isEqualTo(30);
}
@Test
public void parseLocalityLbEndpoints_withHealthyEndpoints() {
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto =
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints.newBuilder()
.setLocality(Locality.newBuilder()
.setRegion("region-foo").setZone("zone-foo").setSubZone("subZone-foo"))
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(100)) // locality weight
.setPriority(1)
.addLbEndpoints(io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint.newBuilder()
.setEndpoint(Endpoint.newBuilder()
.setAddress(Address.newBuilder()
.setSocketAddress(
SocketAddress.newBuilder()
.setAddress("172.14.14.5").setPortValue(8888))))
.setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.HEALTHY)
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight
.build();
StructOrError<LocalityLbEndpoints> struct = ClientXdsClient.parseLocalityLbEndpoints(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct()).isEqualTo(
LocalityLbEndpoints.create(
Collections.singletonList(LbEndpoint.create("172.14.14.5", 8888, 20, true)), 100, 1));
}
@Test
public void parseLocalityLbEndpoints_treatUnknownHealthAsHealthy() {
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto =
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints.newBuilder()
.setLocality(Locality.newBuilder()
.setRegion("region-foo").setZone("zone-foo").setSubZone("subZone-foo"))
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(100)) // locality weight
.setPriority(1)
.addLbEndpoints(io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint.newBuilder()
.setEndpoint(Endpoint.newBuilder()
.setAddress(Address.newBuilder()
.setSocketAddress(
SocketAddress.newBuilder()
.setAddress("172.14.14.5").setPortValue(8888))))
.setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN)
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight
.build();
StructOrError<LocalityLbEndpoints> struct = ClientXdsClient.parseLocalityLbEndpoints(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct()).isEqualTo(
LocalityLbEndpoints.create(
Collections.singletonList(LbEndpoint.create("172.14.14.5", 8888, 20, true)), 100, 1));
}
@Test
public void parseLocalityLbEndpoints_withUnHealthyEndpoints() {
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto =
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints.newBuilder()
.setLocality(Locality.newBuilder()
.setRegion("region-foo").setZone("zone-foo").setSubZone("subZone-foo"))
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(100)) // locality weight
.setPriority(1)
.addLbEndpoints(io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint.newBuilder()
.setEndpoint(Endpoint.newBuilder()
.setAddress(Address.newBuilder()
.setSocketAddress(
SocketAddress.newBuilder()
.setAddress("172.14.14.5").setPortValue(8888))))
.setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNHEALTHY)
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight
.build();
StructOrError<LocalityLbEndpoints> struct = ClientXdsClient.parseLocalityLbEndpoints(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct()).isEqualTo(
LocalityLbEndpoints.create(
Collections.singletonList(LbEndpoint.create("172.14.14.5", 8888, 20, false)), 100, 1));
}
@Test
public void parseLocalityLbEndpoints_ignorZeroWeightLocality() {
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto =
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints.newBuilder()
.setLocality(Locality.newBuilder()
.setRegion("region-foo").setZone("zone-foo").setSubZone("subZone-foo"))
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(0)) // locality weight
.setPriority(1)
.addLbEndpoints(io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint.newBuilder()
.setEndpoint(Endpoint.newBuilder()
.setAddress(Address.newBuilder()
.setSocketAddress(
SocketAddress.newBuilder()
.setAddress("172.14.14.5").setPortValue(8888))))
.setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN)
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight
.build();
assertThat(ClientXdsClient.parseLocalityLbEndpoints(proto)).isNull();
}
@Test
public void parseLocalityLbEndpoints_invalidPriority() {
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto =
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints.newBuilder()
.setLocality(Locality.newBuilder()
.setRegion("region-foo").setZone("zone-foo").setSubZone("subZone-foo"))
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(100)) // locality weight
.setPriority(-1)
.addLbEndpoints(io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint.newBuilder()
.setEndpoint(Endpoint.newBuilder()
.setAddress(Address.newBuilder()
.setSocketAddress(
SocketAddress.newBuilder()
.setAddress("172.14.14.5").setPortValue(8888))))
.setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN)
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight
.build();
StructOrError<LocalityLbEndpoints> struct = ClientXdsClient.parseLocalityLbEndpoints(proto);
assertThat(struct.getErrorDetail()).isEqualTo("negative priority");
}
@Test
public void parseHttpFilter_unsupportedButOptional() {
HttpFilter httpFilter = HttpFilter.newBuilder()
.setIsOptional(true)
.setTypedConfig(Any.pack(StringValue.of("unsupported")))
.build();
assertThat(ClientXdsClient.parseHttpFilter(httpFilter, filterRegistry, true)).isNull();
}
@Test
public void parseHttpFilter_unsupportedAndRequired() {
HttpFilter httpFilter = HttpFilter.newBuilder()
.setIsOptional(false)
.setName("unsupported.filter")
.setTypedConfig(Any.pack(StringValue.of("string value")))
.build();
assertThat(ClientXdsClient.parseHttpFilter(httpFilter, filterRegistry, true)
.getErrorDetail()).isEqualTo(
"HttpFilter [unsupported.filter]"
+ "(type.googleapis.com/google.protobuf.StringValue) is required but unsupported "
+ "for client");
}
@Test
public void parseHttpFilter_routerFilterForClient() {
filterRegistry.register(RouterFilter.INSTANCE);
HttpFilter httpFilter =
HttpFilter.newBuilder()
.setIsOptional(false)
.setName("envoy.router")
.setTypedConfig(Any.pack(Router.getDefaultInstance()))
.build();
FilterConfig config = ClientXdsClient.parseHttpFilter(
httpFilter, filterRegistry, true /* isForClient */).getStruct();
assertThat(config.typeUrl()).isEqualTo(RouterFilter.TYPE_URL);
}
@Test
public void parseHttpFilter_routerFilterForServer() {
filterRegistry.register(RouterFilter.INSTANCE);
HttpFilter httpFilter =
HttpFilter.newBuilder()
.setIsOptional(false)
.setName("envoy.router")
.setTypedConfig(Any.pack(Router.getDefaultInstance()))
.build();
FilterConfig config = ClientXdsClient.parseHttpFilter(
httpFilter, filterRegistry, false /* isForClient */).getStruct();
assertThat(config.typeUrl()).isEqualTo(RouterFilter.TYPE_URL);
}
@Test
public void parseHttpFilter_faultConfigForClient() {
filterRegistry.register(FaultFilter.INSTANCE);
HttpFilter httpFilter =
HttpFilter.newBuilder()
.setIsOptional(false)
.setName("envoy.fault")
.setTypedConfig(
Any.pack(
HTTPFault.newBuilder()
.setDelay(
FaultDelay.newBuilder()
.setFixedDelay(Durations.fromNanos(1234L)))
.setAbort(
FaultAbort.newBuilder()
.setHttpStatus(300)
.setPercentage(
FractionalPercent.newBuilder()
.setNumerator(10)
.setDenominator(DenominatorType.HUNDRED)))
.build()))
.build();
FilterConfig config = ClientXdsClient.parseHttpFilter(
httpFilter, filterRegistry, true /* isForClient */).getStruct();
assertThat(config).isInstanceOf(FaultConfig.class);
}
@Test
public void parseHttpFilter_faultConfigUnsupportedForServer() {
filterRegistry.register(FaultFilter.INSTANCE);
HttpFilter httpFilter =
HttpFilter.newBuilder()
.setIsOptional(false)
.setName("envoy.fault")
.setTypedConfig(
Any.pack(
HTTPFault.newBuilder()
.setDelay(
FaultDelay.newBuilder()
.setFixedDelay(Durations.fromNanos(1234L)))
.setAbort(
FaultAbort.newBuilder()
.setHttpStatus(300)
.setPercentage(
FractionalPercent.newBuilder()
.setNumerator(10)
.setDenominator(DenominatorType.HUNDRED)))
.build()))
.build();
StructOrError<FilterConfig> config =
ClientXdsClient.parseHttpFilter(httpFilter, filterRegistry, false /* isForClient */);
assertThat(config.getErrorDetail()).isEqualTo(
"HttpFilter [envoy.fault](" + FaultFilter.TYPE_URL + ") is required but "
+ "unsupported for server");
}
@Test
public void parseHttpFilter_rbacConfigForServer() {
filterRegistry.register(RbacFilter.INSTANCE);
HttpFilter httpFilter =
HttpFilter.newBuilder()
.setIsOptional(false)
.setName("envoy.auth")
.setTypedConfig(
Any.pack(
io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC.newBuilder()
.setRules(
RBAC.newBuilder()
.setAction(Action.ALLOW)
.putPolicies(
"allow-all",
Policy.newBuilder()
.addPrincipals(Principal.newBuilder().setAny(true))
.addPermissions(Permission.newBuilder().setAny(true))
.build())
.build())
.build()))
.build();
FilterConfig config = ClientXdsClient.parseHttpFilter(
httpFilter, filterRegistry, false /* isForClient */).getStruct();
assertThat(config).isInstanceOf(RbacConfig.class);
}
@Test
public void parseHttpFilter_rbacConfigUnsupportedForClient() {
filterRegistry.register(RbacFilter.INSTANCE);
HttpFilter httpFilter =
HttpFilter.newBuilder()
.setIsOptional(false)
.setName("envoy.auth")
.setTypedConfig(
Any.pack(
io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC.newBuilder()
.setRules(
RBAC.newBuilder()
.setAction(Action.ALLOW)
.putPolicies(
"allow-all",
Policy.newBuilder()
.addPrincipals(Principal.newBuilder().setAny(true))
.addPermissions(Permission.newBuilder().setAny(true))
.build())
.build())
.build()))
.build();
StructOrError<FilterConfig> config =
ClientXdsClient.parseHttpFilter(httpFilter, filterRegistry, true /* isForClient */);
assertThat(config.getErrorDetail()).isEqualTo(
"HttpFilter [envoy.auth](" + RbacFilter.TYPE_URL + ") is required but "
+ "unsupported for client");
}
@Test
public void parseOverrideRbacFilterConfig() {
filterRegistry.register(RbacFilter.INSTANCE);
RBACPerRoute rbacPerRoute =
RBACPerRoute.newBuilder()
.setRbac(
io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC.newBuilder()
.setRules(
RBAC.newBuilder()
.setAction(Action.ALLOW)
.putPolicies(
"allow-all",
Policy.newBuilder()
.addPrincipals(Principal.newBuilder().setAny(true))
.addPermissions(Permission.newBuilder().setAny(true))
.build())))
.build();
Map<String, Any> configOverrides = ImmutableMap.of("envoy.auth", Any.pack(rbacPerRoute));
Map<String, FilterConfig> parsedConfigs =
ClientXdsClient.parseOverrideFilterConfigs(configOverrides, filterRegistry).getStruct();
assertThat(parsedConfigs).hasSize(1);
assertThat(parsedConfigs).containsKey("envoy.auth");
assertThat(parsedConfigs.get("envoy.auth")).isInstanceOf(RbacConfig.class);
}
@Test
public void parseOverrideFilterConfigs_unsupportedButOptional() {
filterRegistry.register(FaultFilter.INSTANCE);
HTTPFault httpFault = HTTPFault.newBuilder()
.setDelay(FaultDelay.newBuilder().setFixedDelay(Durations.fromNanos(3000)))
.build();
Map<String, Any> configOverrides = ImmutableMap.of(
"envoy.fault",
Any.pack(httpFault),
"unsupported.filter",
Any.pack(io.envoyproxy.envoy.config.route.v3.FilterConfig.newBuilder()
.setIsOptional(true).setConfig(Any.pack(StringValue.of("string value")))
.build()));
Map<String, FilterConfig> parsedConfigs =
ClientXdsClient.parseOverrideFilterConfigs(configOverrides, filterRegistry).getStruct();
assertThat(parsedConfigs).hasSize(1);
assertThat(parsedConfigs).containsKey("envoy.fault");
}
@Test
public void parseOverrideFilterConfigs_unsupportedAndRequired() {
filterRegistry.register(FaultFilter.INSTANCE);
HTTPFault httpFault = HTTPFault.newBuilder()
.setDelay(FaultDelay.newBuilder().setFixedDelay(Durations.fromNanos(3000)))
.build();
Map<String, Any> configOverrides = ImmutableMap.of(
"envoy.fault",
Any.pack(httpFault),
"unsupported.filter",
Any.pack(io.envoyproxy.envoy.config.route.v3.FilterConfig.newBuilder()
.setIsOptional(false).setConfig(Any.pack(StringValue.of("string value")))
.build()));
assertThat(ClientXdsClient.parseOverrideFilterConfigs(configOverrides, filterRegistry)
.getErrorDetail()).isEqualTo(
"HttpFilter [unsupported.filter]"
+ "(type.googleapis.com/google.protobuf.StringValue) is required but unsupported");
configOverrides = ImmutableMap.of(
"envoy.fault",
Any.pack(httpFault),
"unsupported.filter",
Any.pack(StringValue.of("string value")));
assertThat(ClientXdsClient.parseOverrideFilterConfigs(configOverrides, filterRegistry)
.getErrorDetail()).isEqualTo(
"HttpFilter [unsupported.filter]"
+ "(type.googleapis.com/google.protobuf.StringValue) is required but unsupported");
}
@Test
public void parseHttpConnectionManager_xffNumTrustedHopsUnsupported()
throws ResourceInvalidException {
@SuppressWarnings("deprecation")
HttpConnectionManager hcm = HttpConnectionManager.newBuilder().setXffNumTrustedHops(2).build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("HttpConnectionManager with xff_num_trusted_hops unsupported");
ClientXdsClient.parseHttpConnectionManager(
hcm, new HashSet<String>(), filterRegistry, false /* does not matter */,
true /* does not matter */);
}
@Test
public void parseHttpConnectionManager_missingRdsAndInlinedRouteConfiguration()
throws ResourceInvalidException {
HttpConnectionManager hcm =
HttpConnectionManager.newBuilder()
.setCommonHttpProtocolOptions(
HttpProtocolOptions.newBuilder()
.setMaxStreamDuration(Durations.fromNanos(1000L)))
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("HttpConnectionManager neither has inlined route_config nor RDS");
ClientXdsClient.parseHttpConnectionManager(
hcm, new HashSet<String>(), filterRegistry, false /* does not matter */,
true /* does not matter */);
}
@Test
public void parseHttpConnectionManager_duplicateHttpFilters() throws ResourceInvalidException {
HttpConnectionManager hcm =
HttpConnectionManager.newBuilder()
.addHttpFilters(
HttpFilter.newBuilder().setName("envoy.filter.foo").setIsOptional(true))
.addHttpFilters(
HttpFilter.newBuilder().setName("envoy.filter.foo").setIsOptional(true))
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("HttpConnectionManager contains duplicate HttpFilter: envoy.filter.foo");
ClientXdsClient.parseHttpConnectionManager(
hcm, new HashSet<String>(), filterRegistry, true /* parseHttpFilter */,
true /* does not matter */);
}
@Test
public void parseCluster_ringHashLbPolicy_defaultLbConfig() throws ResourceInvalidException {
Cluster cluster = Cluster.newBuilder()
.setName("cluster-foo.googleapis.com")
.setType(DiscoveryType.EDS)
.setEdsClusterConfig(
EdsClusterConfig.newBuilder()
.setEdsConfig(
ConfigSource.newBuilder()
.setAds(AggregatedConfigSource.getDefaultInstance()))
.setServiceName("service-foo.googleapis.com"))
.setLbPolicy(LbPolicy.RING_HASH)
.build();
CdsUpdate update = ClientXdsClient.parseCluster(cluster, new HashSet<String>(), null);
assertThat(update.lbPolicy()).isEqualTo(CdsUpdate.LbPolicy.RING_HASH);
assertThat(update.minRingSize())
.isEqualTo(ClientXdsClient.DEFAULT_RING_HASH_LB_POLICY_MIN_RING_SIZE);
assertThat(update.maxRingSize())
.isEqualTo(ClientXdsClient.DEFAULT_RING_HASH_LB_POLICY_MAX_RING_SIZE);
}
@Test
public void parseCluster_transportSocketMatches_exception() throws ResourceInvalidException {
Cluster cluster = Cluster.newBuilder()
.setName("cluster-foo.googleapis.com")
.setType(DiscoveryType.EDS)
.setEdsClusterConfig(
EdsClusterConfig.newBuilder()
.setEdsConfig(
ConfigSource.newBuilder()
.setAds(AggregatedConfigSource.getDefaultInstance()))
.setServiceName("service-foo.googleapis.com"))
.setLbPolicy(LbPolicy.ROUND_ROBIN)
.addTransportSocketMatches(
Cluster.TransportSocketMatch.newBuilder().setName("match1").build())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage(
"Cluster cluster-foo.googleapis.com: transport-socket-matches not supported.");
ClientXdsClient.parseCluster(cluster, new HashSet<String>(), null);
}
@Test
public void parseCluster_ringHashLbPolicy_invalidRingSizeConfig_minGreaterThanMax()
throws ResourceInvalidException {
Cluster cluster = Cluster.newBuilder()
.setName("cluster-foo.googleapis.com")
.setType(DiscoveryType.EDS)
.setEdsClusterConfig(
EdsClusterConfig.newBuilder()
.setEdsConfig(
ConfigSource.newBuilder()
.setAds(AggregatedConfigSource.getDefaultInstance()))
.setServiceName("service-foo.googleapis.com"))
.setLbPolicy(LbPolicy.RING_HASH)
.setRingHashLbConfig(
RingHashLbConfig.newBuilder()
.setHashFunction(HashFunction.XX_HASH)
.setMinimumRingSize(UInt64Value.newBuilder().setValue(1000L))
.setMaximumRingSize(UInt64Value.newBuilder().setValue(100L)))
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("Cluster cluster-foo.googleapis.com: invalid ring_hash_lb_config");
ClientXdsClient.parseCluster(cluster, new HashSet<String>(), null);
}
@Test
public void parseCluster_ringHashLbPolicy_invalidRingSizeConfig_tooLargeRingSize()
throws ResourceInvalidException {
Cluster cluster = Cluster.newBuilder()
.setName("cluster-foo.googleapis.com")
.setType(DiscoveryType.EDS)
.setEdsClusterConfig(
EdsClusterConfig.newBuilder()
.setEdsConfig(
ConfigSource.newBuilder()
.setAds(AggregatedConfigSource.getDefaultInstance()))
.setServiceName("service-foo.googleapis.com"))
.setLbPolicy(LbPolicy.RING_HASH)
.setRingHashLbConfig(
RingHashLbConfig.newBuilder()
.setHashFunction(HashFunction.XX_HASH)
.setMinimumRingSize(UInt64Value.newBuilder().setValue(1000L))
.setMaximumRingSize(
UInt64Value.newBuilder()
.setValue(ClientXdsClient.MAX_RING_HASH_LB_POLICY_RING_SIZE + 1)))
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("Cluster cluster-foo.googleapis.com: invalid ring_hash_lb_config");
ClientXdsClient.parseCluster(cluster, new HashSet<String>(), null);
}
@Test
public void parseServerSideListener_invalidTrafficDirection() throws ResourceInvalidException {
Listener listener =
Listener.newBuilder()
.setName("listener1")
.setTrafficDirection(TrafficDirection.OUTBOUND)
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("Listener listener1 with invalid traffic direction: OUTBOUND");
ClientXdsClient.parseServerSideListener(
listener, new HashSet<String>(), null, filterRegistry, null, true /* does not matter */);
}
@Test
public void parseServerSideListener_listenerFiltersPresent() throws ResourceInvalidException {
Listener listener =
Listener.newBuilder()
.setName("listener1")
.setTrafficDirection(TrafficDirection.INBOUND)
.addListenerFilters(ListenerFilter.newBuilder().build())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("Listener listener1 cannot have listener_filters");
ClientXdsClient.parseServerSideListener(
listener, new HashSet<String>(), null, filterRegistry, null, true /* does not matter */);
}
@Test
public void parseServerSideListener_useOriginalDst() throws ResourceInvalidException {
Listener listener =
Listener.newBuilder()
.setName("listener1")
.setTrafficDirection(TrafficDirection.INBOUND)
.setUseOriginalDst(BoolValue.of(true))
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("Listener listener1 cannot have use_original_dst set to true");
ClientXdsClient.parseServerSideListener(
listener, new HashSet<String>(), null, filterRegistry, null, true /* does not matter */);
}
@Test
public void parseServerSideListener_nonUniqueFilterChainMatch() throws ResourceInvalidException {
Filter filter1 = buildHttpConnectionManagerFilter(
HttpFilter.newBuilder().setName("http-filter-1").setIsOptional(true).build());
FilterChainMatch filterChainMatch1 =
FilterChainMatch.newBuilder()
.addAllSourcePorts(Arrays.asList(80, 8080))
.addAllPrefixRanges(Arrays.asList(CidrRange.newBuilder().setAddressPrefix("192.168.0.0")
.setPrefixLen(UInt32Value.of(16)).build(),
CidrRange.newBuilder().setAddressPrefix("10.0.0.0").setPrefixLen(UInt32Value.of(8))
.build()))
.build();
FilterChain filterChain1 =
FilterChain.newBuilder()
.setName("filter-chain-1")
.setFilterChainMatch(filterChainMatch1)
.addFilters(filter1)
.build();
Filter filter2 = buildHttpConnectionManagerFilter(
HttpFilter.newBuilder().setName("http-filter-2").setIsOptional(true).build());
FilterChainMatch filterChainMatch2 =
FilterChainMatch.newBuilder()
.addAllSourcePorts(Arrays.asList(443, 8080))
.addAllPrefixRanges(Arrays.asList(
CidrRange.newBuilder().setAddressPrefix("2001:DB8::8:800:200C:417A")
.setPrefixLen(UInt32Value.of(60)).build(),
CidrRange.newBuilder().setAddressPrefix("192.168.0.0")
.setPrefixLen(UInt32Value.of(16)).build()))
.build();
FilterChain filterChain2 =
FilterChain.newBuilder()
.setName("filter-chain-2")
.setFilterChainMatch(filterChainMatch2)
.addFilters(filter2)
.build();
Listener listener =
Listener.newBuilder()
.setName("listener1")
.setTrafficDirection(TrafficDirection.INBOUND)
.addAllFilterChains(Arrays.asList(filterChain1, filterChain2))
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("Found duplicate matcher:");
ClientXdsClient.parseServerSideListener(
listener, new HashSet<String>(), null, filterRegistry, null, true /* does not matter */);
}
@Test
public void parseServerSideListener_nonUniqueFilterChainMatch_sameFilter()
throws ResourceInvalidException {
Filter filter1 = buildHttpConnectionManagerFilter(
HttpFilter.newBuilder().setName("http-filter-1").setIsOptional(true).build());
FilterChainMatch filterChainMatch1 =
FilterChainMatch.newBuilder()
.addAllSourcePorts(Arrays.asList(80, 8080))
.addAllPrefixRanges(Arrays.asList(
CidrRange.newBuilder().setAddressPrefix("10.0.0.0").setPrefixLen(UInt32Value.of(8))
.build()))
.build();
FilterChain filterChain1 =
FilterChain.newBuilder()
.setName("filter-chain-1")
.setFilterChainMatch(filterChainMatch1)
.addFilters(filter1)
.build();
Filter filter2 = buildHttpConnectionManagerFilter(
HttpFilter.newBuilder().setName("http-filter-2").setIsOptional(true).build());
FilterChainMatch filterChainMatch2 =
FilterChainMatch.newBuilder()
.addAllSourcePorts(Arrays.asList(443, 8080))
.addAllPrefixRanges(Arrays.asList(
CidrRange.newBuilder().setAddressPrefix("192.168.0.0")
.setPrefixLen(UInt32Value.of(16)).build(),
CidrRange.newBuilder().setAddressPrefix("192.168.0.0")
.setPrefixLen(UInt32Value.of(16)).build()))
.build();
FilterChain filterChain2 =
FilterChain.newBuilder()
.setName("filter-chain-2")
.setFilterChainMatch(filterChainMatch2)
.addFilters(filter2)
.build();
Listener listener =
Listener.newBuilder()
.setName("listener1")
.setTrafficDirection(TrafficDirection.INBOUND)
.addAllFilterChains(Arrays.asList(filterChain1, filterChain2))
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("Found duplicate matcher:");
ClientXdsClient.parseServerSideListener(
listener, new HashSet<String>(), null, filterRegistry, null, true /* does not matter */);
}
@Test
public void parseServerSideListener_uniqueFilterChainMatch() throws ResourceInvalidException {
Filter filter1 = buildHttpConnectionManagerFilter(
HttpFilter.newBuilder().setName("http-filter-1").setIsOptional(true).build());
FilterChainMatch filterChainMatch1 =
FilterChainMatch.newBuilder()
.addAllSourcePorts(Arrays.asList(80, 8080))
.addAllPrefixRanges(Arrays.asList(CidrRange.newBuilder().setAddressPrefix("192.168.0.0")
.setPrefixLen(UInt32Value.of(16)).build(),
CidrRange.newBuilder().setAddressPrefix("10.0.0.0").setPrefixLen(UInt32Value.of(8))
.build()))
.setSourceType(FilterChainMatch.ConnectionSourceType.EXTERNAL)
.build();
FilterChain filterChain1 =
FilterChain.newBuilder()
.setName("filter-chain-1")
.setFilterChainMatch(filterChainMatch1)
.addFilters(filter1)
.build();
Filter filter2 = buildHttpConnectionManagerFilter(
HttpFilter.newBuilder().setName("http-filter-2").setIsOptional(true).build());
FilterChainMatch filterChainMatch2 =
FilterChainMatch.newBuilder()
.addAllSourcePorts(Arrays.asList(443, 8080))
.addAllPrefixRanges(Arrays.asList(
CidrRange.newBuilder().setAddressPrefix("2001:DB8::8:800:200C:417A")
.setPrefixLen(UInt32Value.of(60)).build(),
CidrRange.newBuilder().setAddressPrefix("192.168.0.0")
.setPrefixLen(UInt32Value.of(16)).build()))
.setSourceType(FilterChainMatch.ConnectionSourceType.ANY)
.build();
FilterChain filterChain2 =
FilterChain.newBuilder()
.setName("filter-chain-2")
.setFilterChainMatch(filterChainMatch2)
.addFilters(filter2)
.build();
Listener listener =
Listener.newBuilder()
.setName("listener1")
.setTrafficDirection(TrafficDirection.INBOUND)
.addAllFilterChains(Arrays.asList(filterChain1, filterChain2))
.build();
ClientXdsClient.parseServerSideListener(
listener, new HashSet<String>(), null, filterRegistry, null, true /* does not matter */);
}
@Test
public void parseFilterChain_noHcm() throws ResourceInvalidException {
FilterChain filterChain =
FilterChain.newBuilder()
.setName("filter-chain-foo")
.setFilterChainMatch(FilterChainMatch.getDefaultInstance())
.setTransportSocket(TransportSocket.getDefaultInstance())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage(
"FilterChain filter-chain-foo missing required HttpConnectionManager filter");
ClientXdsClient.parseFilterChain(
filterChain, new HashSet<String>(), null, filterRegistry, null, null,
true /* does not matter */);
}
@Test
public void parseFilterChain_duplicateFilter() throws ResourceInvalidException {
Filter filter = buildHttpConnectionManagerFilter(
HttpFilter.newBuilder().setName("http-filter-foo").setIsOptional(true).build());
FilterChain filterChain =
FilterChain.newBuilder()
.setName("filter-chain-foo")
.setFilterChainMatch(FilterChainMatch.getDefaultInstance())
.setTransportSocket(TransportSocket.getDefaultInstance())
.addAllFilters(Arrays.asList(filter, filter))
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage(
"FilterChain filter-chain-foo with duplicated filter: envoy.http_connection_manager");
ClientXdsClient.parseFilterChain(
filterChain, new HashSet<String>(), null, filterRegistry, null, null,
true /* does not matter */);
}
@Test
public void parseFilterChain_filterMissingTypedConfig() throws ResourceInvalidException {
Filter filter = Filter.newBuilder().setName("envoy.http_connection_manager").build();
FilterChain filterChain =
FilterChain.newBuilder()
.setName("filter-chain-foo")
.setFilterChainMatch(FilterChainMatch.getDefaultInstance())
.setTransportSocket(TransportSocket.getDefaultInstance())
.addFilters(filter)
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage(
"FilterChain filter-chain-foo contains filter envoy.http_connection_manager "
+ "without typed_config");
ClientXdsClient.parseFilterChain(
filterChain, new HashSet<String>(), null, filterRegistry, null, null,
true /* does not matter */);
}
@Test
public void parseFilterChain_unsupportedFilter() throws ResourceInvalidException {
Filter filter =
Filter.newBuilder()
.setName("unsupported")
.setTypedConfig(Any.newBuilder().setTypeUrl("unsupported-type-url"))
.build();
FilterChain filterChain =
FilterChain.newBuilder()
.setName("filter-chain-foo")
.setFilterChainMatch(FilterChainMatch.getDefaultInstance())
.setTransportSocket(TransportSocket.getDefaultInstance())
.addFilters(filter)
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage(
"FilterChain filter-chain-foo contains filter unsupported with unsupported "
+ "typed_config type unsupported-type-url");
ClientXdsClient.parseFilterChain(
filterChain, new HashSet<String>(), null, filterRegistry, null, null,
true /* does not matter */);
}
@Test
public void parseFilterChain_noName_generatedUuid() throws ResourceInvalidException {
FilterChain filterChain1 =
FilterChain.newBuilder()
.setFilterChainMatch(FilterChainMatch.getDefaultInstance())
.addFilters(buildHttpConnectionManagerFilter(
HttpFilter.newBuilder()
.setName("http-filter-foo")
.setIsOptional(true)
.build()))
.build();
FilterChain filterChain2 =
FilterChain.newBuilder()
.setFilterChainMatch(FilterChainMatch.getDefaultInstance())
.addFilters(buildHttpConnectionManagerFilter(
HttpFilter.newBuilder()
.setName("http-filter-bar")
.setIsOptional(true)
.build()))
.build();
EnvoyServerProtoData.FilterChain parsedFilterChain1 = ClientXdsClient.parseFilterChain(
filterChain1, new HashSet<String>(), null, filterRegistry, null,
null, true /* does not matter */);
EnvoyServerProtoData.FilterChain parsedFilterChain2 = ClientXdsClient.parseFilterChain(
filterChain2, new HashSet<String>(), null, filterRegistry, null,
null, true /* does not matter */);
assertThat(parsedFilterChain1.getName()).isNotEqualTo(parsedFilterChain2.getName());
}
@Test
public void validateCommonTlsContext_tlsParams() throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setTlsParams(TlsParameters.getDefaultInstance())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("common-tls-context with tls_params is not supported");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false);
}
@Test
public void validateCommonTlsContext_customHandshaker() throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setCustomHandshaker(TypedExtensionConfig.getDefaultInstance())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("common-tls-context with custom_handshaker is not supported");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false);
}
@Test
public void validateCommonTlsContext_validationContext() throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setValidationContext(CertificateValidationContext.getDefaultInstance())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("ca_certificate_provider_instance is required in upstream-tls-context");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false);
}
@Test
public void validateCommonTlsContext_validationContextSdsSecretConfig()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setValidationContextSdsSecretConfig(SdsSecretConfig.getDefaultInstance())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage(
"common-tls-context with validation_context_sds_secret_config is not supported");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false);
}
@Test
@SuppressWarnings("deprecation")
public void validateCommonTlsContext_validationContextCertificateProvider()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setValidationContextCertificateProvider(
CommonTlsContext.CertificateProvider.getDefaultInstance())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage(
"common-tls-context with validation_context_certificate_provider is not supported");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false);
}
@Test
@SuppressWarnings("deprecation")
public void validateCommonTlsContext_validationContextCertificateProviderInstance()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setValidationContextCertificateProviderInstance(
CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage(
"common-tls-context with validation_context_certificate_provider_instance is not "
+ "supported");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false);
}
@Test
public void validateCommonTlsContext_tlsCertificateProviderInstance_isRequiredForServer()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage(
"tls_certificate_provider_instance is required in downstream-tls-context");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, true);
}
@Test
@SuppressWarnings("deprecation")
public void validateCommonTlsContext_tlsNewCertificateProviderInstance()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setTlsCertificateProviderInstance(
CertificateProviderPluginInstance.newBuilder().setInstanceName("name1").build())
.build();
ClientXdsClient
.validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), true);
}
@Test
@SuppressWarnings("deprecation")
public void validateCommonTlsContext_tlsCertificateProviderInstance()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setTlsCertificateCertificateProviderInstance(
CertificateProviderInstance.newBuilder().setInstanceName("name1").build())
.build();
ClientXdsClient
.validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), true);
}
@Test
@SuppressWarnings("deprecation")
public void validateCommonTlsContext_tlsCertificateProviderInstance_absentInBootstrapFile()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setTlsCertificateCertificateProviderInstance(
CertificateProviderInstance.newBuilder().setInstanceName("bad-name").build())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage(
"CertificateProvider instance name 'bad-name' not defined in the bootstrap file.");
ClientXdsClient
.validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), true);
}
@Test
@SuppressWarnings("deprecation")
public void validateCommonTlsContext_validationContextProviderInstance()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setCombinedValidationContext(
CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
.setValidationContextCertificateProviderInstance(
CertificateProviderInstance.newBuilder().setInstanceName("name1").build())
.build())
.build();
ClientXdsClient
.validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), false);
}
@Test
@SuppressWarnings("deprecation")
public void validateCommonTlsContext_validationContextProviderInstance_absentInBootstrapFile()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setCombinedValidationContext(
CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
.setValidationContextCertificateProviderInstance(
CertificateProviderInstance.newBuilder().setInstanceName("bad-name").build())
.build())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage(
"ca_certificate_provider_instance name 'bad-name' not defined in the bootstrap file.");
ClientXdsClient
.validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), false);
}
@Test
public void validateCommonTlsContext_tlsCertificatesCount() throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.addTlsCertificates(TlsCertificate.getDefaultInstance())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("tls_certificate_provider_instance is unset");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false);
}
@Test
public void validateCommonTlsContext_tlsCertificateSdsSecretConfigsCount()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.addTlsCertificateSdsSecretConfigs(SdsSecretConfig.getDefaultInstance())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage(
"tls_certificate_provider_instance is unset");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false);
}
@Test
@SuppressWarnings("deprecation")
public void validateCommonTlsContext_tlsCertificateCertificateProvider()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setTlsCertificateCertificateProvider(
CommonTlsContext.CertificateProvider.getDefaultInstance())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage(
"tls_certificate_provider_instance is unset");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false);
}
@Test
public void validateCommonTlsContext_combinedValidationContext_isRequiredForClient()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("ca_certificate_provider_instance is required in upstream-tls-context");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false);
}
@Test
public void validateCommonTlsContext_combinedValidationContextWithoutCertProviderInstance()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setCombinedValidationContext(
CommonTlsContext.CombinedCertificateValidationContext.getDefaultInstance())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage(
"ca_certificate_provider_instance is required in upstream-tls-context");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false);
}
@Test
@SuppressWarnings("deprecation")
public void validateCommonTlsContext_combinedValContextWithDefaultValContextForServer()
throws ResourceInvalidException, InvalidProtocolBufferException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setCombinedValidationContext(
CombinedCertificateValidationContext.newBuilder()
.setValidationContextCertificateProviderInstance(
CertificateProviderInstance.getDefaultInstance())
.setDefaultValidationContext(CertificateValidationContext.newBuilder()
.addMatchSubjectAltNames(StringMatcher.newBuilder().setExact("foo.com").build())
.build()))
.setTlsCertificateCertificateProviderInstance(
CertificateProviderInstance.getDefaultInstance())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("match_subject_alt_names only allowed in upstream_tls_context");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), true);
}
@Test
@SuppressWarnings("deprecation")
public void validateCommonTlsContext_combinedValContextWithDefaultValContextVerifyCertSpki()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setCombinedValidationContext(
CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
.setValidationContextCertificateProviderInstance(
CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
.setDefaultValidationContext(
CertificateValidationContext.newBuilder().addVerifyCertificateSpki("foo")))
.setTlsCertificateCertificateProviderInstance(
CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("verify_certificate_spki in default_validation_context is not "
+ "supported");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false);
}
@Test
@SuppressWarnings("deprecation")
public void validateCommonTlsContext_combinedValContextWithDefaultValContextVerifyCertHash()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setCombinedValidationContext(
CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
.setValidationContextCertificateProviderInstance(
CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
.setDefaultValidationContext(
CertificateValidationContext.newBuilder().addVerifyCertificateHash("foo")))
.setTlsCertificateCertificateProviderInstance(
CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("verify_certificate_hash in default_validation_context is not "
+ "supported");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false);
}
@Test
@SuppressWarnings("deprecation")
public void validateCommonTlsContext_combinedValContextDfltValContextRequireSignedCertTimestamp()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setCombinedValidationContext(
CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
.setValidationContextCertificateProviderInstance(
CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
.setDefaultValidationContext(CertificateValidationContext.newBuilder()
.setRequireSignedCertificateTimestamp(BoolValue.of(true))))
.setTlsCertificateCertificateProviderInstance(
CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage(
"require_signed_certificate_timestamp in default_validation_context is not "
+ "supported");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false);
}
@Test
@SuppressWarnings("deprecation")
public void validateCommonTlsContext_combinedValidationContextWithDefaultValidationContextCrl()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setCombinedValidationContext(
CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
.setValidationContextCertificateProviderInstance(
CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
.setDefaultValidationContext(CertificateValidationContext.newBuilder()
.setCrl(DataSource.getDefaultInstance())))
.setTlsCertificateCertificateProviderInstance(
CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("crl in default_validation_context is not supported");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false);
}
@Test
@SuppressWarnings("deprecation")
public void validateCommonTlsContext_combinedValContextWithDfltValContextCustomValidatorConfig()
throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setCombinedValidationContext(
CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
.setValidationContextCertificateProviderInstance(
CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
.setDefaultValidationContext(CertificateValidationContext.newBuilder()
.setCustomValidatorConfig(TypedExtensionConfig.getDefaultInstance())))
.setTlsCertificateCertificateProviderInstance(
CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("custom_validator_config in default_validation_context is not "
+ "supported");
ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false);
}
@Test
public void validateDownstreamTlsContext_noCommonTlsContext() throws ResourceInvalidException {
DownstreamTlsContext downstreamTlsContext = DownstreamTlsContext.getDefaultInstance();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("common-tls-context is required in downstream-tls-context");
ClientXdsClient.validateDownstreamTlsContext(downstreamTlsContext, null);
}
@Test
@SuppressWarnings("deprecation")
public void validateDownstreamTlsContext_hasRequireSni() throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setCombinedValidationContext(
CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
.setValidationContextCertificateProviderInstance(
CommonTlsContext.CertificateProviderInstance.getDefaultInstance()))
.setTlsCertificateCertificateProviderInstance(
CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
.build();
DownstreamTlsContext downstreamTlsContext = DownstreamTlsContext.newBuilder()
.setCommonTlsContext(commonTlsContext)
.setRequireSni(BoolValue.of(true))
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("downstream-tls-context with require-sni is not supported");
ClientXdsClient.validateDownstreamTlsContext(downstreamTlsContext, ImmutableSet.of(""));
}
@Test
@SuppressWarnings("deprecation")
public void validateDownstreamTlsContext_hasOcspStaplePolicy() throws ResourceInvalidException {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setCombinedValidationContext(
CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
.setValidationContextCertificateProviderInstance(
CommonTlsContext.CertificateProviderInstance.getDefaultInstance()))
.setTlsCertificateCertificateProviderInstance(
CommonTlsContext.CertificateProviderInstance.getDefaultInstance())
.build();
DownstreamTlsContext downstreamTlsContext = DownstreamTlsContext.newBuilder()
.setCommonTlsContext(commonTlsContext)
.setOcspStaplePolicy(DownstreamTlsContext.OcspStaplePolicy.STRICT_STAPLING)
.build();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage(
"downstream-tls-context with ocsp_staple_policy value STRICT_STAPLING is not supported");
ClientXdsClient.validateDownstreamTlsContext(downstreamTlsContext, ImmutableSet.of(""));
}
@Test
public void validateUpstreamTlsContext_noCommonTlsContext() throws ResourceInvalidException {
UpstreamTlsContext upstreamTlsContext = UpstreamTlsContext.getDefaultInstance();
thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("common-tls-context is required in upstream-tls-context");
ClientXdsClient.validateUpstreamTlsContext(upstreamTlsContext, null);
}
private static Filter buildHttpConnectionManagerFilter(HttpFilter... httpFilters) {
return Filter.newBuilder()
.setName("envoy.http_connection_manager")
.setTypedConfig(
Any.pack(
HttpConnectionManager.newBuilder()
.setRds(
Rds.newBuilder()
.setRouteConfigName("route-config.googleapis.com")
.setConfigSource(
ConfigSource.newBuilder()
.setAds(AggregatedConfigSource.getDefaultInstance())))
.addAllHttpFilters(Arrays.asList(httpFilters))
.build(),
"type.googleapis.com"))
.build();
}
}