Skip to content

Add state query param to Get snapshots API #123618

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

Closed
wants to merge 8 commits into from

Conversation

ElenaStoeva
Copy link
Contributor

@ElenaStoeva ElenaStoeva commented Feb 27, 2025

Closes #97446

This PR introduces a new state query parameter for the Get Snapshots API, allowing users to filter snapshots by state. The parameter accepts comma-separated values for states: SUCCESS, IN_PROGRESS, FAILED, PARTIAL, INCOMPATIBLE (case-insensitive).

🎯 Motivation & Use Case

This is my mini-project for the AppEx team’s On-Week, where I aimed to explore contributing to Elasticsearch while addressing several Kibana issues.

In Kibana's Snapshot Restore page, multiple open issues could be resolved by adding this parameter:

1️⃣ Fixing incorrect selection of last successful managed snapshot

  • Helps correctly identify the last successful managed snapshot, ensuring delete actions are disabled for it.
  • Resolves bugs kibana#158548 and kibana#153107

2️⃣ Enhancing the Snapshots table:

✅ Summary of Changes

  • Added state as an optional query parameter to GET _snapshot/{repository}/_all
  • Updated filtering logic in TransportGetSnapshotsAction
  • Added validation of the parameter value
  • Added integration tests in GetSnapshotsIT.java

@ElenaStoeva ElenaStoeva added >enhancement :Distributed Coordination/Snapshot/Restore Anything directly related to the `_snapshot/*` APIs v9.1.0 labels Feb 27, 2025
@ElenaStoeva ElenaStoeva self-assigned this Feb 27, 2025
Copy link
Contributor

Documentation preview:

@elasticsearchmachine elasticsearchmachine added the external-contributor Pull request authored by a developer outside the Elasticsearch team label Feb 27, 2025
@ElenaStoeva
Copy link
Contributor Author

@elasticsearchmachine test this

@ElenaStoeva ElenaStoeva marked this pull request as ready for review February 28, 2025 18:12
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-distributed-coordination (Team:Distributed Coordination)

@elasticsearchmachine elasticsearchmachine added the Team:Distributed Coordination Meta label for Distributed Coordination team label Feb 28, 2025
@ElenaStoeva ElenaStoeva added Team:Distributed (Obsolete) Meta label for distributed team (obsolete). Replaced by Distributed Indexing/Coordination. and removed Team:Distributed Coordination Meta label for Distributed Coordination team labels Feb 28, 2025
@elasticsearchmachine elasticsearchmachine added the Team:Distributed Coordination Meta label for Distributed Coordination team label Feb 28, 2025
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-distributed-obsolete (Team:Distributed (Obsolete))

@@ -571,6 +575,10 @@ private boolean matchesPredicates(SnapshotInfo snapshotInfo) {
return false;
}

if (state != null) {
return state.equalsIgnoreCase(snapshotInfo.state().toString());
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 also do this as a pre-flight check (in the other matchesPredicates method)? No need to do the expensive SnapshotInfo read if we can help it, and most of the time this info is going to be available in RepositoryDetails.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure! Changed this in d9f8faf.

@@ -77,6 +79,8 @@ public class GetSnapshotsRequest extends MasterNodeRequest<GetSnapshotsRequest>

private boolean includeIndexNames = true;

private String state;
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 make this a (nullable) SnapshotState rather than a string? Also please mark as @Nullable with a comment saying what the null value means.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also are you sure you only want to pick one state? What about a comma-separated list of values perhaps? For instance SUCCESS,PARTIAL would give you all the complete snapshots. If so, maybe an EnumSet<SnapshotState> instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great idea! I think it would be useful to make it an EnumSet<SnapshotState> so that we can filter by multiple states. Added the change with d9f8faf.

@@ -633,6 +634,57 @@ public void testRetrievingSnapshotsWhenRepositoryIsMissing() throws Exception {
expectThrows(RepositoryMissingException.class, multiRepoFuture::actionGet);
}

public void testFilterByState() throws Exception {
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 also add this feature to testAllFeatures?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I added some checks there as well. Let me know if I need to add any other tests.

@ElenaStoeva
Copy link
Contributor Author

Many thanks for the review @DaveCTurner! I addressed your comments, please let me know if you have any additional suggestions.

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.

👍 API makes more sense now. I did another pass and left more comments


final String stateString = request.param("state");
if (stateString == null || stateString.isEmpty()) {
getSnapshotsRequest.state(EnumSet.noneOf(SnapshotState.class));
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggest defaulting to EnumSet.allOf rather than having a special case for the empty set lower down. We could reasonably reject the empty set in org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest#validate I suppose since otherwise that'll just filter out everything.

@@ -82,6 +85,14 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC
final SortOrder order = SortOrder.fromString(request.param("order", getSnapshotsRequest.order().toString()));
getSnapshotsRequest.order(order);
getSnapshotsRequest.includeIndexNames(request.paramAsBoolean(INDEX_NAMES_XCONTENT_PARAM, getSnapshotsRequest.includeIndexNames()));

final String stateString = request.param("state");
if (stateString == null || stateString.isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: suggest using org.elasticsearch.common.Strings#hasLength(java.lang.String) here

@@ -558,11 +564,16 @@ private boolean matchesPredicates(SnapshotId snapshotId, RepositoryData reposito
return false;
}

final var details = repositoryData.getSnapshotDetails(snapshotId);

if (!state.isEmpty() && !state.contains(details.getSnapshotState())) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The style check will not like the unary ! operators here, you need to say state.contains(details.getSnapshotState()) == false instead.

@@ -181,6 +184,7 @@ private class GetSnapshotsOperation {
private final SnapshotNamePredicate snapshotNamePredicate;
private final SnapshotPredicates fromSortValuePredicates;
private final Predicate<String> slmPolicyPredicate;
private final EnumSet<SnapshotState> state;
Copy link
Contributor

Choose a reason for hiding this comment

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

naming nit

Suggested change
private final EnumSet<SnapshotState> state;
private final EnumSet<SnapshotState> states;

@@ -150,4 +153,8 @@ public GetSnapshotsRequestBuilder setIncludeIndexNames(boolean indices) {

}

public GetSnapshotsRequestBuilder setState(EnumSet<SnapshotState> state) {
Copy link
Contributor

Choose a reason for hiding this comment

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

naming nit

Suggested change
public GetSnapshotsRequestBuilder setState(EnumSet<SnapshotState> state) {
public GetSnapshotsRequestBuilder setStates(EnumSet<SnapshotState> state) {

@@ -558,11 +564,16 @@ private boolean matchesPredicates(SnapshotId snapshotId, RepositoryData reposito
return false;
}

final var details = repositoryData.getSnapshotDetails(snapshotId);

if (!state.isEmpty() && !state.contains(details.getSnapshotState())) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh also we need to handle the cases where details or details.getSnapshotState() are null. In that case, we cannot do the pre-flight check, we will need to retrieve the SnapshotInfo and then check that in the other matchesPredicates() method

* @param name the state name
* @return state
*/
public static SnapshotState of(String name) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Naming nit: maybe fromString rather than of? As written here it makes sense if you call the method directly but it's a little awkward to read .map(SnapshotState::of), I'd prefer .map(SnapshotState::fromString).

Also could we not use the built-in valueOf()?

@@ -85,6 +85,10 @@
"verbose":{
"type":"boolean",
"description":"Whether to show verbose snapshot info or only show the basic info found in the repository index blob"
},
"state": {
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 also add some simple smoke tests to rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/snapshot.get/10_basic.yml just to check that we are parsing the parameter ok. No need for anything exhaustive, just check that e.g. SUCCESS and SUCCESS,PARTIAL both return the expected snapshots.

@@ -912,9 +963,15 @@ public void testAllFeatures() {
// INDICES and by SHARDS. The actual sorting behaviour for these cases is tested elsewhere, here we're just checking that sorting
// interacts correctly with the other parameters to the API.

final EnumSet<SnapshotState> state = EnumSet.of(randomFrom(SnapshotState.values()));
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 test a random nonempty subset rather than just a singleton?

// compute the ordered sequence of snapshots which match the repository/snapshot name filters and SLM policy filter
final var selectedSnapshots = snapshotInfos.stream()
.filter(snapshotInfoPredicate)
.filter(s -> state.contains(s.state()))
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should fold the state filter into snapshotInfoPredicate, since we're really implementing it alongside the SLM policy check etc.

@ElenaStoeva
Copy link
Contributor Author

Thanks for the review, @DaveCTurner, and apologies for the delay in addressing the feedback. I've been focused on Kibana-related tasks for my team and, unfortunately, haven’t had a chance to revisit this PR. @JeremyDahlgren reached out and offered to take it over, so he’ll be picking it up from here. I really appreciate your time and input!

@ElenaStoeva
Copy link
Contributor Author

Closing in favor of #128635

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 >enhancement external-contributor Pull request authored by a developer outside the Elasticsearch team Team:Distributed Coordination Meta label for Distributed Coordination team Team:Distributed (Obsolete) Meta label for distributed team (obsolete). Replaced by Distributed Indexing/Coordination. v9.1.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a STATE query parameter to Get snapshot API
4 participants