Issue 111: fixed blobstore related bugs and accomodated eventual consistency

git-svn-id: http://jclouds.googlecode.com/svn/trunk@2040 3d8758e0-26b5-11de-8745-db77d3ebf521
This commit is contained in:
adrian.f.cole 2009-11-03 01:53:42 +00:00
parent f3cd3f2db9
commit 197801204e
5 changed files with 96 additions and 22 deletions

View File

@ -77,11 +77,11 @@ public class AtmosBlobStore implements BlobStore {
private final BlobToHttpGetOptions blob2ObjectGetOptions; private final BlobToHttpGetOptions blob2ObjectGetOptions;
private final DirectoryEntryListToResourceMetadataList container2ResourceList; private final DirectoryEntryListToResourceMetadataList container2ResourceList;
private final ExecutorService service; private final ExecutorService service;
@Inject(optional = true) @Inject(optional = true)
@Named(BlobStoreConstants.PROPERTY_BLOBSTORE_TIMEOUT) @Named(BlobStoreConstants.PROPERTY_BLOBSTORE_TIMEOUT)
protected long requestTimeoutMilliseconds = 30000; protected long requestTimeoutMilliseconds = 30000;
@Inject @Inject
private AtmosBlobStore(AtmosStorageClient connection, Blob.Factory blobFactory, private AtmosBlobStore(AtmosStorageClient connection, Blob.Factory blobFactory,
LoggerFactory logFactory, ClearListStrategy clearContainerStrategy, LoggerFactory logFactory, ClearListStrategy clearContainerStrategy,
@ -125,7 +125,7 @@ public class AtmosBlobStore implements BlobStore {
}); });
} }
public Future<Boolean> createContainer(String container) { public Future<Boolean> createContainer(String container) {
return wrapFuture(connection.createDirectory(container), new Function<URI, Boolean>() { return wrapFuture(connection.createDirectory(container), new Function<URI, Boolean>() {
@ -173,7 +173,7 @@ public class AtmosBlobStore implements BlobStore {
public Future<? extends ListContainerResponse<? extends ResourceMetadata>> list( public Future<? extends ListContainerResponse<? extends ResourceMetadata>> list(
String container, org.jclouds.blobstore.options.ListContainerOptions... optionsList) { String container, org.jclouds.blobstore.options.ListContainerOptions... optionsList) {
if (optionsList.length == 1) { if (optionsList.length == 1) {
if (!optionsList[0].isRecursive()) { if (optionsList[0].isRecursive()) {
throw new UnsupportedOperationException("recursive not currently supported in emcsaas"); throw new UnsupportedOperationException("recursive not currently supported in emcsaas");
} }
if (optionsList[0].getPath() != null) { if (optionsList[0].getPath() != null) {
@ -184,6 +184,9 @@ public class AtmosBlobStore implements BlobStore {
return wrapFuture(connection.listDirectory(container, nativeOptions), container2ResourceList); return wrapFuture(connection.listDirectory(container, nativeOptions), container2ResourceList);
} }
/**
* Since there is no etag support in atmos, we just return the path.
*/
public Future<String> putBlob(final String container, final Blob blob) { public Future<String> putBlob(final String container, final Blob blob) {
final String path = container + "/" + blob.getMetadata().getName(); final String path = container + "/" + blob.getMetadata().getName();
@ -191,19 +194,24 @@ public class AtmosBlobStore implements BlobStore {
.deletePath(path), new Function<Void, String>() { .deletePath(path), new Function<Void, String>() {
public String apply(Void from) { public String apply(Void from) {
boolean exists = connection.pathExists(path); try {
if (!exists) if (!Utils.enventuallyTrue(new Supplier<Boolean>() {
try { public Boolean get() {
if (blob.getMetadata().getContentMD5() != null) return !connection.pathExists(path);
blob.getMetadata().getUserMetadata().put("content-md5", }
HttpUtils.toHexString(blob.getMetadata().getContentMD5())); }, requestTimeoutMilliseconds)) {
connection.createFile(container, blob2Object.apply(blob)).get(); throw new IllegalStateException(path + " still exists after deleting!");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
} }
return null; if (blob.getMetadata().getContentMD5() != null)
blob.getMetadata().getUserMetadata().put("content-md5",
HttpUtils.toHexString(blob.getMetadata().getContentMD5()));
connection.createFile(container, blob2Object.apply(blob)).get();
return path;
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
} }
}); });

View File

@ -1,5 +1,9 @@
package org.jclouds.atmosonline.saas.blobstore.functions; package org.jclouds.atmosonline.saas.blobstore.functions;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -8,8 +12,11 @@ import org.jclouds.atmosonline.saas.functions.AtmosObjectName;
import org.jclouds.blobstore.domain.MutableBlobMetadata; import org.jclouds.blobstore.domain.MutableBlobMetadata;
import org.jclouds.blobstore.domain.ResourceType; import org.jclouds.blobstore.domain.ResourceType;
import org.jclouds.blobstore.domain.internal.MutableBlobMetadataImpl; import org.jclouds.blobstore.domain.internal.MutableBlobMetadataImpl;
import org.jclouds.http.HttpUtils;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
/** /**
* @author Adrian Cole * @author Adrian Cole
@ -17,6 +24,9 @@ import com.google.common.base.Function;
@Singleton @Singleton
public class ObjectToBlobMetadata implements Function<AtmosObject, MutableBlobMetadata> { public class ObjectToBlobMetadata implements Function<AtmosObject, MutableBlobMetadata> {
private final AtmosObjectName objectName; private final AtmosObjectName objectName;
private static final Set<String> systemMetadata = ImmutableSet.of("atime", "mtime", "ctime",
"itime", "type", "uid", "gid", "objectid", "objname", "size", "nlink", "policyname",
"content-md5");
@Inject @Inject
protected ObjectToBlobMetadata(AtmosObjectName objectName) { protected ObjectToBlobMetadata(AtmosObjectName objectName) {
@ -27,13 +37,21 @@ public class ObjectToBlobMetadata implements Function<AtmosObject, MutableBlobMe
MutableBlobMetadata to = new MutableBlobMetadataImpl(); MutableBlobMetadata to = new MutableBlobMetadataImpl();
to.setId(from.getSystemMetadata().getObjectID()); to.setId(from.getSystemMetadata().getObjectID());
to.setLastModified(from.getSystemMetadata().getLastUserDataModification()); to.setLastModified(from.getSystemMetadata().getLastUserDataModification());
to.setContentMD5(from.getContentMetadata().getContentMD5()); String md5hex = from.getUserMetadata().getMetadata().get("content-md5");
if (md5hex != null)
to.setContentMD5(HttpUtils.fromHexString(md5hex));
if (from.getContentMetadata().getContentType() != null) if (from.getContentMetadata().getContentType() != null)
to.setContentType(from.getContentMetadata().getContentType()); to.setContentType(from.getContentMetadata().getContentType());
to.setName(objectName.apply(from)); to.setName(objectName.apply(from));
to.setSize(from.getSystemMetadata().getSize()); to.setSize(from.getSystemMetadata().getSize());
to.setType(ResourceType.BLOB); to.setType(ResourceType.BLOB);
to.setUserMetadata(from.getUserMetadata().getMetadata()); Map<String, String> lowerKeyMetadata = Maps.newHashMap();
for (Entry<String, String> entry : from.getUserMetadata().getMetadata().entrySet()) {
String key = entry.getKey().toLowerCase();
if (!systemMetadata.contains(key))
lowerKeyMetadata.put(key, entry.getValue());
}
to.setUserMetadata(lowerKeyMetadata);
return to; return to;
} }
} }

View File

@ -27,8 +27,8 @@ public class FindMD5InUserMetadata implements ContainsValueInListStrategy {
private final AtmosStorageClient client; private final AtmosStorageClient client;
@Inject @Inject
private FindMD5InUserMetadata(ObjectMD5 objectMD5, private FindMD5InUserMetadata(ObjectMD5 objectMD5, ListBlobMetadataStrategy getAllBlobMetadata,
ListBlobMetadataStrategy getAllBlobMetadata, AtmosStorageClient client) { AtmosStorageClient client) {
this.objectMD5 = objectMD5; this.objectMD5 = objectMD5;
this.getAllBlobMetadata = getAllBlobMetadata; this.getAllBlobMetadata = getAllBlobMetadata;
this.client = client; this.client = client;
@ -39,7 +39,8 @@ public class FindMD5InUserMetadata implements ContainsValueInListStrategy {
byte[] toSearch = objectMD5.apply(value); byte[] toSearch = objectMD5.apply(value);
String hex = HttpUtils.toHexString(toSearch); String hex = HttpUtils.toHexString(toSearch);
for (BlobMetadata metadata : getAllBlobMetadata.execute(containerName, options)) { for (BlobMetadata metadata : getAllBlobMetadata.execute(containerName, options)) {
UserMetadata properties = client.getUserMetadata(containerName+"/"+metadata.getName()); UserMetadata properties = client.headFile(containerName + "/" + metadata.getName())
.getUserMetadata();
if (hex.equals(properties.getMetadata().get("content-md5"))) if (hex.equals(properties.getMetadata().get("content-md5")))
return true; return true;
} }

View File

@ -195,7 +195,7 @@ public class AtmosStorageClientLiveTest {
// loop to gather metrics // loop to gather metrics
for (boolean stream : new Boolean[] { true, false }) { for (boolean stream : new Boolean[] { true, false }) {
for (int i = 0; i < 30; i++) { for (int i = 0; i < 10; i++) {
System.err.printf("upload/delete/create attempt %d type %s%n", i + 1, stream ? "stream" System.err.printf("upload/delete/create attempt %d type %s%n", i + 1, stream ? "stream"
: "string"); : "string");
// try updating // try updating

View File

@ -23,8 +23,13 @@
*/ */
package org.jclouds.atmosonline.saas.blobstore.integration; package org.jclouds.atmosonline.saas.blobstore.integration;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.jclouds.atmosonline.saas.AtmosStorageClient; import org.jclouds.atmosonline.saas.AtmosStorageClient;
import org.jclouds.blobstore.integration.internal.BaseBlobIntegrationTest; import org.jclouds.blobstore.integration.internal.BaseBlobIntegrationTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test; import org.testng.annotations.Test;
/** /**
@ -34,4 +39,46 @@ import org.testng.annotations.Test;
@Test(groups = { "integration", "live" }, testName = "emcsaas.AtmosStorageIntegrationTest") @Test(groups = { "integration", "live" }, testName = "emcsaas.AtmosStorageIntegrationTest")
public class AtmosStorageIntegrationTest extends BaseBlobIntegrationTest<AtmosStorageClient> { public class AtmosStorageIntegrationTest extends BaseBlobIntegrationTest<AtmosStorageClient> {
@DataProvider(name = "delete")
// no unicode support
@Override
public Object[][] createData() {
return new Object[][] { { "normal" } };
}
@Override
@Test(enabled = false)
public void testGetIfMatch() throws InterruptedException, ExecutionException, TimeoutException,
IOException {
// no etag support
}
@Override
@Test(enabled = false)
public void testGetIfModifiedSince() throws InterruptedException, ExecutionException,
TimeoutException, IOException {
// not supported
}
@Override
@Test(enabled = false)
public void testGetIfNoneMatch() throws InterruptedException, ExecutionException,
TimeoutException, IOException {
// no etag support
}
@Override
@Test(enabled = false)
public void testGetIfUnmodifiedSince() throws InterruptedException, ExecutionException,
TimeoutException, IOException {
// not supported
}
@Override
@Test(enabled = false)
public void testGetTwoRanges() throws InterruptedException, ExecutionException,
TimeoutException, IOException {
// not supported
}
} }