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

grpc / grpc-java / #19786

22 Apr 2025 09:04AM UTC coverage: 88.62% (+0.03%) from 88.591%
#19786

push

github

web-flow
xds: add the missing xds.authority metric (#12018)

This completes the [XDS client metrics](https://github.com/grpc/proposal/blob/master/A78-grpc-metrics-wrr-pf-xds.md#xdsclient) by adding the remaining grpc.xds.authority metric.

34771 of 39236 relevant lines covered (88.62%)

0.89 hits per line

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

86.79
/../xds/src/main/java/io/grpc/xds/client/XdsClient.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.client;
18

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

22
import com.google.common.base.Joiner;
23
import com.google.common.base.Splitter;
24
import com.google.common.net.UrlEscapers;
25
import com.google.common.util.concurrent.ListenableFuture;
26
import com.google.common.util.concurrent.MoreExecutors;
27
import com.google.protobuf.Any;
28
import io.grpc.ExperimentalApi;
29
import io.grpc.Status;
30
import io.grpc.xds.client.Bootstrapper.ServerInfo;
31
import java.net.URI;
32
import java.net.URISyntaxException;
33
import java.util.ArrayList;
34
import java.util.Collection;
35
import java.util.Collections;
36
import java.util.List;
37
import java.util.Map;
38
import java.util.concurrent.Executor;
39
import java.util.concurrent.Future;
40
import java.util.concurrent.atomic.AtomicInteger;
41
import javax.annotation.Nullable;
42

43
/**
44
 * An {@link XdsClient} instance encapsulates all of the logic for communicating with the xDS
45
 * server. It may create multiple RPC streams (or a single ADS stream) for a series of xDS
46
 * protocols (e.g., LDS, RDS, VHDS, CDS and EDS) over a single channel. Watch-based interfaces
47
 * are provided for each set of data needed by gRPC.
48
 */
49
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/10862")
50
public abstract class XdsClient {
1✔
51

52
  public static boolean isResourceNameValid(String resourceName, String typeUrl) {
53
    checkNotNull(resourceName, "resourceName");
1✔
54
    if (!resourceName.startsWith(XDSTP_SCHEME)) {
1✔
55
      return true;
1✔
56
    }
57
    URI uri;
58
    try {
59
      uri = new URI(resourceName);
1✔
60
    } catch (URISyntaxException e) {
×
61
      return false;
×
62
    }
1✔
63
    String path = uri.getPath();
1✔
64
    // path must be in the form of /{resource type}/{id/*}
65
    Splitter slashSplitter = Splitter.on('/').omitEmptyStrings();
1✔
66
    if (path == null) {
1✔
67
      return false;
×
68
    }
69
    List<String> pathSegs = slashSplitter.splitToList(path);
1✔
70
    if (pathSegs.size() < 2) {
1✔
71
      return false;
1✔
72
    }
73
    String type = pathSegs.get(0);
1✔
74
    if (!type.equals(slashSplitter.splitToList(typeUrl).get(1))) {
1✔
75
      return false;
1✔
76
    }
77
    return true;
1✔
78
  }
79

80
  /*
81
   * Convert the XDSTP resource name to its canonical version.
82
   */
83
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/10862")
84
  public static String canonifyResourceName(String resourceName) {
85
    checkNotNull(resourceName, "resourceName");
1✔
86
    if (!resourceName.startsWith(XDSTP_SCHEME)) {
1✔
87
      return resourceName;
1✔
88
    }
89
    URI uri = URI.create(resourceName);
1✔
90
    String rawQuery = uri.getRawQuery();
1✔
91
    Splitter ampSplitter = Splitter.on('&').omitEmptyStrings();
1✔
92
    if (rawQuery == null) {
1✔
93
      return resourceName;
1✔
94
    }
95
    List<String> queries = ampSplitter.splitToList(rawQuery);
1✔
96
    if (queries.size() < 2) {
1✔
97
      return resourceName;
1✔
98
    }
99
    List<String> canonicalContextParams = new ArrayList<>(queries.size());
1✔
100
    for (String query : queries) {
1✔
101
      canonicalContextParams.add(query);
1✔
102
    }
1✔
103
    Collections.sort(canonicalContextParams);
1✔
104
    String canonifiedQuery = Joiner.on('&').join(canonicalContextParams);
1✔
105
    return resourceName.replace(rawQuery, canonifiedQuery);
1✔
106
  }
107

108
  /*
109
   * Percent encode the input using the url path segment escaper.
110
   */
111
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/10862")
112
  public static String percentEncodePath(String input) {
113
    Iterable<String> pathSegs = Splitter.on('/').split(input);
1✔
114
    List<String> encodedSegs = new ArrayList<>();
1✔
115
    for (String pathSeg : pathSegs) {
1✔
116
      encodedSegs.add(UrlEscapers.urlPathSegmentEscaper().escape(pathSeg));
1✔
117
    }
1✔
118
    return Joiner.on('/').join(encodedSegs);
1✔
119
  }
120

121
  /**
122
   * Returns the authority from the resource name.
123
   */
124
  public static String getAuthorityFromResourceName(String resourceNames) {
125
    String authority;
126
    if (resourceNames.startsWith(XDSTP_SCHEME)) {
1✔
127
      URI uri = URI.create(resourceNames);
1✔
128
      authority = uri.getAuthority();
1✔
129
      if (authority == null) {
1✔
130
        authority = "";
1✔
131
      }
132
    } else {
1✔
133
      authority = null;
1✔
134
    }
135
    return authority;
1✔
136
  }
137

138
  public interface ResourceUpdate {}
139

140
  /**
141
   * Watcher interface for a single requested xDS resource.
142
   */
143
  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/10862")
144
  public interface ResourceWatcher<T extends ResourceUpdate> {
145

146
    /**
147
     * Called when the resource discovery RPC encounters some transient error.
148
     *
149
     * <p>Note that we expect that the implementer to:
150
     * - Comply with the guarantee to not generate certain statuses by the library:
151
     *   https://grpc.github.io/grpc/core/md_doc_statuscodes.html. If the code needs to be
152
     *   propagated to the channel, override it with {@link io.grpc.Status.Code#UNAVAILABLE}.
153
     * - Keep {@link Status} description in one form or another, as it contains valuable debugging
154
     *   information.
155
     */
156
    void onError(Status error);
157

158
    /**
159
     * Called when the requested resource is not available.
160
     *
161
     * @param resourceName name of the resource requested in discovery request.
162
     */
163
    void onResourceDoesNotExist(String resourceName);
164

165
    void onChanged(T update);
166
  }
167

168
  /**
169
   * The metadata of the xDS resource; used by the xDS config dump.
170
   */
171
  public static final class ResourceMetadata {
172
    private final String version;
173
    private final ResourceMetadataStatus status;
174
    private final long updateTimeNanos;
175
    private final boolean cached;
176
    @Nullable private final Any rawResource;
177
    @Nullable private final UpdateFailureState errorState;
178

179
    private ResourceMetadata(
180
        ResourceMetadataStatus status, String version, long updateTimeNanos, boolean cached,
181
        @Nullable Any rawResource, @Nullable UpdateFailureState errorState) {
1✔
182
      this.status = checkNotNull(status, "status");
1✔
183
      this.version = checkNotNull(version, "version");
1✔
184
      this.updateTimeNanos = updateTimeNanos;
1✔
185
      this.cached = cached;
1✔
186
      this.rawResource = rawResource;
1✔
187
      this.errorState = errorState;
1✔
188
    }
1✔
189

190
    public static ResourceMetadata newResourceMetadataUnknown() {
191
      return new ResourceMetadata(ResourceMetadataStatus.UNKNOWN, "", 0, false,null, null);
1✔
192
    }
193

194
    public static ResourceMetadata newResourceMetadataRequested() {
195
      return new ResourceMetadata(ResourceMetadataStatus.REQUESTED, "", 0, false, null, null);
1✔
196
    }
197

198
    public static ResourceMetadata newResourceMetadataDoesNotExist() {
199
      return new ResourceMetadata(ResourceMetadataStatus.DOES_NOT_EXIST, "", 0, false, null, null);
1✔
200
    }
201

202
    public static ResourceMetadata newResourceMetadataAcked(
203
        Any rawResource, String version, long updateTimeNanos) {
204
      checkNotNull(rawResource, "rawResource");
1✔
205
      return new ResourceMetadata(
1✔
206
          ResourceMetadataStatus.ACKED, version, updateTimeNanos, true, rawResource, null);
207
    }
208

209
    public static ResourceMetadata newResourceMetadataNacked(
210
        ResourceMetadata metadata, String failedVersion, long failedUpdateTime,
211
        String failedDetails, boolean cached) {
212
      checkNotNull(metadata, "metadata");
1✔
213
      return new ResourceMetadata(ResourceMetadataStatus.NACKED,
1✔
214
          metadata.getVersion(), metadata.getUpdateTimeNanos(), cached, metadata.getRawResource(),
1✔
215
          new UpdateFailureState(failedVersion, failedUpdateTime, failedDetails));
216
    }
217

218
    /** The last successfully updated version of the resource. */
219
    public String getVersion() {
220
      return version;
1✔
221
    }
222

223
    /** The client status of this resource. */
224
    public ResourceMetadataStatus getStatus() {
225
      return status;
1✔
226
    }
227

228
    /** The timestamp when the resource was last successfully updated. */
229
    public long getUpdateTimeNanos() {
230
      return updateTimeNanos;
1✔
231
    }
232

233
    /** Returns whether the resource was cached. */
234
    public boolean isCached() {
235
      return cached;
1✔
236
    }
237

238
    /** The last successfully updated xDS resource as it was returned by the server. */
239
    @Nullable
240
    public Any getRawResource() {
241
      return rawResource;
1✔
242
    }
243

244
    /** The metadata capturing the error details of the last rejected update of the resource. */
245
    @Nullable
246
    public UpdateFailureState getErrorState() {
247
      return errorState;
1✔
248
    }
249

250
    /**
251
     * Resource status from the view of a xDS client, which tells the synchronization
252
     * status between the xDS client and the xDS server.
253
     *
254
     * <p>This is a native representation of xDS ConfigDump ClientResourceStatus, see
255
     * <a href="https://github.com/envoyproxy/envoy/blob/main/api/envoy/admin/v3/config_dump.proto">
256
     * config_dump.proto</a>
257
     */
258
    public enum ResourceMetadataStatus {
1✔
259
      UNKNOWN, REQUESTED, DOES_NOT_EXIST, ACKED, NACKED
1✔
260
    }
261

262
    /**
263
     * Captures error metadata of failed resource updates.
264
     *
265
     * <p>This is a native representation of xDS ConfigDump UpdateFailureState, see
266
     * <a href="https://github.com/envoyproxy/envoy/blob/main/api/envoy/admin/v3/config_dump.proto">
267
     * config_dump.proto</a>
268
     */
269
    public static final class UpdateFailureState {
270
      private final String failedVersion;
271
      private final long failedUpdateTimeNanos;
272
      private final String failedDetails;
273

274
      private UpdateFailureState(
275
          String failedVersion, long failedUpdateTimeNanos, String failedDetails) {
1✔
276
        this.failedVersion = checkNotNull(failedVersion, "failedVersion");
1✔
277
        this.failedUpdateTimeNanos = failedUpdateTimeNanos;
1✔
278
        this.failedDetails = checkNotNull(failedDetails, "failedDetails");
1✔
279
      }
1✔
280

281
      /** The rejected version string of the last failed update attempt. */
282
      public String getFailedVersion() {
283
        return failedVersion;
1✔
284
      }
285

286
      /** Details about the last failed update attempt. */
287
      public long getFailedUpdateTimeNanos() {
288
        return failedUpdateTimeNanos;
1✔
289
      }
290

291
      /** Timestamp of the last failed update attempt. */
292
      public String getFailedDetails() {
293
        return failedDetails;
1✔
294
      }
295
    }
296
  }
297

298
  /**
299
   * Shutdown this {@link XdsClient} and release resources.
300
   */
301
  public void shutdown() {
302
    throw new UnsupportedOperationException();
×
303
  }
304

305
  /**
306
   * Returns {@code true} if {@link #shutdown()} has been called.
307
   */
308
  public boolean isShutDown() {
309
    throw new UnsupportedOperationException();
×
310
  }
311

312
  /**
313
   * Returns the config used to bootstrap this XdsClient {@link Bootstrapper.BootstrapInfo}.
314
   */
315
  public Bootstrapper.BootstrapInfo getBootstrapInfo() {
316
    throw new UnsupportedOperationException();
×
317
  }
318

319
  /**
320
   * Returns the implementation specific security configuration used in this XdsClient.
321
   */
322
  public Object getSecurityConfig() {
323
    throw new UnsupportedOperationException();
×
324
  }
325

326
  /**
327
   * Returns a {@link ListenableFuture} to the snapshot of the subscribed resources as
328
   * they are at the moment of the call.
329
   *
330
   * <p>The snapshot is a map from the "resource type" to
331
   * a map ("resource name": "resource metadata").
332
   */
333
  // Must be synchronized.
334
  public ListenableFuture<Map<XdsResourceType<?>, Map<String, ResourceMetadata>>>
335
      getSubscribedResourcesMetadataSnapshot() {
336
    throw new UnsupportedOperationException();
×
337
  }
338

339
  /**
340
   * Registers a data watcher for the given Xds resource.
341
   */
342
  public <T extends ResourceUpdate> void watchXdsResource(XdsResourceType<T> type,
343
      String resourceName,
344
      ResourceWatcher<T> watcher,
345
      Executor executor) {
346
    throw new UnsupportedOperationException();
×
347
  }
348

349
  public <T extends ResourceUpdate> void watchXdsResource(XdsResourceType<T> type,
350
      String resourceName,
351
      ResourceWatcher<T> watcher) {
352
    watchXdsResource(type, resourceName, watcher, MoreExecutors.directExecutor());
1✔
353
  }
1✔
354

355
  /**
356
   * Unregisters the given resource watcher.
357
   */
358
  public <T extends ResourceUpdate> void cancelXdsResourceWatch(XdsResourceType<T> type,
359
      String resourceName,
360
      ResourceWatcher<T> watcher) {
361
    throw new UnsupportedOperationException();
×
362
  }
363

364
  /**
365
   * Adds drop stats for the specified cluster with edsServiceName by using the returned object
366
   * to record dropped requests. Drop stats recorded with the returned object will be reported
367
   * to the load reporting server. The returned object is reference counted and the caller should
368
   * use {@link LoadStatsManager2.ClusterDropStats#release} to release its <i>hard</i> reference
369
   * when it is safe to stop reporting dropped RPCs for the specified cluster in the future.
370
   */
371
  public LoadStatsManager2.ClusterDropStats addClusterDropStats(
372
      Bootstrapper.ServerInfo serverInfo, String clusterName, @Nullable String edsServiceName) {
373
    throw new UnsupportedOperationException();
×
374
  }
375

376
  /**
377
   * Adds load stats for the specified locality (in the specified cluster with edsServiceName) by
378
   * using the returned object to record RPCs. Load stats recorded with the returned object will
379
   * be reported to the load reporting server. The returned object is reference counted and the
380
   * caller should use {@link LoadStatsManager2.ClusterLocalityStats#release} to release its
381
   * <i>hard</i> reference when it is safe to stop reporting RPC loads for the specified locality
382
   * in the future.
383
   */
384
  public LoadStatsManager2.ClusterLocalityStats addClusterLocalityStats(
385
      Bootstrapper.ServerInfo serverInfo, String clusterName, @Nullable String edsServiceName,
386
      Locality locality) {
387
    throw new UnsupportedOperationException();
×
388
  }
389

390
  /**
391
   * Returns a map of control plane server info objects to the LoadReportClients that are
392
   * responsible for sending load reports to the control plane servers.
393
   */
394
  public Map<Bootstrapper.ServerInfo, LoadReportClient> getServerLrsClientMap() {
395
    throw new UnsupportedOperationException();
×
396
  }
397

398
  /** Callback used to report a gauge metric value for server connections. */
399
  public interface ServerConnectionCallback {
400
    void reportServerConnectionGauge(boolean isConnected, String xdsServer);
401
  }
402

403
  /**
404
   * Reports whether xDS client has a "working" ADS stream to xDS server. The definition of a
405
   * working stream is defined in gRFC A78.
406
   *
407
   * @see <a
408
   *     href="https://github.com/grpc/proposal/blob/master/A78-grpc-metrics-wrr-pf-xds.md#xdsclient">
409
   *     A78-grpc-metrics-wrr-pf-xds.md</a>
410
   */
411
  public Future<Void> reportServerConnections(ServerConnectionCallback callback) {
412
    throw new UnsupportedOperationException();
×
413
  }
414

415
  static final class ProcessingTracker {
416
    private final AtomicInteger pendingTask = new AtomicInteger(1);
1✔
417
    private final Executor executor;
418
    private final Runnable completionListener;
419

420
    ProcessingTracker(Runnable completionListener, Executor executor) {
1✔
421
      this.executor = executor;
1✔
422
      this.completionListener = completionListener;
1✔
423
    }
1✔
424

425
    void startTask() {
426
      pendingTask.incrementAndGet();
1✔
427
    }
1✔
428

429
    void onComplete() {
430
      if (pendingTask.decrementAndGet() == 0) {
1✔
431
        executor.execute(completionListener);
1✔
432
      }
433
    }
1✔
434
  }
435

436
  interface XdsResponseHandler {
437
    /** Called when a xds response is received. */
438
    void handleResourceResponse(
439
        XdsResourceType<?> resourceType, ServerInfo serverInfo, String versionInfo,
440
        List<Any> resources, String nonce, boolean isFirstResponse,
441
        ProcessingTracker processingTracker);
442

443
    /** Called when the ADS stream is closed passively. */
444
    // Must be synchronized.
445
    void handleStreamClosed(Status error, boolean shouldTryFallback);
446
  }
447

448
  interface ResourceStore {
449

450
    /**
451
     * Returns the collection of resources currently subscribed to which have an authority matching
452
     * one of those for which the ControlPlaneClient associated with the specified ServerInfo is
453
     * the active one, or {@code null} if no such resources are currently subscribed to.
454
     *
455
     * <p>Note an empty collection indicates subscribing to resources of the given type with
456
     * wildcard mode.
457
     *
458
     * @param serverInfo the xds server to get the resources from
459
     * @param type       the type of the resources that should be retrieved
460
     */
461
    // Must be synchronized.
462
    @Nullable
463
    Collection<String> getSubscribedResources(
464
        ServerInfo serverInfo, XdsResourceType<? extends ResourceUpdate> type);
465

466
    Map<String, XdsResourceType<?>> getSubscribedResourceTypesWithTypeUrl();
467

468
    /**
469
     * For any of the subscribers to one of the specified resources, if there isn't a result or
470
     * an existing timer for the resource, start a timer for the resource.
471
     */
472
    void startMissingResourceTimers(Collection<String> resourceNames,
473
                                    XdsResourceType<?> resourceType);
474
  }
475
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc