You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sk...@apache.org on 2022/02/21 10:28:24 UTC
[ignite] branch master updated: IGNITE-16579 Fixed an issue that caused a failed deactivation of the cluster. Fixes #9834
This is an automated email from the ASF dual-hosted git repository.
sk0x50 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 45da1f5 IGNITE-16579 Fixed an issue that caused a failed deactivation of the cluster. Fixes #9834
45da1f5 is described below
commit 45da1f56bb2d39e44bbd543882f9d1fe4397a439
Author: Slava Koptilin <sl...@gmail.com>
AuthorDate: Mon Feb 21 13:25:24 2022 +0300
IGNITE-16579 Fixed an issue that caused a failed deactivation of the cluster. Fixes #9834
---
.../apache/ignite/util/GridCommandHandlerTest.java | 5 +-
.../internal/processors/cache/GridCacheUtils.java | 50 +++++
.../cluster/GridClusterStateProcessor.java | 232 ++++++++++++++-------
.../cache/IgniteClusterActivateDeactivateTest.java | 5 +-
...usterActivateDeactivateTestWithPersistence.java | 220 ++++++++++++++++++-
5 files changed, 428 insertions(+), 84 deletions(-)
diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java
index 12a142d..809d78a 100644
--- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java
+++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java
@@ -968,7 +968,10 @@ public class GridCommandHandlerTest extends GridCommandHandlerClusterPerMethodAb
CountDownLatch latch = getNewStateLatch(ignite.cluster().state(), state);
- assertEquals(EXIT_CODE_OK, execute("--set-state", strState));
+ if (state == INACTIVE)
+ assertEquals(EXIT_CODE_OK, execute("--set-state", strState, "--force"));
+ else
+ assertEquals(EXIT_CODE_OK, execute("--set-state", strState));
latch.await(getTestTimeout(), TimeUnit.MILLISECONDS);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUtils.java
index 1369eff..01eb84a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUtils.java
@@ -1968,6 +1968,56 @@ public class GridCacheUtils {
}
/**
+ * Finds and returns a data region configuration with the specified name.
+ *
+ * @param dsCfg Data storage configuration.
+ * @param name Name of data region configuration to find.
+ * @return Data region configuration with the specified name
+ * or {@code null} if the given data storage configuration does not contain such data region.
+ */
+ @Nullable public static DataRegionConfiguration findDataRegionConfiguration(
+ @Nullable DataStorageConfiguration dsCfg,
+ @Nullable String name
+ ) {
+ if (dsCfg == null || name == null)
+ return null;
+
+ if (dsCfg.getDefaultDataRegionConfiguration().getName().equals(name))
+ return dsCfg.getDefaultDataRegionConfiguration();
+
+ DataRegionConfiguration[] regions = dsCfg.getDataRegionConfigurations();
+
+ if (regions == null)
+ return null;
+
+ for (int i = 0; i < regions.length; ++i) {
+ if (regions[i].getName().equals(name))
+ return regions[i];
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds and returns a data region configuration with the specified name that is configured on remote node.
+ *
+ * @param node Remote node.
+ * @param marshaller JDK marshaller that is used in order to extract data storage configuration.
+ * @param clsLdr Classloader that is used in order to extract data storage configuration.
+ * @param name Name of data region configuration to find.
+ * @return Data region configuration with the specified name
+ * or {@code null} if the given data storage configuration does not contain such data region.
+ */
+ @Nullable public static DataRegionConfiguration findRemoteDataRegionConfiguration(
+ ClusterNode node,
+ JdkMarshaller marshaller,
+ ClassLoader clsLdr,
+ @Nullable String name
+ ) {
+ return findDataRegionConfiguration(extractDataStorage(node, marshaller, clsLdr), name);
+ }
+
+ /**
* @return {@code true} if persistence is enabled for a default data region, {@code false} if not.
*/
public static boolean isDefaultDataRegionPersistent(DataStorageConfiguration cfg) {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cluster/GridClusterStateProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cluster/GridClusterStateProcessor.java
index caa7855..a57dd5c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cluster/GridClusterStateProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cluster/GridClusterStateProcessor.java
@@ -40,6 +40,7 @@ import org.apache.ignite.IgniteLogger;
import org.apache.ignite.cluster.BaselineNode;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.cluster.ClusterState;
+import org.apache.ignite.configuration.DataRegionConfiguration;
import org.apache.ignite.configuration.DataStorageConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.events.BaselineConfigurationChangedEvent;
@@ -62,6 +63,7 @@ import org.apache.ignite.internal.managers.systemview.walker.BaselineNodeAttribu
import org.apache.ignite.internal.managers.systemview.walker.BaselineNodeViewWalker;
import org.apache.ignite.internal.processors.GridProcessorAdapter;
import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.cache.DynamicCacheDescriptor;
import org.apache.ignite.internal.processors.cache.ExchangeActions;
import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
@@ -88,6 +90,7 @@ import org.apache.ignite.internal.util.typedef.internal.A;
import org.apache.ignite.internal.util.typedef.internal.CU;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteBiPredicate;
import org.apache.ignite.lang.IgniteFuture;
import org.apache.ignite.lang.IgniteInClosure;
import org.apache.ignite.lang.IgniteProductVersion;
@@ -114,7 +117,6 @@ import static org.apache.ignite.internal.IgniteFeatures.SAFE_CLUSTER_DEACTIVATIO
import static org.apache.ignite.internal.IgniteFeatures.allNodesSupports;
import static org.apache.ignite.internal.managers.communication.GridIoPolicy.SYSTEM_POOL;
import static org.apache.ignite.internal.processors.cache.GridCacheUtils.extractDataStorage;
-import static org.apache.ignite.internal.processors.cache.GridCacheUtils.isPersistentCache;
import static org.apache.ignite.internal.processors.metric.impl.MetricUtils.metricName;
import static org.apache.ignite.internal.util.IgniteUtils.toStringSafe;
@@ -709,103 +711,105 @@ public class GridClusterStateProcessor extends GridProcessorAdapter implements I
}
}
}
- else {
- if (isApplicable(msg, state)) {
- if (msg.state() == INACTIVE && !msg.forceDeactivation() && hasInMemoryCache() &&
- allNodesSupports(ctx.discovery().serverNodes(topVer), SAFE_CLUSTER_DEACTIVATION)) {
+ else if (isApplicable(msg, state)) {
+ if (msg.state() == INACTIVE && !msg.forceDeactivation() &&
+ allNodesSupports(ctx.discovery().serverNodes(topVer), SAFE_CLUSTER_DEACTIVATION)) {
+ List<String> inMemCaches = listInMemoryUserCaches();
+
+ if (!inMemCaches.isEmpty()) {
GridChangeGlobalStateFuture stateFut = changeStateFuture(msg);
if (stateFut != null) {
stateFut.onDone(new IgniteException(DATA_LOST_ON_DEACTIVATION_WARNING
- + " To deactivate cluster pass flag 'force'."));
+ + " In memory caches: " + inMemCaches + " .To deactivate cluster pass '--force' flag."));
}
return false;
}
+ }
- ExchangeActions exchangeActions;
+ ExchangeActions exchangeActions;
- try {
- exchangeActions = ctx.cache().onStateChangeRequest(msg, topVer, state);
- }
- catch (IgniteCheckedException e) {
- GridChangeGlobalStateFuture fut = changeStateFuture(msg);
+ try {
+ exchangeActions = ctx.cache().onStateChangeRequest(msg, topVer, state);
+ }
+ catch (IgniteCheckedException e) {
+ GridChangeGlobalStateFuture fut = changeStateFuture(msg);
- if (fut != null)
- fut.onDone(e);
+ if (fut != null)
+ fut.onDone(e);
- return false;
- }
+ return false;
+ }
- Set<UUID> nodeIds = U.newHashSet(discoCache.allNodes().size());
+ Set<UUID> nodeIds = U.newHashSet(discoCache.allNodes().size());
- for (ClusterNode node : discoCache.allNodes())
- nodeIds.add(node.id());
+ for (ClusterNode node : discoCache.allNodes())
+ nodeIds.add(node.id());
- GridChangeGlobalStateFuture fut = changeStateFuture(msg);
+ GridChangeGlobalStateFuture fut = changeStateFuture(msg);
- if (fut != null)
- fut.setRemaining(nodeIds, topVer.nextMinorVersion());
+ if (fut != null)
+ fut.setRemaining(nodeIds, topVer.nextMinorVersion());
- if (log.isInfoEnabled())
- log.info("Started state transition: " + prettyStr(msg.state()));
-
- BaselineTopologyHistoryItem bltHistItem = BaselineTopologyHistoryItem.fromBaseline(
- state.baselineTopology());
-
- transitionFuts.put(msg.requestId(), new GridFutureAdapter<Void>());
-
- DiscoveryDataClusterState newState = globalState = DiscoveryDataClusterState.createTransitionState(
- msg.state(),
- state,
- activate(state.state(), msg.state()) || msg.forceChangeBaselineTopology()
- ? msg.baselineTopology()
- : state.baselineTopology(),
- msg.requestId(),
- topVer,
- nodeIds
- );
+ if (log.isInfoEnabled())
+ log.info("Started state transition: " + prettyStr(msg.state()));
- ctx.durableBackgroundTask().onStateChangeStarted(msg);
+ BaselineTopologyHistoryItem bltHistItem = BaselineTopologyHistoryItem.fromBaseline(
+ state.baselineTopology());
- if (msg.forceChangeBaselineTopology())
- newState.setTransitionResult(msg.requestId(), msg.state());
+ transitionFuts.put(msg.requestId(), new GridFutureAdapter<Void>());
- AffinityTopologyVersion stateChangeTopVer = topVer.nextMinorVersion();
+ DiscoveryDataClusterState newState = globalState = DiscoveryDataClusterState.createTransitionState(
+ msg.state(),
+ state,
+ activate(state.state(), msg.state()) || msg.forceChangeBaselineTopology()
+ ? msg.baselineTopology()
+ : state.baselineTopology(),
+ msg.requestId(),
+ topVer,
+ nodeIds
+ );
- StateChangeRequest req = new StateChangeRequest(
- msg,
- bltHistItem,
- state.state(),
- stateChangeTopVer
- );
+ ctx.durableBackgroundTask().onStateChangeStarted(msg);
- exchangeActions.stateChangeRequest(req);
+ if (msg.forceChangeBaselineTopology())
+ newState.setTransitionResult(msg.requestId(), msg.state());
- msg.exchangeActions(exchangeActions);
+ AffinityTopologyVersion stateChangeTopVer = topVer.nextMinorVersion();
- if (newState.state() != state.state()) {
- if (ctx.event().isRecordable(EventType.EVT_CLUSTER_STATE_CHANGE_STARTED)) {
- ctx.pools().getStripedExecutorService().execute(
- () -> ctx.event().record(new ClusterStateChangeStartedEvent(
- state.state(),
- newState.state(),
- ctx.discovery().localNode(),
- "Cluster state change started."
- ))
- );
- }
- }
+ StateChangeRequest req = new StateChangeRequest(
+ msg,
+ bltHistItem,
+ state.state(),
+ stateChangeTopVer
+ );
- return true;
- }
- else {
- // State already changed.
- GridChangeGlobalStateFuture stateFut = changeStateFuture(msg);
+ exchangeActions.stateChangeRequest(req);
+
+ msg.exchangeActions(exchangeActions);
- if (stateFut != null)
- stateFut.onDone();
+ if (newState.state() != state.state()) {
+ if (ctx.event().isRecordable(EventType.EVT_CLUSTER_STATE_CHANGE_STARTED)) {
+ ctx.pools().getStripedExecutorService().execute(
+ () -> ctx.event().record(new ClusterStateChangeStartedEvent(
+ state.state(),
+ newState.state(),
+ ctx.discovery().localNode(),
+ "Cluster state change started."
+ ))
+ );
+ }
}
+
+ return true;
+ }
+ else {
+ // State already changed.
+ GridChangeGlobalStateFuture stateFut = changeStateFuture(msg);
+
+ if (stateFut != null)
+ stateFut.onDone();
}
return false;
@@ -1925,16 +1929,6 @@ public class GridClusterStateProcessor extends GridProcessorAdapter implements I
}
/**
- * @return {@code True} if cluster has in-memory caches (without persistence) including the system caches.
- * {@code False} otherwise.
- */
- private boolean hasInMemoryCache() {
- return ctx.cache().cacheDescriptors().values().stream()
- .anyMatch(desc -> !isPersistentCache(desc.cacheConfiguration(), ctx.config().getDataStorageConfiguration())
- && (!desc.cacheConfiguration().isWriteBehindEnabled() || !desc.cacheConfiguration().isReadThrough()));
- }
-
- /**
* Gets state of given two with minimal number of features.
* <p/>
* The order: {@link ClusterState#ACTIVE} > {@link ClusterState#ACTIVE_READ_ONLY} > {@link ClusterState#INACTIVE}.
@@ -1970,6 +1964,82 @@ public class GridClusterStateProcessor extends GridProcessorAdapter implements I
}
/**
+ * @return Lists in-memory user defined caches.
+ */
+ private List<String> listInMemoryUserCaches() {
+ IgniteBiPredicate<DynamicCacheDescriptor, DataRegionConfiguration> inMemoryPred = (desc, dataRegionCfg) -> {
+ return !(dataRegionCfg != null && dataRegionCfg.isPersistenceEnabled())
+ && (!desc.cacheConfiguration().isWriteThrough() || !desc.cacheConfiguration().isReadThrough());
+ };
+
+ if (ctx.discovery().localNode().isClient()) {
+ // Need to check cache descriptors using server node storage configurations.
+ // The reason for this is that client node may not be configured with the required data region configuration.
+ List<ClusterNode> srvs = ctx.discovery().discoCache().serverNodes();
+
+ if (F.isEmpty(srvs))
+ return Collections.emptyList();
+
+ return ctx.cache().cacheDescriptors().values().stream()
+ // Filter out system caches
+ .filter(desc -> !CU.isSystemCache(desc.cacheName()))
+ .filter(desc -> {
+ String dataRegionName = desc.cacheConfiguration().getDataRegionName();
+
+ // We always should use server data storage configurations instead of client node config,
+ // because it is not validated when the client node joins the cluster
+ // and so it cannot be the source of truth.
+ DataRegionConfiguration dataRegionCfg = null;
+
+ // Need to find out the first server node that knows about this data region and its configuration.
+ for (ClusterNode n : srvs) {
+ dataRegionCfg = CU.findRemoteDataRegionConfiguration(
+ n,
+ ctx.marshallerContext().jdkMarshaller(),
+ U.resolveClassLoader(ctx.config()),
+ dataRegionName);
+
+ if (dataRegionCfg != null)
+ break;
+ }
+
+ return inMemoryPred.apply(desc, dataRegionCfg);
+ })
+ .map(DynamicCacheDescriptor::cacheName)
+ .collect(Collectors.toList());
+ }
+
+ return ctx.cache().cacheDescriptors().values().stream()
+ .filter(desc -> !CU.isSystemCache(desc.cacheName()))
+ .filter(desc -> {
+ String dataRegionName = desc.cacheConfiguration().getDataRegionName();
+
+ DataRegionConfiguration dataRegionCfg =
+ CU.findDataRegionConfiguration(ctx.config().getDataStorageConfiguration(), dataRegionName);
+
+ if (dataRegionCfg == null) {
+ List<ClusterNode> srvs = ctx.discovery().discoCache().serverNodes();
+
+ // Need to find out the first server node that knows about this data region and its configuration.
+ for (ClusterNode n : srvs) {
+ dataRegionCfg = CU.findRemoteDataRegionConfiguration(
+ n,
+ ctx.marshallerContext().jdkMarshaller(),
+ U.resolveClassLoader(ctx.config()),
+ dataRegionName);
+
+ if (dataRegionCfg != null)
+ break;
+ }
+ }
+
+ return inMemoryPred.apply(desc, dataRegionCfg);
+ })
+ .map(DynamicCacheDescriptor::cacheName)
+ .collect(Collectors.toList());
+ }
+
+ /**
*
*/
private class GridChangeGlobalStateFuture extends GridFutureAdapter<Void> {
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClusterActivateDeactivateTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClusterActivateDeactivateTest.java
index 3136782..2ad649e 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClusterActivateDeactivateTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClusterActivateDeactivateTest.java
@@ -83,7 +83,7 @@ public class IgniteClusterActivateDeactivateTest extends GridCommonAbstractTest
static final String CACHE_NAME_PREFIX = "cache-";
/** Non-persistent data region name. */
- private static final String NO_PERSISTENCE_REGION = "no-persistence-region";
+ protected static final String NO_PERSISTENCE_REGION = "no-persistence-region";
/** */
private static final int DEFAULT_CACHES_COUNT = 2;
@@ -1419,6 +1419,9 @@ public class IgniteClusterActivateDeactivateTest extends GridCommonAbstractTest
if (persistenceEnabled())
ignite.cluster().state(ACTIVE);
+ // Create a new cache in order to trigger all needed checks on deactivation.
+ ignite.getOrCreateCache("test-partitioned-cache");
+
checkDeactivation(ignite, () -> mxBean.active(false), false);
checkDeactivation(ignite, () -> mxBean.clusterState(INACTIVE.name()), false);
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClusterActivateDeactivateTestWithPersistence.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClusterActivateDeactivateTestWithPersistence.java
index 6649072..bb05795 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClusterActivateDeactivateTestWithPersistence.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/IgniteClusterActivateDeactivateTestWithPersistence.java
@@ -18,9 +18,11 @@
package org.apache.ignite.internal.processors.cache;
import java.util.Arrays;
+import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -33,6 +35,7 @@ import org.apache.ignite.cache.CacheWriteSynchronizationMode;
import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction;
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.internal.IgniteEx;
@@ -48,10 +51,12 @@ import org.junit.Assert;
import org.junit.Assume;
import org.junit.Test;
+import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.ignite.cluster.ClusterState.ACTIVE;
import static org.apache.ignite.cluster.ClusterState.ACTIVE_READ_ONLY;
import static org.apache.ignite.cluster.ClusterState.INACTIVE;
import static org.apache.ignite.testframework.GridTestUtils.assertActive;
+import static org.apache.ignite.testframework.GridTestUtils.assertThrows;
import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause;
import static org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause;
@@ -59,6 +64,12 @@ import static org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCaus
*
*/
public class IgniteClusterActivateDeactivateTestWithPersistence extends IgniteClusterActivateDeactivateTest {
+ /** Indicates that additional data region configuration should be added on server node. */
+ private boolean addAdditionalDataRegion;
+
+ /** Persistent data region name. */
+ private static final String ADDITIONAL_PERSISTENT_DATA_REGION = "additional-persistent-region";
+
/** {@inheritDoc} */
@Override protected boolean persistenceEnabled() {
return true;
@@ -80,7 +91,214 @@ public class IgniteClusterActivateDeactivateTestWithPersistence extends IgniteCl
/** {@inheritDoc} */
@Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
- return super.getConfiguration(igniteInstanceName).setAutoActivationEnabled(false);
+ IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName).setAutoActivationEnabled(false);
+
+ if (addAdditionalDataRegion) {
+ DataRegionConfiguration[] originRegions = cfg.getDataStorageConfiguration().getDataRegionConfigurations();
+
+ DataRegionConfiguration[] regions = Arrays.copyOf(originRegions, originRegions.length + 1);
+
+ regions[originRegions.length] = new DataRegionConfiguration()
+ .setName(ADDITIONAL_PERSISTENT_DATA_REGION)
+ .setPersistenceEnabled(true);
+
+ cfg.getDataStorageConfiguration().setDataRegionConfigurations(regions);
+ }
+
+ if (cfg.isClientMode())
+ cfg.setDataStorageConfiguration(null);
+
+ return cfg;
+ }
+
+ /**
+ * Tests "soft" deactivation (without using the --force flag)
+ * when the client node does not have the configured data storage and the cluster contains persistent caches.
+ *
+ * Expected behavior: the cluster should be deactivated successfully (there is no data loss).
+ *
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testDeactivateClusterWithPersistentCache() throws Exception {
+ IgniteEx srv = startGrid(0);
+
+ IgniteEx clientNode = startClientGrid(1);
+
+ clientNode.cluster().state(ACTIVE);
+
+ DataRegionConfiguration dfltDataRegion = srv
+ .configuration()
+ .getDataStorageConfiguration()
+ .getDefaultDataRegionConfiguration();
+
+ assertTrue(
+ "It is assumed that the default data storage region is persistent.",
+ dfltDataRegion.isPersistenceEnabled());
+
+ // Create a new cache that is placed into pesristent data region.
+ clientNode.getOrCreateCache(new CacheConfiguration<>("test-client-cache")
+ .setDataRegionName(dfltDataRegion.getName()));
+
+ // Try to deactivate the cluster without the `force` flag.
+ IgniteInternalFuture<?> deactivateFut = srv
+ .context()
+ .state()
+ .changeGlobalState(INACTIVE, false, Collections.emptyList(), false);
+
+ try {
+ deactivateFut.get(10, SECONDS);
+ }
+ catch (IgniteCheckedException e) {
+ log.error("Failed to deactivate the cluster.", e);
+
+ fail("Failed to deactivate the cluster. [err=" + e.getMessage() + ']');
+ }
+
+ awaitPartitionMapExchange();
+
+ // Let's check that all nodes in the cluster have the same state.
+ for (Ignite node : G.allGrids()) {
+ IgniteEx n = (IgniteEx)node;
+
+ ClusterState state = n.context().state().clusterState().state();
+
+ assertTrue(
+ "Node must be in inactive state. " +
+ "[node=" + n.configuration().getIgniteInstanceName() + ", actual=" + state + ']',
+ INACTIVE == state
+ );
+ }
+ }
+
+ /**
+ * Tests "soft" deactivation (without using the --force flag)
+ * when the client node does not have the configured data storage and the cluster contains in-memory caches.
+ *
+ * Expected behavior: deactivation should fail due to potential data loss.
+ *
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testDeactivateClusterWithInMemoryCaches() throws Exception {
+ IgniteEx srv = startGrid(0);
+
+ IgniteEx clientNode = startClientGrid(1);
+
+ clientNode.cluster().state(ACTIVE);
+
+ DataStorageConfiguration dsCfg = srv.configuration().getDataStorageConfiguration();
+
+ DataRegionConfiguration nonPersistentRegion = Arrays.stream(dsCfg.getDataRegionConfigurations())
+ .filter(region -> NO_PERSISTENCE_REGION.equals(region.getName()))
+ .findFirst()
+ .orElse(null);
+
+ assertTrue(
+ "It is assumed that the '" + NO_PERSISTENCE_REGION + "' data storage region exists and non-persistent.",
+ nonPersistentRegion != null && !nonPersistentRegion.isPersistenceEnabled());
+
+ // Create a new cache that is placed into non persistent data region.
+ clientNode.getOrCreateCache(new CacheConfiguration<>("test-client-cache")
+ .setDataRegionName(nonPersistentRegion.getName()));
+
+ // Try to deactivate the cluster without the `force` flag.
+ IgniteInternalFuture<?> deactivateFut = srv
+ .context()
+ .state()
+ .changeGlobalState(INACTIVE, false, Collections.emptyList(), false);
+
+ assertThrows(
+ log,
+ () -> deactivateFut.get(10, SECONDS),
+ IgniteCheckedException.class,
+ "Deactivation stopped. Deactivation clears in-memory caches (without persistence) including the system caches.");
+
+ awaitPartitionMapExchange();
+
+ // Let's check that all nodes in the cluster have the same state.
+ for (Ignite node : G.allGrids()) {
+ IgniteEx n = (IgniteEx)node;
+
+ ClusterState state = n.context().state().clusterState().state();
+
+ assertTrue(
+ "Node must be in active state. " +
+ "[node=" + n.configuration().getIgniteInstanceName() + ", actual=" + state + ']',
+ ACTIVE == state
+ );
+ }
+ }
+
+ /**
+ * Tests "soft" deactivation (without using the --force flag)
+ * when the cluster contains persistent caches and cluster nodes "support" different lists of data regions.
+ *
+ * Expected behavior: the cluster should be deactivated successfully (there is no data loss)..
+ *
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testDeactivateClusterWithPersistentCachesAndDifferentDataRegions() throws Exception {
+ IgniteEx srv = startGrid(0);
+
+ addAdditionalDataRegion = true;
+
+ IgniteEx srv1 = startGrid(1);
+
+ IgniteEx clientNode = startClientGrid(2);
+
+ clientNode.cluster().state(ACTIVE);
+
+ DataStorageConfiguration dsCfg = srv1.configuration().getDataStorageConfiguration();
+
+ DataRegionConfiguration persistentRegion = Arrays.stream(dsCfg.getDataRegionConfigurations())
+ .filter(region -> ADDITIONAL_PERSISTENT_DATA_REGION.equals(region.getName()))
+ .findFirst()
+ .orElse(null);
+
+ assertTrue(
+ "It is assumed that the '" + ADDITIONAL_PERSISTENT_DATA_REGION + "' data storage region exists and persistent.",
+ persistentRegion != null && persistentRegion.isPersistenceEnabled());
+
+ final UUID srv1NodeId = srv1.localNode().id();
+
+ // Create a new cache that is placed into persistent data region.
+ srv.getOrCreateCache(new CacheConfiguration<>("test-client-cache")
+ .setDataRegionName(persistentRegion.getName())
+ .setAffinity(new RendezvousAffinityFunction(false, 1))
+ // This cache should only be started on srv1 node.
+ .setNodeFilter(node -> node.id().equals(srv1NodeId)));
+
+ // Try to deactivate the cluster without the `force` flag.
+ IgniteInternalFuture<?> deactivateFut = srv
+ .context()
+ .state()
+ .changeGlobalState(INACTIVE, false, Collections.emptyList(), false);
+
+ try {
+ deactivateFut.get(10, SECONDS);
+ }
+ catch (IgniteCheckedException e) {
+ log.error("Failed to deactivate the cluster.", e);
+
+ fail("Failed to deactivate the cluster. [err=" + e.getMessage() + ']');
+ }
+
+ awaitPartitionMapExchange();
+
+ // Let's check that all nodes in the cluster have the same state.
+ for (Ignite node : G.allGrids()) {
+ IgniteEx n = (IgniteEx)node;
+
+ ClusterState state = n.context().state().clusterState().state();
+
+ assertTrue(
+ "Node must be in inactive state. " +
+ "[node=" + n.configuration().getIgniteInstanceName() + ", actual=" + state + ']',
+ INACTIVE == state
+ );
+ }
}
/**