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

grpc / grpc-java / #19990

19 Sep 2025 03:23PM UTC coverage: 88.547% (+0.04%) from 88.511%
#19990

push

github

ejona86
xds: Remove verify TODO for onResult2 error status

This had been accidentally left in 0c179e3f9.

Requesting a refresh is pretty close to RetryingNameResolver's behavior
of exponential backoff. While not identical, it is the closest we can
get easily.

34668 of 39152 relevant lines covered (88.55%)

0.89 hits per line

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

93.35
/../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);
1✔
234
    resolveState.start();
1✔
235
  }
1✔
236

237
  @Override
238
  public void refresh() {
239
    if (resolveState != null) {
×
240
      resolveState.refresh();
×
241
    }
242
  }
×
243

244
  private static String expandPercentS(String template, String replacement) {
245
    return template.replace("%s", replacement);
1✔
246
  }
247

248
  @Override
249
  public void shutdown() {
250
    logger.log(XdsLogLevel.INFO, "Shutdown");
1✔
251
    if (resolveState != null) {
1✔
252
      resolveState.shutdown();
1✔
253
    }
254
    if (xdsClient != null) {
1✔
255
      xdsClient = xdsClientPool.returnObject(xdsClient);
1✔
256
    }
257
  }
1✔
258

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

295
  @VisibleForTesting
296
  XdsClient getXdsClient() {
297
    return xdsClient;
1✔
298
  }
299

300
  // called in syncContext
301
  private void updateResolutionResult(XdsConfig xdsConfig) {
302
    syncContext.throwIfNotInThisSynchronizationContext();
1✔
303

304
    ImmutableMap.Builder<String, Object> childPolicy = new ImmutableMap.Builder<>();
1✔
305
    for (String name : clusterRefs.keySet()) {
1✔
306
      Map<String, ?> lbPolicy = clusterRefs.get(name).toLbPolicy();
1✔
307
      childPolicy.put(name, ImmutableMap.of("lbPolicy", ImmutableList.of(lbPolicy)));
1✔
308
    }
1✔
309
    Map<String, ?> rawServiceConfig = ImmutableMap.of(
1✔
310
        "loadBalancingConfig",
311
        ImmutableList.of(ImmutableMap.of(
1✔
312
            XdsLbPolicies.CLUSTER_MANAGER_POLICY_NAME,
313
            ImmutableMap.of("childPolicy", childPolicy.buildOrThrow()))));
1✔
314

315
    if (logger.isLoggable(XdsLogLevel.INFO)) {
1✔
316
      logger.log(
×
317
          XdsLogLevel.INFO, "Generated service config: {0}", new Gson().toJson(rawServiceConfig));
×
318
    }
319
    ConfigOrError parsedServiceConfig = serviceConfigParser.parseServiceConfig(rawServiceConfig);
1✔
320
    Attributes attrs =
321
        Attributes.newBuilder()
1✔
322
            .set(XdsAttributes.XDS_CLIENT_POOL, xdsClientPool)
1✔
323
            .set(XdsAttributes.XDS_CONFIG, xdsConfig)
1✔
324
            .set(XdsAttributes.XDS_CLUSTER_SUBSCRIPT_REGISTRY, resolveState.xdsDependencyManager)
1✔
325
            .set(XdsAttributes.CALL_COUNTER_PROVIDER, callCounterProvider)
1✔
326
            .set(InternalConfigSelector.KEY, configSelector)
1✔
327
            .build();
1✔
328
    ResolutionResult result =
329
        ResolutionResult.newBuilder()
1✔
330
            .setAttributes(attrs)
1✔
331
            .setServiceConfig(parsedServiceConfig)
1✔
332
            .build();
1✔
333
    if (!listener.onResult2(result).isOk()) {
1✔
334
      resolveState.xdsDependencyManager.requestReresolution();
×
335
    }
336
  }
1✔
337

338
  /**
339
   * Returns {@code true} iff {@code hostName} matches the domain name {@code pattern} with
340
   * case-insensitive.
341
   *
342
   * <p>Wildcard pattern rules:
343
   * <ol>
344
   * <li>A single asterisk (*) matches any domain.</li>
345
   * <li>Asterisk (*) is only permitted in the left-most or the right-most part of the pattern,
346
   *     but not both.</li>
347
   * </ol>
348
   */
349
  @VisibleForTesting
350
  static boolean matchHostName(String hostName, String pattern) {
351
    checkArgument(hostName.length() != 0 && !hostName.startsWith(".") && !hostName.endsWith("."),
1✔
352
        "Invalid host name");
353
    checkArgument(pattern.length() != 0 && !pattern.startsWith(".") && !pattern.endsWith("."),
1✔
354
        "Invalid pattern/domain name");
355

356
    hostName = hostName.toLowerCase(Locale.US);
1✔
357
    pattern = pattern.toLowerCase(Locale.US);
1✔
358
    // hostName and pattern are now in lower case -- domain names are case-insensitive.
359

360
    if (!pattern.contains("*")) {
1✔
361
      // Not a wildcard pattern -- hostName and pattern must match exactly.
362
      return hostName.equals(pattern);
1✔
363
    }
364
    // Wildcard pattern
365

366
    if (pattern.length() == 1) {
1✔
367
      return true;
×
368
    }
369

370
    int index = pattern.indexOf('*');
1✔
371

372
    // At most one asterisk (*) is allowed.
373
    if (pattern.indexOf('*', index + 1) != -1) {
1✔
374
      return false;
×
375
    }
376

377
    // Asterisk can only match prefix or suffix.
378
    if (index != 0 && index != pattern.length() - 1) {
1✔
379
      return false;
×
380
    }
381

382
    // HostName must be at least as long as the pattern because asterisk has to
383
    // match one or more characters.
384
    if (hostName.length() < pattern.length()) {
1✔
385
      return false;
1✔
386
    }
387

388
    if (index == 0 && hostName.endsWith(pattern.substring(1))) {
1✔
389
      // Prefix matching fails.
390
      return true;
1✔
391
    }
392

393
    // Pattern matches hostname if suffix matching succeeds.
394
    return index == pattern.length() - 1
1✔
395
        && hostName.startsWith(pattern.substring(0, pattern.length() - 1));
1✔
396
  }
397

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

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

503
                @Override
504
                public void onHeaders(Metadata headers) {
505
                  committed = true;
1✔
506
                  releaseCluster(finalCluster);
1✔
507
                  delegate().onHeaders(headers);
1✔
508
                }
1✔
509

510
                @Override
511
                public void onClose(Status status, Metadata trailers) {
512
                  if (!committed) {
1✔
513
                    releaseCluster(finalCluster);
1✔
514
                  }
515
                  delegate().onClose(status, trailers);
1✔
516
                }
1✔
517
              };
518
              delegate().start(listener, headers);
1✔
519
            }
1✔
520
          };
521
        }
522
      }
523

524
      return
1✔
525
          Result.newBuilder()
1✔
526
              .setConfig(config)
1✔
527
              .setInterceptor(combineInterceptors(
1✔
528
                  ImmutableList.of(filters, new ClusterSelectionInterceptor())))
1✔
529
              .build();
1✔
530
    }
531

532
    private boolean retainCluster(String cluster) {
533
      ClusterRefState clusterRefState = clusterRefs.get(cluster);
1✔
534
      if (clusterRefState == null) {
1✔
535
        return false;
×
536
      }
537
      AtomicInteger refCount = clusterRefState.refCount;
1✔
538
      int count;
539
      do {
540
        count = refCount.get();
1✔
541
        if (count == 0) {
1✔
542
          return false;
×
543
        }
544
      } while (!refCount.compareAndSet(count, count + 1));
1✔
545
      return true;
1✔
546
    }
547

548
    private void releaseCluster(final String cluster) {
549
      int count = clusterRefs.get(cluster).refCount.decrementAndGet();
1✔
550
      if (count < 0) {
1✔
551
        throw new AssertionError();
×
552
      }
553
      if (count == 0) {
1✔
554
        syncContext.execute(new Runnable() {
1✔
555
          @Override
556
          public void run() {
557
            if (clusterRefs.get(cluster).refCount.get() != 0) {
1✔
558
              throw new AssertionError();
×
559
            }
560
            clusterRefs.remove(cluster).close();
1✔
561
            if (resolveState.lastConfigOrStatus.hasValue()) {
1✔
562
              updateResolutionResult(resolveState.lastConfigOrStatus.getValue());
1✔
563
            } else {
564
              resolveState.cleanUpRoutes(resolveState.lastConfigOrStatus.getStatus());
×
565
            }
566
          }
1✔
567
        });
568
      }
569
    }
1✔
570

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

602
  static final class PassthroughClientInterceptor implements ClientInterceptor {
1✔
603
    @Override
604
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
605
        MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
606
      return next.newCall(method, callOptions);
1✔
607
    }
608
  }
609

610
  private static ClientInterceptor combineInterceptors(final List<ClientInterceptor> interceptors) {
611
    if (interceptors.size() == 0) {
1✔
612
      return new PassthroughClientInterceptor();
1✔
613
    }
614
    if (interceptors.size() == 1) {
1✔
615
      return interceptors.get(0);
1✔
616
    }
617
    return new ClientInterceptor() {
1✔
618
      @Override
619
      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
620
          MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
621
        next = ClientInterceptors.interceptForward(next, interceptors);
1✔
622
        return next.newCall(method, callOptions);
1✔
623
      }
624
    };
625
  }
626

627
  @Nullable
628
  private static String getHeaderValue(Metadata headers, String headerName) {
629
    if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
1✔
630
      return null;
×
631
    }
632
    if (headerName.equals("content-type")) {
1✔
633
      return "application/grpc";
×
634
    }
635
    Metadata.Key<String> key;
636
    try {
637
      key = Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER);
1✔
638
    } catch (IllegalArgumentException e) {
×
639
      return null;
×
640
    }
1✔
641
    Iterable<String> values = headers.getAll(key);
1✔
642
    return values == null ? null : Joiner.on(",").join(values);
1✔
643
  }
644

645
  private static String prefixedClusterName(String name) {
646
    return "cluster:" + name;
1✔
647
  }
648

649
  private static String prefixedClusterSpecifierPluginName(String pluginName) {
650
    return "cluster_specifier_plugin:" + pluginName;
1✔
651
  }
652

653
  class ResolveState implements XdsDependencyManager.XdsConfigWatcher {
1✔
654
    private final ConfigOrError emptyServiceConfig =
1✔
655
        serviceConfigParser.parseServiceConfig(Collections.<String, Object>emptyMap());
1✔
656
    private final String authority;
657
    private final XdsDependencyManager xdsDependencyManager;
658
    private boolean stopped;
659
    @Nullable
660
    private Set<String> existingClusters;  // clusters to which new requests can be routed
661
    private StatusOr<XdsConfig> lastConfigOrStatus;
662

663
    private ResolveState(String ldsResourceName) {
1✔
664
      authority = overrideAuthority != null ? overrideAuthority : encodedServiceAuthority;
1✔
665
      xdsDependencyManager =
1✔
666
          new XdsDependencyManager(xdsClient, syncContext, authority, ldsResourceName,
1✔
667
              nameResolverArgs);
1✔
668
    }
1✔
669

670
    void start() {
671
      xdsDependencyManager.start(this);
1✔
672
    }
1✔
673

674
    void refresh() {
675
      xdsDependencyManager.requestReresolution();
×
676
    }
×
677

678
    private void shutdown() {
679
      if (stopped) {
1✔
680
        return;
×
681
      }
682

683
      stopped = true;
1✔
684
      xdsDependencyManager.shutdown();
1✔
685
      updateActiveFilters(null);
1✔
686
    }
1✔
687

688
    @Override
689
    public void onUpdate(StatusOr<XdsConfig> updateOrStatus) {
690
      if (stopped) {
1✔
691
        return;
×
692
      }
693
      logger.log(XdsLogLevel.INFO, "Receive XDS resource update: {0}", updateOrStatus);
1✔
694

695
      lastConfigOrStatus = updateOrStatus;
1✔
696
      if (!updateOrStatus.hasValue()) {
1✔
697
        updateActiveFilters(null);
1✔
698
        cleanUpRoutes(updateOrStatus.getStatus());
1✔
699
        return;
1✔
700
      }
701

702
      // Process Route
703
      XdsConfig update = updateOrStatus.getValue();
1✔
704
      HttpConnectionManager httpConnectionManager = update.getListener().httpConnectionManager();
1✔
705
      if (httpConnectionManager == null) {
1✔
706
        logger.log(XdsLogLevel.INFO, "API Listener: httpConnectionManager does not exist.");
×
707
        updateActiveFilters(null);
×
708
        cleanUpRoutes(updateOrStatus.getStatus());
×
709
        return;
×
710
      }
711

712
      VirtualHost virtualHost = update.getVirtualHost();
1✔
713
      ImmutableList<NamedFilterConfig> filterConfigs = httpConnectionManager.httpFilterConfigs();
1✔
714
      long streamDurationNano = httpConnectionManager.httpMaxStreamDurationNano();
1✔
715

716
      updateActiveFilters(filterConfigs);
1✔
717
      updateRoutes(update, virtualHost, streamDurationNano, filterConfigs);
1✔
718
    }
1✔
719

720
    // called in syncContext
721
    private void updateActiveFilters(@Nullable List<NamedFilterConfig> filterConfigs) {
722
      if (filterConfigs == null) {
1✔
723
        filterConfigs = ImmutableList.of();
1✔
724
      }
725
      Set<String> filtersToShutdown = new HashSet<>(activeFilters.keySet());
1✔
726
      for (NamedFilterConfig namedFilter : filterConfigs) {
1✔
727
        String typeUrl = namedFilter.filterConfig.typeUrl();
1✔
728
        String filterKey = namedFilter.filterStateKey();
1✔
729

730
        Filter.Provider provider = filterRegistry.get(typeUrl);
1✔
731
        checkNotNull(provider, "provider %s", typeUrl);
1✔
732
        Filter filter = activeFilters.computeIfAbsent(
1✔
733
            filterKey, k -> provider.newInstance(namedFilter.name));
1✔
734
        checkNotNull(filter, "filter %s", filterKey);
1✔
735
        filtersToShutdown.remove(filterKey);
1✔
736
      }
1✔
737

738
      // Shutdown filters not present in current HCM.
739
      for (String filterKey : filtersToShutdown) {
1✔
740
        Filter filterToShutdown = activeFilters.remove(filterKey);
1✔
741
        checkNotNull(filterToShutdown, "filterToShutdown %s", filterKey);
1✔
742
        filterToShutdown.close();
1✔
743
      }
1✔
744
    }
1✔
745

746
    private void updateRoutes(
747
        XdsConfig xdsConfig,
748
        @Nullable VirtualHost virtualHost,
749
        long httpMaxStreamDurationNano,
750
        @Nullable List<NamedFilterConfig> filterConfigs) {
751
      List<Route> routes = virtualHost.routes();
1✔
752
      ImmutableList.Builder<RouteData> routesData = ImmutableList.builder();
1✔
753

754
      // Populate all clusters to which requests can be routed to through the virtual host.
755
      Set<String> clusters = new HashSet<>();
1✔
756
      // uniqueName -> clusterName
757
      Map<String, String> clusterNameMap = new HashMap<>();
1✔
758
      // uniqueName -> pluginConfig
759
      Map<String, RlsPluginConfig> rlsPluginConfigMap = new HashMap<>();
1✔
760
      for (Route route : routes) {
1✔
761
        RouteAction action = route.routeAction();
1✔
762
        String prefixedName;
763
        if (action == null) {
1✔
764
          routesData.add(new RouteData(route.routeMatch(), null, ImmutableList.of()));
1✔
765
        } else if (action.cluster() != null) {
1✔
766
          prefixedName = prefixedClusterName(action.cluster());
1✔
767
          clusters.add(prefixedName);
1✔
768
          clusterNameMap.put(prefixedName, action.cluster());
1✔
769
          ClientInterceptor filters = createFilters(filterConfigs, virtualHost, route, null);
1✔
770
          routesData.add(new RouteData(route.routeMatch(), route.routeAction(), filters));
1✔
771
        } else if (action.weightedClusters() != null) {
1✔
772
          ImmutableList.Builder<ClientInterceptor> filterList = ImmutableList.builder();
1✔
773
          for (ClusterWeight weightedCluster : action.weightedClusters()) {
1✔
774
            prefixedName = prefixedClusterName(weightedCluster.name());
1✔
775
            clusters.add(prefixedName);
1✔
776
            clusterNameMap.put(prefixedName, weightedCluster.name());
1✔
777
            filterList.add(createFilters(filterConfigs, virtualHost, route, weightedCluster));
1✔
778
          }
1✔
779
          routesData.add(
1✔
780
              new RouteData(route.routeMatch(), route.routeAction(), filterList.build()));
1✔
781
        } else if (action.namedClusterSpecifierPluginConfig() != null) {
1✔
782
          PluginConfig pluginConfig = action.namedClusterSpecifierPluginConfig().config();
1✔
783
          if (pluginConfig instanceof RlsPluginConfig) {
1✔
784
            prefixedName = prefixedClusterSpecifierPluginName(
1✔
785
                action.namedClusterSpecifierPluginConfig().name());
1✔
786
            clusters.add(prefixedName);
1✔
787
            rlsPluginConfigMap.put(prefixedName, (RlsPluginConfig) pluginConfig);
1✔
788
          }
789
          ClientInterceptor filters = createFilters(filterConfigs, virtualHost, route, null);
1✔
790
          routesData.add(new RouteData(route.routeMatch(), route.routeAction(), filters));
1✔
791
        } else {
792
          // Discard route
793
        }
794
      }
1✔
795

796
      // Updates channel's load balancing config whenever the set of selectable clusters changes.
797
      boolean shouldUpdateResult = existingClusters == null;
1✔
798
      Set<String> addedClusters =
799
          existingClusters == null ? clusters : Sets.difference(clusters, existingClusters);
1✔
800
      Set<String> deletedClusters =
801
          existingClusters == null
1✔
802
              ? Collections.emptySet() : Sets.difference(existingClusters, clusters);
1✔
803
      existingClusters = clusters;
1✔
804
      for (String cluster : addedClusters) {
1✔
805
        if (clusterRefs.containsKey(cluster)) {
1✔
806
          clusterRefs.get(cluster).refCount.incrementAndGet();
1✔
807
        } else {
808
          if (clusterNameMap.containsKey(cluster)) {
1✔
809
            assert cluster.startsWith("cluster:");
1✔
810
            XdsConfig.Subscription subscription =
1✔
811
                xdsDependencyManager.subscribeToCluster(cluster.substring("cluster:".length()));
1✔
812
            clusterRefs.put(
1✔
813
                cluster,
814
                ClusterRefState.forCluster(
1✔
815
                    new AtomicInteger(1), clusterNameMap.get(cluster), subscription));
1✔
816
          }
817
          if (rlsPluginConfigMap.containsKey(cluster)) {
1✔
818
            clusterRefs.put(
1✔
819
                cluster,
820
                ClusterRefState.forRlsPlugin(
1✔
821
                    new AtomicInteger(1), rlsPluginConfigMap.get(cluster)));
1✔
822
          }
823
          shouldUpdateResult = true;
1✔
824
        }
825
      }
1✔
826
      for (String cluster : clusters) {
1✔
827
        RlsPluginConfig rlsPluginConfig = rlsPluginConfigMap.get(cluster);
1✔
828
        if (!Objects.equals(rlsPluginConfig, clusterRefs.get(cluster).rlsPluginConfig)) {
1✔
829
          ClusterRefState newClusterRefState =
1✔
830
              ClusterRefState.forRlsPlugin(clusterRefs.get(cluster).refCount, rlsPluginConfig);
1✔
831
          clusterRefs.put(cluster, newClusterRefState);
1✔
832
          shouldUpdateResult = true;
1✔
833
        }
834
      }
1✔
835
      // Update service config to include newly added clusters.
836
      if (shouldUpdateResult && routingConfig != null) {
1✔
837
        updateResolutionResult(xdsConfig);
1✔
838
        shouldUpdateResult = false;
1✔
839
      } else {
840
        // Need to update at least once
841
        shouldUpdateResult = true;
1✔
842
      }
843
      // Make newly added clusters selectable by config selector and deleted clusters no longer
844
      // selectable.
845
      routingConfig = new RoutingConfig(xdsConfig, httpMaxStreamDurationNano, routesData.build());
1✔
846
      for (String cluster : deletedClusters) {
1✔
847
        int count = clusterRefs.get(cluster).refCount.decrementAndGet();
1✔
848
        if (count == 0) {
1✔
849
          clusterRefs.remove(cluster).close();
1✔
850
          shouldUpdateResult = true;
1✔
851
        }
852
      }
1✔
853
      if (shouldUpdateResult) {
1✔
854
        updateResolutionResult(xdsConfig);
1✔
855
      }
856
    }
1✔
857

858
    private ClientInterceptor createFilters(
859
        @Nullable List<NamedFilterConfig> filterConfigs,
860
        VirtualHost virtualHost,
861
        Route route,
862
        @Nullable ClusterWeight weightedCluster) {
863
      if (filterConfigs == null) {
1✔
864
        return new PassthroughClientInterceptor();
1✔
865
      }
866

867
      Map<String, FilterConfig> selectedOverrideConfigs =
1✔
868
          new HashMap<>(virtualHost.filterConfigOverrides());
1✔
869
      selectedOverrideConfigs.putAll(route.filterConfigOverrides());
1✔
870
      if (weightedCluster != null) {
1✔
871
        selectedOverrideConfigs.putAll(weightedCluster.filterConfigOverrides());
1✔
872
      }
873

874
      ImmutableList.Builder<ClientInterceptor> filterInterceptors = ImmutableList.builder();
1✔
875
      for (NamedFilterConfig namedFilter : filterConfigs) {
1✔
876
        String name = namedFilter.name;
1✔
877
        FilterConfig config = namedFilter.filterConfig;
1✔
878
        FilterConfig overrideConfig = selectedOverrideConfigs.get(name);
1✔
879
        String filterKey = namedFilter.filterStateKey();
1✔
880

881
        Filter filter = activeFilters.get(filterKey);
1✔
882
        checkNotNull(filter, "activeFilters.get(%s)", filterKey);
1✔
883
        ClientInterceptor interceptor =
1✔
884
            filter.buildClientInterceptor(config, overrideConfig, scheduler);
1✔
885

886
        if (interceptor != null) {
1✔
887
          filterInterceptors.add(interceptor);
1✔
888
        }
889
      }
1✔
890

891
      // Combine interceptors produced by different filters into a single one that executes
892
      // them sequentially. The order is preserved.
893
      return combineInterceptors(filterInterceptors.build());
1✔
894
    }
895

896
    private void cleanUpRoutes(Status error) {
897
      routingConfig = new RoutingConfig(error);
1✔
898
      if (existingClusters != null) {
1✔
899
        for (String cluster : existingClusters) {
1✔
900
          int count = clusterRefs.get(cluster).refCount.decrementAndGet();
1✔
901
          if (count == 0) {
1✔
902
            clusterRefs.remove(cluster).close();
1✔
903
          }
904
        }
1✔
905
        existingClusters = null;
1✔
906
      }
907

908
      // Without addresses the default LB (normally pick_first) should become TRANSIENT_FAILURE, and
909
      // the config selector handles the error message itself.
910
      listener.onResult2(ResolutionResult.newBuilder()
1✔
911
          .setAttributes(Attributes.newBuilder()
1✔
912
            .set(InternalConfigSelector.KEY, configSelector)
1✔
913
            .build())
1✔
914
          .setServiceConfig(emptyServiceConfig)
1✔
915
          .build());
1✔
916
    }
1✔
917
  }
918

919
  /**
920
   * VirtualHost-level configuration for request routing.
921
   */
922
  private static class RoutingConfig {
923
    final XdsConfig xdsConfig;
924
    final long fallbackTimeoutNano;
925
    final ImmutableList<RouteData> routes;
926
    final Status errorStatus;
927

928
    private RoutingConfig(
929
        XdsConfig xdsConfig, long fallbackTimeoutNano, ImmutableList<RouteData> routes) {
1✔
930
      this.xdsConfig = checkNotNull(xdsConfig, "xdsConfig");
1✔
931
      this.fallbackTimeoutNano = fallbackTimeoutNano;
1✔
932
      this.routes = checkNotNull(routes, "routes");
1✔
933
      this.errorStatus = null;
1✔
934
    }
1✔
935

936
    private RoutingConfig(Status errorStatus) {
1✔
937
      this.xdsConfig = null;
1✔
938
      this.fallbackTimeoutNano = 0;
1✔
939
      this.routes = null;
1✔
940
      this.errorStatus = checkNotNull(errorStatus, "errorStatus");
1✔
941
      checkArgument(!errorStatus.isOk(), "errorStatus should not be okay");
1✔
942
    }
1✔
943
  }
944

945
  static final class RouteData {
946
    final RouteMatch routeMatch;
947
    /** null implies non-forwarding action. */
948
    @Nullable
949
    final RouteAction routeAction;
950
    /**
951
     * Only one of these interceptors should be used per-RPC. There are only multiple values in the
952
     * list for weighted clusters, in which case the order of the list mirrors the weighted
953
     * clusters.
954
     */
955
    final ImmutableList<ClientInterceptor> filterChoices;
956

957
    RouteData(RouteMatch routeMatch, @Nullable RouteAction routeAction, ClientInterceptor filter) {
958
      this(routeMatch, routeAction, ImmutableList.of(filter));
1✔
959
    }
1✔
960

961
    RouteData(
962
        RouteMatch routeMatch,
963
        @Nullable RouteAction routeAction,
964
        ImmutableList<ClientInterceptor> filterChoices) {
1✔
965
      this.routeMatch = checkNotNull(routeMatch, "routeMatch");
1✔
966
      checkArgument(
1✔
967
          routeAction == null || !filterChoices.isEmpty(),
1✔
968
          "filter may be empty only for non-forwarding action");
969
      this.routeAction = routeAction;
1✔
970
      if (routeAction != null && routeAction.weightedClusters() != null) {
1✔
971
        checkArgument(
1✔
972
            routeAction.weightedClusters().size() == filterChoices.size(),
1✔
973
            "filter choices must match size of weighted clusters");
974
      }
975
      for (ClientInterceptor filter : filterChoices) {
1✔
976
        checkNotNull(filter, "entry in filterChoices is null");
1✔
977
      }
1✔
978
      this.filterChoices = checkNotNull(filterChoices, "filterChoices");
1✔
979
    }
1✔
980
  }
981

982
  private static class ClusterRefState {
983
    final AtomicInteger refCount;
984
    @Nullable
985
    final String traditionalCluster;
986
    @Nullable
987
    final RlsPluginConfig rlsPluginConfig;
988
    @Nullable
989
    final XdsConfig.Subscription subscription;
990

991
    private ClusterRefState(
992
        AtomicInteger refCount, @Nullable String traditionalCluster,
993
        @Nullable RlsPluginConfig rlsPluginConfig, @Nullable XdsConfig.Subscription subscription) {
1✔
994
      this.refCount = refCount;
1✔
995
      checkArgument(traditionalCluster == null ^ rlsPluginConfig == null,
1✔
996
          "There must be exactly one non-null value in traditionalCluster and pluginConfig");
997
      this.traditionalCluster = traditionalCluster;
1✔
998
      this.rlsPluginConfig = rlsPluginConfig;
1✔
999
      this.subscription = subscription;
1✔
1000
    }
1✔
1001

1002
    private Map<String, ?> toLbPolicy() {
1003
      if (traditionalCluster != null) {
1✔
1004
        return ImmutableMap.of(
1✔
1005
            XdsLbPolicies.CDS_POLICY_NAME,
1006
            ImmutableMap.of("cluster", traditionalCluster));
1✔
1007
      } else {
1008
        ImmutableMap<String, ?> rlsConfig = new ImmutableMap.Builder<String, Object>()
1✔
1009
            .put("routeLookupConfig", rlsPluginConfig.config())
1✔
1010
            .put(
1✔
1011
                "childPolicy",
1012
                ImmutableList.of(ImmutableMap.of(XdsLbPolicies.CDS_POLICY_NAME, ImmutableMap.of(
1✔
1013
                    "is_dynamic", true))))
1✔
1014
            .put("childPolicyConfigTargetFieldName", "cluster")
1✔
1015
            .buildOrThrow();
1✔
1016
        return ImmutableMap.of("rls_experimental", rlsConfig);
1✔
1017
      }
1018
    }
1019

1020
    private void close() {
1021
      if (subscription != null) {
1✔
1022
        subscription.close();
1✔
1023
      }
1024
    }
1✔
1025

1026
    static ClusterRefState forCluster(
1027
        AtomicInteger refCount, String name, XdsConfig.Subscription subscription) {
1028
      return new ClusterRefState(refCount, name, null, checkNotNull(subscription, "subscription"));
1✔
1029
    }
1030

1031
    static ClusterRefState forRlsPlugin(
1032
        AtomicInteger refCount,
1033
        RlsPluginConfig rlsPluginConfig) {
1034
      return new ClusterRefState(refCount, null, rlsPluginConfig, null);
1✔
1035
    }
1036
  }
1037
}
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