Skip to content
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

Conversation

nicktindall
Copy link
Contributor

Implemented in order that we can use it to test the fix for #116546

@nicktindall nicktindall added >test Issues or PRs that are addressing/adding tests :Distributed Coordination/Snapshot/Restore Anything directly related to the `_snapshot/*` APIs labels Dec 9, 2024
@nicktindall nicktindall marked this pull request as ready for review December 9, 2024 08:56
@elasticsearchmachine elasticsearchmachine added the Team:Distributed Coordination Meta label for Distributed Coordination team label Dec 9, 2024
@elasticsearchmachine
Copy link
Collaborator

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);
Copy link
Contributor

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)?

Copy link
Contributor Author

@nicktindall nicktindall Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 70c5da2 (then better in fdf8992)

});
}

BlobVersion updateResumableBlob(String path, String uploadId, BytesReference newContents) {
Copy link
Contributor

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.

Copy link
Contributor Author

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.

} finally {
int read = exchange.getRequestBody().read();
assert read == -1 : "Request body should have been fully read here but saw [" + read + "]";
Copy link
Contributor Author

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 HttpHandlers had this check.

Copy link
Contributor

@DaveCTurner DaveCTurner left a 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

@nicktindall
Copy link
Contributor Author

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.

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);
Copy link
Contributor Author

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.

Copy link
Contributor

@DaveCTurner DaveCTurner left a 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;
Copy link
Contributor

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?

Copy link
Contributor Author

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;
Copy link
Contributor

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?

Copy link
Contributor Author

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)
Copy link
Contributor

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?

Copy link
Contributor Author

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()
Copy link
Contributor

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)?

Copy link
Contributor Author

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()
Copy link
Contributor

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?

Copy link
Contributor Author

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() {
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved in above commits

Copy link
Contributor

@DaveCTurner DaveCTurner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM thanks Nick

@nicktindall nicktindall merged commit 0dbe034 into elastic:main Jan 6, 2025
24 checks passed
@nicktindall nicktindall deleted the ES-5679_implement_cas_support_gcp_test_fixture branch January 6, 2025 06:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
:Distributed Coordination/Snapshot/Restore Anything directly related to the `_snapshot/*` APIs Team:Distributed Coordination Meta label for Distributed Coordination team >test Issues or PRs that are addressing/adding tests v9.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants