diff --git a/docs/changelog/128273.yaml b/docs/changelog/128273.yaml new file mode 100644 index 0000000000000..0f6a7ce2561d7 --- /dev/null +++ b/docs/changelog/128273.yaml @@ -0,0 +1,5 @@ +pr: 128273 +summary: Improve get-snapshots message for unreadable repository +area: Snapshot/Restore +type: enhancement +issues: [] diff --git a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/GetSnapshotsIT.java b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/GetSnapshotsIT.java index c0e63520fee9e..0923b4a3bfda9 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/GetSnapshotsIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/GetSnapshotsIT.java @@ -40,7 +40,9 @@ import org.elasticsearch.core.Predicates; import org.elasticsearch.repositories.RepositoriesService; import org.elasticsearch.repositories.RepositoryData; +import org.elasticsearch.repositories.RepositoryException; import org.elasticsearch.repositories.RepositoryMissingException; +import org.elasticsearch.repositories.blobstore.BlobStoreRepository; import org.elasticsearch.repositories.fs.FsRepository; import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.test.ESTestCase; @@ -633,6 +635,48 @@ public void testRetrievingSnapshotsWhenRepositoryIsMissing() throws Exception { expectThrows(RepositoryMissingException.class, multiRepoFuture::actionGet); } + public void testRetrievingSnapshotsWhenRepositoryIsUnreadable() throws Exception { + final String repoName = randomIdentifier(); + final Path repoPath = randomRepoPath(); + createRepository( + repoName, + "fs", + Settings.builder().put("location", repoPath).put(BlobStoreRepository.CACHE_REPOSITORY_DATA.getKey(), false) + ); + createNSnapshots(repoName, randomIntBetween(1, 3)); + + try { + try (var directoryStream = Files.newDirectoryStream(repoPath)) { + for (final var directoryEntry : directoryStream) { + if (Files.isRegularFile(directoryEntry) && directoryEntry.getFileName().toString().startsWith("index-")) { + Files.writeString(directoryEntry, "invalid"); + } + } + } + + final var repositoryException = safeAwaitAndUnwrapFailure( + RepositoryException.class, + GetSnapshotsResponse.class, + l -> clusterAdmin().prepareGetSnapshots(TEST_REQUEST_TIMEOUT, repoName) + .setSort(SnapshotSortKey.NAME) + .setIgnoreUnavailable(randomBoolean()) + .execute(l) + ); + assertEquals( + Strings.format("[%s] cannot retrieve snapshots list from this repository", repoName), + repositoryException.getMessage() + ); + assertEquals( + Strings.format("[%s] Unexpected exception when loading repository data", repoName), + repositoryException.getCause().getMessage() + ); + } finally { + safeAwait( + l -> clusterAdmin().prepareDeleteRepository(TEST_REQUEST_TIMEOUT, TEST_REQUEST_TIMEOUT, repoName).execute(l.map(v -> null)) + ); + } + } + // Create a snapshot that is guaranteed to have a unique start time and duration for tests around ordering by either. // Don't use this with more than 3 snapshots on platforms with low-resolution clocks as the durations could always collide there // causing an infinite loop 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 3e0411930191a..09ea80a5bff88 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 @@ -38,6 +38,7 @@ import org.elasticsearch.repositories.RepositoriesService; import org.elasticsearch.repositories.Repository; import org.elasticsearch.repositories.RepositoryData; +import org.elasticsearch.repositories.RepositoryException; import org.elasticsearch.repositories.RepositoryMissingException; import org.elasticsearch.repositories.ResolvedRepositories; import org.elasticsearch.search.sort.SortOrder; @@ -285,7 +286,20 @@ private void populateResults(ActionListener listener) { ), repositoryName -> asyncRepositoryContentsListener -> SubscribableListener - .newForked(l -> maybeGetRepositoryData(repositoryName, l)) + .newForked( + l -> maybeGetRepositoryData( + repositoryName, + l.delegateResponse( + (ll, e) -> ll.onFailure( + new RepositoryException( + repositoryName, + "cannot retrieve snapshots list from this repository", + e + ) + ) + ) + ) + ) .andThenApply(repositoryData -> { assert ThreadPool.assertCurrentThreadPool(ThreadPool.Names.MANAGEMENT); cancellableTask.ensureNotCancelled();