Skip to content

Commit a16a1de

Browse files
committed
Reconcile remote index settings on switch to strict mode
Signed-off-by: Shourya Dutta Biswas <[email protected]>
1 parent 8ae728c commit a16a1de

File tree

5 files changed

+352
-96
lines changed

5 files changed

+352
-96
lines changed

server/src/internalClusterTest/java/org/opensearch/remotemigration/MigrationBaseTestCase.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import java.util.concurrent.atomic.AtomicBoolean;
4747
import java.util.concurrent.atomic.AtomicLong;
4848

49+
import static org.opensearch.cluster.metadata.IndexMetadata.SETTING_NUMBER_OF_REPLICAS;
4950
import static org.opensearch.cluster.routing.allocation.decider.EnableAllocationDecider.CLUSTER_ROUTING_REBALANCE_ENABLE_SETTING;
5051
import static org.opensearch.gateway.remote.RemoteClusterStateService.REMOTE_CLUSTER_STATE_ENABLED_SETTING;
5152
import static org.opensearch.node.remotestore.RemoteStoreNodeService.MIGRATION_DIRECTION_SETTING;
@@ -277,4 +278,30 @@ protected IndexShard getIndexShard(String dataNode, String indexName) throws Exe
277278
IndexService indexService = indicesService.indexService(new Index(indexName, uuid));
278279
return indexService.getShard(0);
279280
}
281+
282+
public void changeReplicaCountAndEnsureGreen(int replicaCount, String indexName) {
283+
assertAcked(
284+
client().admin()
285+
.indices()
286+
.prepareUpdateSettings(indexName)
287+
.setSettings(Settings.builder().put(SETTING_NUMBER_OF_REPLICAS, replicaCount))
288+
);
289+
ensureGreen(indexName);
290+
}
291+
292+
public void completeDocRepToRemoteMigration() {
293+
assertTrue(
294+
internalCluster().client()
295+
.admin()
296+
.cluster()
297+
.prepareUpdateSettings()
298+
.setPersistentSettings(
299+
Settings.builder()
300+
.put(REMOTE_STORE_COMPATIBILITY_MODE_SETTING.getKey(), "strict")
301+
.put(MIGRATION_DIRECTION_SETTING.getKey(), "none")
302+
)
303+
.get()
304+
.isAcknowledged()
305+
);
306+
}
280307
}

server/src/internalClusterTest/java/org/opensearch/remotemigration/RemoteMigrationIndexMetadataUpdateIT.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,74 @@ public void testRemoteIndexPathFileExistsAfterMigration() throws Exception {
546546
assertTrue(Arrays.stream(files).anyMatch(file -> file.toString().contains(fileNamePrefix)));
547547
}
548548

549+
/**
550+
* Scenario:
551+
* Creates an index with 1 pri 1 rep setup with 3 docrep nodes (1 cluster manager + 2 data nodes),
552+
* initiate migration and create 3 remote nodes (1 cluster manager + 2 data nodes) and moves over
553+
* only primary shard copy of the index
554+
* After the primary shard copy is relocated, decrease replica count to 0, stop all docrep nodes
555+
* and conclude migration. Remote store index settings should be applied to the index at this point.
556+
*/
557+
public void testIndexSettingsUpdateDuringReplicaCountDecrement() throws Exception {
558+
String indexName = "migration-index-replica-decrement";
559+
String docrepClusterManager = internalCluster().startClusterManagerOnlyNode();
560+
561+
logger.info("---> Starting 2 docrep nodes");
562+
List<String> docrepNodeNames = internalCluster().startDataOnlyNodes(2);
563+
internalCluster().validateClusterFormed();
564+
565+
logger.info("---> Creating index with 1 primary and 1 replica");
566+
Settings oneReplica = Settings.builder()
567+
.put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 1)
568+
.put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
569+
.build();
570+
createIndexAndAssertDocrepProperties(indexName, oneReplica);
571+
572+
int docsToIndex = randomIntBetween(10, 100);
573+
logger.info("---> Indexing {} on both indices", docsToIndex);
574+
indexBulk(indexName, docsToIndex);
575+
576+
logger.info(
577+
"---> Stopping shard rebalancing to ensure shards do not automatically move over to newer nodes after they are launched"
578+
);
579+
stopShardRebalancing();
580+
581+
logger.info("---> Starting 3 remote store enabled nodes");
582+
initDocRepToRemoteMigration();
583+
setAddRemote(true);
584+
internalCluster().startClusterManagerOnlyNode();
585+
List<String> remoteNodeNames = internalCluster().startDataOnlyNodes(2);
586+
internalCluster().validateClusterFormed();
587+
588+
String primaryNode = primaryNodeName(indexName);
589+
590+
logger.info("---> Moving over primary to remote store enabled nodes");
591+
assertAcked(
592+
client().admin()
593+
.cluster()
594+
.prepareReroute()
595+
.add(new MoveAllocationCommand(indexName, 0, primaryNode, remoteNodeNames.get(0)))
596+
.execute()
597+
.actionGet()
598+
);
599+
waitForRelocation();
600+
waitNoPendingTasksOnAll();
601+
602+
logger.info("---> Reducing replica count to 0 for the index");
603+
changeReplicaCountAndEnsureGreen(0, indexName);
604+
assertDocrepProperties(indexName);
605+
606+
logger.info("---> Stopping all docrep nodes");
607+
internalCluster().stopRandomNode(InternalTestCluster.nameFilter(docrepClusterManager));
608+
for (String node : docrepNodeNames) {
609+
internalCluster().stopRandomNode(InternalTestCluster.nameFilter(node));
610+
}
611+
internalCluster().validateClusterFormed();
612+
completeDocRepToRemoteMigration();
613+
waitNoPendingTasksOnAll();
614+
assertRemoteProperties(indexName);
615+
}
616+
549617
private void createIndexAndAssertDocrepProperties(String index, Settings settings) {
550618
createIndexAssertHealthAndDocrepProperties(index, settings, this::ensureGreen);
551619
}

server/src/main/java/org/opensearch/action/admin/cluster/settings/TransportClusterUpdateSettingsAction.java

Lines changed: 87 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.opensearch.cluster.metadata.Metadata;
4848
import org.opensearch.cluster.node.DiscoveryNode;
4949
import org.opensearch.cluster.node.DiscoveryNodes;
50+
import org.opensearch.cluster.routing.RoutingTable;
5051
import org.opensearch.cluster.routing.allocation.AllocationService;
5152
import org.opensearch.cluster.service.ClusterManagerTaskKeys;
5253
import org.opensearch.cluster.service.ClusterManagerTaskThrottler;
@@ -66,10 +67,13 @@
6667

6768
import java.io.IOException;
6869
import java.util.Collection;
70+
import java.util.Collections;
71+
import java.util.List;
72+
import java.util.Map;
6973
import java.util.Set;
7074
import java.util.stream.Collectors;
7175

72-
import static org.opensearch.index.remote.RemoteMigrationIndexMetadataUpdater.indexHasAllRemoteStoreRelatedMetadata;
76+
import static org.opensearch.index.remote.RemoteMigrationIndexMetadataUpdater.indexHasRemoteStoreSettings;
7377

7478
/**
7579
* Transport action for updating cluster settings
@@ -270,6 +274,13 @@ public ClusterState execute(final ClusterState currentState) {
270274
logger
271275
);
272276
changed = clusterState != currentState;
277+
// Remote Store migration: Checks if the applied cluster settings
278+
// has switched the cluster to STRICT mode. If so, checks and applies
279+
// appropriate index settings depending on the current set of node types
280+
// in the cluster
281+
if (isSwitchToStrictCompatibilityMode(clusterState.metadata().settings())) {
282+
return finalizeMigration(clusterState);
283+
}
273284
return clusterState;
274285
}
275286
}
@@ -284,11 +295,9 @@ public ClusterState execute(final ClusterState currentState) {
284295
public void validateCompatibilityModeSettingRequest(ClusterUpdateSettingsRequest request, ClusterState clusterState) {
285296
Settings settings = Settings.builder().put(request.persistentSettings()).put(request.transientSettings()).build();
286297
if (RemoteStoreNodeService.REMOTE_STORE_COMPATIBILITY_MODE_SETTING.exists(settings)) {
287-
String value = RemoteStoreNodeService.REMOTE_STORE_COMPATIBILITY_MODE_SETTING.get(settings).mode;
288298
validateAllNodesOfSameVersion(clusterState.nodes());
289-
if (RemoteStoreNodeService.CompatibilityMode.STRICT.mode.equals(value)) {
299+
if (isSwitchToStrictCompatibilityMode(settings)) {
290300
validateAllNodesOfSameType(clusterState.nodes());
291-
validateIndexSettings(clusterState);
292301
}
293302
}
294303
}
@@ -323,18 +332,83 @@ private void validateAllNodesOfSameType(DiscoveryNodes discoveryNodes) {
323332
}
324333

325334
/**
326-
* Verifies that while trying to switch to STRICT compatibility mode,
327-
* all indices in the cluster have {@link RemoteMigrationIndexMetadataUpdater#indexHasAllRemoteStoreRelatedMetadata(IndexMetadata)} as <code>true</code>.
328-
* If not, throws {@link SettingsException}
329-
* @param clusterState current cluster state
335+
* Finalizes the docrep to remote-store migration process by applying remote store based index settings
336+
* on indices that are missing them. No-Op if all indices already have the settings applied through
337+
* IndexMetadataUpdater
338+
*
339+
* @param incomingState mutated cluster state after cluster settings were applied
340+
* @return new cluster state with index settings updated
341+
*/
342+
public ClusterState finalizeMigration(ClusterState incomingState) {
343+
Map<String, DiscoveryNode> discoveryNodeMap = incomingState.nodes().getNodes();
344+
if (discoveryNodeMap.isEmpty() == false) {
345+
boolean allNodesRemote = discoveryNodeMap.values().stream().allMatch(discoNode -> discoNode.isRemoteStoreNode() == true);
346+
if (allNodesRemote == true) {
347+
List<IndexMetadata> indicesWithoutRemoteStoreSettings = getIndicesWithoutRemoteStoreSettings(incomingState);
348+
if (indicesWithoutRemoteStoreSettings.isEmpty() == true) {
349+
logger.info("All indices in the cluster has remote store based index settings");
350+
} else {
351+
Metadata mutatedMetadata = applyRemoteStoreSettings(incomingState, indicesWithoutRemoteStoreSettings);
352+
return ClusterState.builder(incomingState).metadata(mutatedMetadata).build();
353+
}
354+
} else {
355+
// TODO: Revert all remote store settings for remote store -> docrep migration
356+
logger.debug("All nodes in the cluster are not remote nodes. Skipping.");
357+
}
358+
}
359+
return incomingState;
360+
}
361+
362+
/**
363+
* Filters out indices which does not have remote store based
364+
* index settings applied even after all shard copies have
365+
* migrated to remote store enabled nodes
330366
*/
331-
private void validateIndexSettings(ClusterState clusterState) {
367+
private List<IndexMetadata> getIndicesWithoutRemoteStoreSettings(ClusterState clusterState) {
332368
Collection<IndexMetadata> allIndicesMetadata = clusterState.metadata().indices().values();
333-
if (allIndicesMetadata.isEmpty() == false
334-
&& allIndicesMetadata.stream().anyMatch(indexMetadata -> indexHasAllRemoteStoreRelatedMetadata(indexMetadata) == false)) {
335-
throw new SettingsException(
336-
"can not switch to STRICT compatibility mode since all indices in the cluster does not have remote store based index settings"
369+
if (allIndicesMetadata.isEmpty() == false) {
370+
List<IndexMetadata> indicesWithoutRemoteSettings = allIndicesMetadata.stream()
371+
.filter(idxMd -> indexHasRemoteStoreSettings(idxMd.getSettings()) == false)
372+
.collect(Collectors.toList());
373+
logger.debug(
374+
"Attempting to switch to strict mode. Count of indices without remote store settings {}",
375+
indicesWithoutRemoteSettings.size()
376+
);
377+
return indicesWithoutRemoteSettings;
378+
}
379+
return Collections.emptyList();
380+
}
381+
382+
/**
383+
* Applies remote store index settings through {@link RemoteMigrationIndexMetadataUpdater}
384+
*/
385+
private Metadata applyRemoteStoreSettings(ClusterState clusterState, List<IndexMetadata> indicesWithRemoteStoreSettings) {
386+
Metadata.Builder metadataBuilder = Metadata.builder(clusterState.getMetadata());
387+
RoutingTable currentRoutingTable = clusterState.getRoutingTable();
388+
DiscoveryNodes currentDiscoveryNodes = clusterState.getNodes();
389+
for (IndexMetadata indexMetadata : indicesWithRemoteStoreSettings) {
390+
IndexMetadata.Builder indexMetadataBuilder = IndexMetadata.builder(indexMetadata);
391+
RemoteMigrationIndexMetadataUpdater indexMetadataUpdater = new RemoteMigrationIndexMetadataUpdater(
392+
currentDiscoveryNodes,
393+
currentRoutingTable,
394+
indexMetadata,
395+
null,
396+
logger
337397
);
398+
indexMetadataUpdater.maybeAddRemoteIndexSettings(indexMetadataBuilder, indexMetadata.getIndex().getName());
399+
metadataBuilder.put(indexMetadataBuilder);
338400
}
401+
return metadataBuilder.build();
402+
}
403+
404+
/**
405+
* Checks if the incoming cluster settings payload is attempting to switch
406+
* the cluster to `STRICT` compatibility mode
407+
* Visible only for tests
408+
*/
409+
public boolean isSwitchToStrictCompatibilityMode(Settings settings) {
410+
return RemoteStoreNodeService.CompatibilityMode.STRICT.mode.equals(
411+
RemoteStoreNodeService.REMOTE_STORE_COMPATIBILITY_MODE_SETTING.get(settings).mode
412+
);
339413
}
340414
}

server/src/main/java/org/opensearch/index/remote/RemoteMigrationIndexMetadataUpdater.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.opensearch.cluster.routing.RoutingTable;
1616
import org.opensearch.cluster.routing.ShardRouting;
1717
import org.opensearch.cluster.routing.ShardRoutingState;
18+
import org.opensearch.common.Nullable;
1819
import org.opensearch.common.settings.Settings;
1920
import org.opensearch.index.remote.RemoteStoreEnums.PathType;
2021
import org.opensearch.indices.replication.common.ReplicationType;
@@ -49,7 +50,7 @@ public RemoteMigrationIndexMetadataUpdater(
4950
DiscoveryNodes discoveryNodes,
5051
RoutingTable routingTable,
5152
IndexMetadata indexMetadata,
52-
Settings clusterSettings,
53+
@Nullable Settings clusterSettings,
5354
Logger logger
5455

5556
) {

0 commit comments

Comments
 (0)