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>

Reply via email to