Skip to content

feat(storage): add DirectPath over Cloud Interconnect channel telemetry and diagnostics - #14903

Draft
nidhiii-27 wants to merge 1 commit into
mainfrom
feat/storage-dp-interconnect-phase2
Draft

nidhiii-27 wants to merge 1 commit into
mainfrom
feat/storage-dp-interconnect-phase2

Conversation

@nidhiii-27

Copy link
Copy Markdown
Contributor

Summary

Implements Phase 2 (Telemetry, Diagnostics, and Parity Fixes) of DirectPath over Cloud Interconnect (DP over GCI) for google-cloud-storage, achieving cross-SDK parity with C++ PRs googleapis/google-cloud-cpp#16478, googleapis/google-cloud-cpp#16538, googleapis/google-cloud-cpp#16556, googleapis/google-cloud-cpp#16568, and googleapis/google-cloud-cpp#16605.

Key Changes

  • GrpcChannelTelemetry:
    • URI and host classification helpers (hasQueryParameter, detectTransportType, c2pTargetHost, authorityHost, isDirectPathInterconnectHost, selectedTransportType, grpcForceXdsIsSupported, isDirectPathInterconnectEnabled, resolveEffectiveTarget) for CloudPath, DirectPath, and DirectPathInterconnect.
    • Two-stage diagnostic logging (logChannelConfiguration and logChannelReady / channelReadyNote) reporting actionable WARNING and INFO messages for DirectPath over Cloud Interconnect configuration, runtime gRPC force-xds support, xDS vs DNS LB fallback, and channel readiness latency.
    • Non-owning async channel[0] readiness watcher (startChannelTelemetry / createChannelPrimer) using WeakReference<ManagedChannel> and notifyWhenStateChanged.
    • OpenTelemetry metric callback (makeChannelMetricsCallback) recording internal/client/channel_creation_latency (in seconds) with transport_type attribute (CloudPath, DirectPath, DirectPathInterconnect).
  • OpenTelemetryBootstrappingUtils:
    • Registers the storage.googleapis.com/internal/client/channel_creation_latency histogram view in createMeterProvider.
    • Adds bootstrapGrpcMetrics returning both the ChannelConfigurator and MeterProvider.
    • Updates getCloudMonitoringEndpoint to handle google-c2p:/// / google-c2p-experimental:/// targets, storage-direct.googleapis.com, storage-preprod-direct.sandbox.googleapis.com, and storage-direct.<universeDomain>.
  • GrpcStorageOptions:
    • Fixes builder precedence so setAttemptDirectPath(false) disables DirectPath even when setAttemptDirectPathXdsOverInterconnect(true) or GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS_OVER_INTERCONNECT=true is set.
    • Wires GrpcChannelTelemetry configuration logging, ChannelPrimer, and OpenTelemetry channel metrics callback into resolveSettingsAndOpts.
  • Unit Tests:
    • GrpcChannelTelemetryTest and OpenTelemetryBootstrappingUtilsTest covering URI/host helpers, diagnostic logs and warnings, async state transitions, OpenTelemetry histogram recording, and builder precedence.

(Scoped to java-storage/google-cloud-storage/ to avoid any merge conflict with #14864.)

…ry and diagnostics

- Add GrpcChannelTelemetry with URI and host classification (CloudPath, DirectPath, DirectPathInterconnect), two-stage diagnostic logging (logChannelConfiguration and logChannelReady), non-owning async channel[0] readiness watcher via ChannelPrimer, and internal/client/channel_creation_latency OpenTelemetry histogram callback.
- Register storage.googleapis.com/internal/client/channel_creation_latency histogram view and support DirectPath over Interconnect endpoints in OpenTelemetryBootstrappingUtils.
- Fix GrpcStorageOptions builder precedence so setAttemptDirectPath(false) overrides DirectPath over Cloud Interconnect as intended, and wire channel telemetry and diagnostics.
- Add unit tests in GrpcChannelTelemetryTest and OpenTelemetryBootstrappingUtilsTest.

[Generated-by: AI]
@nidhiii-27 nidhiii-27 added the ai-generated Pull Request generated by AI label Nov 3, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces gRPC channel telemetry and diagnostics for Google Cloud Storage, adding the GrpcChannelTelemetry class to support and monitor CloudPath, DirectPath, and DirectPath over Cloud Interconnect. It integrates this telemetry into GrpcStorageOptions and enhances OpenTelemetryBootstrappingUtils to bootstrap gRPC metrics and resolve Cloud Monitoring endpoints. Feedback on the changes highlights critical robustness improvements, including catching Throwable during static class-probing to prevent initialization crashes, calling setAccessible(true) and catching Exception to safely reflect on package-private channel implementations, and adding a defensive null check for credentials to avoid potential null pointer exceptions.

Comment on lines +487 to +495
private static boolean detectGrpcForceXdsSupport() {
try {
Class<?> clazz = Class.forName("io.grpc.googleapis.GoogleCloudToProdNameResolver");
clazz.getDeclaredField("forceXds");
return true;
} catch (ReflectiveOperationException | SecurityException e) {
return false;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In environments where GoogleCloudToProdNameResolver is present but its dependencies or superclasses are missing, Class.forName can throw a LinkageError (such as NoClassDefFoundError). Since this method is called during static initialization of GrpcChannelTelemetry, any uncaught Error will result in an ExceptionInInitializerError, preventing the class from loading and crashing the application. Catching Throwable instead of ReflectiveOperationException | SecurityException ensures robust class-probing without risking initialization failures.

Suggested change
private static boolean detectGrpcForceXdsSupport() {
try {
Class<?> clazz = Class.forName("io.grpc.googleapis.GoogleCloudToProdNameResolver");
clazz.getDeclaredField("forceXds");
return true;
} catch (ReflectiveOperationException | SecurityException e) {
return false;
}
}
private static boolean detectGrpcForceXdsSupport() {
try {
Class<?> clazz = Class.forName("io.grpc.googleapis.GoogleCloudToProdNameResolver");
clazz.getDeclaredField("forceXds");
return true;
} catch (Throwable e) {
return false;
}
}

Comment on lines +523 to +537
private static String defaultExtractLbPolicyName(@Nullable ManagedChannel channel) {
if (channel == null) {
return null;
}
try {
Method m = channel.getClass().getMethod("getLoadBalancingPolicyName");
Object result = m.invoke(channel);
if (result instanceof String) {
return (String) result;
}
} catch (ReflectiveOperationException | SecurityException ignored) {
// Standard io.grpc.ManagedChannel does not expose getLoadBalancingPolicyName publicly.
}
return null;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the runtime class of the ManagedChannel is package-private (such as io.grpc.internal.ManagedChannelImpl), calling Method.invoke on a Method retrieved via getClass().getMethod(...) will throw an IllegalAccessException because the class itself is not accessible, even if the method is public. To bypass this, m.setAccessible(true) should be called. Additionally, on JDK 9+, calling setAccessible(true) on a module-protected class can throw an InaccessibleObjectException (which is a RuntimeException). Since the catch block only catches ReflectiveOperationException and SecurityException, this runtime exception would propagate and crash the application. Catching Exception and calling setAccessible(true) resolves both issues safely.

Suggested change
private static String defaultExtractLbPolicyName(@Nullable ManagedChannel channel) {
if (channel == null) {
return null;
}
try {
Method m = channel.getClass().getMethod("getLoadBalancingPolicyName");
Object result = m.invoke(channel);
if (result instanceof String) {
return (String) result;
}
} catch (ReflectiveOperationException | SecurityException ignored) {
// Standard io.grpc.ManagedChannel does not expose getLoadBalancingPolicyName publicly.
}
return null;
}
private static String defaultExtractLbPolicyName(@Nullable ManagedChannel channel) {
if (channel == null) {
return null;
}
try {
Method m = channel.getClass().getMethod("getLoadBalancingPolicyName");
m.setAccessible(true);
Object result = m.invoke(channel);
if (result instanceof String) {
return (String) result;
}
} catch (Exception ignored) {
// Standard io.grpc.ManagedChannel does not expose getLoadBalancingPolicyName publicly.
}
return null;
}
References
  1. Catching Throwable instead of Exception is acceptable at Future boundaries or during stream cleanup in asynchronous operations to ensure all failures are captured and propagated. For general reflection or runtime exception handling, prefer catching Exception to avoid catching critical JVM Errors.

Comment on lines +363 to +366
InstantiatingGrpcChannelProvider telemetryProvider =
credentials instanceof NoCredentials
? baseProvider
: (InstantiatingGrpcChannelProvider) baseProvider.withCredentials(credentials);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If credentials is null, credentials instanceof NoCredentials evaluates to false, which will lead to calling baseProvider.withCredentials(null). Depending on the GAX provider implementation, passing null to withCredentials might throw a NullPointerException or cause downstream initialization issues. Adding an explicit null check for credentials ensures defensive safety.

Suggested change
InstantiatingGrpcChannelProvider telemetryProvider =
credentials instanceof NoCredentials
? baseProvider
: (InstantiatingGrpcChannelProvider) baseProvider.withCredentials(credentials);
InstantiatingGrpcChannelProvider telemetryProvider =
(credentials == null || credentials instanceof NoCredentials)
? baseProvider
: (InstantiatingGrpcChannelProvider) baseProvider.withCredentials(credentials);

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-generated Pull Request generated by AI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant