Skip to content

Commit 850435b

Browse files
build: remove native image tests from required checks (googleapis#2584)
* test: backoff and retry for RESOURCE_EXHAUSTED when creating test instance Do a backoff and retry if CreateInstance fails during integration tests. * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * fix: correctly unwrap SpannerException * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * test: back off more and retry more * fix: prevent -1 as sleep value * build: reuse test instance * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * build: try to get native build to pick up instance * build: remove native image tests from required checks * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * test: ignore creation error * test: use us-central config * fix: fall back to first config for emulator --------- Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 08af671 commit 850435b

File tree

6 files changed

+129
-41
lines changed

6 files changed

+129
-41
lines changed

.github/sync-repo-settings.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ branchProtectionRules:
1919
- compile (8)
2020
- compile (11)
2121
- OwlBot Post Processor
22-
- 'Kokoro - Test: Java GraalVM Native Image'
23-
- 'Kokoro - Test: Java 17 GraalVM Native Image'
2422
- pattern: 3.3.x
2523
isAdminEnforced: true
2624
requiredApprovingReviewCount: 1

.kokoro/build.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ integration)
8888
-Dclirr.skip=true \
8989
-Denforcer.skip=true \
9090
-Dmaven.main.skip=true \
91+
-Dspanner.gce.config.project_id=gcloud-devel \
92+
-Dspanner.testenv.instance=projects/gcloud-devel/instances/java-client-integration-tests \
9193
-fae \
9294
verify
9395
RETURN_CODE=$?
@@ -126,12 +128,12 @@ integration-cloud-staging)
126128
;;
127129
graalvm)
128130
# Run Unit and Integration Tests with Native Image
129-
mvn test -Pnative -Penable-integration-tests
131+
mvn test -Pnative -Penable-integration-tests -Dspanner.gce.config.project_id=gcloud-devel -Dspanner.testenv.instance=projects/gcloud-devel/instances/java-client-integration-tests
130132
RETURN_CODE=$?
131133
;;
132134
graalvm17)
133135
# Run Unit and Integration Tests with Native Image
134-
mvn test -Pnative -Penable-integration-tests
136+
mvn test -Pnative -Penable-integration-tests -Dspanner.gce.config.project_id=gcloud-devel -Dspanner.testenv.instance=projects/gcloud-devel/instances/java-client-integration-tests
135137
RETURN_CODE=$?
136138
;;
137139
slowtests)

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,20 @@ If you are using Maven without the BOM, add this to your dependencies:
5050
If you are using Gradle 5.x or later, add this to your dependencies:
5151

5252
```Groovy
53-
implementation platform('com.google.cloud:libraries-bom:26.21.0')
53+
implementation platform('com.google.cloud:libraries-bom:26.22.0')
5454
5555
implementation 'com.google.cloud:google-cloud-spanner'
5656
```
5757
If you are using Gradle without BOM, add this to your dependencies:
5858

5959
```Groovy
60-
implementation 'com.google.cloud:google-cloud-spanner:6.44.0'
60+
implementation 'com.google.cloud:google-cloud-spanner:6.45.0'
6161
```
6262

6363
If you are using SBT, add this to your dependencies:
6464

6565
```Scala
66-
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.44.0"
66+
libraryDependencies += "com.google.cloud" % "google-cloud-spanner" % "6.45.0"
6767
```
6868
<!-- {x-version-update-end} -->
6969

@@ -430,7 +430,7 @@ Java is a registered trademark of Oracle and/or its affiliates.
430430
[kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-spanner/java11.html
431431
[stability-image]: https://img.shields.io/badge/stability-stable-green
432432
[maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-spanner.svg
433-
[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.44.0
433+
[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-spanner/6.45.0
434434
[authentication]: https://github.com/googleapis/google-cloud-java#authentication
435435
[auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes
436436
[predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles

google-cloud-spanner/pom.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@
122122
<configuration>
123123
<buildArgs combine.children="append">
124124
<buildArg>-Dspanner.testenv.config.class=${spanner.testenv.config.class}</buildArg>
125+
<buildArg>-Dspanner.testenv.instance=${spanner.testenv.instance}</buildArg>
126+
<buildArg>-Dspanner.gce.config.project_id=${spanner.gce.config.project_id}</buildArg>
127+
<buildArg>-Dspanner.testenv.kms_key.name=${spanner.testenv.kms_key.name}</buildArg>
125128
</buildArgs>
126129
</configuration>
127130
</plugin>

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

Lines changed: 64 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,16 @@
1818

1919
import static com.google.common.base.Preconditions.checkState;
2020

21+
import com.google.api.client.util.ExponentialBackOff;
2122
import com.google.api.gax.longrunning.OperationFuture;
2223
import com.google.api.gax.paging.Page;
2324
import com.google.cloud.Timestamp;
2425
import com.google.cloud.spanner.testing.EmulatorSpannerHelper;
2526
import com.google.cloud.spanner.testing.RemoteSpannerHelper;
2627
import com.google.common.collect.Iterators;
2728
import com.google.spanner.admin.instance.v1.CreateInstanceMetadata;
28-
import io.grpc.Status;
2929
import java.util.Random;
30+
import java.util.concurrent.ExecutionException;
3031
import java.util.concurrent.TimeUnit;
3132
import java.util.logging.Level;
3233
import java.util.logging.Logger;
@@ -52,6 +53,9 @@ public class IntegrationTestEnv extends ExternalResource {
5253
*/
5354
public static final String TEST_INSTANCE_PROPERTY = "spanner.testenv.instance";
5455

56+
public static final String MAX_CREATE_INSTANCE_ATTEMPTS =
57+
"spanner.testenv.max_create_instance_attempts";
58+
5559
private static final Logger logger = Logger.getLogger(IntegrationTestEnv.class.getName());
5660

5761
private TestEnvConfig config;
@@ -126,9 +130,14 @@ protected void after() {
126130
this.config.tearDown();
127131
}
128132

129-
private void initializeInstance(InstanceId instanceId) {
130-
InstanceConfig instanceConfig =
131-
Iterators.get(instanceAdminClient.listInstanceConfigs().iterateAll().iterator(), 0, null);
133+
private void initializeInstance(InstanceId instanceId) throws Exception {
134+
InstanceConfig instanceConfig;
135+
try {
136+
instanceConfig = instanceAdminClient.getInstanceConfig("regional-us-central1");
137+
} catch (Throwable ignore) {
138+
instanceConfig =
139+
Iterators.get(instanceAdminClient.listInstanceConfigs().iterateAll().iterator(), 0, null);
140+
}
132141
checkState(instanceConfig != null, "No instance configs found");
133142

134143
InstanceConfigId configId = instanceConfig.getId();
@@ -142,40 +151,62 @@ private void initializeInstance(InstanceId instanceId) {
142151
OperationFuture<Instance, CreateInstanceMetadata> op =
143152
instanceAdminClient.createInstance(instance);
144153
Instance createdInstance;
154+
int maxAttempts = 25;
145155
try {
146-
createdInstance = op.get();
147-
} catch (Exception e) {
148-
boolean cancelled = false;
156+
maxAttempts =
157+
Integer.parseInt(
158+
System.getProperty(MAX_CREATE_INSTANCE_ATTEMPTS, String.valueOf(maxAttempts)));
159+
} catch (NumberFormatException ignore) {
160+
// Ignore and fall back to the default.
161+
}
162+
ExponentialBackOff backOff =
163+
new ExponentialBackOff.Builder()
164+
.setInitialIntervalMillis(5_000)
165+
.setMaxIntervalMillis(500_000)
166+
.setMultiplier(2.0)
167+
.build();
168+
int attempts = 0;
169+
while (true) {
149170
try {
150-
// Try to cancel the createInstance operation.
151-
instanceAdminClient.cancelOperation(op.getName());
152-
com.google.longrunning.Operation createOperation =
153-
instanceAdminClient.getOperation(op.getName());
154-
cancelled =
155-
createOperation.hasError()
156-
&& createOperation.getError().getCode() == Status.CANCELLED.getCode().value();
157-
if (cancelled) {
158-
logger.info("Cancelled the createInstance operation because the operation failed");
159-
} else {
160-
logger.info(
161-
"Tried to cancel the createInstance operation because the operation failed, but the operation could not be cancelled. Current status: "
162-
+ createOperation.getError().getCode());
163-
}
164-
} catch (Throwable t) {
165-
logger.log(Level.WARNING, "Failed to cancel the createInstance operation", t);
166-
}
167-
if (!cancelled) {
168-
try {
169-
instanceAdminClient.deleteInstance(instanceId.getInstance());
170-
logger.info(
171-
"Deleted the test instance because the createInstance operation failed and cancelling the operation did not succeed");
172-
} catch (Throwable t) {
173-
logger.log(Level.WARNING, "Failed to delete the test instance", t);
171+
createdInstance = op.get();
172+
} catch (Exception e) {
173+
SpannerException spannerException =
174+
(e instanceof ExecutionException && e.getCause() != null)
175+
? SpannerExceptionFactory.asSpannerException(e.getCause())
176+
: SpannerExceptionFactory.asSpannerException(e);
177+
if (attempts < maxAttempts && isRetryableResourceExhaustedException(spannerException)) {
178+
attempts++;
179+
if (spannerException.getRetryDelayInMillis() > 0L) {
180+
//noinspection BusyWait
181+
Thread.sleep(spannerException.getRetryDelayInMillis());
182+
} else {
183+
// The Math.max(...) prevents Backoff#STOP (=-1) to be used as the sleep value.
184+
//noinspection BusyWait
185+
Thread.sleep(Math.max(backOff.getMaxIntervalMillis(), backOff.nextBackOffMillis()));
186+
}
187+
continue;
174188
}
189+
throw SpannerExceptionFactory.newSpannerException(
190+
spannerException.getErrorCode(),
191+
String.format(
192+
"Could not create test instance and giving up after %d attempts: %s",
193+
attempts, e.getMessage()),
194+
e);
175195
}
176-
throw SpannerExceptionFactory.newSpannerException(e);
196+
logger.log(Level.INFO, "Created test instance: {0}", createdInstance.getId());
197+
break;
198+
}
199+
}
200+
201+
static boolean isRetryableResourceExhaustedException(SpannerException exception) {
202+
if (exception.getErrorCode() != ErrorCode.RESOURCE_EXHAUSTED) {
203+
return false;
177204
}
178-
logger.log(Level.INFO, "Created test instance: {0}", createdInstance.getId());
205+
return exception
206+
.getMessage()
207+
.contains(
208+
"Quota exceeded for quota metric 'Instance create requests' and limit 'Instance create requests per minute'")
209+
|| exception.getMessage().matches(".*cannot add \\d+ nodes in region.*");
179210
}
180211

181212
private void cleanUpOldDatabases(InstanceId instanceId) {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.spanner;
18+
19+
import static com.google.cloud.spanner.IntegrationTestEnv.isRetryableResourceExhaustedException;
20+
import static org.junit.Assert.assertFalse;
21+
import static org.junit.Assert.assertTrue;
22+
23+
import org.junit.Test;
24+
import org.junit.runner.RunWith;
25+
import org.junit.runners.JUnit4;
26+
27+
@RunWith(JUnit4.class)
28+
public class IntegrationTestEnvTest {
29+
30+
@Test
31+
public void testIsRetryableResourceExhaustedException() {
32+
assertFalse(
33+
isRetryableResourceExhaustedException(
34+
SpannerExceptionFactory.newSpannerException(ErrorCode.INVALID_ARGUMENT, "test")));
35+
assertFalse(
36+
isRetryableResourceExhaustedException(
37+
SpannerExceptionFactory.newSpannerException(ErrorCode.RESOURCE_EXHAUSTED, "test")));
38+
assertTrue(
39+
isRetryableResourceExhaustedException(
40+
SpannerExceptionFactory.newSpannerException(
41+
ErrorCode.RESOURCE_EXHAUSTED,
42+
"Operation with name \"projects/my-project/instances/my-instance/operations/32bb3dccf4243afc\" failed with status = GrpcStatusCode{transportCode=RESOURCE_EXHAUSTED} and message = Project 123 cannot add 1 nodes in region .")));
43+
assertTrue(
44+
isRetryableResourceExhaustedException(
45+
SpannerExceptionFactory.newSpannerException(
46+
ErrorCode.RESOURCE_EXHAUSTED,
47+
"Operation with name \"projects/my-project/instances/my-instance/operations/32bb3dccf4243afc\" failed with status = GrpcStatusCode{transportCode=RESOURCE_EXHAUSTED} and message = Project 123 cannot add 99 nodes in region .")));
48+
assertTrue(
49+
isRetryableResourceExhaustedException(
50+
SpannerExceptionFactory.newSpannerException(
51+
ErrorCode.RESOURCE_EXHAUSTED,
52+
"Could not create instance. Quota exceeded for quota metric 'Instance create requests' and limit 'Instance create requests per minute'")));
53+
}
54+
}

0 commit comments

Comments
 (0)