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

grpc / grpc-java / #19908

16 Jul 2025 07:54PM UTC coverage: 88.593% (+0.07%) from 88.528%
#19908

push

github

ejona86
Revert "xds: Convert CdsLb to XdsDepManager"

This reverts commit 297ab05ef.

b/430347751 shows multiple concerning behaviors in the xDS stack with
the new A74 config update model. XdsDepManager and CdsLB2 still seem to
be working correctly, but the change is exacerbated issues in other
parts of the stack, like RingHashConfig not having equals fixed in
a8de9f07ab.

Revert only for the v1.74.x release, leaving it on master.

34647 of 39108 relevant lines covered (88.59%)

0.89 hits per line

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

94.27
/../xds/src/main/java/io/grpc/xds/XdsNameResolver.java
1
/*
2
 * Copyright 2019 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.checkArgument;
20
import static com.google.common.base.Preconditions.checkNotNull;
21
import static io.grpc.xds.client.Bootstrapper.XDSTP_SCHEME;
22

23
import com.google.common.annotations.VisibleForTesting;
24
import com.google.common.base.Joiner;
25
import com.google.common.base.Strings;
26
import com.google.common.collect.ImmutableList;
27
import com.google.common.collect.ImmutableMap;
28
import com.google.common.collect.Sets;
29
import com.google.gson.Gson;
30
import com.google.protobuf.util.Durations;
31
import io.grpc.Attributes;
32
import io.grpc.CallOptions;
33
import io.grpc.Channel;
34
import io.grpc.ClientCall;
35
import io.grpc.ClientInterceptor;
36
import io.grpc.ClientInterceptors;
37
import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
38
import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
39
import io.grpc.InternalConfigSelector;
40
import io.grpc.InternalLogId;
41
import io.grpc.LoadBalancer.PickSubchannelArgs;
42
import io.grpc.Metadata;
43
import io.grpc.MethodDescriptor;
44
import io.grpc.MetricRecorder;
45
import io.grpc.NameResolver;
46
import io.grpc.Status;
47
import io.grpc.Status.Code;
48
import io.grpc.StatusOr;
49
import io.grpc.SynchronizationContext;
50
import io.grpc.internal.GrpcUtil;
51
import io.grpc.internal.ObjectPool;
52
import io.grpc.xds.ClusterSpecifierPlugin.PluginConfig;
53
import io.grpc.xds.Filter.FilterConfig;
54
import io.grpc.xds.Filter.NamedFilterConfig;
55
import io.grpc.xds.RouteLookupServiceClusterSpecifierPlugin.RlsPluginConfig;
56
import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl;
57
import io.grpc.xds.VirtualHost.Route;
58
import io.grpc.xds.VirtualHost.Route.RouteAction;
59
import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight;
60
import io.grpc.xds.VirtualHost.Route.RouteAction.HashPolicy;
61
import io.grpc.xds.VirtualHost.Route.RouteAction.RetryPolicy;
62
import io.grpc.xds.VirtualHost.Route.RouteMatch;
63
import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider;
64
import io.grpc.xds.client.Bootstrapper.AuthorityInfo;
65
import io.grpc.xds.client.Bootstrapper.BootstrapInfo;
66
import io.grpc.xds.client.XdsClient;
67
import io.grpc.xds.client.XdsLogger;
68
import io.grpc.xds.client.XdsLogger.XdsLogLevel;
69
import java.net.URI;
70
import java.util.ArrayList;
71
import java.util.Collections;
72
import java.util.HashMap;
73
import java.util.HashSet;
74
import java.util.List;
75
import java.util.Locale;
76
import java.util.Map;
77
import java.util.Objects;
78
import java.util.Set;
79
import java.util.concurrent.ConcurrentHashMap;
80
import java.util.concurrent.ConcurrentMap;
81
import java.util.concurrent.ScheduledExecutorService;
82
import java.util.concurrent.atomic.AtomicInteger;
83
import javax.annotation.Nullable;
84

85
/**
86
 * A {@link NameResolver} for resolving gRPC target names with "xds:" scheme.
87
 *
88
 * <p>Resolving a gRPC target involves contacting the control plane management server via xDS
89
 * protocol to retrieve service information and produce a service config to the caller.
90
 *
91
 * @see XdsNameResolverProvider
92
 */
93
final class XdsNameResolver extends NameResolver {
94

95
  static final CallOptions.Key<String> CLUSTER_SELECTION_KEY =
1✔
96
      CallOptions.Key.create("io.grpc.xds.CLUSTER_SELECTION_KEY");
1✔
97
  static final CallOptions.Key<XdsConfig> XDS_CONFIG_CALL_OPTION_KEY =
1✔
98
      CallOptions.Key.create("io.grpc.xds.XDS_CONFIG_CALL_OPTION_KEY");
1✔
99
  static final CallOptions.Key<Long> RPC_HASH_KEY =
1✔
100
      CallOptions.Key.create("io.grpc.xds.RPC_HASH_KEY");
1✔
101
  static final CallOptions.Key<Boolean> AUTO_HOST_REWRITE_KEY =
1✔
102
      CallOptions.Key.create("io.grpc.xds.AUTO_HOST_REWRITE_KEY");
1✔
103
  @VisibleForTesting
104
  static boolean enableTimeout =
1✔
105
      Strings.isNullOrEmpty(System.getenv("GRPC_XDS_EXPERIMENTAL_ENABLE_TIMEOUT"))
1✔
106
          || Boolean.parseBoolean(System.getenv("GRPC_XDS_EXPERIMENTAL_ENABLE_TIMEOUT"));
1✔
107

108
  private final InternalLogId logId;
109
  private final XdsLogger logger;
110
  @Nullable
111
  private final String targetAuthority;
112
  private final String target;
113
  private final String serviceAuthority;
114
  // Encoded version of the service authority as per 
115
  // https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.
116
  private final String encodedServiceAuthority;
117
  private final String overrideAuthority;
118
  private final ServiceConfigParser serviceConfigParser;
119
  private final SynchronizationContext syncContext;
120
  private final ScheduledExecutorService scheduler;
121
  private final XdsClientPoolFactory xdsClientPoolFactory;
122
  private final ThreadSafeRandom random;
123
  private final FilterRegistry filterRegistry;
124
  private final XxHash64 hashFunc = XxHash64.INSTANCE;
1✔
125
  // Clusters (with reference counts) to which new/existing requests can be/are routed.
126
  // put()/remove() must be called in SyncContext, and get() can be called in any thread.
127
  private final ConcurrentMap<String, ClusterRefState> clusterRefs = new ConcurrentHashMap<>();
1✔
128
  private final ConfigSelector configSelector = new ConfigSelector();
1✔
129
  private final long randomChannelId;
130
  private final MetricRecorder metricRecorder;
131
  private final Args nameResolverArgs;
132
  // Must be accessed in syncContext.
133
  // Filter instances are unique per channel, and per filter (name+typeUrl).
134
  // NamedFilterConfig.filterStateKey -> filter_instance.
135
  private final HashMap<String, Filter> activeFilters = new HashMap<>();
1✔
136

137
  private volatile RoutingConfig routingConfig;
138
  private Listener2 listener;
139
  private ObjectPool<XdsClient> xdsClientPool;
140
  private XdsClient xdsClient;
141
  private CallCounterProvider callCounterProvider;
142
  private ResolveState resolveState;
143

144
  XdsNameResolver(
145
      URI targetUri, String name, @Nullable String overrideAuthority,
146
      ServiceConfigParser serviceConfigParser,
147
      SynchronizationContext syncContext, ScheduledExecutorService scheduler,
148
      @Nullable Map<String, ?> bootstrapOverride,
149
      MetricRecorder metricRecorder, Args nameResolverArgs) {
150
    this(targetUri, targetUri.getAuthority(), name, overrideAuthority, serviceConfigParser,
1✔
151
        syncContext, scheduler, SharedXdsClientPoolProvider.getDefaultProvider(),
1✔
152
        ThreadSafeRandomImpl.instance, FilterRegistry.getDefaultRegistry(), bootstrapOverride,
1✔
153
        metricRecorder, nameResolverArgs);
154
  }
1✔
155

156
  @VisibleForTesting
157
  XdsNameResolver(
158
      URI targetUri, @Nullable String targetAuthority, String name,
159
      @Nullable String overrideAuthority, ServiceConfigParser serviceConfigParser,
160
      SynchronizationContext syncContext, ScheduledExecutorService scheduler,
161
      XdsClientPoolFactory xdsClientPoolFactory, ThreadSafeRandom random,
162
      FilterRegistry filterRegistry, @Nullable Map<String, ?> bootstrapOverride,
163
      MetricRecorder metricRecorder, Args nameResolverArgs) {
1✔
164
    this.targetAuthority = targetAuthority;
1✔
165
    target = targetUri.toString();
1✔
166

167
    // The name might have multiple slashes so encode it before verifying.
168
    serviceAuthority = checkNotNull(name, "name");
1✔
169
    this.encodedServiceAuthority = 
1✔
170
      GrpcUtil.checkAuthority(GrpcUtil.AuthorityEscaper.encodeAuthority(serviceAuthority));
1✔
171

172
    this.overrideAuthority = overrideAuthority;
1✔
173
    this.serviceConfigParser = checkNotNull(serviceConfigParser, "serviceConfigParser");
1✔
174
    this.syncContext = checkNotNull(syncContext, "syncContext");
1✔
175
    this.scheduler = checkNotNull(scheduler, "scheduler");
1✔
176
    this.xdsClientPoolFactory = bootstrapOverride == null ? checkNotNull(xdsClientPoolFactory,
1✔
177
            "xdsClientPoolFactory") : new SharedXdsClientPoolProvider();
1✔
178
    this.xdsClientPoolFactory.setBootstrapOverride(bootstrapOverride);
1✔
179
    this.random = checkNotNull(random, "random");
1✔
180
    this.filterRegistry = checkNotNull(filterRegistry, "filterRegistry");
1✔
181
    this.metricRecorder = metricRecorder;
1✔
182
    this.nameResolverArgs = checkNotNull(nameResolverArgs, "nameResolverArgs");
1✔
183

184
    randomChannelId = random.nextLong();
1✔
185
    logId = InternalLogId.allocate("xds-resolver", name);
1✔
186
    logger = XdsLogger.withLogId(logId);
1✔
187
    logger.log(XdsLogLevel.INFO, "Created resolver for {0}", name);
1✔
188
  }
1✔
189

190
  @Override
191
  public String getServiceAuthority() {
192
    return encodedServiceAuthority;
1✔
193
  }
194

195
  @Override
196
  public void start(Listener2 listener) {
197
    this.listener = checkNotNull(listener, "listener");
1✔
198
    try {
199
      xdsClientPool = xdsClientPoolFactory.getOrCreate(target, metricRecorder);
1✔
200
    } catch (Exception e) {
1✔
201
      listener.onError(
1✔
202
          Status.UNAVAILABLE.withDescription("Failed to initialize xDS").withCause(e));
1✔
203
      return;
1✔
204
    }
1✔
205
    xdsClient = xdsClientPool.getObject();
1✔
206
    BootstrapInfo bootstrapInfo = xdsClient.getBootstrapInfo();
1✔
207
    String listenerNameTemplate;
208
    if (targetAuthority == null) {
1✔
209
      listenerNameTemplate = bootstrapInfo.clientDefaultListenerResourceNameTemplate();
1✔
210
    } else {
211
      AuthorityInfo authorityInfo = bootstrapInfo.authorities().get(targetAuthority);
1✔
212
      if (authorityInfo == null) {
1✔
213
        listener.onError(Status.INVALID_ARGUMENT.withDescription(
1✔
214
            "invalid target URI: target authority not found in the bootstrap"));
215
        return;
1✔
216
      }
217
      listenerNameTemplate = authorityInfo.clientListenerResourceNameTemplate();
1✔
218
    }
219
    String replacement = serviceAuthority;
1✔
220
    if (listenerNameTemplate.startsWith(XDSTP_SCHEME)) {
1✔
221
      replacement = XdsClient.percentEncodePath(replacement);
1✔
222
    }
223
    String ldsResourceName = expandPercentS(listenerNameTemplate, replacement);
1✔
224
    if (!XdsClient.isResourceNameValid(ldsResourceName, XdsListenerResource.getInstance().typeUrl())
1✔
225
        ) {
226
      listener.onError(Status.INVALID_ARGUMENT.withDescription(
×
227
          "invalid listener resource URI for service authority: " + serviceAuthority));
228
      return;
×
229
    }
230
    ldsResourceName = XdsClient.canonifyResourceName(ldsResourceName);
1✔
231
    callCounterProvider = SharedCallCounterMap.getInstance();
1✔
232

233
    resolveState = new ResolveState(ldsResourceName); // auto starts
1✔
234
  }
1✔
235

236
  private static String expandPercentS(String template, String replacement) {
237
    return template.replace("%s", replacement);
1✔
238
  }
239

240
  @Override
241
  public void shutdown() {
242
    logger.log(XdsLogLevel.INFO, "Shutdown");
1✔
243
    if (resolveState != null) {
1✔
244
      resolveState.shutdown();
1✔
245
    }
246
    if (xdsClient != null) {
1✔
247
      xdsClient = xdsClientPool.returnObject(xdsClient);
1✔
248
    }
249
  }
1✔
250

251
  @VisibleForTesting
252
  static Map<String, ?> generateServiceConfigWithMethodConfig(
253
      @Nullable Long timeoutNano, @Nullable RetryPolicy retryPolicy) {
254
    if (timeoutNano == null
1✔
255
        && (retryPolicy == null || retryPolicy.retryableStatusCodes().isEmpty())) {
1✔
256
      return Collections.emptyMap();
1✔
257
    }
258
    ImmutableMap.Builder<String, Object> methodConfig = ImmutableMap.builder();
1✔
259
    methodConfig.put(
1✔
260
        "name", Collections.singletonList(Collections.emptyMap()));
1✔
261
    if (retryPolicy != null && !retryPolicy.retryableStatusCodes().isEmpty()) {
1✔
262
      ImmutableMap.Builder<String, Object> rawRetryPolicy = ImmutableMap.builder();
1✔
263
      rawRetryPolicy.put("maxAttempts", (double) retryPolicy.maxAttempts());
1✔
264
      rawRetryPolicy.put("initialBackoff", Durations.toString(retryPolicy.initialBackoff()));
1✔
265
      rawRetryPolicy.put("maxBackoff", Durations.toString(retryPolicy.maxBackoff()));
1✔
266
      rawRetryPolicy.put("backoffMultiplier", 2D);
1✔
267
      List<String> codes = new ArrayList<>(retryPolicy.retryableStatusCodes().size());
1✔
268
      for (Code code : retryPolicy.retryableStatusCodes()) {
1✔
269
        codes.add(code.name());
1✔
270
      }
1✔
271
      rawRetryPolicy.put(
1✔
272
          "retryableStatusCodes", Collections.unmodifiableList(codes));
1✔
273
      if (retryPolicy.perAttemptRecvTimeout() != null) {
1✔
274
        rawRetryPolicy.put(
×
275
            "perAttemptRecvTimeout", Durations.toString(retryPolicy.perAttemptRecvTimeout()));
×
276
      }
277
      methodConfig.put("retryPolicy", rawRetryPolicy.buildOrThrow());
1✔
278
    }
279
    if (timeoutNano != null) {
1✔
280
      String timeout = timeoutNano / 1_000_000_000.0 + "s";
1✔
281
      methodConfig.put("timeout", timeout);
1✔
282
    }
283
    return Collections.singletonMap(
1✔
284
        "methodConfig", Collections.singletonList(methodConfig.buildOrThrow()));
1✔
285
  }
286

287
  @VisibleForTesting
288
  XdsClient getXdsClient() {
289
    return xdsClient;
1✔
290
  }
291

292
  // called in syncContext
293
  private void updateResolutionResult(XdsConfig xdsConfig) {
294
    syncContext.throwIfNotInThisSynchronizationContext();
1✔
295

296
    ImmutableMap.Builder<String, Object> childPolicy = new ImmutableMap.Builder<>();
1✔
297
    for (String name : clusterRefs.keySet()) {
1✔
298
      Map<String, ?> lbPolicy = clusterRefs.get(name).toLbPolicy();
1✔
299
      childPolicy.put(name, ImmutableMap.of("lbPolicy", ImmutableList.of(lbPolicy)));
1✔
300
    }
1✔
301
    Map<String, ?> rawServiceConfig = ImmutableMap.of(
1✔
302
        "loadBalancingConfig",
303
        ImmutableList.of(ImmutableMap.of(
1✔
304
            XdsLbPolicies.CLUSTER_MANAGER_POLICY_NAME,
305
            ImmutableMap.of("childPolicy", childPolicy.buildOrThrow()))));
1✔
306

307
    if (logger.isLoggable(XdsLogLevel.INFO)) {
1✔
308
      logger.log(
×
309
          XdsLogLevel.INFO, "Generated service config: {0}", new Gson().toJson(rawServiceConfig));
×
310
    }
311
    ConfigOrError parsedServiceConfig = serviceConfigParser.parseServiceConfig(rawServiceConfig);
1✔
312
    Attributes attrs =
313
        Attributes.newBuilder()
1✔
314
            .set(XdsAttributes.XDS_CLIENT_POOL, xdsClientPool)
1✔
315
            .set(XdsAttributes.XDS_CONFIG, xdsConfig)
1✔
316
            .set(XdsAttributes.XDS_CLUSTER_SUBSCRIPT_REGISTRY, resolveState.xdsDependencyManager)
1✔
317
            .set(XdsAttributes.CALL_COUNTER_PROVIDER, callCounterProvider)
1✔
318
            .set(InternalConfigSelector.KEY, configSelector)
1✔
319
            .build();
1✔
320
    ResolutionResult result =
321
        ResolutionResult.newBuilder()
1✔
322
            .setAttributes(attrs)
1✔
323
            .setServiceConfig(parsedServiceConfig)
1✔
324
            .build();
1✔
325
    listener.onResult2(result);
1✔
326
  }
1✔
327

328
  /**
329
   * Returns {@code true} iff {@code hostName} matches the domain name {@code pattern} with
330
   * case-insensitive.
331
   *
332
   * <p>Wildcard pattern rules:
333
   * <ol>
334
   * <li>A single asterisk (*) matches any domain.</li>
335
   * <li>Asterisk (*) is only permitted in the left-most or the right-most part of the pattern,
336
   *     but not both.</li>
337
   * </ol>
338
   */
339
  @VisibleForTesting
340
  static boolean matchHostName(String hostName, String pattern) {
341
    checkArgument(hostName.length() != 0 && !hostName.startsWith(".") && !hostName.endsWith("."),
1✔
342
        "Invalid host name");
343
    checkArgument(pattern.length() != 0 && !pattern.startsWith(".") && !pattern.endsWith("."),
1✔
344
        "Invalid pattern/domain name");
345

346
    hostName = hostName.toLowerCase(Locale.US);
1✔
347
    pattern = pattern.toLowerCase(Locale.US);
1✔
348
    // hostName and pattern are now in lower case -- domain names are case-insensitive.
349

350
    if (!pattern.contains("*")) {
1✔
351
      // Not a wildcard pattern -- hostName and pattern must match exactly.
352
      return hostName.equals(pattern);
1✔
353
    }
354
    // Wildcard pattern
355

356
    if (pattern.length() == 1) {
1✔
357
      return true;
×
358
    }
359

360
    int index = pattern.indexOf('*');
1✔
361

362
    // At most one asterisk (*) is allowed.
363
    if (pattern.indexOf('*', index + 1) != -1) {
1✔
364
      return false;
×
365
    }
366

367
    // Asterisk can only match prefix or suffix.
368
    if (index != 0 && index != pattern.length() - 1) {
1✔
369
      return false;
×
370
    }
371

372
    // HostName must be at least as long as the pattern because asterisk has to
373
    // match one or more characters.
374
    if (hostName.length() < pattern.length()) {
1✔
375
      return false;
1✔
376
    }
377

378
    if (index == 0 && hostName.endsWith(pattern.substring(1))) {
1✔
379
      // Prefix matching fails.
380
      return true;
1✔
381
    }
382

383
    // Pattern matches hostname if suffix matching succeeds.
384
    return index == pattern.length() - 1
1✔
385
        && hostName.startsWith(pattern.substring(0, pattern.length() - 1));
1✔
386
  }
387

388
  private final class ConfigSelector extends InternalConfigSelector {
1✔
389
    @Override
390
    public Result selectConfig(PickSubchannelArgs args) {
391
      RoutingConfig routingCfg;
392
      RouteData selectedRoute;
393
      String cluster;
394
      ClientInterceptor filters;
395
      Metadata headers = args.getHeaders();
1✔
396
      String path = "/" + args.getMethodDescriptor().getFullMethodName();
1✔
397
      do {
398
        routingCfg = routingConfig;
1✔
399
        if (routingCfg.errorStatus != null) {
1✔
400
          return Result.forError(routingCfg.errorStatus);
1✔
401
        }
402
        selectedRoute = null;
1✔
403
        for (RouteData route : routingCfg.routes) {
1✔
404
          if (RoutingUtils.matchRoute(route.routeMatch, path, headers, random)) {
1✔
405
            selectedRoute = route;
1✔
406
            break;
1✔
407
          }
408
        }
1✔
409
        if (selectedRoute == null) {
1✔
410
          return Result.forError(
1✔
411
              Status.UNAVAILABLE.withDescription("Could not find xDS route matching RPC"));
1✔
412
        }
413
        if (selectedRoute.routeAction == null) {
1✔
414
          return Result.forError(Status.UNAVAILABLE.withDescription(
1✔
415
              "Could not route RPC to Route with non-forwarding action"));
416
        }
417
        RouteAction action = selectedRoute.routeAction;
1✔
418
        if (action.cluster() != null) {
1✔
419
          cluster = prefixedClusterName(action.cluster());
1✔
420
          filters = selectedRoute.filterChoices.get(0);
1✔
421
        } else if (action.weightedClusters() != null) {
1✔
422
          // XdsRouteConfigureResource verifies the total weight will not be 0 or exceed uint32
423
          long totalWeight = 0;
1✔
424
          for (ClusterWeight weightedCluster : action.weightedClusters()) {
1✔
425
            totalWeight += weightedCluster.weight();
1✔
426
          }
1✔
427
          long select = random.nextLong(totalWeight);
1✔
428
          long accumulator = 0;
1✔
429
          for (int i = 0; ; i++) {
1✔
430
            ClusterWeight weightedCluster = action.weightedClusters().get(i);
1✔
431
            accumulator += weightedCluster.weight();
1✔
432
            if (select < accumulator) {
1✔
433
              cluster = prefixedClusterName(weightedCluster.name());
1✔
434
              filters = selectedRoute.filterChoices.get(i);
1✔
435
              break;
1✔
436
            }
437
          }
438
        } else if (action.namedClusterSpecifierPluginConfig() != null) {
1✔
439
          cluster =
1✔
440
              prefixedClusterSpecifierPluginName(action.namedClusterSpecifierPluginConfig().name());
1✔
441
          filters = selectedRoute.filterChoices.get(0);
1✔
442
        } else {
443
          // updateRoutes() discards routes with unknown actions
444
          throw new AssertionError();
×
445
        }
446
      } while (!retainCluster(cluster));
1✔
447

448
      final RouteAction routeAction = selectedRoute.routeAction;
1✔
449
      Long timeoutNanos = null;
1✔
450
      if (enableTimeout) {
1✔
451
        timeoutNanos = routeAction.timeoutNano();
1✔
452
        if (timeoutNanos == null) {
1✔
453
          timeoutNanos = routingCfg.fallbackTimeoutNano;
1✔
454
        }
455
        if (timeoutNanos <= 0) {
1✔
456
          timeoutNanos = null;
1✔
457
        }
458
      }
459
      RetryPolicy retryPolicy = routeAction.retryPolicy();
1✔
460
      // TODO(chengyuanzhang): avoid service config generation and parsing for each call.
461
      Map<String, ?> rawServiceConfig =
1✔
462
          generateServiceConfigWithMethodConfig(timeoutNanos, retryPolicy);
1✔
463
      ConfigOrError parsedServiceConfig = serviceConfigParser.parseServiceConfig(rawServiceConfig);
1✔
464
      Object config = parsedServiceConfig.getConfig();
1✔
465
      if (config == null) {
1✔
466
        releaseCluster(cluster);
×
467
        return Result.forError(
×
468
            parsedServiceConfig.getError().augmentDescription(
×
469
                "Failed to parse service config (method config)"));
470
      }
471
      final String finalCluster = cluster;
1✔
472
      final XdsConfig xdsConfig = routingCfg.xdsConfig;
1✔
473
      final long hash = generateHash(routeAction.hashPolicies(), headers);
1✔
474
      class ClusterSelectionInterceptor implements ClientInterceptor {
1✔
475
        @Override
476
        public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
477
            final MethodDescriptor<ReqT, RespT> method, CallOptions callOptions,
478
            final Channel next) {
479
          CallOptions callOptionsForCluster =
1✔
480
              callOptions.withOption(CLUSTER_SELECTION_KEY, finalCluster)
1✔
481
                  .withOption(XDS_CONFIG_CALL_OPTION_KEY, xdsConfig)
1✔
482
                  .withOption(RPC_HASH_KEY, hash);
1✔
483
          if (routeAction.autoHostRewrite()) {
1✔
484
            callOptionsForCluster = callOptionsForCluster.withOption(AUTO_HOST_REWRITE_KEY, true);
1✔
485
          }
486
          return new SimpleForwardingClientCall<ReqT, RespT>(
1✔
487
              next.newCall(method, callOptionsForCluster)) {
1✔
488
            @Override
489
            public void start(Listener<RespT> listener, Metadata headers) {
490
              listener = new SimpleForwardingClientCallListener<RespT>(listener) {
1✔
491
                boolean committed;
492

493
                @Override
494
                public void onHeaders(Metadata headers) {
495
                  committed = true;
1✔
496
                  releaseCluster(finalCluster);
1✔
497
                  delegate().onHeaders(headers);
1✔
498
                }
1✔
499

500
                @Override
501
                public void onClose(Status status, Metadata trailers) {
502
                  if (!committed) {
1✔
503
                    releaseCluster(finalCluster);
1✔
504
                  }
505
                  delegate().onClose(status, trailers);
1✔
506
                }
1✔
507
              };
508
              delegate().start(listener, headers);
1✔
509
            }
1✔
510
          };
511
        }
512
      }
513

514
      return
1✔
515
          Result.newBuilder()
1✔
516
              .setConfig(config)
1✔
517
              .setInterceptor(combineInterceptors(
1✔
518
                  ImmutableList.of(filters, new ClusterSelectionInterceptor())))
1✔
519
              .build();
1✔
520
    }
521

522
    private boolean retainCluster(String cluster) {
523
      ClusterRefState clusterRefState = clusterRefs.get(cluster);
1✔
524
      if (clusterRefState == null) {
1✔
525
        return false;
×
526
      }
527
      AtomicInteger refCount = clusterRefState.refCount;
1✔
528
      int count;
529
      do {
530
        count = refCount.get();
1✔
531
        if (count == 0) {
1✔
532
          return false;
×
533
        }
534
      } while (!refCount.compareAndSet(count, count + 1));
1✔
535
      return true;
1✔
536
    }
537

538
    private void releaseCluster(final String cluster) {
539
      int count = clusterRefs.get(cluster).refCount.decrementAndGet();
1✔
540
      if (count < 0) {
1✔
541
        throw new AssertionError();
×
542
      }
543
      if (count == 0) {
1✔
544
        syncContext.execute(new Runnable() {
1✔
545
          @Override
546
          public void run() {
547
            if (clusterRefs.get(cluster).refCount.get() != 0) {
1✔
548
              throw new AssertionError();
×
549
            }
550
            clusterRefs.remove(cluster);
1✔
551
            if (resolveState.lastConfigOrStatus.hasValue()) {
1✔
552
              updateResolutionResult(resolveState.lastConfigOrStatus.getValue());
1✔
553
            } else {
554
              resolveState.cleanUpRoutes(resolveState.lastConfigOrStatus.getStatus());
×
555
            }
556
          }
1✔
557
        });
558
      }
559
    }
1✔
560

561
    private long generateHash(List<HashPolicy> hashPolicies, Metadata headers) {
562
      Long hash = null;
1✔
563
      for (HashPolicy policy : hashPolicies) {
1✔
564
        Long newHash = null;
1✔
565
        if (policy.type() == HashPolicy.Type.HEADER) {
1✔
566
          String value = getHeaderValue(headers, policy.headerName());
1✔
567
          if (value != null) {
1✔
568
            if (policy.regEx() != null && policy.regExSubstitution() != null) {
1✔
569
              value = policy.regEx().matcher(value).replaceAll(policy.regExSubstitution());
1✔
570
            }
571
            newHash = hashFunc.hashAsciiString(value);
1✔
572
          }
573
        } else if (policy.type() == HashPolicy.Type.CHANNEL_ID) {
1✔
574
          newHash = hashFunc.hashLong(randomChannelId);
1✔
575
        }
576
        if (newHash != null ) {
1✔
577
          // Rotating the old value prevents duplicate hash rules from cancelling each other out
578
          // and preserves all of the entropy.
579
          long oldHash = hash != null ? ((hash << 1L) | (hash >> 63L)) : 0;
1✔
580
          hash = oldHash ^ newHash;
1✔
581
        }
582
        // If the policy is a terminal policy and a hash has been generated, ignore
583
        // the rest of the hash policies.
584
        if (policy.isTerminal() && hash != null) {
1✔
585
          break;
×
586
        }
587
      }
1✔
588
      return hash == null ? random.nextLong() : hash;
1✔
589
    }
590
  }
591

592
  static final class PassthroughClientInterceptor implements ClientInterceptor {
1✔
593
    @Override
594
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
595
        MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
596
      return next.newCall(method, callOptions);
1✔
597
    }
598
  }
599

600
  private static ClientInterceptor combineInterceptors(final List<ClientInterceptor> interceptors) {
601
    if (interceptors.size() == 0) {
1✔
602
      return new PassthroughClientInterceptor();
1✔
603
    }
604
    if (interceptors.size() == 1) {
1✔
605
      return interceptors.get(0);
1✔
606
    }
607
    return new ClientInterceptor() {
1✔
608
      @Override
609
      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
610
          MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
611
        next = ClientInterceptors.interceptForward(next, interceptors);
1✔
612
        return next.newCall(method, callOptions);
1✔
613
      }
614
    };
615
  }
616

617
  @Nullable
618
  private static String getHeaderValue(Metadata headers, String headerName) {
619
    if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
1✔
620
      return null;
×
621
    }
622
    if (headerName.equals("content-type")) {
1✔
623
      return "application/grpc";
×
624
    }
625
    Metadata.Key<String> key;
626
    try {
627
      key = Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER);
1✔
628
    } catch (IllegalArgumentException e) {
×
629
      return null;
×
630
    }
1✔
631
    Iterable<String> values = headers.getAll(key);
1✔
632
    return values == null ? null : Joiner.on(",").join(values);
1✔
633
  }
634

635
  private static String prefixedClusterName(String name) {
636
    return "cluster:" + name;
1✔
637
  }
638

639
  private static String prefixedClusterSpecifierPluginName(String pluginName) {
640
    return "cluster_specifier_plugin:" + pluginName;
1✔
641
  }
642

643
  class ResolveState implements XdsDependencyManager.XdsConfigWatcher {
644
    private final ConfigOrError emptyServiceConfig =
1✔
645
        serviceConfigParser.parseServiceConfig(Collections.<String, Object>emptyMap());
1✔
646
    private final String authority;
647
    private final XdsDependencyManager xdsDependencyManager;
648
    private boolean stopped;
649
    @Nullable
650
    private Set<String> existingClusters;  // clusters to which new requests can be routed
651
    private StatusOr<XdsConfig> lastConfigOrStatus;
652

653
    private ResolveState(String ldsResourceName) {
1✔
654
      authority = overrideAuthority != null ? overrideAuthority : encodedServiceAuthority;
1✔
655
      xdsDependencyManager =
1✔
656
          new XdsDependencyManager(xdsClient, this, syncContext, authority, ldsResourceName,
1✔
657
              nameResolverArgs, scheduler);
1✔
658
    }
1✔
659

660
    private void shutdown() {
661
      if (stopped) {
1✔
662
        return;
×
663
      }
664

665
      stopped = true;
1✔
666
      xdsDependencyManager.shutdown();
1✔
667
      updateActiveFilters(null);
1✔
668
    }
1✔
669

670
    @Override
671
    public void onUpdate(StatusOr<XdsConfig> updateOrStatus) {
672
      if (stopped) {
1✔
673
        return;
×
674
      }
675
      logger.log(XdsLogLevel.INFO, "Receive XDS resource update: {0}", updateOrStatus);
1✔
676

677
      lastConfigOrStatus = updateOrStatus;
1✔
678
      if (!updateOrStatus.hasValue()) {
1✔
679
        updateActiveFilters(null);
1✔
680
        cleanUpRoutes(updateOrStatus.getStatus());
1✔
681
        return;
1✔
682
      }
683

684
      // Process Route
685
      XdsConfig update = updateOrStatus.getValue();
1✔
686
      HttpConnectionManager httpConnectionManager = update.getListener().httpConnectionManager();
1✔
687
      if (httpConnectionManager == null) {
1✔
688
        logger.log(XdsLogLevel.INFO, "API Listener: httpConnectionManager does not exist.");
×
689
        updateActiveFilters(null);
×
690
        cleanUpRoutes(updateOrStatus.getStatus());
×
691
        return;
×
692
      }
693

694
      VirtualHost virtualHost = update.getVirtualHost();
1✔
695
      ImmutableList<NamedFilterConfig> filterConfigs = httpConnectionManager.httpFilterConfigs();
1✔
696
      long streamDurationNano = httpConnectionManager.httpMaxStreamDurationNano();
1✔
697

698
      updateActiveFilters(filterConfigs);
1✔
699
      updateRoutes(update, virtualHost, streamDurationNano, filterConfigs);
1✔
700
    }
1✔
701

702
    // called in syncContext
703
    private void updateActiveFilters(@Nullable List<NamedFilterConfig> filterConfigs) {
704
      if (filterConfigs == null) {
1✔
705
        filterConfigs = ImmutableList.of();
1✔
706
      }
707
      Set<String> filtersToShutdown = new HashSet<>(activeFilters.keySet());
1✔
708
      for (NamedFilterConfig namedFilter : filterConfigs) {
1✔
709
        String typeUrl = namedFilter.filterConfig.typeUrl();
1✔
710
        String filterKey = namedFilter.filterStateKey();
1✔
711

712
        Filter.Provider provider = filterRegistry.get(typeUrl);
1✔
713
        checkNotNull(provider, "provider %s", typeUrl);
1✔
714
        Filter filter = activeFilters.computeIfAbsent(
1✔
715
            filterKey, k -> provider.newInstance(namedFilter.name));
1✔
716
        checkNotNull(filter, "filter %s", filterKey);
1✔
717
        filtersToShutdown.remove(filterKey);
1✔
718
      }
1✔
719

720
      // Shutdown filters not present in current HCM.
721
      for (String filterKey : filtersToShutdown) {
1✔
722
        Filter filterToShutdown = activeFilters.remove(filterKey);
1✔
723
        checkNotNull(filterToShutdown, "filterToShutdown %s", filterKey);
1✔
724
        filterToShutdown.close();
1✔
725
      }
1✔
726
    }
1✔
727

728
    private void updateRoutes(
729
        XdsConfig xdsConfig,
730
        @Nullable VirtualHost virtualHost,
731
        long httpMaxStreamDurationNano,
732
        @Nullable List<NamedFilterConfig> filterConfigs) {
733
      List<Route> routes = virtualHost.routes();
1✔
734
      ImmutableList.Builder<RouteData> routesData = ImmutableList.builder();
1✔
735

736
      // Populate all clusters to which requests can be routed to through the virtual host.
737
      Set<String> clusters = new HashSet<>();
1✔
738
      // uniqueName -> clusterName
739
      Map<String, String> clusterNameMap = new HashMap<>();
1✔
740
      // uniqueName -> pluginConfig
741
      Map<String, RlsPluginConfig> rlsPluginConfigMap = new HashMap<>();
1✔
742
      for (Route route : routes) {
1✔
743
        RouteAction action = route.routeAction();
1✔
744
        String prefixedName;
745
        if (action == null) {
1✔
746
          routesData.add(new RouteData(route.routeMatch(), null, ImmutableList.of()));
1✔
747
        } else if (action.cluster() != null) {
1✔
748
          prefixedName = prefixedClusterName(action.cluster());
1✔
749
          clusters.add(prefixedName);
1✔
750
          clusterNameMap.put(prefixedName, action.cluster());
1✔
751
          ClientInterceptor filters = createFilters(filterConfigs, virtualHost, route, null);
1✔
752
          routesData.add(new RouteData(route.routeMatch(), route.routeAction(), filters));
1✔
753
        } else if (action.weightedClusters() != null) {
1✔
754
          ImmutableList.Builder<ClientInterceptor> filterList = ImmutableList.builder();
1✔
755
          for (ClusterWeight weightedCluster : action.weightedClusters()) {
1✔
756
            prefixedName = prefixedClusterName(weightedCluster.name());
1✔
757
            clusters.add(prefixedName);
1✔
758
            clusterNameMap.put(prefixedName, weightedCluster.name());
1✔
759
            filterList.add(createFilters(filterConfigs, virtualHost, route, weightedCluster));
1✔
760
          }
1✔
761
          routesData.add(
1✔
762
              new RouteData(route.routeMatch(), route.routeAction(), filterList.build()));
1✔
763
        } else if (action.namedClusterSpecifierPluginConfig() != null) {
1✔
764
          PluginConfig pluginConfig = action.namedClusterSpecifierPluginConfig().config();
1✔
765
          if (pluginConfig instanceof RlsPluginConfig) {
1✔
766
            prefixedName = prefixedClusterSpecifierPluginName(
1✔
767
                action.namedClusterSpecifierPluginConfig().name());
1✔
768
            clusters.add(prefixedName);
1✔
769
            rlsPluginConfigMap.put(prefixedName, (RlsPluginConfig) pluginConfig);
1✔
770
          }
771
          ClientInterceptor filters = createFilters(filterConfigs, virtualHost, route, null);
1✔
772
          routesData.add(new RouteData(route.routeMatch(), route.routeAction(), filters));
1✔
773
        } else {
774
          // Discard route
775
        }
776
      }
1✔
777

778
      // Updates channel's load balancing config whenever the set of selectable clusters changes.
779
      boolean shouldUpdateResult = existingClusters == null;
1✔
780
      Set<String> addedClusters =
781
          existingClusters == null ? clusters : Sets.difference(clusters, existingClusters);
1✔
782
      Set<String> deletedClusters =
783
          existingClusters == null
1✔
784
              ? Collections.emptySet() : Sets.difference(existingClusters, clusters);
1✔
785
      existingClusters = clusters;
1✔
786
      for (String cluster : addedClusters) {
1✔
787
        if (clusterRefs.containsKey(cluster)) {
1✔
788
          clusterRefs.get(cluster).refCount.incrementAndGet();
1✔
789
        } else {
790
          if (clusterNameMap.containsKey(cluster)) {
1✔
791
            clusterRefs.put(
1✔
792
                cluster,
793
                ClusterRefState.forCluster(new AtomicInteger(1), clusterNameMap.get(cluster)));
1✔
794
          }
795
          if (rlsPluginConfigMap.containsKey(cluster)) {
1✔
796
            clusterRefs.put(
1✔
797
                cluster,
798
                ClusterRefState.forRlsPlugin(
1✔
799
                    new AtomicInteger(1), rlsPluginConfigMap.get(cluster)));
1✔
800
          }
801
          shouldUpdateResult = true;
1✔
802
        }
803
      }
1✔
804
      for (String cluster : clusters) {
1✔
805
        RlsPluginConfig rlsPluginConfig = rlsPluginConfigMap.get(cluster);
1✔
806
        if (!Objects.equals(rlsPluginConfig, clusterRefs.get(cluster).rlsPluginConfig)) {
1✔
807
          ClusterRefState newClusterRefState =
1✔
808
              ClusterRefState.forRlsPlugin(clusterRefs.get(cluster).refCount, rlsPluginConfig);
1✔
809
          clusterRefs.put(cluster, newClusterRefState);
1✔
810
          shouldUpdateResult = true;
1✔
811
        }
812
      }
1✔
813
      // Update service config to include newly added clusters.
814
      if (shouldUpdateResult && routingConfig != null) {
1✔
815
        updateResolutionResult(xdsConfig);
1✔
816
        shouldUpdateResult = false;
1✔
817
      }
818
      // Make newly added clusters selectable by config selector and deleted clusters no longer
819
      // selectable.
820
      routingConfig = new RoutingConfig(xdsConfig, httpMaxStreamDurationNano, routesData.build());
1✔
821
      for (String cluster : deletedClusters) {
1✔
822
        int count = clusterRefs.get(cluster).refCount.decrementAndGet();
1✔
823
        if (count == 0) {
1✔
824
          clusterRefs.remove(cluster);
1✔
825
          shouldUpdateResult = true;
1✔
826
        }
827
      }
1✔
828
      if (shouldUpdateResult) {
1✔
829
        updateResolutionResult(xdsConfig);
1✔
830
      }
831
    }
1✔
832

833
    private ClientInterceptor createFilters(
834
        @Nullable List<NamedFilterConfig> filterConfigs,
835
        VirtualHost virtualHost,
836
        Route route,
837
        @Nullable ClusterWeight weightedCluster) {
838
      if (filterConfigs == null) {
1✔
839
        return new PassthroughClientInterceptor();
1✔
840
      }
841

842
      Map<String, FilterConfig> selectedOverrideConfigs =
1✔
843
          new HashMap<>(virtualHost.filterConfigOverrides());
1✔
844
      selectedOverrideConfigs.putAll(route.filterConfigOverrides());
1✔
845
      if (weightedCluster != null) {
1✔
846
        selectedOverrideConfigs.putAll(weightedCluster.filterConfigOverrides());
1✔
847
      }
848

849
      ImmutableList.Builder<ClientInterceptor> filterInterceptors = ImmutableList.builder();
1✔
850
      for (NamedFilterConfig namedFilter : filterConfigs) {
1✔
851
        String name = namedFilter.name;
1✔
852
        FilterConfig config = namedFilter.filterConfig;
1✔
853
        FilterConfig overrideConfig = selectedOverrideConfigs.get(name);
1✔
854
        String filterKey = namedFilter.filterStateKey();
1✔
855

856
        Filter filter = activeFilters.get(filterKey);
1✔
857
        checkNotNull(filter, "activeFilters.get(%s)", filterKey);
1✔
858
        ClientInterceptor interceptor =
1✔
859
            filter.buildClientInterceptor(config, overrideConfig, scheduler);
1✔
860

861
        if (interceptor != null) {
1✔
862
          filterInterceptors.add(interceptor);
1✔
863
        }
864
      }
1✔
865

866
      // Combine interceptors produced by different filters into a single one that executes
867
      // them sequentially. The order is preserved.
868
      return combineInterceptors(filterInterceptors.build());
1✔
869
    }
870

871
    private void cleanUpRoutes(Status error) {
872
      routingConfig = new RoutingConfig(error);
1✔
873
      if (existingClusters != null) {
1✔
874
        for (String cluster : existingClusters) {
1✔
875
          int count = clusterRefs.get(cluster).refCount.decrementAndGet();
1✔
876
          if (count == 0) {
1✔
877
            clusterRefs.remove(cluster);
1✔
878
          }
879
        }
1✔
880
        existingClusters = null;
1✔
881
      }
882

883
      // Without addresses the default LB (normally pick_first) should become TRANSIENT_FAILURE, and
884
      // the config selector handles the error message itself.
885
      listener.onResult2(ResolutionResult.newBuilder()
1✔
886
          .setAttributes(Attributes.newBuilder()
1✔
887
            .set(InternalConfigSelector.KEY, configSelector)
1✔
888
            .build())
1✔
889
          .setServiceConfig(emptyServiceConfig)
1✔
890
          .build());
1✔
891
    }
1✔
892
  }
893

894
  /**
895
   * VirtualHost-level configuration for request routing.
896
   */
897
  private static class RoutingConfig {
898
    final XdsConfig xdsConfig;
899
    final long fallbackTimeoutNano;
900
    final ImmutableList<RouteData> routes;
901
    final Status errorStatus;
902

903
    private RoutingConfig(
904
        XdsConfig xdsConfig, long fallbackTimeoutNano, ImmutableList<RouteData> routes) {
1✔
905
      this.xdsConfig = checkNotNull(xdsConfig, "xdsConfig");
1✔
906
      this.fallbackTimeoutNano = fallbackTimeoutNano;
1✔
907
      this.routes = checkNotNull(routes, "routes");
1✔
908
      this.errorStatus = null;
1✔
909
    }
1✔
910

911
    private RoutingConfig(Status errorStatus) {
1✔
912
      this.xdsConfig = null;
1✔
913
      this.fallbackTimeoutNano = 0;
1✔
914
      this.routes = null;
1✔
915
      this.errorStatus = checkNotNull(errorStatus, "errorStatus");
1✔
916
      checkArgument(!errorStatus.isOk(), "errorStatus should not be okay");
1✔
917
    }
1✔
918
  }
919

920
  static final class RouteData {
921
    final RouteMatch routeMatch;
922
    /** null implies non-forwarding action. */
923
    @Nullable
924
    final RouteAction routeAction;
925
    /**
926
     * Only one of these interceptors should be used per-RPC. There are only multiple values in the
927
     * list for weighted clusters, in which case the order of the list mirrors the weighted
928
     * clusters.
929
     */
930
    final ImmutableList<ClientInterceptor> filterChoices;
931

932
    RouteData(RouteMatch routeMatch, @Nullable RouteAction routeAction, ClientInterceptor filter) {
933
      this(routeMatch, routeAction, ImmutableList.of(filter));
1✔
934
    }
1✔
935

936
    RouteData(
937
        RouteMatch routeMatch,
938
        @Nullable RouteAction routeAction,
939
        ImmutableList<ClientInterceptor> filterChoices) {
1✔
940
      this.routeMatch = checkNotNull(routeMatch, "routeMatch");
1✔
941
      checkArgument(
1✔
942
          routeAction == null || !filterChoices.isEmpty(),
1✔
943
          "filter may be empty only for non-forwarding action");
944
      this.routeAction = routeAction;
1✔
945
      if (routeAction != null && routeAction.weightedClusters() != null) {
1✔
946
        checkArgument(
1✔
947
            routeAction.weightedClusters().size() == filterChoices.size(),
1✔
948
            "filter choices must match size of weighted clusters");
949
      }
950
      for (ClientInterceptor filter : filterChoices) {
1✔
951
        checkNotNull(filter, "entry in filterChoices is null");
1✔
952
      }
1✔
953
      this.filterChoices = checkNotNull(filterChoices, "filterChoices");
1✔
954
    }
1✔
955
  }
956

957
  private static class ClusterRefState {
958
    final AtomicInteger refCount;
959
    @Nullable
960
    final String traditionalCluster;
961
    @Nullable
962
    final RlsPluginConfig rlsPluginConfig;
963

964
    private ClusterRefState(
965
        AtomicInteger refCount, @Nullable String traditionalCluster,
966
        @Nullable RlsPluginConfig rlsPluginConfig) {
1✔
967
      this.refCount = refCount;
1✔
968
      checkArgument(traditionalCluster == null ^ rlsPluginConfig == null,
1✔
969
          "There must be exactly one non-null value in traditionalCluster and pluginConfig");
970
      this.traditionalCluster = traditionalCluster;
1✔
971
      this.rlsPluginConfig = rlsPluginConfig;
1✔
972
    }
1✔
973

974
    private Map<String, ?> toLbPolicy() {
975
      if (traditionalCluster != null) {
1✔
976
        return ImmutableMap.of(
1✔
977
            XdsLbPolicies.CDS_POLICY_NAME,
978
            ImmutableMap.of("cluster", traditionalCluster));
1✔
979
      } else {
980
        ImmutableMap<String, ?> rlsConfig = new ImmutableMap.Builder<String, Object>()
1✔
981
            .put("routeLookupConfig", rlsPluginConfig.config())
1✔
982
            .put(
1✔
983
                "childPolicy",
984
                ImmutableList.of(ImmutableMap.of(XdsLbPolicies.CDS_POLICY_NAME, ImmutableMap.of())))
1✔
985
            .put("childPolicyConfigTargetFieldName", "cluster")
1✔
986
            .buildOrThrow();
1✔
987
        return ImmutableMap.of("rls_experimental", rlsConfig);
1✔
988
      }
989
    }
990

991
    static ClusterRefState forCluster(AtomicInteger refCount, String name) {
992
      return new ClusterRefState(refCount, name, null);
1✔
993
    }
994

995
    static ClusterRefState forRlsPlugin(AtomicInteger refCount, RlsPluginConfig rlsPluginConfig) {
996
      return new ClusterRefState(refCount, null, rlsPluginConfig);
1✔
997
    }
998
  }
999
}
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

© 2025 Coveralls, Inc