Enforce correct MD5 for local blobstores

Matches behavior of real blobstores.
This commit is contained in:
Andrew Gaul 2013-10-22 16:41:31 -07:00
parent 1d218b1705
commit f4eca0422d
5 changed files with 45 additions and 42 deletions

View File

@ -47,6 +47,7 @@ import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.hash.HashingInputStream;
import com.google.common.io.ByteSource;
@ -201,9 +202,15 @@ public class FilesystemStorageStrategyImpl implements LocalStorageStrategy {
Files.createParentDirs(outputFile);
his = new HashingInputStream(Hashing.md5(), payload.openStream());
Files.asByteSink(outputFile).writeFrom(his);
payload.getContentMetadata().setContentMD5(his.hash());
String eTag = base16().lowerCase().encode(payload.getContentMetadata().getContentMD5());
return eTag;
HashCode actualHashCode = his.hash();
HashCode expectedHashCode = payload.getContentMetadata().getContentMD5AsHashCode();
if (expectedHashCode != null && !actualHashCode.equals(expectedHashCode)) {
throw new IOException("MD5 hash code mismatch, actual: " + actualHashCode +
" expected: " + expectedHashCode);
}
payload.getContentMetadata().setContentMD5(actualHashCode);
// TODO: store metadata in extended attributes when moving to Java 7
return base16().lowerCase().encode(actualHashCode.asBytes());
} catch (IOException ex) {
if (outputFile != null) {
if (!outputFile.delete()) {

View File

@ -77,9 +77,4 @@ public class FilesystemBlobIntegrationTest extends BaseBlobIntegrationTest {
public void testPutObjectStream() throws InterruptedException, IOException, ExecutionException {
throw new SkipException("not yet implemented");
}
@Override
public void testPutIncorrectContentMD5() throws InterruptedException, IOException {
throw new SkipException("not yet implemented");
}
}

View File

@ -392,6 +392,11 @@ public class LocalAsyncBlobStore extends BaseAsyncBlobStore {
try {
return immediateFuture(storageStrategy.putBlob(containerName, blob));
} catch (IOException e) {
if (e.getMessage().startsWith("MD5 hash code mismatch")) {
HttpResponseException exception = returnResponseException(400);
exception.initCause(e);
throw exception;
}
logger.error(e, "An error occurred storing the new blob with name [%s] to container [%s].", blobKey,
containerName);
throw Throwables.propagate(e);

View File

@ -39,14 +39,15 @@ import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.io.MutableContentMetadata;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
import org.jclouds.io.payloads.ByteArrayPayload;
import org.jclouds.io.payloads.DelegatingPayload;
import org.jclouds.util.Closeables2;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimaps;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.hash.HashingInputStream;
import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
import com.google.common.net.HttpHeaders;
@ -123,10 +124,25 @@ public class TransientStorageStrategy implements LocalStorageStrategy {
@Override
public String putBlob(final String containerName, final Blob blob) throws IOException {
Blob newBlob = createUpdatedCopyOfBlobInContainer(containerName, blob);
byte[] payload;
HashCode actualHashCode;
HashingInputStream input = new HashingInputStream(Hashing.md5(), blob.getPayload().openStream());
try {
payload = ByteStreams.toByteArray(input);
actualHashCode = input.hash();
HashCode expectedHashCode = blob.getPayload().getContentMetadata().getContentMD5AsHashCode();
if (expectedHashCode != null && !actualHashCode.equals(expectedHashCode)) {
throw new IOException("MD5 hash code mismatch, actual: " + actualHashCode +
" expected: " + expectedHashCode);
}
} finally {
Closeables2.closeQuietly(input);
}
Blob newBlob = createUpdatedCopyOfBlobInContainer(containerName, blob, payload, actualHashCode);
Map<String, Blob> map = containerToBlobs.get(containerName);
map.put(newBlob.getMetadata().getName(), newBlob);
return base16().lowerCase().encode(newBlob.getPayload().getContentMetadata().getContentMD5());
return base16().lowerCase().encode(actualHashCode.asBytes());
}
@Override
@ -146,37 +162,22 @@ public class TransientStorageStrategy implements LocalStorageStrategy {
return "/";
}
private Blob createUpdatedCopyOfBlobInContainer(String containerName, Blob in) {
private Blob createUpdatedCopyOfBlobInContainer(String containerName, Blob in, byte[] input, HashCode contentMd5) {
checkNotNull(containerName, "containerName");
checkNotNull(in, "blob");
checkNotNull(in.getPayload(), "blob.payload");
ByteArrayPayload payload = (in.getPayload() instanceof ByteArrayPayload) ? ByteArrayPayload.class.cast(in
.getPayload()) : null;
if (payload == null)
payload = (in.getPayload() instanceof DelegatingPayload) ? (DelegatingPayload.class.cast(in.getPayload())
.getDelegate() instanceof ByteArrayPayload) ? ByteArrayPayload.class.cast(DelegatingPayload.class
.cast(in.getPayload()).getDelegate()) : null : null;
try {
if (payload == null || !(payload instanceof ByteArrayPayload)) {
checkNotNull(input, "input");
checkNotNull(contentMd5, "contentMd5");
Payload payload = Payloads.newByteSourcePayload(ByteSource.wrap(input));
MutableContentMetadata oldMd = in.getPayload().getContentMetadata();
byte[] out = ByteStreams.toByteArray(in.getPayload());
payload = Payloads.newByteArrayPayload(out);
HttpUtils.copy(oldMd, payload.getContentMetadata());
payload.getContentMetadata().setContentMD5(Hashing.md5().hashBytes(out));
} else {
if (payload.getContentMetadata().getContentMD5() == null) {
payload.getContentMetadata().setContentMD5(ByteStreams.hash(payload, Hashing.md5()));
}
}
} catch (IOException e) {
Throwables.propagate(e);
}
payload.getContentMetadata().setContentMD5(contentMd5);
Blob blob = blobFactory.create(BlobStoreUtils.copy(in.getMetadata()));
blob.setPayload(payload);
blob.getMetadata().setContainer(containerName);
blob.getMetadata().setUri(
uriBuilder(new StringBuilder("mem://").append(containerName)).path(in.getMetadata().getName()).build());
blob.getMetadata().setLastModified(new Date());
String eTag = base16().lowerCase().encode(payload.getContentMetadata().getContentMD5());
String eTag = base16().lowerCase().encode(contentMd5.asBytes());
blob.getMetadata().setETag(eTag);
// Set HTTP headers to match metadata
blob.getAllHeaders().replaceValues(HttpHeaders.LAST_MODIFIED,

View File

@ -27,9 +27,4 @@ public class TransientBlobIntegrationTest extends BaseBlobIntegrationTest {
public TransientBlobIntegrationTest() {
provider = "transient";
}
@Override
public void testPutIncorrectContentMD5() throws InterruptedException, IOException {
throw new SkipException("not yet implemented");
}
}