From de05d7e27bd38edcd35a4e7c719dde05e69eacc4 Mon Sep 17 00:00:00 2001 From: Anton Kanugalawattage Date: Tue, 30 Jun 2026 05:06:33 -0400 Subject: [PATCH] feature: ecds Signed-off-by: Anton Kanugalawattage --- .../controlplane/cache/Resources.java | 18 ++++- .../controlplane/cache/TestResources.java | 13 +++ .../controlplane/cache/v3/Snapshot.java | 79 ++++++++++++++++++- .../cache/v3/SimpleCacheTest.java | 8 +- .../controlplane/cache/v3/SnapshotTest.java | 57 +++++++++++++ .../server/V3DiscoveryServer.java | 22 ++++++ .../server/V3DiscoveryServerTest.java | 54 ++++++++++++- 7 files changed, 241 insertions(+), 10 deletions(-) diff --git a/cache/src/main/java/io/envoyproxy/controlplane/cache/Resources.java b/cache/src/main/java/io/envoyproxy/controlplane/cache/Resources.java index b405005aa..2f3c2bd91 100644 --- a/cache/src/main/java/io/envoyproxy/controlplane/cache/Resources.java +++ b/cache/src/main/java/io/envoyproxy/controlplane/cache/Resources.java @@ -4,6 +4,7 @@ import static io.envoyproxy.controlplane.cache.Resources.ApiVersion.V3; import static io.envoyproxy.controlplane.cache.Resources.ResourceType.CLUSTER; import static io.envoyproxy.controlplane.cache.Resources.ResourceType.ENDPOINT; +import static io.envoyproxy.controlplane.cache.Resources.ResourceType.EXTENSION_CONFIG; import static io.envoyproxy.controlplane.cache.Resources.ResourceType.LISTENER; import static io.envoyproxy.controlplane.cache.Resources.ResourceType.ROUTE; import static io.envoyproxy.controlplane.cache.Resources.ResourceType.SECRET; @@ -16,6 +17,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; import io.envoyproxy.envoy.config.cluster.v3.Cluster; +import io.envoyproxy.envoy.config.core.v3.TypedExtensionConfig; import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; import io.envoyproxy.envoy.config.listener.v3.Filter; import io.envoyproxy.envoy.config.listener.v3.FilterChain; @@ -42,7 +44,8 @@ public enum ResourceType { ENDPOINT, LISTENER, ROUTE, - SECRET + SECRET, + EXTENSION_CONFIG } public enum ApiVersion { @@ -66,6 +69,8 @@ public static class V3 { "type.googleapis.com/envoy.config.route.v3" + ".RouteConfiguration"; public static final String SECRET_TYPE_URL = "type.googleapis.com/envoy.extensions" + ".transport_sockets.tls.v3.Secret"; + public static final String EXTENSION_CONFIG_TYPE_URL = + "type.googleapis.com/envoy.config.core.v3" + ".TypedExtensionConfig"; public static final List TYPE_URLS = ImmutableList.of( @@ -73,11 +78,12 @@ public static class V3 { ENDPOINT_TYPE_URL, LISTENER_TYPE_URL, ROUTE_TYPE_URL, - SECRET_TYPE_URL); + SECRET_TYPE_URL, + EXTENSION_CONFIG_TYPE_URL); } public static final List RESOURCE_TYPES_IN_ORDER = - ImmutableList.of(CLUSTER, ENDPOINT, LISTENER, ROUTE, SECRET); + ImmutableList.of(CLUSTER, ENDPOINT, LISTENER, ROUTE, SECRET, EXTENSION_CONFIG); public static final Map TYPE_URLS_TO_RESOURCE_TYPE = new ImmutableMap.Builder() @@ -86,6 +92,7 @@ public static class V3 { .put(Resources.V3.LISTENER_TYPE_URL, LISTENER) .put(Resources.V3.ROUTE_TYPE_URL, ROUTE) .put(Resources.V3.SECRET_TYPE_URL, SECRET) + .put(Resources.V3.EXTENSION_CONFIG_TYPE_URL, EXTENSION_CONFIG) .build(); public static final Map> RESOURCE_TYPE_BY_URL = @@ -95,6 +102,7 @@ public static class V3 { .put(Resources.V3.LISTENER_TYPE_URL, Listener.class) .put(Resources.V3.ROUTE_TYPE_URL, RouteConfiguration.class) .put(Resources.V3.SECRET_TYPE_URL, Secret.class) + .put(Resources.V3.EXTENSION_CONFIG_TYPE_URL, TypedExtensionConfig.class) .build(); /** @@ -123,6 +131,10 @@ public static String getResourceName(Message resource) { return ((Secret) resource).getName(); } + if (resource instanceof TypedExtensionConfig) { + return ((TypedExtensionConfig) resource).getName(); + } + return ""; } diff --git a/cache/src/main/java/io/envoyproxy/controlplane/cache/TestResources.java b/cache/src/main/java/io/envoyproxy/controlplane/cache/TestResources.java index 1ae8865cf..4b1832d46 100644 --- a/cache/src/main/java/io/envoyproxy/controlplane/cache/TestResources.java +++ b/cache/src/main/java/io/envoyproxy/controlplane/cache/TestResources.java @@ -14,6 +14,7 @@ import io.envoyproxy.envoy.config.core.v3.GrpcService; import io.envoyproxy.envoy.config.core.v3.SocketAddress; import io.envoyproxy.envoy.config.core.v3.SocketAddress.Protocol; +import io.envoyproxy.envoy.config.core.v3.TypedExtensionConfig; import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; import io.envoyproxy.envoy.config.endpoint.v3.Endpoint; import io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint; @@ -245,5 +246,17 @@ public static Secret createSecret(String secretName) { .build(); } + /** + * Returns a new test v3 extension config (ECDS), wrapping a no-op HTTP router filter. + * + * @param name name of the new extension config + */ + public static TypedExtensionConfig createExtensionConfig(String name) { + return TypedExtensionConfig.newBuilder() + .setName(name) + .setTypedConfig(Any.pack(Router.newBuilder().build())) + .build(); + } + private TestResources() {} } diff --git a/cache/src/main/java/io/envoyproxy/controlplane/cache/v3/Snapshot.java b/cache/src/main/java/io/envoyproxy/controlplane/cache/v3/Snapshot.java index 97f0e0abf..a0286e50c 100644 --- a/cache/src/main/java/io/envoyproxy/controlplane/cache/v3/Snapshot.java +++ b/cache/src/main/java/io/envoyproxy/controlplane/cache/v3/Snapshot.java @@ -12,6 +12,7 @@ import io.envoyproxy.controlplane.cache.SnapshotResources; import io.envoyproxy.controlplane.cache.VersionedResource; import io.envoyproxy.envoy.config.cluster.v3.Cluster; +import io.envoyproxy.envoy.config.core.v3.TypedExtensionConfig; import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; import io.envoyproxy.envoy.config.listener.v3.Listener; import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; @@ -46,6 +47,30 @@ public static Snapshot create( Iterable secrets, String version) { + return create(clusters, endpoints, listeners, routes, secrets, Collections.emptySet(), version); + } + + /** + * Returns a new {@link io.envoyproxy.controlplane.cache.v3.Snapshot} instance that is versioned uniformly across all + * resources, including ECDS extension configs. + * + * @param clusters the cluster resources in this snapshot + * @param endpoints the endpoint resources in this snapshot + * @param listeners the listener resources in this snapshot + * @param routes the route resources in this snapshot + * @param secrets the secret resources in this snapshot + * @param extensionConfigs the ECDS extension config resources in this snapshot + * @param version the version associated with all resources in this snapshot + */ + public static Snapshot create( + Iterable clusters, + Iterable endpoints, + Iterable listeners, + Iterable routes, + Iterable secrets, + Iterable extensionConfigs, + String version) { + return new AutoValue_Snapshot( SnapshotResources .create(generateSnapshotResourceIterable(clusters), version), @@ -56,7 +81,9 @@ public static Snapshot create( SnapshotResources .create(generateSnapshotResourceIterable(routes), version), SnapshotResources - .create(generateSnapshotResourceIterable(secrets), version)); + .create(generateSnapshotResourceIterable(secrets), version), + SnapshotResources + .create(generateSnapshotResourceIterable(extensionConfigs), version)); } /** @@ -84,6 +111,41 @@ public static Snapshot create( Iterable secrets, String secretsVersion) { + return create(clusters, clustersVersion, endpoints, endpointsVersion, listeners, listenersVersion, + routes, routesVersion, secrets, secretsVersion, Collections.emptySet(), ""); + } + + /** + * Returns a new {@link io.envoyproxy.controlplane.cache.v3.Snapshot} instance that has separate versions for each + * resource type, including ECDS extension configs. + * + * @param clusters the cluster resources in this snapshot + * @param clustersVersion the version of the cluster resources + * @param endpoints the endpoint resources in this snapshot + * @param endpointsVersion the version of the endpoint resources + * @param listeners the listener resources in this snapshot + * @param listenersVersion the version of the listener resources + * @param routes the route resources in this snapshot + * @param routesVersion the version of the route resources + * @param secrets the secret resources in this snapshot + * @param secretsVersion the version of the secret resources + * @param extensionConfigs the ECDS extension config resources in this snapshot + * @param extensionConfigsVersion the version of the ECDS extension config resources + */ + public static Snapshot create( + Iterable clusters, + String clustersVersion, + Iterable endpoints, + String endpointsVersion, + Iterable listeners, + String listenersVersion, + Iterable routes, + String routesVersion, + Iterable secrets, + String secretsVersion, + Iterable extensionConfigs, + String extensionConfigsVersion) { + // TODO(snowp): add a builder alternative return new AutoValue_Snapshot( SnapshotResources.create(generateSnapshotResourceIterable(clusters), @@ -95,7 +157,9 @@ public static Snapshot create( SnapshotResources .create(generateSnapshotResourceIterable(routes), routesVersion), SnapshotResources.create(generateSnapshotResourceIterable(secrets), - secretsVersion)); + secretsVersion), + SnapshotResources.create(generateSnapshotResourceIterable(extensionConfigs), + extensionConfigsVersion)); } /** @@ -133,6 +197,11 @@ public static Snapshot createEmpty(String version) { */ public abstract SnapshotResources secrets(); + /** + * Returns all extension config items in the ECDS payload. + */ + public abstract SnapshotResources extensionConfigs(); + /** * Asserts that all dependent resources are included in the snapshot. All EDS resources are listed by name in CDS * resources, and all RDS resources are listed by name in LDS resources. @@ -191,6 +260,8 @@ public Map> resources(String typeUrl) { return (Map) routes().resources(); case SECRET: return (Map) secrets().resources(); + case EXTENSION_CONFIG: + return (Map) extensionConfigs().resources(); default: return ImmutableMap.of(); } @@ -213,6 +284,8 @@ public Map> versionedResources(ResourceType resourc return (Map) routes().versionedResources(); case SECRET: return (Map) secrets().versionedResources(); + case EXTENSION_CONFIG: + return (Map) extensionConfigs().versionedResources(); default: return ImmutableMap.of(); } @@ -268,6 +341,8 @@ public String version(ResourceType resourceType, List resourceNames) { return routes().version(resourceNames); case SECRET: return secrets().version(resourceNames); + case EXTENSION_CONFIG: + return extensionConfigs().version(resourceNames); default: return ""; } diff --git a/cache/src/test/java/io/envoyproxy/controlplane/cache/v3/SimpleCacheTest.java b/cache/src/test/java/io/envoyproxy/controlplane/cache/v3/SimpleCacheTest.java index b8654f6ba..733916cb2 100644 --- a/cache/src/test/java/io/envoyproxy/controlplane/cache/v3/SimpleCacheTest.java +++ b/cache/src/test/java/io/envoyproxy/controlplane/cache/v3/SimpleCacheTest.java @@ -16,6 +16,7 @@ import io.envoyproxy.controlplane.cache.XdsRequest; import io.envoyproxy.envoy.config.cluster.v3.Cluster; import io.envoyproxy.envoy.config.core.v3.Node; +import io.envoyproxy.envoy.config.core.v3.TypedExtensionConfig; import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; import io.envoyproxy.envoy.config.listener.v3.Listener; import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; @@ -40,6 +41,7 @@ public class SimpleCacheTest { private static final String LISTENER_NAME = "listener0"; private static final String ROUTE_NAME = "route0"; private static final String SECRET_NAME = "secret0"; + private static final String EXTENSION_CONFIG_NAME = "extensionConfig0"; private static final String VERSION1 = UUID.randomUUID().toString(); private static final String VERSION2 = UUID.randomUUID().toString(); @@ -50,6 +52,7 @@ public class SimpleCacheTest { ImmutableList.of(Listener.newBuilder().setName(LISTENER_NAME).build()), ImmutableList.of(RouteConfiguration.newBuilder().setName(ROUTE_NAME).build()), ImmutableList.of(Secret.newBuilder().setName(SECRET_NAME).build()), + ImmutableList.of(TypedExtensionConfig.newBuilder().setName(EXTENSION_CONFIG_NAME).build()), VERSION1); private static final Snapshot SNAPSHOT2 = Snapshot.create( @@ -58,6 +61,7 @@ public class SimpleCacheTest { ImmutableList.of(Listener.newBuilder().setName(LISTENER_NAME).build()), ImmutableList.of(RouteConfiguration.newBuilder().setName(ROUTE_NAME).build()), ImmutableList.of(Secret.newBuilder().setName(SECRET_NAME).build()), + ImmutableList.of(TypedExtensionConfig.newBuilder().setName(EXTENSION_CONFIG_NAME).build()), VERSION2); private static final Snapshot MULTIPLE_RESOURCES_SNAPSHOT2 = Snapshot.create( @@ -68,6 +72,7 @@ public class SimpleCacheTest { ImmutableList.of(Listener.newBuilder().setName(LISTENER_NAME).build()), ImmutableList.of(RouteConfiguration.newBuilder().setName(ROUTE_NAME).build()), ImmutableList.of(Secret.newBuilder().setName(SECRET_NAME).build()), + ImmutableList.of(TypedExtensionConfig.newBuilder().setName(EXTENSION_CONFIG_NAME).build()), VERSION2); private static void assertThatWatchIsOpenWithNoResponses(WatchAndTracker watchAndTracker) { @@ -297,7 +302,8 @@ public void successfullyWatchAllResourceTypesWithSetBeforeWatchWithRequestVersio Resources.V3.CLUSTER_TYPE_URL, Resources.V3.ENDPOINT_TYPE_URL, Resources.V3.ENDPOINT_TYPE_URL, Resources.V3.LISTENER_TYPE_URL, Resources.V3.LISTENER_TYPE_URL, ROUTE_TYPE_URL, ROUTE_TYPE_URL, - Resources.V3.SECRET_TYPE_URL, Resources.V3.SECRET_TYPE_URL); + Resources.V3.SECRET_TYPE_URL, Resources.V3.SECRET_TYPE_URL, + Resources.V3.EXTENSION_CONFIG_TYPE_URL, Resources.V3.EXTENSION_CONFIG_TYPE_URL); } @Test diff --git a/cache/src/test/java/io/envoyproxy/controlplane/cache/v3/SnapshotTest.java b/cache/src/test/java/io/envoyproxy/controlplane/cache/v3/SnapshotTest.java index d40b02bd6..3da2f42a7 100644 --- a/cache/src/test/java/io/envoyproxy/controlplane/cache/v3/SnapshotTest.java +++ b/cache/src/test/java/io/envoyproxy/controlplane/cache/v3/SnapshotTest.java @@ -2,6 +2,7 @@ import static io.envoyproxy.controlplane.cache.Resources.V3.CLUSTER_TYPE_URL; import static io.envoyproxy.controlplane.cache.Resources.V3.ENDPOINT_TYPE_URL; +import static io.envoyproxy.controlplane.cache.Resources.V3.EXTENSION_CONFIG_TYPE_URL; import static io.envoyproxy.controlplane.cache.Resources.V3.LISTENER_TYPE_URL; import static io.envoyproxy.controlplane.cache.Resources.V3.ROUTE_TYPE_URL; import static io.envoyproxy.envoy.config.core.v3.ApiVersion.V3; @@ -14,6 +15,7 @@ import io.envoyproxy.controlplane.cache.TestResources; import io.envoyproxy.controlplane.cache.VersionedResource; import io.envoyproxy.envoy.config.cluster.v3.Cluster; +import io.envoyproxy.envoy.config.core.v3.TypedExtensionConfig; import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; import io.envoyproxy.envoy.config.listener.v3.Listener; import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; @@ -29,6 +31,7 @@ public class SnapshotTest { private static final String LISTENER_NAME = "listener0"; private static final String ROUTE_NAME = "route0"; private static final String SECRET_NAME = "secret0"; + private static final String EXTENSION_CONFIG_NAME = "extensionConfig0"; private static final int ENDPOINT_PORT = ThreadLocalRandom.current().nextInt(10000, 20000); private static final int LISTENER_PORT = ThreadLocalRandom.current().nextInt(20000, 30000); @@ -41,6 +44,8 @@ public class SnapshotTest { private static final RouteConfiguration ROUTE = TestResources.createRoute(ROUTE_NAME, CLUSTER_NAME); private static final Secret SECRET = TestResources.createSecret(SECRET_NAME); + private static final TypedExtensionConfig + EXTENSION_CONFIG = TestResources.createExtensionConfig(EXTENSION_CONFIG_NAME); @Test public void createSingleVersionSetsResourcesCorrectly() { @@ -114,6 +119,58 @@ public void createSeparateVersionsSetsResourcesCorrectly() { assertThat(snapshot.routes().version()).isEqualTo(routesVersion); } + @Test + public void createSetsExtensionConfigsCorrectly() { + final String version = UUID.randomUUID().toString(); + + Snapshot snapshot = Snapshot.create( + ImmutableList.of(CLUSTER), + ImmutableList.of(ENDPOINT), + ImmutableList.of(LISTENER), + ImmutableList.of(ROUTE), + ImmutableList.of(SECRET), + ImmutableList.of(EXTENSION_CONFIG), + version); + + assertThat(snapshot.extensionConfigs().resources()) + .containsEntry(EXTENSION_CONFIG_NAME, EXTENSION_CONFIG) + .hasSize(1); + assertThat(snapshot.extensionConfigs().version()).isEqualTo(version); + + assertThat(snapshot.resources(EXTENSION_CONFIG_TYPE_URL)) + .containsEntry(EXTENSION_CONFIG_NAME, VersionedResource.create(EXTENSION_CONFIG)) + .hasSize(1); + assertThat(snapshot.version(EXTENSION_CONFIG_TYPE_URL)).isEqualTo(version); + + // The legacy create overload leaves the ECDS payload empty. + Snapshot withoutEcds = Snapshot.create( + ImmutableList.of(CLUSTER), + ImmutableList.of(ENDPOINT), + ImmutableList.of(LISTENER), + ImmutableList.of(ROUTE), + ImmutableList.of(SECRET), + version); + assertThat(withoutEcds.extensionConfigs().resources()).isEmpty(); + } + + @Test + public void createSeparateVersionsSetsExtensionConfigsCorrectly() { + final String extensionConfigsVersion = UUID.randomUUID().toString(); + + Snapshot snapshot = Snapshot.create( + ImmutableList.of(CLUSTER), UUID.randomUUID().toString(), + ImmutableList.of(ENDPOINT), UUID.randomUUID().toString(), + ImmutableList.of(LISTENER), UUID.randomUUID().toString(), + ImmutableList.of(ROUTE), UUID.randomUUID().toString(), + ImmutableList.of(SECRET), UUID.randomUUID().toString(), + ImmutableList.of(EXTENSION_CONFIG), extensionConfigsVersion); + + assertThat(snapshot.extensionConfigs().resources()) + .containsEntry(EXTENSION_CONFIG_NAME, EXTENSION_CONFIG) + .hasSize(1); + assertThat(snapshot.extensionConfigs().version()).isEqualTo(extensionConfigsVersion); + } + @Test @SuppressWarnings("unchecked") public void resourcesReturnsExpectedResources() { diff --git a/server/src/main/java/io/envoyproxy/controlplane/server/V3DiscoveryServer.java b/server/src/main/java/io/envoyproxy/controlplane/server/V3DiscoveryServer.java index 3c3621c62..34004b35b 100644 --- a/server/src/main/java/io/envoyproxy/controlplane/server/V3DiscoveryServer.java +++ b/server/src/main/java/io/envoyproxy/controlplane/server/V3DiscoveryServer.java @@ -2,6 +2,7 @@ import static io.envoyproxy.envoy.service.discovery.v3.AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceImplBase; import static io.envoyproxy.envoy.service.endpoint.v3.EndpointDiscoveryServiceGrpc.EndpointDiscoveryServiceImplBase; +import static io.envoyproxy.envoy.service.extension.v3.ExtensionConfigDiscoveryServiceGrpc.ExtensionConfigDiscoveryServiceImplBase; import static io.envoyproxy.envoy.service.listener.v3.ListenerDiscoveryServiceGrpc.ListenerDiscoveryServiceImplBase; import static io.envoyproxy.envoy.service.route.v3.RouteDiscoveryServiceGrpc.RouteDiscoveryServiceImplBase; import static io.envoyproxy.envoy.service.secret.v3.SecretDiscoveryServiceGrpc.SecretDiscoveryServiceImplBase; @@ -185,6 +186,27 @@ public StreamObserver deltaSecrets( }; } + /** + * Returns an ECDS implementation that uses this server's {@link ConfigWatcher}. + */ + public ExtensionConfigDiscoveryServiceImplBase getExtensionConfigDiscoveryServiceImpl() { + return new ExtensionConfigDiscoveryServiceImplBase() { + @Override + public StreamObserver streamExtensionConfigs( + StreamObserver responseObserver) { + + return createRequestHandler(responseObserver, false, Resources.V3.EXTENSION_CONFIG_TYPE_URL); + } + + @Override + public StreamObserver deltaExtensionConfigs( + StreamObserver responseObserver) { + + return createDeltaRequestHandler(responseObserver, false, Resources.V3.EXTENSION_CONFIG_TYPE_URL); + } + }; + } + @Override protected XdsRequest wrapXdsRequest(DiscoveryRequest request) { return XdsRequest.create(request); diff --git a/server/src/test/java/io/envoyproxy/controlplane/server/V3DiscoveryServerTest.java b/server/src/test/java/io/envoyproxy/controlplane/server/V3DiscoveryServerTest.java index 6e41b4282..6f1f064b3 100644 --- a/server/src/test/java/io/envoyproxy/controlplane/server/V3DiscoveryServerTest.java +++ b/server/src/test/java/io/envoyproxy/controlplane/server/V3DiscoveryServerTest.java @@ -24,6 +24,7 @@ import io.envoyproxy.controlplane.server.exception.RequestException; import io.envoyproxy.envoy.config.cluster.v3.Cluster; import io.envoyproxy.envoy.config.core.v3.Node; +import io.envoyproxy.envoy.config.core.v3.TypedExtensionConfig; import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; import io.envoyproxy.envoy.config.listener.v3.Listener; import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; @@ -37,6 +38,8 @@ import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse; import io.envoyproxy.envoy.service.endpoint.v3.EndpointDiscoveryServiceGrpc; import io.envoyproxy.envoy.service.endpoint.v3.EndpointDiscoveryServiceGrpc.EndpointDiscoveryServiceStub; +import io.envoyproxy.envoy.service.extension.v3.ExtensionConfigDiscoveryServiceGrpc; +import io.envoyproxy.envoy.service.extension.v3.ExtensionConfigDiscoveryServiceGrpc.ExtensionConfigDiscoveryServiceStub; import io.envoyproxy.envoy.service.listener.v3.ListenerDiscoveryServiceGrpc; import io.envoyproxy.envoy.service.listener.v3.ListenerDiscoveryServiceGrpc.ListenerDiscoveryServiceStub; import io.envoyproxy.envoy.service.route.v3.RouteDiscoveryServiceGrpc; @@ -75,6 +78,7 @@ public class V3DiscoveryServerTest { private static final String LISTENER_NAME = "listener0"; private static final String ROUTE_NAME = "route0"; private static final String SECRET_NAME = "secret0"; + private static final String EXTENSION_CONFIG_NAME = "extensionConfig0"; private static final int ENDPOINT_PORT = Ports.getAvailablePort(); private static final int LISTENER_PORT = Ports.getAvailablePort(); @@ -95,6 +99,8 @@ public class V3DiscoveryServerTest { private static final RouteConfiguration ROUTE = TestResources.createRoute(ROUTE_NAME, CLUSTER_NAME); private static final Secret SECRET = TestResources.createSecret(SECRET_NAME); + private static final TypedExtensionConfig + EXTENSION_CONFIG = TestResources.createExtensionConfig(EXTENSION_CONFIG_NAME); @Rule public final GrpcServerRule grpcServer = new GrpcServerRule().directExecutor(); @@ -140,6 +146,12 @@ public void testAggregatedHandler() throws InterruptedException { .addResourceNames(SECRET_NAME) .build()); + requestObserver.onNext(DiscoveryRequest.newBuilder() + .setNode(NODE) + .setTypeUrl(Resources.V3.EXTENSION_CONFIG_TYPE_URL) + .addResourceNames(EXTENSION_CONFIG_NAME) + .build()); + requestObserver.onCompleted(); if (!responseObserver.completedLatch.await(1, TimeUnit.SECONDS) || responseObserver.error.get()) { @@ -171,12 +183,15 @@ public void testSeparateHandlers() throws InterruptedException { grpcServer.getServiceRegistry().addService(server.getListenerDiscoveryServiceImpl()); grpcServer.getServiceRegistry().addService(server.getRouteDiscoveryServiceImpl()); grpcServer.getServiceRegistry().addService(server.getSecretDiscoveryServiceImpl()); + grpcServer.getServiceRegistry().addService(server.getExtensionConfigDiscoveryServiceImpl()); ClusterDiscoveryServiceStub clusterStub = ClusterDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); EndpointDiscoveryServiceStub endpointStub = EndpointDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); ListenerDiscoveryServiceStub listenerStub = ListenerDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); RouteDiscoveryServiceStub routeStub = RouteDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); SecretDiscoveryServiceStub secretStub = SecretDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); + ExtensionConfigDiscoveryServiceStub extensionConfigStub = + ExtensionConfigDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); for (String typeUrl : Resources.V3.TYPE_URLS) { MockDiscoveryResponseObserver responseObserver = new MockDiscoveryResponseObserver(); @@ -205,6 +220,10 @@ public void testSeparateHandlers() throws InterruptedException { requestObserver = secretStub.streamSecrets(responseObserver); discoveryRequestBuilder.addResourceNames(SECRET_NAME); break; + case Resources.V3.EXTENSION_CONFIG_TYPE_URL: + requestObserver = extensionConfigStub.streamExtensionConfigs(responseObserver); + discoveryRequestBuilder.addResourceNames(EXTENSION_CONFIG_NAME); + break; default: fail("Unsupported resource type: " + typeUrl); } @@ -372,12 +391,15 @@ public void testSeparateHandlersDefaultRequestType() throws InterruptedException grpcServer.getServiceRegistry().addService(server.getListenerDiscoveryServiceImpl()); grpcServer.getServiceRegistry().addService(server.getRouteDiscoveryServiceImpl()); grpcServer.getServiceRegistry().addService(server.getSecretDiscoveryServiceImpl()); + grpcServer.getServiceRegistry().addService(server.getExtensionConfigDiscoveryServiceImpl()); ClusterDiscoveryServiceStub clusterStub = ClusterDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); EndpointDiscoveryServiceStub endpointStub = EndpointDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); ListenerDiscoveryServiceStub listenerStub = ListenerDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); RouteDiscoveryServiceStub routeStub = RouteDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); SecretDiscoveryServiceStub secretStub = SecretDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); + ExtensionConfigDiscoveryServiceStub extensionConfigStub = + ExtensionConfigDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); for (String typeUrl : Resources.V3.TYPE_URLS) { MockDiscoveryResponseObserver responseObserver = new MockDiscoveryResponseObserver(); @@ -400,6 +422,9 @@ public void testSeparateHandlersDefaultRequestType() throws InterruptedException case Resources.V3.SECRET_TYPE_URL: requestObserver = secretStub.streamSecrets(responseObserver); break; + case Resources.V3.EXTENSION_CONFIG_TYPE_URL: + requestObserver = extensionConfigStub.streamExtensionConfigs(responseObserver); + break; default: fail("Unsupported resource type: " + typeUrl); } @@ -517,6 +542,12 @@ public void onV3StreamResponse(long streamId, DiscoveryRequest request, .addResourceNames(SECRET_NAME) .build()); + requestObserver.onNext(DiscoveryRequest.newBuilder() + .setNode(NODE) + .setTypeUrl(Resources.V3.EXTENSION_CONFIG_TYPE_URL) + .addResourceNames(EXTENSION_CONFIG_NAME) + .build()); + if (!streamRequestLatch.get().await(1, TimeUnit.SECONDS)) { fail("failed to execute onStreamRequest callback before timeout"); } @@ -567,6 +598,14 @@ public void onV3StreamResponse(long streamId, DiscoveryRequest request, .setVersionInfo(VERSION) .build()); + requestObserver.onNext(DiscoveryRequest.newBuilder() + .setNode(NODE) + .setResponseNonce("5") + .setTypeUrl(Resources.V3.EXTENSION_CONFIG_TYPE_URL) + .addResourceNames(EXTENSION_CONFIG_NAME) + .setVersionInfo(VERSION) + .build()); + if (!streamRequestLatch.get().await(1, TimeUnit.SECONDS)) { fail("failed to execute onStreamRequest callback before timeout"); } @@ -659,12 +698,15 @@ public void onV3StreamResponse(long streamId, DiscoveryRequest request, grpcServer.getServiceRegistry().addService(server.getListenerDiscoveryServiceImpl()); grpcServer.getServiceRegistry().addService(server.getRouteDiscoveryServiceImpl()); grpcServer.getServiceRegistry().addService(server.getSecretDiscoveryServiceImpl()); + grpcServer.getServiceRegistry().addService(server.getExtensionConfigDiscoveryServiceImpl()); ClusterDiscoveryServiceStub clusterStub = ClusterDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); EndpointDiscoveryServiceStub endpointStub = EndpointDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); ListenerDiscoveryServiceStub listenerStub = ListenerDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); RouteDiscoveryServiceStub routeStub = RouteDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); SecretDiscoveryServiceStub secretStub = SecretDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); + ExtensionConfigDiscoveryServiceStub extensionConfigStub = + ExtensionConfigDiscoveryServiceGrpc.newStub(grpcServer.getChannel()); for (String typeUrl : Resources.V3.TYPE_URLS) { MockDiscoveryResponseObserver responseObserver = new MockDiscoveryResponseObserver(); @@ -687,6 +729,9 @@ public void onV3StreamResponse(long streamId, DiscoveryRequest request, case Resources.V3.SECRET_TYPE_URL: requestObserver = secretStub.streamSecrets(responseObserver); break; + case Resources.V3.EXTENSION_CONFIG_TYPE_URL: + requestObserver = extensionConfigStub.streamExtensionConfigs(responseObserver); + break; default: fail("Unsupported resource type: " + typeUrl); } @@ -719,11 +764,11 @@ public void onV3StreamResponse(long streamId, DiscoveryRequest request, callbacks.assertThatNoErrors(); - assertThat(callbacks.streamCloseCount).hasValue(5); + assertThat(callbacks.streamCloseCount).hasValue(Resources.V3.TYPE_URLS.size()); assertThat(callbacks.streamCloseWithErrorCount).hasValue(0); - assertThat(callbacks.streamOpenCount).hasValue(5); - assertThat(callbacks.streamRequestCount).hasValue(5); - assertThat(callbacks.streamResponseCount).hasValue(5); + assertThat(callbacks.streamOpenCount).hasValue(Resources.V3.TYPE_URLS.size()); + assertThat(callbacks.streamRequestCount).hasValue(Resources.V3.TYPE_URLS.size()); + assertThat(callbacks.streamResponseCount).hasValue(Resources.V3.TYPE_URLS.size()); } @Test @@ -1012,6 +1057,7 @@ private static Table> createRespon .put(Resources.V3.LISTENER_TYPE_URL, VERSION, ImmutableList.of(LISTENER)) .put(Resources.V3.ROUTE_TYPE_URL, VERSION, ImmutableList.of(ROUTE)) .put(Resources.V3.SECRET_TYPE_URL, VERSION, ImmutableList.of(SECRET)) + .put(Resources.V3.EXTENSION_CONFIG_TYPE_URL, VERSION, ImmutableList.of(EXTENSION_CONFIG)) .build(); }