Skip to content

Commit 5877a99

Browse files
sschepensSnow Pettersen
authored andcommitted
cache: Per resource versioning (#94)
This adds the concept of ResourceVersionResolver that receives a list of resource names and should return a version for those resources. This could be used by users of java-control-plane to implement per-resource versioning in EDS and RDS. The adds new methods but does not break existing APIs. Signed-off-by: Jakub Dyszkiewicz <[email protected]> Signed-off-by: sschepens <[email protected]>
1 parent 05839d9 commit 5877a99

File tree

5 files changed

+166
-19
lines changed

5 files changed

+166
-19
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.envoyproxy.controlplane.cache;
2+
3+
import java.util.Collections;
4+
import java.util.List;
5+
6+
/**
7+
* {@code ResourceVersionResolver} calculates a version for resources in {@link SnapshotCache}.
8+
* It can be used to take advantage of Envoy's per-resource versioning.
9+
*/
10+
public interface ResourceVersionResolver {
11+
/**
12+
* Returns a version for resources.
13+
*
14+
* @param resourceNames list of resourceNames requested an empty list means all resources.
15+
* @return version for the resources received
16+
*/
17+
String version(List<String> resourceNames);
18+
19+
/**
20+
* Returns a version for all resources.
21+
*
22+
* @return version for all the resources in the {@link SnapshotCache}.
23+
*/
24+
default String version() {
25+
return version(Collections.emptyList());
26+
}
27+
}

cache/src/main/java/io/envoyproxy/controlplane/cache/SimpleCache.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public Watch createWatch(
100100
status.setLastWatchRequestTime(System.currentTimeMillis());
101101

102102
Snapshot snapshot = snapshots.get(group);
103-
String version = snapshot == null ? "" : snapshot.version(request.getTypeUrl());
103+
String version = snapshot == null ? "" : snapshot.version(request.getTypeUrl(), request.getResourceNamesList());
104104

105105
Watch watch = new Watch(ads, request, responseConsumer);
106106

@@ -213,7 +213,7 @@ public void setSnapshot(T group, Snapshot snapshot) {
213213
}
214214

215215
status.watchesRemoveIf((id, watch) -> {
216-
String version = snapshot.version(watch.request().getTypeUrl());
216+
String version = snapshot.version(watch.request().getTypeUrl(), watch.request().getResourceNamesList());
217217

218218
if (!watch.request().getVersionInfo().equals(version)) {
219219
LOGGER.info("responding to open watch {}[{}] with new version {}",
@@ -273,15 +273,15 @@ private boolean respond(Watch watch, Snapshot snapshot, T group) {
273273
"not responding in ADS mode for {} from node {} at version {} for request [{}] since [{}] not in snapshot",
274274
watch.request().getTypeUrl(),
275275
group,
276-
snapshot.version(watch.request().getTypeUrl()),
276+
snapshot.version(watch.request().getTypeUrl(), watch.request().getResourceNamesList()),
277277
String.join(", ", watch.request().getResourceNamesList()),
278278
String.join(", ", missingNames));
279279

280280
return false;
281281
}
282282
}
283283

284-
String version = snapshot.version(watch.request().getTypeUrl());
284+
String version = snapshot.version(watch.request().getTypeUrl(), watch.request().getResourceNamesList());
285285

286286
LOGGER.info("responding for {} from node {} at version {} with version {}",
287287
watch.request().getTypeUrl(),

cache/src/main/java/io/envoyproxy/controlplane/cache/Snapshot.java

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import io.envoyproxy.envoy.api.v2.RouteConfiguration;
1717
import io.envoyproxy.envoy.api.v2.auth.Secret;
1818
import java.util.Collections;
19+
import java.util.List;
1920
import java.util.Map;
2021
import java.util.Set;
2122

@@ -84,6 +85,40 @@ public static Snapshot create(
8485
SnapshotResources.create(secrets, secretsVersion));
8586
}
8687

88+
/**
89+
* Returns a new {@link Snapshot} instance that has separate versions for each resource type.
90+
*
91+
* @param clusters the cluster resources in this snapshot
92+
* @param clusterVersionResolver version resolver of the clusters in this snapshot
93+
* @param endpoints the endpoint resources in this snapshot
94+
* @param endpointVersionResolver version resolver of the endpoints in this snapshot
95+
* @param listeners the listener resources in this snapshot
96+
* @param listenerVersionResolver version resolver of listeners in this snapshot
97+
* @param routes the route resources in this snapshot
98+
* @param routeVersionResolver version resolver of the routes in this snapshot
99+
* @param secrets the secret resources in this snapshot
100+
* @param secretVersionResolver version resolver of the secrets in this snapshot
101+
*/
102+
public static Snapshot create(
103+
Iterable<Cluster> clusters,
104+
ResourceVersionResolver clusterVersionResolver,
105+
Iterable<ClusterLoadAssignment> endpoints,
106+
ResourceVersionResolver endpointVersionResolver,
107+
Iterable<Listener> listeners,
108+
ResourceVersionResolver listenerVersionResolver,
109+
Iterable<RouteConfiguration> routes,
110+
ResourceVersionResolver routeVersionResolver,
111+
Iterable<Secret> secrets,
112+
ResourceVersionResolver secretVersionResolver) {
113+
114+
return new AutoValue_Snapshot(
115+
SnapshotResources.create(clusters, clusterVersionResolver),
116+
SnapshotResources.create(endpoints, endpointVersionResolver),
117+
SnapshotResources.create(listeners, listenerVersionResolver),
118+
SnapshotResources.create(routes, routeVersionResolver),
119+
SnapshotResources.create(secrets, secretVersionResolver));
120+
}
121+
87122
/**
88123
* Creates an empty snapshot with the given version.
89124
*
@@ -170,21 +205,32 @@ public void ensureConsistent() throws SnapshotConsistencyException {
170205
* @param typeUrl the URL for the requested resource type
171206
*/
172207
public String version(String typeUrl) {
208+
return version(typeUrl, Collections.emptyList());
209+
}
210+
211+
/**
212+
* Returns the version in this snapshot for the given resource type.
213+
*
214+
* @param typeUrl the URL for the requested resource type
215+
* @param resourceNames list of requested resource names,
216+
* used to calculate a version for the given resources
217+
*/
218+
public String version(String typeUrl, List<String> resourceNames) {
173219
if (Strings.isNullOrEmpty(typeUrl)) {
174220
return "";
175221
}
176222

177223
switch (typeUrl) {
178224
case CLUSTER_TYPE_URL:
179-
return clusters().version();
225+
return clusters().version(resourceNames);
180226
case ENDPOINT_TYPE_URL:
181-
return endpoints().version();
227+
return endpoints().version(resourceNames);
182228
case LISTENER_TYPE_URL:
183-
return listeners().version();
229+
return listeners().version(resourceNames);
184230
case ROUTE_TYPE_URL:
185-
return routes().version();
231+
return routes().version(resourceNames);
186232
case SECRET_TYPE_URL:
187-
return secrets().version();
233+
return secrets().version(resourceNames);
188234
default:
189235
return "";
190236
}

cache/src/main/java/io/envoyproxy/controlplane/cache/SnapshotResources.java

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import com.google.auto.value.AutoValue;
44
import com.google.common.collect.ImmutableMap;
55
import com.google.protobuf.Message;
6+
7+
import java.util.List;
68
import java.util.Map;
79
import java.util.stream.Collector;
810
import java.util.stream.StreamSupport;
@@ -19,14 +21,34 @@ public abstract class SnapshotResources<T extends Message> {
1921
*/
2022
public static <T extends Message> SnapshotResources<T> create(Iterable<T> resources, String version) {
2123
return new AutoValue_SnapshotResources<>(
22-
StreamSupport.stream(resources.spliterator(), false)
23-
.collect(
24-
Collector.of(
25-
ImmutableMap.Builder<String, T>::new,
26-
(b, e) -> b.put(Resources.getResourceName(e), e),
27-
(b1, b2) -> b1.putAll(b2.build()),
28-
ImmutableMap.Builder::build)),
29-
version);
24+
resourcesMap(resources),
25+
(r) -> version
26+
);
27+
}
28+
29+
/**
30+
* Returns a new {@link SnapshotResources} instance with versions by resource name.
31+
*
32+
* @param resources the resources in this collection
33+
* @param versionResolver version resolver for the resources in this collection
34+
* @param <T> the type of resources in this collection
35+
*/
36+
public static <T extends Message> SnapshotResources<T> create(
37+
Iterable<T> resources,
38+
ResourceVersionResolver versionResolver) {
39+
return new AutoValue_SnapshotResources<>(
40+
resourcesMap(resources),
41+
versionResolver);
42+
}
43+
44+
private static <T extends Message> ImmutableMap<String, T> resourcesMap(Iterable<T> resources) {
45+
return StreamSupport.stream(resources.spliterator(), false)
46+
.collect(
47+
Collector.of(
48+
ImmutableMap.Builder<String, T>::new,
49+
(b, e) -> b.put(Resources.getResourceName(e), e),
50+
(b1, b2) -> b1.putAll(b2.build()),
51+
ImmutableMap.Builder::build));
3052
}
3153

3254
/**
@@ -35,7 +57,24 @@ public static <T extends Message> SnapshotResources<T> create(Iterable<T> resour
3557
public abstract Map<String, T> resources();
3658

3759
/**
38-
* Returns the version associated with this resources in this collection.
60+
* Returns the version associated with this all resources in this collection.
3961
*/
40-
public abstract String version();
62+
public String version() {
63+
return resourceVersionResolver().version();
64+
}
65+
66+
/**
67+
* Returns the version associated with the requested resources in this collection.
68+
*
69+
* @param resourceNames list of list of requested resources.
70+
*/
71+
public String version(List<String> resourceNames) {
72+
return resourceVersionResolver().version(resourceNames);
73+
}
74+
75+
/**
76+
* Returns the version resolver associated with this resources in this collection.
77+
*/
78+
public abstract ResourceVersionResolver resourceVersionResolver();
79+
4180
}

cache/src/test/java/io/envoyproxy/controlplane/cache/SnapshotResourcesTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import static org.assertj.core.api.Assertions.assertThat;
44

55
import com.google.common.collect.ImmutableList;
6+
import com.google.common.collect.ImmutableMap;
7+
68
import io.envoyproxy.envoy.api.v2.Cluster;
9+
import java.util.Map;
710
import java.util.UUID;
811
import org.junit.Test;
912

@@ -28,4 +31,36 @@ public void createBuildsResourcesMapWithNameAndPopulatesVersion() {
2831

2932
assertThat(snapshot.version()).isEqualTo(version);
3033
}
34+
35+
@Test
36+
public void populatesVersionWithSeparateVersionPerCluster() {
37+
final String aggregateVersion = UUID.randomUUID().toString();
38+
39+
final Map<String, String> versions = ImmutableMap.of(
40+
CLUSTER0_NAME, UUID.randomUUID().toString(),
41+
CLUSTER1_NAME, UUID.randomUUID().toString()
42+
);
43+
44+
SnapshotResources<Cluster> snapshot = SnapshotResources.create(
45+
ImmutableList.of(CLUSTER0, CLUSTER1), resourceNames -> {
46+
if (resourceNames.size() != 1 || !versions.containsKey(resourceNames.get(0))) {
47+
return aggregateVersion;
48+
}
49+
return versions.get(resourceNames.get(0));
50+
}
51+
);
52+
53+
// when no resource name provided, the aggregated version should be returned
54+
assertThat(snapshot.version()).isEqualTo(aggregateVersion);
55+
56+
// when one resource name is provided, the cluster version should be returned
57+
assertThat(snapshot.version(ImmutableList.of(CLUSTER0_NAME))).isEqualTo(versions.get(CLUSTER0_NAME));
58+
assertThat(snapshot.version(ImmutableList.of(CLUSTER1_NAME))).isEqualTo(versions.get(CLUSTER1_NAME));
59+
60+
// when an unknown resource name is provided, the aggregated version should be returned
61+
assertThat(snapshot.version(ImmutableList.of("unknown_cluster_name"))).isEqualTo(aggregateVersion);
62+
63+
// when multiple resource names are provided, the aggregated version should be returned
64+
assertThat(snapshot.version(ImmutableList.of(CLUSTER1_NAME, CLUSTER1_NAME))).isEqualTo(aggregateVersion);
65+
}
3166
}

0 commit comments

Comments
 (0)