Skip to content

Commit ffb0537

Browse files
authored
Merge pull request #3 from UltimateSoftware/VerifyEtagAndRetry
Verify etag and retry
2 parents fcdba6a + 483dd69 commit ffb0537

File tree

2 files changed

+39
-19
lines changed

2 files changed

+39
-19
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@ dependencies {
6767
compile group: 'com.google.guava', name: 'guava', version: guavaVersion
6868
compile group: 'org.javaswift', name: 'joss', version: jossVersion
6969
compile group: 'org.apache.httpcomponents', name: 'httpclient', version: httpclientVersion
70+
compile group: 'commons-codec', name: 'commons-codec', version: commonsCodecVersion
7071
runtime group: 'org.apache.httpcomponents', name: 'httpcore', version: httpcoreVersion
7172
runtime group: 'javax.activation', name: 'activation', version: javaxActivationVersion
7273
runtime group: 'org.slf4j', name: 'slf4j-api', version: slf4jVersion
7374
runtime group: 'org.slf4j', name: 'slf4j-log4j12', version: slf4jVersion
74-
runtime group: 'commons-codec', name: 'commons-codec', version: commonsCodecVersion
7575
runtime group: 'commons-logging', name: 'commons-logging', version: commonsloggingVersion
7676
runtime group: 'org.codehaus.jackson', name: 'jackson-mapper-asl', version: jacksonmapperVersion
7777
runtime group: 'commons-lang', name: 'commons-lang', version: commonslangVersion

src/main/java/org/wikimedia/elasticsearch/swift/repositories/blobstore/SwiftBlobContainer.java

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.wikimedia.elasticsearch.swift.repositories.blobstore;
1818

19+
import org.apache.commons.codec.digest.DigestUtils;
1920
import org.apache.logging.log4j.LogManager;
2021
import org.apache.logging.log4j.Logger;
2122
import org.elasticsearch.common.Nullable;
@@ -37,10 +38,10 @@
3738
import org.wikimedia.elasticsearch.swift.util.retry.WithTimeout;
3839
import org.wikimedia.elasticsearch.swift.repositories.SwiftRepository;
3940

40-
import java.io.BufferedInputStream;
41+
import java.io.ByteArrayInputStream;
4142
import java.io.ByteArrayOutputStream;
42-
import java.io.IOException;
4343
import java.io.InputStream;
44+
import java.io.IOException;
4445
import java.nio.file.FileAlreadyExistsException;
4546

4647
import java.nio.file.NoSuchFileException;
@@ -299,22 +300,37 @@ private String buildKey(String blobName) {
299300
}
300301

301302
/**
302-
* Fetch a given blob into a BufferedInputStream
303+
* Fetch a given blob into memory, verify etag, and return InputStream.
303304
* @param blobName The blob name to read
304305
* @return a stream
305306
*/
306307
@Override
307308
public InputStream readBlob(final String blobName) throws IOException {
309+
String objectName = buildKey(blobName);
310+
308311
try {
309312
return withTimeout().retry(retryIntervalS, shortOperationTimeoutS, TimeUnit.SECONDS, () -> {
310313
try {
311-
InputStream downloadStream = SwiftPerms.execThrows(() ->
312-
blobStore.getContainer().getObject(buildKey(blobName)).downloadObjectAsInputStream()
313-
);
314-
return new BufferedInputStream(downloadStream, (int) blobStore.getBufferSizeInBytes());
314+
return SwiftPerms.execThrows(() -> {
315+
StoredObject storedObject = blobStore.getContainer().getObject(objectName);
316+
InputStream rawInputStream = storedObject.downloadObjectAsInputStream();
317+
int contentLength = (int) storedObject.getContentLength();
318+
String objectEtag = storedObject.getEtag();
319+
byte[] objectData = readAllBytes(rawInputStream, contentLength);
320+
String dataEtag = DigestUtils.md5Hex(objectData);
321+
322+
if (!dataEtag.equals(objectEtag)) {
323+
String message = "cannot read blob [" + objectName + "]: server etag [" + objectEtag +
324+
"] does not match calculated etag [" + dataEtag + "]";
325+
logger.warn(message);
326+
throw new IOException(message);
327+
}
328+
329+
return new ByteArrayInputStream(objectData);
330+
});
315331
}
316332
catch (NotFoundException e) {
317-
String message = "cannot read blob [" + buildKey(blobName) + "]";
333+
String message = "cannot read blob [" + objectName + "]";
318334
logger.warn(message);
319335
NoSuchFileException e2 = new NoSuchFileException(message);
320336
e2.initCause(e);
@@ -326,7 +342,7 @@ public InputStream readBlob(final String blobName) throws IOException {
326342
throw e;
327343
}
328344
catch(Exception e) {
329-
throw new BlobStoreException("cannot read blob [" + buildKey(blobName) + "]", e);
345+
throw new BlobStoreException("cannot read blob [" + objectName + "]", e);
330346
}
331347
}
332348

@@ -335,7 +351,8 @@ public void writeBlob(final String blobName,
335351
final InputStream in,
336352
final long blobSize,
337353
boolean failIfAlreadyExists) throws IOException {
338-
byte[] bytes = readAllBytes(in);
354+
// async execution races against the InputStream closed in the caller. Read all data locally.
355+
byte[] bytes = readAllBytes(in, -1);
339356

340357
if (executor != null && allowConcurrentIO) {
341358
Future<Void> task = executor.submit(() -> {
@@ -350,16 +367,19 @@ public void writeBlob(final String blobName,
350367
internalWriteBlob(blobName, bytes, failIfAlreadyExists);
351368
}
352369

353-
private byte[] readAllBytes(InputStream in) throws IOException {
354-
final byte[] buffer = new byte[1024];
355-
ByteArrayOutputStream baos = new ByteArrayOutputStream(buffer.length);
356-
int read;
370+
private byte[] readAllBytes(InputStream in, int sizeHint) throws IOException {
371+
int bufferSize = (int) blobStore.getBufferSizeInBytes();
372+
final byte[] buffer = new byte[bufferSize];
357373

358-
while ((read = in.read(buffer)) != -1) {
359-
baos.write(buffer, 0, read);
360-
}
374+
try (ByteArrayOutputStream baos = new ByteArrayOutputStream(sizeHint > 0 ? sizeHint : bufferSize)) {
375+
int read;
361376

362-
return baos.toByteArray();
377+
while ((read = in.read(buffer)) != -1) {
378+
baos.write(buffer, 0, read);
379+
}
380+
381+
return baos.toByteArray();
382+
}
363383
}
364384

365385
private void internalWriteBlob(String blobName, byte[] bytes, boolean failIfAlreadyExists) throws IOException {

0 commit comments

Comments
 (0)