public static Response create(DiscoveryRequest request, Collection<? extends Message> resources, String version) { return new AutoValue_Response(request, resources, version); }
/** * 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. * * <p>Note that clusters and listeners are requested without name references, so Envoy will accept the snapshot list * of clusters as-is, even if it does not match all references found in xDS. * * @throws SnapshotConsistencyException if the snapshot is not consistent */ public void ensureConsistent() throws SnapshotConsistencyException { Set<String> clusterEndpointRefs = Resources.getResourceReferences(clusters().resources().values()); ensureAllResourceNamesExist(CLUSTER_TYPE_URL, ENDPOINT_TYPE_URL, clusterEndpointRefs, endpoints().resources()); Set<String> listenerRouteRefs = Resources.getResourceReferences(listeners().resources().values()); ensureAllResourceNamesExist(LISTENER_TYPE_URL, ROUTE_TYPE_URL, listenerRouteRefs, routes().resources()); }
/** * Returns a new {@link Snapshot} instance that is versioned uniformly across all resources. * * @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 version the version associated with all resources in this snapshot */ public static Snapshot create( Iterable<Cluster> clusters, Iterable<ClusterLoadAssignment> endpoints, Iterable<Listener> listeners, Iterable<RouteConfiguration> routes, Iterable<Secret> secrets, String version) { return new AutoValue_Snapshot( SnapshotResources.create(clusters, version), SnapshotResources.create(endpoints, version), SnapshotResources.create(listeners, version), SnapshotResources.create(routes, version), SnapshotResources.create(secrets, version)); }
Consumer<Response> responseConsumer) { T group = groups.hash(request.getNode()); CacheStatusInfo<T> status = statuses.computeIfAbsent(group, g -> new CacheStatusInfo<>(group)); status.setLastWatchRequestTime(System.currentTimeMillis()); String version = snapshot == null ? "" : snapshot.version(request.getTypeUrl()); Watch watch = new Watch(ads, request, responseConsumer); if (snapshot.resources(request.getTypeUrl()) .keySet() .stream() .anyMatch(newResourceHints::contains)) { respond(watch, snapshot, group); request.getVersionInfo()); status.setWatch(watchId, watch); watch.setStop(() -> status.removeWatch(watchId)); respond(watch, snapshot, group);
/** * Returns the version in this snapshot for the given resource type. * * @param typeUrl the URL for the requested resource type */ public String version(String typeUrl) { if (Strings.isNullOrEmpty(typeUrl)) { return ""; } switch (typeUrl) { case CLUSTER_TYPE_URL: return clusters().version(); case ENDPOINT_TYPE_URL: return endpoints().version(); case LISTENER_TYPE_URL: return listeners().version(); case ROUTE_TYPE_URL: return routes().version(); case SECRET_TYPE_URL: return secrets().version(); default: return ""; } }
/** * Returns the resources with the given type. * * @param typeUrl the URL for the requested resource type */ public Map<String, ? extends Message> resources(String typeUrl) { if (Strings.isNullOrEmpty(typeUrl)) { return ImmutableMap.of(); } switch (typeUrl) { case CLUSTER_TYPE_URL: return clusters().resources(); case ENDPOINT_TYPE_URL: return endpoints().resources(); case LISTENER_TYPE_URL: return listeners().resources(); case ROUTE_TYPE_URL: return routes().resources(); case SECRET_TYPE_URL: return secrets().resources(); default: return ImmutableMap.of(); } }
private void respond(Watch watch, Snapshot snapshot, T group) { Map<String, ? extends Message> snapshotResources = snapshot.resources(watch.request().getTypeUrl()); if (!watch.request().getResourceNamesList().isEmpty() && watch.ads()) { Collection<String> missingNames = watch.request().getResourceNamesList().stream() .filter(name -> !snapshotResources.containsKey(name)) .collect(Collectors.toList()); LOGGER.info( "not responding in ADS mode for {} from node {} at version {} for request [{}] since [{}] not in snapshot", watch.request().getTypeUrl(), group, snapshot.version(watch.request().getTypeUrl()), String.join(", ", watch.request().getResourceNamesList()), String.join(", ", missingNames)); String version = snapshot.version(watch.request().getTypeUrl()); watch.request().getTypeUrl(), group, watch.request().getVersionInfo(), version); Response response = createResponse( watch.request(), snapshotResources, version); watch.respond(response); } catch (WatchCancelledException e) {
status.watchesRemoveIf((id, watch) -> { String version = snapshot.version(watch.request().getTypeUrl()); if (!watch.request().getVersionInfo().equals(version)) { LOGGER.info("responding to open watch {}[{}] with new version {}", id, String.join(", ", watch.request().getResourceNamesList()), version); respond(watch, snapshot, group);
private void send(Response response, String typeUrl) { String nonce = Long.toString(streamNonce.getAndIncrement()); DiscoveryResponse discoveryResponse = DiscoveryResponse.newBuilder() .setVersionInfo(response.version()) .addAllResources(response.resources().stream().map(Any::pack).collect(Collectors.toList())) .setTypeUrl(typeUrl) .setNonce(nonce) .build(); LOGGER.info("[{}] response {} with nonce {} version {}", streamId, typeUrl, nonce, response.version()); callbacks.forEach(cb -> cb.onStreamResponse(streamId, response.request(), discoveryResponse)); // Store the latest response *before* we send the response. This ensures that by the time the request // is processed the map is guaranteed to be updated. Doing it afterwards leads to a race conditions // which may see the incoming request arrive before the map is updated, failing the nonce check erroneously. latestResponse.put(typeUrl, discoveryResponse); try { responseObserver.onNext(discoveryResponse); } catch (StatusRuntimeException e) { if (!Status.CANCELLED.getCode().equals(e.getStatus().getCode())) { throw e; } } } }
/** * Sends the given response to the watch's response handler. * * @param response the response to be handled * @throws WatchCancelledException if the watch has already been cancelled */ public void respond(Response response) throws WatchCancelledException { if (isCancelled()) { throw new WatchCancelledException(); } responseConsumer.accept(response); }
/** * Returns a new {@link SnapshotResources} instance. * * @param resources the resources in this collection * @param version the version associated with the resources in this collection * @param <T> the type of resources in this collection */ public static <T extends Message> SnapshotResources<T> create(Iterable<T> resources, String version) { return new AutoValue_SnapshotResources<>( StreamSupport.stream(resources.spliterator(), false) .collect( Collector.of( ImmutableMap.Builder<String, T>::new, (b, e) -> b.put(Resources.getResourceName(e), e), (b1, b2) -> b1.putAll(b2.build()), ImmutableMap.Builder::build)), version); }
@Override public synchronized void onStreamRequest(long streamId, DiscoveryRequest request) { T groupIdentifier = nodeGroup.hash(request.getNode()); SnapshotState snapshotState = this.snapshotStates.computeIfAbsent(groupIdentifier, x -> new SnapshotState()); snapshotState.lastSeen = clock.instant(); if (groupByStream.put(streamId, groupIdentifier) == null) { snapshotState.streamCount++; } }
oldWatch.cancel(); return configWatcher.createWatch( ads, request,
/** * Creates an empty snapshot with the given version. * * @param version the version of the snapshot resources */ public static Snapshot createEmpty(String version) { return create(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), version); }
/** * {@inheritDoc} */ @Override public boolean clearSnapshot(T group) { writeLock.lock(); try { CacheStatusInfo<T> status = statuses.get(group); // If we don't know about this group, do nothing. if (status != null && status.numWatches() > 0) { LOGGER.warn("tried to clear snapshot for group with existing watches, group={}", group); return false; } statuses.remove(group); snapshots.remove(group); return true; } finally { writeLock.unlock(); } }
throw new SnapshotConsistencyException( String.format( "Mismatched %s -> %s reference and resource lengths, [%s] != %d", throw new SnapshotConsistencyException( String.format( "%s named '%s', referenced by a %s, not listed in [%s]",
/** * Returns the name of the given resource message. * * @param anyResource the resource message * @throws RuntimeException if the passed Any doesn't correspond to an xDS resource */ public static String getResourceName(Any anyResource) { Class<? extends Message> clazz = RESOURCE_TYPE_BY_URL.get(anyResource.getTypeUrl()); Preconditions.checkNotNull(clazz, "cannot unpack non-xDS message type"); try { return getResourceName(anyResource.unpack(clazz)); } catch (InvalidProtocolBufferException e) { throw new RuntimeException(e); } }
private Response createResponse(DiscoveryRequest request, Map<String, ? extends Message> resources, String version) { Collection<? extends Message> filtered = request.getResourceNamesList().isEmpty() ? resources.values() : request.getResourceNamesList().stream() .map(resources::get) .filter(Objects::nonNull) .collect(Collectors.toList()); return Response.create(request, filtered, version); }
@VisibleForTesting synchronized void deleteUnreferenced(Clock clock) { // Keep track of snapshots to delete to avoid CME. Set<T> toDelete = new LinkedHashSet<>(); for (Map.Entry<T, SnapshotState> entry : snapshotStates.entrySet()) { if (entry.getValue().streamCount == 0 && entry.getValue().lastSeen.isBefore( clock.instant().minus(collectAfterMillis, ChronoUnit.MILLIS))) { // clearSnapshot will do nothing and return false if there are any pending watches - this // ensures that we don't actually remove a snapshot that's in use. T groupIdentifier = entry.getKey(); if (snapshotCache.clearSnapshot(groupIdentifier)) { toDelete.add(groupIdentifier); } } } toDelete.forEach(group -> { snapshotStates.remove(group); collectorCallbacks.forEach(cb -> cb.accept(group)); }); }