fix(artifacts): do not derive a save's version number from a failed listing - #1419
Open
svetanis wants to merge 1 commit into
Open
fix(artifacts): do not derive a save's version number from a failed listing#1419svetanis wants to merge 1 commit into
svetanis wants to merge 1 commit into
Conversation
Contributor
|
Hi @svetanis, thank you for your contribution! We appreciate you taking the time to submit this pull request. I noticed that the branch is currently out of sync with the base branch. could you please resolve the merge conflicts so we can proceed with the review? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
1. Link to an existing issue (if applicable):
2. Or, if no issue exists, describe the change:
Problem:
GcsArtifactService.listVersionsreturns an empty list when the listing raisesStorageException.saveArtifactAndReturnBlobderives the next version number from that result(
versions.isEmpty() ? 0 : max(versions) + 1) and writes with no precondition. At the point wherethe version number is chosen, an empty list can mean either that the artifact has no versions or
that the listing did not complete — so a 503, a 429, a timeout or IAM propagation makes the save
compute version 0 and write over the object already stored at version 0, while returning success and
version
0to the caller.listArtifactKeysissues the samestorageClient.list(bucketName, BlobListOption.prefix(...))calland surfaces a failure as
VerifyException.listVersionsis the one whose result selects the blobname a save writes to.
Solution:
Separate the listing from the decision about what a failed listing means. The query moves into a
private
readVersionsthat letsStorageExceptionpropagate, and each caller applies its ownpolicy:
listVersionscatches it and returns an empty list as before, the save path catches it andthrows. Four pieces:
readVersions— new private methodlistVersions, moved verbatim; the stream pipeline is unchangedlistVersions— public, behavior unchangedreadVersions, catchStorageException, returnImmutableList.of()versionsBeforeSaving— new private methodreadVersions, catchStorageException, throwVerifyException("Failed to list artifact versions from GCS", e)— the same formlistArtifactKeysalready uses for this operation, naming what failedsaveArtifactAndReturnBlob— one linelistVersions(...), nowSingle.fromCallable(() -> versionsBeforeSaving(...)); everything after it is untouchedlistVersionshas three callers besides the save path —loadArtifact,deleteArtifact, andArtifactControllerin thedevmodule — so removing the catch outright would changeall of them as well. Leaving it in place keeps the diff to the one caller that overwrites stored
data: those three behave exactly as before, and
listVersions_storageException_returnsEmptyListpasses untouched. That is a scoping choice, not a claim that the empty list is right for them; the
issue lists them as related and uncovered. If you would rather the catch went away and those callers
moved with it, say so and I will make that change instead.
core/src/main/java/…/artifacts/GcsArtifactService.java+81/-18core/src/test/java/…/artifacts/GcsArtifactServiceTest.javaTesting Plan
Unit Tests:
GcsArtifactServiceTest: Tests run: 26, Failures: 0, Errors: 0, Skipped: 0. Both new tests wereconfirmed failing on unmodified
mainby revertingGcsArtifactService.javaalone and re-running —Tests run: 26, Failures: 2, exactly these two:mainsave_listStorageException_propagatesVerifyException, withhasCauseThat().isInstanceOf(StorageException.class)so the underlying failure stays visiblesave_listStorageException_doesNotWriteverify(mockStorage, never()).create(...)Both also
verify(mockStorage).list(...), so neither can pass by failing earlier for an unrelatedreason.
Manual End-to-End (E2E) Tests:
PAYLOAD-Athrough a fully-permissionedclient, then save
PAYLOAD-Bto the same filename — arm 1 through an identity deniedstorage.objects.list, arm 2 through a client that can list. The bucket is inspected afterwardswith a privileged credential, since the restricted identity cannot list.
PAYLOAD-APAYLOAD-Astorage.objects.listPAYLOAD-BPAYLOAD-AloadArtifact(0)PAYLOAD-BPAYLOAD-AThe control arm is unchanged by the fix in both runs — versions
[0, 1], both objects kept — so thetwo arms differ only in which client performs the second save.
Checklist