fix(Datastore): Create a plaintext transport channel when using the Emulator#12721
fix(Datastore): Create a plaintext transport channel when using the Emulator#12721mw-ship-it wants to merge 1 commit intogoogleapis:mainfrom
Conversation
## Problem Statement
When using the `java-datastore` SDK with `GrpcTransportOptions` and connecting
to a local Datastore Emulator (e.g., via the `DATASTORE_EMULATOR_HOST`
environment variable), the client consistently throws an `UNAUTHENTICATED:
Request is missing required authentication credential` error.
### The Root Cause
In `GrpcDatastoreRpc.java`, the SDK constructor detects the emulator correctly
and creates an unauthenticated, plaintext gRPC channel specifically tailored for
local development via `getClientContextForEmulator(datastoreOptions)`.
However, right after generating this specialized context, the SDK uses a builder
(`DatastoreStubSettings.newBuilder`) that **unconditionally overwrites** the
transport channel provider with a default production-oriented channel provider
in order to set channel pooling limits.
Because the emulator's custom local channel gets overwritten by this production
channel configuration, the SDK ultimately discards the emulator settings and
attempts to connect securely to the actual production endpoint
(`datastore.googleapis.com`), but without any credentials (since it knows it's
an emulator). Production immediately rejects the call.
## The Fix
The fix converts the monolithic `DatastoreStubSettings` builder chain into a
sequential one, and conditionally applies the custom channel pooling provider
only if the environment is **not** an emulator:
```java
DatastoreStubSettings.Builder datastoreStubSettingsBuilder =
DatastoreStubSettings.newBuilder(clientContext)
.applyToAllUnaryMethods(retrySettingSetter(datastoreOptions));
if (!isEmulator(datastoreOptions)) {
datastoreStubSettingsBuilder.setTransportChannelProvider(
// ... Production connection pool settings
);
}
```
## Why this is a safe fix without side effects
1. **Zero Impact on Production Traffic:** The fix relies exclusively on the
existing, battle-tested `isEmulator()` method. If the SDK is pointed at
production, `!isEmulator` returns true, and the code path executes the exact
same transport channel override as before. Production throughput, connection
pooling, and auth behavior remain 100% untouched.
2. **Restores Original Intent:** For emulator traffic, the channel provider
override is gracefully skipped. This allows the custom, unauthenticated
local gRPC channel created specifically for the emulator in
`getClientContextForEmulator()` to survive and be used by the
`GrpcDatastoreStub`.
3. **Architecturally Safe:** It introduces no new dependencies, requires no new
variables or complex state management, and makes no changes to the public
API surface. It simply resolves an unintentional clobbering of configuration
variables at object instantiation time.
There was a problem hiding this comment.
Code Review
This pull request updates the GrpcDatastoreRpc class to conditionally configure the gRPC connection pool, ensuring it is only set when not running in an emulator environment. Additionally, the DatastoreStubSettings.Builder has been renamed for clarity. The review feedback recommends removing the descriptive comment for the connection pool configuration to align with repository standards regarding stale or misplaced comments.
| /* For non-emulator, configure default gRPC Connection pool with minChannelCount = 1 */ | ||
| DatastoreStubSettings.Builder datastoreStubSettingsBuilder = | ||
| DatastoreStubSettings.newBuilder(clientContext) | ||
| .applyToAllUnaryMethods(retrySettingSetter(datastoreOptions)) | ||
| .setTransportChannelProvider( | ||
| DatastoreSettings.defaultGrpcTransportProviderBuilder() | ||
| .setChannelPoolSettings( | ||
| ChannelPoolSettings.builder() | ||
| .setInitialChannelCount(DatastoreOptions.INIT_CHANNEL_COUNT) | ||
| .setMinChannelCount(DatastoreOptions.MIN_CHANNEL_COUNT) | ||
| .build()) | ||
| .build()); | ||
|
|
||
| datastoreStub = GrpcDatastoreStub.create(builder.build()); | ||
| .applyToAllUnaryMethods(retrySettingSetter(datastoreOptions)); | ||
| if (!isEmulator(datastoreOptions)) { |
There was a problem hiding this comment.
The comment describing the connection pool configuration is misplaced and refers to a specific implementation context. In accordance with repository rules, comments that refer to stale or misplaced implementations should be removed rather than updated or moved to a different block.
DatastoreStubSettings.Builder datastoreStubSettingsBuilder =
DatastoreStubSettings.newBuilder(clientContext)
.applyToAllUnaryMethods(retrySettingSetter(datastoreOptions));
if (!isEmulator(datastoreOptions)) {References
- Remove comments that refer to stale implementations instead of updating them.
Problem Statement
When using the
java-datastoreSDK withGrpcTransportOptionsand connecting to a local Datastore Emulator (e.g., via theDATASTORE_EMULATOR_HOSTenvironment variable), the client consistently throws anUNAUTHENTICATED: Request is missing required authentication credentialerror.The Root Cause
In
GrpcDatastoreRpc.java, the SDK constructor detects the emulator correctly and creates an unauthenticated, plaintext gRPC channel specifically tailored for local development viagetClientContextForEmulator(datastoreOptions).However, right after generating this specialized context, the SDK uses a builder (
DatastoreStubSettings.newBuilder) that unconditionally overwrites the transport channel provider with a default production-oriented channel provider in order to set channel pooling limits.Because the emulator's custom local channel gets overwritten by this production channel configuration, the SDK ultimately discards the emulator settings and attempts to connect securely to the actual production endpoint (
datastore.googleapis.com), but without any credentials (since it knows it's an emulator). Production immediately rejects the call.The Fix
The fix converts the monolithic
DatastoreStubSettingsbuilder chain into a sequential one, and conditionally applies the custom channel pooling provider only if the environment is not an emulator:Why this is a safe fix without side effects
Zero Impact on Production Traffic: The fix relies exclusively on the existing, battle-tested
isEmulator()method. If the SDK is pointed at production,!isEmulatorreturns true, and the code path executes the exact same transport channel override as before. Production throughput, connection pooling, and auth behavior remain 100% untouched.Restores Original Intent: For emulator traffic, the channel provider override is gracefully skipped. This allows the custom, unauthenticated local gRPC channel created specifically for the emulator in
getClientContextForEmulator()to survive and be used by theGrpcDatastoreStub.Architecturally Safe: It introduces no new dependencies, requires no new variables or complex state management, and makes no changes to the public API surface. It simply resolves an unintentional clobbering of configuration variables at object instantiation time.