Recurse in subdirectories before deleting blobs

This ensures that we have at most one PageSet of Futures awaiting
completion.
This commit is contained in:
Andrew Gaul 2012-04-26 18:07:52 -07:00 committed by Adrian Cole
parent bc302adb92
commit 1cab7fd07b
2 changed files with 74 additions and 63 deletions

View File

@ -93,9 +93,9 @@ public class DeleteAllKeysInList implements ClearListStrategy, ClearContainerStr
message = message + " recursively"; message = message + " recursively";
Map<StorageMetadata, Exception> exceptions = Maps.newHashMap(); Map<StorageMetadata, Exception> exceptions = Maps.newHashMap();
PageSet<? extends StorageMetadata> listing; PageSet<? extends StorageMetadata> listing;
Iterable<? extends StorageMetadata> toDelete;
int maxErrors = 3; // TODO parameterize int maxErrors = 3; // TODO parameterize
for (int i = 0; i < maxErrors; ) { for (int i = 0; i < maxErrors; ) {
// fetch partial directory listing
try { try {
listing = connection.list(containerName, options).get(); listing = connection.list(containerName, options).get();
} catch (ExecutionException ee) { } catch (ExecutionException ee) {
@ -108,11 +108,30 @@ public class DeleteAllKeysInList implements ClearListStrategy, ClearContainerStr
} catch (InterruptedException ie) { } catch (InterruptedException ie) {
throw Throwables.propagate(ie); throw Throwables.propagate(ie);
} }
toDelete = filterListing(listing, options);
// recurse on subdirectories
if (options.isRecursive()) {
for (StorageMetadata md : listing) {
String fullPath = parentIsFolder(options, md) ? options.getDir() + "/"
+ md.getName() : md.getName();
switch (md.getType()) {
case BLOB:
break;
case FOLDER:
case RELATIVE_PATH:
if (options.isRecursive() && !fullPath.equals(options.getDir())) {
execute(containerName, options.clone().inDirectory(fullPath));
}
break;
case CONTAINER:
throw new IllegalArgumentException("Container type not supported");
}
}
}
// remove blobs and now-empty subdirectories
Map<StorageMetadata, Future<?>> responses = Maps.newHashMap(); Map<StorageMetadata, Future<?>> responses = Maps.newHashMap();
try { for (StorageMetadata md : listing) {
for (final StorageMetadata md : toDelete) {
String fullPath = parentIsFolder(options, md) ? options.getDir() + "/" String fullPath = parentIsFolder(options, md) ? options.getDir() + "/"
+ md.getName() : md.getName(); + md.getName() : md.getName();
switch (md.getType()) { switch (md.getType()) {
@ -120,24 +139,21 @@ public class DeleteAllKeysInList implements ClearListStrategy, ClearContainerStr
responses.put(md, connection.removeBlob(containerName, fullPath)); responses.put(md, connection.removeBlob(containerName, fullPath));
break; break;
case FOLDER: case FOLDER:
if (options.isRecursive() && !fullPath.equals(options.getDir())) { if (options.isRecursive()) {
execute(containerName, options.clone().inDirectory(fullPath));
}
responses.put(md, connection.deleteDirectory(containerName, fullPath)); responses.put(md, connection.deleteDirectory(containerName, fullPath));
}
break; break;
case RELATIVE_PATH: case RELATIVE_PATH:
if (options.isRecursive() && !fullPath.equals(options.getDir())) { if (options.isRecursive()) {
execute(containerName, options.clone().inDirectory(fullPath));
}
responses.put(md, connection.deleteDirectory(containerName, md.getName())); responses.put(md, connection.deleteDirectory(containerName, md.getName()));
}
break; break;
case CONTAINER: case CONTAINER:
throw new IllegalArgumentException("Container type not supported"); throw new IllegalArgumentException("Container type not supported");
} }
} }
} finally {
exceptions = awaitCompletion(responses, userExecutor, maxTime, logger, message); exceptions = awaitCompletion(responses, userExecutor, maxTime, logger, message);
}
if (!exceptions.isEmpty()) { if (!exceptions.isEmpty()) {
++i; ++i;
retryHandler.imposeBackoffExponentialDelay(i, message); retryHandler.imposeBackoffExponentialDelay(i, message);
@ -157,29 +173,4 @@ public class DeleteAllKeysInList implements ClearListStrategy, ClearContainerStr
private boolean parentIsFolder(final ListContainerOptions options, final StorageMetadata md) { private boolean parentIsFolder(final ListContainerOptions options, final StorageMetadata md) {
return (options.getDir() != null && md.getName().indexOf('/') == -1); return (options.getDir() != null && md.getName().indexOf('/') == -1);
} }
private Iterable<? extends StorageMetadata> filterListing(
final PageSet<? extends StorageMetadata> listing,
final ListContainerOptions options) {
Iterable<? extends StorageMetadata> toDelete = Iterables.filter(listing,
new Predicate<StorageMetadata>() {
@Override
public boolean apply(StorageMetadata input) {
switch (input.getType()) {
case BLOB:
return true;
case FOLDER:
case RELATIVE_PATH:
if (options.isRecursive())
return true;
break;
}
return false;
}
});
return toDelete;
}
} }

View File

@ -23,8 +23,8 @@ import static org.testng.Assert.assertEquals;
import org.jclouds.ContextBuilder; import org.jclouds.ContextBuilder;
import org.jclouds.blobstore.BlobStore; import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.options.ListContainerOptions; import org.jclouds.blobstore.options.ListContainerOptions;
import org.testng.annotations.AfterClass; import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.io.Closeables; import com.google.common.io.Closeables;
@ -38,40 +38,60 @@ import com.google.inject.Injector;
public class DeleteAllKeysInListTest { public class DeleteAllKeysInListTest {
private BlobStore blobstore; private BlobStore blobstore;
private DeleteAllKeysInList deleter; private DeleteAllKeysInList deleter;
private static final String containerName = "container";
private static final String directoryName = "directory";
@BeforeClass @BeforeMethod
void setupBlobStore() { void setupBlobStore() {
Injector injector = ContextBuilder.newBuilder("transient").buildInjector(); Injector injector = ContextBuilder.newBuilder("transient").buildInjector();
blobstore = injector.getInstance(BlobStore.class); blobstore = injector.getInstance(BlobStore.class);
deleter = injector.getInstance(DeleteAllKeysInList.class); deleter = injector.getInstance(DeleteAllKeysInList.class);
createDataSet();
}
@AfterMethod
void close() {
Closeables.closeQuietly(blobstore.getContext());
} }
public void testExecuteWithoutOptionsClearsRecursively() { public void testExecuteWithoutOptionsClearsRecursively() {
blobstore.createContainerInLocation(null, "goodies"); deleter.execute(containerName);
for (int i = 0; i < 1001; i++) { assertEquals(blobstore.countBlobs(containerName), 0);
blobstore.putBlob("goodies", blobstore.blobBuilder(i + "").payload(i + "").build());
} }
assertEquals(blobstore.countBlobs("goodies"), 1001);
deleter.execute("goodies"); public void testExecuteRecursive() {
assertEquals(blobstore.countBlobs("goodies"), 0); deleter.execute(containerName, ListContainerOptions.Builder.recursive());
assertEquals(blobstore.countBlobs(containerName), 0);
} }
public void testExecuteNonRecursive() { public void testExecuteNonRecursive() {
blobstore.createContainerInLocation(null, "foo"); deleter.execute(containerName, ListContainerOptions.NONE);
for (int i = 0; i < 1001; i++) { assertEquals(blobstore.countBlobs(containerName), 2222);
blobstore.putBlob("foo", blobstore.blobBuilder(i + "").payload(i + "").build());
}
for (int i = 0; i < 1001; i++) {
blobstore.putBlob("foo", blobstore.blobBuilder("dir/" + i + "").payload(i + "").build());
}
assertEquals(blobstore.countBlobs("foo"), 2002);
deleter.execute("foo", ListContainerOptions.Builder.inDirectory("dir"));
assertEquals(blobstore.countBlobs("foo"), 1001);
} }
@AfterClass public void testExecuteInDirectory() {
void close() { deleter.execute(containerName, ListContainerOptions.Builder.inDirectory(directoryName));
if (blobstore != null) assertEquals(blobstore.countBlobs(containerName), 1111);
Closeables.closeQuietly(blobstore.getContext()); }
/**
* Create a container "container" with 1111 blobs named "blob-%d". Create a
* subdirectory "directory" which contains 2222 more blobs named
* "directory/blob-%d".
*/
private void createDataSet() {
String blobNameFmt = "blob-%d";
String directoryBlobNameFmt = "%s/blob-%d";
blobstore.createContainerInLocation(null, containerName);
for (int i = 0; i < 1111; i++) {
String blobName = String.format(blobNameFmt, i);
blobstore.putBlob(containerName, blobstore.blobBuilder(blobName).payload(blobName).build());
}
for (int i = 0; i < 2222; i++) {
String directoryBlobName = String.format(directoryBlobNameFmt, directoryName, i);
blobstore.putBlob(containerName, blobstore.blobBuilder(directoryBlobName).payload(directoryBlobName).build());
}
assertEquals(blobstore.countBlobs(containerName), 3333);
} }
} }