Skip to content

Commit

Permalink
Add v3 xDS support
Browse files Browse the repository at this point in the history
This commit adds support for the v3 xDS transport as well as support for
storing and serving v3 resources out of the resource cache.

Note that resource versions and xDS transport version are decoupled to
provide a migration path to v3. End users can decide to generate v3
resources and serve them over v2 transport during migration, or continue
to generate v2 resources and serve them over v3 transport.

For implementation simplicity, it is not possible to generate a
combination of v2 and v3 resources in the control plane, which would
require either up or down conversion.

Signed-off-by: Michael Puncel <mpuncel@squareup.com>
  • Loading branch information
mpuncel committed Jul 28, 2020
1 parent df5fac6 commit cae4ebc
Show file tree
Hide file tree
Showing 56 changed files with 5,298 additions and 698 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.envoyproxy.controlplane.cache;

import io.envoyproxy.envoy.api.v2.core.Node;
import java.util.Collection;
import javax.annotation.concurrent.ThreadSafe;

Expand All @@ -11,13 +10,13 @@
public interface Cache<T> extends ConfigWatcher {

/**
* Returns all known {@link Node} groups.
* Returns all known groups.
*
*/
Collection<T> groups();

/**
* Returns the current {@link StatusInfo} for the given {@link Node} group.
* Returns the current {@link StatusInfo} for the given group.
*
* @param group the node group whose status is being fetched
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.envoyproxy.controlplane.cache;

import io.envoyproxy.envoy.api.v2.DiscoveryRequest;
import java.util.Set;
import java.util.function.Consumer;
import javax.annotation.concurrent.ThreadSafe;
Expand All @@ -25,7 +24,7 @@ public interface ConfigWatcher {
*/
Watch createWatch(
boolean ads,
DiscoveryRequest request,
XdsRequest request,
Set<String> knownResourceNames,
Consumer<Response> responseConsumer,
boolean hasClusterChanged);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,12 @@ public interface NodeGroup<T> {
*
* @param node identifier for the envoy instance that is requesting config
*/
T hash(Node node);
T hashV2(Node node);

/**
* Returns a consistent identifier of the given {@link io.envoyproxy.envoy.config.core.v3.Node}.
*
* @param node identifier for the envoy instance that is requesting config
*/
T hashV3(io.envoyproxy.envoy.config.core.v3.Node node);
}
190 changes: 174 additions & 16 deletions cache/src/main/java/io/envoyproxy/controlplane/cache/Resources.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package io.envoyproxy.controlplane.cache;

import static com.google.common.base.Strings.isNullOrEmpty;
import static io.envoyproxy.controlplane.cache.Resources.ApiVersion.V2;
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.LISTENER;
import static io.envoyproxy.controlplane.cache.Resources.ResourceType.ROUTE;
import static io.envoyproxy.controlplane.cache.Resources.ResourceType.SECRET;
import static io.envoyproxy.envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager.RouteSpecifierCase.RDS;

import com.google.common.base.Preconditions;
Expand Down Expand Up @@ -30,33 +37,108 @@

public class Resources {

/**
* Version-agnostic representation of a resource. This is useful when the version qualifier
* isn't needed.
*/
public enum ResourceType {
CLUSTER,
ENDPOINT,
LISTENER,
ROUTE,
SECRET
}

public enum ApiVersion {
V2,
V3
}

private static final Logger LOGGER = LoggerFactory.getLogger(Resources.class);

static final String FILTER_ENVOY_ROUTER = "envoy.router";
static final String FILTER_HTTP_CONNECTION_MANAGER = "envoy.http_connection_manager";

private static final String TYPE_URL_PREFIX = "type.googleapis.com/envoy.api.v2.";

public static final String CLUSTER_TYPE_URL = TYPE_URL_PREFIX + "Cluster";
public static final String ENDPOINT_TYPE_URL = TYPE_URL_PREFIX + "ClusterLoadAssignment";
public static final String LISTENER_TYPE_URL = TYPE_URL_PREFIX + "Listener";
public static final String ROUTE_TYPE_URL = TYPE_URL_PREFIX + "RouteConfiguration";
public static final String SECRET_TYPE_URL = TYPE_URL_PREFIX + "auth.Secret";

public static final List<String> TYPE_URLS = ImmutableList.of(
public static final String V3_CLUSTER_TYPE_URL = "type.googleapis.com/envoy.config.cluster.v3"
+ ".Cluster";
public static final String V3_ENDPOINT_TYPE_URL = "type.googleapis.com/envoy.config.endpoint.v3"
+ ".ClusterLoadAssignment";
public static final String V3_LISTENER_TYPE_URL = "type.googleapis.com/envoy.config.listener.v3"
+ ".Listener";
public static final String V3_ROUTE_TYPE_URL = "type.googleapis.com/envoy.config.route.v3"
+ ".RouteConfiguration";
public static final String V3_SECRET_TYPE_URL = "type.googleapis.com/envoy.extensions"
+ ".transport_sockets.tls.v3.Secret";

private static final String V2_TYPE_URL_PREFIX = "type.googleapis.com/envoy.api.v2.";
public static final String CLUSTER_TYPE_URL = V2_TYPE_URL_PREFIX + "Cluster";
public static final String ENDPOINT_TYPE_URL = V2_TYPE_URL_PREFIX + "ClusterLoadAssignment";
public static final String LISTENER_TYPE_URL = V2_TYPE_URL_PREFIX + "Listener";
public static final String ROUTE_TYPE_URL = V2_TYPE_URL_PREFIX + "RouteConfiguration";
public static final String SECRET_TYPE_URL = V2_TYPE_URL_PREFIX + "auth.Secret";

public static final List<String> V2_TYPE_URLS = ImmutableList.of(
CLUSTER_TYPE_URL,
ENDPOINT_TYPE_URL,
LISTENER_TYPE_URL,
ROUTE_TYPE_URL,
SECRET_TYPE_URL);

public static final Map<String, Class<? extends Message>> RESOURCE_TYPE_BY_URL = ImmutableMap.of(
CLUSTER_TYPE_URL, Cluster.class,
ENDPOINT_TYPE_URL, ClusterLoadAssignment.class,
LISTENER_TYPE_URL, Listener.class,
ROUTE_TYPE_URL, RouteConfiguration.class,
SECRET_TYPE_URL, Secret.class
);
public static final List<String> V3_TYPE_URLS = ImmutableList.of(
V3_CLUSTER_TYPE_URL,
V3_ENDPOINT_TYPE_URL,
V3_LISTENER_TYPE_URL,
V3_ROUTE_TYPE_URL,
V3_SECRET_TYPE_URL);

public static final List<ResourceType> RESOURCE_TYPES_IN_ORDER = ImmutableList.of(
CLUSTER,
ENDPOINT,
LISTENER,
ROUTE,
SECRET);

public static final Map<String, String> V3_TYPE_URLS_TO_V2 = ImmutableMap.of(
V3_CLUSTER_TYPE_URL, CLUSTER_TYPE_URL,
V3_ENDPOINT_TYPE_URL, ENDPOINT_TYPE_URL,
V3_LISTENER_TYPE_URL, LISTENER_TYPE_URL,
V3_ROUTE_TYPE_URL, ROUTE_TYPE_URL,
V3_SECRET_TYPE_URL, SECRET_TYPE_URL);

public static final Map<String, String> V2_TYPE_URLS_TO_V3 = ImmutableMap.of(
CLUSTER_TYPE_URL, V3_CLUSTER_TYPE_URL,
ENDPOINT_TYPE_URL, V3_ENDPOINT_TYPE_URL,
LISTENER_TYPE_URL, V3_LISTENER_TYPE_URL,
ROUTE_TYPE_URL, V3_ROUTE_TYPE_URL,
SECRET_TYPE_URL, V3_SECRET_TYPE_URL);

public static final Map<String, ResourceType> TYPE_URLS_TO_RESOURCE_TYPE =
new ImmutableMap.Builder<String, ResourceType>()
.put(V3_CLUSTER_TYPE_URL, CLUSTER)
.put(CLUSTER_TYPE_URL, CLUSTER)
.put(V3_ENDPOINT_TYPE_URL, ENDPOINT)
.put(ENDPOINT_TYPE_URL, ENDPOINT)
.put(V3_LISTENER_TYPE_URL, LISTENER)
.put(LISTENER_TYPE_URL, LISTENER)
.put(V3_ROUTE_TYPE_URL, ROUTE)
.put(ROUTE_TYPE_URL, ROUTE)
.put(V3_SECRET_TYPE_URL, SECRET)
.put(SECRET_TYPE_URL, SECRET)
.build();

public static final Map<String, Class<? extends Message>> RESOURCE_TYPE_BY_URL =
new ImmutableMap.Builder<String, Class<? extends Message>>()
.put(CLUSTER_TYPE_URL, Cluster.class)
.put(ENDPOINT_TYPE_URL, ClusterLoadAssignment.class)
.put(LISTENER_TYPE_URL, Listener.class)
.put(ROUTE_TYPE_URL, RouteConfiguration.class)
.put(SECRET_TYPE_URL, Secret.class)
.put(V3_CLUSTER_TYPE_URL, io.envoyproxy.envoy.config.cluster.v3.Cluster.class)
.put(V3_ENDPOINT_TYPE_URL, io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment.class)
.put(V3_LISTENER_TYPE_URL, io.envoyproxy.envoy.config.listener.v3.Listener.class)
.put(V3_ROUTE_TYPE_URL, io.envoyproxy.envoy.config.route.v3.RouteConfiguration.class)
.put(V3_SECRET_TYPE_URL, io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.Secret.class)
.build();

/**
* Returns the name of the given resource message.
Expand Down Expand Up @@ -84,6 +166,26 @@ public static String getResourceName(Message resource) {
return ((Secret) resource).getName();
}

if (resource instanceof io.envoyproxy.envoy.config.cluster.v3.Cluster) {
return ((io.envoyproxy.envoy.config.cluster.v3.Cluster) resource).getName();
}

if (resource instanceof io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment) {
return ((io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment) resource).getClusterName();
}

if (resource instanceof io.envoyproxy.envoy.config.listener.v3.Listener) {
return ((io.envoyproxy.envoy.config.listener.v3.Listener) resource).getName();
}

if (resource instanceof io.envoyproxy.envoy.config.route.v3.RouteConfiguration) {
return ((io.envoyproxy.envoy.config.route.v3.RouteConfiguration) resource).getName();
}

if (resource instanceof io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.Secret) {
return ((io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.Secret) resource).getName();
}

return "";
}

Expand Down Expand Up @@ -133,6 +235,17 @@ public static Set<String> getResourceReferences(Collection<? extends Message> re
refs.add(c.getName());
}
}
} else if (r instanceof io.envoyproxy.envoy.config.cluster.v3.Cluster) {
io.envoyproxy.envoy.config.cluster.v3.Cluster c = (io.envoyproxy.envoy.config.cluster.v3.Cluster) r;

// For EDS clusters, use the cluster name or the service name override.
if (c.getType() == io.envoyproxy.envoy.config.cluster.v3.Cluster.DiscoveryType.EDS) {
if (!isNullOrEmpty(c.getEdsClusterConfig().getServiceName())) {
refs.add(c.getEdsClusterConfig().getServiceName());
} else {
refs.add(c.getName());
}
}
} else if (r instanceof Listener) {
Listener l = (Listener) r;

Expand Down Expand Up @@ -160,12 +273,57 @@ public static Set<String> getResourceReferences(Collection<? extends Message> re
}
}
}
} else if (r instanceof io.envoyproxy.envoy.config.listener.v3.Listener) {

io.envoyproxy.envoy.config.listener.v3.Listener l =
(io.envoyproxy.envoy.config.listener.v3.Listener) r;

// Extract the route configuration names from the HTTP connection manager.
for (io.envoyproxy.envoy.config.listener.v3.FilterChain chain : l.getFilterChainsList()) {
for (io.envoyproxy.envoy.config.listener.v3.Filter filter : chain.getFiltersList()) {
if (!filter.getName().equals(FILTER_HTTP_CONNECTION_MANAGER)) {
continue;
}

try {
io.envoyproxy.envoy.extensions.filters.network
.http_connection_manager.v3.HttpConnectionManager config = filter
.getTypedConfig().unpack(
io.envoyproxy.envoy.extensions.filters.network
.http_connection_manager.v3.HttpConnectionManager.class);

if (config.getRouteSpecifierCase() == io.envoyproxy.envoy.extensions.filters.network
.http_connection_manager.v3.HttpConnectionManager.RouteSpecifierCase.RDS
&& !isNullOrEmpty(config.getRds().getRouteConfigName())) {
refs.add(config.getRds().getRouteConfigName());
}
} catch (InvalidProtocolBufferException e) {
LOGGER.error(
"Failed to convert HTTP connection manager config struct into protobuf message for listener {}",
getResourceName(l),
e);
}
}
}
}
}

return refs.build();
}

/**
* Returns the API version (v2 or v3) for a given type URL.
*/
public static ApiVersion getResourceApiVersion(String typeUrl) {
if (V2_TYPE_URLS.contains(typeUrl)) {
return V2;
} else if (V3_TYPE_URLS.contains(typeUrl)) {
return V3;
}

throw new RuntimeException(String.format("Unsupported API version for type URL %s", typeUrl));
}

private static void structAsMessage(Struct struct, Message.Builder messageBuilder)
throws InvalidProtocolBufferException {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.google.auto.value.AutoValue;
import com.google.protobuf.Message;
import io.envoyproxy.envoy.api.v2.DiscoveryRequest;
import java.util.Collection;

/**
Expand All @@ -11,14 +10,15 @@
@AutoValue
public abstract class Response {

public static Response create(DiscoveryRequest request, Collection<? extends Message> resources, String version) {
public static Response create(XdsRequest request, Collection<? extends Message> resources,
String version) {
return new AutoValue_Response(request, resources, version);
}

/**
* Returns the original request associated with the response.
*/
public abstract DiscoveryRequest request();
public abstract XdsRequest request();

/**
* Returns the resources to include in the response.
Expand Down
Loading

0 comments on commit cae4ebc

Please sign in to comment.