-
Notifications
You must be signed in to change notification settings - Fork 25.2k
Make GoogleCloudStorageRetryingInputStream request same generation on resume #127626
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
6238090
e94b246
bcab09b
98a80c3
08e1b95
09c6e6e
576109c
4feb87d
0886ba7
cdfffa5
d3c0e6a
f3371e4
89b926a
fca2161
2d2960c
ce03dae
eebcb0c
4fd9b81
a8826fb
aeea9f6
03a9b8d
289a594
a60d7e0
b0f9298
caaf45c
b800710
0f8796b
16d6c39
a70e2ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,6 +45,7 @@ | |
import org.elasticsearch.core.SuppressForbidden; | ||
import org.elasticsearch.core.TimeValue; | ||
import org.elasticsearch.http.ResponseInjectingHttpHandler; | ||
import org.elasticsearch.mocksocket.MockHttpServer; | ||
import org.elasticsearch.repositories.blobstore.AbstractBlobContainerRetriesTestCase; | ||
import org.elasticsearch.repositories.blobstore.ESMockAPIBasedRepositoryIntegTestCase; | ||
import org.elasticsearch.rest.RestStatus; | ||
|
@@ -56,6 +57,7 @@ | |
import java.io.InputStream; | ||
import java.net.InetSocketAddress; | ||
import java.net.SocketTimeoutException; | ||
import java.nio.file.NoSuchFileException; | ||
import java.util.Arrays; | ||
import java.util.HashMap; | ||
import java.util.Iterator; | ||
|
@@ -71,6 +73,7 @@ | |
|
||
import static fixture.gcs.TestUtils.createServiceAccount; | ||
import static java.nio.charset.StandardCharsets.UTF_8; | ||
import static org.elasticsearch.common.io.Streams.readFully; | ||
import static org.elasticsearch.repositories.blobstore.BlobStoreTestUtil.randomPurpose; | ||
import static org.elasticsearch.repositories.blobstore.ESBlobStoreRepositoryIntegTestCase.randomBytes; | ||
import static org.elasticsearch.repositories.gcs.GoogleCloudStorageBlobStore.MAX_DELETES_PER_BATCH; | ||
|
@@ -86,6 +89,7 @@ | |
import static org.hamcrest.Matchers.is; | ||
import static org.hamcrest.Matchers.lessThanOrEqualTo; | ||
import static org.hamcrest.Matchers.notNullValue; | ||
import static org.hamcrest.Matchers.startsWith; | ||
|
||
@SuppressForbidden(reason = "use a http server") | ||
public class GoogleCloudStorageBlobContainerRetriesTests extends AbstractBlobContainerRetriesTestCase { | ||
|
@@ -570,6 +574,54 @@ public void testCompareAndExchangeWhenThrottled() throws IOException { | |
container.delete(randomPurpose()); | ||
} | ||
|
||
public void testContentsChangeWhileStreaming() throws IOException { | ||
GoogleCloudStorageHttpHandler handler = new GoogleCloudStorageHttpHandler("bucket"); | ||
httpServer.createContext("/", handler); | ||
final int enoughBytesToTriggerChunkedDownload = Math.toIntExact(ByteSizeValue.ofMb(30).getBytes()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mind adding a comment to explain briefly how the size of 30mb is decided? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The naming is actually incorrect (I don't think the current client does any chunking for downloads, only uploads). We just need to ensure the blob is large enough that it doesn't get entirely buffered when we read the first byte. This is probably a TCP level thing because as far as I can see the google or Java HTTP infrastructure merely relies on the underlying JVM/OS behaviour. I did some experiments locally and 2MB is usually enough to exceed the buffer, so 30M should be fairly conservative. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in ce03dae |
||
|
||
final BlobContainer container = createBlobContainer(1, null, null, null, null, null, null); | ||
|
||
final String key = randomIdentifier(); | ||
byte[] initialValue = randomByteArrayOfLength(enoughBytesToTriggerChunkedDownload); | ||
container.writeBlob(randomPurpose(), key, new BytesArray(initialValue), true); | ||
|
||
BytesReference reference = readFully(container.readBlob(randomPurpose(), key)); | ||
assertEquals(new BytesArray(initialValue), reference); | ||
|
||
try (InputStream inputStream = container.readBlob(randomPurpose(), key)) { | ||
// Trigger the first chunk to load | ||
int read = inputStream.read(); | ||
assert read != -1; | ||
|
||
// Restart the server (this triggers a retry) | ||
restartHttpServer(); | ||
httpServer.createContext("/", handler); | ||
|
||
// Update the file | ||
byte[] updatedValue = randomByteArrayOfLength(enoughBytesToTriggerChunkedDownload); | ||
container.writeBlob(randomPurpose(), key, new BytesArray(updatedValue), false); | ||
|
||
// Read the rest of the stream, it should throw because the contents changed | ||
String message = assertThrows(NoSuchFileException.class, () -> readFully(inputStream)).getMessage(); | ||
assertThat( | ||
message, | ||
startsWith( | ||
"Blob object [" | ||
+ container.path().buildAsString() | ||
+ key | ||
+ "] generation [1] unavailable on resume (contents changed, or object deleted):" | ||
) | ||
); | ||
} | ||
} | ||
|
||
private void restartHttpServer() throws IOException { | ||
InetSocketAddress currentAddress = httpServer.getAddress(); | ||
httpServer.stop(0); | ||
httpServer = MockHttpServer.createHttp(currentAddress, 0); | ||
Comment on lines
+627
to
+629
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder whether rebinding to the same address can be flaky sometimes. We can have it as is for now and see how it goes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it should be OK as long as we have |
||
httpServer.start(); | ||
} | ||
|
||
private HttpHandler safeHandler(HttpHandler handler) { | ||
final HttpHandler loggingHandler = ESMockAPIBasedRepositoryIntegTestCase.wrap(handler, logger); | ||
return exchange -> { | ||
|
Uh oh!
There was an error while loading. Please reload this page.