You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by ga...@apache.org on 2013/09/22 06:29:37 UTC
git commit: JCLOUDS-251: Swift: Delete chunks when deleting a
multipart blob
Updated Branches:
refs/heads/1.6.x d6bb789ef -> 44e848723
JCLOUDS-251: Swift: Delete chunks when deleting a multipart blob
Also:
- Make SwiftBlobIntegrationLiveTest.testMultipartChunkedFileStream more realistic by uploading a file large enough to be split into parts.
- JavaDoc fixes for SwiftBlobStore: don't reference nonexistent methods.
Project: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/commit/44e84872
Tree: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/tree/44e84872
Diff: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/diff/44e84872
Branch: refs/heads/1.6.x
Commit: 44e84872300915fc36fe0a1e1abf2f436e62aa70
Parents: d6bb789
Author: Francis Devereux <fr...@bright-interactive.co.uk>
Authored: Thu Aug 22 15:27:25 2013 +0100
Committer: Andrew Gaul <ga...@apache.org>
Committed: Sat Sep 21 20:35:04 2013 -0700
----------------------------------------------------------------------
.../CloudFilesBlobIntegrationLiveTest.java | 23 +++++++
.../swift/blobstore/SwiftBlobStore.java | 56 ++++++++++++++-
.../domain/MutableObjectInfoWithMetadata.java | 3 +
...DelegatingMutableObjectInfoWithMetadata.java | 10 +++
.../MutableObjectInfoWithMetadataImpl.java | 21 +++++-
.../functions/ParseObjectInfoFromHeaders.java | 2 +
.../swift/blobstore/SwiftBlobStoreTest.java | 45 +++++++++++++
.../SwiftBlobIntegrationLiveTest.java | 71 ++++++++++++++++----
.../internal/BaseBlobIntegrationTest.java | 7 ++
9 files changed, 220 insertions(+), 18 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/44e84872/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/integration/CloudFilesBlobIntegrationLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/integration/CloudFilesBlobIntegrationLiveTest.java b/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/integration/CloudFilesBlobIntegrationLiveTest.java
index a56e9f8..3d7f42b 100644
--- a/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/integration/CloudFilesBlobIntegrationLiveTest.java
+++ b/apis/cloudfiles/src/test/java/org/jclouds/cloudfiles/blobstore/integration/CloudFilesBlobIntegrationLiveTest.java
@@ -16,10 +16,15 @@
*/
package org.jclouds.cloudfiles.blobstore.integration;
+import java.io.IOException;
+
+import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.openstack.swift.blobstore.integration.SwiftBlobIntegrationLiveTest;
import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+
/**
*
* @author Adrian Cole
@@ -38,4 +43,22 @@ public class CloudFilesBlobIntegrationLiveTest extends SwiftBlobIntegrationLiveT
.getMetadata().getContentMetadata().getContentDisposition();
}
+ @Test(groups = { "integration", "live" })
+ public void testChunksAreDeletedWhenMultipartBlobIsDeleted() throws IOException, InterruptedException {
+ String containerName = getContainerName();
+ try {
+ BlobStore blobStore = view.getBlobStore();
+
+ long countBefore = blobStore.countBlobs(containerName);
+ String blobName = "deleteme.txt";
+ addMultipartBlobToContainer(containerName, blobName);
+
+ blobStore.removeBlob(containerName, blobName);
+ long countAfter = blobStore.countBlobs(containerName);
+
+ assertEquals(countAfter, countBefore);
+ } finally {
+ returnContainer(containerName);
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/44e84872/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/SwiftBlobStore.java
----------------------------------------------------------------------
diff --git a/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/SwiftBlobStore.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/SwiftBlobStore.java
index 8c0ac70..2940d6f 100644
--- a/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/SwiftBlobStore.java
+++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/SwiftBlobStore.java
@@ -16,8 +16,10 @@
*/
package org.jclouds.openstack.swift.blobstore;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.blobstore.util.BlobStoreUtils.createParentIfNeededAsync;
+import static org.jclouds.openstack.swift.options.ListContainerOptions.Builder.withPrefix;
import java.util.Set;
@@ -25,6 +27,7 @@ import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
+import com.google.common.annotations.VisibleForTesting;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.BlobMetadata;
@@ -50,8 +53,11 @@ import org.jclouds.openstack.swift.blobstore.functions.ObjectToBlob;
import org.jclouds.openstack.swift.blobstore.functions.ObjectToBlobMetadata;
import org.jclouds.openstack.swift.blobstore.strategy.internal.MultipartUploadStrategy;
import org.jclouds.openstack.swift.domain.ContainerMetadata;
+import org.jclouds.openstack.swift.domain.MutableObjectInfoWithMetadata;
+import org.jclouds.openstack.swift.domain.ObjectInfo;
import com.google.common.base.Function;
+import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
@@ -118,7 +124,7 @@ public class SwiftBlobStore extends BaseBlobStore {
}
/**
- * This implementation invokes {@link CommonSwiftClient#putBucketInRegion}
+ * This implementation invokes {@link CommonSwiftClient#createContainer}
*
* @param location
* currently ignored
@@ -145,7 +151,7 @@ public class SwiftBlobStore extends BaseBlobStore {
}
/**
- * This implementation invokes {@link CommonSwiftClient#blobExists}
+ * This implementation invokes {@link CommonSwiftClient#objectExists}
*
* @param container
* container name
@@ -225,7 +231,53 @@ public class SwiftBlobStore extends BaseBlobStore {
*/
@Override
public void removeBlob(String container, String key) {
+ String objectManifest = getObjectManifestOrNull(container, key);
+
sync.removeObject(container, key);
+
+ if (!Strings.isNullOrEmpty(objectManifest)) {
+ removeObjectsWithPrefix(objectManifest);
+ }
+ }
+
+ private String getObjectManifestOrNull(String container, String key) {
+ MutableObjectInfoWithMetadata objectInfo = sync.getObjectInfo(container, key);
+ return objectInfo == null ? null : objectInfo.getObjectManifest();
+ }
+
+ private void removeObjectsWithPrefix(String containerAndPrefix) {
+ String[] parts = splitContainerAndKey(containerAndPrefix);
+
+ String container = parts[0];
+ String prefix = parts[1];
+
+ removeObjectsWithPrefix(container, prefix);
+ }
+
+ @VisibleForTesting
+ static String[] splitContainerAndKey(String containerAndKey) {
+ String[] parts = containerAndKey.split("/", 2);
+ checkArgument(parts.length == 2,
+ "No / separator found in \"%s\"",
+ containerAndKey);
+ return parts;
+ }
+
+ private void removeObjectsWithPrefix(String container, String prefix) {
+ String nextMarker = null;
+ do {
+ org.jclouds.openstack.swift.options.ListContainerOptions listContainerOptions =
+ withPrefix(prefix);
+ if (nextMarker != null) {
+ listContainerOptions = listContainerOptions.afterMarker(nextMarker);
+ }
+
+ PageSet<ObjectInfo> chunks = sync.listObjects(container, listContainerOptions);
+ for (ObjectInfo chunk : chunks) {
+ sync.removeObject(container, chunk.getName());
+ }
+ nextMarker = chunks.getNextMarker();
+ } while (nextMarker != null);
}
@Override
http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/44e84872/apis/swift/src/main/java/org/jclouds/openstack/swift/domain/MutableObjectInfoWithMetadata.java
----------------------------------------------------------------------
diff --git a/apis/swift/src/main/java/org/jclouds/openstack/swift/domain/MutableObjectInfoWithMetadata.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/domain/MutableObjectInfoWithMetadata.java
index ee326a4..103292f 100644
--- a/apis/swift/src/main/java/org/jclouds/openstack/swift/domain/MutableObjectInfoWithMetadata.java
+++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/domain/MutableObjectInfoWithMetadata.java
@@ -48,4 +48,7 @@ public interface MutableObjectInfoWithMetadata extends ObjectInfo {
Map<String, String> getMetadata();
+ String getObjectManifest();
+
+ void setObjectManifest(String objectManifest);
}
http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/44e84872/apis/swift/src/main/java/org/jclouds/openstack/swift/domain/internal/DelegatingMutableObjectInfoWithMetadata.java
----------------------------------------------------------------------
diff --git a/apis/swift/src/main/java/org/jclouds/openstack/swift/domain/internal/DelegatingMutableObjectInfoWithMetadata.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/domain/internal/DelegatingMutableObjectInfoWithMetadata.java
index 57d3444..9b57686 100644
--- a/apis/swift/src/main/java/org/jclouds/openstack/swift/domain/internal/DelegatingMutableObjectInfoWithMetadata.java
+++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/domain/internal/DelegatingMutableObjectInfoWithMetadata.java
@@ -147,4 +147,14 @@ public class DelegatingMutableObjectInfoWithMetadata extends BaseMutableContentM
public URI getUri() {
return delegate.getUri();
}
+
+ @Override
+ public void setObjectManifest(String objectManifest) {
+ delegate.setObjectManifest(objectManifest);
+ }
+
+ @Override
+ public String getObjectManifest() {
+ return delegate.getObjectManifest();
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/44e84872/apis/swift/src/main/java/org/jclouds/openstack/swift/domain/internal/MutableObjectInfoWithMetadataImpl.java
----------------------------------------------------------------------
diff --git a/apis/swift/src/main/java/org/jclouds/openstack/swift/domain/internal/MutableObjectInfoWithMetadataImpl.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/domain/internal/MutableObjectInfoWithMetadataImpl.java
index dc09819..484888d 100644
--- a/apis/swift/src/main/java/org/jclouds/openstack/swift/domain/internal/MutableObjectInfoWithMetadataImpl.java
+++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/domain/internal/MutableObjectInfoWithMetadataImpl.java
@@ -41,6 +41,7 @@ public class MutableObjectInfoWithMetadataImpl implements MutableObjectInfoWithM
private byte[] hash;
private String contentType = MediaType.APPLICATION_OCTET_STREAM;
private Date lastModified;
+ private String objectManifest;
private final Map<String, String> metadata = Maps.newLinkedHashMap();
/**
@@ -121,6 +122,7 @@ public class MutableObjectInfoWithMetadataImpl implements MutableObjectInfoWithM
int result = 1;
result = prime * result + ((container == null) ? 0 : container.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
+ result = prime * result + ((objectManifest == null) ? 0 : objectManifest.hashCode());
return result;
}
@@ -143,6 +145,11 @@ public class MutableObjectInfoWithMetadataImpl implements MutableObjectInfoWithM
return false;
} else if (!name.equals(other.name))
return false;
+ if (objectManifest == null) {
+ if (other.objectManifest != null)
+ return false;
+ } else if (!objectManifest.equals(other.objectManifest))
+ return false;
return true;
}
@@ -197,9 +204,19 @@ public class MutableObjectInfoWithMetadataImpl implements MutableObjectInfoWithM
}
@Override
+ public String getObjectManifest() {
+ return objectManifest;
+ }
+
+ @Override
+ public void setObjectManifest(String objectManifest) {
+ this.objectManifest = objectManifest;
+ }
+
+ @Override
public String toString() {
- return String.format("[name=%s, container=%s, uri=%s, bytes=%s, contentType=%s, lastModified=%s, hash=%s]", name,
- container, uri, bytes, contentType, lastModified, Arrays.toString(hash));
+ return String.format("[name=%s, container=%s, uri=%s, bytes=%s, contentType=%s, lastModified=%s, hash=%s, objectManifest=%s]",
+ name, container, uri, bytes, contentType, lastModified, Arrays.toString(hash), objectManifest);
}
}
http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/44e84872/apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeaders.java
----------------------------------------------------------------------
diff --git a/apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeaders.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeaders.java
index bd082fe..d9f0297 100644
--- a/apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeaders.java
+++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeaders.java
@@ -65,6 +65,8 @@ public class ParseObjectInfoFromHeaders implements Function<HttpResponse, Mutabl
if (eTagHeader != null) {
to.setHash(ETagUtils.convertHexETagToByteArray(eTagHeader));
}
+ to.setObjectManifest(from.getFirstHeaderOrNull("X-Object-Manifest"));
+
return to;
}
http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/44e84872/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/SwiftBlobStoreTest.java
----------------------------------------------------------------------
diff --git a/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/SwiftBlobStoreTest.java b/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/SwiftBlobStoreTest.java
new file mode 100644
index 0000000..439d7e0
--- /dev/null
+++ b/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/SwiftBlobStoreTest.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.openstack.swift.blobstore;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+@Test(groups = "unit")
+public class SwiftBlobStoreTest {
+ @Test
+ public void testSplitContainerAndKey() {
+ String container = "test-container";
+ String key = "key/with/some/slashes/in/it/and/a/trailing/slash/";
+
+ String containerAndKey = container + "/" + key;
+
+ String[] split = SwiftBlobStore.splitContainerAndKey(containerAndKey);
+ String actualContainer = split[0];
+ String actualKey = split[1];
+
+ assertEquals(actualContainer, container);
+ assertEquals(actualKey, key);
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class,
+ expectedExceptionsMessageRegExp = "No / separator found in \"not-a-container-and-key\"")
+ public void testSplitContainerAndKeyWithNoSeparator() {
+ SwiftBlobStore.splitContainerAndKey("not-a-container-and-key");
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/44e84872/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftBlobIntegrationLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftBlobIntegrationLiveTest.java b/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftBlobIntegrationLiveTest.java
index 819fe83..c331bcf 100644
--- a/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftBlobIntegrationLiveTest.java
+++ b/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/integration/SwiftBlobIntegrationLiveTest.java
@@ -21,11 +21,13 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
+import com.google.common.io.ByteStreams;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.integration.internal.BaseBlobIntegrationTest;
import org.jclouds.blobstore.options.PutOptions;
import org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties;
+import org.jclouds.openstack.swift.blobstore.strategy.MultipartUpload;
import org.testng.ITestContext;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
@@ -34,6 +36,9 @@ import org.testng.annotations.Test;
import com.google.common.io.Files;
import com.google.common.io.InputSupplier;
+import static org.testng.Assert.assertNotEquals;
+import static org.testng.Assert.assertTrue;
+
/**
*
* @author James Murty
@@ -41,15 +46,21 @@ import com.google.common.io.InputSupplier;
*/
@Test(groups = "live")
public class SwiftBlobIntegrationLiveTest extends BaseBlobIntegrationTest {
+ /**
+ * Use the minimum part size to minimise the file size that we have to
+ * upload to get a multipart blob thereby make the test run faster
+ */
+ private static final long PART_SIZE = MultipartUpload.MIN_PART_SIZE;
+
@Override
protected Properties setupProperties() {
Properties props = super.setupProperties();
setIfTestSystemPropertyPresent(props, KeystoneProperties.CREDENTIAL_TYPE);
+ props.setProperty("jclouds.mpu.parts.size", String.valueOf(PART_SIZE));
return props;
}
private InputSupplier<InputStream> oneHundredOneConstitutions;
- private byte[] oneHundredOneConstitutionsMD5;
public SwiftBlobIntegrationLiveTest() {
provider = System.getProperty("test.swift.provider", "swift");
@@ -66,7 +77,6 @@ public class SwiftBlobIntegrationLiveTest extends BaseBlobIntegrationTest {
public void setUpResourcesOnThisThread(ITestContext testContext) throws Exception {
super.setUpResourcesOnThisThread(testContext);
oneHundredOneConstitutions = getTestDataSupplier();
- oneHundredOneConstitutionsMD5 = md5Supplier(oneHundredOneConstitutions);
}
@Override
@@ -91,19 +101,52 @@ public class SwiftBlobIntegrationLiveTest extends BaseBlobIntegrationTest {
{ "asteri*k" }, { "{great<r}" }, { "lesst>en" }, { "p|pe" } };
}
+ @Test(groups = { "integration", "live" })
public void testMultipartChunkedFileStream() throws IOException, InterruptedException {
- Files.copy(oneHundredOneConstitutions, new File("target/const.txt"));
- String containerName = getContainerName();
-
- try {
- BlobStore blobStore = view.getBlobStore();
- blobStore.createContainerInLocation(null, containerName);
- Blob blob = blobStore.blobBuilder("const.txt")
- .payload(new File("target/const.txt")).contentMD5(oneHundredOneConstitutionsMD5).build();
- blobStore.putBlob(containerName, blob, PutOptions.Builder.multipart());
- } finally {
- returnContainer(containerName);
- }
+ String containerName = getContainerName();
+ try {
+ BlobStore blobStore = view.getBlobStore();
+ long countBefore = blobStore.countBlobs(containerName);
+
+ addMultipartBlobToContainer(containerName, "const.txt");
+
+ long countAfter = blobStore.countBlobs(containerName);
+ assertNotEquals(countBefore, countAfter,
+ "No blob was created");
+ assertTrue(countAfter - countBefore > 1,
+ "A multipart blob wasn't actually created - " +
+ "there was only 1 extra blob but there should be one manifest blob and multiple chunk blobs");
+ } finally {
+ returnContainer(containerName);
+ }
+ }
+
+ protected void addMultipartBlobToContainer(String containerName, String key) throws IOException {
+ File fileToUpload = createFileBiggerThan(PART_SIZE);
+
+ BlobStore blobStore = view.getBlobStore();
+ blobStore.createContainerInLocation(null, containerName);
+ Blob blob = blobStore.blobBuilder(key)
+ .payload(fileToUpload)
+ .build();
+ blobStore.putBlob(containerName, blob, PutOptions.Builder.multipart());
+ }
+
+ @SuppressWarnings("unchecked")
+ private File createFileBiggerThan(long partSize) throws IOException {
+ long copiesNeeded = (partSize / getOneHundredOneConstitutionsLength()) + 1;
+
+ InputSupplier<InputStream> temp = ByteStreams.join(oneHundredOneConstitutions);
+
+ for (int i = 0; i < copiesNeeded; i++) {
+ temp = ByteStreams.join(temp, oneHundredOneConstitutions);
+ }
+
+ File fileToUpload = new File("target/lots-of-const.txt");
+ Files.copy(temp, fileToUpload);
+
+ assertTrue(fileToUpload.length() > partSize);
+ return fileToUpload;
}
@Override
http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/44e84872/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
----------------------------------------------------------------------
diff --git a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
index d96393e..5e5d3c9 100644
--- a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
+++ b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
@@ -122,6 +122,13 @@ public class BaseBlobIntegrationTest extends BaseBlobStoreIntegrationTest {
return temp;
}
+ public static long getOneHundredOneConstitutionsLength() throws IOException {
+ if (oneHundredOneConstitutionsLength == 0) {
+ getTestDataSupplier();
+ }
+ return oneHundredOneConstitutionsLength;
+ }
+
/**
* Attempt to capture the issue detailed in
* http://groups.google.com/group/jclouds/browse_thread/thread/4a7c8d58530b287f