diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java index a00b9f1143758..3158fc675c3b4 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/snapshots/get/TransportGetSnapshotsAction.java @@ -129,7 +129,7 @@ protected void masterOperation( request.offset(), request.size(), request.order(), - new SnapshotPredicates(request), + SnapshotPredicates.fromRequest(request), listener ); } @@ -318,10 +318,9 @@ private void loadSnapshotInfos( return; } - final BiPredicate preflightPredicate = predicates.preflightPredicate(); if (repositoryData != null) { for (SnapshotId snapshotId : repositoryData.getSnapshotIds()) { - if (preflightPredicate == null || preflightPredicate.test(snapshotId, repositoryData)) { + if (predicates.test(snapshotId, repositoryData)) { allSnapshotIds.put(snapshotId.getName(), new Snapshot(repo, snapshotId)); } } @@ -386,11 +385,11 @@ private void loadSnapshotInfos( sortBy, after, order, - predicates.snapshotPredicate(), + predicates, listener ); } else { - assert predicates.snapshotPredicate() == null : "filtering is not supported in non-verbose mode"; + assert predicates.isMatchAll() : "filtering is not supported in non-verbose mode"; final SnapshotsInRepo snapshotInfos; if (repositoryData != null) { // want non-current snapshots as well, which are found in the repository data @@ -426,7 +425,7 @@ private void snapshots( GetSnapshotsRequest.SortBy sortBy, @Nullable GetSnapshotsRequest.After after, SortOrder order, - @Nullable Predicate predicate, + SnapshotPredicates predicate, ActionListener listener ) { if (task.notifyIfCancelled(listener)) { @@ -443,8 +442,8 @@ private void snapshots( for (SnapshotsInProgress.Entry entry : entries) { if (snapshotIdsToIterate.remove(entry.snapshot().getSnapshotId())) { final SnapshotInfo snapshotInfo = new SnapshotInfo(entry); - if (predicate == null || predicate.test(snapshotInfo)) { - snapshotSet.add(new SnapshotInfo(entry)); + if (predicate.test(snapshotInfo)) { + snapshotSet.add(snapshotInfo); } } } @@ -472,17 +471,11 @@ private void snapshots( return; } repository.getSnapshotInfo( - new GetSnapshotInfoContext( - snapshotIdsToIterate, - ignoreUnavailable == false, - task::isCancelled, - predicate == null ? (context, snapshotInfo) -> snapshotInfos.add(snapshotInfo) : (context, snapshotInfo) -> { - if (predicate.test(snapshotInfo)) { - snapshotInfos.add(snapshotInfo); - } - }, - allDoneListener - ) + new GetSnapshotInfoContext(snapshotIdsToIterate, ignoreUnavailable == false, task::isCancelled, (context, snapshotInfo) -> { + if (predicate.test(snapshotInfo)) { + snapshotInfos.add(snapshotInfo); + } + }, allDoneListener) ); } @@ -672,71 +665,6 @@ private static Predicate buildAfterPredicate( } } - private static SnapshotPredicate filterBySLMPolicies(String[] slmPolicies) { - final List includePatterns = new ArrayList<>(); - final List excludePatterns = new ArrayList<>(); - boolean seenWildcard = false; - boolean matchNoPolicy = false; - for (String slmPolicy : slmPolicies) { - if (seenWildcard && slmPolicy.length() > 1 && slmPolicy.startsWith("-")) { - excludePatterns.add(slmPolicy.substring(1)); - } else { - if (Regex.isSimpleMatchPattern(slmPolicy)) { - seenWildcard = true; - } else if (GetSnapshotsRequest.NO_POLICY_PATTERN.equals(slmPolicy)) { - matchNoPolicy = true; - } - includePatterns.add(slmPolicy); - } - } - final String[] includes = includePatterns.toArray(Strings.EMPTY_ARRAY); - final String[] excludes = excludePatterns.toArray(Strings.EMPTY_ARRAY); - final boolean matchWithoutPolicy = matchNoPolicy; - return new SnapshotPredicate() { - @Override - public boolean matchesPreflight(SnapshotId snapshotId, RepositoryData repositoryData) { - final RepositoryData.SnapshotDetails details = repositoryData.getSnapshotDetails(snapshotId); - final String policy; - if (details == null || (details.getSlmPolicy() == null)) { - // no SLM policy recorded - return true; - } else { - final String policyFound = details.getSlmPolicy(); - // empty string means that snapshot was not created by an SLM policy - policy = policyFound.isEmpty() ? null : policyFound; - } - return matchPolicy(includes, excludes, matchWithoutPolicy, policy); - } - - @Override - public boolean matches(SnapshotInfo snapshotInfo) { - final Map metadata = snapshotInfo.userMetadata(); - final String policy; - if (metadata == null) { - policy = null; - } else { - final Object policyFound = metadata.get(SnapshotsService.POLICY_ID_METADATA_FIELD); - policy = policyFound instanceof String ? (String) policyFound : null; - } - return matchPolicy(includes, excludes, matchWithoutPolicy, policy); - } - }; - } - - private static boolean matchPolicy(String[] includes, String[] excludes, boolean matchWithoutPolicy, @Nullable String policy) { - if (policy == null) { - return matchWithoutPolicy; - } - if (Regex.simpleMatch(includes, policy) == false) { - return false; - } - return excludes.length == 0 || Regex.simpleMatch(excludes, policy) == false; - } - - private static Predicate filterByLongOffset(ToLongFunction extractor, long after, SortOrder order) { - return order == SortOrder.ASC ? info -> after <= extractor.applyAsLong(info) : info -> after >= extractor.applyAsLong(info); - } - private static Predicate filterByLongOffset( ToLongFunction extractor, long after, @@ -770,130 +698,183 @@ private static int compareName(String name, String repoName, SnapshotInfo info) } /** - * A pair of predicates for the get snapshots action. The {@link #preflightPredicate()} is applied to combinations of snapshot id and - * repository data to determine which snapshots to fully load from the repository and rules out all snapshots that do not match the - * given {@link GetSnapshotsRequest} that can be ruled out through the information in {@link RepositoryData}. - * The predicate returned by {@link #snapshotPredicate()} is then applied the instances of {@link SnapshotInfo} that were loaded from - * the repository to filter out those remaining that did not match the request but could not be ruled out without loading their - * {@link SnapshotInfo}. + * A pair of predicates for the get snapshots action. The {@link #test(SnapshotId, RepositoryData)} predicate is applied to combinations + * of snapshot id and repository data to determine which snapshots to fully load from the repository and rules out all snapshots that do + * not match the given {@link GetSnapshotsRequest} that can be ruled out through the information in {@link RepositoryData}. + * The predicate returned by {@link #test(SnapshotInfo)} predicate is then applied the instances of {@link SnapshotInfo} that were + * loaded from the repository to filter out those remaining that did not match the request but could not be ruled out without loading + * their {@link SnapshotInfo}. */ private static final class SnapshotPredicates { - private final Predicate snapshotPredicate; + private static final SnapshotPredicates MATCH_ALL = new SnapshotPredicates(null, null); + @Nullable // if all snapshot IDs match private final BiPredicate preflightPredicate; - SnapshotPredicates(GetSnapshotsRequest request) { - Predicate snapshotPredicate = null; - final String[] slmPolicies = request.policies(); - final String fromSortValue = request.fromSortValue(); - BiPredicate preflightPredicate = null; - if (slmPolicies.length > 0) { - final SnapshotPredicate predicate = filterBySLMPolicies(slmPolicies); - snapshotPredicate = predicate::matches; - preflightPredicate = predicate::matchesPreflight; + @Nullable // if all snapshots match + private final Predicate snapshotPredicate; + + private SnapshotPredicates( + @Nullable BiPredicate preflightPredicate, + @Nullable Predicate snapshotPredicate + ) { + this.snapshotPredicate = snapshotPredicate; + this.preflightPredicate = preflightPredicate; + } + + boolean test(SnapshotId snapshotId, RepositoryData repositoryData) { + return preflightPredicate == null || preflightPredicate.test(snapshotId, repositoryData); + } + + boolean isMatchAll() { + return snapshotPredicate == null; + } + + boolean test(SnapshotInfo snapshotInfo) { + return snapshotPredicate == null || snapshotPredicate.test(snapshotInfo); + } + + private SnapshotPredicates and(SnapshotPredicates other) { + return this == MATCH_ALL ? other + : other == MATCH_ALL ? this + : new SnapshotPredicates( + preflightPredicate == null ? other.preflightPredicate : other.preflightPredicate == null ? preflightPredicate : null, + snapshotPredicate == null ? other.snapshotPredicate : other.snapshotPredicate == null ? snapshotPredicate : null + ); + } + + static SnapshotPredicates fromRequest(GetSnapshotsRequest request) { + return getSortValuePredicate(request.fromSortValue(), request.sort(), request.order()).and( + getSlmPredicates(request.policies()) + ); + } + + private static SnapshotPredicates getSlmPredicates(String[] slmPolicies) { + if (slmPolicies.length == 0) { + return MATCH_ALL; } - final GetSnapshotsRequest.SortBy sortBy = request.sort(); - final SortOrder order = request.order(); - if (fromSortValue == null) { - this.preflightPredicate = preflightPredicate; - } else { - final Predicate fromSortValuePredicate; - final BiPredicate preflightPred; - switch (sortBy) { - case START_TIME: - final long after = Long.parseLong(fromSortValue); - preflightPred = order == SortOrder.ASC ? (snapshotId, repositoryData) -> { - final long startTime = getStartTime(snapshotId, repositoryData); - return startTime == -1 || after <= startTime; - } : (snapshotId, repositoryData) -> { - final long startTime = getStartTime(snapshotId, repositoryData); - return startTime == -1 || after >= startTime; - }; - fromSortValuePredicate = filterByLongOffset(SnapshotInfo::startTime, after, order); - break; - case NAME: - preflightPred = order == SortOrder.ASC - ? (snapshotId, repositoryData) -> fromSortValue.compareTo(snapshotId.getName()) <= 0 - : (snapshotId, repositoryData) -> fromSortValue.compareTo(snapshotId.getName()) >= 0; - fromSortValuePredicate = null; - break; - case DURATION: - final long afterDuration = Long.parseLong(fromSortValue); - preflightPred = order == SortOrder.ASC ? (snapshotId, repositoryData) -> { - final long duration = getDuration(snapshotId, repositoryData); - return duration == -1 || afterDuration <= duration; - } : (snapshotId, repositoryData) -> { - final long duration = getDuration(snapshotId, repositoryData); - return duration == -1 || afterDuration >= duration; - }; - fromSortValuePredicate = filterByLongOffset(info -> info.endTime() - info.startTime(), afterDuration, order); - break; - case INDICES: - final int afterIndexCount = Integer.parseInt(fromSortValue); - preflightPred = order == SortOrder.ASC - ? (snapshotId, repositoryData) -> afterIndexCount <= indexCount(snapshotId, repositoryData) - : (snapshotId, repositoryData) -> afterIndexCount >= indexCount(snapshotId, repositoryData); - fromSortValuePredicate = null; - break; - case REPOSITORY: - // already handled in #maybeFilterRepositories - preflightPred = null; - fromSortValuePredicate = null; - break; - case SHARDS: - preflightPred = null; - fromSortValuePredicate = filterByLongOffset(SnapshotInfo::totalShards, Integer.parseInt(fromSortValue), order); - break; - case FAILED_SHARDS: - preflightPred = null; - fromSortValuePredicate = filterByLongOffset(SnapshotInfo::failedShards, Integer.parseInt(fromSortValue), order); - break; - default: - throw new AssertionError("unexpected sort column [" + sortBy + "]"); - } - if (snapshotPredicate == null) { - snapshotPredicate = fromSortValuePredicate; - } else if (fromSortValuePredicate != null) { - snapshotPredicate = fromSortValuePredicate.and(snapshotPredicate); - } - if (preflightPredicate == null) { - this.preflightPredicate = preflightPred; + final List includePatterns = new ArrayList<>(); + final List excludePatterns = new ArrayList<>(); + boolean seenWildcard = false; + boolean matchNoPolicy = false; + for (String slmPolicy : slmPolicies) { + if (seenWildcard && slmPolicy.length() > 1 && slmPolicy.startsWith("-")) { + excludePatterns.add(slmPolicy.substring(1)); } else { - if (preflightPred != null) { - this.preflightPredicate = preflightPredicate.and(preflightPred); - } else { - this.preflightPredicate = preflightPredicate; + if (Regex.isSimpleMatchPattern(slmPolicy)) { + seenWildcard = true; + } else if (GetSnapshotsRequest.NO_POLICY_PATTERN.equals(slmPolicy)) { + matchNoPolicy = true; } + includePatterns.add(slmPolicy); } } - this.snapshotPredicate = snapshotPredicate; - } - - @Nullable - public Predicate snapshotPredicate() { - return snapshotPredicate; + final String[] includes = includePatterns.toArray(Strings.EMPTY_ARRAY); + final String[] excludes = excludePatterns.toArray(Strings.EMPTY_ARRAY); + final boolean matchWithoutPolicy = matchNoPolicy; + return new SnapshotPredicates(((snapshotId, repositoryData) -> { + final RepositoryData.SnapshotDetails details = repositoryData.getSnapshotDetails(snapshotId); + final String policy; + if (details == null || (details.getSlmPolicy() == null)) { + // no SLM policy recorded + return true; + } else { + final String policyFound = details.getSlmPolicy(); + // empty string means that snapshot was not created by an SLM policy + policy = policyFound.isEmpty() ? null : policyFound; + } + return matchPolicy(includes, excludes, matchWithoutPolicy, policy); + }), snapshotInfo -> { + final Map metadata = snapshotInfo.userMetadata(); + final String policy; + if (metadata == null) { + policy = null; + } else { + final Object policyFound = metadata.get(SnapshotsService.POLICY_ID_METADATA_FIELD); + policy = policyFound instanceof String ? (String) policyFound : null; + } + return matchPolicy(includes, excludes, matchWithoutPolicy, policy); + }); } - @Nullable - public BiPredicate preflightPredicate() { - return preflightPredicate; + private static boolean matchPolicy(String[] includes, String[] excludes, boolean matchWithoutPolicy, @Nullable String policy) { + if (policy == null) { + return matchWithoutPolicy; + } + if (Regex.simpleMatch(includes, policy) == false) { + return false; + } + return excludes.length == 0 || Regex.simpleMatch(excludes, policy) == false; } - } + private static SnapshotPredicates getSortValuePredicate(String fromSortValue, GetSnapshotsRequest.SortBy sortBy, SortOrder order) { + if (fromSortValue == null) { + return MATCH_ALL; + } - private interface SnapshotPredicate { + switch (sortBy) { + case START_TIME: + final long after = Long.parseLong(fromSortValue); + return new SnapshotPredicates(order == SortOrder.ASC ? (snapshotId, repositoryData) -> { + final long startTime = getStartTime(snapshotId, repositoryData); + return startTime == -1 || after <= startTime; + } : (snapshotId, repositoryData) -> { + final long startTime = getStartTime(snapshotId, repositoryData); + return startTime == -1 || after >= startTime; + }, filterByLongOffset(SnapshotInfo::startTime, after, order)); + + case NAME: + return new SnapshotPredicates( + order == SortOrder.ASC + ? (snapshotId, repositoryData) -> fromSortValue.compareTo(snapshotId.getName()) <= 0 + : (snapshotId, repositoryData) -> fromSortValue.compareTo(snapshotId.getName()) >= 0, + null + ); + + case DURATION: + final long afterDuration = Long.parseLong(fromSortValue); + return new SnapshotPredicates(order == SortOrder.ASC ? (snapshotId, repositoryData) -> { + final long duration = getDuration(snapshotId, repositoryData); + return duration == -1 || afterDuration <= duration; + } : (snapshotId, repositoryData) -> { + final long duration = getDuration(snapshotId, repositoryData); + return duration == -1 || afterDuration >= duration; + }, filterByLongOffset(info -> info.endTime() - info.startTime(), afterDuration, order)); + + case INDICES: + final int afterIndexCount = Integer.parseInt(fromSortValue); + return new SnapshotPredicates( + order == SortOrder.ASC + ? (snapshotId, repositoryData) -> afterIndexCount <= indexCount(snapshotId, repositoryData) + : (snapshotId, repositoryData) -> afterIndexCount >= indexCount(snapshotId, repositoryData), + null + ); + + case REPOSITORY: + // already handled in #maybeFilterRepositories + return MATCH_ALL; + + case SHARDS: + return new SnapshotPredicates( + null, + filterByLongOffset(SnapshotInfo::totalShards, Integer.parseInt(fromSortValue), order) + ); + case FAILED_SHARDS: + return new SnapshotPredicates( + null, + filterByLongOffset(SnapshotInfo::failedShards, Integer.parseInt(fromSortValue), order) + ); + default: + throw new AssertionError("unexpected sort column [" + sortBy + "]"); + } + } - /** - * Checks if a snapshot matches the predicate by testing its {@link SnapshotId} for a given {@link RepositoryData}. - */ - boolean matchesPreflight(SnapshotId snapshotId, RepositoryData repositoryData); + private static Predicate filterByLongOffset(ToLongFunction extractor, long after, SortOrder order) { + return order == SortOrder.ASC ? info -> after <= extractor.applyAsLong(info) : info -> after >= extractor.applyAsLong(info); + } - /** - * Checks if a snapshot matches the predicate by testing its {@link SnapshotInfo}. - */ - boolean matches(SnapshotInfo snapshotInfo); } private static final class SnapshotsInRepo {