Skip to content

Commit 8b05f9d

Browse files
committed
Changing compatibility mode with multiple node versions
Signed-off-by: Lakshya Taragi <lakshya.taragi@gmail.com>
1 parent 579cb3f commit 8b05f9d

File tree

3 files changed

+166
-28
lines changed

3 files changed

+166
-28
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public void testSwitchToStrictMode() throws Exception {
100100
exception.getMessage()
101101
);
102102

103-
logger.info(" --> stop remote node");
103+
logger.info(" --> stop remote node so that cluster had only non-remote nodes");
104104
internalCluster().stopRandomNode(InternalTestCluster.nameFilter(remoteNodeName));
105105
ensureStableCluster(2);
106106

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

+40-13
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565
import java.util.ArrayList;
6666
import java.util.List;
6767
import java.util.Optional;
68+
import java.util.Set;
69+
import java.util.stream.Collectors;
6870

6971
/**
7072
* Transport action for updating cluster settings
@@ -272,26 +274,51 @@ public ClusterState execute(final ClusterState currentState) {
272274
}
273275

274276
/**
275-
* Verifies that while trying to switch to STRICT compatibility mode, all nodes must be of the
276-
* same type (all remote or all non-remote). If not, it throws SettingsException error
277+
* Runs various checks associated with changing cluster compatibility mode
277278
* @param request cluster settings update request, for settings to be updated and new values
278279
* @param clusterState current state of cluster, for information on nodes
279280
*/
280281
public static void validateCompatibilityModeSettingRequest(ClusterUpdateSettingsRequest request, ClusterState clusterState) {
281282
if (RemoteStoreNodeService.REMOTE_STORE_COMPATIBILITY_MODE_SETTING.exists(request.persistentSettings())) {
282-
String value = request.persistentSettings().get(RemoteStoreNodeService.REMOTE_STORE_COMPATIBILITY_MODE_SETTING.getKey());
283+
String value = request.persistentSettings()
284+
.get(RemoteStoreNodeService.REMOTE_STORE_COMPATIBILITY_MODE_SETTING.getKey())
285+
.toLowerCase();
286+
List<DiscoveryNode> discoveryNodeList = new ArrayList<>(clusterState.nodes().getNodes().values());
287+
validateAllNodesOfSameVersion(discoveryNodeList);
283288
if (value.equals(RemoteStoreNodeService.CompatibilityMode.STRICT.mode)) {
284-
List<DiscoveryNode> discoveryNodeList = new ArrayList<>(clusterState.nodes().getNodes().values());
285-
Optional<DiscoveryNode> remoteNode = discoveryNodeList.stream().filter(DiscoveryNode::isRemoteStoreNode).findFirst();
286-
Optional<DiscoveryNode> nonRemoteNode = discoveryNodeList.stream()
287-
.filter(dn -> dn.isRemoteStoreNode() == false)
288-
.findFirst();
289-
if (remoteNode.isPresent() && nonRemoteNode.isPresent()) {
290-
throw new SettingsException(
291-
"can not switch to STRICT compatibility mode when the cluster contains both remote and non-remote nodes"
292-
);
293-
}
289+
validateAllNodesOfSameType(discoveryNodeList);
294290
}
295291
}
296292
}
293+
294+
/**
295+
* Verifies that while trying to change the compatibility mode, all nodes must have the same version.
296+
* If not, it throws SettingsException error
297+
* @param discoveryNodeList list of the current discovery nodes in the cluster
298+
*/
299+
private static void validateAllNodesOfSameVersion(List<DiscoveryNode> discoveryNodeList) {
300+
Set<Version> versions = discoveryNodeList.stream().map(DiscoveryNode::getVersion).collect(Collectors.toSet());
301+
if (versions.size() != 1) {
302+
throw new SettingsException(
303+
"can not change the compatibility mode when all the nodes in cluster are not of the same version. Present versions: "
304+
+ versions
305+
);
306+
}
307+
}
308+
309+
/**
310+
* Verifies that while trying to switch to STRICT compatibility mode, all nodes must be of the
311+
* same type (all remote or all non-remote). If not, it throws SettingsException error
312+
* @param discoveryNodeList list of the current discovery nodes in the cluster
313+
*/
314+
private static void validateAllNodesOfSameType(List<DiscoveryNode> discoveryNodeList) {
315+
Optional<DiscoveryNode> remoteNode = discoveryNodeList.stream().filter(DiscoveryNode::isRemoteStoreNode).findFirst();
316+
Optional<DiscoveryNode> nonRemoteNode = discoveryNodeList.stream().filter(dn -> dn.isRemoteStoreNode() == false).findFirst();
317+
if (remoteNode.isPresent() && nonRemoteNode.isPresent()) {
318+
throw new SettingsException(
319+
"can not switch to STRICT compatibility mode when the cluster contains both remote and non-remote nodes"
320+
);
321+
}
322+
}
323+
297324
}

server/src/test/java/org/opensearch/action/support/clustermanager/TransportClusterManagerNodeActionTests.java

+125-14
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.opensearch.cluster.node.DiscoveryNodes;
3838
import org.opensearch.cluster.service.ClusterManagerThrottlingException;
3939
import org.opensearch.cluster.service.ClusterService;
40+
import org.opensearch.common.UUIDs;
4041
import org.opensearch.common.action.ActionFuture;
4142
import org.opensearch.common.settings.Settings;
4243
import org.opensearch.common.settings.SettingsException;
@@ -84,6 +85,9 @@
8485
import static org.opensearch.node.remotestore.RemoteStoreNodeService.REMOTE_STORE_COMPATIBILITY_MODE_SETTING;
8586
import static org.opensearch.test.ClusterServiceUtils.createClusterService;
8687
import static org.opensearch.test.ClusterServiceUtils.setState;
88+
import static org.opensearch.test.VersionUtils.randomCompatibleVersion;
89+
import static org.opensearch.test.VersionUtils.randomOpenSearchVersion;
90+
import static org.hamcrest.Matchers.containsString;
8791
import static org.hamcrest.Matchers.equalTo;
8892
import static org.hamcrest.Matchers.instanceOf;
8993

@@ -711,26 +715,28 @@ public void testDontAllowSwitchingToStrictCompatibilityModeForMixedCluster() {
711715
Settings nodeSettings = Settings.builder().put(REMOTE_STORE_MIGRATION_EXPERIMENTAL, "true").build();
712716
FeatureFlags.initializeFeatureFlags(nodeSettings);
713717

718+
// request to change cluster compatibility mode to STRICT
719+
Settings currentCompatibilityModeSettings = Settings.builder()
720+
.put(REMOTE_STORE_COMPATIBILITY_MODE_SETTING.getKey(), CompatibilityMode.MIXED)
721+
.build();
722+
Settings intendedCompatibilityModeSettings = Settings.builder()
723+
.put(REMOTE_STORE_COMPATIBILITY_MODE_SETTING.getKey(), CompatibilityMode.STRICT)
724+
.build();
714725
ClusterUpdateSettingsRequest request = new ClusterUpdateSettingsRequest();
715-
request.persistentSettings(
716-
Settings.builder().put(REMOTE_STORE_COMPATIBILITY_MODE_SETTING.getKey(), CompatibilityMode.STRICT).build()
717-
);
726+
request.persistentSettings(intendedCompatibilityModeSettings);
718727

719-
DiscoveryNode nonRemoteNode = getNonRemoteNode();
720-
DiscoveryNode remoteNode = getRemoteNode();
728+
// mixed cluster (containing both remote and non-remote nodes)
729+
DiscoveryNode nonRemoteNode1 = getNonRemoteNode();
730+
DiscoveryNode remoteNode1 = getRemoteNode();
721731

722732
DiscoveryNodes discoveryNodes = DiscoveryNodes.builder()
723-
.add(nonRemoteNode)
724-
.localNodeId(nonRemoteNode.getId())
725-
.add(remoteNode)
726-
.localNodeId(remoteNode.getId())
733+
.add(nonRemoteNode1)
734+
.localNodeId(nonRemoteNode1.getId())
735+
.add(remoteNode1)
736+
.localNodeId(remoteNode1.getId())
727737
.build();
728738

729-
Settings mixedModeCompatibilitySettings = Settings.builder()
730-
.put(REMOTE_STORE_COMPATIBILITY_MODE_SETTING.getKey(), CompatibilityMode.MIXED)
731-
.build();
732-
733-
Metadata metadata = Metadata.builder().persistentSettings(mixedModeCompatibilitySettings).build();
739+
Metadata metadata = Metadata.builder().persistentSettings(currentCompatibilityModeSettings).build();
734740

735741
ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT).metadata(metadata).nodes(discoveryNodes).build();
736742

@@ -742,5 +748,110 @@ public void testDontAllowSwitchingToStrictCompatibilityModeForMixedCluster() {
742748
"can not switch to STRICT compatibility mode when the cluster contains both remote and non-remote nodes",
743749
exception.getMessage()
744750
);
751+
752+
DiscoveryNode nonRemoteNode2 = getNonRemoteNode();
753+
DiscoveryNode remoteNode2 = getRemoteNode();
754+
755+
// cluster with only non-remote nodes
756+
discoveryNodes = DiscoveryNodes.builder()
757+
.add(nonRemoteNode1)
758+
.localNodeId(nonRemoteNode1.getId())
759+
.add(nonRemoteNode2)
760+
.localNodeId(nonRemoteNode2.getId())
761+
.build();
762+
ClusterState sameTypeClusterState = ClusterState.builder(clusterState).nodes(discoveryNodes).build();
763+
TransportClusterUpdateSettingsAction.validateCompatibilityModeSettingRequest(request, sameTypeClusterState);
764+
765+
// cluster with only non-remote nodes
766+
discoveryNodes = DiscoveryNodes.builder()
767+
.add(remoteNode1)
768+
.localNodeId(remoteNode1.getId())
769+
.add(remoteNode2)
770+
.localNodeId(remoteNode2.getId())
771+
.build();
772+
sameTypeClusterState = ClusterState.builder(sameTypeClusterState).nodes(discoveryNodes).build();
773+
TransportClusterUpdateSettingsAction.validateCompatibilityModeSettingRequest(request, sameTypeClusterState);
774+
}
775+
776+
public void testDontAllowSwitchingCompatibilityModeForClusterWithMultipleVersions() {
777+
Settings nodeSettings = Settings.builder().put(REMOTE_STORE_MIGRATION_EXPERIMENTAL, "true").build();
778+
FeatureFlags.initializeFeatureFlags(nodeSettings);
779+
780+
// request to change cluster compatibility mode
781+
boolean toStrictMode = randomBoolean();
782+
Settings currentCompatibilityModeSettings = Settings.builder()
783+
.put(REMOTE_STORE_COMPATIBILITY_MODE_SETTING.getKey(), CompatibilityMode.MIXED)
784+
.build();
785+
Settings intendedCompatibilityModeSettings = Settings.builder()
786+
.put(REMOTE_STORE_COMPATIBILITY_MODE_SETTING.getKey(), toStrictMode ? CompatibilityMode.STRICT : CompatibilityMode.MIXED)
787+
.build();
788+
ClusterUpdateSettingsRequest request = new ClusterUpdateSettingsRequest();
789+
request.persistentSettings(intendedCompatibilityModeSettings);
790+
791+
// two different but compatible open search versions for the discovery nodes
792+
final Version version1 = randomOpenSearchVersion(random());
793+
final Version version2 = randomCompatibleVersion(random(), version1);
794+
795+
assert version1.equals(version2) == false : "current nodes in the cluster must be of different versions";
796+
797+
DiscoveryNode discoveryNode1 = new DiscoveryNode(
798+
UUIDs.base64UUID(),
799+
buildNewFakeTransportAddress(),
800+
toStrictMode ? remoteStoreNodeAttributes(SEGMENT_REPO, TRANSLOG_REPO) : Collections.emptyMap(),
801+
DiscoveryNodeRole.BUILT_IN_ROLES,
802+
version1
803+
);
804+
DiscoveryNode discoveryNode2 = new DiscoveryNode(
805+
UUIDs.base64UUID(),
806+
buildNewFakeTransportAddress(),
807+
toStrictMode ? remoteStoreNodeAttributes(SEGMENT_REPO, TRANSLOG_REPO) : Collections.emptyMap(),
808+
DiscoveryNodeRole.BUILT_IN_ROLES,
809+
version2 // not same as discoveryNode1
810+
);
811+
812+
DiscoveryNodes discoveryNodes = DiscoveryNodes.builder()
813+
.add(discoveryNode1)
814+
.localNodeId(discoveryNode1.getId())
815+
.add(discoveryNode2)
816+
.localNodeId(discoveryNode2.getId())
817+
.build();
818+
819+
Metadata metadata = Metadata.builder().persistentSettings(currentCompatibilityModeSettings).build();
820+
821+
ClusterState differentVersionClusterState = ClusterState.builder(ClusterName.DEFAULT)
822+
.metadata(metadata)
823+
.nodes(discoveryNodes)
824+
.build();
825+
826+
// changing compatibility mode when all nodes are not of the same version
827+
final SettingsException exception = expectThrows(
828+
SettingsException.class,
829+
() -> TransportClusterUpdateSettingsAction.validateCompatibilityModeSettingRequest(request, differentVersionClusterState)
830+
);
831+
assertThat(
832+
exception.getMessage(),
833+
containsString(
834+
"can not change the compatibility mode when all the nodes in cluster are not of the same version. Present versions: ["
835+
)
836+
);
837+
838+
// changing compatibility mode when all nodes are of the same version
839+
discoveryNode2 = new DiscoveryNode(
840+
UUIDs.base64UUID(),
841+
buildNewFakeTransportAddress(),
842+
toStrictMode ? remoteStoreNodeAttributes(SEGMENT_REPO, TRANSLOG_REPO) : Collections.emptyMap(),
843+
DiscoveryNodeRole.BUILT_IN_ROLES,
844+
version1 // same as discoveryNode1
845+
);
846+
discoveryNodes = DiscoveryNodes.builder()
847+
.add(discoveryNode1)
848+
.localNodeId(discoveryNode1.getId())
849+
.add(discoveryNode2)
850+
.localNodeId(discoveryNode2.getId())
851+
.build();
852+
853+
ClusterState sameVersionClusterState = ClusterState.builder(differentVersionClusterState).nodes(discoveryNodes).build();
854+
TransportClusterUpdateSettingsAction.validateCompatibilityModeSettingRequest(request, sameVersionClusterState);
745855
}
856+
746857
}

0 commit comments

Comments
 (0)