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

grpc / grpc-java / #20353

07 Jul 2026 10:24AM UTC coverage: 89.104% (-0.02%) from 89.122%
#20353

push

github

web-flow
enable child channel plugins (#12578)

Implements https://github.com/grpc/proposal/pull/529

38027 of 42677 relevant lines covered (89.1%)

0.89 hits per line

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

94.75
/../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.ChannelConfigurator;
33
import io.grpc.InternalServerInterceptors;
34
import io.grpc.Metadata;
35
import io.grpc.MethodDescriptor;
36
import io.grpc.MetricRecorder;
37
import io.grpc.Server;
38
import io.grpc.ServerBuilder;
39
import io.grpc.ServerCall;
40
import io.grpc.ServerCall.Listener;
41
import io.grpc.ServerCallHandler;
42
import io.grpc.ServerInterceptor;
43
import io.grpc.ServerServiceDefinition;
44
import io.grpc.Status;
45
import io.grpc.StatusException;
46
import io.grpc.StatusOr;
47
import io.grpc.SynchronizationContext;
48
import io.grpc.SynchronizationContext.ScheduledHandle;
49
import io.grpc.internal.GrpcUtil;
50
import io.grpc.internal.ObjectPool;
51
import io.grpc.internal.SharedResourceHolder;
52
import io.grpc.xds.EnvoyServerProtoData.FilterChain;
53
import io.grpc.xds.Filter.FilterConfig;
54
import io.grpc.xds.Filter.FilterContext;
55
import io.grpc.xds.Filter.NamedFilterConfig;
56
import io.grpc.xds.FilterChainMatchingProtocolNegotiators.FilterChainMatchingHandler.FilterChainSelector;
57
import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl;
58
import io.grpc.xds.VirtualHost.Route;
59
import io.grpc.xds.XdsListenerResource.LdsUpdate;
60
import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate;
61
import io.grpc.xds.XdsServerBuilder.XdsServingStatusListener;
62
import io.grpc.xds.client.Bootstrapper.BootstrapInfo;
63
import io.grpc.xds.client.XdsClient;
64
import io.grpc.xds.client.XdsClient.ResourceWatcher;
65
import io.grpc.xds.internal.security.SslContextProviderSupplier;
66
import java.io.IOException;
67
import java.net.InetAddress;
68
import java.net.SocketAddress;
69
import java.util.ArrayList;
70
import java.util.HashMap;
71
import java.util.HashSet;
72
import java.util.List;
73
import java.util.Map;
74
import java.util.Set;
75
import java.util.concurrent.CountDownLatch;
76
import java.util.concurrent.ExecutionException;
77
import java.util.concurrent.ScheduledExecutorService;
78
import java.util.concurrent.TimeUnit;
79
import java.util.concurrent.atomic.AtomicBoolean;
80
import java.util.concurrent.atomic.AtomicReference;
81
import java.util.logging.Level;
82
import java.util.logging.Logger;
83
import javax.annotation.Nullable;
84

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

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

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

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

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

133
  private final ChannelConfigurator channelConfigurator;
134

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

157
  XdsServerWrapper(
158
      String listenerAddress,
159
      ServerBuilder<?> delegateBuilder,
160
      XdsServingStatusListener listener,
161
      FilterChainSelectorManager filterChainSelectorManager,
162
      XdsClientPoolFactory xdsClientPoolFactory,
163
      @Nullable Map<String, ?> bootstrapOverride,
164
      FilterRegistry filterRegistry) {
165
    this(
1✔
166
        listenerAddress,
167
        delegateBuilder,
168
        listener,
169
        filterChainSelectorManager,
170
        xdsClientPoolFactory,
171
        bootstrapOverride,
172
        filterRegistry,
173
        builder -> { });
×
174
  }
1✔
175

176
  @VisibleForTesting
177
  XdsServerWrapper(
178
          String listenerAddress,
179
          ServerBuilder<?> delegateBuilder,
180
          XdsServingStatusListener listener,
181
          FilterChainSelectorManager filterChainSelectorManager,
182
          XdsClientPoolFactory xdsClientPoolFactory,
183
          @Nullable Map<String, ?> bootstrapOverride,
184
          FilterRegistry filterRegistry,
185
          ScheduledExecutorService timeService) {
186
    this(
1✔
187
        listenerAddress,
188
        delegateBuilder,
189
        listener,
190
        filterChainSelectorManager,
191
        xdsClientPoolFactory,
192
        bootstrapOverride,
193
        filterRegistry,
194
        timeService,
195
        builder -> { });
×
196
  }
1✔
197

198
  @VisibleForTesting
199
  XdsServerWrapper(
200
          String listenerAddress,
201
          ServerBuilder<?> delegateBuilder,
202
          XdsServingStatusListener listener,
203
          FilterChainSelectorManager filterChainSelectorManager,
204
          XdsClientPoolFactory xdsClientPoolFactory,
205
          @Nullable Map<String, ?> bootstrapOverride,
206
          FilterRegistry filterRegistry,
207
          ScheduledExecutorService timeService,
208
          ChannelConfigurator channelConfigurator) {
1✔
209
    this.listenerAddress = checkNotNull(listenerAddress, "listenerAddress");
1✔
210
    this.delegateBuilder = checkNotNull(delegateBuilder, "delegateBuilder");
1✔
211
    this.delegateBuilder.intercept(new ConfigApplyingInterceptor());
1✔
212
    this.listener = checkNotNull(listener, "listener");
1✔
213
    this.filterChainSelectorManager
1✔
214
        = checkNotNull(filterChainSelectorManager, "filterChainSelectorManager");
1✔
215
    this.xdsClientPoolFactory = checkNotNull(xdsClientPoolFactory, "xdsClientPoolFactory");
1✔
216
    this.bootstrapOverride = bootstrapOverride;
1✔
217
    this.timeService = checkNotNull(timeService, "timeService");
1✔
218
    this.filterRegistry = checkNotNull(filterRegistry,"filterRegistry");
1✔
219
    this.delegate = delegateBuilder.build();
1✔
220
    this.channelConfigurator = checkNotNull(channelConfigurator, "channelConfigurator");
1✔
221
  }
1✔
222

223
  @Override
224
  public Server start() throws IOException {
225
    checkState(started.compareAndSet(false, true), "Already started");
1✔
226
    syncContext.execute(new Runnable() {
1✔
227
      @Override
228
      public void run() {
229
        internalStart();
1✔
230
      }
1✔
231
    });
232
    Exception exception;
233
    try {
234
      exception = initialStartFuture.get();
1✔
235
    } catch (InterruptedException | ExecutionException e) {
×
236
      throw new RuntimeException(e);
×
237
    }
1✔
238
    if (exception != null) {
1✔
239
      throw (exception instanceof IOException) ? (IOException) exception :
1✔
240
              new IOException(exception);
1✔
241
    }
242
    return this;
1✔
243
  }
244

245
  private void internalStart() {
246
    try {
247
      BootstrapInfo bootstrapInfo;
248
      if (bootstrapOverride == null) {
1✔
249
        bootstrapInfo = GrpcBootstrapperImpl.defaultBootstrap();
×
250
      } else {
251
        bootstrapInfo = new GrpcBootstrapperImpl().bootstrap(bootstrapOverride);
1✔
252
      }
253
      xdsClientPool = xdsClientPoolFactory.getOrCreate(
1✔
254
          "#server", bootstrapInfo, new MetricRecorder() {},
1✔
255
          channelConfigurator);
256
    } catch (Exception e) {
×
257
      StatusException statusException = Status.UNAVAILABLE.withDescription(
×
258
              "Failed to initialize xDS").withCause(e).asException();
×
259
      listener.onNotServing(statusException);
×
260
      initialStartFuture.set(statusException);
×
261
      return;
×
262
    }
1✔
263
    xdsClient = xdsClientPool.getObject();
1✔
264
    String listenerTemplate = xdsClient.getBootstrapInfo().serverListenerResourceNameTemplate();
1✔
265
    if (listenerTemplate == null) {
1✔
266
      StatusException statusException =
1✔
267
          Status.UNAVAILABLE.withDescription(
1✔
268
              "Can only support xDS v3 with listener resource name template").asException();
1✔
269
      listener.onNotServing(statusException);
1✔
270
      initialStartFuture.set(statusException);
1✔
271
      xdsClient = xdsClientPool.returnObject(xdsClient);
1✔
272
      return;
1✔
273
    }
274
    String replacement = listenerAddress;
1✔
275
    if (listenerTemplate.startsWith(XDSTP_SCHEME)) {
1✔
276
      replacement = XdsClient.percentEncodePath(replacement);
1✔
277
    }
278
    discoveryState = new DiscoveryState(listenerTemplate.replaceAll("%s", replacement));
1✔
279
  }
1✔
280

281
  @Override
282
  public Server shutdown() {
283
    if (!shutdown.compareAndSet(false, true)) {
1✔
284
      return this;
1✔
285
    }
286
    syncContext.execute(new Runnable() {
1✔
287
      @Override
288
      public void run() {
289
        if (!delegate.isShutdown()) {
1✔
290
          delegate.shutdown();
1✔
291
        }
292
        internalShutdown();
1✔
293
      }
1✔
294
    });
295
    return this;
1✔
296
  }
297

298
  @Override
299
  public Server shutdownNow() {
300
    if (!shutdown.compareAndSet(false, true)) {
1✔
301
      return this;
1✔
302
    }
303
    syncContext.execute(new Runnable() {
1✔
304
      @Override
305
      public void run() {
306
        if (!delegate.isShutdown()) {
1✔
307
          delegate.shutdownNow();
1✔
308
        }
309
        internalShutdown();
1✔
310
        initialStartFuture.set(new IOException("server is forcefully shut down"));
1✔
311
      }
1✔
312
    });
313
    return this;
1✔
314
  }
315

316
  // Must run in SynchronizationContext
317
  private void internalShutdown() {
318
    logger.log(Level.FINER, "Shutting down XdsServerWrapper");
1✔
319
    if (discoveryState != null) {
1✔
320
      discoveryState.shutdown();
1✔
321
    }
322
    if (xdsClient != null) {
1✔
323
      xdsClient = xdsClientPool.returnObject(xdsClient);
1✔
324
    }
325
    if (restartTimer != null) {
1✔
326
      restartTimer.cancel();
1✔
327
    }
328
    if (sharedTimeService) {
1✔
329
      SharedResourceHolder.release(GrpcUtil.TIMER_SERVICE, timeService);
1✔
330
    }
331
    isServing = false;
1✔
332
    internalTerminationLatch.countDown();
1✔
333
  }
1✔
334

335
  @Override
336
  public boolean isShutdown() {
337
    return shutdown.get();
1✔
338
  }
339

340
  @Override
341
  public boolean isTerminated() {
342
    return internalTerminationLatch.getCount() == 0 && delegate.isTerminated();
1✔
343
  }
344

345
  @Override
346
  public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
347
    long startTime = System.nanoTime();
1✔
348
    if (!internalTerminationLatch.await(timeout, unit)) {
1✔
349
      return false;
×
350
    }
351
    long remainingTime = unit.toNanos(timeout) - (System.nanoTime() - startTime);
1✔
352
    return delegate.awaitTermination(remainingTime, TimeUnit.NANOSECONDS);
1✔
353
  }
354

355
  @Override
356
  public void awaitTermination() throws InterruptedException {
357
    internalTerminationLatch.await();
1✔
358
    delegate.awaitTermination();
1✔
359
  }
1✔
360

361
  @Override
362
  public int getPort() {
363
    return delegate.getPort();
1✔
364
  }
365

366
  @Override
367
  public List<? extends SocketAddress> getListenSockets() {
368
    return delegate.getListenSockets();
1✔
369
  }
370

371
  @Override
372
  public List<ServerServiceDefinition> getServices() {
373
    return delegate.getServices();
×
374
  }
375

376
  @Override
377
  public List<ServerServiceDefinition> getImmutableServices() {
378
    return delegate.getImmutableServices();
×
379
  }
380

381
  @Override
382
  public List<ServerServiceDefinition> getMutableServices() {
383
    return delegate.getMutableServices();
×
384
  }
385

386
  // Must run in SynchronizationContext
387
  private void startDelegateServer() {
388
    if (restartTimer != null && restartTimer.isPending()) {
1✔
389
      return;
×
390
    }
391
    if (isServing) {
1✔
392
      return;
1✔
393
    }
394
    if (delegate.isShutdown()) {
1✔
395
      delegate = delegateBuilder.build();
1✔
396
    }
397
    try {
398
      delegate.start();
1✔
399
      listener.onServing();
1✔
400
      isServing = true;
1✔
401
      if (!initialStarted) {
1✔
402
        initialStarted = true;
1✔
403
        initialStartFuture.set(null);
1✔
404
      }
405
      logger.log(Level.FINER, "Delegate server started.");
1✔
406
    } catch (IOException e) {
1✔
407
      logger.log(Level.FINE, "Fail to start delegate server: {0}", e);
1✔
408
      if (!initialStarted) {
1✔
409
        initialStarted = true;
1✔
410
        initialStartFuture.set(e);
1✔
411
      } else {
412
        listener.onNotServing(e);
1✔
413
      }
414
      restartTimer = syncContext.schedule(
1✔
415
        new RestartTask(), RETRY_DELAY_NANOS, TimeUnit.NANOSECONDS, timeService);
416
    }
1✔
417
  }
1✔
418

419
  private final class RestartTask implements Runnable {
1✔
420
    @Override
421
    public void run() {
422
      startDelegateServer();
1✔
423
    }
1✔
424
  }
425

426
  private final class DiscoveryState implements ResourceWatcher<LdsUpdate> {
427
    private final String resourceName;
428
    // RDS resource name is the key.
429
    private final Map<String, RouteDiscoveryState> routeDiscoveryStates = new HashMap<>();
1✔
430
    // Track pending RDS resources using rds name.
431
    private final Set<String> pendingRds = new HashSet<>();
1✔
432
    // Most recently discovered filter chains.
433
    private List<FilterChain> filterChains = new ArrayList<>();
1✔
434
    // Most recently discovered default filter chain.
435
    @Nullable
436
    private FilterChain defaultFilterChain;
437
    private boolean stopped;
438
    private final Map<FilterChain, AtomicReference<ServerRoutingConfig>> savedRdsRoutingConfigRef 
1✔
439
        = new HashMap<>();
440
    private final ServerInterceptor noopInterceptor = new ServerInterceptor() {
1✔
441
      @Override
442
      public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,
443
          Metadata headers, ServerCallHandler<ReqT, RespT> next) {
444
        return next.startCall(call, headers);
1✔
445
      }
446
    };
447

448
    private DiscoveryState(String resourceName) {
1✔
449
      this.resourceName = checkNotNull(resourceName, "resourceName");
1✔
450
      xdsClient.watchXdsResource(
1✔
451
          XdsListenerResource.getInstance(), resourceName, this, syncContext);
1✔
452
    }
1✔
453

454
    @Override
455
    public void onResourceChanged(final StatusOr<LdsUpdate> update) {
456
      if (stopped) {
1✔
457
        return;
×
458
      }
459

460
      if (!update.hasValue()) {
1✔
461
        Status status = update.getStatus();
1✔
462
        StatusException statusException = Status.UNAVAILABLE.withDescription(
1✔
463
                String.format("Listener %s unavailable: %s", resourceName, status.getDescription()))
1✔
464
            .withCause(status.asException())
1✔
465
            .asException();
1✔
466
        handleConfigNotFoundOrMismatch(statusException);
1✔
467
        return;
1✔
468
      }
469

470
      final LdsUpdate ldsUpdate = update.getValue();
1✔
471
      logger.log(Level.FINEST, "Received Lds update {0}", ldsUpdate);
1✔
472
      if (ldsUpdate.listener() == null) {
1✔
473
        handleConfigNotFoundOrMismatch(
1✔
474
            Status.NOT_FOUND.withDescription("Listener is null in LdsUpdate").asException());
1✔
475
        return;
1✔
476
      }
477
      String ldsAddress = ldsUpdate.listener().address();
1✔
478
      if (ldsAddress == null || ldsUpdate.listener().protocol() != Protocol.TCP
1✔
479
          || !ipAddressesMatch(ldsAddress)) {
1✔
480
        handleConfigNotFoundOrMismatch(
1✔
481
            Status.UNKNOWN.withDescription(
1✔
482
                String.format(
1✔
483
                    "Listener address mismatch: expected %s, but got %s.",
484
                    listenerAddress, ldsAddress)).asException());
1✔
485
        return;
1✔
486
      }
487

488
      if (!pendingRds.isEmpty()) {
1✔
489
        // filter chain state has not yet been applied to filterChainSelectorManager and there
490
        releaseSuppliersInFlight();
1✔
491
        pendingRds.clear();
1✔
492
      }
493

494
      filterChains = ldsUpdate.listener().filterChains();
1✔
495
      defaultFilterChain = ldsUpdate.listener().defaultFilterChain();
1✔
496
      updateActiveFilters();
1✔
497

498
      List<FilterChain> allFilterChains = filterChains;
1✔
499
      if (defaultFilterChain != null) {
1✔
500
        allFilterChains = new ArrayList<>(filterChains);
1✔
501
        allFilterChains.add(defaultFilterChain);
1✔
502
      }
503

504
      Set<String> allRds = new HashSet<>();
1✔
505
      for (FilterChain filterChain : allFilterChains) {
1✔
506
        HttpConnectionManager hcm = filterChain.httpConnectionManager();
1✔
507
        if (hcm.virtualHosts() == null) {
1✔
508
          RouteDiscoveryState rdsState = routeDiscoveryStates.get(hcm.rdsName());
1✔
509
          if (rdsState == null) {
1✔
510
            rdsState = new RouteDiscoveryState(hcm.rdsName());
1✔
511
            routeDiscoveryStates.put(hcm.rdsName(), rdsState);
1✔
512
            xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),
1✔
513
                hcm.rdsName(), rdsState, syncContext);
1✔
514
          }
515
          if (rdsState.isPending) {
1✔
516
            pendingRds.add(hcm.rdsName());
1✔
517
          }
518
          allRds.add(hcm.rdsName());
1✔
519
        }
520
      }
1✔
521

522
      for (Map.Entry<String, RouteDiscoveryState> entry: routeDiscoveryStates.entrySet()) {
1✔
523
        if (!allRds.contains(entry.getKey())) {
1✔
524
          xdsClient.cancelXdsResourceWatch(XdsRouteConfigureResource.getInstance(),
1✔
525
              entry.getKey(), entry.getValue());
1✔
526
        }
527
      }
1✔
528
      routeDiscoveryStates.keySet().retainAll(allRds);
1✔
529
      if (pendingRds.isEmpty()) {
1✔
530
        updateSelector();
1✔
531
      }
532
    }
1✔
533

534
    @Override
535
    public void onAmbientError(final Status error) {
536
      if (stopped) {
1✔
537
        return;
×
538
      }
539
      String description = error.getDescription() == null ? "" : error.getDescription() + " ";
1✔
540
      Status errorWithNodeId = error.withDescription(
1✔
541
          description + "xDS node ID: " + xdsClient.getBootstrapInfo().node().getId());
1✔
542
      logger.log(Level.FINE, "Error from XdsClient", errorWithNodeId);
1✔
543

544
      if (!isServing) {
1✔
545
        listener.onNotServing(errorWithNodeId.asException());
×
546
      }
547
    }
1✔
548

549
    private boolean ipAddressesMatch(String ldsAddress) {
550
      HostAndPort ldsAddressHnP = HostAndPort.fromString(ldsAddress);
1✔
551
      HostAndPort listenerAddressHnP = HostAndPort.fromString(listenerAddress);
1✔
552
      if (!ldsAddressHnP.hasPort() || !listenerAddressHnP.hasPort()
1✔
553
          || ldsAddressHnP.getPort() != listenerAddressHnP.getPort()) {
1✔
554
        return false;
1✔
555
      }
556
      InetAddress listenerIp = InetAddresses.forString(listenerAddressHnP.getHost());
1✔
557
      InetAddress ldsIp = InetAddresses.forString(ldsAddressHnP.getHost());
1✔
558
      return listenerIp.equals(ldsIp);
1✔
559
    }
560

561
    private void shutdown() {
562
      stopped = true;
1✔
563
      cleanUpRouteDiscoveryStates();
1✔
564
      logger.log(Level.FINE, "Stop watching LDS resource {0}", resourceName);
1✔
565
      xdsClient.cancelXdsResourceWatch(XdsListenerResource.getInstance(), resourceName, this);
1✔
566
      shutdownActiveFilters();
1✔
567
      List<SslContextProviderSupplier> toRelease = getSuppliersInUse();
1✔
568
      filterChainSelectorManager.updateSelector(FilterChainSelector.NO_FILTER_CHAIN);
1✔
569
      for (SslContextProviderSupplier s: toRelease) {
1✔
570
        s.close();
1✔
571
      }
1✔
572
      releaseSuppliersInFlight();
1✔
573
    }
1✔
574

575
    private void updateSelector() {
576
      // This is regenerated in generateRoutingConfig() calls below.
577
      savedRdsRoutingConfigRef.clear();
1✔
578

579
      // Prepare server routing config map.
580
      ImmutableMap.Builder<FilterChain, AtomicReference<ServerRoutingConfig>> routingConfigs =
581
          ImmutableMap.builder();
1✔
582
      for (FilterChain filterChain: filterChains) {
1✔
583
        HashMap<String, Filter> chainFilters = activeFilters.get(filterChain.name());
1✔
584
        routingConfigs.put(filterChain, generateRoutingConfig(filterChain, chainFilters));
1✔
585
      }
1✔
586

587
      // Prepare the new selector.
588
      FilterChainSelector selector;
589
      if (defaultFilterChain != null) {
1✔
590
        selector = new FilterChainSelector(
1✔
591
            routingConfigs.build(),
1✔
592
            defaultFilterChain.sslContextProviderSupplier(),
1✔
593
            generateRoutingConfig(defaultFilterChain, activeFiltersDefaultChain));
1✔
594
      } else {
595
        selector = new FilterChainSelector(routingConfigs.build());
1✔
596
      }
597

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

601
      // Swap the selectors, initiate a graceful shutdown of the old one.
602
      logger.log(Level.FINEST, "Updating selector {0}", selector);
1✔
603
      filterChainSelectorManager.updateSelector(selector);
1✔
604

605
      // Release old resources.
606
      for (SslContextProviderSupplier supplier: oldSslSuppliers) {
1✔
607
        supplier.close();
1✔
608
      }
1✔
609

610
      // Now that we have valid Transport Socket config, we can start/restart listening on a port.
611
      startDelegateServer();
1✔
612
    }
1✔
613

614
    // called in syncContext
615
    private void updateActiveFilters() {
616
      Set<String> removedChains = new HashSet<>(activeFilters.keySet());
1✔
617
      for (FilterChain filterChain: filterChains) {
1✔
618
        removedChains.remove(filterChain.name());
1✔
619
        updateActiveFiltersForChain(
1✔
620
            activeFilters.computeIfAbsent(filterChain.name(), k -> new HashMap<>()),
1✔
621
            filterChain.httpConnectionManager().httpFilterConfigs());
1✔
622
      }
1✔
623

624
      // Shutdown all filters of chains missing from the LDS.
625
      for (String chainToShutdown : removedChains) {
1✔
626
        HashMap<String, Filter> filtersToShutdown = activeFilters.get(chainToShutdown);
1✔
627
        checkNotNull(filtersToShutdown, "filtersToShutdown of chain %s", chainToShutdown);
1✔
628
        updateActiveFiltersForChain(filtersToShutdown, null);
1✔
629
        activeFilters.remove(chainToShutdown);
1✔
630
      }
1✔
631

632
      // Default chain.
633
      ImmutableList<NamedFilterConfig> defaultChainConfigs = null;
1✔
634
      if (defaultFilterChain != null) {
1✔
635
        defaultChainConfigs = defaultFilterChain.httpConnectionManager().httpFilterConfigs();
1✔
636
      }
637
      updateActiveFiltersForChain(activeFiltersDefaultChain, defaultChainConfigs);
1✔
638
    }
1✔
639

640
    // called in syncContext
641
    private void shutdownActiveFilters() {
642
      for (HashMap<String, Filter> chainFilters : activeFilters.values()) {
1✔
643
        checkNotNull(chainFilters, "chainFilters");
1✔
644
        updateActiveFiltersForChain(chainFilters, null);
1✔
645
      }
1✔
646
      activeFilters.clear();
1✔
647
      updateActiveFiltersForChain(activeFiltersDefaultChain, null);
1✔
648
    }
1✔
649

650
    // called in syncContext
651
    private void updateActiveFiltersForChain(
652
        Map<String, Filter> chainFilters, @Nullable List<NamedFilterConfig> filterConfigs) {
653
      if (filterConfigs == null) {
1✔
654
        filterConfigs = ImmutableList.of();
1✔
655
      }
656

657
      Set<String> filtersToShutdown = new HashSet<>(chainFilters.keySet());
1✔
658
      for (NamedFilterConfig namedFilter : filterConfigs) {
1✔
659
        String typeUrl = namedFilter.filterConfig.typeUrl();
1✔
660
        String filterKey = namedFilter.filterStateKey();
1✔
661

662
        Filter.Provider provider = filterRegistry.get(typeUrl);
1✔
663
        checkNotNull(provider, "provider %s", typeUrl);
1✔
664
        Filter filter = chainFilters.computeIfAbsent(
1✔
665
            filterKey, k -> provider.newInstance(
1✔
666
                FilterContext.create(namedFilter.name, new MetricRecorder() {})));
1✔
667
        checkNotNull(filter, "filter %s", filterKey);
1✔
668
        filtersToShutdown.remove(filterKey);
1✔
669
      }
1✔
670

671
      // Shutdown filters not present in current HCM.
672
      for (String filterKey : filtersToShutdown) {
1✔
673
        Filter filterToShutdown = chainFilters.remove(filterKey);
1✔
674
        checkNotNull(filterToShutdown, "filterToShutdown %s", filterKey);
1✔
675
        filterToShutdown.close();
1✔
676
      }
1✔
677
    }
1✔
678

679
    private AtomicReference<ServerRoutingConfig> generateRoutingConfig(
680
        FilterChain filterChain, Map<String, Filter> chainFilters) {
681
      HttpConnectionManager hcm = filterChain.httpConnectionManager();
1✔
682
      ServerRoutingConfig routingConfig;
683

684
      // Inlined routes.
685
      ImmutableList<VirtualHost> vhosts = hcm.virtualHosts();
1✔
686
      if (vhosts != null) {
1✔
687
        routingConfig = ServerRoutingConfig.create(vhosts,
1✔
688
            generatePerRouteInterceptors(hcm.httpFilterConfigs(), vhosts, chainFilters));
1✔
689
        return new AtomicReference<>(routingConfig);
1✔
690
      }
691

692
      // Routes from RDS.
693
      RouteDiscoveryState rds = routeDiscoveryStates.get(hcm.rdsName());
1✔
694
      checkNotNull(rds, "rds");
1✔
695

696
      ImmutableList<VirtualHost> savedVhosts = rds.savedVirtualHosts;
1✔
697
      if (savedVhosts != null) {
1✔
698
        routingConfig = ServerRoutingConfig.create(savedVhosts,
1✔
699
            generatePerRouteInterceptors(hcm.httpFilterConfigs(), savedVhosts, chainFilters));
1✔
700
      } else {
701
        routingConfig = ServerRoutingConfig.FAILING_ROUTING_CONFIG;
1✔
702
      }
703
      AtomicReference<ServerRoutingConfig> routingConfigRef = new AtomicReference<>(routingConfig);
1✔
704
      savedRdsRoutingConfigRef.put(filterChain, routingConfigRef);
1✔
705
      return routingConfigRef;
1✔
706
    }
707

708
    private ImmutableMap<Route, ServerInterceptor> generatePerRouteInterceptors(
709
        @Nullable List<NamedFilterConfig> filterConfigs,
710
        List<VirtualHost> virtualHosts,
711
        Map<String, Filter> chainFilters) {
712
      syncContext.throwIfNotInThisSynchronizationContext();
1✔
713

714
      checkNotNull(chainFilters, "chainFilters");
1✔
715
      ImmutableMap.Builder<Route, ServerInterceptor> perRouteInterceptors =
1✔
716
          new ImmutableMap.Builder<>();
717

718
      for (VirtualHost virtualHost : virtualHosts) {
1✔
719
        for (Route route : virtualHost.routes()) {
1✔
720
          // Short circuit.
721
          if (filterConfigs == null) {
1✔
722
            perRouteInterceptors.put(route, noopInterceptor);
×
723
            continue;
×
724
          }
725

726
          // Override vhost filter configs with more specific per-route configs.
727
          Map<String, FilterConfig> perRouteOverrides = ImmutableMap.<String, FilterConfig>builder()
1✔
728
              .putAll(virtualHost.filterConfigOverrides())
1✔
729
              .putAll(route.filterConfigOverrides())
1✔
730
              .buildKeepingLast();
1✔
731

732
          // Interceptors for this vhost/route combo.
733
          List<ServerInterceptor> interceptors = new ArrayList<>(filterConfigs.size());
1✔
734
          for (NamedFilterConfig namedFilter : filterConfigs) {
1✔
735
            String name = namedFilter.name;
1✔
736
            FilterConfig config = namedFilter.filterConfig;
1✔
737
            FilterConfig overrideConfig = perRouteOverrides.get(name);
1✔
738
            String filterKey = namedFilter.filterStateKey();
1✔
739

740
            Filter filter = chainFilters.get(filterKey);
1✔
741
            checkNotNull(filter, "chainFilters.get(%s)", filterKey);
1✔
742
            ServerInterceptor interceptor = filter.buildServerInterceptor(config, overrideConfig);
1✔
743

744
            if (interceptor != null) {
1✔
745
              interceptors.add(interceptor);
1✔
746
            }
747
          }
1✔
748

749
          // Combine interceptors produced by different filters into a single one that executes
750
          // them sequentially. The order is preserved.
751
          perRouteInterceptors.put(route, combineInterceptors(interceptors));
1✔
752
        }
1✔
753
      }
1✔
754

755
      return perRouteInterceptors.buildOrThrow();
1✔
756
    }
757

758
    private ServerInterceptor combineInterceptors(final List<ServerInterceptor> interceptors) {
759
      if (interceptors.isEmpty()) {
1✔
760
        return noopInterceptor;
1✔
761
      }
762
      if (interceptors.size() == 1) {
1✔
763
        return interceptors.get(0);
×
764
      }
765
      return new ServerInterceptor() {
1✔
766
        @Override
767
        public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,
768
            Metadata headers, ServerCallHandler<ReqT, RespT> next) {
769
          // intercept forward
770
          for (int i = interceptors.size() - 1; i >= 0; i--) {
1✔
771
            next = InternalServerInterceptors.interceptCallHandlerCreate(
1✔
772
                interceptors.get(i), next);
1✔
773
          }
774
          return next.startCall(call, headers);
1✔
775
        }
776
      };
777
    }
778

779
    private void handleConfigNotFoundOrMismatch(StatusException exception) {
780
      cleanUpRouteDiscoveryStates();
1✔
781
      shutdownActiveFilters();
1✔
782
      List<SslContextProviderSupplier> toRelease = getSuppliersInUse();
1✔
783
      filterChainSelectorManager.updateSelector(FilterChainSelector.NO_FILTER_CHAIN);
1✔
784
      for (SslContextProviderSupplier s: toRelease) {
1✔
785
        s.close();
1✔
786
      }
1✔
787
      if (restartTimer != null) {
1✔
788
        restartTimer.cancel();
1✔
789
      }
790
      if (!delegate.isShutdown()) {
1✔
791
        delegate.shutdown();  // let in-progress calls finish
1✔
792
      }
793
      isServing = false;
1✔
794
      listener.onNotServing(exception);
1✔
795
    }
1✔
796

797
    private void cleanUpRouteDiscoveryStates() {
798
      for (RouteDiscoveryState rdsState : routeDiscoveryStates.values()) {
1✔
799
        String rdsName = rdsState.resourceName;
1✔
800
        logger.log(Level.FINE, "Stop watching RDS resource {0}", rdsName);
1✔
801
        xdsClient.cancelXdsResourceWatch(XdsRouteConfigureResource.getInstance(), rdsName,
1✔
802
            rdsState);
803
      }
1✔
804
      routeDiscoveryStates.clear();
1✔
805
      savedRdsRoutingConfigRef.clear();
1✔
806
    }
1✔
807

808
    private List<SslContextProviderSupplier> getSuppliersInUse() {
809
      List<SslContextProviderSupplier> toRelease = new ArrayList<>();
1✔
810
      FilterChainSelector selector = filterChainSelectorManager.getSelectorToUpdateSelector();
1✔
811
      if (selector != null) {
1✔
812
        for (FilterChain f: selector.getRoutingConfigs().keySet()) {
1✔
813
          if (f.sslContextProviderSupplier() != null) {
1✔
814
            toRelease.add(f.sslContextProviderSupplier());
1✔
815
          }
816
        }
1✔
817
        SslContextProviderSupplier defaultSupplier =
1✔
818
                selector.getDefaultSslContextProviderSupplier();
1✔
819
        if (defaultSupplier != null) {
1✔
820
          toRelease.add(defaultSupplier);
1✔
821
        }
822
      }
823
      return toRelease;
1✔
824
    }
825

826
    private void releaseSuppliersInFlight() {
827
      SslContextProviderSupplier supplier;
828
      for (FilterChain filterChain : filterChains) {
1✔
829
        supplier = filterChain.sslContextProviderSupplier();
1✔
830
        if (supplier != null) {
1✔
831
          supplier.close();
1✔
832
        }
833
      }
1✔
834
      if (defaultFilterChain != null
1✔
835
              && (supplier = defaultFilterChain.sslContextProviderSupplier()) != null) {
1✔
836
        supplier.close();
1✔
837
      }
838
    }
1✔
839

840
    private final class RouteDiscoveryState implements ResourceWatcher<RdsUpdate> {
841
      private final String resourceName;
842
      private ImmutableList<VirtualHost> savedVirtualHosts;
843
      private boolean isPending = true;
1✔
844

845
      private RouteDiscoveryState(String resourceName) {
1✔
846
        this.resourceName = checkNotNull(resourceName, "resourceName");
1✔
847
      }
1✔
848

849
      @Override
850
      public void onResourceChanged(final StatusOr<RdsUpdate> update) {
851
        syncContext.execute(() -> {
1✔
852
          if (!routeDiscoveryStates.containsKey(resourceName)) {
1✔
853
            return; // Watcher has been cancelled.
1✔
854
          }
855

856
          if (update.hasValue()) {
1✔
857
            if (savedVirtualHosts == null && !isPending) {
1✔
858
              logger.log(Level.WARNING, "Received valid Rds {0} configuration.", resourceName);
1✔
859
            }
860
            savedVirtualHosts = ImmutableList.copyOf(update.getValue().virtualHosts);
1✔
861
          } else {
862
            logger.log(Level.WARNING, "Rds {0} unavailable: {1}",
1✔
863
                new Object[]{resourceName, update.getStatus()});
1✔
864
            savedVirtualHosts = null;
1✔
865
          }
866
          // In both cases, a change has occurred that requires a config update.
867
          updateRdsRoutingConfig();
1✔
868
          maybeUpdateSelector();
1✔
869
        });
1✔
870
      }
1✔
871

872
      @Override
873
      public void onAmbientError(final Status error) {
874
        syncContext.execute(() -> {
1✔
875
          if (!routeDiscoveryStates.containsKey(resourceName)) {
1✔
876
            return; // Watcher has been cancelled.
×
877
          }
878
          String description = error.getDescription() == null ? "" : error.getDescription() + " ";
1✔
879
          Status errorWithNodeId = error.withDescription(
1✔
880
              description + "xDS node ID: " + xdsClient.getBootstrapInfo().node().getId());
1✔
881
          logger.log(Level.WARNING, "Error loading RDS resource {0} from XdsClient: {1}.",
1✔
882
              new Object[]{resourceName, errorWithNodeId});
883

884
          // Per gRFC A88, ambient errors should not trigger a configuration change.
885
          // Therefore, we do NOT call maybeUpdateSelector() here.
886
        });
1✔
887
      }
1✔
888

889
      private void updateRdsRoutingConfig() {
890
        for (FilterChain filterChain : savedRdsRoutingConfigRef.keySet()) {
1✔
891
          HttpConnectionManager hcm = filterChain.httpConnectionManager();
1✔
892
          if (!resourceName.equals(hcm.rdsName())) {
1✔
893
            continue;
1✔
894
          }
895

896
          ServerRoutingConfig updatedRoutingConfig;
897
          if (savedVirtualHosts == null) {
1✔
898
            updatedRoutingConfig = ServerRoutingConfig.FAILING_ROUTING_CONFIG;
1✔
899
          } else {
900
            HashMap<String, Filter> chainFilters = activeFilters.get(filterChain.name());
1✔
901
            ImmutableMap<Route, ServerInterceptor> interceptors = generatePerRouteInterceptors(
1✔
902
                hcm.httpFilterConfigs(), savedVirtualHosts, chainFilters);
1✔
903
            updatedRoutingConfig = ServerRoutingConfig.create(savedVirtualHosts, interceptors);
1✔
904
          }
905

906
          logger.log(Level.FINEST, "Updating filter chain {0} rds routing config: {1}",
1✔
907
              new Object[]{filterChain.name(), updatedRoutingConfig});
1✔
908
          savedRdsRoutingConfigRef.get(filterChain).set(updatedRoutingConfig);
1✔
909
        }
1✔
910
      }
1✔
911

912
      // Update the selector to use the most recently updated configs only after all rds have been
913
      // discovered for the first time. Later changes on rds will be applied through virtual host
914
      // list atomic ref.
915
      private void maybeUpdateSelector() {
916
        isPending = false;
1✔
917
        boolean isLastPending = pendingRds.remove(resourceName) && pendingRds.isEmpty();
1✔
918
        if (isLastPending) {
1✔
919
          updateSelector();
1✔
920
        }
921
      }
1✔
922
    }
923
  }
924

925
  @VisibleForTesting
926
  final class ConfigApplyingInterceptor implements ServerInterceptor {
1✔
927
    private final ServerInterceptor noopInterceptor = new ServerInterceptor() {
1✔
928
      @Override
929
      public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,
930
          Metadata headers, ServerCallHandler<ReqT, RespT> next) {
931
        return next.startCall(call, headers);
×
932
      }
933
    };
934

935
    @Override
936
    public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,
937
        Metadata headers, ServerCallHandler<ReqT, RespT> next) {
938
      AtomicReference<ServerRoutingConfig> routingConfigRef =
1✔
939
          call.getAttributes().get(ATTR_SERVER_ROUTING_CONFIG);
1✔
940
      ServerRoutingConfig routingConfig = routingConfigRef == null ? null :
1✔
941
          routingConfigRef.get();
1✔
942
      if (routingConfig == null || routingConfig == ServerRoutingConfig.FAILING_ROUTING_CONFIG) {
1✔
943
        String errorMsg = "Missing or broken xDS routing config: RDS config unavailable.";
1✔
944
        call.close(Status.UNAVAILABLE.withDescription(errorMsg), new Metadata());
1✔
945
        return new Listener<ReqT>() {};
1✔
946
      }
947
      List<VirtualHost> virtualHosts = routingConfig.virtualHosts();
1✔
948
      VirtualHost virtualHost = RoutingUtils.findVirtualHostForHostName(
1✔
949
          virtualHosts, call.getAuthority());
1✔
950
      if (virtualHost == null) {
1✔
951
        call.close(
1✔
952
            Status.UNAVAILABLE.withDescription("Could not find xDS virtual host matching RPC"),
1✔
953
            new Metadata());
954
        return new Listener<ReqT>() {};
1✔
955
      }
956
      Route selectedRoute = null;
1✔
957
      MethodDescriptor<ReqT, RespT> method = call.getMethodDescriptor();
1✔
958
      for (Route route : virtualHost.routes()) {
1✔
959
        if (RoutingUtils.matchRoute(
1✔
960
            route.routeMatch(), "/" + method.getFullMethodName(), headers, random)) {
1✔
961
          selectedRoute = route;
1✔
962
          break;
1✔
963
        }
964
      }
1✔
965
      if (selectedRoute == null) {
1✔
966
        call.close(Status.UNAVAILABLE.withDescription("Could not find xDS route matching RPC"),
1✔
967
            new Metadata());
968
        return new ServerCall.Listener<ReqT>() {};
1✔
969
      }
970
      if (selectedRoute.routeAction() != null) {
1✔
971
        call.close(Status.UNAVAILABLE.withDescription("Invalid xDS route action for matching "
1✔
972
            + "route: only Route.non_forwarding_action should be allowed."), new Metadata());
973
        return new ServerCall.Listener<ReqT>() {};
1✔
974
      }
975
      ServerInterceptor routeInterceptor = noopInterceptor;
1✔
976
      Map<Route, ServerInterceptor> perRouteInterceptors = routingConfig.interceptors();
1✔
977
      if (perRouteInterceptors != null && perRouteInterceptors.get(selectedRoute) != null) {
1✔
978
        routeInterceptor = perRouteInterceptors.get(selectedRoute);
1✔
979
      }
980
      return routeInterceptor.interceptCall(call, headers, next);
1✔
981
    }
982
  }
983

984
  /**
985
   * The HttpConnectionManager level configuration.
986
   */
987
  @AutoValue
988
  abstract static class ServerRoutingConfig {
1✔
989
    @VisibleForTesting
990
    static final ServerRoutingConfig FAILING_ROUTING_CONFIG = ServerRoutingConfig.create(
1✔
991
        ImmutableList.<VirtualHost>of(), ImmutableMap.<Route, ServerInterceptor>of());
1✔
992

993
    abstract ImmutableList<VirtualHost> virtualHosts();
994

995
    // Prebuilt per route server interceptors from http filter configs.
996
    abstract ImmutableMap<Route, ServerInterceptor> interceptors();
997

998
    /**
999
     * Server routing configuration.
1000
     * */
1001
    public static ServerRoutingConfig create(
1002
        ImmutableList<VirtualHost> virtualHosts,
1003
        ImmutableMap<Route, ServerInterceptor> interceptors) {
1004
      checkNotNull(virtualHosts, "virtualHosts");
1✔
1005
      checkNotNull(interceptors, "interceptors");
1✔
1006
      return new AutoValue_XdsServerWrapper_ServerRoutingConfig(virtualHosts, interceptors);
1✔
1007
    }
1008
  }
1009
}
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