This is an automated email from the ASF dual-hosted git repository.
abhishekrb pushed a commit to branch 29.0.1
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/29.0.1 by this push:
new c43fbf3ed79 Remove exception on failure response from GCS delete API
(#16047) (#16095)
c43fbf3ed79 is described below
commit c43fbf3ed79eb0f96c373f842a7dcb883ecc8053
Author: Vishesh Garg <[email protected]>
AuthorDate: Mon Mar 11 13:25:09 2024 +0530
Remove exception on failure response from GCS delete API (#16047) (#16095)
* Throw 404 Exception on failure response from GCS delete API
* Replace String.format
* Apply suggestions from code review
Co-authored-by: Abhishek Radhakrishnan <[email protected]>
* Remove exception for file not found and fix tests
* Add warn log and fix intellij inspection errors
* More intellij inspection fixes
* * Change to debug log
* change runtime exception class for code coverage
* Add file paths for batch delete failures
* Move failedPaths computation to inside isDebugEnabled flag
* Correct handling of StorageException
* Address review comments
* Remove unused exceptions
* Address code coverage and review comments
* Minor corrections
---------
Co-authored-by: Abhishek Radhakrishnan <[email protected]>
(cherry picked from commit bed5d9c3b21929e27e4875f9998770a3af3023f5)
---
.../storage/google/GoogleDataSegmentKiller.java | 32 +--
.../apache/druid/storage/google/GoogleStorage.java | 43 ++--
.../apache/druid/storage/google/GoogleUtils.java | 4 +
.../google/output/GoogleStorageConnector.java | 35 +++-
.../google/GoogleDataSegmentKillerTest.java | 98 ++++-----
.../druid/storage/google/GoogleStorageTest.java | 63 ++++--
.../druid/storage/google/GoogleTaskLogsTest.java | 7 +-
.../druid/storage/google/GoogleTestUtils.java | 2 +-
.../druid/storage/google/GoogleUtilsTest.java | 26 +++
.../google/output/GoogleStorageConnectorTest.java | 225 ++++++++++++++++++++-
.../apache/druid/testsEx/utils/GcsTestUtil.java | 2 +-
11 files changed, 400 insertions(+), 137 deletions(-)
diff --git
a/extensions-core/google-extensions/src/main/java/org/apache/druid/storage/google/GoogleDataSegmentKiller.java
b/extensions-core/google-extensions/src/main/java/org/apache/druid/storage/google/GoogleDataSegmentKiller.java
index c0dc4c8ea33..42cc7c194b4 100644
---
a/extensions-core/google-extensions/src/main/java/org/apache/druid/storage/google/GoogleDataSegmentKiller.java
+++
b/extensions-core/google-extensions/src/main/java/org/apache/druid/storage/google/GoogleDataSegmentKiller.java
@@ -19,13 +19,12 @@
package org.apache.druid.storage.google;
-import com.google.api.client.http.HttpResponseException;
+import com.google.cloud.storage.StorageException;
import com.google.common.base.Predicates;
import com.google.inject.Inject;
import org.apache.druid.java.util.common.ISE;
import org.apache.druid.java.util.common.MapUtils;
import org.apache.druid.java.util.common.RE;
-import org.apache.druid.java.util.common.RetryUtils;
import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.segment.loading.DataSegmentKiller;
import org.apache.druid.segment.loading.SegmentLoadingException;
@@ -70,35 +69,24 @@ public class GoogleDataSegmentKiller implements
DataSegmentKiller
// anymore, but we still delete them if exists.
deleteIfPresent(bucket, descriptorPath);
}
- catch (IOException e) {
+ catch (StorageException e) {
throw new SegmentLoadingException(e, "Couldn't kill segment[%s]: [%s]",
segment.getId(), e.getMessage());
}
}
- private void deleteIfPresent(String bucket, String path) throws IOException
+ private void deleteIfPresent(String bucket, String path)
{
try {
- RetryUtils.retry(
- (RetryUtils.Task<Void>) () -> {
- storage.delete(bucket, path);
- return null;
- },
- GoogleUtils::isRetryable,
- 1,
- 5
- );
- }
- catch (HttpResponseException e) {
- if (e.getStatusCode() != 404) {
- throw e;
- }
- LOG.debug("Already deleted: [%s] [%s]", bucket, path);
+ GoogleUtils.retryGoogleCloudStorageOperation(() -> {
+ storage.delete(bucket, path);
+ return null;
+ });
}
- catch (IOException ioe) {
- throw ioe;
+ catch (StorageException e) {
+ throw e;
}
catch (Exception e) {
- throw new RE(e, "Failed to delete [%s] [%s]", bucket, path);
+ throw new RE(e, "Failed to delete google cloud storage object from
bucket [%s] and path [%s].", bucket, path);
}
}
diff --git
a/extensions-core/google-extensions/src/main/java/org/apache/druid/storage/google/GoogleStorage.java
b/extensions-core/google-extensions/src/main/java/org/apache/druid/storage/google/GoogleStorage.java
index 301f848339f..57c2ac1843b 100644
---
a/extensions-core/google-extensions/src/main/java/org/apache/druid/storage/google/GoogleStorage.java
+++
b/extensions-core/google-extensions/src/main/java/org/apache/druid/storage/google/GoogleStorage.java
@@ -31,6 +31,7 @@ import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
import org.apache.druid.java.util.common.HumanReadableBytes;
import org.apache.druid.java.util.common.IOE;
+import org.apache.druid.java.util.common.logger.Logger;
import javax.annotation.Nullable;
import java.io.IOException;
@@ -52,6 +53,8 @@ public class GoogleStorage
* <p>
* See OmniDataSegmentKiller for how DataSegmentKillers are initialized.
*/
+ private static final Logger log = new Logger(GoogleStorage.class);
+
private final Supplier<Storage> storage;
private final HumanReadableBytes DEFAULT_WRITE_CHUNK_SIZE = new
HumanReadableBytes("4MiB");
@@ -131,7 +134,7 @@ public class GoogleStorage
{
Blob blob = storage.get().get(bucket, path,
Storage.BlobGetOption.fields(Storage.BlobField.values()));
if (blob == null) {
- throw new IOE("Failed fetching google cloud storage object [bucket: %s,
path: %s]", bucket, path);
+ throw new IOE("Failed to fetch google cloud storage object from bucket
[%s] and path[%s].", bucket, path);
}
return new GoogleStorageObjectMetadata(
blob.getBucket(),
@@ -142,28 +145,41 @@ public class GoogleStorage
);
}
- public void delete(final String bucket, final String path) throws IOException
+
+ /**
+ * Deletes an object in a bucket on the specified path
+
+ * A false response from GCS delete API is indicative of file not found. Any
other error is raised as a StorageException
+ * and should be explicitly handled.
+ Ref: <a
href="https://github.com/googleapis/java-storage/blob/v2.29.1/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java">HttpStorageRpc.java</a>
+ *
+ * @param bucket GCS bucket
+ * @param path Object path
+ */
+ public void delete(final String bucket, final String path)
{
if (!storage.get().delete(bucket, path)) {
- throw new IOE(
- "Failed deleting google cloud storage object [bucket: %s path: %s]",
- bucket,
- path
- );
+ log.debug("Google cloud storage object to be deleted not found in bucket
[%s] and path [%s].", bucket, path);
}
}
/**
* Deletes a list of objects in a bucket
+ * A false response from GCS delete API is indicative of file not found. Any
other error is raised as a StorageException
+ * and should be explicitly handled.
+ * Ref: <a
href="https://github.com/googleapis/java-storage/blob/v2.29.1/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java">HttpStorageRpc.java</a>
*
* @param bucket GCS bucket
* @param paths Iterable for absolute paths of objects to be deleted inside
the bucket
*/
- public void batchDelete(final String bucket, final Iterable<String> paths)
throws IOException
+ public void batchDelete(final String bucket, final Iterable<String> paths)
{
- List<Boolean> statuses = storage.get().delete(Iterables.transform(paths,
input -> BlobId.of(bucket, input)));
+ final List<Boolean> statuses =
storage.get().delete(Iterables.transform(paths, input -> BlobId.of(bucket,
input)));
if (statuses.contains(false)) {
- throw new IOE("Failed deleting google cloud storage object(s)");
+ log.debug(
+ "Google cloud storage object(s) to be deleted not found in bucket
[%s].",
+ bucket
+ );
}
}
@@ -177,7 +193,7 @@ public class GoogleStorage
{
Blob blob = storage.get().get(bucket, path,
Storage.BlobGetOption.fields(Storage.BlobField.SIZE));
if (blob == null) {
- throw new IOE("Failed fetching google cloud storage object [bucket: %s,
path: %s]", bucket, path);
+ throw new IOE("Failed to fetch google cloud storage object from bucket
[%s] and path [%s].", bucket, path);
}
return blob.getSize();
}
@@ -186,7 +202,7 @@ public class GoogleStorage
{
Blob blob = storage.get().get(bucket, path,
Storage.BlobGetOption.fields(Storage.BlobField.GENERATION));
if (blob == null) {
- throw new IOE("Failed fetching google cloud storage object [bucket: %s,
path: %s]", bucket, path);
+ throw new IOE("Failed to fetch google cloud storage object from bucket
[%s] and path [%s].", bucket, path);
}
return blob.getGeneratedId();
}
@@ -223,7 +239,7 @@ public class GoogleStorage
Page<Blob> blobPage = storage.get().list(bucket, options.toArray(new
Storage.BlobListOption[0]));
if (blobPage == null) {
- throw new IOE("Failed fetching google cloud storage object [bucket: %s,
prefix: %s]", bucket, prefix);
+ throw new IOE("Failed to fetch google cloud storage object from bucket
[%s] and prefix [%s].", bucket, prefix);
}
@@ -247,6 +263,5 @@ public class GoogleStorage
{
BlobId blobId = BlobId.of(bucket, path);
return BlobInfo.newBuilder(blobId).build();
-
}
}
diff --git
a/extensions-core/google-extensions/src/main/java/org/apache/druid/storage/google/GoogleUtils.java
b/extensions-core/google-extensions/src/main/java/org/apache/druid/storage/google/GoogleUtils.java
index a819442ef35..cba718ac338 100644
---
a/extensions-core/google-extensions/src/main/java/org/apache/druid/storage/google/GoogleUtils.java
+++
b/extensions-core/google-extensions/src/main/java/org/apache/druid/storage/google/GoogleUtils.java
@@ -20,6 +20,7 @@
package org.apache.druid.storage.google;
import com.google.api.client.http.HttpResponseException;
+import com.google.cloud.storage.StorageException;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import org.apache.druid.data.input.impl.CloudObjectLocation;
@@ -40,6 +41,9 @@ public class GoogleUtils
if (t instanceof HttpResponseException) {
final HttpResponseException e = (HttpResponseException) t;
return e.getStatusCode() == 429 || (e.getStatusCode() / 500 == 1);
+ } else if (t instanceof StorageException) {
+ final StorageException e = (StorageException) t;
+ return e.isRetryable();
}
return t instanceof IOException;
}
diff --git
a/extensions-core/google-extensions/src/main/java/org/apache/druid/storage/google/output/GoogleStorageConnector.java
b/extensions-core/google-extensions/src/main/java/org/apache/druid/storage/google/output/GoogleStorageConnector.java
index 6edbad3beaf..0d147cce25a 100644
---
a/extensions-core/google-extensions/src/main/java/org/apache/druid/storage/google/output/GoogleStorageConnector.java
+++
b/extensions-core/google-extensions/src/main/java/org/apache/druid/storage/google/output/GoogleStorageConnector.java
@@ -73,7 +73,7 @@ public class GoogleStorageConnector extends
ChunkingStorageConnector<GoogleInput
catch (IOException e) {
throw new RE(
e,
- StringUtils.format("Cannot create tempDir : [%s] for google storage
connector", config.getTempDir())
+ StringUtils.format("Cannot create tempDir [%s] for google storage
connector", config.getTempDir())
);
}
}
@@ -95,7 +95,7 @@ public class GoogleStorageConnector extends
ChunkingStorageConnector<GoogleInput
{
try {
final String fullPath = objectPath(path);
- log.debug("Deleting file at bucket: [%s], path: [%s]",
config.getBucket(), fullPath);
+ log.debug("Deleting file at bucket [%s] and path [%s].",
config.getBucket(), fullPath);
GoogleUtils.retryGoogleCloudStorageOperation(
() -> {
@@ -105,7 +105,7 @@ public class GoogleStorageConnector extends
ChunkingStorageConnector<GoogleInput
);
}
catch (Exception e) {
- log.error("Error occurred while deleting file at path [%s]. Error:
[%s]", path, e.getMessage());
+ log.error("Failed to delete object at bucket [%s] and path [%s].",
config.getBucket(), path);
throw new IOException(e);
}
}
@@ -113,7 +113,17 @@ public class GoogleStorageConnector extends
ChunkingStorageConnector<GoogleInput
@Override
public void deleteFiles(Iterable<String> paths) throws IOException
{
- storage.batchDelete(config.getBucket(), Iterables.transform(paths,
this::objectPath));
+ try {
+ GoogleUtils.retryGoogleCloudStorageOperation(() -> {
+ storage.batchDelete(config.getBucket(), Iterables.transform(paths,
this::objectPath));
+ return null;
+ });
+ }
+ catch (Exception e) {
+ log.error("Failed to delete object(s) at bucket [%s].",
config.getBucket());
+ throw new IOException(e);
+ }
+
}
@Override
@@ -127,10 +137,19 @@ public class GoogleStorageConnector extends
ChunkingStorageConnector<GoogleInput
inputDataConfig.getMaxListingLength()
);
- storage.batchDelete(
- config.getBucket(),
- () -> Iterators.transform(storageObjects,
GoogleStorageObjectMetadata::getName)
- );
+ try {
+ GoogleUtils.retryGoogleCloudStorageOperation(() -> {
+ storage.batchDelete(
+ config.getBucket(),
+ () -> Iterators.transform(storageObjects,
GoogleStorageObjectMetadata::getName)
+ );
+ return null;
+ });
+ }
+ catch (Exception e) {
+ log.error("Failed to delete object(s) at bucket [%s] and prefix [%s].",
config.getBucket(), fullPath);
+ throw new IOException(e);
+ }
}
@Override
diff --git
a/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleDataSegmentKillerTest.java
b/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleDataSegmentKillerTest.java
index 8d9612f4d8d..87b406b1fa4 100644
---
a/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleDataSegmentKillerTest.java
+++
b/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleDataSegmentKillerTest.java
@@ -19,11 +19,7 @@
package org.apache.druid.storage.google;
-import com.google.api.client.googleapis.json.GoogleJsonResponseException;
-import
com.google.api.client.googleapis.testing.json.GoogleJsonResponseExceptionFactoryTesting;
-import com.google.api.client.http.HttpHeaders;
-import com.google.api.client.http.HttpResponseException;
-import com.google.api.client.json.jackson2.JacksonFactory;
+import com.google.cloud.storage.StorageException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.apache.druid.java.util.common.ISE;
@@ -54,8 +50,8 @@ public class GoogleDataSegmentKillerTest extends
EasyMockSupport
private static final long TIME_0 = 0L;
private static final long TIME_1 = 1L;
private static final int MAX_KEYS = 1;
- private static final Exception RECOVERABLE_EXCEPTION = new
HttpResponseException.Builder(429, "recoverable", new HttpHeaders()).build();
- private static final Exception NON_RECOVERABLE_EXCEPTION = new
HttpResponseException.Builder(404, "non recoverable", new
HttpHeaders()).build();
+ private static final Exception RECOVERABLE_EXCEPTION = new
StorageException(429, "recoverable");
+ private static final Exception NON_RECOVERABLE_EXCEPTION = new
StorageException(404, "non-recoverable");
private static final DataSegment DATA_SEGMENT = new DataSegment(
@@ -83,7 +79,7 @@ public class GoogleDataSegmentKillerTest extends
EasyMockSupport
}
@Test
- public void killTest() throws SegmentLoadingException, IOException
+ public void killTest() throws SegmentLoadingException
{
storage.delete(EasyMock.eq(BUCKET), EasyMock.eq(INDEX_PATH));
EasyMock.expectLastCall();
@@ -99,38 +95,30 @@ public class GoogleDataSegmentKillerTest extends
EasyMockSupport
verifyAll();
}
- @Test(expected = SegmentLoadingException.class)
- public void killWithErrorTest() throws SegmentLoadingException, IOException
+ @Test
+ public void killWithErrorTest()
{
- final GoogleJsonResponseException exception =
GoogleJsonResponseExceptionFactoryTesting.newMock(
- JacksonFactory.getDefaultInstance(),
- 300,
- "test"
- );
- storage.delete(EasyMock.eq(BUCKET), EasyMock.eq(INDEX_PATH));
- EasyMock.expectLastCall().andThrow(exception);
+ Assert.assertThrows(SegmentLoadingException.class, () -> {
+ storage.delete(EasyMock.eq(BUCKET), EasyMock.eq(INDEX_PATH));
+ EasyMock.expectLastCall().andThrow(NON_RECOVERABLE_EXCEPTION);
- replayAll();
+ replayAll();
- GoogleDataSegmentKiller killer = new GoogleDataSegmentKiller(storage,
accountConfig, inputDataConfig);
+ GoogleDataSegmentKiller killer = new GoogleDataSegmentKiller(storage,
accountConfig, inputDataConfig);
- killer.kill(DATA_SEGMENT);
+ killer.kill(DATA_SEGMENT);
- verifyAll();
+ verifyAll();
+ });
}
@Test
- public void killRetryWithErrorTest() throws SegmentLoadingException,
IOException
+ public void killRetryWithErrorTest() throws SegmentLoadingException
{
- final GoogleJsonResponseException exception =
GoogleJsonResponseExceptionFactoryTesting.newMock(
- JacksonFactory.getDefaultInstance(),
- 500,
- "test"
- );
storage.delete(EasyMock.eq(BUCKET), EasyMock.eq(INDEX_PATH));
- EasyMock.expectLastCall().andThrow(exception).once().andVoid().once();
+
EasyMock.expectLastCall().andThrow(RECOVERABLE_EXCEPTION).once().andVoid().once();
storage.delete(EasyMock.eq(BUCKET), EasyMock.eq(DESCRIPTOR_PATH));
- EasyMock.expectLastCall().andThrow(exception).once().andVoid().once();
+
EasyMock.expectLastCall().andThrow(RECOVERABLE_EXCEPTION).once().andVoid().once();
replayAll();
@@ -142,25 +130,16 @@ public class GoogleDataSegmentKillerTest extends
EasyMockSupport
}
@Test
- public void
test_killAll_accountConfigWithNullBucketAndPrefix_throwsISEException() throws
IOException
+ public void
test_killAll_accountConfigWithNullBucketAndPrefix_throwsISEException()
{
EasyMock.expect(accountConfig.getBucket()).andReturn(null).atLeastOnce();
EasyMock.expect(accountConfig.getPrefix()).andReturn(null).anyTimes();
+ GoogleDataSegmentKiller killer = new GoogleDataSegmentKiller(storage,
accountConfig, inputDataConfig);
+ EasyMock.replay(storage, inputDataConfig, accountConfig);
- boolean thrownISEException = false;
-
- try {
- GoogleDataSegmentKiller killer = new GoogleDataSegmentKiller(storage,
accountConfig, inputDataConfig);
- EasyMock.replay(storage, inputDataConfig, accountConfig);
-
- killer.killAll();
- }
- catch (ISE e) {
- thrownISEException = true;
- }
+ Assert.assertThrows(ISE.class, killer::killAll);
- Assert.assertTrue(thrownISEException);
EasyMock.verify(accountConfig, inputDataConfig, storage);
}
@@ -217,34 +196,27 @@ public class GoogleDataSegmentKillerTest extends
EasyMockSupport
}
@Test
- public void
test_killAll_nonrecoverableExceptionWhenListingObjects_doesntDeleteAnyTaskLogs()
+ public void
test_killAll_nonrecoverableExceptionWhenListingObjects_doesntDeleteAnyTaskLogs()
throws IOException
{
- boolean ioExceptionThrown = false;
- try {
- GoogleStorageObjectMetadata object1 =
GoogleTestUtils.newStorageObject(BUCKET, KEY_1, TIME_0);
+ GoogleStorageObjectMetadata object1 =
GoogleTestUtils.newStorageObject(BUCKET, KEY_1, TIME_0);
- GoogleTestUtils.expectListObjectsPageRequest(storage, PREFIX_URI,
MAX_KEYS, ImmutableList.of(object1));
+ GoogleTestUtils.expectListObjectsPageRequest(storage, PREFIX_URI,
MAX_KEYS, ImmutableList.of(object1));
- GoogleTestUtils.expectDeleteObjects(
- storage,
- ImmutableList.of(),
- ImmutableMap.of(object1, NON_RECOVERABLE_EXCEPTION)
- );
+ GoogleTestUtils.expectDeleteObjects(
+ storage,
+ ImmutableList.of(),
+ ImmutableMap.of(object1, NON_RECOVERABLE_EXCEPTION)
+ );
- EasyMock.expect(accountConfig.getBucket()).andReturn(BUCKET).anyTimes();
- EasyMock.expect(accountConfig.getPrefix()).andReturn(PREFIX).anyTimes();
-
EasyMock.expect(inputDataConfig.getMaxListingLength()).andReturn(MAX_KEYS);
+ EasyMock.expect(accountConfig.getBucket()).andReturn(BUCKET).anyTimes();
+ EasyMock.expect(accountConfig.getPrefix()).andReturn(PREFIX).anyTimes();
+ EasyMock.expect(inputDataConfig.getMaxListingLength()).andReturn(MAX_KEYS);
- EasyMock.replay(accountConfig, inputDataConfig, storage);
+ EasyMock.replay(accountConfig, inputDataConfig, storage);
- GoogleDataSegmentKiller killer = new GoogleDataSegmentKiller(storage,
accountConfig, inputDataConfig);
- killer.killAll();
- }
- catch (IOException e) {
- ioExceptionThrown = true;
- }
+ GoogleDataSegmentKiller killer = new GoogleDataSegmentKiller(storage,
accountConfig, inputDataConfig);
- Assert.assertTrue(ioExceptionThrown);
+ Assert.assertThrows(IOException.class, killer::killAll);
EasyMock.verify(accountConfig, inputDataConfig, storage);
}
diff --git
a/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleStorageTest.java
b/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleStorageTest.java
index 71562802207..0a6d346d1c4 100644
---
a/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleStorageTest.java
+++
b/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleStorageTest.java
@@ -23,9 +23,11 @@ import com.google.api.gax.paging.Page;
import com.google.cloud.storage.Blob;
import com.google.cloud.storage.BlobId;
import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageException;
import com.google.common.collect.ImmutableList;
import org.easymock.Capture;
import org.easymock.EasyMock;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -50,6 +52,8 @@ public class GoogleStorageTest
static final String PATH = "/path";
static final long SIZE = 100;
static final OffsetDateTime UPDATE_TIME = OffsetDateTime.MIN;
+ private static final Exception STORAGE_EXCEPTION = new StorageException(404,
"Runtime Storage Exception");
+
@Before
public void setUp()
@@ -62,7 +66,7 @@ public class GoogleStorageTest
}
@Test
- public void testDeleteSuccess() throws IOException
+ public void testDeleteSuccess()
{
EasyMock.expect(mockStorage.delete(EasyMock.eq(BUCKET),
EasyMock.eq(PATH))).andReturn(true);
EasyMock.replay(mockStorage);
@@ -70,23 +74,23 @@ public class GoogleStorageTest
}
@Test
- public void testDeleteFailure()
+ public void testDeleteFileNotFound()
{
EasyMock.expect(mockStorage.delete(EasyMock.eq(BUCKET),
EasyMock.eq(PATH))).andReturn(false);
EasyMock.replay(mockStorage);
- boolean thrownIOException = false;
- try {
- googleStorage.delete(BUCKET, PATH);
-
- }
- catch (IOException e) {
- thrownIOException = true;
- }
- assertTrue(thrownIOException);
+ googleStorage.delete(BUCKET, PATH);
+ }
+
+ @Test
+ public void testDeleteFailure()
+ {
+ EasyMock.expect(mockStorage.delete(EasyMock.eq(BUCKET),
EasyMock.eq(PATH))).andThrow(STORAGE_EXCEPTION);
+ EasyMock.replay(mockStorage);
+ Assert.assertThrows(StorageException.class, () ->
googleStorage.delete(BUCKET, PATH));
}
@Test
- public void testBatchDeleteSuccess() throws IOException
+ public void testBatchDeleteSuccess()
{
List<String> paths = ImmutableList.of("/path1", "/path2");
final Capture<Iterable<BlobId>> pathIterable = Capture.newInstance();
@@ -103,6 +107,29 @@ public class GoogleStorageTest
assertTrue(paths.size() == recordedPaths.size() &&
paths.containsAll(recordedPaths) && recordedPaths.containsAll(
paths));
assertEquals(BUCKET, recordedBlobIds.get(0).getBucket());
+
+ }
+
+ @Test
+ public void testBatchDeleteFileNotFound()
+ {
+ List<String> paths = ImmutableList.of("/path1", "/path2");
+ final Capture<Iterable<BlobId>> pathIterable = Capture.newInstance();
+
EasyMock.expect(mockStorage.delete(EasyMock.capture(pathIterable))).andReturn(ImmutableList.of(true,
false));
+ EasyMock.replay(mockStorage);
+
+ googleStorage.batchDelete(BUCKET, paths);
+
+ List<BlobId> recordedBlobIds = new ArrayList<>();
+ pathIterable.getValue().iterator().forEachRemaining(recordedBlobIds::add);
+
+ List<String> recordedPaths =
recordedBlobIds.stream().map(BlobId::getName).collect(Collectors.toList());
+
+ assertTrue(paths.size() == recordedPaths.size());
+ assertTrue(paths.containsAll(recordedPaths));
+ assertTrue(recordedPaths.containsAll(paths));
+ assertEquals(BUCKET, recordedBlobIds.get(0).getBucket());
+
}
@Test
@@ -110,17 +137,9 @@ public class GoogleStorageTest
{
List<String> paths = ImmutableList.of("/path1", "/path2");
EasyMock.expect(mockStorage.delete((Iterable<BlobId>)
EasyMock.anyObject()))
- .andReturn(ImmutableList.of(false, true));
+ .andThrow(STORAGE_EXCEPTION);
EasyMock.replay(mockStorage);
- boolean thrownIOException = false;
- try {
- googleStorage.batchDelete(BUCKET, paths);
-
- }
- catch (IOException e) {
- thrownIOException = true;
- }
- assertTrue(thrownIOException);
+ Assert.assertThrows(StorageException.class, () ->
googleStorage.batchDelete(BUCKET, paths));
}
@Test
diff --git
a/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleTaskLogsTest.java
b/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleTaskLogsTest.java
index a0f17c97d91..438d4b8ed6f 100644
---
a/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleTaskLogsTest.java
+++
b/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleTaskLogsTest.java
@@ -19,9 +19,8 @@
package org.apache.druid.storage.google;
-import com.google.api.client.http.HttpHeaders;
-import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.InputStreamContent;
+import com.google.cloud.storage.StorageException;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -58,8 +57,8 @@ public class GoogleTaskLogsTest extends EasyMockSupport
private static final long TIME_NOW = 2L;
private static final long TIME_FUTURE = 3L;
private static final int MAX_KEYS = 1;
- private static final Exception RECOVERABLE_EXCEPTION = new
HttpResponseException.Builder(429, "recoverable", new HttpHeaders()).build();
- private static final Exception NON_RECOVERABLE_EXCEPTION = new
HttpResponseException.Builder(404, "non recoverable", new
HttpHeaders()).build();
+ private static final Exception RECOVERABLE_EXCEPTION = new
StorageException(429, "recoverable");
+ private static final Exception NON_RECOVERABLE_EXCEPTION = new
StorageException(404, "non-recoverable");
private GoogleStorage storage;
private GoogleTaskLogs googleTaskLogs;
diff --git
a/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleTestUtils.java
b/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleTestUtils.java
index c68911448e2..032ab08a066 100644
---
a/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleTestUtils.java
+++
b/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleTestUtils.java
@@ -71,7 +71,7 @@ public class GoogleTestUtils extends EasyMockSupport
GoogleStorage storage,
List<GoogleStorageObjectMetadata> deleteObjectExpected,
Map<GoogleStorageObjectMetadata, Exception> deleteObjectToException
- ) throws IOException
+ )
{
Map<GoogleStorageObjectMetadata,
IExpectationSetters<GoogleStorageObjectMetadata>>
requestToResultExpectationSetter = new HashMap<>();
for (Map.Entry<GoogleStorageObjectMetadata, Exception>
deleteObjectAndException : deleteObjectToException.entrySet()) {
diff --git
a/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleUtilsTest.java
b/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleUtilsTest.java
index aac7ab0fbf7..76f6dfe9817 100644
---
a/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleUtilsTest.java
+++
b/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/GoogleUtilsTest.java
@@ -22,6 +22,7 @@ package org.apache.druid.storage.google;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpResponseException;
+import com.google.cloud.storage.StorageException;
import org.junit.Assert;
import org.junit.Test;
@@ -78,5 +79,30 @@ public class GoogleUtilsTest
new IOException("generic io exception")
)
);
+ Assert.assertFalse(
+ GoogleUtils.isRetryable(
+ new StorageException(404, "ignored")
+ )
+ );
+ Assert.assertTrue(
+ GoogleUtils.isRetryable(
+ new StorageException(429, "ignored")
+ )
+ );
+ Assert.assertTrue(
+ GoogleUtils.isRetryable(
+ new StorageException(500, "ignored")
+ )
+ );
+ Assert.assertTrue(
+ GoogleUtils.isRetryable(
+ new StorageException(503, "ignored")
+ )
+ );
+ Assert.assertFalse(
+ GoogleUtils.isRetryable(
+ new StorageException(599, "ignored")
+ )
+ );
}
}
diff --git
a/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/output/GoogleStorageConnectorTest.java
b/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/output/GoogleStorageConnectorTest.java
index 7a5e6ba107b..c1cf4304c4b 100644
---
a/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/output/GoogleStorageConnectorTest.java
+++
b/extensions-core/google-extensions/src/test/java/org/apache/druid/storage/google/output/GoogleStorageConnectorTest.java
@@ -19,6 +19,7 @@
package org.apache.druid.storage.google.output;
+import com.google.cloud.storage.StorageException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
@@ -54,6 +55,8 @@ public class GoogleStorageConnectorTest
GoogleStorageConnector googleStorageConnector;
private final GoogleStorage googleStorage =
EasyMock.createMock(GoogleStorage.class);
+ private static final Exception RECOVERABLE_EXCEPTION = new
StorageException(429, "recoverable");
+ private static final Exception NON_RECOVERABLE_EXCEPTION = new
StorageException(404, "non-recoverable");
@Before
public void setUp() throws IOException
@@ -91,7 +94,7 @@ public class GoogleStorageConnectorTest
}
@Test
- public void testDeleteFile() throws IOException
+ public void testDeleteFileSuccess() throws IOException
{
Capture<String> bucketCapture = EasyMock.newCapture();
Capture<String> pathCapture = EasyMock.newCapture();
@@ -107,7 +110,40 @@ public class GoogleStorageConnectorTest
}
@Test
- public void testDeleteFiles() throws IOException
+ public void testDeleteFileRetrySuccess() throws IOException
+ {
+ Capture<String> bucketCapture = EasyMock.newCapture();
+ Capture<String> pathCapture = EasyMock.newCapture();
+ googleStorage.delete(
+ EasyMock.capture(bucketCapture),
+ EasyMock.capture(pathCapture)
+ );
+
EasyMock.expectLastCall().andThrow(RECOVERABLE_EXCEPTION).once().andVoid().once();
+
+ EasyMock.replay(googleStorage);
+ googleStorageConnector.deleteFile(TEST_FILE);
+ Assert.assertEquals(BUCKET, bucketCapture.getValue());
+ Assert.assertEquals(PREFIX + "/" + TEST_FILE, pathCapture.getValue());
+ }
+
+ @Test
+ public void testDeleteFileFailure()
+ {
+ Capture<String> bucketCapture = EasyMock.newCapture();
+ Capture<String> pathCapture = EasyMock.newCapture();
+ googleStorage.delete(
+ EasyMock.capture(bucketCapture),
+ EasyMock.capture(pathCapture)
+ );
+
EasyMock.expectLastCall().andThrow(NON_RECOVERABLE_EXCEPTION).once().andVoid().once();
+ EasyMock.replay(googleStorage);
+ Assert.assertThrows(IOException.class, () ->
googleStorageConnector.deleteFile(TEST_FILE));
+ Assert.assertEquals(BUCKET, bucketCapture.getValue());
+ Assert.assertEquals(PREFIX + "/" + TEST_FILE, pathCapture.getValue());
+ }
+
+ @Test
+ public void testDeleteFilesSuccess() throws IOException
{
Capture<String> containerCapture = EasyMock.newCapture();
Capture<Iterable<String>> pathsCapture = EasyMock.newCapture();
@@ -125,6 +161,191 @@ public class GoogleStorageConnectorTest
EasyMock.reset(googleStorage);
}
+ @Test
+ public void testDeleteFilesRetrySuccess() throws IOException
+ {
+ Capture<String> containerCapture = EasyMock.newCapture();
+ Capture<Iterable<String>> pathsCapture = EasyMock.newCapture();
+ googleStorage.batchDelete(EasyMock.capture(containerCapture),
EasyMock.capture(pathsCapture));
+
EasyMock.expectLastCall().andThrow(RECOVERABLE_EXCEPTION).once().andVoid().once();
+
+ EasyMock.replay(googleStorage);
+ googleStorageConnector.deleteFiles(ImmutableList.of(TEST_FILE + "_1.part",
TEST_FILE + "_2.json"));
+ Assert.assertEquals(BUCKET, containerCapture.getValue());
+ Assert.assertEquals(
+ ImmutableList.of(
+ PREFIX + "/" + TEST_FILE + "_1.part",
+ PREFIX + "/" + TEST_FILE + "_2.json"
+ ),
+ Lists.newArrayList(pathsCapture.getValue())
+ );
+ EasyMock.reset(googleStorage);
+ }
+
+ @Test
+ public void testDeleteFilesFailure()
+ {
+ Capture<String> containerCapture = EasyMock.newCapture();
+ Capture<Iterable<String>> pathsCapture = EasyMock.newCapture();
+ googleStorage.batchDelete(EasyMock.capture(containerCapture),
EasyMock.capture(pathsCapture));
+
EasyMock.expectLastCall().andThrow(NON_RECOVERABLE_EXCEPTION).once().andVoid().once();
+
+ EasyMock.replay(googleStorage);
+ Assert.assertThrows(
+ IOException.class,
+ () -> googleStorageConnector.deleteFiles(ImmutableList.of(
+ TEST_FILE + "_1.part",
+ TEST_FILE + "_2.json"
+ ))
+ );
+ Assert.assertEquals(BUCKET, containerCapture.getValue());
+ Assert.assertEquals(
+ ImmutableList.of(
+ PREFIX + "/" + TEST_FILE + "_1.part",
+ PREFIX + "/" + TEST_FILE + "_2.json"
+ ),
+ Lists.newArrayList(pathsCapture.getValue())
+ );
+ EasyMock.reset(googleStorage);
+ }
+
+ @Test
+ public void testDeleteFilesRecursivelySuccess() throws IOException
+ {
+
+ GoogleStorageObjectMetadata objectMetadata1 = new
GoogleStorageObjectMetadata(
+ BUCKET,
+ PREFIX + "/x/y/" + TEST_FILE,
+ (long) 3,
+ null
+ );
+
+ GoogleStorageObjectMetadata objectMetadata2 = new
GoogleStorageObjectMetadata(
+ BUCKET,
+ PREFIX + "/p/q/r/" + TEST_FILE,
+ (long) 4,
+ null
+ );
+
+ Capture<Long> maxListingCapture = EasyMock.newCapture();
+ Capture<String> pageTokenCapture = EasyMock.newCapture();
+ EasyMock.expect(googleStorage.list(
+ EasyMock.anyString(),
+ EasyMock.anyString(),
+ EasyMock.capture(maxListingCapture),
+ EasyMock.capture(pageTokenCapture)
+ ))
+ .andReturn(new
GoogleStorageObjectPage(ImmutableList.of(objectMetadata1, objectMetadata2),
null));
+
+
+ Capture<String> containerCapture = EasyMock.newCapture();
+ Capture<Iterable<String>> pathsCapture = EasyMock.newCapture();
+ googleStorage.batchDelete(EasyMock.capture(containerCapture),
EasyMock.capture(pathsCapture));
+ EasyMock.expectLastCall().andVoid().once();
+
+ EasyMock.replay(googleStorage);
+ googleStorageConnector.deleteRecursively("");
+ Assert.assertEquals(BUCKET, containerCapture.getValue());
+ Assert.assertEquals(
+ ImmutableList.of(
+ PREFIX + "/x/y/" + TEST_FILE,
+ PREFIX + "/p/q/r/" + TEST_FILE
+ ),
+ Lists.newArrayList(pathsCapture.getValue())
+ );
+ EasyMock.reset(googleStorage);
+ }
+ @Test
+ public void testDeleteFilesRecursivelyRetrySuccess() throws IOException
+ {
+
+ GoogleStorageObjectMetadata objectMetadata1 = new
GoogleStorageObjectMetadata(
+ BUCKET,
+ PREFIX + "/x/y/" + TEST_FILE,
+ (long) 3,
+ null
+ );
+
+ GoogleStorageObjectMetadata objectMetadata2 = new
GoogleStorageObjectMetadata(
+ BUCKET,
+ PREFIX + "/p/q/r/" + TEST_FILE,
+ (long) 4,
+ null
+ );
+
+ Capture<Long> maxListingCapture = EasyMock.newCapture();
+ Capture<String> pageTokenCapture = EasyMock.newCapture();
+ EasyMock.expect(googleStorage.list(
+ EasyMock.anyString(),
+ EasyMock.anyString(),
+ EasyMock.capture(maxListingCapture),
+ EasyMock.capture(pageTokenCapture)
+ ))
+ .andReturn(new
GoogleStorageObjectPage(ImmutableList.of(objectMetadata1, objectMetadata2),
null));
+
+
+ Capture<String> containerCapture = EasyMock.newCapture();
+ Capture<Iterable<String>> pathsCapture = EasyMock.newCapture();
+ googleStorage.batchDelete(EasyMock.capture(containerCapture),
EasyMock.capture(pathsCapture));
+
EasyMock.expectLastCall().andThrow(RECOVERABLE_EXCEPTION).once().andVoid().once();
+
+ EasyMock.replay(googleStorage);
+ googleStorageConnector.deleteRecursively("");
+ Assert.assertEquals(BUCKET, containerCapture.getValue());
+ Assert.assertEquals(
+ ImmutableList.of(
+ PREFIX + "/x/y/" + TEST_FILE,
+ PREFIX + "/p/q/r/" + TEST_FILE
+ ),
+ Lists.newArrayList(pathsCapture.getValue())
+ );
+ }
+ @Test
+ public void testDeleteFilesRecursivelyFailure() throws IOException
+ {
+
+ GoogleStorageObjectMetadata objectMetadata1 = new
GoogleStorageObjectMetadata(
+ BUCKET,
+ PREFIX + "/x/y/" + TEST_FILE,
+ (long) 3,
+ null
+ );
+
+ GoogleStorageObjectMetadata objectMetadata2 = new
GoogleStorageObjectMetadata(
+ BUCKET,
+ PREFIX + "/p/q/r/" + TEST_FILE,
+ (long) 4,
+ null
+ );
+
+ Capture<Long> maxListingCapture = EasyMock.newCapture();
+ Capture<String> pageTokenCapture = EasyMock.newCapture();
+ EasyMock.expect(googleStorage.list(
+ EasyMock.anyString(),
+ EasyMock.anyString(),
+ EasyMock.capture(maxListingCapture),
+ EasyMock.capture(pageTokenCapture)
+ ))
+ .andReturn(new
GoogleStorageObjectPage(ImmutableList.of(objectMetadata1, objectMetadata2),
null));
+
+
+ Capture<String> containerCapture = EasyMock.newCapture();
+ Capture<Iterable<String>> pathsCapture = EasyMock.newCapture();
+ googleStorage.batchDelete(EasyMock.capture(containerCapture),
EasyMock.capture(pathsCapture));
+
EasyMock.expectLastCall().andThrow(NON_RECOVERABLE_EXCEPTION).once().andVoid().once();
+
+ EasyMock.replay(googleStorage);
+ Assert.assertThrows(IOException.class, () ->
googleStorageConnector.deleteRecursively(""));
+ Assert.assertEquals(BUCKET, containerCapture.getValue());
+ Assert.assertEquals(
+ ImmutableList.of(
+ PREFIX + "/x/y/" + TEST_FILE,
+ PREFIX + "/p/q/r/" + TEST_FILE
+ ),
+ Lists.newArrayList(pathsCapture.getValue())
+ );
+ }
+
@Test
public void testListDir() throws IOException
{
diff --git
a/integration-tests-ex/cases/src/test/java/org/apache/druid/testsEx/utils/GcsTestUtil.java
b/integration-tests-ex/cases/src/test/java/org/apache/druid/testsEx/utils/GcsTestUtil.java
index 9c44d5b1439..ec67269e46d 100644
---
a/integration-tests-ex/cases/src/test/java/org/apache/druid/testsEx/utils/GcsTestUtil.java
+++
b/integration-tests-ex/cases/src/test/java/org/apache/druid/testsEx/utils/GcsTestUtil.java
@@ -99,7 +99,7 @@ public class GcsTestUtil
);
}
- public void deleteFileFromGcs(String gcsObjectName) throws IOException
+ public void deleteFileFromGcs(String gcsObjectName)
{
LOG.info("Deleting object %s at path %s in bucket %s", gcsObjectName,
GOOGLE_PREFIX, GOOGLE_BUCKET);
googleStorageClient.delete(GOOGLE_BUCKET, GOOGLE_PREFIX + "/" +
gcsObjectName);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]