Skip to content

Commit 8586481

Browse files
opensearch-trigger-bot[bot]github-actions[bot]sachinpkale
authored
[Backport 2.17] Add restriction to have a single repository with shallow snapshot v2 setting (#15911)
* Add restriction to have a single repository with shallow snapshot v2 setting (#15901) * Add restriction to have a single repository with shallow snapshot v2 setting Signed-off-by: Sachin Kale <[email protected]> * Do not allow shallow snapshot v2 repo name to contain SNAPSHOT_PINNED_TIMESTAMP_DELIMITER Signed-off-by: Sachin Kale <[email protected]> --------- Signed-off-by: Sachin Kale <[email protected]> (cherry picked from commit 330b249) Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * Add a method to avoid breaking changes Signed-off-by: Sachin Kale <[email protected]> --------- Signed-off-by: Sachin Kale <[email protected]> Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Sachin Kale <[email protected]>
1 parent 1dba8c3 commit 8586481

File tree

3 files changed

+241
-5
lines changed

3 files changed

+241
-5
lines changed

server/src/main/java/org/opensearch/node/remotestore/RemoteStorePinnedTimestampService.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import java.io.ByteArrayInputStream;
3131
import java.io.Closeable;
3232
import java.io.IOException;
33+
import java.util.HashMap;
34+
import java.util.HashSet;
3335
import java.util.List;
3436
import java.util.Locale;
3537
import java.util.Map;
@@ -75,25 +77,46 @@ public RemoteStorePinnedTimestampService(
7577
* and starts the asynchronous update task.
7678
*/
7779
public void start() {
78-
validateRemoteStoreConfiguration();
80+
blobContainer = validateAndCreateBlobContainer(settings, repositoriesService.get());
7981
startAsyncUpdateTask(RemoteStoreSettings.getPinnedTimestampsSchedulerInterval());
8082
}
8183

82-
private void validateRemoteStoreConfiguration() {
84+
private static BlobContainer validateAndCreateBlobContainer(Settings settings, RepositoriesService repositoriesService) {
8385
final String remoteStoreRepo = settings.get(
8486
Node.NODE_ATTRIBUTES.getKey() + RemoteStoreNodeAttribute.REMOTE_STORE_SEGMENT_REPOSITORY_NAME_ATTRIBUTE_KEY
8587
);
8688
assert remoteStoreRepo != null : "Remote Segment Store repository is not configured";
87-
final Repository repository = repositoriesService.get().repository(remoteStoreRepo);
89+
final Repository repository = repositoriesService.repository(remoteStoreRepo);
8890
assert repository instanceof BlobStoreRepository : "Repository should be instance of BlobStoreRepository";
8991
BlobStoreRepository blobStoreRepository = (BlobStoreRepository) repository;
90-
blobContainer = blobStoreRepository.blobStore().blobContainer(blobStoreRepository.basePath().add(PINNED_TIMESTAMPS_PATH_TOKEN));
92+
return blobStoreRepository.blobStore().blobContainer(blobStoreRepository.basePath().add(PINNED_TIMESTAMPS_PATH_TOKEN));
9193
}
9294

9395
private void startAsyncUpdateTask(TimeValue pinnedTimestampsSchedulerInterval) {
9496
asyncUpdatePinnedTimestampTask = new AsyncUpdatePinnedTimestampTask(logger, threadPool, pinnedTimestampsSchedulerInterval, true);
9597
}
9698

99+
public static Map<String, Set<Long>> fetchPinnedTimestamps(Settings settings, RepositoriesService repositoriesService)
100+
throws IOException {
101+
BlobContainer blobContainer = validateAndCreateBlobContainer(settings, repositoriesService);
102+
Set<String> pinnedTimestamps = blobContainer.listBlobs().keySet();
103+
Map<String, Set<Long>> pinningEntityTimestampMap = new HashMap<>();
104+
for (String pinnedTimestamp : pinnedTimestamps) {
105+
try {
106+
String[] tokens = pinnedTimestamp.split(PINNED_TIMESTAMPS_FILENAME_SEPARATOR);
107+
Long timestamp = Long.parseLong(tokens[tokens.length - 1]);
108+
String pinningEntity = pinnedTimestamp.substring(0, pinnedTimestamp.lastIndexOf(PINNED_TIMESTAMPS_FILENAME_SEPARATOR));
109+
if (pinningEntityTimestampMap.containsKey(pinningEntity) == false) {
110+
pinningEntityTimestampMap.put(pinningEntity, new HashSet<>());
111+
}
112+
pinningEntityTimestampMap.get(pinningEntity).add(timestamp);
113+
} catch (NumberFormatException e) {
114+
logger.error("Exception while parsing pinned timestamp from {}, skipping this entry", pinnedTimestamp);
115+
}
116+
}
117+
return pinningEntityTimestampMap;
118+
}
119+
97120
/**
98121
* Pins a timestamp in the remote store.
99122
*

server/src/main/java/org/opensearch/repositories/RepositoriesService.java

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@
6868
import org.opensearch.common.util.io.IOUtils;
6969
import org.opensearch.core.action.ActionListener;
7070
import org.opensearch.core.common.Strings;
71+
import org.opensearch.node.remotestore.RemoteStorePinnedTimestampService;
7172
import org.opensearch.repositories.blobstore.MeteredBlobStoreRepository;
73+
import org.opensearch.snapshots.SnapshotsService;
7274
import org.opensearch.threadpool.ThreadPool;
7375
import org.opensearch.transport.TransportService;
7476

@@ -84,6 +86,7 @@
8486
import java.util.stream.Stream;
8587

8688
import static org.opensearch.repositories.blobstore.BlobStoreRepository.REMOTE_STORE_INDEX_SHALLOW_COPY;
89+
import static org.opensearch.repositories.blobstore.BlobStoreRepository.SHALLOW_SNAPSHOT_V2;
8790
import static org.opensearch.repositories.blobstore.BlobStoreRepository.SYSTEM_REPOSITORY_SETTING;
8891

8992
/**
@@ -123,6 +126,7 @@ public class RepositoriesService extends AbstractLifecycleComponent implements C
123126
private final RepositoriesStatsArchive repositoriesStatsArchive;
124127
private final ClusterManagerTaskThrottler.ThrottlingKey putRepositoryTaskKey;
125128
private final ClusterManagerTaskThrottler.ThrottlingKey deleteRepositoryTaskKey;
129+
private final Settings settings;
126130

127131
public RepositoriesService(
128132
Settings settings,
@@ -132,6 +136,7 @@ public RepositoriesService(
132136
Map<String, Repository.Factory> internalTypesRegistry,
133137
ThreadPool threadPool
134138
) {
139+
this.settings = settings;
135140
this.typesRegistry = typesRegistry;
136141
this.internalTypesRegistry = internalTypesRegistry;
137142
this.clusterService = clusterService;
@@ -173,7 +178,7 @@ public void registerOrUpdateRepository(final PutRepositoryRequest request, final
173178
CryptoMetadata.fromRequest(request.cryptoSettings())
174179
);
175180
validate(request.name());
176-
validateRepositoryMetadataSettings(clusterService, request.name(), request.settings());
181+
validateRepositoryMetadataSettings(clusterService, request.name(), request.settings(), repositories, settings, this);
177182
if (newRepositoryMetadata.cryptoMetadata() != null) {
178183
validate(newRepositoryMetadata.cryptoMetadata().keyProviderName());
179184
}
@@ -685,6 +690,17 @@ public static void validateRepositoryMetadataSettings(
685690
ClusterService clusterService,
686691
final String repositoryName,
687692
final Settings repositoryMetadataSettings
693+
) {
694+
validateRepositoryMetadataSettings(clusterService, repositoryName, repositoryMetadataSettings, null, null, null);
695+
}
696+
697+
public static void validateRepositoryMetadataSettings(
698+
ClusterService clusterService,
699+
final String repositoryName,
700+
final Settings repositoryMetadataSettings,
701+
Map<String, Repository> repositories,
702+
Settings settings,
703+
RepositoriesService repositoriesService
688704
) {
689705
// We can add more validations here for repository settings in the future.
690706
Version minVersionInCluster = clusterService.state().getNodes().getMinNodeVersion();
@@ -699,6 +715,57 @@ public static void validateRepositoryMetadataSettings(
699715
+ minVersionInCluster
700716
);
701717
}
718+
if (SHALLOW_SNAPSHOT_V2.get(repositoryMetadataSettings)) {
719+
if (repositories == null || repositoriesService == null || settings == null) {
720+
throw new RepositoryException(
721+
repositoryName,
722+
"setting " + SHALLOW_SNAPSHOT_V2.getKey() + " cannot be enabled if required params are not provided"
723+
);
724+
}
725+
if (minVersionInCluster.onOrAfter(Version.V_2_17_0) == false) {
726+
throw new RepositoryException(
727+
repositoryName,
728+
"setting "
729+
+ SHALLOW_SNAPSHOT_V2.getKey()
730+
+ " cannot be enabled as some of the nodes in cluster are on version older than "
731+
+ Version.V_2_17_0
732+
+ ". Minimum node version in cluster is: "
733+
+ minVersionInCluster
734+
);
735+
}
736+
if (repositoryName.contains(SnapshotsService.SNAPSHOT_PINNED_TIMESTAMP_DELIMITER)) {
737+
throw new RepositoryException(
738+
repositoryName,
739+
"setting "
740+
+ SHALLOW_SNAPSHOT_V2.getKey()
741+
+ " cannot be enabled for repository with "
742+
+ SnapshotsService.SNAPSHOT_PINNED_TIMESTAMP_DELIMITER
743+
+ " in the name as this delimiter is used to create pinning entity"
744+
);
745+
}
746+
if (repositoryWithShallowV2Exists(repositories)) {
747+
throw new RepositoryException(
748+
repositoryName,
749+
"setting "
750+
+ SHALLOW_SNAPSHOT_V2.getKey()
751+
+ " cannot be enabled as this setting can be enabled only on one repository "
752+
+ " and one or more repositories in the cluster have the setting as enabled"
753+
);
754+
}
755+
try {
756+
if (pinnedTimestampExistsWithDifferentRepository(repositoryName, settings, repositoriesService)) {
757+
throw new RepositoryException(
758+
repositoryName,
759+
"setting "
760+
+ SHALLOW_SNAPSHOT_V2.getKey()
761+
+ " cannot be enabled if there are existing snapshots created with shallow V2 "
762+
+ "setting using different repository."
763+
);
764+
}
765+
} catch (IOException e) {
766+
throw new RepositoryException(repositoryName, "Exception while fetching pinned timestamp details");
767+
}
768+
}
702769
// Validation to not allow users to create system repository via put repository call.
703770
if (isSystemRepositorySettingPresent(repositoryMetadataSettings)) {
704771
throw new RepositoryException(
@@ -710,6 +777,28 @@ public static void validateRepositoryMetadataSettings(
710777
}
711778
}
712779

780+
private static boolean repositoryWithShallowV2Exists(Map<String, Repository> repositories) {
781+
return repositories.values().stream().anyMatch(repo -> SHALLOW_SNAPSHOT_V2.get(repo.getMetadata().settings()));
782+
}
783+
784+
private static boolean pinnedTimestampExistsWithDifferentRepository(
785+
String newRepoName,
786+
Settings settings,
787+
RepositoriesService repositoriesService
788+
) throws IOException {
789+
Map<String, Set<Long>> pinningEntityTimestampMap = RemoteStorePinnedTimestampService.fetchPinnedTimestamps(
790+
settings,
791+
repositoriesService
792+
);
793+
for (String pinningEntity : pinningEntityTimestampMap.keySet()) {
794+
String repoNameWithPinnedTimestamps = pinningEntity.split(SnapshotsService.SNAPSHOT_PINNED_TIMESTAMP_DELIMITER)[0];
795+
if (repoNameWithPinnedTimestamps.equals(newRepoName) == false) {
796+
return true;
797+
}
798+
}
799+
return false;
800+
}
801+
713802
private static void ensureRepositoryNotInUse(ClusterState clusterState, String repository) {
714803
if (isRepositoryInUse(clusterState, repository)) {
715804
throw new IllegalStateException("trying to modify or unregister repository that is currently used");

server/src/test/java/org/opensearch/repositories/blobstore/BlobStoreRepositoryRemoteIndexTests.java

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
package org.opensearch.repositories.blobstore;
3434

3535
import org.opensearch.action.admin.cluster.repositories.get.GetRepositoriesResponse;
36+
import org.opensearch.action.admin.cluster.repositories.verify.VerifyRepositoryResponse;
37+
import org.opensearch.action.support.master.AcknowledgedResponse;
3638
import org.opensearch.client.Client;
3739
import org.opensearch.cluster.metadata.RepositoryMetadata;
3840
import org.opensearch.common.settings.Settings;
@@ -41,13 +43,16 @@
4143
import org.opensearch.gateway.remote.RemoteClusterStateService;
4244
import org.opensearch.index.IndexSettings;
4345
import org.opensearch.index.snapshots.blobstore.RemoteStoreShardShallowCopySnapshot;
46+
import org.opensearch.indices.RemoteStoreSettings;
4447
import org.opensearch.indices.replication.common.ReplicationType;
4548
import org.opensearch.repositories.IndexId;
4649
import org.opensearch.repositories.RepositoriesService;
4750
import org.opensearch.repositories.RepositoryData;
51+
import org.opensearch.repositories.RepositoryException;
4852
import org.opensearch.repositories.fs.FsRepository;
4953
import org.opensearch.snapshots.SnapshotId;
5054
import org.opensearch.snapshots.SnapshotInfo;
55+
import org.opensearch.snapshots.SnapshotsService;
5156
import org.opensearch.test.OpenSearchIntegTestCase;
5257

5358
import java.io.IOException;
@@ -64,6 +69,9 @@
6469
import static org.opensearch.node.remotestore.RemoteStoreNodeAttribute.REMOTE_STORE_REPOSITORY_TYPE_ATTRIBUTE_KEY_FORMAT;
6570
import static org.opensearch.node.remotestore.RemoteStoreNodeAttribute.REMOTE_STORE_SEGMENT_REPOSITORY_NAME_ATTRIBUTE_KEY;
6671
import static org.opensearch.node.remotestore.RemoteStoreNodeAttribute.REMOTE_STORE_TRANSLOG_REPOSITORY_NAME_ATTRIBUTE_KEY;
72+
import static org.opensearch.repositories.blobstore.BlobStoreRepository.REMOTE_STORE_INDEX_SHALLOW_COPY;
73+
import static org.opensearch.repositories.blobstore.BlobStoreRepository.SHALLOW_SNAPSHOT_V2;
74+
import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertAcked;
6775
import static org.hamcrest.Matchers.equalTo;
6876

6977
/**
@@ -80,6 +88,7 @@ protected Settings nodeSettings() {
8088
.put(Environment.PATH_HOME_SETTING.getKey(), tempDir)
8189
.put(Environment.PATH_REPO_SETTING.getKey(), tempDir.resolve("repo"))
8290
.put(Environment.PATH_SHARED_DATA_SETTING.getKey(), tempDir.getParent())
91+
.put(RemoteStoreSettings.CLUSTER_REMOTE_STORE_PINNED_TIMESTAMP_ENABLED.getKey(), true)
8392
.build();
8493
}
8594

@@ -372,4 +381,119 @@ public void testRetrieveShallowCopySnapshotCase2() throws IOException {
372381
assertThat(snapshotIds, equalTo(originalSnapshots));
373382
}
374383

384+
public void testRepositoryCreationShallowV2() throws Exception {
385+
Client client = client();
386+
387+
Settings snapshotRepoSettings1 = Settings.builder()
388+
.put(node().settings())
389+
.put("location", OpenSearchIntegTestCase.randomRepoPath(node().settings()))
390+
.put(REMOTE_STORE_INDEX_SHALLOW_COPY.getKey(), true)
391+
.put(SHALLOW_SNAPSHOT_V2.getKey(), true)
392+
.build();
393+
394+
String invalidRepoName = "test" + SnapshotsService.SNAPSHOT_PINNED_TIMESTAMP_DELIMITER + "repo-1";
395+
try {
396+
createRepository(client, invalidRepoName, snapshotRepoSettings1);
397+
} catch (RepositoryException e) {
398+
assertEquals(
399+
"["
400+
+ invalidRepoName
401+
+ "] setting shallow_snapshot_v2 cannot be enabled for repository with __ in the name as this delimiter is used to create pinning entity",
402+
e.getMessage()
403+
);
404+
}
405+
406+
// Create repo with shallow snapshot V2 enabled
407+
createRepository(client, "test-repo-1", snapshotRepoSettings1);
408+
409+
logger.info("--> verify the repository");
410+
VerifyRepositoryResponse verifyRepositoryResponse = client.admin().cluster().prepareVerifyRepository("test-repo-1").get();
411+
assertNotNull(verifyRepositoryResponse.getNodes());
412+
413+
GetRepositoriesResponse getRepositoriesResponse = client.admin().cluster().prepareGetRepositories("test-repo-1").get();
414+
assertTrue(SHALLOW_SNAPSHOT_V2.get(getRepositoriesResponse.repositories().get(0).settings()));
415+
416+
Settings snapshotRepoSettings2 = Settings.builder()
417+
.put(node().settings())
418+
.put("location", OpenSearchIntegTestCase.randomRepoPath(node().settings()))
419+
.put(REMOTE_STORE_INDEX_SHALLOW_COPY.getKey(), true)
420+
.put(SHALLOW_SNAPSHOT_V2.getKey(), true)
421+
.build();
422+
423+
// Create another repo with shallow snapshot V2 enabled, this should fail.
424+
try {
425+
createRepository(client, "test-repo-2", snapshotRepoSettings2);
426+
} catch (RepositoryException e) {
427+
assertEquals(
428+
"[test-repo-2] setting shallow_snapshot_v2 cannot be enabled as this setting can be enabled only on one repository and one or more repositories in the cluster have the setting as enabled",
429+
e.getMessage()
430+
);
431+
}
432+
433+
// Disable shallow snapshot V2 setting on test-repo-1
434+
updateRepository(
435+
client,
436+
"test-repo-1",
437+
Settings.builder().put(snapshotRepoSettings1).put(SHALLOW_SNAPSHOT_V2.getKey(), false).build()
438+
);
439+
getRepositoriesResponse = client.admin().cluster().prepareGetRepositories("test-repo-1").get();
440+
assertFalse(SHALLOW_SNAPSHOT_V2.get(getRepositoriesResponse.repositories().get(0).settings()));
441+
442+
// Create test-repo-2 with shallow snapshot V2 enabled, this should pass now.
443+
createRepository(client, "test-repo-2", snapshotRepoSettings2);
444+
getRepositoriesResponse = client.admin().cluster().prepareGetRepositories("test-repo-2").get();
445+
assertTrue(SHALLOW_SNAPSHOT_V2.get(getRepositoriesResponse.repositories().get(0).settings()));
446+
447+
final String indexName = "test-idx";
448+
createIndex(indexName);
449+
ensureGreen();
450+
indexDocuments(client, indexName);
451+
452+
// Create pinned timestamp snapshot in test-repo-2
453+
SnapshotInfo snapshotInfo = createSnapshot("test-repo-2", "test-snap-2", new ArrayList<>());
454+
assertNotNull(snapshotInfo.snapshotId());
455+
456+
// As snapshot is present, even after disabling shallow snapshot setting in test-repo-2, we will not be able to
457+
// enable shallow snapshot v2 setting in test-repo-1
458+
updateRepository(
459+
client,
460+
"test-repo-2",
461+
Settings.builder().put(snapshotRepoSettings2).put(SHALLOW_SNAPSHOT_V2.getKey(), false).build()
462+
);
463+
getRepositoriesResponse = client.admin().cluster().prepareGetRepositories("test-repo-2").get();
464+
assertFalse(SHALLOW_SNAPSHOT_V2.get(getRepositoriesResponse.repositories().get(0).settings()));
465+
466+
try {
467+
updateRepository(client, "test-repo-1", snapshotRepoSettings1);
468+
} catch (RepositoryException e) {
469+
assertEquals(
470+
"[test-repo-1] setting shallow_snapshot_v2 cannot be enabled if there are existing snapshots created with shallow V2 setting using different repository.",
471+
e.getMessage()
472+
);
473+
}
474+
475+
// After deleting the snapshot, we will be able to enable shallow snapshot v2 setting in test-repo-1
476+
AcknowledgedResponse deleteSnapshotResponse = client().admin().cluster().prepareDeleteSnapshot("test-repo-2", "test-snap-2").get();
477+
478+
assertAcked(deleteSnapshotResponse);
479+
480+
updateRepository(client, "test-repo-1", snapshotRepoSettings1);
481+
getRepositoriesResponse = client.admin().cluster().prepareGetRepositories("test-repo-1").get();
482+
assertTrue(SHALLOW_SNAPSHOT_V2.get(getRepositoriesResponse.repositories().get(0).settings()));
483+
484+
// Having a snapshot in the same repo should allow disabling and re-enabling shallow snapshot v2 setting
485+
snapshotInfo = createSnapshot("test-repo-1", "test-snap-1", new ArrayList<>());
486+
assertNotNull(snapshotInfo.snapshotId());
487+
updateRepository(
488+
client,
489+
"test-repo-1",
490+
Settings.builder().put(snapshotRepoSettings1).put(SHALLOW_SNAPSHOT_V2.getKey(), false).build()
491+
);
492+
getRepositoriesResponse = client.admin().cluster().prepareGetRepositories("test-repo-1").get();
493+
assertFalse(SHALLOW_SNAPSHOT_V2.get(getRepositoriesResponse.repositories().get(0).settings()));
494+
495+
updateRepository(client, "test-repo-1", snapshotRepoSettings1);
496+
getRepositoriesResponse = client.admin().cluster().prepareGetRepositories("test-repo-1").get();
497+
assertTrue(SHALLOW_SNAPSHOT_V2.get(getRepositoriesResponse.repositories().get(0).settings()));
498+
}
375499
}

0 commit comments

Comments
 (0)