This is an automated email from the ASF dual-hosted git repository.
av pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new e79b39591a1 IGNITE-26334 Implement a TopologyValidator based on
DataCenterId (#12442)
e79b39591a1 is described below
commit e79b39591a1d43cb2c1c010d19d597b63fc90592
Author: Anton Vinogradov <[email protected]>
AuthorDate: Fri Oct 31 17:01:45 2025 +0300
IGNITE-26334 Implement a TopologyValidator based on DataCenterId (#12442)
---
.../ignite/configuration/CacheConfiguration.java | 9 +
.../processors/cache/ClusterCachesInfo.java | 12 +-
.../ignite/topology/MdcTopologyValidator.java | 138 +++++++++
.../org/apache/ignite/topology/package-info.java | 23 ++
.../cache/CacheValidatorMetricsTest.java | 4 +
.../IgniteTopologyValidatorAbstractCacheTest.java | 8 +
...teTopologyValidatorCacheGroupsAbstractTest.java | 8 +
.../datacenter/MultiDataCenterDeploymentTest.java | 2 +
.../validator/MdcTopologyValidatorTest.java | 333 +++++++++++++++++++++
.../testsuites/IgniteSpiFailoverSelfTestSuite.java | 6 +-
parent/pom.xml | 2 +-
11 files changed, 540 insertions(+), 5 deletions(-)
diff --git
a/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
b/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
index c0c5100248f..d8ecac8d9d9 100644
---
a/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
+++
b/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
@@ -63,6 +63,7 @@ import org.apache.ignite.lang.IgnitePredicate;
import org.apache.ignite.plugin.CachePluginConfiguration;
import org.apache.ignite.spi.encryption.EncryptionSpi;
import org.apache.ignite.spi.encryption.keystore.KeystoreEncryptionSpi;
+import org.apache.ignite.topology.MdcTopologyValidator;
import org.jetbrains.annotations.Nullable;
import static
org.apache.ignite.IgniteSystemProperties.IGNITE_DEFAULT_DISK_PAGE_COMPRESSION;
@@ -2221,6 +2222,14 @@ public class CacheConfiguration<K, V> extends
MutableConfiguration<K, V> {
* @return {@code this} for chaining.
*/
public CacheConfiguration<K, V> setTopologyValidator(TopologyValidator
topValidator) {
+ try {
+ if (topValidator instanceof MdcTopologyValidator)
+ ((MdcTopologyValidator)topValidator).checkConfiguration();
+ }
+ catch (Exception e) {
+ throw new CacheException(e);
+ }
+
this.topValidator = topValidator;
return this;
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/ClusterCachesInfo.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/ClusterCachesInfo.java
index a76d7dfb50e..fc18b656530 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/ClusterCachesInfo.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/ClusterCachesInfo.java
@@ -409,8 +409,11 @@ public class ClusterCachesInfo {
CU.checkAttributeMismatch(log, rmtAttr.cacheName(), rmt,
"cachePreloadMode",
"Cache preload mode", locAttr.cacheRebalanceMode(),
rmtAttr.cacheRebalanceMode(), true);
- CU.checkAttributeMismatch(log, rmtAttr.cacheName(), rmt,
"topologyValidator",
- "Cache topology validator", locAttr.topologyValidatorClassName(),
rmtAttr.topologyValidatorClassName(), true);
+ CU.checkAttributeMismatch(log, rmtAttr.cacheName(), rmt,
"topologyValidatorClass",
+ "Cache topology validator class",
locAttr.topologyValidatorClassName(), rmtAttr.topologyValidatorClassName(),
true);
+
+ CU.checkAttributeMismatch(log, rmtAttr.cacheName(), rmt,
"topologyValidator", "Cache topology validator",
+ locAttr.configuration().getTopologyValidator(),
rmtAttr.configuration().getTopologyValidator(), true);
ClusterNode rmtNode = ctx.discovery().node(rmt);
@@ -2576,9 +2579,12 @@ public class ClusterCachesInfo {
CU.validateCacheGroupsAttributesMismatch(log, cfg, startCfg,
"dataRegionName", "Data region",
cfg.getDataRegionName(), startCfg.getDataRegionName(), true);
- CU.validateCacheGroupsAttributesMismatch(log, cfg, startCfg,
"topologyValidator", "Topology validator",
+ CU.validateCacheGroupsAttributesMismatch(log, cfg, startCfg,
"topologyValidatorClass", "Topology validator class",
attr1.topologyValidatorClassName(),
attr2.topologyValidatorClassName(), true);
+ CU.validateCacheGroupsAttributesMismatch(log, cfg, startCfg,
"topologyValidator", "Topology validator",
+ cfg.getTopologyValidator(), startCfg.getTopologyValidator(), true);
+
CU.validateCacheGroupsAttributesMismatch(log, cfg, startCfg,
"partitionLossPolicy", "Partition Loss Policy",
cfg.getPartitionLossPolicy(), startCfg.getPartitionLossPolicy(),
true);
diff --git
a/modules/core/src/main/java/org/apache/ignite/topology/MdcTopologyValidator.java
b/modules/core/src/main/java/org/apache/ignite/topology/MdcTopologyValidator.java
new file mode 100644
index 00000000000..5a05222c15c
--- /dev/null
+++
b/modules/core/src/main/java/org/apache/ignite/topology/MdcTopologyValidator.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.topology;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Stream;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.configuration.TopologyValidator;
+import org.apache.ignite.lang.IgniteExperimental;
+
+/**
+ * Multi-Datacenter Topology Validator.
+ *
+ * <p>This class is used to validate the cluster topology in a
multi-datacenter (MDC) environment
+ * by enforcing rules based on the visibility of datacenters.
+ * It provides protection against split-brain scenarios during datacenter
failures or unavailability due to network issues.</p>
+ *
+ * <p>
+ * In order to use MdcTopologyValidator one has to specify datacenter ID
attribute on each server node.
+ * Datacenter ID is an arbitrary string that can be set with {@link
IgniteSystemProperties#IGNITE_DATA_CENTER_ID}
+ * system property on the node startup.
+ * All server nodes belonging to the same datacenter should specify the same
datacenter ID, and nodes in different datacenters
+ * should have different datacenter IDs.
+ * </p>
+ * <p>The validator supports two modes of operation:</p>
+ * <ul>
+ * <li><strong>Majority-based validation:</strong> When an odd number of
datacenters are defined, the validator enables
+ * data modification operations in the cluster segment that contain a
majority of datacenters.
+ * Any segment containing a minority of datacenters is considered as
invalid with only read operations available.</li>
+ * <li><strong>Main Datacenter validation:</strong> When an even number of
datacenters are defined, a main datacenter
+ * should be specified. The cluster segment remains write-accessible
as long as the main datacenter is visible from
+ * all nodes of that segment.</li>
+ * </ul>
+ *
+ * <p><strong>Usage Requirements:</strong></p>
+ * <ul>
+ * <li>If number of datacenters is even, specify a main datacenter via
{@link #setMainDatacenter(String)}.
+ * Set of datacenters could be left null.</li>
+ * <li>If number of datacenters is odd, set of datacenter IDs must be
specified via {@link #setDatacenters(Set)}.
+ * Main datacenter setting is ignored and could be left null.</li>
+ *
+ * </ul>
+ *
+ * <p><strong>Example:</strong></p>
+ * <pre>
+ * MdcTopologyValidator mdcValidator = new MdcTopologyValidator();
+ * mdcValidator.setDatacenters(Set.of("DC1", "DC2", "DC3"));
+ *
+ * CacheConfiguration cacheCfg = new CacheConfiguration("example-cache")
+ * .setTopologyValidator(mdcValidator)
+ * // other cache properties.
+ * </pre>
+ *
+ * <p><strong>Note:</strong> This class is marked with the {@link
IgniteExperimental} annotation and may change in future releases.</p>
+ *
+ * @see TopologyValidator
+ * @since Apache Ignite 2.18
+ */
+@IgniteExperimental
+public class MdcTopologyValidator implements TopologyValidator {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** */
+ private Set<String> dcs;
+
+ /** */
+ private String mainDc;
+
+ /** @param datacenters Datacenters.*/
+ public void setDatacenters(Set<String> datacenters) {
+ dcs = datacenters;
+ }
+
+ /** @param mainDatacenter Main datacenter.*/
+ public void setMainDatacenter(String mainDatacenter) {
+ mainDc = mainDatacenter;
+ }
+
+ /** */
+ public void checkConfiguration() {
+ if (dcs == null && mainDc == null)
+ throw new IllegalStateException("Either set of datacenters or main
datacenter should be specified.");
+
+ if (dcs != null && dcs.isEmpty())
+ throw new IllegalStateException("Please provide a non-empty set of
datacenters.");
+
+ if (mainDc != null && dcs != null && dcs.size() % 2 == 1)
+ throw new IllegalStateException("Uneven number of datacenters
cannot be used along with main datacenter. " +
+ "Please remove main datacenter setting or specify even number
of datacenters.");
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean validate(Collection<ClusterNode> nodes) {
+ Stream<ClusterNode> servers = nodes.stream().filter(node ->
!node.isClient());
+
+ if (mainDc != null)
+ return servers.anyMatch(n -> n.dataCenterId() != null &&
n.dataCenterId().equals(mainDc));
+
+ long visible =
servers.map(ClusterNode::dataCenterId).distinct().count();
+ int half = dcs.size() / 2;
+
+ return visible > half;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ MdcTopologyValidator validator = (MdcTopologyValidator)o;
+
+ return Objects.equals(dcs, validator.dcs) && Objects.equals(mainDc,
validator.mainDc);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return Objects.hash(dcs, mainDc);
+ }
+}
diff --git
a/modules/core/src/main/java/org/apache/ignite/topology/package-info.java
b/modules/core/src/main/java/org/apache/ignite/topology/package-info.java
new file mode 100644
index 00000000000..ec9848c0a63
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/topology/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * <!-- Package description. -->
+ * Contains topology-related classes.
+ */
+
+package org.apache.ignite.topology;
diff --git
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/CacheValidatorMetricsTest.java
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/CacheValidatorMetricsTest.java
index 470771b559b..680aecdfcdc 100644
---
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/CacheValidatorMetricsTest.java
+++
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/CacheValidatorMetricsTest.java
@@ -62,6 +62,10 @@ public class CacheValidatorMetricsTest extends
GridCommonAbstractTest implements
.setCacheMode(CacheMode.REPLICATED)
.setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL)
.setTopologyValidator(new TopologyValidator() {
+ @Override public boolean equals(Object obj) {
+ return true;
+ }
+
@Override public boolean validate(Collection<ClusterNode>
nodes) {
return nodes.size() == 2;
}
diff --git
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteTopologyValidatorAbstractCacheTest.java
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteTopologyValidatorAbstractCacheTest.java
index d5ac52b497f..f8bf8931d66 100644
---
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteTopologyValidatorAbstractCacheTest.java
+++
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteTopologyValidatorAbstractCacheTest.java
@@ -335,6 +335,10 @@ public abstract class
IgniteTopologyValidatorAbstractCacheTest extends IgniteCac
@Override public TopologyValidator topologyValidator(String cacheName)
{
if (CACHE_NAME_1.equals(cacheName)) {
return new TopologyValidator() {
+ @Override public boolean equals(Object obj) {
+ return true;
+ }
+
@Override public boolean validate(Collection<ClusterNode>
nodes) {
return servers(nodes) == 2;
}
@@ -342,6 +346,10 @@ public abstract class
IgniteTopologyValidatorAbstractCacheTest extends IgniteCac
}
else if (CACHE_NAME_2.equals(cacheName)) {
return new TopologyValidator() {
+ @Override public boolean equals(Object obj) {
+ return true;
+ }
+
@Override public boolean validate(Collection<ClusterNode>
nodes) {
return servers(nodes) >= 2;
}
diff --git
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteTopologyValidatorCacheGroupsAbstractTest.java
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteTopologyValidatorCacheGroupsAbstractTest.java
index 7392e933ff7..3a4e716dedc 100644
---
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteTopologyValidatorCacheGroupsAbstractTest.java
+++
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteTopologyValidatorCacheGroupsAbstractTest.java
@@ -134,6 +134,10 @@ public abstract class
IgniteTopologyValidatorCacheGroupsAbstractTest extends Ign
@Override public TopologyValidator topologyValidator(String grpName) {
if (GROUP_1.equals(grpName)) {
return new TopologyValidator() {
+ @Override public boolean equals(Object obj) {
+ return true;
+ }
+
@Override public boolean validate(Collection<ClusterNode>
nodes) {
return nodes.size() == 2;
}
@@ -141,6 +145,10 @@ public abstract class
IgniteTopologyValidatorCacheGroupsAbstractTest extends Ign
}
else if (GROUP_2.equals(grpName)) {
return new TopologyValidator() {
+ @Override public boolean equals(Object obj) {
+ return true;
+ }
+
@Override public boolean validate(Collection<ClusterNode>
nodes) {
return nodes.size() >= 2;
}
diff --git
a/modules/core/src/test/java/org/apache/ignite/spi/discovery/datacenter/MultiDataCenterDeploymentTest.java
b/modules/core/src/test/java/org/apache/ignite/spi/discovery/datacenter/MultiDataCenterDeploymentTest.java
index 51a1c6d14ed..597bf58a629 100644
---
a/modules/core/src/test/java/org/apache/ignite/spi/discovery/datacenter/MultiDataCenterDeploymentTest.java
+++
b/modules/core/src/test/java/org/apache/ignite/spi/discovery/datacenter/MultiDataCenterDeploymentTest.java
@@ -54,6 +54,8 @@ public class MultiDataCenterDeploymentTest extends
GridCommonAbstractTest {
super.afterTest();
stopAllGrids();
+
+ System.clearProperty(IgniteSystemProperties.IGNITE_DATA_CENTER_ID);
}
/**
diff --git
a/modules/core/src/test/java/org/apache/ignite/spi/failover/topology/validator/MdcTopologyValidatorTest.java
b/modules/core/src/test/java/org/apache/ignite/spi/failover/topology/validator/MdcTopologyValidatorTest.java
new file mode 100644
index 00000000000..fa30e32d404
--- /dev/null
+++
b/modules/core/src/test/java/org/apache/ignite/spi/failover/topology/validator/MdcTopologyValidatorTest.java
@@ -0,0 +1,333 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.spi.failover.topology.validator;
+
+import java.util.Set;
+import java.util.concurrent.ThreadLocalRandom;
+import javax.cache.CacheException;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteSystemProperties;
+import org.apache.ignite.cluster.ClusterState;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.DataRegionConfiguration;
+import org.apache.ignite.configuration.DataStorageConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.configuration.TopologyValidator;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.apache.ignite.topology.MdcTopologyValidator;
+import org.junit.Test;
+
+/** */
+public class MdcTopologyValidatorTest extends GridCommonAbstractTest {
+ /** */
+ private static final String DC_ID_0 = "DC0";
+
+ /** */
+ private static final String DC_ID_1 = "DC1";
+
+ /** */
+ private static final String DC_ID_2 = "DC2";
+
+ /** */
+ private static final String KEY = "key";
+
+ /** */
+ private static final String VAL = "val";
+
+ /** {@inheritDoc} */
+ @Override protected void afterTest() throws Exception {
+ super.afterTest();
+
+ stopAllGrids();
+
+ cleanPersistenceDir();
+
+ System.clearProperty(IgniteSystemProperties.IGNITE_DATA_CENTER_ID);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String
igniteInstanceName) throws Exception {
+ return super.getConfiguration(igniteInstanceName)
+ .setDataStorageConfiguration(
+ new DataStorageConfiguration()
+ .setDefaultDataRegionConfiguration(
+ new DataRegionConfiguration()
+ .setPersistenceEnabled(true)
+ )
+ )
+ .setConsistentId(igniteInstanceName);
+ }
+
+ /** */
+ @Test
+ public void testEmptyConfig() {
+ MdcTopologyValidator topValidator = new MdcTopologyValidator();
+
+ GridTestUtils.assertThrows(log,
+ () -> createClusterWithCache(topValidator, false),
+ CacheException.class,
+ "Either set of datacenters or main datacenter should be
specified.");
+ }
+
+ /** */
+ @Test
+ public void testOddDcsWithMain() {
+ MdcTopologyValidator topValidator = new MdcTopologyValidator();
+
+ topValidator.setDatacenters(Set.of(DC_ID_0, DC_ID_1, DC_ID_2));
+ topValidator.setMainDatacenter(DC_ID_1);
+
+ GridTestUtils.assertThrows(log,
+ () -> createClusterWithCache(topValidator, false),
+ CacheException.class,
+ "Uneven number of datacenters cannot be used along with main
datacenter.");
+ }
+
+ /** Checks 1DC case with MdcTopologyValidator usage.*/
+ @Test
+ public void testEmptyDc() {
+ MdcTopologyValidator topValidator = new MdcTopologyValidator();
+
+ topValidator.setDatacenters(Set.of());
+
+ GridTestUtils.assertThrows(log,
+ () -> createClusterWithCache(topValidator, false),
+ CacheException.class,
+ "Please provide a non-empty set of datacenters.");
+ }
+
+ /** */
+ @Test
+ public void testNodeWithoutDcSpecifiedWithMajorityBasedValidator() throws
Exception {
+ MdcTopologyValidator topValidator = new MdcTopologyValidator();
+
+ topValidator.setDatacenters(Set.of(DC_ID_0, DC_ID_1, DC_ID_2));
+
+ IgniteEx srv = startGrid(0);
+
+ waitForTopology(1);
+
+ srv.cluster().state(ClusterState.ACTIVE);
+
+ CacheConfiguration<Object, Object> cfgCache = new
CacheConfiguration<>("cache").setTopologyValidator(topValidator);
+
+ IgniteCache cache = srv.createCache(cfgCache);
+
+ GridTestUtils.assertThrows(log, () -> cache.put(KEY, VAL),
IgniteException.class, "cache topology is not valid");
+ }
+
+ /** */
+ @Test
+ public void testNodeWithoutDcSpecifiedWithMainBasedValidator() throws
Exception {
+ MdcTopologyValidator topValidator = new MdcTopologyValidator();
+
+ topValidator.setMainDatacenter(DC_ID_1);
+
+ IgniteEx srv = startGrid(0);
+
+ waitForTopology(1);
+
+ srv.cluster().state(ClusterState.ACTIVE);
+
+ CacheConfiguration<Object, Object> cfgCache = new
CacheConfiguration<>("cache").setTopologyValidator(topValidator);
+
+ IgniteCache cache = srv.createCache(cfgCache);
+
+ GridTestUtils.assertThrows(log, () -> cache.put(KEY, VAL),
IgniteException.class, "cache topology is not valid");
+ }
+
+ /** */
+ @Test
+ public void testClientDoesNotAffectValidation() throws Exception {
+ MdcTopologyValidator topValidator = new MdcTopologyValidator();
+
+ topValidator.setMainDatacenter(DC_ID_1);
+ topValidator.setDatacenters(Set.of(DC_ID_0, DC_ID_1));
+
+ System.setProperty(IgniteSystemProperties.IGNITE_DATA_CENTER_ID,
DC_ID_0);
+ startGrid(0);
+
+ System.setProperty(IgniteSystemProperties.IGNITE_DATA_CENTER_ID,
DC_ID_1);
+ IgniteEx client = startClientGrid("client");
+
+ waitForTopology(2);
+
+ client.cluster().state(ClusterState.ACTIVE);
+
+ CacheConfiguration<Object, Object> cfgCache = new
CacheConfiguration<>("cache").setTopologyValidator(topValidator);
+
+ IgniteCache<Object, Object> cache = client.getOrCreateCache(cfgCache);
+
+ GridTestUtils.assertThrows(log, () -> cache.put(KEY, VAL),
IgniteException.class, "cache topology is not valid");
+ }
+
+ /** */
+ @Test
+ public void testTopologyValidatorEqualityCheck() throws Exception {
+ System.setProperty(IgniteSystemProperties.IGNITE_DATA_CENTER_ID,
DC_ID_1);
+ IgniteEx srv0 = startGrid(0);
+
+ startGrid(1);
+
+ waitForTopology(2);
+
+ srv0.cluster().state(ClusterState.ACTIVE);
+
+ MdcTopologyValidator topValidator = new MdcTopologyValidator();
+
+ topValidator.setMainDatacenter(DC_ID_1);
+ topValidator.setDatacenters(Set.of(DC_ID_0, DC_ID_1));
+
+ CacheConfiguration<Object, Object> cfgCache1 = new
CacheConfiguration<>(DEFAULT_CACHE_NAME).setTopologyValidator(topValidator);
+
+ srv0.getOrCreateCache(cfgCache1);
+
+ stopGrid(1);
+
+ srv0.destroyCache(cfgCache1.getName());
+
+ topValidator.setMainDatacenter(DC_ID_0); // Changed
+
+ CacheConfiguration<Object, Object> cfgCache2 = new
CacheConfiguration<>(DEFAULT_CACHE_NAME).setTopologyValidator(topValidator);
+
+ srv0.getOrCreateCache(cfgCache2);
+
+ startGrid(2);
+
+ waitForTopology(2);
+
+ GridTestUtils.assertThrows(log, () -> startGrid(1),
IgniteCheckedException.class, "Cache topology validator mismatch");
+ }
+
+ /** */
+ @Test
+ public void testMainDcBasedValidator() throws Exception {
+ MdcTopologyValidator topValidator = new MdcTopologyValidator();
+
+ topValidator.setDatacenters(Set.of(DC_ID_0, DC_ID_1, DC_ID_2, "DC4"));
+ topValidator.setMainDatacenter(DC_ID_1);
+
+ IgniteCache<Object, Object> cache =
createClusterWithCache(topValidator, true);
+
+ cache.put(KEY, VAL);
+ assertEquals(VAL, cache.get(KEY));
+
+ stopGrid(2);
+
+ cache.put(KEY, VAL + 1);
+ assertEquals(VAL + 1, cache.get(KEY));
+
+ stopGrid(1);
+
+ GridTestUtils.assertThrows(log, () -> cache.put(KEY, VAL + 2),
IgniteException.class, "cache topology is not valid");
+ }
+
+ /** */
+ @Test
+ public void testMajorityBasedValidator() throws Exception {
+ MdcTopologyValidator topValidator = new MdcTopologyValidator();
+
+ topValidator.setDatacenters(Set.of(DC_ID_0, DC_ID_1, DC_ID_2));
+
+ IgniteCache<Object, Object> cache =
createClusterWithCache(topValidator, true);
+
+ cache.put(KEY, VAL);
+ assertEquals(VAL, cache.get(KEY));
+
+ int randomNode = ThreadLocalRandom.current().nextInt(1, 3);
+
+ stopGrid(randomNode);
+
+ cache.put(KEY, VAL + 1);
+ assertEquals(VAL + 1, cache.get(KEY));
+
+ stopGrid(randomNode == 1 ? 2 : 1);
+
+ GridTestUtils.assertThrows(log, () -> cache.put(KEY, VAL + 2),
IgniteException.class, "cache topology is not valid");
+ }
+
+ /** */
+ @Test
+ public void testBigCluster() throws Exception {
+ MdcTopologyValidator topValidator = new MdcTopologyValidator();
+
+ topValidator.setDatacenters(Set.of(DC_ID_0, DC_ID_1, DC_ID_2));
+
+ System.setProperty(IgniteSystemProperties.IGNITE_DATA_CENTER_ID,
DC_ID_0);
+
+ startGrid(0);
+
+ System.setProperty(IgniteSystemProperties.IGNITE_DATA_CENTER_ID,
DC_ID_1);
+
+ IgniteEx srv = startGrid(1);
+ startGrid(10);
+ startGrid(11);
+ startGrid(12);
+
+ System.setProperty(IgniteSystemProperties.IGNITE_DATA_CENTER_ID,
DC_ID_2);
+
+ startGrid(2);
+
+ waitForTopology(6);
+
+ srv.cluster().state(ClusterState.ACTIVE);
+
+ CacheConfiguration<Object, Object> cfgCache = new
CacheConfiguration<>("cache").setTopologyValidator(topValidator);
+
+ IgniteCache<Object, Object> cache = srv.createCache(cfgCache);
+
+ cache.put(KEY, VAL);
+ assertEquals(VAL, cache.get(KEY));
+
+ stopGrid(0);
+ stopGrid(2);
+
+ // Checking case when 4 nodes are alive, but only in single DC
+ GridTestUtils.assertThrows(log, () -> cache.put(KEY, VAL + 2),
IgniteException.class, "cache topology is not valid");
+ }
+
+ /** */
+ private IgniteCache<Object, Object>
createClusterWithCache(TopologyValidator topValidator, boolean setDc) throws
Exception {
+ if (setDc)
+ System.setProperty(IgniteSystemProperties.IGNITE_DATA_CENTER_ID,
DC_ID_0);
+
+ IgniteEx srv0 = startGrid(0);
+
+ if (setDc)
+ System.setProperty(IgniteSystemProperties.IGNITE_DATA_CENTER_ID,
DC_ID_1);
+
+ startGrid(1);
+
+ if (setDc)
+ System.setProperty(IgniteSystemProperties.IGNITE_DATA_CENTER_ID,
DC_ID_2);
+
+ startGrid(2);
+
+ waitForTopology(3);
+
+ srv0.cluster().state(ClusterState.ACTIVE);
+
+ CacheConfiguration<Object, Object> cfgCache = new
CacheConfiguration<>("cache").setTopologyValidator(topValidator);
+
+ return srv0.createCache(cfgCache);
+ }
+}
diff --git
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSpiFailoverSelfTestSuite.java
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSpiFailoverSelfTestSuite.java
index 29bfa00ad49..eb6268ed600 100644
---
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSpiFailoverSelfTestSuite.java
+++
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSpiFailoverSelfTestSuite.java
@@ -26,6 +26,7 @@ import
org.apache.ignite.spi.failover.jobstealing.GridJobStealingFailoverSpiSelf
import
org.apache.ignite.spi.failover.jobstealing.GridJobStealingFailoverSpiStartStopSelfTest;
import org.apache.ignite.spi.failover.never.GridNeverFailoverSpiSelfTest;
import
org.apache.ignite.spi.failover.never.GridNeverFailoverSpiStartStopSelfTest;
+import
org.apache.ignite.spi.failover.topology.validator.MdcTopologyValidatorTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@@ -46,7 +47,10 @@ import org.junit.runners.Suite;
GridJobStealingFailoverSpiSelfTest.class,
GridJobStealingFailoverSpiOneNodeSelfTest.class,
GridJobStealingFailoverSpiStartStopSelfTest.class,
- GridJobStealingFailoverSpiConfigSelfTest.class
+ GridJobStealingFailoverSpiConfigSelfTest.class,
+
+ // Topology validator.
+ MdcTopologyValidatorTest.class,
})
public class IgniteSpiFailoverSelfTestSuite {
}
diff --git a/parent/pom.xml b/parent/pom.xml
index 2a0e65ef077..ca803a6f401 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -344,7 +344,7 @@
<groups>
<group>
<title>Common Grid APIs</title>
-
<packages>org.apache.ignite:org.apache.ignite.cluster:org.apache.ignite.lifecycle:org.apache.ignite.configuration:org.apache.ignite.lang:org.apache.ignite.resources:org.apache.ignite.thread:org.apache.ignite.scheduler:org.apache.ignite.events:org.apache.ignite.messaging:org.apache.ignite.startup*:org.apache.ignite.mem</packages>
+
<packages>org.apache.ignite:org.apache.ignite.cluster:org.apache.ignite.lifecycle:org.apache.ignite.configuration:org.apache.ignite.lang:org.apache.ignite.resources:org.apache.ignite.thread:org.apache.ignite.scheduler:org.apache.ignite.events:org.apache.ignite.messaging:org.apache.ignite.startup*:org.apache.ignite.mem:org.apache.ignite.topology</packages>
</group>
<group>
<title>Data Grid APIs</title>