-
Notifications
You must be signed in to change notification settings - Fork 25k
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
Implement CAS support for GCP test fixture #118236
Implement CAS support for GCP test fixture #118236
Conversation
Pinging @elastic/es-distributed-coordination (Team:Distributed Coordination) |
try { | ||
return Long.parseLong(params.get(parameterName)); | ||
} catch (NumberFormatException e) { | ||
throw new MockGcsBlobStore.GcsRestException(RestStatus.BAD_REQUEST, "Invalid long parameter: " + parameterName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we assert this doesn't happen (i.e. fail the test if it does)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
}); | ||
} | ||
|
||
BlobVersion updateResumableBlob(String path, String uploadId, BytesReference newContents) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't look right to me. Resumable blobs are not visible to GET
requests, and you can in principle have two different resumable uploads for the same path at the same time which should not conflict like this.
Admittedly it looks wrong in the existing code too, but we're making it more wrong here I think: a resumed upload needs to check the generation match at the point that the upload completes and becomes visible, not when deciding whether or not to start accepting new data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yep, I misinterpreted the purpose of the upload ID. I've implemented resumable uploads properly now, it was a bit of a can of worms, but I tested it with a streaming upload and it appeared to work as expected.
I restored the if-generation-match to work as you suggested, though I'm not sure if we actually use it in the fixture. In any case it was trivial to support.
…_support_gcp_test_fixture
} finally { | ||
int read = exchange.getRequestBody().read(); | ||
assert read == -1 : "Request body should have been fully read here but saw [" + read + "]"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seemed to throw IOExceptions
due to the body being closed, ExchangeImpl
calls #close()
when you call sendResponseHeaders
with a zero-length body. None of the other HttpHandler
s had this check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM except that I'd quite like to see some testing of the fixture itself, similar to S3HttpHandlerTests
, as there's rather too much logic here to review by eye alone. Maybe add such a test suite in a separate PR, merge it first, and then we can include test changes just for the CAS support in this PR.
Relates #117329
I've put up a PR that tests the existing functionality here #118737 I think some of it is slightly wrong, so there will probably be some minor changes in the test once we merge it into this branch. |
…_support_gcp_test_fixture
final var part1 = randomAlphaOfLength(50); | ||
final var uploadPart1Response = handleRequest(handler, "PUT", sessionURI, part1, contentRangeHeader(0, 50, null)); | ||
assertEquals(new TestHttpResponse(RESUME_INCOMPLETE, rangeHeader(0, 50)), uploadPart1Response); | ||
assertEquals(new TestHttpResponse(RESUME_INCOMPLETE, rangeHeader(0, 49)), uploadPart1Response); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The returned range headers were actually wrong previously. Range is supposed to be inclusive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, just a few tiny comments
|
||
@Override | ||
public String toString() { | ||
return "bytes=" + start + "-" + end; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
toString()
is generally best reserved for debugging-like uses only (and I'd rather keep the default implementation on records if possible). If we need to use a specific format for sending data over the wire, could we do so with a different method please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved in 8364c15
public String toString() { | ||
final String rangeString = hasRange() ? start + "-" + end : "*"; | ||
final String sizeString = hasSize() ? String.valueOf(size) : "*"; | ||
return "bytes " + rangeString + "/" + sizeString; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise here: toString()
is generally best reserved for debugging-like uses only (and I'd rather keep the default implementation on records if possible). If we need to use a specific format for sending data over the wire, could we do so with a different method please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved in 8364c15
// new file, non-zero generation | ||
assertEquals( | ||
RestStatus.PRECONDITION_FAILED, | ||
executeResumableUpload(handler, bucket, blobName + randomIdentifier(), randomBytesReference(randomIntBetween(100, 5_000)), 1L) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I know it's vanishingly unlikely, but for the sake of readability could we make sure to choose a name which is definitely different from the one used in the previous step?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved in 093765d
// update, matched generation | ||
assertEquals( | ||
RestStatus.OK, | ||
executeResumableUpload(handler, bucket, blobName, randomBytesReference(randomIntBetween(100, 5_000)), 1L).restStatus() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: how do we know the generation is 1? Can we get that from the previous upload response (or a get)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved in 2796005
// update, mismatched generation | ||
assertEquals( | ||
RestStatus.PRECONDITION_FAILED, | ||
executeResumableUpload(handler, bucket, blobName, randomBytesReference(randomIntBetween(100, 5_000)), 13L).restStatus() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise how do we know the generation is now not 13?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved in 2796005
); | ||
} | ||
|
||
public void testIfGenerationMatch_MultipartUpload() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar comments here to the resumable case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved in above commits
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM thanks Nick
Implemented in order that we can use it to test the fix for #116546