18
18
19
19
import static com .google .common .base .Preconditions .checkState ;
20
20
21
+ import com .google .api .client .util .ExponentialBackOff ;
21
22
import com .google .api .gax .longrunning .OperationFuture ;
22
23
import com .google .api .gax .paging .Page ;
23
24
import com .google .cloud .Timestamp ;
24
25
import com .google .cloud .spanner .testing .EmulatorSpannerHelper ;
25
26
import com .google .cloud .spanner .testing .RemoteSpannerHelper ;
26
27
import com .google .common .collect .Iterators ;
27
28
import com .google .spanner .admin .instance .v1 .CreateInstanceMetadata ;
28
- import io .grpc .Status ;
29
29
import java .util .Random ;
30
+ import java .util .concurrent .ExecutionException ;
30
31
import java .util .concurrent .TimeUnit ;
31
32
import java .util .logging .Level ;
32
33
import java .util .logging .Logger ;
@@ -52,6 +53,9 @@ public class IntegrationTestEnv extends ExternalResource {
52
53
*/
53
54
public static final String TEST_INSTANCE_PROPERTY = "spanner.testenv.instance" ;
54
55
56
+ public static final String MAX_CREATE_INSTANCE_ATTEMPTS =
57
+ "spanner.testenv.max_create_instance_attempts" ;
58
+
55
59
private static final Logger logger = Logger .getLogger (IntegrationTestEnv .class .getName ());
56
60
57
61
private TestEnvConfig config ;
@@ -126,9 +130,14 @@ protected void after() {
126
130
this .config .tearDown ();
127
131
}
128
132
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
+ }
132
141
checkState (instanceConfig != null , "No instance configs found" );
133
142
134
143
InstanceConfigId configId = instanceConfig .getId ();
@@ -142,40 +151,62 @@ private void initializeInstance(InstanceId instanceId) {
142
151
OperationFuture <Instance , CreateInstanceMetadata > op =
143
152
instanceAdminClient .createInstance (instance );
144
153
Instance createdInstance ;
154
+ int maxAttempts = 25 ;
145
155
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 ) {
149
170
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 ;
174
188
}
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 );
175
195
}
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 ;
177
204
}
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.*" );
179
210
}
180
211
181
212
private void cleanUpOldDatabases (InstanceId instanceId ) {
0 commit comments