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 2014/07/04 10:11:05 UTC

git commit: JCLOUDS-619: Introduce MultipartNamingStrategy to generate part names correctly.

Repository: jclouds
Updated Branches:
  refs/heads/master 05c37c2c7 -> a39eadce5


JCLOUDS-619: Introduce MultipartNamingStrategy to generate part names correctly.


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/a39eadce
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/a39eadce
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/a39eadce

Branch: refs/heads/master
Commit: a39eadce50e3bc37abc0e47564ef1698529bec81
Parents: 05c37c2
Author: Markus von RĂ¼den <mv...@opennms.com>
Authored: Tue Jul 1 14:24:50 2014 +0200
Committer: Andrew Gaul <ga...@apache.org>
Committed: Fri Jul 4 01:06:46 2014 -0700

----------------------------------------------------------------------
 .../internal/MultipartNamingStrategy.java       | 30 ++++++++
 .../SequentialMultipartUploadStrategy.java      | 11 +--
 .../SwiftBlobIntegrationLiveTest.java           | 73 ++++++++++++++++----
 .../internal/MultipartNamingStrategyTest.java   | 71 +++++++++++++++++++
 4 files changed, 169 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/a39eadce/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/strategy/internal/MultipartNamingStrategy.java
----------------------------------------------------------------------
diff --git a/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/strategy/internal/MultipartNamingStrategy.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/strategy/internal/MultipartNamingStrategy.java
new file mode 100644
index 0000000..7ffebc5
--- /dev/null
+++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/strategy/internal/MultipartNamingStrategy.java
@@ -0,0 +1,30 @@
+/*
+ * 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.strategy.internal;
+
+import javax.inject.Singleton;
+
+@Singleton
+public class MultipartNamingStrategy {
+
+   private static final String PART_SEPARATOR = "/";
+
+   protected String getPartName(String key, int partNumber, int totalParts) {
+      int base = (int) Math.log10(totalParts) + 1;
+      return String.format("%s%s%0" + base + "d", key, PART_SEPARATOR, partNumber);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a39eadce/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/strategy/internal/SequentialMultipartUploadStrategy.java
----------------------------------------------------------------------
diff --git a/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/strategy/internal/SequentialMultipartUploadStrategy.java b/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/strategy/internal/SequentialMultipartUploadStrategy.java
index 1874ccd..5f21ddc 100644
--- a/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/strategy/internal/SequentialMultipartUploadStrategy.java
+++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/strategy/internal/SequentialMultipartUploadStrategy.java
@@ -34,7 +34,6 @@ import org.jclouds.openstack.swift.blobstore.functions.BlobToObject;
 import com.google.inject.Inject;
 
 public class SequentialMultipartUploadStrategy implements MultipartUploadStrategy {
-   private static final String PART_SEPARATOR = "/";
 
    @Resource
    @Named(BlobStoreConstants.BLOBSTORE_LOGGER)
@@ -45,15 +44,18 @@ public class SequentialMultipartUploadStrategy implements MultipartUploadStrateg
    private final BlobToObject blob2Object;
    private final MultipartUploadSlicingAlgorithm algorithm;
    private final PayloadSlicer slicer;
+   private final MultipartNamingStrategy namingStrategy;
 
    @Inject
    public SequentialMultipartUploadStrategy(CommonSwiftClient client, Provider<BlobBuilder> blobBuilders,
-         BlobToObject blob2Object, MultipartUploadSlicingAlgorithm algorithm, PayloadSlicer slicer) {
+         BlobToObject blob2Object, MultipartUploadSlicingAlgorithm algorithm, PayloadSlicer slicer,
+         MultipartNamingStrategy namingStrategy) {
       this.client = checkNotNull(client, "client");
       this.blobBuilders = checkNotNull(blobBuilders, "blobBuilders");
       this.blob2Object = checkNotNull(blob2Object, "blob2Object");
       this.algorithm = checkNotNull(algorithm, "algorithm");
       this.slicer = checkNotNull(slicer, "slicer");
+      this.namingStrategy = checkNotNull(namingStrategy, "namingStrategy");
    }
 
    @Override
@@ -68,10 +70,11 @@ public class SequentialMultipartUploadStrategy implements MultipartUploadStrateg
       if (partCount > 0) {
          for (Payload part : slicer.slice(payload, chunkSize)) {
             int partNum = algorithm.getNextPart();
+            String partName = namingStrategy.getPartName(key, partNum, partCount);
             Blob blobPart = blobBuilders.get()
-                                        .name(key + PART_SEPARATOR + partNum)
+                                        .name(partName)
                                         .payload(part)
-                                        .contentDisposition(key + PART_SEPARATOR + partNum)
+                                        .contentDisposition(partName)
                                         .build();
             client.putObject(container, blob2Object.apply(blobPart));
          }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a39eadce/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 fca7145..ecd2370 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
@@ -25,11 +25,14 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Properties;
+import java.util.Random;
 
 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.io.ByteSources;
+import org.jclouds.io.Payload;
 import org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties;
 import org.jclouds.openstack.swift.blobstore.strategy.MultipartUpload;
 import org.testng.ITestContext;
@@ -76,18 +79,18 @@ public class SwiftBlobIntegrationLiveTest extends BaseBlobIntegrationTest {
       throw new SkipException("not yet implemented");
    }
 
-    @BeforeClass(groups = { "integration", "live" }, dependsOnMethods = "setupContext")
-    @Override
-    public void setUpResourcesOnThisThread(ITestContext testContext) throws Exception {
-        super.setUpResourcesOnThisThread(testContext);
-        oneHundredOneConstitutions = getTestDataSupplier();
-    }
+   @BeforeClass(groups = {"integration", "live"}, dependsOnMethods = "setupContext")
+   @Override
+   public void setUpResourcesOnThisThread(ITestContext testContext) throws Exception {
+      super.setUpResourcesOnThisThread(testContext);
+      oneHundredOneConstitutions = getTestDataSupplier();
+   }
 
    @Override
    protected void checkContentDisposition(Blob blob, String contentDisposition) {
-     // This works for Swift Server 1.4.4/SWauth 1.0.3 but was null in previous versions.
-     // TODO: Better testing for the different versions.
-     super.checkContentDisposition(blob, contentDisposition);
+      // This works for Swift Server 1.4.4/SWauth 1.0.3 but was null in previous versions.
+      // TODO: Better testing for the different versions.
+      super.checkContentDisposition(blob, contentDisposition);
    }
 
    // not supported in swift
@@ -121,7 +124,40 @@ public class SwiftBlobIntegrationLiveTest extends BaseBlobIntegrationTest {
                     "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);
+         returnContainer(containerName);
+      }
+   }
+
+   /**
+    * Checks that when there are more than 9 chunks the object names
+    * are set correctly so that the order of the object names matches
+    * the upload order.
+    * See issue https://issues.apache.org/jira/browse/JCLOUDS-619
+    */
+   @Test(groups = {"integration", "live"})
+   public void testMultipartChunkedFilenames() throws InterruptedException, IOException {
+      String containerName = getContainerName();
+      try {
+         BlobStore blobStore = view.getBlobStore();
+         String objectName = "object.txt";
+         long countBefore = blobStore.countBlobs(containerName);
+
+         // we want 11 parts
+         ByteSource inputSource = createByteSource(PART_SIZE * 11);
+         addMultipartBlobToContainer(containerName, objectName, inputSource);
+
+         // did we create enough parts?
+         long countAfter = blobStore.countBlobs(containerName);
+         assertNotEquals(countAfter, countBefore, "No blob was created");
+         assertEquals(countAfter, countBefore + 12,
+                 "12 parts (11 objects + 1 manifest) were expected.");
+
+         // download and check if correct
+         Blob read = blobStore.getBlob(containerName, objectName);
+         Payload readPayload = read.getPayload();
+         assertTrue(inputSource.contentEquals(ByteSources.asByteSource(readPayload.openStream())));
+      } finally {
+         returnContainer(containerName);
       }
    }
 
@@ -158,15 +194,28 @@ public class SwiftBlobIntegrationLiveTest extends BaseBlobIntegrationTest {
 
    protected void addMultipartBlobToContainer(String containerName, String key) throws IOException {
       File fileToUpload = createFileBiggerThan(PART_SIZE);
+      addMultipartBlobToContainer(containerName, key, Files.asByteSource(fileToUpload));
+   }
 
+   protected void addMultipartBlobToContainer(String containerName, String key, ByteSource byteSource) throws IOException {
       BlobStore blobStore = view.getBlobStore();
       blobStore.createContainerInLocation(null, containerName);
       Blob blob = blobStore.blobBuilder(key)
-         .payload(fileToUpload)
-         .build();
+              .payload(byteSource)
+              .contentLength(byteSource.size())
+              .build();
       blobStore.putBlob(containerName, blob, PutOptions.Builder.multipart());
    }
 
+   private ByteSource createByteSource(long size) throws IOException {
+      final Random random = new Random();
+      final byte[] randomBytes = new byte[(int) MultipartUpload.MIN_PART_SIZE];
+      random.nextBytes(randomBytes);
+      ByteSource byteSource = ByteSources.repeatingArrayByteSource(randomBytes).slice(0, size);
+      assertEquals(byteSource.size(), size);
+      return byteSource;
+   }
+
    @SuppressWarnings("unchecked")
    private File createFileBiggerThan(long partSize) throws IOException {
       long copiesNeeded = (partSize / getOneHundredOneConstitutionsLength()) + 1;

http://git-wip-us.apache.org/repos/asf/jclouds/blob/a39eadce/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/strategy/internal/MultipartNamingStrategyTest.java
----------------------------------------------------------------------
diff --git a/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/strategy/internal/MultipartNamingStrategyTest.java b/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/strategy/internal/MultipartNamingStrategyTest.java
new file mode 100644
index 0000000..364c410
--- /dev/null
+++ b/apis/swift/src/test/java/org/jclouds/openstack/swift/blobstore/strategy/internal/MultipartNamingStrategyTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.strategy.internal;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+@Test(testName = "MultipartNamingStrategyTest")
+public class MultipartNamingStrategyTest {
+
+   @Test
+   public void testGetPartNameFirstOneHundred() {
+      final MultipartNamingStrategy strategy = new MultipartNamingStrategy();
+      final String key = "file.txt";
+      final int numberParts = 100;
+
+      // check the first 100
+      for (int i = 0; i < numberParts; i++) {
+         String partName = strategy.getPartName(key, i + 1, numberParts);
+         assertEquals(String.format("file.txt/%03d", i + 1), partName);
+      }
+   }
+
+   @Test
+   public void testGetPartNameChoices() {
+      final MultipartNamingStrategy strategy = new MultipartNamingStrategy();
+      final String key = "file.txt";
+
+      // check less than 10 parts
+      assertEquals(strategy.getPartName(key, 1, 5), "file.txt/1");
+      assertEquals(strategy.getPartName(key, 2, 5), "file.txt/2");
+      assertEquals(strategy.getPartName(key, 5, 5), "file.txt/5");
+
+      // check <= 10 parts
+      assertEquals(strategy.getPartName(key, 1, 10), "file.txt/01");
+      assertEquals(strategy.getPartName(key, 2, 10), "file.txt/02");
+      assertEquals(strategy.getPartName(key, 10, 10), "file.txt/10");
+
+      // check <= 100 parts
+      assertEquals(strategy.getPartName(key, 1, 100), "file.txt/001");
+      assertEquals(strategy.getPartName(key, 9, 100), "file.txt/009");
+      assertEquals(strategy.getPartName(key, 10, 100), "file.txt/010");
+      assertEquals(strategy.getPartName(key, 99, 100), "file.txt/099");
+      assertEquals(strategy.getPartName(key, 100, 100), "file.txt/100");
+
+      // check <= 5000 parts
+      assertEquals(strategy.getPartName(key, 1, 5000), "file.txt/0001");
+      assertEquals(strategy.getPartName(key, 10, 5000), "file.txt/0010");
+      assertEquals(strategy.getPartName(key, 99, 5000), "file.txt/0099");
+      assertEquals(strategy.getPartName(key, 100, 5000), "file.txt/0100");
+      assertEquals(strategy.getPartName(key, 999, 5000), "file.txt/0999");
+      assertEquals(strategy.getPartName(key, 4999, 5000), "file.txt/4999");
+      assertEquals(strategy.getPartName(key, 5000, 500), "file.txt/5000");
+   }
+}
+