1111import fixture .gcs .FakeOAuth2HttpHandler ;
1212import fixture .gcs .GoogleCloudStorageHttpHandler ;
1313
14+ import com .google .api .client .http .HttpExecuteInterceptor ;
15+ import com .google .api .client .http .HttpRequestInitializer ;
1416import com .google .api .gax .retrying .RetrySettings ;
17+ import com .google .cloud .ServiceOptions ;
1518import com .google .cloud .http .HttpTransportOptions ;
1619import com .google .cloud .storage .StorageException ;
1720import com .google .cloud .storage .StorageOptions ;
18- import com .google .cloud .storage .StorageRetryStrategy ;
1921import com .sun .net .httpserver .HttpHandler ;
2022
2123import org .apache .http .HttpStatus ;
6163import java .util .Objects ;
6264import java .util .Optional ;
6365import java .util .Queue ;
66+ import java .util .concurrent .ConcurrentHashMap ;
6467import java .util .concurrent .ConcurrentLinkedQueue ;
6568import java .util .concurrent .atomic .AtomicBoolean ;
6669import java .util .concurrent .atomic .AtomicInteger ;
8992@ SuppressForbidden (reason = "use a http server" )
9093public class GoogleCloudStorageBlobContainerRetriesTests extends AbstractBlobContainerRetriesTestCase {
9194
95+ private final Map <String , AtomicInteger > requestCounters = new ConcurrentHashMap <>();
96+ private String endpointUrlOverride ;
97+
9298 private String httpServerUrl () {
9399 assertThat (httpServer , notNullValue ());
94100 InetSocketAddress address = httpServer .getAddress ();
95101 return "http://" + InetAddresses .toUriString (address .getAddress ()) + ":" + address .getPort ();
96102 }
97103
104+ private String getEndpointUrl () {
105+ return endpointUrlOverride != null ? endpointUrlOverride : httpServerUrl ();
106+ }
107+
98108 @ Override
99109 protected String downloadStorageEndpoint (BlobContainer container , String blob ) {
100110 return "/download/storage/v1/b/bucket/o/" + container .path ().buildAsString () + blob ;
@@ -120,7 +130,7 @@ protected BlobContainer createBlobContainer(
120130 ) {
121131 final Settings .Builder clientSettings = Settings .builder ();
122132 final String client = randomAlphaOfLength (5 ).toLowerCase (Locale .ROOT );
123- clientSettings .put (ENDPOINT_SETTING .getConcreteSettingForNamespace (client ).getKey (), httpServerUrl ());
133+ clientSettings .put (ENDPOINT_SETTING .getConcreteSettingForNamespace (client ).getKey (), getEndpointUrl ());
124134 clientSettings .put (TOKEN_URI_SETTING .getConcreteSettingForNamespace (client ).getKey (), httpServerUrl () + "/token" );
125135 if (readTimeout != null ) {
126136 clientSettings .put (READ_TIMEOUT_SETTING .getConcreteSettingForNamespace (client ).getKey (), readTimeout );
@@ -136,8 +146,33 @@ StorageOptions createStorageOptions(
136146 final GoogleCloudStorageClientSettings gcsClientSettings ,
137147 final HttpTransportOptions httpTransportOptions
138148 ) {
139- StorageOptions options = super .createStorageOptions (gcsClientSettings , httpTransportOptions );
140- RetrySettings .Builder retrySettingsBuilder = RetrySettings .newBuilder ()
149+ final HttpTransportOptions requestCountingHttpTransportOptions = new HttpTransportOptions (
150+ HttpTransportOptions .newBuilder ()
151+ .setConnectTimeout (httpTransportOptions .getConnectTimeout ())
152+ .setHttpTransportFactory (httpTransportOptions .getHttpTransportFactory ())
153+ .setReadTimeout (httpTransportOptions .getReadTimeout ())
154+ ) {
155+ @ Override
156+ public HttpRequestInitializer getHttpRequestInitializer (ServiceOptions <?, ?> serviceOptions ) {
157+ // Add initializer/interceptor without interfering with any pre-existing ones
158+ HttpRequestInitializer httpRequestInitializer = super .getHttpRequestInitializer (serviceOptions );
159+ return request -> {
160+ if (httpRequestInitializer != null ) {
161+ httpRequestInitializer .initialize (request );
162+ }
163+ HttpExecuteInterceptor interceptor = request .getInterceptor ();
164+ request .setInterceptor (req -> {
165+ if (interceptor != null ) {
166+ interceptor .intercept (req );
167+ }
168+ requestCounters .computeIfAbsent (request .getUrl ().getRawPath (), (url ) -> new AtomicInteger ())
169+ .incrementAndGet ();
170+ });
171+ };
172+ }
173+ };
174+ final StorageOptions options = super .createStorageOptions (gcsClientSettings , requestCountingHttpTransportOptions );
175+ final RetrySettings .Builder retrySettingsBuilder = RetrySettings .newBuilder ()
141176 .setTotalTimeout (options .getRetrySettings ().getTotalTimeout ())
142177 .setInitialRetryDelay (Duration .ofMillis (10L ))
143178 .setRetryDelayMultiplier (1.0d )
@@ -150,7 +185,7 @@ StorageOptions createStorageOptions(
150185 retrySettingsBuilder .setMaxAttempts (maxRetries + 1 );
151186 }
152187 return options .toBuilder ()
153- .setStorageRetryStrategy (StorageRetryStrategy . getLegacyStorageRetryStrategy ())
188+ .setStorageRetryStrategy (getRetryStrategy ())
154189 .setHost (options .getHost ())
155190 .setCredentials (options .getCredentials ())
156191 .setRetrySettings (retrySettingsBuilder .build ())
@@ -173,6 +208,25 @@ StorageOptions createStorageOptions(
173208 return new GoogleCloudStorageBlobContainer (randomBoolean () ? BlobPath .EMPTY : BlobPath .EMPTY .add ("foo" ), blobStore );
174209 }
175210
211+ public void testShouldRetryOnConnectionRefused () {
212+ // port 1 should never be open
213+ endpointUrlOverride = "http://127.0.0.1:1" ;
214+ executeListBlobsAndAssertRetries ();
215+ }
216+
217+ public void testShouldRetryOnUnresolvableHost () {
218+ // https://www.rfc-editor.org/rfc/rfc2606.html#page-2
219+ endpointUrlOverride = "http://unresolvable.invalid" ;
220+ executeListBlobsAndAssertRetries ();
221+ }
222+
223+ private void executeListBlobsAndAssertRetries () {
224+ final int maxRetries = randomIntBetween (3 , 5 );
225+ final BlobContainer blobContainer = createBlobContainer (maxRetries , null , null , null , null );
226+ expectThrows (StorageException .class , () -> blobContainer .listBlobs (randomPurpose ()));
227+ assertEquals (maxRetries + 1 , requestCounters .get ("/storage/v1/b/bucket/o" ).get ());
228+ }
229+
176230 public void testReadLargeBlobWithRetries () throws Exception {
177231 final int maxRetries = randomIntBetween (2 , 10 );
178232 final AtomicInteger countDown = new AtomicInteger (maxRetries );
0 commit comments