Skip to content

feat(bigtable): modularize Direct Access compatibility check - #19987

Merged
sushanb merged 7 commits into
googleapis:mainfrom
sushanb:feat/bigtable-directpath-checker
Jun 19, 2026
Merged

feat(bigtable): modularize Direct Access compatibility check#19987
sushanb merged 7 commits into
googleapis:mainfrom
sushanb:feat/bigtable-directpath-checker

Conversation

@sushanb

@sushanb sushanb commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Extract the Direct Access compatibility decision out of BigtableChannelPool behind a new DirectAccessChecker interface so each channel pool factory can plug in its own strategy.
  • Today's only implementation, pingAndWarmDirectAccessChecker, preserves current behavior (PingAndWarm probe + ALTS check + async failure investigation + direct_access/compatible metric). The upcoming session pool factory will plug in a GetClientConfiguration-based checker (driven by ClientConfigurationManager) without touching the pool.
  • A small disabledDirectAccessChecker keeps the direct_access/compatible{reason=manually_disabled} reading when the env/config disables Direct Access — the env var check moves up to the factory so the pool no longer re-reads CBT_ENABLE_DIRECTPATH.

Changes

  • New internal/transport/direct_access_checker.go: DirectAccessChecker interface, pingAndWarmDirectAccessChecker, disabledDirectAccessChecker, and the moved helpers (xdsCdsURITemplate, checkIPPlumbing, checkKernelRoutes, probeSingleEndpoint).
  • internal/transport/connpool.go: remove directAccessDialer, directAccessFeatureFlagsMD, daEligibleGauge fields, the WithDirectAccessDialer / WithDirectAccessFeatureFlagsMetadata options, the in-pool probe + investigation chain, the duplicate CBT_ENABLE_DIRECTPATH check, and the metric reporting helpers. Add WithDirectAccessChecker. NewBigtableChannelPool now just delegates to the checker.
  • internal/transport/channel_pool_factory.go: when isDirectAccessEnabled(config) is true, wire a pingAndWarmDirectAccessChecker; otherwise wire a disabledDirectAccessChecker. Both passed via WithDirectAccessChecker.
  • internal/transport/connpool_test.go: six TestDirectAccessLogic subtests updated to construct the appropriate checker. The env-disabled subtest becomes DirectAccess_DisabledChecker since the env-var gate has moved to the factory layer.

Test plan

  • cd bigtable && go build ./...
  • cd bigtable && go vet ./...
  • cd bigtable && go test ./internal/transport/... -count=1 (full transport suite, including all six TestDirectAccessLogic subtests, TestCreateAndStartManagedChannelPool*, and TestManagedChannelPool_Close)

Extract the Direct Access (DirectPath / DirectPathXds) compatibility
decision out of BigtableChannelPool behind a new DirectAccessChecker
interface so each channel pool factory can plug in its own strategy.

The classic channel pool factory wires up a PingAndWarm-based checker
(today's only behavior); the upcoming session pool factory will swap in
a GetClientConfiguration-based checker driven by ClientConfigurationManager
without touching the pool. A disabledDirectAccessChecker preserves the
direct_access/compatible metric for the env/config-disabled path.

This removes the in-pool probe (checkIfDirectAccessCompatible), the
failure investigation chain (investigateDirectAccessFailure,
probeSingleEndpoint, checkIPPlumbing, checkKernelRoutes), the metric
reporting helpers, the duplicate CBT_ENABLE_DIRECTPATH env check, and the
WithDirectAccessDialer / WithDirectAccessFeatureFlagsMetadata options
from BigtableChannelPool. WithDirectAccessChecker replaces them as the
single seam.
@sushanb
sushanb requested review from a team as code owners June 16, 2026 05:50
@product-auto-label product-auto-label Bot added the api: bigtable Issues related to the Bigtable API. label Jun 16, 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 refactors the Direct Access compatibility check logic by modularizing it into a pluggable DirectAccessChecker interface, moving the classic PingAndWarm probe and the disabled state logic out of the main connection pool into separate implementations in a new file. The review feedback highlights a critical compilation issue where the new file is declared under the wrong package name (internal instead of transport), and suggests improving context propagation by passing the active context to metric recording calls instead of using context.Background().

Comment thread bigtable/internal/transport/direct_access_checker.go
Comment thread bigtable/internal/transport/direct_access_checker.go Outdated
Comment thread bigtable/internal/transport/direct_access_checker.go Outdated
@sushanb
sushanb requested a review from mutianf June 16, 2026 19:11
Comment thread bigtable/internal/transport/connpool.go Outdated
// Add the cached gauge instrument
daEligibleGauge metric.Int64Gauge
// directAccessChecker is the pluggable Direct Access compatibility
// strategy. nil means Direct Access is not configured for this pool;

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.

is this still relevant? checker should always be configured on the pool now right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Made it required.

Comment thread bigtable/internal/transport/connpool.go Outdated
} else {
btopt.Debugf(pool.logger, "bigtable_connpool: Direct Access manually disabled via config or environment.")
pool.reportDirectAccessFailure("manually_disabled")
btopt.Debugf(pool.logger, "bigtable_connpool: Direct Access not configured.")

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.

Do we still need the null check and should we update this log?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

if directAccessChecker is null, we skipp it so.

@mutianf mutianf Jun 17, 2026

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.

"Direct Access not configured." -> this PR seems to suggest that even when Direct Access is not configured, we still provide a DisableDirectAccessChecker. So I wonder if we should update the log to just "DirectAccessChecker not configured" ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The null check is gone — NewBigtableChannelPool now refuses construction without a checker (commit a554712). The "Direct Access not configured." log line went away with the else branch in the same commit, so no log update needed.

// FeatureFlagsMetadata returns the feature-flag metadata to attach when
// priming direct-access connections post-check. Only consulted when
// CheckCompatibility returned true.
FeatureFlagsMetadata() metadata.MD

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.

why do we need to return feature flags?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed.

Comment thread bigtable/internal/transport/direct_access_checker.go
Comment thread bigtable/internal/transport/direct_access_checker.go
sushanb added 5 commits June 16, 2026 22:29
The factory was threading directAccessMD into the pool twice: once via
WithFeatureFlagsMetadata (read by the standard-path connection factory)
and once into the Direct Access checker (read by the DA factory after a
successful probe). Both held the same value.

Make the checker the single source of truth. NewBigtableChannelPool now
seeds factoryFeatureFlagsMD from checker.FeatureFlagsMetadata() before
the probe runs, so both the DA and standard-path connection factories
read it from the same place. The disabled checker stub carries the
metadata too, so the manually-disabled path keeps the same priming
behavior. WithFeatureFlagsMetadata remains as the fallback for callers
that build a pool without a checker.
disabledDirectAccessChecker recorded its single manually_disabled
reading inside the constructor using context.Background(). Move the
recording into CheckCompatibility so it runs with the pool ctx the
factory already threads in — matching reportSuccess/reportFailure on
the ping-and-warm checker and keeping construction side-effect-free.

The pool calls CheckCompatibility exactly once at startup, so the
single-emission guarantee is preserved without an explicit `reported`
flag. Thread the same ctx through pingAndWarmDirectAccessChecker's
reportSuccess for consistency.
Reverse the prior indirection: the checker no longer owns or surfaces
the feature-flag metadata, and the pool no longer sources it from the
checker. The factory passes WithFeatureFlagsMetadata(directAccessMD)
directly, and NewBigtableChannelPool wires pool.featureFlagsMD into
the connectionFactory for both the direct-access and standard-path
priming flows.

Net effect: one option for feature-flag metadata everywhere, smaller
DirectAccessChecker interface (just CheckCompatibility + Dialer), and
the disabled stub stops carrying state it never used itself —
pingAndWarmDirectAccessChecker still holds featureFlagsMD privately
for its own probe Prime() calls.
The factory always wires either pingAndWarmDirectAccessChecker or the
disabled stub, so the nil branch in NewBigtableChannelPool was only
load-bearing for tests. Tighten the contract: refuse construction when
no checker is set, drop the conditional around the compatibility probe,
and document that the disabled stub is the way to opt out.

Tests pass the disabled stub via the shared poolOpts() helper; the
handful of call sites that bypass that helper now pass it explicitly,
and TestNewBigtableChannelPoolEdgeCases gains a NilDirectAccessChecker
case to lock the new error in place.
The field is no longer optional: NewBigtableChannelPool refuses
construction without one. Reflect that in the godoc and point
opt-out callers at the disabled stub.
@sushanb
sushanb requested a review from mutianf June 17, 2026 21:23
…tpath-checker

# Conflicts:
#	bigtable/internal/transport/connpool.go
@sushanb
sushanb enabled auto-merge (squash) June 19, 2026 06:43
@sushanb
sushanb merged commit a25e93d into googleapis:main Jun 19, 2026
18 checks passed
sushanb added a commit that referenced this pull request Jun 23, 2026
…erface (#20027)

## Summary

Mirrors the pluggable shape that PR #19987 established for the Direct
Access compatibility check. Channel priming had been hard-wired into the
pool via three loose options (`WithInstanceName` / `WithAppProfile` /
`WithFeatureFlagsMetadata`) that the `connectionFactory` had to stitch
back together on every dial.

- New `ChannelPrimer` interface (`channel_primer.go`): `Prime(ctx,
*BigtableConn) error`. The pluggable extension point that future pool
factories (session-based, custom) can swap in.
- `pingAndWarmChannelPrimer` is today's only implementation; it owns the
`(instance, appProfile, featureFlagsMD)` tuple and delegates to
`BigtableConn.Prime` — the existing `PingAndWarm` RPC stays untouched.
- New `WithChannelPrimer` pool option replaces `WithInstanceName` /
`WithAppProfile` / `WithFeatureFlagsMetadata`.
- `connectionFactory` now carries a `ChannelPrimer` instead of three
individual fields. **When the primer is nil, `primeWithRetry` returns
immediately** — the pool dials the channel and puts it straight into
rotation, no `PingAndWarm` sent. The classic channel pool factory still
wires the `pingAndWarmChannelPrimer`, so user-facing default behavior is
unchanged.
- `pingAndWarmDirectAccessChecker` reuses the same primer rather than
duplicating `conn.Prime(ctx, instance, profile, flags)` in two places.
The factory now constructs **one** primer and shares it with both the
pool (via `WithChannelPrimer`) and the checker, eliminating the
three-arg drift that existed across the two consumers.

## Test plan

- [x] `go build ./...` clean
- [x] `go vet ./...` clean
- [x] `golint` clean on touched files
- [x] `go test ./internal/transport/...` passes (existing tests
migrated; new `channel_primer_test.go` covers (a)
`pingAndWarmChannelPrimer.Prime` issues `PingAndWarm` carrying the
configured feature-flag metadata, and (b) `connectionFactory` skips
priming entirely when the primer is nil)
sushanb added a commit to sushanb/google-cloud-go that referenced this pull request Jul 6, 2026
…oogleapis#19987)

Hand-port of upstream a25e93d into the sessionz branch. Cherry-pick
failed on layout drift (bigtable/channel_pool_factory.go sits at the
bigtable/ top level here, not in internal/transport/), so the diff was
reproduced by hand.

- New internal/transport/direct_access_checker.go: DirectAccessChecker
  interface, pingAndWarmDirectAccessChecker, disabledDirectAccessChecker,
  and the moved helpers (xdsCdsURITemplate, checkIPPlumbing,
  checkKernelRoutes, probeSingleEndpoint). Constructors exported as
  NewPingAndWarmDirectAccessChecker / NewDisabledDirectAccessChecker so
  the top-level bigtable package can wire them.
- internal/transport/connpool.go: remove directAccessDialer,
  directAccessFeatureFlagsMD, daEligibleGauge fields, the
  WithDirectAccessDialer / WithDirectAccessFeatureFlagsMetadata options,
  the in-pool probe + investigation chain, the duplicate
  CBT_ENABLE_DIRECTPATH check, and the metric reporting helpers. Add
  WithDirectAccessChecker. NewBigtableChannelPool now delegates to the
  checker (and refuses construction without one).
- bigtable/channel_pool_factory.go: gate on isDirectAccessEnabled(config)
  and wire a pingAndWarm checker when Direct Access is on, otherwise a
  disabled checker. The env var check moves out of the pool.
- Test updates: poolOpts() adds the disabled checker;
  TestNewBigtableChannelPoolEdgeCases gains a NilDirectAccessChecker
  case; the six TestDirectAccessLogic subtests construct explicit
  checkers; DirectAccess_DisabledByEnv becomes DirectAccess_DisabledChecker
  since the env-var gate has moved to the factory layer.

Verified: go build ./... , go vet ./... , and
go test ./internal/transport/... ./debugview/ -count=1 -short all pass.
sushanb added a commit to sushanb/google-cloud-go that referenced this pull request Jul 6, 2026
…erface (port of googleapis#20027)

Hand-port of upstream 5214ab7 into the sessionz branch. Cherry-pick
failed on layout drift for the same reason as PR googleapis#19987 — the top-level
bigtable/channel_pool_factory.go doesn't exist upstream — so the diff
was reproduced by hand.

Mirrors the pluggable shape that the DAC extract established:

- New internal/transport/channel_primer.go: ChannelPrimer interface with
  a single Prime(ctx, *BigtableConn) error method. Today's default
  implementation, pingAndWarmChannelPrimer, owns the (instance name,
  app profile, feature-flag metadata) tuple and delegates to
  BigtableConn.Prime. Constructor exported as NewPingAndWarmChannelPrimer
  so the top-level bigtable package can wire it.
- New internal/transport/channel_primer_test.go: covers (a) primer
  delegates PingAndWarm with configured feature-flag metadata,
  (b) connectionFactory skips priming entirely when primer is nil.
- connpool.go: add WithChannelPrimer option and channelPrimer field on
  BigtableChannelPool. Drop WithFeatureFlagsMetadata and the featureFlagsMD
  field. Keep WithInstanceName / WithAppProfile — the sessionz
  ChannelPoolSnapshot still reads them for debugview channelz/configz
  identity. connectionFactory now carries a ChannelPrimer instead of
  three individual fields; primeWithRetry returns immediately when the
  primer is nil so the pool can be used without priming.
- direct_access_checker.go: pingAndWarmDirectAccessChecker now takes a
  ChannelPrimer instead of (instance, appProfile, featureFlagsMD) — both
  CheckCompatibility and probeSingleEndpoint route their Prime through
  it. NewPingAndWarmDirectAccessChecker signature updated accordingly.
- bigtable/channel_pool_factory.go: construct one ChannelPrimer and
  share it with both the pool (via WithChannelPrimer) and the checker,
  eliminating the three-arg drift between them.

Test updates in connpool_test.go: poolOpts() adds the primer,
NilDirectAccessChecker case takes a primer to isolate the checker gate,
TestConnectionFactory factory struct uses the primer field, and the
five TestDirectAccessLogic subtests construct a primer for their DA
checker.

Verified: go build ./... , go vet ./... , and
go test ./internal/transport/... ./debugview/ -count=1 -short all pass.
sushanb added a commit to sushanb/google-cloud-go that referenced this pull request Jul 7, 2026
Take upstream/main's versions of both files. Fork's ports of googleapis#20027
and googleapis#19987 exported the constructors (NewPingAndWarm...) and made
them return interfaces; upstream landed them unexported (newPingAndWarm...)
returning concrete types. Upstream's channel_pool_factory.go calls the
unexported forms, so restoring these files re-aligns the trio.

Downstream break to fix next: fork's internal/session and
internal/transport/session_* code that called the uppercase exported
constructors will now need to be updated to the unexported forms or
to route through the interface accessors upstream provides.
sushanb added a commit to sushanb/google-cloud-go that referenced this pull request Jul 10, 2026
Restructures the spec-driven review deck from a 5-slide feature tour
into a 4-slide 'why we moved from one-shot prompting to specs' story:

- Slide 1 — one-shot prompting definition + the three PRs that shipped
  this way (googleapis#19987 DirectAccessChecker, googleapis#20027 ChannelPrimer, googleapis#20099
  client-side-metrics decouple). Common shape: extract one implicitly-
  unary abstraction into an interface.
- Slide 2 — Jetstream is 30k+ LOC; one-shot breaks. Introduces the five
  spec files with verified invariant counts (10 / 4 / 5 / 3 / 12+PartC)
  and one illustrative rule per spec.
- Slide 3 — SESSION_COMPONENT_SPEC.md tour: Part A (7-layer descriptive
  map), Part B (12 boundary MUST-rules with grep patterns), Part C
  (ownership matrix excerpts).
- Slide 4 — three prompt sizes with real examples from this branch:
  (1) simple refactor — activeVRPC/casActiveVRPC accessor extraction;
  (2) logic addition — adaptive session-creation throttler + how the
  spec invariants (POOL #5, CLIENT #3, B6) chain together;
  (3) big feature — unified debugview/ (7 z-pages behind one Handler)
  and how B3, POOL #4, B10 crystallized during that refactor.
  Plus reviewer-agent flow and PASS/VIOLATION/AMBIGUOUS semantics.

CSS and navigation unchanged. Counter reflects 4 slides. Companion
specs-deck.md not updated in this commit — will follow.
hongalex pushed a commit that referenced this pull request Jul 23, 2026
🤖 I have created a release *beep* *boop*
---


##
[1.51.0](bigtable/v1.50.0...bigtable/v1.51.0)
(2026-07-23)


### Features

* **bigtable:** Add ChainInterceptors and RetryingVRpc for vRPC pipeline
([#20185](#20185))
([c7a832a](c7a832a))
* **bigtable:** Add ClientConfigurationManager
([#19986](#19986))
([3a8f927](3a8f927))
* **bigtable:** Add debug tag counter (recordDebugTag / assertDebugTag)
([#20114](#20114))
([3c97590](3c97590))
* **bigtable:** Add lazyPool helper for on-demand session pool opening
([#20182](#20182))
([f6ae3fb](f6ae3fb))
* **bigtable:** Add PeakEwma continuous time-decay latency tracker
([#20187](#20187))
([9d124ef](9d124ef))
* **bigtable:** Add PoolSizer for server-driven session pool capacity
([#20189](#20189))
([57ebbeb](57ebbeb))
* **bigtable:** Add session package with SessionClient + SessionTableAPI
interfaces
([#20180](#20180))
([4b82fd2](4b82fd2))
* **bigtable:** Add Session primitives (AttemptOutcome, vRPC ctx,
msgtype)
([#20116](#20116))
([e1011e2](e1011e2))
* **bigtable:** Add Session state enum
([#19981](#19981))
([0748972](0748972))
* **bigtable:** Add SessionThrottler / AdaptiveSessionThrottler for
OpenSession pacing
([#20184](#20184))
([02e3c6d](02e3c6d))
* **bigtable:** Add SessionThrottler / AdaptiveSessionThrottler for
OpenSession pacing
([#20184](#20184))
([29be83e](29be83e))
* **bigtable:** Add sessionTracer for per-Session lifecycle + vRPC
metrics
([#20190](#20190))
([a466345](a466345))
* **bigtable:** Enable new auth library and JWT for instance admin
client
([#20013](#20013))
([21c4a44](21c4a44))
* **bigtable:** Modularize channel priming behind a ChannelPrimer
interface
([#20027](#20027))
([5214ab7](5214ab7))
* **bigtable:** Modularize Direct Access compatibility check
([#19987](#19987))
([a25e93d](a25e93d))
* **o11y:** Regenerate clients for LRO tracing
([#20107](#20107))
([779074e](779074e))


### Bug Fixes

* **bigtable:** Default cluster/zone in toOtelMetricAttrs to avoid
Monitoring reject
([#20178](#20178))
([14493f4](14493f4))
* **bigtable:** Eliminate stats-handler MD race in internal/metrics
tracer
([#20158](#20158))
([c387066](c387066))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: bigtable Issues related to the Bigtable API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants