Skip to content

Commit 1bbd24d

Browse files
committed
fix: resolve comments and add new tests to verify that the route-to-leader header exists for RW transactions and does not exist for RO transactions or when the leader aware routing feature is disabled.
1 parent 0156457 commit 1bbd24d

File tree

4 files changed

+95
-14
lines changed

4 files changed

+95
-14
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,11 @@ static Builder newBuilder() {
161161
private SingleReadContext(Builder builder) {
162162
super(builder);
163163
this.bound = builder.bound;
164-
this.routeToLeader = false;
164+
}
165+
166+
@Override
167+
protected boolean isRouteToLeader() {
168+
return false;
165169
}
166170

167171
@GuardedBy("lock")
@@ -292,7 +296,11 @@ static Builder newBuilder() {
292296
this.timestamp = builder.timestamp;
293297
this.transactionId = builder.transactionId;
294298
}
295-
this.routeToLeader = false;
299+
}
300+
301+
@Override
302+
protected boolean isRouteToLeader() {
303+
return false;
296304
}
297305

298306
@Override
@@ -350,7 +358,7 @@ void initTransaction() {
350358
.setOptions(options)
351359
.build();
352360
Transaction transaction =
353-
rpc.beginTransaction(request, session.getOptions(), routeToLeader);
361+
rpc.beginTransaction(request, session.getOptions(), isRouteToLeader());
354362
if (!transaction.hasReadTimestamp()) {
355363
throw SpannerExceptionFactory.newSpannerException(
356364
ErrorCode.INTERNAL, "Missing expected transaction.read_timestamp metadata field");
@@ -383,7 +391,6 @@ void initTransaction() {
383391
Span span;
384392
private final int defaultPrefetchChunks;
385393
private final QueryOptions defaultQueryOptions;
386-
protected boolean routeToLeader = false;
387394

388395
@GuardedBy("lock")
389396
private boolean isValid = true;
@@ -420,6 +427,10 @@ long getSeqNo() {
420427
return seqNo.incrementAndGet();
421428
}
422429

430+
protected boolean isRouteToLeader() {
431+
return false;
432+
}
433+
423434
@Override
424435
public final ResultSet read(
425436
String table, KeySet keys, Iterable<String> columns, ReadOption... options) {
@@ -669,7 +680,7 @@ CloseableIterator<PartialResultSet> startStream(@Nullable ByteString resumeToken
669680
}
670681
SpannerRpc.StreamingCall call =
671682
rpc.executeQuery(
672-
request.build(), stream.consumer(), session.getOptions(), routeToLeader);
683+
request.build(), stream.consumer(), session.getOptions(), isRouteToLeader());
673684
call.request(prefetchChunks);
674685
stream.setCall(call, request.getTransaction().hasBegin());
675686
return stream;
@@ -797,7 +808,8 @@ CloseableIterator<PartialResultSet> startStream(@Nullable ByteString resumeToken
797808
}
798809
builder.setRequestOptions(buildRequestOptions(readOptions));
799810
SpannerRpc.StreamingCall call =
800-
rpc.read(builder.build(), stream.consumer(), session.getOptions(), routeToLeader);
811+
rpc.read(
812+
builder.build(), stream.consumer(), session.getOptions(), isRouteToLeader());
801813
call.request(prefetchChunks);
802814
stream.setCall(call, /* withBeginTransaction = */ builder.getTransaction().hasBegin());
803815
return stream;

google-cloud-spanner/src/main/java/com/google/cloud/spanner/TransactionRunnerImpl.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,11 @@ private TransactionContextImpl(Builder builder) {
196196
this.trackTransactionStarter = builder.trackTransactionStarter;
197197
this.options = builder.options;
198198
this.finishedAsyncOperations.set(null);
199-
this.routeToLeader = true;
199+
}
200+
201+
@Override
202+
protected boolean isRouteToLeader() {
203+
return true;
200204
}
201205

202206
private void increaseAsyncOperations() {
@@ -256,7 +260,7 @@ ApiFuture<Void> ensureTxnAsync() {
256260

257261
private void createTxnAsync(final SettableApiFuture<Void> res) {
258262
span.addAnnotation("Creating Transaction");
259-
final ApiFuture<ByteString> fut = session.beginTransactionAsync(options, routeToLeader);
263+
final ApiFuture<ByteString> fut = session.beginTransactionAsync(options, isRouteToLeader());
260264
fut.addListener(
261265
() -> {
262266
try {
@@ -718,7 +722,7 @@ private ResultSet internalExecuteUpdate(
718722
/* withTransactionSelector = */ true);
719723
try {
720724
com.google.spanner.v1.ResultSet resultSet =
721-
rpc.executeQuery(builder.build(), session.getOptions(), routeToLeader);
725+
rpc.executeQuery(builder.build(), session.getOptions(), isRouteToLeader());
722726
if (resultSet.getMetadata().hasTransaction()) {
723727
onTransactionMetadata(
724728
resultSet.getMetadata().getTransaction(), builder.getTransaction().hasBegin());
@@ -748,7 +752,7 @@ public ApiFuture<Long> executeUpdateAsync(Statement statement, UpdateOption... o
748752
// Register the update as an async operation that must finish before the transaction may
749753
// commit.
750754
increaseAsyncOperations();
751-
resultSet = rpc.executeQueryAsync(builder.build(), session.getOptions(), routeToLeader);
755+
resultSet = rpc.executeQueryAsync(builder.build(), session.getOptions(), isRouteToLeader());
752756
} catch (Throwable t) {
753757
decreaseAsyncOperations();
754758
throw t;

google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/SpannerMetadataProvider.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@
2828
class SpannerMetadataProvider {
2929
private final Map<Metadata.Key<String>, String> headers;
3030
private final String resourceHeaderKey;
31-
private static final String routeToLeaderHeaderKey = "x-goog-spanner-route-to-leader";
31+
private static final String ROUTE_TO_LEADER_HEADER_KEY = "x-goog-spanner-route-to-leader";
3232
private static final Pattern[] RESOURCE_TOKEN_PATTERNS = {
3333
Pattern.compile("^(?<headerValue>projects/[^/]*/instances/[^/]*/databases/[^/]*)(.*)?"),
3434
Pattern.compile("^(?<headerValue>projects/[^/]*/instances/[^/]*)(.*)?")
3535
};
3636

37+
private static final Map<String, List<String>> ROUTE_TO_LEADER_HEADER_MAP =
38+
ImmutableMap.of(ROUTE_TO_LEADER_HEADER_KEY, Collections.singletonList("true"));
39+
3740
private SpannerMetadataProvider(Map<String, String> headers, String resourceHeaderKey) {
3841
this.resourceHeaderKey = resourceHeaderKey;
3942
this.headers = constructHeadersAsMetadata(headers);
@@ -67,9 +70,7 @@ Map<String, List<String>> newExtraHeaders(
6770
}
6871

6972
Map<String, List<String>> newRouteToLeaderHeader() {
70-
return ImmutableMap.<String, List<String>>builder()
71-
.put(routeToLeaderHeaderKey, Collections.singletonList("true"))
72-
.build();
73+
return ROUTE_TO_LEADER_HEADER_MAP;
7374
}
7475

7576
private Map<Metadata.Key<String>, String> constructHeadersAsMetadata(

google-cloud-spanner/src/test/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpcTest.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
import static com.google.common.truth.Truth.assertThat;
2020
import static org.hamcrest.MatcherAssert.assertThat;
2121
import static org.junit.Assert.assertEquals;
22+
import static org.junit.Assert.assertFalse;
2223
import static org.junit.Assert.assertNotNull;
2324
import static org.junit.Assert.assertNull;
2425
import static org.junit.Assert.assertThrows;
26+
import static org.junit.Assert.assertTrue;
2527
import static org.junit.Assume.assumeTrue;
2628

2729
import com.google.api.gax.core.GaxProperties;
@@ -45,6 +47,7 @@
4547
import com.google.cloud.spanner.SpannerOptions;
4648
import com.google.cloud.spanner.SpannerOptions.CallContextConfigurator;
4749
import com.google.cloud.spanner.Statement;
50+
import com.google.cloud.spanner.TransactionRunner;
4851
import com.google.cloud.spanner.spi.v1.GapicSpannerRpc.AdminRequestsLimitExceededRetryAlgorithm;
4952
import com.google.cloud.spanner.spi.v1.SpannerRpc.Option;
5053
import com.google.common.collect.ImmutableList;
@@ -140,6 +143,7 @@ public class GapicSpannerRpcTest {
140143
private static Metadata lastSeenHeaders;
141144
private static String defaultUserAgent;
142145
private static Spanner spanner;
146+
private static boolean isRouteToLeader;
143147

144148
@Parameter public Dialect dialect;
145149

@@ -177,6 +181,17 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
177181
String auth =
178182
headers.get(Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER));
179183
assertThat(auth).isEqualTo("Bearer " + VARIABLE_OAUTH_TOKEN);
184+
if (call.getMethodDescriptor()
185+
.equals(SpannerGrpc.getExecuteStreamingSqlMethod())
186+
|| call.getMethodDescriptor().equals(SpannerGrpc.getExecuteSqlMethod())) {
187+
String routeToLeaderHeader =
188+
headers.get(
189+
Key.of(
190+
"x-goog-spanner-route-to-leader",
191+
Metadata.ASCII_STRING_MARSHALLER));
192+
isRouteToLeader =
193+
(routeToLeaderHeader != null && routeToLeaderHeader.equals("true"));
194+
}
180195
return Contexts.interceptCall(Context.current(), call, headers, next);
181196
}
182197
})
@@ -198,6 +213,7 @@ public void reset() throws InterruptedException {
198213
server.shutdown();
199214
server.awaitTermination();
200215
}
216+
isRouteToLeader = false;
201217
}
202218

203219
@Test
@@ -508,6 +524,54 @@ public void testCustomUserAgent() {
508524
}
509525
}
510526

527+
@Test
528+
public void testRouteToLeaderHeaderForReadOnly() {
529+
final SpannerOptions options = createSpannerOptions();
530+
try (Spanner spanner = options.getService()) {
531+
final DatabaseClient databaseClient =
532+
spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]"));
533+
534+
try (final ResultSet rs = databaseClient.singleUse().executeQuery(SELECT1AND2)) {
535+
rs.next();
536+
}
537+
538+
assertFalse(isRouteToLeader);
539+
}
540+
}
541+
542+
@Test
543+
public void testRouteToLeaderHeaderForReadWrite() {
544+
final SpannerOptions options = createSpannerOptions();
545+
try (Spanner spanner = options.getService()) {
546+
final DatabaseClient databaseClient =
547+
spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]"));
548+
TransactionRunner runner = databaseClient.readWriteTransaction();
549+
runner.run(
550+
transaction -> {
551+
transaction.executeUpdate(UPDATE_FOO_STATEMENT);
552+
return null;
553+
});
554+
}
555+
assertTrue(isRouteToLeader);
556+
}
557+
558+
@Test
559+
public void testRouteToLeaderHeaderWithLeaderAwareRoutingDisabled() {
560+
final SpannerOptions options =
561+
createSpannerOptions().toBuilder().disableLeaderAwareRouting().build();
562+
try (Spanner spanner = options.getService()) {
563+
final DatabaseClient databaseClient =
564+
spanner.getDatabaseClient(DatabaseId.of("[PROJECT]", "[INSTANCE]", "[DATABASE]"));
565+
TransactionRunner runner = databaseClient.readWriteTransaction();
566+
runner.run(
567+
transaction -> {
568+
transaction.executeUpdate(UPDATE_FOO_STATEMENT);
569+
return null;
570+
});
571+
}
572+
assertFalse(isRouteToLeader);
573+
}
574+
511575
private SpannerOptions createSpannerOptions() {
512576
String endpoint = address.getHostString() + ":" + server.getPort();
513577
return SpannerOptions.newBuilder()

0 commit comments

Comments
 (0)