diff --git a/server/src/internalClusterTest/java/org/opensearch/snapshots/DeleteSnapshotIT.java b/server/src/internalClusterTest/java/org/opensearch/snapshots/DeleteSnapshotIT.java index e688a4491b1a7..4eadbfbedca61 100644 --- a/server/src/internalClusterTest/java/org/opensearch/snapshots/DeleteSnapshotIT.java +++ b/server/src/internalClusterTest/java/org/opensearch/snapshots/DeleteSnapshotIT.java @@ -21,6 +21,7 @@ import org.opensearch.index.store.RemoteBufferedOutputDirectory; import org.opensearch.remotestore.RemoteStoreBaseIntegTestCase; import org.opensearch.repositories.RepositoriesService; +import org.opensearch.repositories.RepositoryData; import org.opensearch.repositories.blobstore.BlobStoreRepository; import org.opensearch.test.OpenSearchIntegTestCase; @@ -30,14 +31,13 @@ import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import java.util.stream.Stream; import static org.opensearch.index.remote.RemoteStoreEnums.DataCategory.SEGMENTS; import static org.opensearch.index.remote.RemoteStoreEnums.DataType.LOCK_FILES; -import static org.opensearch.remotestore.RemoteStoreBaseIntegTestCase.remoteStoreClusterSettings; import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertAcked; import static org.hamcrest.Matchers.comparesEqualTo; -import static org.hamcrest.Matchers.is; @OpenSearchIntegTestCase.ClusterScope(scope = OpenSearchIntegTestCase.Scope.TEST, numDataNodes = 0) public class DeleteSnapshotIT extends AbstractSnapshotIntegTestCase { @@ -89,17 +89,23 @@ public void testDeleteShallowCopySnapshot() throws Exception { indexRandomDocs(remoteStoreEnabledIndexName, randomIntBetween(5, 10)); final String shallowSnapshot = "shallow-snapshot"; - createFullSnapshot(snapshotRepoName, shallowSnapshot); - assert (getLockFilesInRemoteStore(remoteStoreEnabledIndexName, REMOTE_REPO_NAME).length == 1); - assert (getRepositoryData(snapshotRepoName).getSnapshotIds().size() == 1); + SnapshotInfo snapshotInfo = createFullSnapshot(snapshotRepoName, shallowSnapshot); + assertEquals(1, getLockFilesInRemoteStore(remoteStoreEnabledIndexName, REMOTE_REPO_NAME).length); + RepositoryData repositoryData = getRepositoryData(snapshotRepoName); + assertEquals(1, repositoryData.getSnapshotIds().size()); + assertSame(repositoryData.getSnapshotType(snapshotInfo.snapshotId()), SnapshotType.SHALLOW_COPY); + // Delete snapshot assertAcked(startDeleteSnapshot(snapshotRepoName, shallowSnapshot).get()); - assert (getRepositoryData(snapshotRepoName).getSnapshotIds().size() == 0); - assert (getLockFilesInRemoteStore(remoteStoreEnabledIndexName, REMOTE_REPO_NAME).length == 0); + + // Get updated repository data. + repositoryData = getRepositoryData(snapshotRepoName); + assertEquals(0, repositoryData.getSnapshotIds().size()); + assertEquals(0, getLockFilesInRemoteStore(remoteStoreEnabledIndexName, REMOTE_REPO_NAME).length); + assertNull(repositoryData.getSnapshotType(snapshotInfo.snapshotId())); } // Deleting multiple shallow copy snapshots as part of single delete call with repo having only shallow copy snapshots. - @AwaitsFix(bugUrl = "https://github.com/opensearch-project/OpenSearch/issues/9208") public void testDeleteMultipleShallowCopySnapshotsCase1() throws Exception { disableRepoConsistencyCheck("Remote store repository is being used in the test"); final Path remoteStoreRepoPath = randomRepoPath(); @@ -121,19 +127,39 @@ public void testDeleteMultipleShallowCopySnapshotsCase1() throws Exception { // Creating some shallow copy snapshots int totalShallowCopySnapshotsCount = randomIntBetween(4, 10); - List shallowCopySnapshots = createNSnapshots(snapshotRepoName, totalShallowCopySnapshotsCount); - List snapshotsToBeDeleted = shallowCopySnapshots.subList(0, randomIntBetween(2, totalShallowCopySnapshotsCount)); + List shallowCopySnapshots = createNSnapshots(snapshotRepoName, totalShallowCopySnapshotsCount); + List snapshotsToBeDeleted = shallowCopySnapshots.subList(0, randomIntBetween(2, totalShallowCopySnapshotsCount)); int tobeDeletedSnapshotsCount = snapshotsToBeDeleted.size(); assert (getLockFilesInRemoteStore(remoteStoreEnabledIndexName, REMOTE_REPO_NAME).length == totalShallowCopySnapshotsCount); - assert (getRepositoryData(snapshotRepoName).getSnapshotIds().size() == totalShallowCopySnapshotsCount); + + RepositoryData repositoryData = getRepositoryData(snapshotRepoName); + assert (repositoryData.getSnapshotIds().size() == totalShallowCopySnapshotsCount); + + validateSnapshotTypes(repositoryData, shallowCopySnapshots, null, null); // Deleting subset of shallow copy snapshots assertAcked( clusterManagerClient.admin() .cluster() - .prepareDeleteSnapshot(snapshotRepoName, snapshotsToBeDeleted.toArray(new String[0])) + .prepareDeleteSnapshot(snapshotRepoName, snapshotsToBeDeleted.stream().map(SnapshotId::getName).toArray(String[]::new)) .get() ); - assert (getRepositoryData(snapshotRepoName).getSnapshotIds().size() == totalShallowCopySnapshotsCount - tobeDeletedSnapshotsCount); + + // getting updated repository data + repositoryData = getRepositoryData(snapshotRepoName); + for (SnapshotId snapshotId : shallowCopySnapshots) { + if (snapshotsToBeDeleted.contains(snapshotId)) { + assertNull(repositoryData.getSnapshotType(snapshotId)); + } else { + assertEquals(repositoryData.getSnapshotType(snapshotId), SnapshotType.SHALLOW_COPY); + } + } + validateSnapshotTypes( + repositoryData, + shallowCopySnapshots.stream().filter(snapId -> !snapshotsToBeDeleted.contains(snapId)).collect(Collectors.toList()), + null, + snapshotsToBeDeleted + ); + assert (repositoryData.getSnapshotIds().size() == totalShallowCopySnapshotsCount - tobeDeletedSnapshotsCount); assert (getLockFilesInRemoteStore(remoteStoreEnabledIndexName, REMOTE_REPO_NAME).length == totalShallowCopySnapshotsCount - tobeDeletedSnapshotsCount); } @@ -141,7 +167,6 @@ public void testDeleteMultipleShallowCopySnapshotsCase1() throws Exception { // Deleting multiple shallow copy snapshots as part of single delete call with both partial and full copy snapshot present in the repo // And then deleting multiple full copy snapshots as part of single delete call with both partial and shallow copy snapshots present in // the repo - @AwaitsFix(bugUrl = "https://github.com/opensearch-project/OpenSearch/issues/8610") public void testDeleteMultipleShallowCopySnapshotsCase2() throws Exception { disableRepoConsistencyCheck("Remote store repository is being used in the test"); final Path remoteStoreRepoPath = randomRepoPath(); @@ -153,79 +178,121 @@ public void testDeleteMultipleShallowCopySnapshotsCase2() throws Exception { final String snapshotRepoName = "snapshot-repo-name"; final Path snapshotRepoPath = randomRepoPath(); createRepository(snapshotRepoName, "mock", snapshotRepoSettingsForShallowCopy(snapshotRepoPath)); - final String testIndex = "index-test"; + final String testIndex = "remote-index-1"; createIndexWithContent(testIndex); + ensureGreen(testIndex); - final String remoteStoreEnabledIndexName = "remote-index-1"; - final Settings remoteStoreEnabledIndexSettings = getRemoteStoreBackedIndexSettings(); - createIndex(remoteStoreEnabledIndexName, remoteStoreEnabledIndexSettings); - indexRandomDocs(remoteStoreEnabledIndexName, randomIntBetween(5, 10)); + final String testIndex2 = "remote-index-2"; + createIndexWithContent(testIndex2); + ensureGreen(testIndex, testIndex2); // Creating a partial shallow copy snapshot final String snapshot = "snapshot"; - blockNodeWithIndex(snapshotRepoName, testIndex); - blockDataNode(snapshotRepoName, dataNode); final Client clusterManagerClient = internalCluster().clusterManagerClient(); + + int partialShallowCopySnapshotCount = 1; final ActionFuture snapshotFuture = clusterManagerClient.admin() .cluster() .prepareCreateSnapshot(snapshotRepoName, snapshot) .setWaitForCompletion(true) + .setPartial(true) .execute(); + blockNodeOnAnyFiles(snapshotRepoName, dataNode); awaitNumberOfSnapshotsInProgress(1); waitForBlock(dataNode, snapshotRepoName, TimeValue.timeValueSeconds(30L)); internalCluster().restartNode(dataNode); - assertThat(snapshotFuture.get().getSnapshotInfo().state(), is(SnapshotState.PARTIAL)); + SnapshotInfo partialSnapshotInfo = snapshotFuture.get().getSnapshotInfo(); + assertEquals(partialSnapshotInfo.state(), SnapshotState.PARTIAL); unblockAllDataNodes(snapshotRepoName); ensureStableCluster(2, clusterManagerNode); + String[] initialLockFiles = getLockFilesInRemoteStore(testIndex2, REMOTE_REPO_NAME); // Creating some shallow copy snapshots - int totalShallowCopySnapshotsCount = randomIntBetween(4, 10); - List shallowCopySnapshots = createNSnapshots(snapshotRepoName, totalShallowCopySnapshotsCount); - List shallowCopySnapshotsToBeDeleted = shallowCopySnapshots.subList(0, randomIntBetween(2, totalShallowCopySnapshotsCount)); + int fullShallowCopySnapshotsCount = randomIntBetween(4, 10); + List shallowCopySnapshots = createNSnapshots(snapshotRepoName, fullShallowCopySnapshotsCount); + + List shallowCopySnapshotsToBeDeleted = shallowCopySnapshots.subList( + 0, + randomIntBetween(2, fullShallowCopySnapshotsCount) + ); int tobeDeletedShallowCopySnapshotsCount = shallowCopySnapshotsToBeDeleted.size(); - totalShallowCopySnapshotsCount += 1; // Adding partial shallow snapshot here + // TODO: remove this line: successfulShallowCopySnapshotsCount += 1; // Adding partial shallow snapshot here // Updating the snapshot repository flag to disable shallow snapshots createRepository(snapshotRepoName, "mock", snapshotRepoPath); // Creating some full copy snapshots int totalFullCopySnapshotsCount = randomIntBetween(4, 10); - List fullCopySnapshots = createNSnapshots(snapshotRepoName, totalFullCopySnapshotsCount); - List fullCopySnapshotsToBeDeleted = fullCopySnapshots.subList(0, randomIntBetween(2, totalFullCopySnapshotsCount)); + List fullCopySnapshots = createNSnapshots(snapshotRepoName, totalFullCopySnapshotsCount); + List fullCopySnapshotsToBeDeleted = fullCopySnapshots.stream() + .filter(snapshotId -> snapshotId != partialSnapshotInfo.snapshotId()) + .collect(Collectors.toList()) + .subList(0, randomIntBetween(2, totalFullCopySnapshotsCount)); int tobeDeletedFullCopySnapshotsCount = fullCopySnapshotsToBeDeleted.size(); - int totalSnapshotsCount = totalFullCopySnapshotsCount + totalShallowCopySnapshotsCount; + int totalSnapshotsCount = totalFullCopySnapshotsCount + fullShallowCopySnapshotsCount + partialShallowCopySnapshotCount; + + String[] lockFiles = getLockFilesInRemoteStore(testIndex2, REMOTE_REPO_NAME); + + assertEquals(lockFiles.length - initialLockFiles.length, fullShallowCopySnapshotsCount); + RepositoryData repositoryData = getRepositoryData(snapshotRepoName); + assert (repositoryData.getSnapshotIds().size() == totalSnapshotsCount); + + // Validate snapshot types list in repository data. + validateSnapshotTypes(repositoryData, shallowCopySnapshots, fullCopySnapshots, null); - assert (getLockFilesInRemoteStore(remoteStoreEnabledIndexName, REMOTE_REPO_NAME).length == totalShallowCopySnapshotsCount); - assert (getRepositoryData(snapshotRepoName).getSnapshotIds().size() == totalSnapshotsCount); // Deleting subset of shallow copy snapshots assertAcked( clusterManagerClient.admin() .cluster() - .prepareDeleteSnapshot(snapshotRepoName, shallowCopySnapshotsToBeDeleted.toArray(new String[0])) + .prepareDeleteSnapshot( + snapshotRepoName, + shallowCopySnapshotsToBeDeleted.stream().map(SnapshotId::getName).toArray(String[]::new) + ) .get() ); totalSnapshotsCount -= tobeDeletedShallowCopySnapshotsCount; - totalShallowCopySnapshotsCount -= tobeDeletedShallowCopySnapshotsCount; - assert (getRepositoryData(snapshotRepoName).getSnapshotIds().size() == totalSnapshotsCount); - assert (getLockFilesInRemoteStore(remoteStoreEnabledIndexName, REMOTE_REPO_NAME).length == totalShallowCopySnapshotsCount); + fullShallowCopySnapshotsCount -= tobeDeletedShallowCopySnapshotsCount; + // Get updated repository data. + repositoryData = getRepositoryData(snapshotRepoName); + assert (repositoryData.getSnapshotIds().size() == totalSnapshotsCount); + assert (getLockFilesInRemoteStore(testIndex2, REMOTE_REPO_NAME).length - initialLockFiles.length == fullShallowCopySnapshotsCount); + // Validate snapshot types list in repository data. + validateSnapshotTypes( + repositoryData, + shallowCopySnapshots.stream().filter(snapId -> !shallowCopySnapshotsToBeDeleted.contains(snapId)).collect(Collectors.toList()), + fullCopySnapshots, + shallowCopySnapshotsToBeDeleted + ); // Deleting subset of full copy snapshots assertAcked( clusterManagerClient.admin() .cluster() - .prepareDeleteSnapshot(snapshotRepoName, fullCopySnapshotsToBeDeleted.toArray(new String[0])) + .prepareDeleteSnapshot( + snapshotRepoName, + fullCopySnapshotsToBeDeleted.stream().map(SnapshotId::getName).toArray(String[]::new) + ) .get() ); totalSnapshotsCount -= tobeDeletedFullCopySnapshotsCount; - assert (getRepositoryData(snapshotRepoName).getSnapshotIds().size() == totalSnapshotsCount); - assert (getLockFilesInRemoteStore(remoteStoreEnabledIndexName, REMOTE_REPO_NAME).length == totalShallowCopySnapshotsCount); + // Get updated repository data. + repositoryData = getRepositoryData(snapshotRepoName); + assert (repositoryData.getSnapshotIds().size() == totalSnapshotsCount); + assert (getLockFilesInRemoteStore(testIndex2, REMOTE_REPO_NAME).length - initialLockFiles.length == fullShallowCopySnapshotsCount); + + // Validate snapshot types list in repository data. + validateSnapshotTypes( + repositoryData, + shallowCopySnapshots.stream().filter(snapId -> !shallowCopySnapshotsToBeDeleted.contains(snapId)).collect(Collectors.toList()), + fullCopySnapshots.stream().filter(snapId -> !fullCopySnapshotsToBeDeleted.contains(snapId)).collect(Collectors.toList()), + Stream.concat(shallowCopySnapshotsToBeDeleted.stream(), fullCopySnapshotsToBeDeleted.stream()).collect(Collectors.toList()) + ); } // Deleting subset of shallow and full copy snapshots as part of single delete call and then deleting all snapshots in the repo. - @AwaitsFix(bugUrl = "https://github.com/opensearch-project/OpenSearch/issues/8610") public void testDeleteMultipleShallowCopySnapshotsCase3() throws Exception { disableRepoConsistencyCheck("Remote store repository is being used in the test"); final Path remoteStoreRepoPath = randomRepoPath(); @@ -248,40 +315,67 @@ public void testDeleteMultipleShallowCopySnapshotsCase3() throws Exception { // Creating some shallow copy snapshots int totalShallowCopySnapshotsCount = randomIntBetween(4, 10); - List shallowCopySnapshots = createNSnapshots(snapshotRepoName, totalShallowCopySnapshotsCount); - List shallowCopySnapshotsToBeDeleted = shallowCopySnapshots.subList(0, randomIntBetween(2, totalShallowCopySnapshotsCount)); + List shallowCopySnapshots = createNSnapshots(snapshotRepoName, totalShallowCopySnapshotsCount); + List shallowCopySnapshotsToBeDeleted = shallowCopySnapshots.subList( + 0, + randomIntBetween(2, totalShallowCopySnapshotsCount) + ); int tobeDeletedShallowCopySnapshotsCount = shallowCopySnapshotsToBeDeleted.size(); // Updating the snapshot repository flag to disable shallow snapshots createRepository(snapshotRepoName, "mock", snapshotRepoPath); // Creating some full copy snapshots int totalFullCopySnapshotsCount = randomIntBetween(4, 10); - List fullCopySnapshots = createNSnapshots(snapshotRepoName, totalFullCopySnapshotsCount); - List fullCopySnapshotsToBeDeleted = fullCopySnapshots.subList(0, randomIntBetween(2, totalFullCopySnapshotsCount)); + List fullCopySnapshots = createNSnapshots(snapshotRepoName, totalFullCopySnapshotsCount); + List fullCopySnapshotsToBeDeleted = fullCopySnapshots.subList(0, randomIntBetween(2, totalFullCopySnapshotsCount)); int tobeDeletedFullCopySnapshotsCount = fullCopySnapshotsToBeDeleted.size(); int totalSnapshotsCount = totalFullCopySnapshotsCount + totalShallowCopySnapshotsCount; assert (getLockFilesInRemoteStore(remoteStoreEnabledIndexName, REMOTE_REPO_NAME).length == totalShallowCopySnapshotsCount); - assert (getRepositoryData(snapshotRepoName).getSnapshotIds().size() == totalSnapshotsCount); + + RepositoryData repositoryData = getRepositoryData(snapshotRepoName); + assert (repositoryData.getSnapshotIds().size() == totalSnapshotsCount); + validateSnapshotTypes(repositoryData, shallowCopySnapshots, fullCopySnapshots, null); // Deleting subset of shallow copy snapshots and full copy snapshots assertAcked( clusterManagerClient.admin() .cluster() .prepareDeleteSnapshot( snapshotRepoName, - Stream.concat(shallowCopySnapshotsToBeDeleted.stream(), fullCopySnapshotsToBeDeleted.stream()).toArray(String[]::new) + Stream.concat( + shallowCopySnapshotsToBeDeleted.stream().map(SnapshotId::getName), + fullCopySnapshotsToBeDeleted.stream().map(SnapshotId::getName) + ).toArray(String[]::new) ) .get() ); totalSnapshotsCount -= (tobeDeletedShallowCopySnapshotsCount + tobeDeletedFullCopySnapshotsCount); totalShallowCopySnapshotsCount -= tobeDeletedShallowCopySnapshotsCount; - assert (getRepositoryData(snapshotRepoName).getSnapshotIds().size() == totalSnapshotsCount); + // Get updated repository data. + repositoryData = getRepositoryData(snapshotRepoName); + assert (repositoryData.getSnapshotIds().size() == totalSnapshotsCount); assert (getLockFilesInRemoteStore(remoteStoreEnabledIndexName, REMOTE_REPO_NAME).length == totalShallowCopySnapshotsCount); + validateSnapshotTypes( + repositoryData, + shallowCopySnapshots.stream().filter(snapId -> !shallowCopySnapshotsToBeDeleted.contains(snapId)).collect(Collectors.toList()), + fullCopySnapshots.stream().filter(snapId -> !fullCopySnapshotsToBeDeleted.contains(snapId)).collect(Collectors.toList()), + Stream.concat(shallowCopySnapshotsToBeDeleted.stream(), fullCopySnapshotsToBeDeleted.stream()).collect(Collectors.toList()) + ); + // Deleting all the remaining snapshots assertAcked(clusterManagerClient.admin().cluster().prepareDeleteSnapshot(snapshotRepoName, "*").get()); - assert (getRepositoryData(snapshotRepoName).getSnapshotIds().size() == 0); + // Get updated repository data. + repositoryData = getRepositoryData(snapshotRepoName); + assert (repositoryData.getSnapshotIds().size() == 0); assert (getLockFilesInRemoteStore(remoteStoreEnabledIndexName, REMOTE_REPO_NAME).length == 0); + + validateSnapshotTypes( + repositoryData, + null, + null, + Stream.concat(shallowCopySnapshots.stream(), fullCopySnapshots.stream()).collect(Collectors.toList()) + ); } public void testRemoteStoreCleanupForDeletedIndex() throws Exception { @@ -363,15 +457,38 @@ public void testRemoteStoreCleanupForDeletedIndex() throws Exception { }, 30, TimeUnit.SECONDS); } - private List createNSnapshots(String repoName, int count) { - final List snapshotNames = new ArrayList<>(count); + private List createNSnapshots(String repoName, int count) { + final List snapshotIds = new ArrayList<>(count); final String prefix = "snap-" + UUIDs.randomBase64UUID(random()).toLowerCase(Locale.ROOT) + "-"; for (int i = 0; i < count; i++) { final String name = prefix + i; - createFullSnapshot(repoName, name); - snapshotNames.add(name); + SnapshotInfo snapshotInfo = createFullSnapshot(repoName, name); + snapshotIds.add(snapshotInfo.snapshotId()); + } + logger.info("--> created {} in [{}]", snapshotIds, repoName); + return snapshotIds; + } + + private void validateSnapshotTypes( + RepositoryData repositoryData, + List shallowSnaps, + List fullSnaps, + List shouldNotPresentSnaps + ) { + if (shallowSnaps != null) { + for (SnapshotId snapId : shallowSnaps) { + assertEquals(repositoryData.getSnapshotType(snapId), SnapshotType.SHALLOW_COPY); + } + } + if (fullSnaps != null) { + for (SnapshotId snapId : fullSnaps) { + assertEquals(repositoryData.getSnapshotType(snapId), SnapshotType.FULL_COPY); + } + } + if (shouldNotPresentSnaps != null) { + for (SnapshotId snapId : shouldNotPresentSnaps) { + assertNull(repositoryData.getSnapshotType(snapId)); + } } - logger.info("--> created {} in [{}]", snapshotNames, repoName); - return snapshotNames; } } diff --git a/server/src/main/java/org/opensearch/repositories/RepositoryData.java b/server/src/main/java/org/opensearch/repositories/RepositoryData.java index ea48d9b1a49fe..0ab5643cda240 100644 --- a/server/src/main/java/org/opensearch/repositories/RepositoryData.java +++ b/server/src/main/java/org/opensearch/repositories/RepositoryData.java @@ -43,6 +43,7 @@ import org.opensearch.core.xcontent.XContentParserUtils; import org.opensearch.snapshots.SnapshotId; import org.opensearch.snapshots.SnapshotState; +import org.opensearch.snapshots.SnapshotType; import java.io.IOException; import java.util.ArrayList; @@ -92,7 +93,8 @@ public final class RepositoryData { Collections.emptyMap(), Collections.emptyMap(), ShardGenerations.EMPTY, - IndexMetaDataGenerations.EMPTY + IndexMetaDataGenerations.EMPTY, + Collections.emptyMap() ); /** @@ -128,6 +130,11 @@ public final class RepositoryData { */ private final ShardGenerations shardGenerations; + /** + * snapshot UUID to snapshot type map. + */ + private final Map snapshotTypes; + public RepositoryData( long genId, Map snapshotIds, @@ -135,7 +142,8 @@ public RepositoryData( Map snapshotVersions, Map> indexSnapshots, ShardGenerations shardGenerations, - IndexMetaDataGenerations indexMetaDataGenerations + IndexMetaDataGenerations indexMetaDataGenerations, + Map snapshotTypes ) { this( genId, @@ -145,7 +153,8 @@ public RepositoryData( Collections.unmodifiableMap(indexSnapshots.keySet().stream().collect(Collectors.toMap(IndexId::getName, Function.identity()))), Collections.unmodifiableMap(indexSnapshots), shardGenerations, - indexMetaDataGenerations + indexMetaDataGenerations, + snapshotTypes ); } @@ -157,7 +166,8 @@ private RepositoryData( Map indices, Map> indexSnapshots, ShardGenerations shardGenerations, - IndexMetaDataGenerations indexMetaDataGenerations + IndexMetaDataGenerations indexMetaDataGenerations, + Map snapshotTypes ) { this.genId = genId; this.snapshotIds = snapshotIds; @@ -167,6 +177,7 @@ private RepositoryData( this.shardGenerations = shardGenerations; this.indexMetaDataGenerations = indexMetaDataGenerations; this.snapshotVersions = snapshotVersions; + this.snapshotTypes = snapshotTypes; assert indices.values().containsAll(shardGenerations.indices()) : "ShardGenerations contained indices " + shardGenerations.indices() + " but snapshots only reference indices " @@ -183,7 +194,8 @@ protected RepositoryData copy() { snapshotVersions, indexSnapshots, shardGenerations, - indexMetaDataGenerations + indexMetaDataGenerations, + snapshotTypes ); } @@ -205,7 +217,31 @@ public RepositoryData withVersions(Map versions) { newVersions, indexSnapshots, shardGenerations, - indexMetaDataGenerations + indexMetaDataGenerations, + snapshotTypes + ); + } + + /** + * Creates a copy of this instance that contains updated snapshot type data. + * @param newSnapshotTypes map of snapshot tp snapshot types + * @return copy with updated snapshot type data + */ + public RepositoryData withSnapshotTypes(Map newSnapshotTypes) { + if (newSnapshotTypes.isEmpty()) { + return this; + } + final Map udpatedSnapshotTypes = new HashMap<>(snapshotTypes); + newSnapshotTypes.forEach((id, type) -> udpatedSnapshotTypes.put(id.getUUID(), type)); + return new RepositoryData( + genId, + snapshotIds, + snapshotStates, + snapshotVersions, + indexSnapshots, + shardGenerations, + indexMetaDataGenerations, + udpatedSnapshotTypes ); } @@ -243,6 +279,13 @@ public Version getVersion(SnapshotId snapshotId) { return snapshotVersions.get(snapshotId.getUUID()); } + /** + * Returns the {@link SnapshotType} for the given snapshot or {@code null} if unknown. + */ + public SnapshotType getSnapshotType(SnapshotId snapshotId) { + return this.snapshotTypes.get(snapshotId.getUUID()); + } + /** * Returns an unmodifiable map of the index names to {@link IndexId} in the repository. */ @@ -319,7 +362,8 @@ public RepositoryData addSnapshot( final Version version, final ShardGenerations shardGenerations, @Nullable final Map indexMetaBlobs, - @Nullable final Map newIdentifiers + @Nullable final Map newIdentifiers, + final SnapshotType snapshotType ) { if (snapshotIds.containsKey(snapshotId.getUUID())) { // if the snapshot id already exists in the repository data, it means an old master @@ -333,6 +377,10 @@ public RepositoryData addSnapshot( newSnapshotStates.put(snapshotId.getUUID(), snapshotState); Map newSnapshotVersions = new HashMap<>(snapshotVersions); newSnapshotVersions.put(snapshotId.getUUID(), version); + + Map newSnapshotTypes = new HashMap<>(snapshotTypes); + newSnapshotTypes.put(snapshotId.getUUID(), snapshotType); + Map> allIndexSnapshots = new HashMap<>(indexSnapshots); for (final IndexId indexId : shardGenerations.indices()) { final List snapshotIds = allIndexSnapshots.get(indexId); @@ -369,7 +417,8 @@ public RepositoryData addSnapshot( newSnapshotVersions, allIndexSnapshots, ShardGenerations.builder().putAll(this.shardGenerations).putAll(shardGenerations).build(), - newIndexMetaGenerations + newIndexMetaGenerations, + newSnapshotTypes ); } @@ -391,7 +440,8 @@ public RepositoryData withGenId(long newGeneration) { indices, indexSnapshots, shardGenerations, - indexMetaDataGenerations + indexMetaDataGenerations, + snapshotTypes ); } @@ -415,9 +465,11 @@ public RepositoryData removeSnapshots(final Collection snapshots, fi } Map newSnapshotStates = new HashMap<>(snapshotStates); final Map newSnapshotVersions = new HashMap<>(snapshotVersions); + final Map newSnapshotTypes = new HashMap<>(snapshotTypes); for (SnapshotId snapshotId : snapshots) { newSnapshotStates.remove(snapshotId.getUUID()); newSnapshotVersions.remove(snapshotId.getUUID()); + newSnapshotTypes.remove(snapshotId.getUUID()); } Map> indexSnapshots = new HashMap<>(); for (final IndexId indexId : indices.values()) { @@ -445,7 +497,8 @@ public RepositoryData removeSnapshots(final Collection snapshots, fi .putAll(updatedShardGenerations) .retainIndicesAndPruneDeletes(indexSnapshots.keySet()) .build(), - indexMetaDataGenerations.withRemovedSnapshots(snapshots) + indexMetaDataGenerations.withRemovedSnapshots(snapshots), + newSnapshotTypes ); } @@ -475,7 +528,8 @@ public boolean equals(Object obj) { && indices.equals(that.indices) && indexSnapshots.equals(that.indexSnapshots) && shardGenerations.equals(that.shardGenerations) - && indexMetaDataGenerations.equals(that.indexMetaDataGenerations); + && indexMetaDataGenerations.equals(that.indexMetaDataGenerations) + && snapshotTypes.equals(that.snapshotTypes); } @Override @@ -487,7 +541,8 @@ public int hashCode() { indices, indexSnapshots, shardGenerations, - indexMetaDataGenerations + indexMetaDataGenerations, + snapshotTypes ); } @@ -542,6 +597,7 @@ public List resolveNewIndices(List indicesToResolve, Map snapshots = new HashMap<>(); final Map snapshotStates = new HashMap<>(); final Map snapshotVersions = new HashMap<>(); + final Map snapshotTypes = new HashMap<>(); final Map> indexSnapshots = new HashMap<>(); final Map indexLookup = new HashMap<>(); final ShardGenerations.Builder shardGenerations = ShardGenerations.builder(); @@ -620,7 +681,7 @@ public static RepositoryData snapshotsFromXContent(XContentParser parser, long g final String field = parser.currentName(); switch (field) { case SNAPSHOTS: - parseSnapshots(parser, snapshots, snapshotStates, snapshotVersions, indexMetaLookup); + parseSnapshots(parser, snapshots, snapshotStates, snapshotVersions, indexMetaLookup, snapshotTypes); break; case INDICES: parseIndices(parser, snapshots, indexSnapshots, indexLookup, shardGenerations); @@ -646,7 +707,8 @@ public static RepositoryData snapshotsFromXContent(XContentParser parser, long g snapshotVersions, indexSnapshots, shardGenerations.build(), - buildIndexMetaGenerations(indexMetaLookup, indexLookup, indexMetaIdentifiers) + buildIndexMetaGenerations(indexMetaLookup, indexLookup, indexMetaIdentifiers), + snapshotTypes ); } @@ -697,7 +759,8 @@ private static void parseSnapshots( Map snapshots, Map snapshotStates, Map snapshotVersions, - Map> indexMetaLookup + Map> indexMetaLookup, + Map snapshotTypes ) throws IOException { XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.nextToken(), parser); final Map stringDeduplicator = new HashMap<>(); @@ -707,6 +770,7 @@ private static void parseSnapshots( SnapshotState state = null; Map metaGenerations = null; Version version = null; + SnapshotType snapshotType = null; while (parser.nextToken() != XContentParser.Token.END_OBJECT) { String currentFieldName = parser.currentName(); parser.nextToken(); @@ -726,6 +790,9 @@ private static void parseSnapshots( case VERSION: version = Version.fromString(parser.text()); break; + case SNAPSHOT_TYPE: + snapshotType = SnapshotType.fromString(parser.text()); + break; } } final SnapshotId snapshotId = new SnapshotId(name, uuid); @@ -735,6 +802,9 @@ private static void parseSnapshots( if (version != null) { snapshotVersions.put(uuid, version); } + if (snapshotType != null) { + snapshotTypes.put(uuid, snapshotType); + } snapshots.put(uuid, snapshotId); if (metaGenerations != null && metaGenerations.isEmpty() == false) { indexMetaLookup.put(snapshotId, metaGenerations); diff --git a/server/src/main/java/org/opensearch/repositories/blobstore/BlobStoreRepository.java b/server/src/main/java/org/opensearch/repositories/blobstore/BlobStoreRepository.java index 1a5701d9204ef..48416d2e0ac53 100644 --- a/server/src/main/java/org/opensearch/repositories/blobstore/BlobStoreRepository.java +++ b/server/src/main/java/org/opensearch/repositories/blobstore/BlobStoreRepository.java @@ -143,6 +143,7 @@ import org.opensearch.snapshots.SnapshotId; import org.opensearch.snapshots.SnapshotInfo; import org.opensearch.snapshots.SnapshotMissingException; +import org.opensearch.snapshots.SnapshotType; import org.opensearch.snapshots.SnapshotsService; import org.opensearch.threadpool.ThreadPool; @@ -1722,7 +1723,8 @@ public void finalizeSnapshot( Version.CURRENT, shardGenerations, indexMetas, - indexMetaIdentifiers + indexMetaIdentifiers, + snapshotInfo.getSnapshotType() ); writeIndexGen( updatedRepositoryData, @@ -2335,22 +2337,23 @@ public void clusterStateProcessed(String source, ClusterState oldState, ClusterS // Step 2: Write new index-N blob to repository and update index.latest setPendingStep.whenComplete(newGen -> threadPool().executor(ThreadPool.Names.SNAPSHOT).execute(ActionRunnable.wrap(listener, l -> { - // BwC logic: Load snapshot version information if any snapshot is missing a version in RepositoryData so that the new - // RepositoryData contains a version for every snapshot - final List snapshotIdsWithoutVersion = repositoryData.getSnapshotIds() + // BwC logic: Load snapshot version or snapshot type information if any snapshot is missing a version or + // type in RepositoryData so that the new RepositoryData contains version and type for every snapshot + final List snapshotIdsWithoutVersionOrType = repositoryData.getSnapshotIds() .stream() .filter(snapshotId -> repositoryData.getVersion(snapshotId) == null) .collect(Collectors.toList()); - if (snapshotIdsWithoutVersion.isEmpty() == false) { + if (snapshotIdsWithoutVersionOrType.isEmpty() == false) { final Map updatedVersionMap = new ConcurrentHashMap<>(); - final GroupedActionListener loadAllVersionsListener = new GroupedActionListener<>( + final Map updatedSnapshotTypeMap = new ConcurrentHashMap<>(); + final GroupedActionListener loadMissingDataListener = new GroupedActionListener<>( ActionListener.runAfter(new ActionListener>() { @Override public void onResponse(Collection voids) { logger.info( - "Successfully loaded all snapshot's version information for {} from snapshot metadata", + "Successfully loaded all snapshot's version and type information for {} from snapshot metadata", AllocationService.firstListElementsToCommaDelimitedString( - snapshotIdsWithoutVersion, + snapshotIdsWithoutVersionOrType, SnapshotId::toString, logger.isDebugEnabled() ) @@ -2359,19 +2362,20 @@ public void onResponse(Collection voids) { @Override public void onFailure(Exception e) { - logger.warn("Failure when trying to load missing version information from snapshot metadata", e); + logger.warn("Failure when trying to load missing version or type information from snapshot metadata", e); } - }, () -> filterRepositoryDataStep.onResponse(repositoryData.withVersions(updatedVersionMap))), - snapshotIdsWithoutVersion.size() + }, + () -> filterRepositoryDataStep.onResponse( + repositoryData.withVersions(updatedVersionMap).withSnapshotTypes(updatedSnapshotTypeMap) + ) + ), + snapshotIdsWithoutVersionOrType.size() ); - for (SnapshotId snapshotId : snapshotIdsWithoutVersion) { - threadPool().executor(ThreadPool.Names.SNAPSHOT) - .execute( - ActionRunnable.run( - loadAllVersionsListener, - () -> updatedVersionMap.put(snapshotId, getSnapshotInfo(snapshotId).version()) - ) - ); + for (SnapshotId snapshotId : snapshotIdsWithoutVersionOrType) { + threadPool().executor(ThreadPool.Names.SNAPSHOT).execute(ActionRunnable.run(loadMissingDataListener, () -> { + updatedVersionMap.put(snapshotId, getSnapshotInfo(snapshotId).version()); + updatedSnapshotTypeMap.put(snapshotId, getSnapshotInfo(snapshotId).getSnapshotType()); + })); } } else { filterRepositoryDataStep.onResponse(repositoryData); diff --git a/server/src/main/java/org/opensearch/snapshots/SnapshotInfo.java b/server/src/main/java/org/opensearch/snapshots/SnapshotInfo.java index 191b872cdd563..dbab9d90cb9c3 100644 --- a/server/src/main/java/org/opensearch/snapshots/SnapshotInfo.java +++ b/server/src/main/java/org/opensearch/snapshots/SnapshotInfo.java @@ -539,6 +539,13 @@ public Boolean isRemoteStoreIndexShallowCopyEnabled() { return remoteStoreIndexShallowCopy; } + public SnapshotType getSnapshotType() { + if (remoteStoreIndexShallowCopy != null && remoteStoreIndexShallowCopy) { + return SnapshotType.SHALLOW_COPY; + } + return SnapshotType.FULL_COPY; + } + /** * Returns shard failures; an empty list will be returned if there were no shard * failures, or if {@link #state()} returns {@code null}. diff --git a/server/src/main/java/org/opensearch/snapshots/SnapshotType.java b/server/src/main/java/org/opensearch/snapshots/SnapshotType.java new file mode 100644 index 0000000000000..9b71b6e53606c --- /dev/null +++ b/server/src/main/java/org/opensearch/snapshots/SnapshotType.java @@ -0,0 +1,37 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.snapshots; + +import org.opensearch.common.annotation.PublicApi; + +@PublicApi(since = "2.15.0") +public enum SnapshotType { + FULL_COPY("full_copy"), + SHALLOW_COPY("shallow_copy"); + + private final String text; + + SnapshotType(String text) { + this.text = text; + } + + @Override + public String toString() { + return text; + } + + public static SnapshotType fromString(String string) { + for (SnapshotType type : values()) { + if (type.text.equals(string)) { + return type; + } + } + throw new IllegalArgumentException("Invalid snapshot_type: " + string); + } +} diff --git a/server/src/main/java/org/opensearch/snapshots/SnapshotsService.java b/server/src/main/java/org/opensearch/snapshots/SnapshotsService.java index acc2dc83749cd..67fcde558850e 100644 --- a/server/src/main/java/org/opensearch/snapshots/SnapshotsService.java +++ b/server/src/main/java/org/opensearch/snapshots/SnapshotsService.java @@ -2238,12 +2238,10 @@ private void deleteSnapshotsFromRepository( assert deleteEntry.state() == SnapshotDeletionsInProgress.State.STARTED : "incorrect state for entry [" + deleteEntry + "]"; final Repository repository = repositoriesService.repository(deleteEntry.repository()); - // TODO: Relying on repository flag to decide delete flow may lead to shallow snapshot blobs not being taken up for cleanup - // when the repository currently have the flag disabled and we try to delete the shallow snapshots taken prior to disabling - // the flag. This can be improved by having the info whether there ever were any shallow snapshot present in this repository - // or not in RepositoryData. - // SEE https://github.com/opensearch-project/OpenSearch/issues/8610 - final boolean cleanupRemoteStoreLockFiles = REMOTE_STORE_INDEX_SHALLOW_COPY.get(repository.getMetadata().settings()); + // If there is at least one shallow copy snapshot in repository, we can use the new method + // deleteSnapshotsAndReleaseLockFiles which will release lock files as well during snapshot deletion. + boolean cleanupRemoteStoreLockFiles = snapshotIds.stream() + .anyMatch(snapshotId -> repositoryData.getSnapshotType(snapshotId) == SnapshotType.SHALLOW_COPY); if (cleanupRemoteStoreLockFiles) { repository.deleteSnapshotsAndReleaseLockFiles( snapshotIds, diff --git a/server/src/test/java/org/opensearch/repositories/RepositoryDataTests.java b/server/src/test/java/org/opensearch/repositories/RepositoryDataTests.java index 46293e6a0db7a..dac7d01aa75e8 100644 --- a/server/src/test/java/org/opensearch/repositories/RepositoryDataTests.java +++ b/server/src/test/java/org/opensearch/repositories/RepositoryDataTests.java @@ -44,6 +44,7 @@ import org.opensearch.core.xcontent.XContentParser; import org.opensearch.snapshots.SnapshotId; import org.opensearch.snapshots.SnapshotState; +import org.opensearch.snapshots.SnapshotType; import org.opensearch.test.OpenSearchTestCase; import java.io.IOException; @@ -129,13 +130,15 @@ public void testAddSnapshots() { final Map indexLookup = shardGenerations.indices() .stream() .collect(Collectors.toMap(Function.identity(), ind -> randomAlphaOfLength(256))); + final SnapshotType expectedType = randomFrom(SnapshotType.FULL_COPY, SnapshotType.SHALLOW_COPY); RepositoryData newRepoData = repositoryData.addSnapshot( newSnapshot, randomFrom(SnapshotState.SUCCESS, SnapshotState.PARTIAL, SnapshotState.FAILED), randomFrom(Version.CURRENT, Version.CURRENT.minimumCompatibilityVersion()), shardGenerations, indexLookup, - indexLookup.values().stream().collect(Collectors.toMap(Function.identity(), ignored -> UUIDs.randomBase64UUID(random()))) + indexLookup.values().stream().collect(Collectors.toMap(Function.identity(), ignored -> UUIDs.randomBase64UUID(random()))), + expectedType ); // verify that the new repository data has the new snapshot and its indices assertTrue(newRepoData.getSnapshotIds().contains(newSnapshot)); @@ -146,6 +149,7 @@ public void testAddSnapshots() { assertEquals(snapshotIds.size(), 1); // if it was a new index, only the new snapshot should be in its set } } + assertTrue(newRepoData.getSnapshotType(newSnapshot) == expectedType); assertEquals(repositoryData.getGenId(), newRepoData.getGenId()); } @@ -154,11 +158,13 @@ public void testInitIndices() { final Map snapshotIds = new HashMap<>(numSnapshots); final Map snapshotStates = new HashMap<>(numSnapshots); final Map snapshotVersions = new HashMap<>(numSnapshots); + final Map snapshotTypes = new HashMap<>(numSnapshots); for (int i = 0; i < numSnapshots; i++) { final SnapshotId snapshotId = new SnapshotId(randomAlphaOfLength(8), UUIDs.randomBase64UUID()); snapshotIds.put(snapshotId.getUUID(), snapshotId); snapshotStates.put(snapshotId.getUUID(), randomFrom(SnapshotState.values())); snapshotVersions.put(snapshotId.getUUID(), randomFrom(Version.CURRENT, Version.CURRENT.minimumCompatibilityVersion())); + snapshotTypes.put(snapshotId.getUUID(), randomFrom(SnapshotType.FULL_COPY, SnapshotType.SHALLOW_COPY)); } RepositoryData repositoryData = new RepositoryData( EMPTY_REPO_GEN, @@ -167,7 +173,8 @@ public void testInitIndices() { Collections.emptyMap(), Collections.emptyMap(), ShardGenerations.EMPTY, - IndexMetaDataGenerations.EMPTY + IndexMetaDataGenerations.EMPTY, + Collections.emptyMap() ); // test that initializing indices works Map> indices = randomIndices(snapshotIds); @@ -178,7 +185,8 @@ public void testInitIndices() { snapshotVersions, indices, ShardGenerations.EMPTY, - IndexMetaDataGenerations.EMPTY + IndexMetaDataGenerations.EMPTY, + snapshotTypes ); List expected = new ArrayList<>(repositoryData.getSnapshotIds()); Collections.sort(expected); @@ -221,7 +229,8 @@ public void testGetSnapshotState() { randomFrom(Version.CURRENT, Version.CURRENT.minimumCompatibilityVersion()), ShardGenerations.EMPTY, Collections.emptyMap(), - Collections.emptyMap() + Collections.emptyMap(), + randomFrom(SnapshotType.SHALLOW_COPY, SnapshotType.FULL_COPY) ); assertEquals(state, repositoryData.getSnapshotState(snapshotId)); assertNull(repositoryData.getSnapshotState(new SnapshotId(randomAlphaOfLength(8), UUIDs.randomBase64UUID()))); @@ -242,10 +251,12 @@ public void testIndexThatReferencesAnUnknownSnapshot() throws IOException { Map snapshotIds = new HashMap<>(); Map snapshotStates = new HashMap<>(); Map snapshotVersions = new HashMap<>(); + Map snapshotTypes = new HashMap<>(); for (SnapshotId snapshotId : parsedRepositoryData.getSnapshotIds()) { snapshotIds.put(snapshotId.getUUID(), snapshotId); snapshotStates.put(snapshotId.getUUID(), parsedRepositoryData.getSnapshotState(snapshotId)); snapshotVersions.put(snapshotId.getUUID(), parsedRepositoryData.getVersion(snapshotId)); + snapshotTypes.put(snapshotId.getUUID(), parsedRepositoryData.getSnapshotType(snapshotId)); } final IndexId corruptedIndexId = randomFrom(parsedRepositoryData.getIndices().values()); @@ -273,7 +284,8 @@ public void testIndexThatReferencesAnUnknownSnapshot() throws IOException { snapshotVersions, indexSnapshots, shardGenBuilder.build(), - IndexMetaDataGenerations.EMPTY + IndexMetaDataGenerations.EMPTY, + snapshotTypes ); final XContentBuilder corruptedBuilder = XContentBuilder.builder(xContent); @@ -392,7 +404,8 @@ public void testIndexMetaDataToRemoveAfterRemovingSnapshotWithSharing() { Version.CURRENT, shardGenerations, indexLookup, - newIdentifiers + newIdentifiers, + SnapshotType.FULL_COPY ); assertEquals( newRepoData.indexMetaDataToRemoveAfterRemovingSnapshots(Collections.singleton(newSnapshot)), @@ -430,7 +443,8 @@ public static RepositoryData generateRandomRepoData() { randomFrom(Version.CURRENT, Version.CURRENT.minimumCompatibilityVersion()), builder.build(), indexLookup, - indexLookup.values().stream().collect(Collectors.toMap(Function.identity(), ignored -> UUIDs.randomBase64UUID(random()))) + indexLookup.values().stream().collect(Collectors.toMap(Function.identity(), ignored -> UUIDs.randomBase64UUID(random()))), + randomFrom(SnapshotType.FULL_COPY, SnapshotType.SHALLOW_COPY) ); } return repositoryData; diff --git a/server/src/test/java/org/opensearch/repositories/blobstore/BlobStoreRepositoryHelperTests.java b/server/src/test/java/org/opensearch/repositories/blobstore/BlobStoreRepositoryHelperTests.java index 29ffb94ce8bf4..81623af93e47b 100644 --- a/server/src/test/java/org/opensearch/repositories/blobstore/BlobStoreRepositoryHelperTests.java +++ b/server/src/test/java/org/opensearch/repositories/blobstore/BlobStoreRepositoryHelperTests.java @@ -31,12 +31,14 @@ import org.opensearch.repositories.RepositoriesService; import org.opensearch.repositories.Repository; import org.opensearch.repositories.fs.FsRepository; +import org.opensearch.snapshots.SnapshotId; import org.opensearch.snapshots.SnapshotInfo; import org.opensearch.snapshots.SnapshotState; import org.opensearch.test.OpenSearchIntegTestCase; import org.opensearch.test.OpenSearchSingleNodeTestCase; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -46,8 +48,6 @@ import static org.opensearch.index.remote.RemoteStoreEnums.DataCategory.SEGMENTS; import static org.opensearch.index.remote.RemoteStoreEnums.DataType.LOCK_FILES; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.is; public class BlobStoreRepositoryHelperTests extends OpenSearchSingleNodeTestCase { @@ -145,12 +145,20 @@ protected SnapshotInfo createSnapshot(String repositoryName, String snapshot, Li .setWaitForCompletion(true) .get(); SnapshotInfo snapshotInfo = response.getSnapshotInfo(); - assertThat(snapshotInfo.state(), is(SnapshotState.SUCCESS)); - assertThat(snapshotInfo.successfulShards(), greaterThan(0)); - assertThat(snapshotInfo.failedShards(), equalTo(0)); + assertEquals(snapshotInfo.state(), SnapshotState.SUCCESS); + assertEquals(snapshotInfo.successfulShards(), snapshotInfo.totalShards()); + assertEquals(snapshotInfo.failedShards(), 0); return snapshotInfo; } + protected List createNSnapshots(String repositoryName, String snapshotPrefix, int snapshotCount, List indices) { + List snapshots = new ArrayList<>(); + for (int i = 0; i < snapshotCount; i++) { + snapshots.add(createSnapshot(repositoryName, snapshotPrefix + "-" + i, indices).snapshotId()); + } + return snapshots; + } + protected void indexDocuments(Client client, String indexName) { int numDocs = randomIntBetween(10, 20); for (int i = 0; i < numDocs; i++) { diff --git a/server/src/test/java/org/opensearch/repositories/blobstore/BlobStoreRepositoryRemoteIndexTests.java b/server/src/test/java/org/opensearch/repositories/blobstore/BlobStoreRepositoryRemoteIndexTests.java index 9cca495cced72..b0dfd3358e84e 100644 --- a/server/src/test/java/org/opensearch/repositories/blobstore/BlobStoreRepositoryRemoteIndexTests.java +++ b/server/src/test/java/org/opensearch/repositories/blobstore/BlobStoreRepositoryRemoteIndexTests.java @@ -32,10 +32,13 @@ package org.opensearch.repositories.blobstore; +import org.opensearch.Version; import org.opensearch.action.admin.cluster.repositories.get.GetRepositoriesResponse; import org.opensearch.client.Client; import org.opensearch.cluster.metadata.RepositoryMetadata; import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.core.index.shard.ShardId; import org.opensearch.env.Environment; import org.opensearch.gateway.remote.RemoteClusterStateService; @@ -43,19 +46,26 @@ import org.opensearch.index.snapshots.blobstore.RemoteStoreShardShallowCopySnapshot; import org.opensearch.indices.replication.common.ReplicationType; import org.opensearch.repositories.IndexId; +import org.opensearch.repositories.IndexMetaDataGenerations; import org.opensearch.repositories.RepositoriesService; import org.opensearch.repositories.RepositoryData; +import org.opensearch.repositories.ShardGenerations; import org.opensearch.repositories.fs.FsRepository; import org.opensearch.snapshots.SnapshotId; import org.opensearch.snapshots.SnapshotInfo; +import org.opensearch.snapshots.SnapshotType; import org.opensearch.test.OpenSearchIntegTestCase; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.function.Function; import java.util.stream.Collectors; import static org.opensearch.indices.IndicesService.CLUSTER_REPLICATION_TYPE_SETTING; @@ -64,6 +74,7 @@ import static org.opensearch.node.remotestore.RemoteStoreNodeAttribute.REMOTE_STORE_REPOSITORY_TYPE_ATTRIBUTE_KEY_FORMAT; import static org.opensearch.node.remotestore.RemoteStoreNodeAttribute.REMOTE_STORE_SEGMENT_REPOSITORY_NAME_ATTRIBUTE_KEY; import static org.opensearch.node.remotestore.RemoteStoreNodeAttribute.REMOTE_STORE_TRANSLOG_REPOSITORY_NAME_ATTRIBUTE_KEY; +import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertAcked; import static org.hamcrest.Matchers.equalTo; /** @@ -191,6 +202,11 @@ public void testRetrieveShallowCopySnapshotCase1() throws IOException { .collect(Collectors.toList()); assertThat(snapshotIds, equalTo(originalSnapshots)); + // Validate that we have correct snapshot types in repository data. + assertSame(repositoryData.getSnapshotType(snapshotId1), SnapshotType.FULL_COPY); + assertSame(repositoryData.getSnapshotType(snapshotId2), SnapshotType.SHALLOW_COPY); + assertSame(repositoryData.getSnapshotType(snapshotId3), SnapshotType.FULL_COPY); + // shallow copy shard metadata - getRemoteStoreShallowCopyShardMetadata RemoteStoreShardShallowCopySnapshot shardShallowCopySnapshot = repository.getRemoteStoreShallowCopyShardMetadata( snapshotId2, @@ -365,12 +381,83 @@ public void testRetrieveShallowCopySnapshotCase2() throws IOException { final RepositoriesService repositoriesService = getInstanceFromNode(RepositoriesService.class); final BlobStoreRepository repository = (BlobStoreRepository) repositoriesService.repository(snapshotRepositoryName); - List snapshotIds = OpenSearchBlobStoreRepositoryIntegTestCase.getRepositoryData(repository) - .getSnapshotIds() + final RepositoryData repositoryData = OpenSearchBlobStoreRepositoryIntegTestCase.getRepositoryData(repository); + + List snapshotIds = repositoryData.getSnapshotIds() .stream() .sorted((s1, s2) -> s1.getName().compareTo(s2.getName())) .collect(Collectors.toList()); - assertThat(snapshotIds, equalTo(originalSnapshots)); + assertEquals(snapshotIds, originalSnapshots); + + // Validate that we have correct snapshot types in repository data. + assertSame(repositoryData.getSnapshotType(snapshotId1), SnapshotType.SHALLOW_COPY); + assertSame(repositoryData.getSnapshotType(snapshotId2), SnapshotType.SHALLOW_COPY); + assertSame(repositoryData.getSnapshotType(snapshotId3), SnapshotType.SHALLOW_COPY); + assertSame(repositoryData.getSnapshotType(snapshotId4), SnapshotType.FULL_COPY); + } + + public void testSnapshotTypesInRepositoryData() throws Exception { + final Client client = client(); + final String snapshotRepositoryName = "test-repo"; + final Path repoPath = OpenSearchIntegTestCase.randomRepoPath(node().settings()); + + logger.info("--> creating snapshot repository"); + Settings snapshotRepoSettings = Settings.builder().put(node().settings()).put("location", repoPath).build(); + createRepository(client, snapshotRepositoryName, snapshotRepoSettings); + + final String snapshotPrefix = "test-snap-"; + logger.info("--> create full copy snapshots"); + createRepository(client, snapshotRepositoryName, snapshotRepoSettings); + List fullCopySnapshots = createNSnapshots(snapshotRepositoryName, snapshotPrefix + "full", 2, new ArrayList<>()); + Settings snapshotRepoSettingsForShallowCopy = Settings.builder() + .put(snapshotRepoSettings) + .put(BlobStoreRepository.REMOTE_STORE_INDEX_SHALLOW_COPY.getKey(), true) + .build(); + updateRepository(client, snapshotRepositoryName, snapshotRepoSettingsForShallowCopy); + + List shallowCopySnapshots = createNSnapshots(snapshotRepositoryName, snapshotPrefix + "shallow", 2, new ArrayList<>()); + RepositoryData repoData = getRepositoryData(snapshotRepositoryName); + + logger.info("--> Strip snapshot type information from index-N blob"); + final RepositoryData withoutVersions = new RepositoryData( + repoData.getGenId(), + repoData.getSnapshotIds().stream().collect(Collectors.toMap(SnapshotId::getUUID, Function.identity())), + repoData.getSnapshotIds().stream().collect(Collectors.toMap(SnapshotId::getUUID, repoData::getSnapshotState)), + Collections.emptyMap(), + Collections.emptyMap(), + ShardGenerations.EMPTY, + IndexMetaDataGenerations.EMPTY, + Collections.emptyMap() + ); + + Files.write( + repoPath.resolve(BlobStoreRepository.INDEX_FILE_PREFIX + withoutVersions.getGenId()), + BytesReference.toBytes( + BytesReference.bytes(withoutVersions.snapshotsToXContent(XContentFactory.jsonBuilder(), Version.CURRENT)) + ), + StandardOpenOption.TRUNCATE_EXISTING + ); + + logger.info("--> Deleting one random snapshot to trigger repository data update"); + final SnapshotId snapshotToDelete = randomFrom(repoData.getSnapshotIds()); + assertAcked(client().admin().cluster().prepareDeleteSnapshot(snapshotRepositoryName, snapshotToDelete.getName()).get()); + + // get updated repo data + repoData = getRepositoryData(snapshotRepositoryName); + assertNull(repoData.getSnapshotType(snapshotToDelete)); + for (SnapshotId snapshotId : repoData.getSnapshotIds()) { + if (fullCopySnapshots.contains(snapshotId)) { + assertEquals(repoData.getSnapshotType(snapshotId), SnapshotType.FULL_COPY); + } else { + assertEquals(repoData.getSnapshotType(snapshotId), SnapshotType.SHALLOW_COPY); + } + } + } + + private RepositoryData getRepositoryData(String repositoryName) { + final RepositoriesService repositoriesService = getInstanceFromNode(RepositoriesService.class); + final BlobStoreRepository repository = (BlobStoreRepository) repositoriesService.repository(repositoryName); + return OpenSearchBlobStoreRepositoryIntegTestCase.getRepositoryData(repository); } } diff --git a/server/src/test/java/org/opensearch/repositories/blobstore/BlobStoreRepositoryTests.java b/server/src/test/java/org/opensearch/repositories/blobstore/BlobStoreRepositoryTests.java index 2445cad01574c..0ae9c5d949894 100644 --- a/server/src/test/java/org/opensearch/repositories/blobstore/BlobStoreRepositoryTests.java +++ b/server/src/test/java/org/opensearch/repositories/blobstore/BlobStoreRepositoryTests.java @@ -58,6 +58,7 @@ import org.opensearch.repositories.fs.FsRepository; import org.opensearch.snapshots.SnapshotId; import org.opensearch.snapshots.SnapshotState; +import org.opensearch.snapshots.SnapshotType; import org.opensearch.test.OpenSearchIntegTestCase; import java.nio.file.Path; @@ -278,8 +279,12 @@ private static void writeIndexGen(BlobStoreRepository repository, RepositoryData } private BlobStoreRepository setupRepo() { - final Client client = client(); final Path location = OpenSearchIntegTestCase.randomRepoPath(node().settings()); + return setupRepo(location); + } + + private BlobStoreRepository setupRepo(Path location) { + final Client client = client(); final String repositoryName = "test-repo"; AcknowledgedResponse putRepositoryResponse = client.admin() @@ -315,7 +320,8 @@ private RepositoryData addRandomSnapshotsToRepoData(RepositoryData repoData, boo Version.CURRENT, shardGenerations, indexLookup, - indexLookup.values().stream().collect(Collectors.toMap(Function.identity(), ignored -> UUIDs.randomBase64UUID(random()))) + indexLookup.values().stream().collect(Collectors.toMap(Function.identity(), ignored -> UUIDs.randomBase64UUID(random()))), + randomFrom(SnapshotType.FULL_COPY, SnapshotType.SHALLOW_COPY) ); } return repoData; diff --git a/test/framework/src/main/java/org/opensearch/index/shard/RestoreOnlyRepository.java b/test/framework/src/main/java/org/opensearch/index/shard/RestoreOnlyRepository.java index be2f895301396..0a5db3f6a5584 100644 --- a/test/framework/src/main/java/org/opensearch/index/shard/RestoreOnlyRepository.java +++ b/test/framework/src/main/java/org/opensearch/index/shard/RestoreOnlyRepository.java @@ -112,7 +112,8 @@ public void getRepositoryData(ActionListener listener) { Collections.emptyMap(), Collections.singletonMap(indexId, emptyList()), ShardGenerations.EMPTY, - IndexMetaDataGenerations.EMPTY + IndexMetaDataGenerations.EMPTY, + Collections.emptyMap() ) ); }