All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.envoyproxy.controlplane.server.DiscoveryRequestStreamObserver Maven / Gradle / Ivy

package io.envoyproxy.controlplane.server;

import static com.google.common.base.Strings.isNullOrEmpty;

import com.google.protobuf.Any;
import io.envoyproxy.controlplane.cache.Resources;
import io.envoyproxy.controlplane.cache.Response;
import io.envoyproxy.controlplane.cache.Watch;
import io.envoyproxy.controlplane.server.exception.RequestException;
import io.envoyproxy.envoy.api.v2.DiscoveryRequest;
import io.envoyproxy.envoy.api.v2.DiscoveryResponse;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.stub.StreamObserver;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * {@code DiscoveryRequestStreamObserver} provides the base implementation for XDS stream handling.
 */
public abstract class DiscoveryRequestStreamObserver implements StreamObserver {
  private static final AtomicLongFieldUpdater streamNonceUpdater =
      AtomicLongFieldUpdater.newUpdater(DiscoveryRequestStreamObserver.class, "streamNonce");
  private static final Logger LOGGER = LoggerFactory.getLogger(DiscoveryServer.class);

  final long streamId;
  private final String defaultTypeUrl;
  private final StreamObserver responseObserver;
  private final Executor executor;
  private final DiscoveryServer discoverySever;
  private volatile long streamNonce;
  private volatile boolean isClosing;

  DiscoveryRequestStreamObserver(String defaultTypeUrl,
                                 StreamObserver responseObserver,
                                 long streamId,
                                 Executor executor,
                                 DiscoveryServer discoveryServer) {
    this.defaultTypeUrl = defaultTypeUrl;
    this.responseObserver = responseObserver;
    this.streamId = streamId;
    this.executor = executor;
    this.streamNonce = 0;
    this.discoverySever = discoveryServer;
  }

  @Override
  public void onNext(DiscoveryRequest request) {
    String requestTypeUrl = request.getTypeUrl().isEmpty() ? defaultTypeUrl : request.getTypeUrl();
    String nonce = request.getResponseNonce();

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("[{}] request {}[{}] with nonce {} from version {}",
          streamId,
          requestTypeUrl,
          String.join(", ", request.getResourceNamesList()),
          nonce,
          request.getVersionInfo());
    }

    try {
      discoverySever.callbacks.forEach(cb -> cb.onStreamRequest(streamId, request));
    } catch (RequestException e) {
      closeWithError(e);
      return;
    }

    LatestDiscoveryResponse latestDiscoveryResponse = latestResponse(requestTypeUrl);
    String resourceNonce = latestDiscoveryResponse == null ? null : latestDiscoveryResponse.nonce();

    if (isNullOrEmpty(resourceNonce) || resourceNonce.equals(nonce)) {
      if (!request.hasErrorDetail() && latestDiscoveryResponse != null) {
        setAckedResources(requestTypeUrl, latestDiscoveryResponse.resourceNames());
      }

      computeWatch(requestTypeUrl, () -> discoverySever.configWatcher.createWatch(
          ads(),
          request,
          ackedResources(requestTypeUrl),
          r -> executor.execute(() -> send(r, requestTypeUrl))));
    }
  }

  @Override
  public void onError(Throwable t) {
    if (!Status.fromThrowable(t).getCode().equals(Status.CANCELLED.getCode())) {
      LOGGER.error("[{}] stream closed with error", streamId, t);
    }

    try {
      discoverySever.callbacks.forEach(cb -> cb.onStreamCloseWithError(streamId, defaultTypeUrl, t));
      closeWithError(Status.fromThrowable(t).asException());
    } finally {
      cancel();
    }
  }

  @Override
  public void onCompleted() {
    LOGGER.debug("[{}] stream closed", streamId);

    try {
      discoverySever.callbacks.forEach(cb -> cb.onStreamClose(streamId, defaultTypeUrl));
      synchronized (responseObserver) {
        if (!isClosing) {
          isClosing = true;
          responseObserver.onCompleted();
        }
      }
    } finally {
      cancel();
    }
  }

  void onCancelled() {
    LOGGER.info("[{}] stream cancelled", streamId);
    cancel();
  }

  void closeWithError(Throwable exception) {
    synchronized (responseObserver) {
      if (!isClosing) {
        isClosing = true;
        responseObserver.onError(exception);
      }
    }
    cancel();
  }

  private void send(Response response, String typeUrl) {
    String nonce = Long.toString(streamNonceUpdater.getAndIncrement(this));

    Collection resources = discoverySever.protoResourcesSerializer.serialize(response.resources());
    DiscoveryResponse discoveryResponse = DiscoveryResponse.newBuilder()
        .setVersionInfo(response.version())
        .addAllResources(resources)
        .setTypeUrl(typeUrl)
        .setNonce(nonce)
        .build();

    LOGGER.debug("[{}] response {} with nonce {} version {}", streamId, typeUrl, nonce, response.version());

    discoverySever.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.
    setLatestResponse(
        typeUrl,
        LatestDiscoveryResponse.create(
            nonce,
            response.resources().stream().map(Resources::getResourceName).collect(Collectors.toSet())
        )
    );
    synchronized (responseObserver) {
      if (!isClosing) {
        try {
          responseObserver.onNext(discoveryResponse);
        } catch (StatusRuntimeException e) {
          if (!Status.CANCELLED.getCode().equals(e.getStatus().getCode())) {
            throw e;
          }
        }
      }
    }
  }

  abstract void cancel();

  abstract boolean ads();

  abstract LatestDiscoveryResponse latestResponse(String typeUrl);

  abstract void setLatestResponse(String typeUrl, LatestDiscoveryResponse response);

  abstract Set ackedResources(String typeUrl);

  abstract void setAckedResources(String typeUrl, Set resources);

  abstract void computeWatch(String typeUrl, Supplier watchCreator);
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy