Skip to content

Commit 6d18b78

Browse files
committed
chore(x-goog-spanner-request-id): setup expectations for end-to-end request-id checks
This change allows us to perform future end-to-end checks to assert on the behaviour of x-goog-spanner-request-id header values. Updates #3537
1 parent 1ae84a0 commit 6d18b78

File tree

6 files changed

+233
-28
lines changed

6 files changed

+233
-28
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class DatabaseClientImpl implements DatabaseClient {
4848
@VisibleForTesting final MultiplexedSessionDatabaseClient multiplexedSessionDatabaseClient;
4949
@VisibleForTesting final boolean useMultiplexedSessionPartitionedOps;
5050
@VisibleForTesting final boolean useMultiplexedSessionForRW;
51-
private final int dbId;
51+
@VisibleForTesting final int dbId;
5252
private final AtomicInteger nthRequest;
5353
private final Map<String, Integer> clientIdToOrdinalMap;
5454

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,9 +1098,6 @@ public boolean equals(Object o) {
10981098
return false;
10991099
}
11001100
RequestIdOption other = (RequestIdOption) o;
1101-
if (this.reqId == null || other.reqId == null) {
1102-
return this.reqId == null && other.reqId == null;
1103-
}
11041101
return Objects.equals(this.reqId, other.reqId);
11051102
}
11061103
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ DatabaseId getDatabaseId() {
223223
@Override
224224
public XGoogSpannerRequestId nextRequestId(long channelId, int attempt) {
225225
return XGoogSpannerRequestId.of(
226-
this.nthId, this.nthRequest.incrementAndGet(), channelId, attempt);
226+
this.nthId, channelId, this.nthRequest.incrementAndGet(), attempt);
227227
}
228228

229229
/** Create a single session. */
@@ -423,7 +423,7 @@ private List<SessionImpl> internalBatchCreateSessions(
423423
span.addAnnotation(String.format("Requesting %d sessions", sessionCount));
424424
try (IScope s = spanner.getTracer().withSpan(span)) {
425425
XGoogSpannerRequestId reqId =
426-
XGoogSpannerRequestId.of(this.nthId, this.nthRequest.incrementAndGet(), channelHint, 1);
426+
XGoogSpannerRequestId.of(this.nthId, channelHint, this.nthRequest.incrementAndGet(), 1);
427427
List<com.google.spanner.v1.Session> sessions =
428428
spanner
429429
.getRpc()

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2042,6 +2042,7 @@ <ReqT, RespT> GrpcCallContext newCallContext(
20422042
}
20432043
}
20442044
if (options != null) {
2045+
// TODO(@odeke-em): Infer the affinity if it doesn't match up with in the request-id.
20452046
context = withRequestId(context, options);
20462047
}
20472048
context = context.withExtraHeaders(metadataProvider.newExtraHeaders(resource, projectName));

google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2907,6 +2907,45 @@ public void testPartitionedDmlDoesNotTimeout() {
29072907
return null;
29082908
}));
29092909
assertEquals(ErrorCode.DEADLINE_EXCEEDED, e.getErrorCode());
2910+
2911+
DatabaseClientImpl dbImpl = ((DatabaseClientImpl) client);
2912+
int channelId = 0;
2913+
try (Session session = dbImpl.getSession()) {
2914+
channelId = ((PooledSessionFuture) session).getChannel();
2915+
}
2916+
int dbId = dbImpl.dbId;
2917+
long NON_DETERMINISTIC = XGoogSpannerRequestIdTest.NON_DETERMINISTIC;
2918+
XGoogSpannerRequestIdTest.MethodAndRequestId[] wantStreamingValues = {
2919+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
2920+
"google.spanner.v1.Spanner/ExecuteStreamingSql",
2921+
new XGoogSpannerRequestId(NON_DETERMINISTIC, channelId, 6, 1)),
2922+
};
2923+
xGoogReqIdInterceptor.checkExpectedStreamingXGoogRequestIds(wantStreamingValues);
2924+
2925+
XGoogSpannerRequestIdTest.MethodAndRequestId[] wantUnaryValues = {
2926+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
2927+
"google.spanner.v1.Spanner/BatchCreateSessions",
2928+
new XGoogSpannerRequestId(NON_DETERMINISTIC, NON_DETERMINISTIC, NON_DETERMINISTIC, 1)),
2929+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
2930+
"google.spanner.v1.Spanner/BatchCreateSessions",
2931+
new XGoogSpannerRequestId(NON_DETERMINISTIC, NON_DETERMINISTIC, NON_DETERMINISTIC, 1)),
2932+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
2933+
"google.spanner.v1.Spanner/BatchCreateSessions",
2934+
new XGoogSpannerRequestId(NON_DETERMINISTIC, NON_DETERMINISTIC, NON_DETERMINISTIC, 1)),
2935+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
2936+
"google.spanner.v1.Spanner/BatchCreateSessions",
2937+
new XGoogSpannerRequestId(NON_DETERMINISTIC, NON_DETERMINISTIC, NON_DETERMINISTIC, 1)),
2938+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
2939+
"google.spanner.v1.Spanner/BeginTransaction",
2940+
new XGoogSpannerRequestId(NON_DETERMINISTIC, channelId, 7, 1)),
2941+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
2942+
"google.spanner.v1.Spanner/CreateSession",
2943+
new XGoogSpannerRequestId(NON_DETERMINISTIC, 0, 1, 1)),
2944+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
2945+
"google.spanner.v1.Spanner/ExecuteSql",
2946+
new XGoogSpannerRequestId(NON_DETERMINISTIC, channelId, 8, 1)),
2947+
};
2948+
xGoogReqIdInterceptor.checkExpectedUnaryXGoogRequestIds(wantUnaryValues);
29102949
}
29112950
}
29122951

@@ -2989,6 +3028,46 @@ public void testPartitionedDmlWithHigherTimeout() {
29893028
.run(transaction -> transaction.executeUpdate(UPDATE_STATEMENT)));
29903029
assertThat(e.getErrorCode()).isEqualTo(ErrorCode.DEADLINE_EXCEEDED);
29913030
assertThat(updateCount).isEqualTo(UPDATE_COUNT);
3031+
3032+
DatabaseClientImpl dbImpl = ((DatabaseClientImpl) client);
3033+
int channelId = 0;
3034+
try (Session session = dbImpl.getSession()) {
3035+
channelId = ((PooledSessionFuture) session).getChannel();
3036+
}
3037+
int dbId = dbImpl.dbId;
3038+
long NON_DETERMINISTIC = XGoogSpannerRequestIdTest.NON_DETERMINISTIC;
3039+
XGoogSpannerRequestIdTest.MethodAndRequestId[] wantStreamingValues = {
3040+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
3041+
"google.spanner.v1.Spanner/ExecuteStreamingSql",
3042+
new XGoogSpannerRequestId(NON_DETERMINISTIC, channelId, 6, 1)),
3043+
};
3044+
3045+
xGoogReqIdInterceptor.checkExpectedStreamingXGoogRequestIds(wantStreamingValues);
3046+
3047+
XGoogSpannerRequestIdTest.MethodAndRequestId[] wantUnaryValues = {
3048+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
3049+
"google.spanner.v1.Spanner/BatchCreateSessions",
3050+
new XGoogSpannerRequestId(NON_DETERMINISTIC, NON_DETERMINISTIC, NON_DETERMINISTIC, 1)),
3051+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
3052+
"google.spanner.v1.Spanner/BatchCreateSessions",
3053+
new XGoogSpannerRequestId(NON_DETERMINISTIC, NON_DETERMINISTIC, NON_DETERMINISTIC, 1)),
3054+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
3055+
"google.spanner.v1.Spanner/BatchCreateSessions",
3056+
new XGoogSpannerRequestId(NON_DETERMINISTIC, NON_DETERMINISTIC, NON_DETERMINISTIC, 1)),
3057+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
3058+
"google.spanner.v1.Spanner/BatchCreateSessions",
3059+
new XGoogSpannerRequestId(NON_DETERMINISTIC, NON_DETERMINISTIC, NON_DETERMINISTIC, 1)),
3060+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
3061+
"google.spanner.v1.Spanner/BeginTransaction",
3062+
new XGoogSpannerRequestId(NON_DETERMINISTIC, channelId, 7, 1)),
3063+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
3064+
"google.spanner.v1.Spanner/CreateSession",
3065+
new XGoogSpannerRequestId(NON_DETERMINISTIC, 0, 1, 1)),
3066+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
3067+
"google.spanner.v1.Spanner/ExecuteSql",
3068+
new XGoogSpannerRequestId(NON_DETERMINISTIC, channelId, 8, 1)),
3069+
};
3070+
xGoogReqIdInterceptor.checkExpectedUnaryXGoogRequestIds(wantUnaryValues);
29923071
}
29933072
}
29943073

@@ -5314,6 +5393,30 @@ public void testSessionPoolExhaustedError_containsStackTraces() {
53145393
}
53155394
// Closing the transactions should return the sessions to the pool.
53165395
assertEquals(4, pool.getNumberOfSessionsInPool());
5396+
5397+
DatabaseClientImpl dbClient = (DatabaseClientImpl) client;
5398+
int channelId = 0;
5399+
try (Session session = dbClient.getSession()) {
5400+
channelId = ((PooledSessionFuture) session).getChannel();
5401+
}
5402+
int dbId = dbClient.dbId;
5403+
XGoogSpannerRequestIdTest.MethodAndRequestId[] wantStreamingValues = {};
5404+
5405+
xGoogReqIdInterceptor.checkExpectedStreamingXGoogRequestIds(wantStreamingValues);
5406+
long NON_DETERMINISTIC = XGoogSpannerRequestIdTest.NON_DETERMINISTIC;
5407+
5408+
XGoogSpannerRequestIdTest.MethodAndRequestId[] wantUnaryValues = {
5409+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
5410+
"google.spanner.v1.Spanner/BatchCreateSessions",
5411+
new XGoogSpannerRequestId(NON_DETERMINISTIC, NON_DETERMINISTIC, NON_DETERMINISTIC, 1)),
5412+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
5413+
"google.spanner.v1.Spanner/BatchCreateSessions",
5414+
new XGoogSpannerRequestId(NON_DETERMINISTIC, NON_DETERMINISTIC, NON_DETERMINISTIC, 1)),
5415+
XGoogSpannerRequestIdTest.ofMethodAndRequestId(
5416+
"google.spanner.v1.Spanner/CreateSession",
5417+
new XGoogSpannerRequestId(NON_DETERMINISTIC, 0, 1, 1)),
5418+
};
5419+
xGoogReqIdInterceptor.checkExpectedUnaryXGoogRequestIds(wantUnaryValues);
53175420
}
53185421
}
53195422

google-cloud-spanner/src/test/java/com/google/cloud/spanner/XGoogSpannerRequestIdTest.java

Lines changed: 126 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import io.grpc.ServerInterceptor;
2929
import io.grpc.Status;
3030
import java.util.ArrayList;
31+
import java.util.Arrays;
32+
import java.util.Comparator;
3133
import java.util.List;
3234
import java.util.Map;
3335
import java.util.Objects;
@@ -41,6 +43,7 @@
4143

4244
@RunWith(JUnit4.class)
4345
public class XGoogSpannerRequestIdTest {
46+
public static long NON_DETERMINISTIC = -1;
4447

4548
@Test
4649
public void testEquals() {
@@ -157,40 +160,65 @@ private void assertMonotonicityOfIds(String prefix, List<XGoogSpannerRequestId>
157160
+ String.join("\n\t", violations.toArray(new String[0])));
158161
}
159162

160-
public static class methodAndRequestId {
161-
String method;
162-
String requestId;
163-
164-
public methodAndRequestId(String method, String requestId) {
165-
this.method = method;
166-
this.requestId = requestId;
167-
}
168-
169-
public String toString() {
170-
return "{" + this.method + ":" + this.requestId + "}";
171-
}
172-
}
173-
174-
public methodAndRequestId[] accumulatedUnaryValues() {
175-
List<methodAndRequestId> accumulated = new ArrayList();
163+
public MethodAndRequestId[] accumulatedUnaryValues() {
164+
List<MethodAndRequestId> accumulated = new ArrayList();
176165
this.unaryResults.forEach(
177166
(String method, CopyOnWriteArrayList<XGoogSpannerRequestId> values) -> {
178167
for (int i = 0; i < values.size(); i++) {
179-
accumulated.add(new methodAndRequestId(method, values.get(i).toString()));
168+
accumulated.add(new MethodAndRequestId(method, values.get(i)));
180169
}
181170
});
182-
return accumulated.toArray(new methodAndRequestId[0]);
171+
return accumulated.toArray(new MethodAndRequestId[0]);
183172
}
184173

185-
public methodAndRequestId[] accumulatedStreamingValues() {
186-
List<methodAndRequestId> accumulated = new ArrayList();
174+
public MethodAndRequestId[] accumulatedStreamingValues() {
175+
List<MethodAndRequestId> accumulated = new ArrayList();
187176
this.streamingResults.forEach(
188177
(String method, CopyOnWriteArrayList<XGoogSpannerRequestId> values) -> {
189178
for (int i = 0; i < values.size(); i++) {
190-
accumulated.add(new methodAndRequestId(method, values.get(i).toString()));
179+
accumulated.add(new MethodAndRequestId(method, values.get(i)));
191180
}
192181
});
193-
return accumulated.toArray(new methodAndRequestId[0]);
182+
return accumulated.toArray(new MethodAndRequestId[0]);
183+
}
184+
185+
public void checkExpectedUnaryXGoogRequestIds(MethodAndRequestId... wantUnaryValues) {
186+
MethodAndRequestId[] gotUnaryValues = this.accumulatedUnaryValues();
187+
sortValues(gotUnaryValues);
188+
for (int i = 0; i < gotUnaryValues.length && false; i++) {
189+
System.out.println("\033[33misUnary: #" + i + ":: " + gotUnaryValues[i] + "\033[00m");
190+
}
191+
assertEquals(wantUnaryValues, gotUnaryValues);
192+
}
193+
194+
public void checkAtLeastHasExpectedUnaryXGoogRequestIds(MethodAndRequestId... wantUnaryValues) {
195+
MethodAndRequestId[] gotUnaryValues = this.accumulatedUnaryValues();
196+
sortValues(gotUnaryValues);
197+
for (int i = 0; i < gotUnaryValues.length && false; i++) {
198+
System.out.println("\033[33misUnary: #" + i + ":: " + gotUnaryValues[i] + "\033[00m");
199+
}
200+
if (wantUnaryValues.length < gotUnaryValues.length) {
201+
MethodAndRequestId[] gotSliced =
202+
Arrays.copyOfRange(gotUnaryValues, 0, wantUnaryValues.length);
203+
assertEquals(wantUnaryValues, gotSliced);
204+
} else {
205+
assertEquals(wantUnaryValues, gotUnaryValues);
206+
}
207+
}
208+
209+
private void sortValues(MethodAndRequestId[] values) {
210+
massageValues(values);
211+
Arrays.sort(values, new MethodAndRequestIdComparator());
212+
}
213+
214+
public void checkExpectedStreamingXGoogRequestIds(MethodAndRequestId... wantStreamingValues) {
215+
MethodAndRequestId[] gotStreamingValues = this.accumulatedStreamingValues();
216+
for (int i = 0; i < gotStreamingValues.length && false; i++) {
217+
System.out.println(
218+
"\033[32misStreaming: #" + i + ":: " + gotStreamingValues[i] + "\033[00m");
219+
}
220+
sortValues(gotStreamingValues);
221+
assertEquals(wantStreamingValues, gotStreamingValues);
194222
}
195223

196224
public void reset() {
@@ -199,4 +227,80 @@ public void reset() {
199227
this.streamingResults.clear();
200228
}
201229
}
230+
231+
public static class MethodAndRequestId {
232+
String method;
233+
XGoogSpannerRequestId requestId;
234+
235+
public MethodAndRequestId(String method, XGoogSpannerRequestId requestId) {
236+
this.method = method;
237+
this.requestId = requestId;
238+
}
239+
240+
public String toString() {
241+
return "{" + this.method + ":" + this.requestId.debugToString() + "}";
242+
}
243+
244+
@Override
245+
public boolean equals(Object o) {
246+
if (!(o instanceof MethodAndRequestId)) {
247+
return false;
248+
}
249+
MethodAndRequestId other = (MethodAndRequestId) o;
250+
return Objects.equals(this.method, other.method)
251+
&& Objects.equals(this.requestId, other.requestId);
252+
}
253+
}
254+
255+
static class MethodAndRequestIdComparator implements Comparator<MethodAndRequestId> {
256+
@Override
257+
public int compare(MethodAndRequestId mr1, MethodAndRequestId mr2) {
258+
int cmpMethod = mr1.method.compareTo(mr2.method);
259+
if (cmpMethod != 0) {
260+
return cmpMethod;
261+
}
262+
263+
if (Objects.equals(mr1.requestId, mr2.requestId)) {
264+
return 0;
265+
}
266+
if (mr1.requestId.isGreaterThan(mr2.requestId)) {
267+
return +1;
268+
}
269+
return -1;
270+
}
271+
}
272+
273+
static void massageValues(MethodAndRequestId[] mreqs) {
274+
for (int i = 0; i < mreqs.length; i++) {
275+
MethodAndRequestId mreq = mreqs[i];
276+
// BatchCreateSessions is so hard to control as the round-robin doling out
277+
// hence we might need to be able to scrub the nth_request that won't match
278+
// nth_req in consecutive order of nth_client.
279+
if (mreq.method.compareTo("google.spanner.v1.Spanner/BatchCreateSessions") == 0) {
280+
mreqs[i] =
281+
new MethodAndRequestId(
282+
mreq.method,
283+
mreq.requestId
284+
.withNthRequest(NON_DETERMINISTIC)
285+
.withChannelId(NON_DETERMINISTIC)
286+
.withNthClientId(NON_DETERMINISTIC));
287+
} else if (mreq.method.compareTo("google.spanner.v1.Spanner/BeginTransaction") == 0
288+
|| mreq.method.compareTo("google.spanner.v1.Spanner/ExecuteStreamingSql") == 0
289+
|| mreq.method.compareTo("google.spanner.v1.Spanner/ExecuteSql") == 0
290+
|| mreq.method.compareTo("google.spanner.v1.Spanner/CreateSession") == 0
291+
|| mreq.method.compareTo("google.spanner.v1.Spanner/Commit") == 0) {
292+
mreqs[i] =
293+
new MethodAndRequestId(mreq.method, mreq.requestId.withNthClientId(NON_DETERMINISTIC));
294+
}
295+
}
296+
}
297+
298+
public static MethodAndRequestId ofMethodAndRequestId(String method, String reqId) {
299+
return new MethodAndRequestId(method, XGoogSpannerRequestId.of(reqId));
300+
}
301+
302+
public static MethodAndRequestId ofMethodAndRequestId(
303+
String method, XGoogSpannerRequestId reqId) {
304+
return new MethodAndRequestId(method, reqId);
305+
}
202306
}

0 commit comments

Comments
 (0)