From 372f0988caa52f72685d16436430dcd7124459cc Mon Sep 17 00:00:00 2001 From: Sushan Bhattarai Date: Tue, 24 Feb 2026 01:00:34 -0500 Subject: [PATCH 1/3] feat(bigtable): add a feature on enabling direct access via check --- .../v2/stub/EnhancedBigtableStubSettings.java | 52 ++++-- .../gaxx/grpc/BigtableChannelPool.java | 17 +- .../grpc/BigtableDirectAccessChecker.java | 158 ++++++++++++++++++ .../BigtableTransportChannelProvider.java | 100 +++++++++-- .../gaxx/grpc/DirectAccessChecker.java | 24 +++ .../v2/BigtableDataClientFactoryTest.java | 5 +- .../v2/stub/EnhancedBigtableStubTest.java | 3 +- .../gaxx/grpc/BigtableChannelPoolTest.java | 41 ++++- .../grpc/BigtableDirectAccessCheckerTest.java | 78 +++++++++ 9 files changed, 445 insertions(+), 33 deletions(-) create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableDirectAccessChecker.java create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/DirectAccessChecker.java create mode 100644 google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/gaxx/grpc/BigtableDirectAccessCheckerTest.java diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java index 0ce0c7b299..864cd95aec 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java @@ -237,6 +237,25 @@ public boolean areInternalMetricsEnabled() { return areInternalMetricsEnabled; } + /** Applies common pool, message size, and keep-alive settings to the provided builder. */ + private static InstantiatingGrpcChannelProvider.Builder commonTraits( + InstantiatingGrpcChannelProvider.Builder builder) { + return builder + .setChannelPoolSettings( + ChannelPoolSettings.builder() + .setInitialChannelCount(10) + .setMinRpcsPerChannel(1) + // Keep it conservative as we scale the channel size every 1min + // and delta is 2 channels. + .setMaxRpcsPerChannel(25) + .setPreemptiveRefreshEnabled(true) + .build()) + .setMaxInboundMessageSize(MAX_MESSAGE_SIZE) + .setKeepAliveTime(Duration.ofSeconds(30)) // sends ping in this interval + .setKeepAliveTimeout( + Duration.ofSeconds(10)); // wait this long before considering the connection dead + } + /** Returns a builder for the default ChannelProvider for this service. */ public static InstantiatingGrpcChannelProvider.Builder defaultGrpcTransportProviderBuilder() { InstantiatingGrpcChannelProvider.Builder grpcTransportProviderBuilder = @@ -256,20 +275,25 @@ public static InstantiatingGrpcChannelProvider.Builder defaultGrpcTransportProvi Collections.singletonList(InstantiatingGrpcChannelProvider.HardBoundTokenTypes.ALTS)); } } - return grpcTransportProviderBuilder - .setChannelPoolSettings( - ChannelPoolSettings.builder() - .setInitialChannelCount(10) - .setMinRpcsPerChannel(1) - // Keep it conservative as we scale the channel size every 1min - // and delta is 2 channels. - .setMaxRpcsPerChannel(25) - .setPreemptiveRefreshEnabled(true) - .build()) - .setMaxInboundMessageSize(MAX_MESSAGE_SIZE) - .setKeepAliveTime(Duration.ofSeconds(30)) // sends ping in this interval - .setKeepAliveTimeout( - Duration.ofSeconds(10)); // wait this long before considering the connection dead + return commonTraits(grpcTransportProviderBuilder); + } + + /** Returns a builder for the Direct Access Transport Provider. */ + /** Applies Direct Access traits (DirectPath & ALTS) to an existing builder. */ + public static InstantiatingGrpcChannelProvider.Builder applyDirectAccessTraits( + InstantiatingGrpcChannelProvider.Builder builder) { + + builder + .setAttemptDirectPathXds() + .setAttemptDirectPath(true) + .setAllowNonDefaultServiceAccount(true); + + if (!DIRECT_PATH_BOUND_TOKEN_DISABLED) { + builder.setAllowHardBoundTokenTypes( + Collections.singletonList(InstantiatingGrpcChannelProvider.HardBoundTokenTypes.ALTS)); + } + + return builder; } @SuppressWarnings("WeakerAccess") diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableChannelPool.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableChannelPool.java index f5f1928c2a..4ec954fdcf 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableChannelPool.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableChannelPool.java @@ -82,9 +82,11 @@ public static BigtableChannelPool create( BigtableChannelPoolSettings settings, ChannelFactory channelFactory, ChannelPrimer channelPrimer, - ScheduledExecutorService backgroundExecutor) + ScheduledExecutorService backgroundExecutor, + @Nullable ManagedChannel preCreatedChannel) throws IOException { - return new BigtableChannelPool(settings, channelFactory, channelPrimer, backgroundExecutor); + return new BigtableChannelPool( + settings, channelFactory, channelPrimer, backgroundExecutor, preCreatedChannel); } /** @@ -99,7 +101,8 @@ public static BigtableChannelPool create( BigtableChannelPoolSettings settings, ChannelFactory channelFactory, ChannelPrimer channelPrimer, - ScheduledExecutorService executor) + ScheduledExecutorService executor, + @Nullable ManagedChannel preCreatedChannel) throws IOException { this.settings = settings; this.channelFactory = channelFactory; @@ -110,8 +113,12 @@ public static BigtableChannelPool create( this.channelPoolHealthChecker.start(); ImmutableList.Builder initialListBuilder = ImmutableList.builder(); - - for (int i = 0; i < settings.getInitialChannelCount(); i++) { + int channelsToCreate = settings.getInitialChannelCount(); + if (preCreatedChannel != null) { + initialListBuilder.add(new Entry(preCreatedChannel)); + channelsToCreate--; + } + for (int i = 0; i < channelsToCreate; i++) { ManagedChannel newChannel = channelFactory.createSingleChannel(); channelPrimer.primeChannel(newChannel); initialListBuilder.add(new Entry(newChannel)); diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableDirectAccessChecker.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableDirectAccessChecker.java new file mode 100644 index 0000000000..4f70f0177b --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableDirectAccessChecker.java @@ -0,0 +1,158 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.bigtable.gaxx.grpc; + +import com.google.cloud.bigtable.data.v2.stub.BigtableChannelPrimer; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ClientInterceptors; +import io.grpc.ForwardingClientCall; +import io.grpc.ForwardingClientCallListener; +import io.grpc.ManagedChannel; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.alts.AltsContextUtil; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Logger; + +public class BigtableDirectAccessChecker implements DirectAccessChecker { + private static final Logger LOG = Logger.getLogger(BigtableDirectAccessChecker.class.getName()); + private final ChannelPrimer channelPrimer; + + public BigtableDirectAccessChecker(ChannelPrimer channelPrimer) { + this.channelPrimer = channelPrimer; + } + + /// Performs a request on the provided channel to check for Direct Access eligibility. + @Override + public boolean check(ManagedChannel channel) { + // Return false in case channelPrime is not an instance, rare + if (!(channelPrimer instanceof BigtableChannelPrimer)) { + return false; + } + + BigtableChannelPrimer primer = (BigtableChannelPrimer) channelPrimer; + final AtomicBoolean isDirectAccessEligible = new AtomicBoolean(false); + + // Create the interceptor to check the headers + ClientInterceptor altsInterceptor = + new ClientInterceptor() { + @Override + public ClientCall interceptCall( + MethodDescriptor methodDescriptor, + CallOptions callOptions, + Channel next) { + + // Capture the actual ClientCall to access its attributes later + final ClientCall thisCall = next.newCall(methodDescriptor, callOptions); + + return new ForwardingClientCall.SimpleForwardingClientCall(thisCall) { + @Override + public void start(Listener responseListener, Metadata headers) { + + // Wrap the listener to intercept the response headers + Listener forwardingListener = + new ForwardingClientCallListener.SimpleForwardingClientCallListener( + responseListener) { + @Override + public void onHeaders(Metadata responseHeaders) { + boolean altsCheckPassed = false; + try { + LOG.info("Checking AltsContextUtil on attributes during onHeaders..."); + // Verify ALTS context is present + if (AltsContextUtil.check(thisCall.getAttributes())) { + altsCheckPassed = true; + } + } catch (Exception e) { + LOG.warning("AltsContextUtil check failed: " + e.getMessage()); + } + + LOG.info("ALTS check: " + altsCheckPassed); + if (altsCheckPassed) { + isDirectAccessEligible.set(true); + } + + super.onHeaders(responseHeaders); + } + }; + + super.start(forwardingListener, headers); + } + }; + } + }; + + try { + // Wrap the channel with our custom ALTS interceptor + Channel interceptedChannel = ClientInterceptors.intercept(channel, altsInterceptor); + + ManagedChannel wrappedManagedChannel = + new ManagedChannel() { + @Override + public ClientCall newCall( + MethodDescriptor methodDescriptor, CallOptions callOptions) { + // Delegate RPCs to the intercepted channel! + return interceptedChannel.newCall(methodDescriptor, callOptions); + } + + @Override + public String authority() { + return channel.authority(); + } + + // Delegate all lifecycle methods to the original ManagedChannel + @Override + public ManagedChannel shutdown() { + return channel.shutdown(); + } + + @Override + public boolean isShutdown() { + return channel.isShutdown(); + } + + @Override + public boolean isTerminated() { + return channel.isTerminated(); + } + + @Override + public ManagedChannel shutdownNow() { + return channel.shutdownNow(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) + throws InterruptedException { + return channel.awaitTermination(timeout, unit); + } + }; + + // Delegate the actual RPC execution to the primer. + // This synchronously sends the PingAndWarm request and triggers the onHeaders callback. + channelPrimer.primeChannel(wrappedManagedChannel); + + } catch (Exception e) { + LOG.warning("The direct access probe failed to execute: " + e.getMessage()); + } + + return isDirectAccessEligible.get(); + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableTransportChannelProvider.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableTransportChannelProvider.java index e21c100c9c..a8e8d6acc6 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableTransportChannelProvider.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableTransportChannelProvider.java @@ -23,6 +23,7 @@ import com.google.api.gax.rpc.TransportChannel; import com.google.api.gax.rpc.TransportChannelProvider; import com.google.auth.Credentials; +import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStubSettings; import com.google.cloud.bigtable.data.v2.stub.metrics.ChannelPoolMetricsTracer; import com.google.common.base.Preconditions; import io.grpc.ManagedChannel; @@ -30,6 +31,8 @@ import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.Nullable; /** @@ -38,20 +41,25 @@ */ @InternalApi public final class BigtableTransportChannelProvider implements TransportChannelProvider { + private static final Logger LOG = + Logger.getLogger(BigtableTransportChannelProvider.class.getName()); private final InstantiatingGrpcChannelProvider delegate; private final ChannelPrimer channelPrimer; @Nullable private final ChannelPoolMetricsTracer channelPoolMetricsTracer; @Nullable private final ScheduledExecutorService backgroundExecutor; + @Nullable private final Map headers; // <-- Add this field private BigtableTransportChannelProvider( InstantiatingGrpcChannelProvider instantiatingGrpcChannelProvider, ChannelPrimer channelPrimer, ChannelPoolMetricsTracer channelPoolMetricsTracer, - ScheduledExecutorService backgroundExecutor) { + ScheduledExecutorService backgroundExecutor, + @Nullable Map headers) { delegate = Preconditions.checkNotNull(instantiatingGrpcChannelProvider); this.channelPrimer = channelPrimer; this.channelPoolMetricsTracer = channelPoolMetricsTracer; this.backgroundExecutor = backgroundExecutor; + this.headers = headers; } @Override @@ -76,7 +84,7 @@ public BigtableTransportChannelProvider withExecutor(Executor executor) { InstantiatingGrpcChannelProvider newChannelProvider = (InstantiatingGrpcChannelProvider) delegate.withExecutor(executor); return new BigtableTransportChannelProvider( - newChannelProvider, channelPrimer, channelPoolMetricsTracer, backgroundExecutor); + newChannelProvider, channelPrimer, channelPoolMetricsTracer, backgroundExecutor, headers); } @Override @@ -89,7 +97,7 @@ public TransportChannelProvider withBackgroundExecutor(ScheduledExecutorService InstantiatingGrpcChannelProvider newChannelProvider = (InstantiatingGrpcChannelProvider) delegate.withBackgroundExecutor(executor); return new BigtableTransportChannelProvider( - newChannelProvider, channelPrimer, channelPoolMetricsTracer, executor); + newChannelProvider, channelPrimer, channelPoolMetricsTracer, executor, headers); } @Override @@ -102,7 +110,7 @@ public BigtableTransportChannelProvider withHeaders(Map headers) InstantiatingGrpcChannelProvider newChannelProvider = (InstantiatingGrpcChannelProvider) delegate.withHeaders(headers); return new BigtableTransportChannelProvider( - newChannelProvider, channelPrimer, channelPoolMetricsTracer, backgroundExecutor); + newChannelProvider, channelPrimer, channelPoolMetricsTracer, backgroundExecutor, headers); } @Override @@ -115,7 +123,7 @@ public TransportChannelProvider withEndpoint(String endpoint) { InstantiatingGrpcChannelProvider newChannelProvider = (InstantiatingGrpcChannelProvider) delegate.withEndpoint(endpoint); return new BigtableTransportChannelProvider( - newChannelProvider, channelPrimer, channelPoolMetricsTracer, backgroundExecutor); + newChannelProvider, channelPrimer, channelPoolMetricsTracer, backgroundExecutor, headers); } @Deprecated @@ -130,12 +138,81 @@ public TransportChannelProvider withPoolSize(int size) { InstantiatingGrpcChannelProvider newChannelProvider = (InstantiatingGrpcChannelProvider) delegate.withPoolSize(size); return new BigtableTransportChannelProvider( - newChannelProvider, channelPrimer, channelPoolMetricsTracer, backgroundExecutor); + newChannelProvider, channelPrimer, channelPoolMetricsTracer, backgroundExecutor, headers); + } + + // We need this for direct access checker. + private Map updateFeatureFlags( + Map originalHeaders, boolean isDirectAccessEligible) { + if (originalHeaders == null) { + return java.util.Collections.emptyMap(); + } + java.util.Map newHeaders = new java.util.HashMap<>(originalHeaders); + String encodedFlags = newHeaders.get("bigtable-features"); + + if (encodedFlags != null) { + try { + byte[] decoded = java.util.Base64.getUrlDecoder().decode(encodedFlags); + com.google.bigtable.v2.FeatureFlags flags = + com.google.bigtable.v2.FeatureFlags.parseFrom(decoded); + + com.google.bigtable.v2.FeatureFlags updatedFlags = + flags.toBuilder() + .setDirectAccessRequested(isDirectAccessEligible) + .setTrafficDirectorEnabled(isDirectAccessEligible) + .build(); + + newHeaders.put( + "bigtable-features", + java.util.Base64.getUrlEncoder().encodeToString(updatedFlags.toByteArray())); + } catch (Exception e) { + // use original headers + } + } + return newHeaders; } /** Expected to only be called once when BigtableClientContext is created */ @Override public TransportChannel getTransportChannel() throws IOException { + Map directAccessEligibleHeaders = updateFeatureFlags(this.headers, true); + + InstantiatingGrpcChannelProvider.Builder directAccessProvider = + EnhancedBigtableStubSettings.applyDirectAccessTraits(delegate.toBuilder()) + .setChannelPoolSettings(ChannelPoolSettings.staticallySized(1)); + + InstantiatingGrpcChannelProvider directAccessProviderWithHeaders = + (InstantiatingGrpcChannelProvider) + directAccessProvider.build().withHeaders(directAccessEligibleHeaders); + + GrpcTransportChannel directAccessTransportChannel = + (GrpcTransportChannel) directAccessProviderWithHeaders.getTransportChannel(); + ManagedChannel maybeDirectAccessChannel = + (ManagedChannel) directAccessTransportChannel.getChannel(); + DirectAccessChecker directAccessChecker = new BigtableDirectAccessChecker(channelPrimer); + boolean isDirectAccessEligible = false; + + try { + isDirectAccessEligible = directAccessChecker.check(maybeDirectAccessChannel); + } catch (Exception e) { + LOG.log( + Level.INFO, + "Failed to probe for Direct Access eligibility. Falling back to default routing.", + e); + } + + InstantiatingGrpcChannelProvider selectedProvider; + ManagedChannel preCreatedChannel = null; + + if (isDirectAccessEligible) { + selectedProvider = directAccessProviderWithHeaders; + preCreatedChannel = maybeDirectAccessChannel; + } else { + Map fallbackHeaders = updateFeatureFlags(this.headers, false); + maybeDirectAccessChannel.shutdownNow(); + selectedProvider = (InstantiatingGrpcChannelProvider) delegate.withHeaders(fallbackHeaders); + } + // This provider's main purpose is to replace the default GAX ChannelPool // with a custom BigtableChannelPool, reusing the delegate's configuration. @@ -143,7 +220,9 @@ public TransportChannel getTransportChannel() throws IOException { // We achieve this by configuring our delegate to not use its own pooling // (by setting pool size to 1) and then calling getTransportChannel() on it. InstantiatingGrpcChannelProvider singleChannelProvider = - delegate.toBuilder().setChannelPoolSettings(ChannelPoolSettings.staticallySized(1)).build(); + selectedProvider.toBuilder() + .setChannelPoolSettings(ChannelPoolSettings.staticallySized(1)) + .build(); ChannelFactory channelFactory = () -> { @@ -161,7 +240,7 @@ public TransportChannel getTransportChannel() throws IOException { BigtableChannelPool btChannelPool = BigtableChannelPool.create( - btPoolSettings, channelFactory, channelPrimer, backgroundExecutor); + btPoolSettings, channelFactory, channelPrimer, backgroundExecutor, preCreatedChannel); if (channelPoolMetricsTracer != null) { channelPoolMetricsTracer.registerChannelInsightsProvider(btChannelPool::getChannelInfos); @@ -187,7 +266,7 @@ public TransportChannelProvider withCredentials(Credentials credentials) { InstantiatingGrpcChannelProvider newChannelProvider = (InstantiatingGrpcChannelProvider) delegate.withCredentials(credentials); return new BigtableTransportChannelProvider( - newChannelProvider, channelPrimer, channelPoolMetricsTracer, backgroundExecutor); + newChannelProvider, channelPrimer, channelPoolMetricsTracer, backgroundExecutor, headers); } /** Creates a BigtableTransportChannelProvider. */ @@ -200,6 +279,7 @@ public static BigtableTransportChannelProvider create( instantiatingGrpcChannelProvider, channelPrimer, outstandingRpcsMetricTracker, - backgroundExecutor); + backgroundExecutor, + null); } } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/DirectAccessChecker.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/DirectAccessChecker.java new file mode 100644 index 0000000000..6e4400578a --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/DirectAccessChecker.java @@ -0,0 +1,24 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.bigtable.gaxx.grpc; + +import io.grpc.ManagedChannel; + +/** Evaluates whether a given channel supports Direct Access. */ +public interface DirectAccessChecker { + /// Performs a request on the provided channel to check for Direct Access eligibility. + boolean check(ManagedChannel channel); +} diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactoryTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactoryTest.java index b8c187a8ac..3733b41928 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactoryTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/BigtableDataClientFactoryTest.java @@ -289,7 +289,7 @@ public void testCreateWithRefreshingChannel() throws Exception { Mockito.verify(executorProvider, Mockito.times(1)).getExecutor(); Mockito.verify(watchdogProvider, Mockito.times(1)).getWatchdog(); - assertThat(warmedChannels).hasSize(poolSize); + assertThat(warmedChannels).hasSize(poolSize + 1); assertThat(warmedChannels.values()).doesNotContain(false); // Wait for all the connections to close asynchronously @@ -297,7 +297,8 @@ public void testCreateWithRefreshingChannel() throws Exception { long sleepTimeMs = 1000; Thread.sleep(sleepTimeMs); // Verify that all the channels are closed - assertThat(terminateAttributes).hasSize(poolSize); + // We need to consider the direct path channel. + assertThat(terminateAttributes).hasSize(poolSize + 1); } @Test diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubTest.java index 1531506a11..6edb39e400 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubTest.java @@ -538,7 +538,8 @@ public void testChannelPrimerConfigured() throws IOException { defaultSettings.toBuilder().setRefreshingChannel(true).build(); try (EnhancedBigtableStub ignored = EnhancedBigtableStub.create(settings)) { - assertThat(fakeDataService.pingRequests).hasSize(1); + // oops, direct access checker pingRequests + assertThat(fakeDataService.pingRequests).hasSize(2); } } diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/gaxx/grpc/BigtableChannelPoolTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/gaxx/grpc/BigtableChannelPoolTest.java index d1059c0362..c07b2e9a11 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/gaxx/grpc/BigtableChannelPoolTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/gaxx/grpc/BigtableChannelPoolTest.java @@ -107,7 +107,8 @@ public void setUp() throws IOException { .setMaxChannelCount(1) .build(); channelPool = - new BigtableChannelPool(settings, mockChannelFactory, mockChannelPrimer, executorService); + new BigtableChannelPool( + settings, mockChannelFactory, mockChannelPrimer, executorService, null); // Capture the listener when start is called // Configure mockClientCall.start to capture the listener @@ -233,4 +234,42 @@ public void testMixedRpcs() { assertThat(entry.getAndResetErrorCount()).isEqualTo(1); // The last failure assertThat(entry.totalOutstandingRpcs()).isEqualTo(0); } + + @Test + public void testPreCreatedChannelInitialization() throws IOException { + reset(mockChannelFactory); + reset(mockChannelPrimer); + + ManagedChannel newMockChannel = mock(ManagedChannel.class); + when(mockChannelFactory.createSingleChannel()).thenReturn(newMockChannel); + + BigtableChannelPoolSettings settings = + BigtableChannelPoolSettings.builder() + .setInitialChannelCount(3) // Request 3 channels total + .setMinChannelCount(1) + .setMaxChannelCount(5) + .build(); + + ManagedChannel preCreatedChannelMock = mock(ManagedChannel.class); + + // Create the pool, passing in our pre-created channel + BigtableChannelPool poolWithPreCreated = + new BigtableChannelPool( + settings, + mockChannelFactory, + mockChannelPrimer, + executorService, + preCreatedChannelMock); + + // Verify the pool size is exactly 3 + List infos = poolWithPreCreated.getChannelInfos(); + assertThat(infos).hasSize(3); + + // Verify the factory was only called 2 times. + verify(mockChannelFactory, times(2)).createSingleChannel(); + + // Verify the primer was only called 2 times. + // As pre created channel is already primed. + verify(mockChannelPrimer, times(2)).primeChannel(any(ManagedChannel.class)); + } } diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/gaxx/grpc/BigtableDirectAccessCheckerTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/gaxx/grpc/BigtableDirectAccessCheckerTest.java new file mode 100644 index 0000000000..930f6d48a8 --- /dev/null +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/gaxx/grpc/BigtableDirectAccessCheckerTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.bigtable.gaxx.grpc; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; + +import com.google.cloud.bigtable.data.v2.stub.BigtableChannelPrimer; +import io.grpc.ManagedChannel; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class BigtableDirectAccessCheckerTest { + + private BigtableChannelPrimer mockPrimer; + private ManagedChannel mockChannel; + private BigtableDirectAccessChecker checker; + + @Before + public void setUp() { + mockPrimer = mock(BigtableChannelPrimer.class); + mockChannel = mock(ManagedChannel.class); + // Ensure the call returns empty attributes by default to avoid NullPointerExceptions + checker = new BigtableDirectAccessChecker(mockPrimer); + } + + @Test + public void testProbeInvalidPrimerReturnsFalse() { + ChannelPrimer invalidPrimer = mock(ChannelPrimer.class); + BigtableDirectAccessChecker invalidChecker = new BigtableDirectAccessChecker(invalidPrimer); + + boolean result = invalidChecker.check(mockChannel); + + assertThat(result).isFalse(); + } + + @Test + public void testProbeErrorSituationReturnsFalse() { + doThrow(new RuntimeException("Simulated network failure")) + .when(mockPrimer) + .primeChannel((ManagedChannel) any(ManagedChannel.class)); + + boolean result = checker.check(mockChannel); + assertThat(result).isFalse(); + } + + @Test + public void testProbe_TimeoutDuringPriming_ReturnsFalse() { + doThrow( + io.grpc.Status.DEADLINE_EXCEEDED + .withDescription("deadline exceeded after 1m") + .asRuntimeException()) + .when(mockPrimer) + .primeChannel(any(ManagedChannel.class)); + + boolean result = checker.check(mockChannel); + assertThat(result).isFalse(); + } +} From 524f554ab5ea042bd41a3b65cba7f15dee38951f Mon Sep 17 00:00:00 2001 From: Sushan Bhattarai Date: Tue, 24 Feb 2026 12:47:37 -0500 Subject: [PATCH 2/3] add metrics for direct access --- .../data/v2/stub/BigtableClientContext.java | 11 ++- .../BigtableDirectAccessMetricsRecorder.java | 50 +++++++++++++ .../stub/metrics/BuiltinMetricsConstants.java | 7 +- .../metrics/DirectAccessMetricsRecorder.java | 27 +++++++ .../grpc/BigtableDirectAccessChecker.java | 9 ++- .../BigtableTransportChannelProvider.java | 71 ++++++++++++++----- .../gaxx/grpc/DirectAccessChecker.java | 4 +- 7 files changed, 155 insertions(+), 24 deletions(-) create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableDirectAccessMetricsRecorder.java create mode 100644 google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/DirectAccessMetricsRecorder.java diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/BigtableClientContext.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/BigtableClientContext.java index c89f368190..effca7af22 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/BigtableClientContext.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/BigtableClientContext.java @@ -29,10 +29,12 @@ import com.google.auth.oauth2.ServiceAccountJwtAccessCredentials; import com.google.bigtable.v2.InstanceName; import com.google.cloud.bigtable.data.v2.internal.JwtCredentialsWithAudience; +import com.google.cloud.bigtable.data.v2.stub.metrics.BigtableDirectAccessMetricsRecorder; import com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsConstants; import com.google.cloud.bigtable.data.v2.stub.metrics.ChannelPoolMetricsTracer; import com.google.cloud.bigtable.data.v2.stub.metrics.CompositeTracerFactory; import com.google.cloud.bigtable.data.v2.stub.metrics.CustomOpenTelemetryMetricsProvider; +import com.google.cloud.bigtable.data.v2.stub.metrics.DirectAccessMetricsRecorder; import com.google.cloud.bigtable.data.v2.stub.metrics.Util; import com.google.cloud.bigtable.gaxx.grpc.BigtableTransportChannelProvider; import com.google.cloud.bigtable.gaxx.grpc.ChannelPrimer; @@ -167,12 +169,19 @@ public static BigtableClientContext create( builder.getHeaderProvider().getHeaders()); } + DirectAccessMetricsRecorder directAccessMetricsRecorder = + DirectAccessMetricsRecorder.NOOP_DIRECT_ACCESS_METRIC_RECORDER; + if (builtinOtel != null) { + directAccessMetricsRecorder = new BigtableDirectAccessMetricsRecorder(builtinOtel); + } + BigtableTransportChannelProvider btTransportProvider = BigtableTransportChannelProvider.create( transportProvider.build(), channelPrimer, channelPoolMetricsTracer, - backgroundExecutor); + backgroundExecutor, + directAccessMetricsRecorder); builder.setTransportChannelProvider(btTransportProvider); } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableDirectAccessMetricsRecorder.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableDirectAccessMetricsRecorder.java new file mode 100644 index 0000000000..edc887fab9 --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BigtableDirectAccessMetricsRecorder.java @@ -0,0 +1,50 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.bigtable.data.v2.stub.metrics; + +import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsConstants.DIRECT_ACCESS_COMPATIBLE_NAME; +import static com.google.cloud.bigtable.data.v2.stub.metrics.BuiltinMetricsConstants.METER_NAME; + +import com.google.api.core.InternalApi; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; + +@InternalApi +public class BigtableDirectAccessMetricsRecorder implements DirectAccessMetricsRecorder { + private final LongGauge compatibleGauge; + + public BigtableDirectAccessMetricsRecorder(OpenTelemetry openTelemetry) { + Meter meter = openTelemetry.getMeter(METER_NAME); + this.compatibleGauge = + meter + .gaugeBuilder(DIRECT_ACCESS_COMPATIBLE_NAME) + .ofLongs() + .setDescription( + "Reports 1 if the environment is eligible for DirectPath, 0 otherwise. Based on an attempt at startup.") + .setUnit("1") + .build(); + } + + @Override + public void recordEligibility(boolean isEligible) { + if (compatibleGauge != null) { + // Record 1 if eligible, 0 otherwise + compatibleGauge.set(isEligible ? 1L : 0L); + } + } +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsConstants.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsConstants.java index 810d555de2..fd8cbee658 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsConstants.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/BuiltinMetricsConstants.java @@ -101,6 +101,7 @@ public class BuiltinMetricsConstants { static final String BATCH_WRITE_FLOW_CONTROL_TARGET_QPS_NAME = "batch_write_flow_control_target_qps"; static final String BATCH_WRITE_FLOW_CONTROL_FACTOR_NAME = "batch_write_flow_control_factor"; + static final String DIRECT_ACCESS_COMPATIBLE_NAME = "direct_access/compatible"; // Start allow list of metrics that will be exported as internal public static final Map> GRPC_METRICS = @@ -168,7 +169,11 @@ public class BuiltinMetricsConstants { .build(); public static final Set INTERNAL_METRICS = - ImmutableSet.of(PER_CONNECTION_ERROR_COUNT_NAME, OUTSTANDING_RPCS_PER_CHANNEL_NAME).stream() + ImmutableSet.of( + PER_CONNECTION_ERROR_COUNT_NAME, + OUTSTANDING_RPCS_PER_CHANNEL_NAME, + DIRECT_ACCESS_COMPATIBLE_NAME) + .stream() .map(m -> METER_NAME + m) .collect(ImmutableSet.toImmutableSet()); // End allow list of metrics that will be exported diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/DirectAccessMetricsRecorder.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/DirectAccessMetricsRecorder.java new file mode 100644 index 0000000000..706c32d3aa --- /dev/null +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/metrics/DirectAccessMetricsRecorder.java @@ -0,0 +1,27 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.bigtable.data.v2.stub.metrics; + +import com.google.api.core.InternalApi; + +@InternalApi +public interface DirectAccessMetricsRecorder { + /** Records whether the underlying transport was eligible for Direct Access. */ + void recordEligibility(boolean isEligible); + + DirectAccessMetricsRecorder NOOP_DIRECT_ACCESS_METRIC_RECORDER = isEligible -> {}; +} diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableDirectAccessChecker.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableDirectAccessChecker.java index 4f70f0177b..8df302e0c0 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableDirectAccessChecker.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableDirectAccessChecker.java @@ -16,6 +16,7 @@ package com.google.cloud.bigtable.gaxx.grpc; +import com.google.api.core.InternalApi; import com.google.cloud.bigtable.data.v2.stub.BigtableChannelPrimer; import io.grpc.CallOptions; import io.grpc.Channel; @@ -32,6 +33,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; +@InternalApi public class BigtableDirectAccessChecker implements DirectAccessChecker { private static final Logger LOG = Logger.getLogger(BigtableDirectAccessChecker.class.getName()); private final ChannelPrimer channelPrimer; @@ -75,16 +77,13 @@ public void start(Listener responseListener, Metadata headers) { public void onHeaders(Metadata responseHeaders) { boolean altsCheckPassed = false; try { - LOG.info("Checking AltsContextUtil on attributes during onHeaders..."); // Verify ALTS context is present if (AltsContextUtil.check(thisCall.getAttributes())) { altsCheckPassed = true; } } catch (Exception e) { - LOG.warning("AltsContextUtil check failed: " + e.getMessage()); + LOG.warning("direct access check failed: " + e.getMessage()); } - - LOG.info("ALTS check: " + altsCheckPassed); if (altsCheckPassed) { isDirectAccessEligible.set(true); } @@ -147,7 +146,7 @@ public boolean awaitTermination(long timeout, TimeUnit unit) // Delegate the actual RPC execution to the primer. // This synchronously sends the PingAndWarm request and triggers the onHeaders callback. - channelPrimer.primeChannel(wrappedManagedChannel); + primer.primeChannel(wrappedManagedChannel); } catch (Exception e) { LOG.warning("The direct access probe failed to execute: " + e.getMessage()); diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableTransportChannelProvider.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableTransportChannelProvider.java index a8e8d6acc6..97c8f7421c 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableTransportChannelProvider.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/BigtableTransportChannelProvider.java @@ -25,6 +25,7 @@ import com.google.auth.Credentials; import com.google.cloud.bigtable.data.v2.stub.EnhancedBigtableStubSettings; import com.google.cloud.bigtable.data.v2.stub.metrics.ChannelPoolMetricsTracer; +import com.google.cloud.bigtable.data.v2.stub.metrics.DirectAccessMetricsRecorder; import com.google.common.base.Preconditions; import io.grpc.ManagedChannel; import java.io.IOException; @@ -47,19 +48,25 @@ public final class BigtableTransportChannelProvider implements TransportChannelP private final ChannelPrimer channelPrimer; @Nullable private final ChannelPoolMetricsTracer channelPoolMetricsTracer; @Nullable private final ScheduledExecutorService backgroundExecutor; - @Nullable private final Map headers; // <-- Add this field + @Nullable private final Map headers; + @Nullable private final DirectAccessMetricsRecorder directAccessMetricsRecorder; private BigtableTransportChannelProvider( InstantiatingGrpcChannelProvider instantiatingGrpcChannelProvider, ChannelPrimer channelPrimer, ChannelPoolMetricsTracer channelPoolMetricsTracer, ScheduledExecutorService backgroundExecutor, - @Nullable Map headers) { + @Nullable Map headers, + @Nullable DirectAccessMetricsRecorder directAccessMetricsRecorder) { delegate = Preconditions.checkNotNull(instantiatingGrpcChannelProvider); this.channelPrimer = channelPrimer; this.channelPoolMetricsTracer = channelPoolMetricsTracer; this.backgroundExecutor = backgroundExecutor; this.headers = headers; + this.directAccessMetricsRecorder = + directAccessMetricsRecorder != null + ? directAccessMetricsRecorder + : DirectAccessMetricsRecorder.NOOP_DIRECT_ACCESS_METRIC_RECORDER; } @Override @@ -84,7 +91,12 @@ public BigtableTransportChannelProvider withExecutor(Executor executor) { InstantiatingGrpcChannelProvider newChannelProvider = (InstantiatingGrpcChannelProvider) delegate.withExecutor(executor); return new BigtableTransportChannelProvider( - newChannelProvider, channelPrimer, channelPoolMetricsTracer, backgroundExecutor, headers); + newChannelProvider, + channelPrimer, + channelPoolMetricsTracer, + backgroundExecutor, + headers, + directAccessMetricsRecorder); } @Override @@ -97,7 +109,12 @@ public TransportChannelProvider withBackgroundExecutor(ScheduledExecutorService InstantiatingGrpcChannelProvider newChannelProvider = (InstantiatingGrpcChannelProvider) delegate.withBackgroundExecutor(executor); return new BigtableTransportChannelProvider( - newChannelProvider, channelPrimer, channelPoolMetricsTracer, executor, headers); + newChannelProvider, + channelPrimer, + channelPoolMetricsTracer, + executor, + headers, + directAccessMetricsRecorder); } @Override @@ -110,7 +127,12 @@ public BigtableTransportChannelProvider withHeaders(Map headers) InstantiatingGrpcChannelProvider newChannelProvider = (InstantiatingGrpcChannelProvider) delegate.withHeaders(headers); return new BigtableTransportChannelProvider( - newChannelProvider, channelPrimer, channelPoolMetricsTracer, backgroundExecutor, headers); + newChannelProvider, + channelPrimer, + channelPoolMetricsTracer, + backgroundExecutor, + headers, + directAccessMetricsRecorder); } @Override @@ -123,7 +145,12 @@ public TransportChannelProvider withEndpoint(String endpoint) { InstantiatingGrpcChannelProvider newChannelProvider = (InstantiatingGrpcChannelProvider) delegate.withEndpoint(endpoint); return new BigtableTransportChannelProvider( - newChannelProvider, channelPrimer, channelPoolMetricsTracer, backgroundExecutor, headers); + newChannelProvider, + channelPrimer, + channelPoolMetricsTracer, + backgroundExecutor, + headers, + directAccessMetricsRecorder); } @Deprecated @@ -138,7 +165,12 @@ public TransportChannelProvider withPoolSize(int size) { InstantiatingGrpcChannelProvider newChannelProvider = (InstantiatingGrpcChannelProvider) delegate.withPoolSize(size); return new BigtableTransportChannelProvider( - newChannelProvider, channelPrimer, channelPoolMetricsTracer, backgroundExecutor, headers); + newChannelProvider, + channelPrimer, + channelPoolMetricsTracer, + backgroundExecutor, + headers, + directAccessMetricsRecorder); } // We need this for direct access checker. @@ -195,12 +227,12 @@ public TransportChannel getTransportChannel() throws IOException { try { isDirectAccessEligible = directAccessChecker.check(maybeDirectAccessChannel); } catch (Exception e) { - LOG.log( - Level.INFO, - "Failed to probe for Direct Access eligibility. Falling back to default routing.", - e); + LOG.log(Level.INFO, "Client is not direct access eligible, using standard transport.", e); } + // constructor ensures no null + directAccessMetricsRecorder.recordEligibility(isDirectAccessEligible); + InstantiatingGrpcChannelProvider selectedProvider; ManagedChannel preCreatedChannel = null; @@ -266,20 +298,27 @@ public TransportChannelProvider withCredentials(Credentials credentials) { InstantiatingGrpcChannelProvider newChannelProvider = (InstantiatingGrpcChannelProvider) delegate.withCredentials(credentials); return new BigtableTransportChannelProvider( - newChannelProvider, channelPrimer, channelPoolMetricsTracer, backgroundExecutor, headers); + newChannelProvider, + channelPrimer, + channelPoolMetricsTracer, + backgroundExecutor, + headers, + directAccessMetricsRecorder); } /** Creates a BigtableTransportChannelProvider. */ public static BigtableTransportChannelProvider create( InstantiatingGrpcChannelProvider instantiatingGrpcChannelProvider, ChannelPrimer channelPrimer, - ChannelPoolMetricsTracer outstandingRpcsMetricTracker, - ScheduledExecutorService backgroundExecutor) { + ChannelPoolMetricsTracer channelPoolMetricsTracer, + ScheduledExecutorService backgroundExecutor, + DirectAccessMetricsRecorder directAccessMetricsRecorder) { return new BigtableTransportChannelProvider( instantiatingGrpcChannelProvider, channelPrimer, - outstandingRpcsMetricTracker, + channelPoolMetricsTracer, backgroundExecutor, - null); + null, + directAccessMetricsRecorder); } } diff --git a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/DirectAccessChecker.java b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/DirectAccessChecker.java index 6e4400578a..f3a5b142c3 100644 --- a/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/DirectAccessChecker.java +++ b/google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/gaxx/grpc/DirectAccessChecker.java @@ -15,9 +15,11 @@ */ package com.google.cloud.bigtable.gaxx.grpc; +import com.google.api.core.InternalApi; import io.grpc.ManagedChannel; -/** Evaluates whether a given channel supports Direct Access. */ +@InternalApi +/* Evaluates whether a given channel supports Direct Access. */ public interface DirectAccessChecker { /// Performs a request on the provided channel to check for Direct Access eligibility. boolean check(ManagedChannel channel); From f8d03471349ef5a342f25405516099652fbf58ed Mon Sep 17 00:00:00 2001 From: Sushan Bhattarai Date: Tue, 24 Feb 2026 12:49:02 -0500 Subject: [PATCH 3/3] add metrics for direct access --- .../bigtable/gaxx/grpc/BigtableDirectAccessCheckerTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/gaxx/grpc/BigtableDirectAccessCheckerTest.java b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/gaxx/grpc/BigtableDirectAccessCheckerTest.java index 930f6d48a8..5d4d2139e3 100644 --- a/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/gaxx/grpc/BigtableDirectAccessCheckerTest.java +++ b/google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/gaxx/grpc/BigtableDirectAccessCheckerTest.java @@ -39,7 +39,6 @@ public class BigtableDirectAccessCheckerTest { public void setUp() { mockPrimer = mock(BigtableChannelPrimer.class); mockChannel = mock(ManagedChannel.class); - // Ensure the call returns empty attributes by default to avoid NullPointerExceptions checker = new BigtableDirectAccessChecker(mockPrimer); } @@ -64,7 +63,7 @@ public void testProbeErrorSituationReturnsFalse() { } @Test - public void testProbe_TimeoutDuringPriming_ReturnsFalse() { + public void testProbeTimeoutDuringPrimingReturnsFalse() { doThrow( io.grpc.Status.DEADLINE_EXCEEDED .withDescription("deadline exceeded after 1m")