• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #20337

26 Jun 2026 11:37AM UTC coverage: 89.06% (+0.2%) from 88.876%
#20337

push

github

web-flow
Ext_proc filter and client interceptor (#12792)

Implements ext_proc filter from [grfc A93](https://github.com/grpc/proposal/pull/484)  (internal [design doc](http://go/ext-proc-design-java)).

37585 of 42202 relevant lines covered (89.06%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.08
/../xds/src/main/java/io/grpc/xds/XdsServerWrapper.java
1
/*
2
 * Copyright 2021 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc.xds;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20
import static com.google.common.base.Preconditions.checkState;
21
import static io.grpc.xds.client.Bootstrapper.XDSTP_SCHEME;
22

23
import com.google.auto.value.AutoValue;
24
import com.google.common.annotations.VisibleForTesting;
25
import com.google.common.collect.ImmutableList;
26
import com.google.common.collect.ImmutableMap;
27
import com.google.common.net.HostAndPort;
28
import com.google.common.net.InetAddresses;
29
import com.google.common.util.concurrent.SettableFuture;
30
import io.envoyproxy.envoy.config.core.v3.SocketAddress.Protocol;
31
import io.grpc.Attributes;
32
import io.grpc.InternalServerInterceptors;
33
import io.grpc.Metadata;
34
import io.grpc.MethodDescriptor;
35
import io.grpc.MetricRecorder;
36
import io.grpc.Server;
37
import io.grpc.ServerBuilder;
38
import io.grpc.ServerCall;
39
import io.grpc.ServerCall.Listener;
40
import io.grpc.ServerCallHandler;
41
import io.grpc.ServerInterceptor;
42
import io.grpc.ServerServiceDefinition;
43
import io.grpc.Status;
44
import io.grpc.StatusException;
45
import io.grpc.StatusOr;
46
import io.grpc.SynchronizationContext;
47
import io.grpc.SynchronizationContext.ScheduledHandle;
48
import io.grpc.internal.GrpcUtil;
49
import io.grpc.internal.ObjectPool;
50
import io.grpc.internal.SharedResourceHolder;
51
import io.grpc.xds.EnvoyServerProtoData.FilterChain;
52
import io.grpc.xds.Filter.FilterConfig;
53
import io.grpc.xds.Filter.FilterContext;
54
import io.grpc.xds.Filter.NamedFilterConfig;
55
import io.grpc.xds.FilterChainMatchingProtocolNegotiators.FilterChainMatchingHandler.FilterChainSelector;
56
import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl;
57
import io.grpc.xds.VirtualHost.Route;
58
import io.grpc.xds.XdsListenerResource.LdsUpdate;
59
import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate;
60
import io.grpc.xds.XdsServerBuilder.XdsServingStatusListener;
61
import io.grpc.xds.client.Bootstrapper.BootstrapInfo;
62
import io.grpc.xds.client.XdsClient;
63
import io.grpc.xds.client.XdsClient.ResourceWatcher;
64
import io.grpc.xds.internal.security.SslContextProviderSupplier;
65
import java.io.IOException;
66
import java.net.InetAddress;
67
import java.net.SocketAddress;
68
import java.util.ArrayList;
69
import java.util.HashMap;
70
import java.util.HashSet;
71
import java.util.List;
72
import java.util.Map;
73
import java.util.Set;
74
import java.util.concurrent.CountDownLatch;
75
import java.util.concurrent.ExecutionException;
76
import java.util.concurrent.ScheduledExecutorService;
77
import java.util.concurrent.TimeUnit;
78
import java.util.concurrent.atomic.AtomicBoolean;
79
import java.util.concurrent.atomic.AtomicReference;
80
import java.util.logging.Level;
81
import java.util.logging.Logger;
82
import javax.annotation.Nullable;
83

84
final class XdsServerWrapper extends Server {
85
  private static final Logger logger = Logger.getLogger(XdsServerWrapper.class.getName());
1✔
86

87
  private final SynchronizationContext syncContext = new SynchronizationContext(
1✔
88
      new Thread.UncaughtExceptionHandler() {
1✔
89
        @Override
90
        public void uncaughtException(Thread t, Throwable e) {
91
          logger.log(Level.SEVERE, "Exception!" + e);
×
92
          // TODO(chengyuanzhang): implement cleanup.
93
        }
×
94
      });
95

96
  public static final Attributes.Key<AtomicReference<ServerRoutingConfig>>
97
      ATTR_SERVER_ROUTING_CONFIG =
1✔
98
      Attributes.Key.create("io.grpc.xds.ServerWrapper.serverRoutingConfig");
1✔
99

100
  @VisibleForTesting
101
  static final long RETRY_DELAY_NANOS = TimeUnit.MINUTES.toNanos(1);
1✔
102
  private final String listenerAddress;
103
  private final ServerBuilder<?> delegateBuilder;
104
  private boolean sharedTimeService;
105
  private final ScheduledExecutorService timeService;
106
  private final FilterRegistry filterRegistry;
107
  private final ThreadSafeRandom random = ThreadSafeRandomImpl.instance;
1✔
108
  private final XdsClientPoolFactory xdsClientPoolFactory;
109
  private final @Nullable Map<String, ?> bootstrapOverride;
110
  private final XdsServingStatusListener listener;
111
  private final FilterChainSelectorManager filterChainSelectorManager;
112
  private final AtomicBoolean started = new AtomicBoolean(false);
1✔
113
  private final AtomicBoolean shutdown = new AtomicBoolean(false);
1✔
114
  private boolean isServing;
115
  private final CountDownLatch internalTerminationLatch = new CountDownLatch(1);
1✔
116
  private final SettableFuture<Exception> initialStartFuture = SettableFuture.create();
1✔
117
  private boolean initialStarted;
118
  private ScheduledHandle restartTimer;
119
  private ObjectPool<XdsClient> xdsClientPool;
120
  private XdsClient xdsClient;
121
  private DiscoveryState discoveryState;
122
  private volatile Server delegate;
123

124
  // Must be accessed in syncContext.
125
  // Filter instances are unique per Server, per FilterChain, and per filter's name+typeUrl.
126
  // FilterChain.name -> <NamedFilterConfig.filterStateKey -> filter_instance>.
127
  private final HashMap<String, HashMap<String, Filter>> activeFilters = new HashMap<>();
1✔
128
  // Default filter chain Filter instances are unique per Server, and per filter's name+typeUrl.
129
  // NamedFilterConfig.filterStateKey -> filter_instance.
130
  private final HashMap<String, Filter> activeFiltersDefaultChain = new HashMap<>();
1✔
131

132
  XdsServerWrapper(
133
      String listenerAddress,
134
      ServerBuilder<?> delegateBuilder,
135
      XdsServingStatusListener listener,
136
      FilterChainSelectorManager filterChainSelectorManager,
137
      XdsClientPoolFactory xdsClientPoolFactory,
138
      @Nullable Map<String, ?> bootstrapOverride,
139
      FilterRegistry filterRegistry) {
140
    this(
1✔
141
        listenerAddress,
142
        delegateBuilder,
143
        listener,
144
        filterChainSelectorManager,
145
        xdsClientPoolFactory,
146
        bootstrapOverride,
147
        filterRegistry,
148
        SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE));
1✔
149
    sharedTimeService = true;
1✔
150
  }
1✔
151

152
  @VisibleForTesting
153
  XdsServerWrapper(
154
          String listenerAddress,
155
          ServerBuilder<?> delegateBuilder,
156
          XdsServingStatusListener listener,
157
          FilterChainSelectorManager filterChainSelectorManager,
158
          XdsClientPoolFactory xdsClientPoolFactory,
159
          @Nullable Map<String, ?> bootstrapOverride,
160
          FilterRegistry filterRegistry,
161
          ScheduledExecutorService timeService) {
1✔
162
    this.listenerAddress = checkNotNull(listenerAddress, "listenerAddress");
1✔
163
    this.delegateBuilder = checkNotNull(delegateBuilder, "delegateBuilder");
1✔
164
    this.delegateBuilder.intercept(new ConfigApplyingInterceptor());
1✔
165
    this.listener = checkNotNull(listener, "listener");
1✔
166
    this.filterChainSelectorManager
1✔
167
        = checkNotNull(filterChainSelectorManager, "filterChainSelectorManager");
1✔
168
    this.xdsClientPoolFactory = checkNotNull(xdsClientPoolFactory, "xdsClientPoolFactory");
1✔
169
    this.bootstrapOverride = bootstrapOverride;
1✔
170
    this.timeService = checkNotNull(timeService, "timeService");
1✔
171
    this.filterRegistry = checkNotNull(filterRegistry,"filterRegistry");
1✔
172
    this.delegate = delegateBuilder.build();
1✔
173
  }
1✔
174

175
  @Override
176
  public Server start() throws IOException {
177
    checkState(started.compareAndSet(false, true), "Already started");
1✔
178
    syncContext.execute(new Runnable() {
1✔
179
      @Override
180
      public void run() {
181
        internalStart();
1✔
182
      }
1✔
183
    });
184
    Exception exception;
185
    try {
186
      exception = initialStartFuture.get();
1✔
187
    } catch (InterruptedException | ExecutionException e) {
×
188
      throw new RuntimeException(e);
×
189
    }
1✔
190
    if (exception != null) {
1✔
191
      throw (exception instanceof IOException) ? (IOException) exception :
1✔
192
              new IOException(exception);
1✔
193
    }
194
    return this;
1✔
195
  }
196

197
  private void internalStart() {
198
    try {
199
      BootstrapInfo bootstrapInfo;
200
      if (bootstrapOverride == null) {
1✔
201
        bootstrapInfo = GrpcBootstrapperImpl.defaultBootstrap();
×
202
      } else {
203
        bootstrapInfo = new GrpcBootstrapperImpl().bootstrap(bootstrapOverride);
1✔
204
      }
205
      xdsClientPool = xdsClientPoolFactory.getOrCreate(
1✔
206
          "#server", bootstrapInfo, new MetricRecorder() {});
1✔
207
    } catch (Exception e) {
×
208
      StatusException statusException = Status.UNAVAILABLE.withDescription(
×
209
              "Failed to initialize xDS").withCause(e).asException();
×
210
      listener.onNotServing(statusException);
×
211
      initialStartFuture.set(statusException);
×
212
      return;
×
213
    }
1✔
214
    xdsClient = xdsClientPool.getObject();
1✔
215
    String listenerTemplate = xdsClient.getBootstrapInfo().serverListenerResourceNameTemplate();
1✔
216
    if (listenerTemplate == null) {
1✔
217
      StatusException statusException =
1✔
218
          Status.UNAVAILABLE.withDescription(
1✔
219
              "Can only support xDS v3 with listener resource name template").asException();
1✔
220
      listener.onNotServing(statusException);
1✔
221
      initialStartFuture.set(statusException);
1✔
222
      xdsClient = xdsClientPool.returnObject(xdsClient);
1✔
223
      return;
1✔
224
    }
225
    String replacement = listenerAddress;
1✔
226
    if (listenerTemplate.startsWith(XDSTP_SCHEME)) {
1✔
227
      replacement = XdsClient.percentEncodePath(replacement);
1✔
228
    }
229
    discoveryState = new DiscoveryState(listenerTemplate.replaceAll("%s", replacement));
1✔
230
  }
1✔
231

232
  @Override
233
  public Server shutdown() {
234
    if (!shutdown.compareAndSet(false, true)) {
1✔
235
      return this;
1✔
236
    }
237
    syncContext.execute(new Runnable() {
1✔
238
      @Override
239
      public void run() {
240
        if (!delegate.isShutdown()) {
1✔
241
          delegate.shutdown();
1✔
242
        }
243
        internalShutdown();
1✔
244
      }
1✔
245
    });
246
    return this;
1✔
247
  }
248

249
  @Override
250
  public Server shutdownNow() {
251
    if (!shutdown.compareAndSet(false, true)) {
1✔
252
      return this;
1✔
253
    }
254
    syncContext.execute(new Runnable() {
1✔
255
      @Override
256
      public void run() {
257
        if (!delegate.isShutdown()) {
1✔
258
          delegate.shutdownNow();
1✔
259
        }
260
        internalShutdown();
1✔
261
        initialStartFuture.set(new IOException("server is forcefully shut down"));
1✔
262
      }
1✔
263
    });
264
    return this;
1✔
265
  }
266

267
  // Must run in SynchronizationContext
268
  private void internalShutdown() {
269
    logger.log(Level.FINER, "Shutting down XdsServerWrapper");
1✔
270
    if (discoveryState != null) {
1✔
271
      discoveryState.shutdown();
1✔
272
    }
273
    if (xdsClient != null) {
1✔
274
      xdsClient = xdsClientPool.returnObject(xdsClient);
1✔
275
    }
276
    if (restartTimer != null) {
1✔
277
      restartTimer.cancel();
1✔
278
    }
279
    if (sharedTimeService) {
1✔
280
      SharedResourceHolder.release(GrpcUtil.TIMER_SERVICE, timeService);
1✔
281
    }
282
    isServing = false;
1✔
283
    internalTerminationLatch.countDown();
1✔
284
  }
1✔
285

286
  @Override
287
  public boolean isShutdown() {
288
    return shutdown.get();
1✔
289
  }
290

291
  @Override
292
  public boolean isTerminated() {
293
    return internalTerminationLatch.getCount() == 0 && delegate.isTerminated();
1✔
294
  }
295

296
  @Override
297
  public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
298
    long startTime = System.nanoTime();
1✔
299
    if (!internalTerminationLatch.await(timeout, unit)) {
1✔
300
      return false;
×
301
    }
302
    long remainingTime = unit.toNanos(timeout) - (System.nanoTime() - startTime);
1✔
303
    return delegate.awaitTermination(remainingTime, TimeUnit.NANOSECONDS);
1✔
304
  }
305

306
  @Override
307
  public void awaitTermination() throws InterruptedException {
308
    internalTerminationLatch.await();
1✔
309
    delegate.awaitTermination();
1✔
310
  }
1✔
311

312
  @Override
313
  public int getPort() {
314
    return delegate.getPort();
1✔
315
  }
316

317
  @Override
318
  public List<? extends SocketAddress> getListenSockets() {
319
    return delegate.getListenSockets();
1✔
320
  }
321

322
  @Override
323
  public List<ServerServiceDefinition> getServices() {
324
    return delegate.getServices();
×
325
  }
326

327
  @Override
328
  public List<ServerServiceDefinition> getImmutableServices() {
329
    return delegate.getImmutableServices();
×
330
  }
331

332
  @Override
333
  public List<ServerServiceDefinition> getMutableServices() {
334
    return delegate.getMutableServices();
×
335
  }
336

337
  // Must run in SynchronizationContext
338
  private void startDelegateServer() {
339
    if (restartTimer != null && restartTimer.isPending()) {
1✔
340
      return;
×
341
    }
342
    if (isServing) {
1✔
343
      return;
1✔
344
    }
345
    if (delegate.isShutdown()) {
1✔
346
      delegate = delegateBuilder.build();
1✔
347
    }
348
    try {
349
      delegate.start();
1✔
350
      listener.onServing();
1✔
351
      isServing = true;
1✔
352
      if (!initialStarted) {
1✔
353
        initialStarted = true;
1✔
354
        initialStartFuture.set(null);
1✔
355
      }
356
      logger.log(Level.FINER, "Delegate server started.");
1✔
357
    } catch (IOException e) {
1✔
358
      logger.log(Level.FINE, "Fail to start delegate server: {0}", e);
1✔
359
      if (!initialStarted) {
1✔
360
        initialStarted = true;
1✔
361
        initialStartFuture.set(e);
1✔
362
      } else {
363
        listener.onNotServing(e);
1✔
364
      }
365
      restartTimer = syncContext.schedule(
1✔
366
        new RestartTask(), RETRY_DELAY_NANOS, TimeUnit.NANOSECONDS, timeService);
367
    }
1✔
368
  }
1✔
369

370
  private final class RestartTask implements Runnable {
1✔
371
    @Override
372
    public void run() {
373
      startDelegateServer();
1✔
374
    }
1✔
375
  }
376

377
  private final class DiscoveryState implements ResourceWatcher<LdsUpdate> {
378
    private final String resourceName;
379
    // RDS resource name is the key.
380
    private final Map<String, RouteDiscoveryState> routeDiscoveryStates = new HashMap<>();
1✔
381
    // Track pending RDS resources using rds name.
382
    private final Set<String> pendingRds = new HashSet<>();
1✔
383
    // Most recently discovered filter chains.
384
    private List<FilterChain> filterChains = new ArrayList<>();
1✔
385
    // Most recently discovered default filter chain.
386
    @Nullable
387
    private FilterChain defaultFilterChain;
388
    private boolean stopped;
389
    private final Map<FilterChain, AtomicReference<ServerRoutingConfig>> savedRdsRoutingConfigRef 
1✔
390
        = new HashMap<>();
391
    private final ServerInterceptor noopInterceptor = new ServerInterceptor() {
1✔
392
      @Override
393
      public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,
394
          Metadata headers, ServerCallHandler<ReqT, RespT> next) {
395
        return next.startCall(call, headers);
1✔
396
      }
397
    };
398

399
    private DiscoveryState(String resourceName) {
1✔
400
      this.resourceName = checkNotNull(resourceName, "resourceName");
1✔
401
      xdsClient.watchXdsResource(
1✔
402
          XdsListenerResource.getInstance(), resourceName, this, syncContext);
1✔
403
    }
1✔
404

405
    @Override
406
    public void onResourceChanged(final StatusOr<LdsUpdate> update) {
407
      if (stopped) {
1✔
408
        return;
×
409
      }
410

411
      if (!update.hasValue()) {
1✔
412
        Status status = update.getStatus();
1✔
413
        StatusException statusException = Status.UNAVAILABLE.withDescription(
1✔
414
                String.format("Listener %s unavailable: %s", resourceName, status.getDescription()))
1✔
415
            .withCause(status.asException())
1✔
416
            .asException();
1✔
417
        handleConfigNotFoundOrMismatch(statusException);
1✔
418
        return;
1✔
419
      }
420

421
      final LdsUpdate ldsUpdate = update.getValue();
1✔
422
      logger.log(Level.FINEST, "Received Lds update {0}", ldsUpdate);
1✔
423
      if (ldsUpdate.listener() == null) {
1✔
424
        handleConfigNotFoundOrMismatch(
1✔
425
            Status.NOT_FOUND.withDescription("Listener is null in LdsUpdate").asException());
1✔
426
        return;
1✔
427
      }
428
      String ldsAddress = ldsUpdate.listener().address();
1✔
429
      if (ldsAddress == null || ldsUpdate.listener().protocol() != Protocol.TCP
1✔
430
          || !ipAddressesMatch(ldsAddress)) {
1✔
431
        handleConfigNotFoundOrMismatch(
1✔
432
            Status.UNKNOWN.withDescription(
1✔
433
                String.format(
1✔
434
                    "Listener address mismatch: expected %s, but got %s.",
435
                    listenerAddress, ldsAddress)).asException());
1✔
436
        return;
1✔
437
      }
438

439
      if (!pendingRds.isEmpty()) {
1✔
440
        // filter chain state has not yet been applied to filterChainSelectorManager and there
441
        releaseSuppliersInFlight();
1✔
442
        pendingRds.clear();
1✔
443
      }
444

445
      filterChains = ldsUpdate.listener().filterChains();
1✔
446
      defaultFilterChain = ldsUpdate.listener().defaultFilterChain();
1✔
447
      updateActiveFilters();
1✔
448

449
      List<FilterChain> allFilterChains = filterChains;
1✔
450
      if (defaultFilterChain != null) {
1✔
451
        allFilterChains = new ArrayList<>(filterChains);
1✔
452
        allFilterChains.add(defaultFilterChain);
1✔
453
      }
454

455
      Set<String> allRds = new HashSet<>();
1✔
456
      for (FilterChain filterChain : allFilterChains) {
1✔
457
        HttpConnectionManager hcm = filterChain.httpConnectionManager();
1✔
458
        if (hcm.virtualHosts() == null) {
1✔
459
          RouteDiscoveryState rdsState = routeDiscoveryStates.get(hcm.rdsName());
1✔
460
          if (rdsState == null) {
1✔
461
            rdsState = new RouteDiscoveryState(hcm.rdsName());
1✔
462
            routeDiscoveryStates.put(hcm.rdsName(), rdsState);
1✔
463
            xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),
1✔
464
                hcm.rdsName(), rdsState, syncContext);
1✔
465
          }
466
          if (rdsState.isPending) {
1✔
467
            pendingRds.add(hcm.rdsName());
1✔
468
          }
469
          allRds.add(hcm.rdsName());
1✔
470
        }
471
      }
1✔
472

473
      for (Map.Entry<String, RouteDiscoveryState> entry: routeDiscoveryStates.entrySet()) {
1✔
474
        if (!allRds.contains(entry.getKey())) {
1✔
475
          xdsClient.cancelXdsResourceWatch(XdsRouteConfigureResource.getInstance(),
1✔
476
              entry.getKey(), entry.getValue());
1✔
477
        }
478
      }
1✔
479
      routeDiscoveryStates.keySet().retainAll(allRds);
1✔
480
      if (pendingRds.isEmpty()) {
1✔
481
        updateSelector();
1✔
482
      }
483
    }
1✔
484

485
    @Override
486
    public void onAmbientError(final Status error) {
487
      if (stopped) {
1✔
488
        return;
×
489
      }
490
      String description = error.getDescription() == null ? "" : error.getDescription() + " ";
1✔
491
      Status errorWithNodeId = error.withDescription(
1✔
492
          description + "xDS node ID: " + xdsClient.getBootstrapInfo().node().getId());
1✔
493
      logger.log(Level.FINE, "Error from XdsClient", errorWithNodeId);
1✔
494

495
      if (!isServing) {
1✔
496
        listener.onNotServing(errorWithNodeId.asException());
×
497
      }
498
    }
1✔
499

500
    private boolean ipAddressesMatch(String ldsAddress) {
501
      HostAndPort ldsAddressHnP = HostAndPort.fromString(ldsAddress);
1✔
502
      HostAndPort listenerAddressHnP = HostAndPort.fromString(listenerAddress);
1✔
503
      if (!ldsAddressHnP.hasPort() || !listenerAddressHnP.hasPort()
1✔
504
          || ldsAddressHnP.getPort() != listenerAddressHnP.getPort()) {
1✔
505
        return false;
1✔
506
      }
507
      InetAddress listenerIp = InetAddresses.forString(listenerAddressHnP.getHost());
1✔
508
      InetAddress ldsIp = InetAddresses.forString(ldsAddressHnP.getHost());
1✔
509
      return listenerIp.equals(ldsIp);
1✔
510
    }
511

512
    private void shutdown() {
513
      stopped = true;
1✔
514
      cleanUpRouteDiscoveryStates();
1✔
515
      logger.log(Level.FINE, "Stop watching LDS resource {0}", resourceName);
1✔
516
      xdsClient.cancelXdsResourceWatch(XdsListenerResource.getInstance(), resourceName, this);
1✔
517
      shutdownActiveFilters();
1✔
518
      List<SslContextProviderSupplier> toRelease = getSuppliersInUse();
1✔
519
      filterChainSelectorManager.updateSelector(FilterChainSelector.NO_FILTER_CHAIN);
1✔
520
      for (SslContextProviderSupplier s: toRelease) {
1✔
521
        s.close();
1✔
522
      }
1✔
523
      releaseSuppliersInFlight();
1✔
524
    }
1✔
525

526
    private void updateSelector() {
527
      // This is regenerated in generateRoutingConfig() calls below.
528
      savedRdsRoutingConfigRef.clear();
1✔
529

530
      // Prepare server routing config map.
531
      ImmutableMap.Builder<FilterChain, AtomicReference<ServerRoutingConfig>> routingConfigs =
532
          ImmutableMap.builder();
1✔
533
      for (FilterChain filterChain: filterChains) {
1✔
534
        HashMap<String, Filter> chainFilters = activeFilters.get(filterChain.name());
1✔
535
        routingConfigs.put(filterChain, generateRoutingConfig(filterChain, chainFilters));
1✔
536
      }
1✔
537

538
      // Prepare the new selector.
539
      FilterChainSelector selector;
540
      if (defaultFilterChain != null) {
1✔
541
        selector = new FilterChainSelector(
1✔
542
            routingConfigs.build(),
1✔
543
            defaultFilterChain.sslContextProviderSupplier(),
1✔
544
            generateRoutingConfig(defaultFilterChain, activeFiltersDefaultChain));
1✔
545
      } else {
546
        selector = new FilterChainSelector(routingConfigs.build());
1✔
547
      }
548

549
      // Prepare the list of current selector's resources to close later.
550
      List<SslContextProviderSupplier> oldSslSuppliers = getSuppliersInUse();
1✔
551

552
      // Swap the selectors, initiate a graceful shutdown of the old one.
553
      logger.log(Level.FINEST, "Updating selector {0}", selector);
1✔
554
      filterChainSelectorManager.updateSelector(selector);
1✔
555

556
      // Release old resources.
557
      for (SslContextProviderSupplier supplier: oldSslSuppliers) {
1✔
558
        supplier.close();
1✔
559
      }
1✔
560

561
      // Now that we have valid Transport Socket config, we can start/restart listening on a port.
562
      startDelegateServer();
1✔
563
    }
1✔
564

565
    // called in syncContext
566
    private void updateActiveFilters() {
567
      Set<String> removedChains = new HashSet<>(activeFilters.keySet());
1✔
568
      for (FilterChain filterChain: filterChains) {
1✔
569
        removedChains.remove(filterChain.name());
1✔
570
        updateActiveFiltersForChain(
1✔
571
            activeFilters.computeIfAbsent(filterChain.name(), k -> new HashMap<>()),
1✔
572
            filterChain.httpConnectionManager().httpFilterConfigs());
1✔
573
      }
1✔
574

575
      // Shutdown all filters of chains missing from the LDS.
576
      for (String chainToShutdown : removedChains) {
1✔
577
        HashMap<String, Filter> filtersToShutdown = activeFilters.get(chainToShutdown);
1✔
578
        checkNotNull(filtersToShutdown, "filtersToShutdown of chain %s", chainToShutdown);
1✔
579
        updateActiveFiltersForChain(filtersToShutdown, null);
1✔
580
        activeFilters.remove(chainToShutdown);
1✔
581
      }
1✔
582

583
      // Default chain.
584
      ImmutableList<NamedFilterConfig> defaultChainConfigs = null;
1✔
585
      if (defaultFilterChain != null) {
1✔
586
        defaultChainConfigs = defaultFilterChain.httpConnectionManager().httpFilterConfigs();
1✔
587
      }
588
      updateActiveFiltersForChain(activeFiltersDefaultChain, defaultChainConfigs);
1✔
589
    }
1✔
590

591
    // called in syncContext
592
    private void shutdownActiveFilters() {
593
      for (HashMap<String, Filter> chainFilters : activeFilters.values()) {
1✔
594
        checkNotNull(chainFilters, "chainFilters");
1✔
595
        updateActiveFiltersForChain(chainFilters, null);
1✔
596
      }
1✔
597
      activeFilters.clear();
1✔
598
      updateActiveFiltersForChain(activeFiltersDefaultChain, null);
1✔
599
    }
1✔
600

601
    // called in syncContext
602
    private void updateActiveFiltersForChain(
603
        Map<String, Filter> chainFilters, @Nullable List<NamedFilterConfig> filterConfigs) {
604
      if (filterConfigs == null) {
1✔
605
        filterConfigs = ImmutableList.of();
1✔
606
      }
607

608
      Set<String> filtersToShutdown = new HashSet<>(chainFilters.keySet());
1✔
609
      for (NamedFilterConfig namedFilter : filterConfigs) {
1✔
610
        String typeUrl = namedFilter.filterConfig.typeUrl();
1✔
611
        String filterKey = namedFilter.filterStateKey();
1✔
612

613
        Filter.Provider provider = filterRegistry.get(typeUrl);
1✔
614
        checkNotNull(provider, "provider %s", typeUrl);
1✔
615
        Filter filter = chainFilters.computeIfAbsent(
1✔
616
            filterKey, k -> provider.newInstance(
1✔
617
                FilterContext.create(namedFilter.name, new MetricRecorder() {})));
1✔
618
        checkNotNull(filter, "filter %s", filterKey);
1✔
619
        filtersToShutdown.remove(filterKey);
1✔
620
      }
1✔
621

622
      // Shutdown filters not present in current HCM.
623
      for (String filterKey : filtersToShutdown) {
1✔
624
        Filter filterToShutdown = chainFilters.remove(filterKey);
1✔
625
        checkNotNull(filterToShutdown, "filterToShutdown %s", filterKey);
1✔
626
        filterToShutdown.close();
1✔
627
      }
1✔
628
    }
1✔
629

630
    private AtomicReference<ServerRoutingConfig> generateRoutingConfig(
631
        FilterChain filterChain, Map<String, Filter> chainFilters) {
632
      HttpConnectionManager hcm = filterChain.httpConnectionManager();
1✔
633
      ServerRoutingConfig routingConfig;
634

635
      // Inlined routes.
636
      ImmutableList<VirtualHost> vhosts = hcm.virtualHosts();
1✔
637
      if (vhosts != null) {
1✔
638
        routingConfig = ServerRoutingConfig.create(vhosts,
1✔
639
            generatePerRouteInterceptors(hcm.httpFilterConfigs(), vhosts, chainFilters));
1✔
640
        return new AtomicReference<>(routingConfig);
1✔
641
      }
642

643
      // Routes from RDS.
644
      RouteDiscoveryState rds = routeDiscoveryStates.get(hcm.rdsName());
1✔
645
      checkNotNull(rds, "rds");
1✔
646

647
      ImmutableList<VirtualHost> savedVhosts = rds.savedVirtualHosts;
1✔
648
      if (savedVhosts != null) {
1✔
649
        routingConfig = ServerRoutingConfig.create(savedVhosts,
1✔
650
            generatePerRouteInterceptors(hcm.httpFilterConfigs(), savedVhosts, chainFilters));
1✔
651
      } else {
652
        routingConfig = ServerRoutingConfig.FAILING_ROUTING_CONFIG;
1✔
653
      }
654
      AtomicReference<ServerRoutingConfig> routingConfigRef = new AtomicReference<>(routingConfig);
1✔
655
      savedRdsRoutingConfigRef.put(filterChain, routingConfigRef);
1✔
656
      return routingConfigRef;
1✔
657
    }
658

659
    private ImmutableMap<Route, ServerInterceptor> generatePerRouteInterceptors(
660
        @Nullable List<NamedFilterConfig> filterConfigs,
661
        List<VirtualHost> virtualHosts,
662
        Map<String, Filter> chainFilters) {
663
      syncContext.throwIfNotInThisSynchronizationContext();
1✔
664

665
      checkNotNull(chainFilters, "chainFilters");
1✔
666
      ImmutableMap.Builder<Route, ServerInterceptor> perRouteInterceptors =
1✔
667
          new ImmutableMap.Builder<>();
668

669
      for (VirtualHost virtualHost : virtualHosts) {
1✔
670
        for (Route route : virtualHost.routes()) {
1✔
671
          // Short circuit.
672
          if (filterConfigs == null) {
1✔
673
            perRouteInterceptors.put(route, noopInterceptor);
×
674
            continue;
×
675
          }
676

677
          // Override vhost filter configs with more specific per-route configs.
678
          Map<String, FilterConfig> perRouteOverrides = ImmutableMap.<String, FilterConfig>builder()
1✔
679
              .putAll(virtualHost.filterConfigOverrides())
1✔
680
              .putAll(route.filterConfigOverrides())
1✔
681
              .buildKeepingLast();
1✔
682

683
          // Interceptors for this vhost/route combo.
684
          List<ServerInterceptor> interceptors = new ArrayList<>(filterConfigs.size());
1✔
685
          for (NamedFilterConfig namedFilter : filterConfigs) {
1✔
686
            String name = namedFilter.name;
1✔
687
            FilterConfig config = namedFilter.filterConfig;
1✔
688
            FilterConfig overrideConfig = perRouteOverrides.get(name);
1✔
689
            String filterKey = namedFilter.filterStateKey();
1✔
690

691
            Filter filter = chainFilters.get(filterKey);
1✔
692
            checkNotNull(filter, "chainFilters.get(%s)", filterKey);
1✔
693
            ServerInterceptor interceptor = filter.buildServerInterceptor(config, overrideConfig);
1✔
694

695
            if (interceptor != null) {
1✔
696
              interceptors.add(interceptor);
1✔
697
            }
698
          }
1✔
699

700
          // Combine interceptors produced by different filters into a single one that executes
701
          // them sequentially. The order is preserved.
702
          perRouteInterceptors.put(route, combineInterceptors(interceptors));
1✔
703
        }
1✔
704
      }
1✔
705

706
      return perRouteInterceptors.buildOrThrow();
1✔
707
    }
708

709
    private ServerInterceptor combineInterceptors(final List<ServerInterceptor> interceptors) {
710
      if (interceptors.isEmpty()) {
1✔
711
        return noopInterceptor;
1✔
712
      }
713
      if (interceptors.size() == 1) {
1✔
714
        return interceptors.get(0);
×
715
      }
716
      return new ServerInterceptor() {
1✔
717
        @Override
718
        public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,
719
            Metadata headers, ServerCallHandler<ReqT, RespT> next) {
720
          // intercept forward
721
          for (int i = interceptors.size() - 1; i >= 0; i--) {
1✔
722
            next = InternalServerInterceptors.interceptCallHandlerCreate(
1✔
723
                interceptors.get(i), next);
1✔
724
          }
725
          return next.startCall(call, headers);
1✔
726
        }
727
      };
728
    }
729

730
    private void handleConfigNotFoundOrMismatch(StatusException exception) {
731
      cleanUpRouteDiscoveryStates();
1✔
732
      shutdownActiveFilters();
1✔
733
      List<SslContextProviderSupplier> toRelease = getSuppliersInUse();
1✔
734
      filterChainSelectorManager.updateSelector(FilterChainSelector.NO_FILTER_CHAIN);
1✔
735
      for (SslContextProviderSupplier s: toRelease) {
1✔
736
        s.close();
1✔
737
      }
1✔
738
      if (restartTimer != null) {
1✔
739
        restartTimer.cancel();
1✔
740
      }
741
      if (!delegate.isShutdown()) {
1✔
742
        delegate.shutdown();  // let in-progress calls finish
1✔
743
      }
744
      isServing = false;
1✔
745
      listener.onNotServing(exception);
1✔
746
    }
1✔
747

748
    private void cleanUpRouteDiscoveryStates() {
749
      for (RouteDiscoveryState rdsState : routeDiscoveryStates.values()) {
1✔
750
        String rdsName = rdsState.resourceName;
1✔
751
        logger.log(Level.FINE, "Stop watching RDS resource {0}", rdsName);
1✔
752
        xdsClient.cancelXdsResourceWatch(XdsRouteConfigureResource.getInstance(), rdsName,
1✔
753
            rdsState);
754
      }
1✔
755
      routeDiscoveryStates.clear();
1✔
756
      savedRdsRoutingConfigRef.clear();
1✔
757
    }
1✔
758

759
    private List<SslContextProviderSupplier> getSuppliersInUse() {
760
      List<SslContextProviderSupplier> toRelease = new ArrayList<>();
1✔
761
      FilterChainSelector selector = filterChainSelectorManager.getSelectorToUpdateSelector();
1✔
762
      if (selector != null) {
1✔
763
        for (FilterChain f: selector.getRoutingConfigs().keySet()) {
1✔
764
          if (f.sslContextProviderSupplier() != null) {
1✔
765
            toRelease.add(f.sslContextProviderSupplier());
1✔
766
          }
767
        }
1✔
768
        SslContextProviderSupplier defaultSupplier =
1✔
769
                selector.getDefaultSslContextProviderSupplier();
1✔
770
        if (defaultSupplier != null) {
1✔
771
          toRelease.add(defaultSupplier);
1✔
772
        }
773
      }
774
      return toRelease;
1✔
775
    }
776

777
    private void releaseSuppliersInFlight() {
778
      SslContextProviderSupplier supplier;
779
      for (FilterChain filterChain : filterChains) {
1✔
780
        supplier = filterChain.sslContextProviderSupplier();
1✔
781
        if (supplier != null) {
1✔
782
          supplier.close();
1✔
783
        }
784
      }
1✔
785
      if (defaultFilterChain != null
1✔
786
              && (supplier = defaultFilterChain.sslContextProviderSupplier()) != null) {
1✔
787
        supplier.close();
1✔
788
      }
789
    }
1✔
790

791
    private final class RouteDiscoveryState implements ResourceWatcher<RdsUpdate> {
792
      private final String resourceName;
793
      private ImmutableList<VirtualHost> savedVirtualHosts;
794
      private boolean isPending = true;
1✔
795

796
      private RouteDiscoveryState(String resourceName) {
1✔
797
        this.resourceName = checkNotNull(resourceName, "resourceName");
1✔
798
      }
1✔
799

800
      @Override
801
      public void onResourceChanged(final StatusOr<RdsUpdate> update) {
802
        syncContext.execute(() -> {
1✔
803
          if (!routeDiscoveryStates.containsKey(resourceName)) {
1✔
804
            return; // Watcher has been cancelled.
1✔
805
          }
806

807
          if (update.hasValue()) {
1✔
808
            if (savedVirtualHosts == null && !isPending) {
1✔
809
              logger.log(Level.WARNING, "Received valid Rds {0} configuration.", resourceName);
1✔
810
            }
811
            savedVirtualHosts = ImmutableList.copyOf(update.getValue().virtualHosts);
1✔
812
          } else {
813
            logger.log(Level.WARNING, "Rds {0} unavailable: {1}",
1✔
814
                new Object[]{resourceName, update.getStatus()});
1✔
815
            savedVirtualHosts = null;
1✔
816
          }
817
          // In both cases, a change has occurred that requires a config update.
818
          updateRdsRoutingConfig();
1✔
819
          maybeUpdateSelector();
1✔
820
        });
1✔
821
      }
1✔
822

823
      @Override
824
      public void onAmbientError(final Status error) {
825
        syncContext.execute(() -> {
1✔
826
          if (!routeDiscoveryStates.containsKey(resourceName)) {
1✔
827
            return; // Watcher has been cancelled.
×
828
          }
829
          String description = error.getDescription() == null ? "" : error.getDescription() + " ";
1✔
830
          Status errorWithNodeId = error.withDescription(
1✔
831
              description + "xDS node ID: " + xdsClient.getBootstrapInfo().node().getId());
1✔
832
          logger.log(Level.WARNING, "Error loading RDS resource {0} from XdsClient: {1}.",
1✔
833
              new Object[]{resourceName, errorWithNodeId});
834

835
          // Per gRFC A88, ambient errors should not trigger a configuration change.
836
          // Therefore, we do NOT call maybeUpdateSelector() here.
837
        });
1✔
838
      }
1✔
839

840
      private void updateRdsRoutingConfig() {
841
        for (FilterChain filterChain : savedRdsRoutingConfigRef.keySet()) {
1✔
842
          HttpConnectionManager hcm = filterChain.httpConnectionManager();
1✔
843
          if (!resourceName.equals(hcm.rdsName())) {
1✔
844
            continue;
1✔
845
          }
846

847
          ServerRoutingConfig updatedRoutingConfig;
848
          if (savedVirtualHosts == null) {
1✔
849
            updatedRoutingConfig = ServerRoutingConfig.FAILING_ROUTING_CONFIG;
1✔
850
          } else {
851
            HashMap<String, Filter> chainFilters = activeFilters.get(filterChain.name());
1✔
852
            ImmutableMap<Route, ServerInterceptor> interceptors = generatePerRouteInterceptors(
1✔
853
                hcm.httpFilterConfigs(), savedVirtualHosts, chainFilters);
1✔
854
            updatedRoutingConfig = ServerRoutingConfig.create(savedVirtualHosts, interceptors);
1✔
855
          }
856

857
          logger.log(Level.FINEST, "Updating filter chain {0} rds routing config: {1}",
1✔
858
              new Object[]{filterChain.name(), updatedRoutingConfig});
1✔
859
          savedRdsRoutingConfigRef.get(filterChain).set(updatedRoutingConfig);
1✔
860
        }
1✔
861
      }
1✔
862

863
      // Update the selector to use the most recently updated configs only after all rds have been
864
      // discovered for the first time. Later changes on rds will be applied through virtual host
865
      // list atomic ref.
866
      private void maybeUpdateSelector() {
867
        isPending = false;
1✔
868
        boolean isLastPending = pendingRds.remove(resourceName) && pendingRds.isEmpty();
1✔
869
        if (isLastPending) {
1✔
870
          updateSelector();
1✔
871
        }
872
      }
1✔
873
    }
874
  }
875

876
  @VisibleForTesting
877
  final class ConfigApplyingInterceptor implements ServerInterceptor {
1✔
878
    private final ServerInterceptor noopInterceptor = new ServerInterceptor() {
1✔
879
      @Override
880
      public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,
881
          Metadata headers, ServerCallHandler<ReqT, RespT> next) {
882
        return next.startCall(call, headers);
×
883
      }
884
    };
885

886
    @Override
887
    public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,
888
        Metadata headers, ServerCallHandler<ReqT, RespT> next) {
889
      AtomicReference<ServerRoutingConfig> routingConfigRef =
1✔
890
          call.getAttributes().get(ATTR_SERVER_ROUTING_CONFIG);
1✔
891
      ServerRoutingConfig routingConfig = routingConfigRef == null ? null :
1✔
892
          routingConfigRef.get();
1✔
893
      if (routingConfig == null || routingConfig == ServerRoutingConfig.FAILING_ROUTING_CONFIG) {
1✔
894
        String errorMsg = "Missing or broken xDS routing config: RDS config unavailable.";
1✔
895
        call.close(Status.UNAVAILABLE.withDescription(errorMsg), new Metadata());
1✔
896
        return new Listener<ReqT>() {};
1✔
897
      }
898
      List<VirtualHost> virtualHosts = routingConfig.virtualHosts();
1✔
899
      VirtualHost virtualHost = RoutingUtils.findVirtualHostForHostName(
1✔
900
          virtualHosts, call.getAuthority());
1✔
901
      if (virtualHost == null) {
1✔
902
        call.close(
1✔
903
            Status.UNAVAILABLE.withDescription("Could not find xDS virtual host matching RPC"),
1✔
904
            new Metadata());
905
        return new Listener<ReqT>() {};
1✔
906
      }
907
      Route selectedRoute = null;
1✔
908
      MethodDescriptor<ReqT, RespT> method = call.getMethodDescriptor();
1✔
909
      for (Route route : virtualHost.routes()) {
1✔
910
        if (RoutingUtils.matchRoute(
1✔
911
            route.routeMatch(), "/" + method.getFullMethodName(), headers, random)) {
1✔
912
          selectedRoute = route;
1✔
913
          break;
1✔
914
        }
915
      }
1✔
916
      if (selectedRoute == null) {
1✔
917
        call.close(Status.UNAVAILABLE.withDescription("Could not find xDS route matching RPC"),
1✔
918
            new Metadata());
919
        return new ServerCall.Listener<ReqT>() {};
1✔
920
      }
921
      if (selectedRoute.routeAction() != null) {
1✔
922
        call.close(Status.UNAVAILABLE.withDescription("Invalid xDS route action for matching "
1✔
923
            + "route: only Route.non_forwarding_action should be allowed."), new Metadata());
924
        return new ServerCall.Listener<ReqT>() {};
1✔
925
      }
926
      ServerInterceptor routeInterceptor = noopInterceptor;
1✔
927
      Map<Route, ServerInterceptor> perRouteInterceptors = routingConfig.interceptors();
1✔
928
      if (perRouteInterceptors != null && perRouteInterceptors.get(selectedRoute) != null) {
1✔
929
        routeInterceptor = perRouteInterceptors.get(selectedRoute);
1✔
930
      }
931
      return routeInterceptor.interceptCall(call, headers, next);
1✔
932
    }
933
  }
934

935
  /**
936
   * The HttpConnectionManager level configuration.
937
   */
938
  @AutoValue
939
  abstract static class ServerRoutingConfig {
1✔
940
    @VisibleForTesting
941
    static final ServerRoutingConfig FAILING_ROUTING_CONFIG = ServerRoutingConfig.create(
1✔
942
        ImmutableList.<VirtualHost>of(), ImmutableMap.<Route, ServerInterceptor>of());
1✔
943

944
    abstract ImmutableList<VirtualHost> virtualHosts();
945

946
    // Prebuilt per route server interceptors from http filter configs.
947
    abstract ImmutableMap<Route, ServerInterceptor> interceptors();
948

949
    /**
950
     * Server routing configuration.
951
     * */
952
    public static ServerRoutingConfig create(
953
        ImmutableList<VirtualHost> virtualHosts,
954
        ImmutableMap<Route, ServerInterceptor> interceptors) {
955
      checkNotNull(virtualHosts, "virtualHosts");
1✔
956
      checkNotNull(interceptors, "interceptors");
1✔
957
      return new AutoValue_XdsServerWrapper_ServerRoutingConfig(virtualHosts, interceptors);
1✔
958
    }
959
  }
960
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc