diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAbstractSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAbstractSelfTest.java index 784cebcf3454e..cfeb90a3e9e9c 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAbstractSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorAbstractSelfTest.java @@ -143,8 +143,11 @@ import static org.apache.ignite.cache.CacheMode.REPLICATED; import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_ASYNC; import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC; +import static org.apache.ignite.cluster.ClusterState.ACTIVE; +import static org.apache.ignite.cluster.ClusterState.INACTIVE; import static org.apache.ignite.configuration.WALMode.NONE; import static org.apache.ignite.internal.IgniteVersionUtils.VER_STR; +import static org.apache.ignite.internal.processors.cluster.GridClusterStateProcessor.DATA_LOST_ON_DEACTIVATION_WARNING; import static org.apache.ignite.internal.processors.query.QueryUtils.TEMPLATE_PARTITIONED; import static org.apache.ignite.internal.processors.query.QueryUtils.TEMPLATE_REPLICATED; import static org.apache.ignite.internal.processors.rest.GridRestResponse.STATUS_FAILED; @@ -254,18 +257,35 @@ private void assertCacheMetrics(String content) throws IOException { * @throws IOException If parsing failed. */ protected JsonNode validateJsonResponse(String content) throws IOException { + return validateJsonResponse(content, false); + } + + /** + * Validates JSON response. + * + * @param content Content to check. + * @param errorExpected is error expected. + * @return REST result if {@code errorExpected} is {@code false}. Error instead. + * @throws IOException If parsing failed. + */ + protected JsonNode validateJsonResponse(String content, boolean errorExpected) throws IOException { assertNotNull(content); assertFalse(content.isEmpty()); JsonNode node = JSON_MAPPER.readTree(content); - assertTrue("Unexpected error: " + node.get("error").asText(), node.get("error").isNull()); + JsonNode errNode = node.get("error"); + + if (errorExpected) + assertTrue("Expected an error.", !errNode.isNull()); + else + assertTrue("Unexpected error: " + errNode.asText(), errNode.isNull()); assertEquals(STATUS_SUCCESS, node.get("successStatus").asInt()); assertNotSame(securityEnabled(), node.get("sessionToken").isNull()); - return node.get("response"); + return node.get(errorExpected ? "error" : "response"); } /** @@ -887,11 +907,23 @@ public void testGetAndReplace() throws Exception { public void testDeactivateActivate() throws Exception { assertClusterState(true); + changeClusterState(GridRestCommand.CLUSTER_SET_STATE, "state", INACTIVE.name()); + + changeClusterState(GridRestCommand.CLUSTER_SET_STATE, "state", INACTIVE.name(), "force", "true"); + + changeClusterState(GridRestCommand.CLUSTER_SET_STATE, "state", ACTIVE.name()); + changeClusterState(GridRestCommand.CLUSTER_DEACTIVATE); + + changeClusterState(GridRestCommand.CLUSTER_DEACTIVATE, "force", "true"); + changeClusterState(GridRestCommand.CLUSTER_ACTIVATE); // same for deprecated. changeClusterState(GridRestCommand.CLUSTER_INACTIVE); + + changeClusterState(GridRestCommand.CLUSTER_INACTIVE, "force", "true"); + changeClusterState(GridRestCommand.CLUSTER_ACTIVE); initCache(); @@ -3181,16 +3213,37 @@ private void assertClusterState(boolean exp) throws Exception { * Change cluster state and test new state. * * @param cmd Command. + * @param params Arguments for {@code cmd}. * @throws Exception If failed. */ - private void changeClusterState(GridRestCommand cmd) throws Exception { - String ret = content(null, cmd); + private void changeClusterState(GridRestCommand cmd, String... params) throws Exception { + String ret = content(null, cmd, params); - JsonNode res = validateJsonResponse(ret); + boolean force = false; + + boolean deactivate = cmd == GridRestCommand.CLUSTER_INACTIVE || cmd == GridRestCommand.CLUSTER_DEACTIVATE; + + for (int i = 0; i < params.length; ++i) { + String p = params[i]; + + if ("force".equals(p) && params[i + 1].equals("true")) + force = true; + + if (cmd == GridRestCommand.CLUSTER_SET_STATE && p.equals("state")) + deactivate = params[i + 1].equals(INACTIVE.name()); + } + + boolean errorExpected = !force && deactivate; + + JsonNode res = validateJsonResponse(ret, errorExpected); assertFalse(res.isNull()); - assertTrue(res.asText().startsWith(cmd.key())); - assertClusterState(cmd == GridRestCommand.CLUSTER_ACTIVATE || cmd == GridRestCommand.CLUSTER_ACTIVE); + if (errorExpected) + assertTrue(res.asText().contains(DATA_LOST_ON_DEACTIVATION_WARNING)); + else + assertTrue(res.asText().startsWith(cmd.key())); + + assertClusterState(!deactivate || !force); } } diff --git a/modules/core/src/main/java/org/apache/ignite/Ignite.java b/modules/core/src/main/java/org/apache/ignite/Ignite.java index 72f5fa59c4e75..14b5797eb7008 100644 --- a/modules/core/src/main/java/org/apache/ignite/Ignite.java +++ b/modules/core/src/main/java/org/apache/ignite/Ignite.java @@ -663,6 +663,9 @@ public IgniteQueue queue(String name, int cap, @Nullable CollectionConfig /** * Changes Ignite grid state to active or inactive. + *

+ * NOTE: + * Deactivation clears in-memory caches (without persistence) including the system caches. * * @param active If {@code True} start activation process. If {@code False} start deactivation process. * @throws IgniteException If there is an already started transaction or lock in the same thread. diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteCluster.java b/modules/core/src/main/java/org/apache/ignite/IgniteCluster.java index 05dcf3eba5578..2ba83c6f0583a 100644 --- a/modules/core/src/main/java/org/apache/ignite/IgniteCluster.java +++ b/modules/core/src/main/java/org/apache/ignite/IgniteCluster.java @@ -455,6 +455,9 @@ public IgniteFuture> startNodesAsync(Collecti /** * Changes Ignite grid state to active or inactive. + *

+ * NOTE: + * Deactivation clears in-memory caches (without persistence) including the system caches. * * @param active If {@code True} start activation process. If {@code False} start deactivation process. * @throws IgniteException If there is an already started transaction or lock in the same thread. @@ -472,6 +475,9 @@ public IgniteFuture> startNodesAsync(Collecti /** * Changes current cluster state to given {@code newState} cluster state. + *

+ * NOTE: + * Deactivation clears in-memory caches (without persistence) including the system caches. * * @param newState New cluster state. * @throws IgniteException If there is an already started transaction or lock in the same thread. diff --git a/modules/core/src/main/java/org/apache/ignite/cluster/ClusterState.java b/modules/core/src/main/java/org/apache/ignite/cluster/ClusterState.java index b87cb9855824f..85d95d6cdd832 100644 --- a/modules/core/src/main/java/org/apache/ignite/cluster/ClusterState.java +++ b/modules/core/src/main/java/org/apache/ignite/cluster/ClusterState.java @@ -23,7 +23,12 @@ * Cluster states. */ public enum ClusterState { - /** Cluster deactivated. Cache operations aren't allowed. */ + /** + * Cluster deactivated. Cache operations aren't allowed. + *

+ * NOTE: + * Deactivation clears in-memory caches (without persistence) including the system caches. + */ INACTIVE, /** Cluster activated. All cache operations are allowed. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/IgniteFeatures.java b/modules/core/src/main/java/org/apache/ignite/internal/IgniteFeatures.java index 88526f52bd791..a5f7b4f58e0f9 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/IgniteFeatures.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/IgniteFeatures.java @@ -19,6 +19,7 @@ import java.util.BitSet; import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.cluster.ClusterState; import org.apache.ignite.internal.managers.encryption.GridEncryptionManager; import org.apache.ignite.spi.communication.tcp.TcpCommunicationSpi; import org.apache.ignite.spi.communication.tcp.messages.HandshakeWaitMessage; @@ -87,6 +88,13 @@ public enum IgniteFeatures { /** ContinuousQuery with security subject id support. */ CONT_QRY_SECURITY_AWARE(21), + /** + * Preventing loss of in-memory data when deactivating the cluster. + * + * @see ClusterState#INACTIVE + */ + SAFE_CLUSTER_DEACTIVATION(22), + /** Long operations dump timeout. */ LONG_OPERATIONS_DUMP_TIMEOUT(30); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/GridClientClusterState.java b/modules/core/src/main/java/org/apache/ignite/internal/client/GridClientClusterState.java index 6476a58dbc416..177534eb179ab 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/client/GridClientClusterState.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/client/GridClientClusterState.java @@ -25,14 +25,15 @@ public interface GridClientClusterState { /** * @param active {@code True} activate, {@code False} deactivate. - * @deprecated Use {@link #state()} instead. + * @throws GridClientException If the request to change the cluster state failed. + * @deprecated Use {@link #state(ClusterState, boolean)} instead. */ @Deprecated public void active(boolean active) throws GridClientException; /** * @return {@code Boolean} - Current cluster state. {@code True} active, {@code False} inactive. - * @deprecated Use {@link #state(ClusterState)} instead. + * @deprecated Use {@link #state()} instead. */ @Deprecated public boolean active() throws GridClientException; @@ -47,9 +48,11 @@ public interface GridClientClusterState { * Changes cluster state to {@code newState}. * * @param newState New cluster state. + * @param forceDeactivation If {@code true}, cluster deactivation will be forced. * @throws GridClientException If the request to change the cluster state failed. + * @see ClusterState#INACTIVE */ - public void state(ClusterState newState) throws GridClientException; + public void state(ClusterState newState, boolean forceDeactivation) throws GridClientException; /** * Get the cluster name. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/impl/GridClientClusterStateImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/client/impl/GridClientClusterStateImpl.java index 2cdcc14cae84a..438972371abbc 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/client/impl/GridClientClusterStateImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/client/impl/GridClientClusterStateImpl.java @@ -18,7 +18,9 @@ package org.apache.ignite.internal.client.impl; import java.util.Collection; +import java.util.UUID; import org.apache.ignite.cluster.ClusterState; +import org.apache.ignite.internal.IgniteFeatures; import org.apache.ignite.internal.client.GridClientClusterState; import org.apache.ignite.internal.client.GridClientException; import org.apache.ignite.internal.client.GridClientNode; @@ -26,6 +28,10 @@ import org.apache.ignite.internal.client.balancer.GridClientLoadBalancer; import org.apache.ignite.internal.client.impl.connection.GridClientConnection; +import static org.apache.ignite.cluster.ClusterState.ACTIVE; +import static org.apache.ignite.cluster.ClusterState.INACTIVE; +import static org.apache.ignite.internal.client.util.GridClientUtils.checkFeatureSupportedByCluster; + /** * */ @@ -50,7 +56,7 @@ public GridClientClusterStateImpl( /** {@inheritDoc} */ @Override public void active(final boolean active) throws GridClientException { - withReconnectHandling((conn, nodeId) -> conn.changeState(active, nodeId)).get(); + state(active ? ACTIVE : INACTIVE, true); } /** {@inheritDoc} */ @@ -64,8 +70,22 @@ public GridClientClusterStateImpl( } /** {@inheritDoc} */ - @Override public void state(ClusterState newState) throws GridClientException { - withReconnectHandling((con, nodeId) -> con.changeState(newState, nodeId)).get(); + @Override public void state(ClusterState newState, boolean forceDeactivation) throws GridClientException { + // Check compatibility of new forced deactivation on all nodes. + UUID oldVerNode = checkFeatureSupportedByCluster(client, IgniteFeatures.SAFE_CLUSTER_DEACTIVATION, false, + false); + + if (oldVerNode == null) + withReconnectHandling((con, nodeId) -> con.changeState(newState, nodeId, forceDeactivation)).get(); + else { + if (newState == INACTIVE && !forceDeactivation) { + throw new GridClientException("Deactivation stopped. Found a node not supporting checking of " + + "safety of this operation: " + oldVerNode + ". Deactivation clears in-memory caches (without " + + "persistence) including the system caches. To deactivate cluster pass flag 'force'."); + } + + withReconnectHandling((con, nodeId) -> con.changeState(newState, nodeId)).get(); + } } /** {@inheritDoc} */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/impl/connection/GridClientConnection.java b/modules/core/src/main/java/org/apache/ignite/internal/client/impl/connection/GridClientConnection.java index 27da9868e8b27..5240e707d29be 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/client/impl/connection/GridClientConnection.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/client/impl/connection/GridClientConnection.java @@ -25,7 +25,6 @@ import java.util.Set; import java.util.UUID; import javax.net.ssl.SSLContext; - import org.apache.ignite.cluster.ClusterState; import org.apache.ignite.internal.client.GridClientCacheFlag; import org.apache.ignite.internal.client.GridClientClosedException; @@ -310,25 +309,26 @@ public abstract GridClientFutureAdapter execute(String taskName, Object a boolean keepBinaries) throws GridClientConnectionResetException, GridClientClosedException; /** - * Change grid global state. + * Changes grid global state. * - * @param active Active. + * @param state New cluster state. * @param destNodeId Destination node id. - * @deprecated Use {@link #changeState(ClusterState, UUID)} instead. + * @throws GridClientConnectionResetException In case of error. + * @throws GridClientClosedException If client was manually closed before request was sent over network. */ - @Deprecated - public abstract GridClientFuture changeState(boolean active, UUID destNodeId) - throws GridClientClosedException, GridClientConnectionResetException; + public abstract GridClientFuture changeState(ClusterState state, UUID destNodeId) + throws GridClientClosedException, GridClientConnectionResetException; /** * Changes grid global state. * * @param state New cluster state. * @param destNodeId Destination node id. + * @param forceDeactivation If {@code true}, cluster deactivation will be forced. * @throws GridClientConnectionResetException In case of error. * @throws GridClientClosedException If client was manually closed before request was sent over network. */ - public abstract GridClientFuture changeState(ClusterState state, UUID destNodeId) + public abstract GridClientFuture changeState(ClusterState state, UUID destNodeId, boolean forceDeactivation) throws GridClientClosedException, GridClientConnectionResetException; /** diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/impl/connection/GridClientNioTcpConnection.java b/modules/core/src/main/java/org/apache/ignite/internal/client/impl/connection/GridClientNioTcpConnection.java index ebaae1a09e7bd..45d47797a4811 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/client/impl/connection/GridClientNioTcpConnection.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/client/impl/connection/GridClientNioTcpConnection.java @@ -62,6 +62,7 @@ import org.apache.ignite.internal.processors.rest.client.message.GridClientCacheRequest; import org.apache.ignite.internal.processors.rest.client.message.GridClientClusterNameRequest; import org.apache.ignite.internal.processors.rest.client.message.GridClientClusterStateRequest; +import org.apache.ignite.internal.processors.rest.client.message.GridClientClusterStateRequestV2; import org.apache.ignite.internal.processors.rest.client.message.GridClientHandshakeRequest; import org.apache.ignite.internal.processors.rest.client.message.GridClientMessage; import org.apache.ignite.internal.processors.rest.client.message.GridClientNodeBean; @@ -84,8 +85,6 @@ import org.jetbrains.annotations.Nullable; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.apache.ignite.cluster.ClusterState.ACTIVE; -import static org.apache.ignite.cluster.ClusterState.INACTIVE; import static org.apache.ignite.internal.client.GridClientCacheFlag.KEEP_BINARIES; import static org.apache.ignite.internal.client.GridClientCacheFlag.encodeCacheFlags; import static org.apache.ignite.internal.client.impl.connection.GridClientConnectionCloseReason.CONN_IDLE; @@ -817,11 +816,11 @@ private GridClientAuthenticationRequest buildAuthRequest() { } /** {@inheritDoc} */ - @Override public GridClientFuture changeState( - boolean active, - UUID destNodeId - ) throws GridClientClosedException, GridClientConnectionResetException { - return changeState(active ? ACTIVE : INACTIVE, destNodeId); + @Override public GridClientFuture changeState(ClusterState state, UUID destNodeId, boolean forceDeactivation) + throws GridClientClosedException, GridClientConnectionResetException { + assert state != null; + + return makeRequest(GridClientClusterStateRequestV2.state(state, forceDeactivation), destNodeId); } /** {@inheritDoc} */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/util/GridClientUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/client/util/GridClientUtils.java index 7beae77d68a49..3e922d2a349f7 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/client/util/GridClientUtils.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/client/util/GridClientUtils.java @@ -23,9 +23,14 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; +import org.apache.ignite.internal.IgniteFeatures; +import org.apache.ignite.internal.IgniteNodeAttributes; +import org.apache.ignite.internal.client.GridClient; +import org.apache.ignite.internal.client.GridClientException; import org.apache.ignite.internal.client.GridClientNode; import org.apache.ignite.internal.client.GridClientPredicate; import org.apache.ignite.internal.client.GridClientProtocol; @@ -174,4 +179,40 @@ public static int safeAbs(int i) { return i < 0 ? 0 : i; } + + /** + * Checks that all cluster nodes support specified feature. + * + * @param client Client. + * @param feature Feature. + * @param validateClientNodes Whether client nodes should be checked as well. + * @param failIfUnsupportedFound If {@code true}, fails when found a node unsupporting {@code feature}. + * unsupporting {@code feature}. + * @return Id of node unsupporting {@code feature}. {@code Null} if all nodes support {@code feature}. + */ + public static UUID checkFeatureSupportedByCluster( + GridClient client, + IgniteFeatures feature, + boolean validateClientNodes, + boolean failIfUnsupportedFound + ) throws GridClientException { + Collection nodes = validateClientNodes ? + client.compute().nodes() : + client.compute().nodes(GridClientNode::connectable); + + for (GridClientNode node : nodes) { + byte[] featuresAttrBytes = node.attribute(IgniteNodeAttributes.ATTR_IGNITE_FEATURES); + + if (!IgniteFeatures.nodeSupports(featuresAttrBytes, feature)) { + if (failIfUnsupportedFound) { + throw new GridClientException("Failed to execute command: cluster contains node that " + + "doesn't support feature [nodeId=" + node.nodeId() + ", feature=" + feature + ']'); + } + else + return node.nodeId(); + } + } + + return null; + } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/cluster/IgniteClusterImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/cluster/IgniteClusterImpl.java index 7fc666a2131f5..5e407cab83f3f 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/cluster/IgniteClusterImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/cluster/IgniteClusterImpl.java @@ -339,7 +339,7 @@ public IgniteClusterImpl(GridKernalContext ctx) { guard(); try { - ctx.state().changeGlobalState(newState, serverNodes(), false).get(); + ctx.state().changeGlobalState(newState, true, serverNodes(), false).get(); } catch (IgniteCheckedException e) { throw U.convertException(e); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/ClusterStateChangeCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/ClusterStateChangeCommand.java index d9b8809e66de2..c357597cb848c 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/ClusterStateChangeCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/ClusterStateChangeCommand.java @@ -25,8 +25,8 @@ import org.apache.ignite.internal.client.GridClientConfiguration; import static org.apache.ignite.cluster.ClusterState.ACTIVE; -import static org.apache.ignite.cluster.ClusterState.INACTIVE; import static org.apache.ignite.cluster.ClusterState.ACTIVE_READ_ONLY; +import static org.apache.ignite.cluster.ClusterState.INACTIVE; import static org.apache.ignite.internal.commandline.CommandList.SET_STATE; import static org.apache.ignite.internal.commandline.CommandLogger.optional; import static org.apache.ignite.internal.commandline.CommandLogger.or; @@ -36,12 +36,18 @@ * Command to change cluster state. */ public class ClusterStateChangeCommand implements Command { + /** Flag of forced cluster deactivation. */ + static final String FORCE_COMMAND = "--force"; + /** New cluster state */ private ClusterState state; /** Cluster name. */ private String clusterName; + /** If {@code true}, cluster deactivation will be forced. */ + private boolean forceDeactivation; + /** {@inheritDoc} */ @Override public void printUsage(Logger log) { Map params = new LinkedHashMap<>(); @@ -50,7 +56,8 @@ public class ClusterStateChangeCommand implements Command { params.put(INACTIVE.toString(), "Deactivate cluster."); params.put(ACTIVE_READ_ONLY.toString(), "Activate cluster. Cache updates are denied."); - Command.usage(log, "Change cluster state:", SET_STATE, params, or((Object[])ClusterState.values()), optional(CMD_AUTO_CONFIRMATION)); + Command.usage(log, "Change cluster state:", SET_STATE, params, or((Object[])ClusterState.values()), + optional(FORCE_COMMAND), optional(CMD_AUTO_CONFIRMATION)); } /** {@inheritDoc} */ @@ -68,7 +75,7 @@ public class ClusterStateChangeCommand implements Command { /** {@inheritDoc} */ @Override public Object execute(GridClientConfiguration clientCfg, Logger log) throws Exception { try (GridClient client = Command.startClient(clientCfg)) { - client.state().state(state); + client.state().state(state, forceDeactivation); log.info("Cluster state changed to " + state); @@ -91,6 +98,18 @@ public class ClusterStateChangeCommand implements Command { catch (IllegalArgumentException e) { throw new IllegalArgumentException("Can't parse new cluster state. State: " + s, e); } + + forceDeactivation = false; + + if (argIter.hasNextArg()) { + String arg = argIter.peekNextArg(); + + if (FORCE_COMMAND.equalsIgnoreCase(arg)) { + forceDeactivation = true; + + argIter.nextArg(""); + } + } } /** {@inheritDoc} */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/DeactivateCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/DeactivateCommand.java index 0704fa88e2e60..10d0fe56235d3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/DeactivateCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/DeactivateCommand.java @@ -18,10 +18,11 @@ package org.apache.ignite.internal.commandline; import java.util.logging.Logger; +import org.apache.ignite.cluster.ClusterState; import org.apache.ignite.internal.client.GridClient; -import org.apache.ignite.internal.client.GridClientClusterState; import org.apache.ignite.internal.client.GridClientConfiguration; +import static org.apache.ignite.internal.commandline.ClusterStateChangeCommand.FORCE_COMMAND; import static org.apache.ignite.internal.commandline.CommandList.DEACTIVATE; import static org.apache.ignite.internal.commandline.CommandList.SET_STATE; import static org.apache.ignite.internal.commandline.CommandLogger.optional; @@ -36,9 +37,13 @@ public class DeactivateCommand implements Command { /** Cluster name. */ private String clusterName; + /** If {@code true}, cluster deactivation will be forced. */ + private boolean forceDeactivation; + /** {@inheritDoc} */ @Override public void printUsage(Logger logger) { - Command.usage(logger, "Deactivate cluster (deprecated. Use " + SET_STATE.toString() + " instead):", DEACTIVATE, optional(CMD_AUTO_CONFIRMATION)); + Command.usage(logger, "Deactivate cluster (deprecated. Use " + SET_STATE.toString() + " instead):", DEACTIVATE, + optional(FORCE_COMMAND), optional(CMD_AUTO_CONFIRMATION)); } /** {@inheritDoc} */ @@ -63,9 +68,7 @@ public class DeactivateCommand implements Command { logger.warning("Command deprecated. Use " + SET_STATE.toString() + " instead."); try (GridClient client = Command.startClient(clientCfg)) { - GridClientClusterState state = client.state(); - - state.active(false); + client.state().state(ClusterState.INACTIVE, forceDeactivation); logger.info("Cluster deactivated"); } @@ -78,6 +81,21 @@ public class DeactivateCommand implements Command { return null; } + /** {@inheritDoc} */ + @Override public void parseArguments(CommandArgIterator argIter) { + forceDeactivation = false; + + if (argIter.hasNextArg()) { + String arg = argIter.peekNextArg(); + + if (FORCE_COMMAND.equalsIgnoreCase(arg)) { + forceDeactivation = true; + + argIter.nextArg(""); + } + } + } + /** {@inheritDoc} */ @Override public Void arg() { return null; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/TxCommands.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/TxCommands.java index 8c1d64e1ae522..6b0d10f6fc545 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/TxCommands.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/TxCommands.java @@ -18,7 +18,6 @@ package org.apache.ignite.internal.commandline; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -29,11 +28,9 @@ import java.util.regex.PatternSyntaxException; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.internal.IgniteFeatures; -import org.apache.ignite.internal.IgniteNodeAttributes; import org.apache.ignite.internal.client.GridClient; import org.apache.ignite.internal.client.GridClientConfiguration; import org.apache.ignite.internal.client.GridClientException; -import org.apache.ignite.internal.client.GridClientNode; import org.apache.ignite.internal.commandline.argument.CommandArgUtils; import org.apache.ignite.internal.processors.cache.version.GridCacheVersion; import org.apache.ignite.internal.util.typedef.F; @@ -52,6 +49,7 @@ import org.apache.ignite.internal.visor.tx.VisorTxTaskResult; import org.apache.ignite.transactions.TransactionState; +import static org.apache.ignite.internal.client.util.GridClientUtils.checkFeatureSupportedByCluster; import static org.apache.ignite.internal.commandline.CommandList.TX; import static org.apache.ignite.internal.commandline.CommandLogger.DOUBLE_INDENT; import static org.apache.ignite.internal.commandline.CommandLogger.optional; @@ -350,7 +348,7 @@ private static String nodeDescription(ClusterNode node) { * @param client Client. */ private Object transactionInfo(GridClient client, GridClientConfiguration conf) throws GridClientException { - checkFeatureSupportedByCluster(client, IgniteFeatures.TX_INFO_COMMAND, true); + checkFeatureSupportedByCluster(client, IgniteFeatures.TX_INFO_COMMAND, true, true); GridCacheVersion nearXidVer = executeTask(client, FetchNearXidVersionTask.class, args.txInfoArgument(), conf); @@ -553,32 +551,6 @@ private void printTxInfoHistoricalResult(Map res } } - /** - * Checks that all cluster nodes support specified feature. - * - * @param client Client. - * @param feature Feature. - * @param validateClientNodes Whether client nodes should be checked as well. - */ - private static void checkFeatureSupportedByCluster( - GridClient client, - IgniteFeatures feature, - boolean validateClientNodes - ) throws GridClientException { - Collection nodes = validateClientNodes ? - client.compute().nodes() : - client.compute().nodes(GridClientNode::connectable); - - for (GridClientNode node : nodes) { - byte[] featuresAttrBytes = node.attribute(IgniteNodeAttributes.ATTR_IGNITE_FEATURES); - - if (!IgniteFeatures.nodeSupports(featuresAttrBytes, feature)) { - throw new IllegalStateException("Failed to execute command: cluster contains node that " + - "doesn't support feature [nodeId=" + node.nodeId() + ", feature=" + feature + ']'); - } - } - } - /** {@inheritDoc} */ @Override public String name() { return TX.toCommandName(); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cluster/ChangeGlobalStateMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cluster/ChangeGlobalStateMessage.java index a74917d2d2a2e..2f430e8e70a7f 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cluster/ChangeGlobalStateMessage.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cluster/ChangeGlobalStateMessage.java @@ -71,11 +71,15 @@ public class ChangeGlobalStateMessage implements DiscoveryCustomMessage { @GridToStringExclude @Nullable private transient ServiceDeploymentActions serviceDeploymentActions; + /** If {@code true}, cluster deactivation will be forced. */ + private boolean forceDeactivation; + /** * @param reqId State change request ID. * @param initiatingNodeId Node initiated state change. * @param storedCfgs Configurations read from persistent store. * @param state New cluster state. + * @param forceDeactivation If {@code true}, cluster deactivation will be forced. * @param baselineTopology Baseline topology. * @param forceChangeBaselineTopology Force change baseline topology flag. * @param timestamp Timestamp. @@ -85,6 +89,7 @@ public ChangeGlobalStateMessage( UUID initiatingNodeId, @Nullable List storedCfgs, ClusterState state, + boolean forceDeactivation, BaselineTopology baselineTopology, boolean forceChangeBaselineTopology, long timestamp @@ -96,6 +101,7 @@ public ChangeGlobalStateMessage( this.initiatingNodeId = initiatingNodeId; this.storedCfgs = storedCfgs; this.state = state; + this.forceDeactivation = forceDeactivation; this.baselineTopology = baselineTopology; this.forceChangeBaselineTopology = forceChangeBaselineTopology; this.timestamp = timestamp; @@ -204,6 +210,14 @@ public boolean forceChangeBaselineTopology() { return baselineTopology; } + /** + * @return {@code True} if cluster deactivation will be forced. {@code False} otherwise. + * @see ClusterState#INACTIVE + */ + public boolean forceDeactivation() { + return forceDeactivation; + } + /** * @return Timestamp. */ 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 682c6dd7e6131..3031e4b51ccb3 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 @@ -96,8 +96,8 @@ import org.jetbrains.annotations.Nullable; import static org.apache.ignite.cluster.ClusterState.ACTIVE; -import static org.apache.ignite.cluster.ClusterState.INACTIVE; import static org.apache.ignite.cluster.ClusterState.ACTIVE_READ_ONLY; +import static org.apache.ignite.cluster.ClusterState.INACTIVE; import static org.apache.ignite.cluster.ClusterState.lesserOf; import static org.apache.ignite.configuration.IgniteConfiguration.DFLT_STATE_ON_START; import static org.apache.ignite.events.EventType.EVT_NODE_FAILED; @@ -105,8 +105,11 @@ import static org.apache.ignite.events.EventType.EVT_NODE_LEFT; import static org.apache.ignite.internal.GridComponent.DiscoveryDataExchangeType.STATE_PROC; import static org.apache.ignite.internal.IgniteFeatures.CLUSTER_READ_ONLY_MODE; +import static org.apache.ignite.internal.IgniteFeatures.SAFE_CLUSTER_DEACTIVATION; +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; /** * @@ -115,6 +118,10 @@ public class GridClusterStateProcessor extends GridProcessorAdapter implements I /** */ private static final String METASTORE_CURR_BLT_KEY = "metastoreBltKey"; + /** Warning of unsafe cluster deactivation. */ + public static final String DATA_LOST_ON_DEACTIVATION_WARNING = "Deactivation stopped. Deactivation clears " + + "in-memory caches (without persistence) including the system caches."; + /** */ private boolean inMemoryMode; @@ -463,7 +470,7 @@ else if (stateOnStart == null) && !inMemoryMode && isBaselineSatisfied(state.baselineTopology(), discoCache.serverNodes()) ) - changeGlobalState(targetState, state.baselineTopology().currentBaseline(), false); + changeGlobalState(targetState, true, state.baselineTopology().currentBaseline(), false); } return null; @@ -622,6 +629,18 @@ protected void afterStateChangeFinished(IgniteUuid msgId, boolean success) { } else { if (isApplicable(msg, state)) { + if (msg.state() == INACTIVE && !msg.forceDeactivation() && hasInMemoryCache() && + allNodesSupports(ctx.discovery().serverNodes(topVer), SAFE_CLUSTER_DEACTIVATION)) { + GridChangeGlobalStateFuture stateFut = changeStateFuture(msg); + + if (stateFut != null) { + stateFut.onDone(new IgniteException(DATA_LOST_ON_DEACTIVATION_WARNING + + " To deactivate cluster pass flag 'force'.")); + } + + return false; + } + ExchangeActions exchangeActions; try { @@ -934,10 +953,11 @@ protected IgniteCheckedException concurrentStateChangeError(ClusterState state, /** {@inheritDoc} */ @Override public IgniteInternalFuture changeGlobalState( ClusterState state, + boolean forceDeactivation, Collection baselineNodes, boolean forceChangeBaselineTopology ) { - return changeGlobalState(state, baselineNodes, forceChangeBaselineTopology, false); + return changeGlobalState(state, forceDeactivation, baselineNodes, forceChangeBaselineTopology, false); } /** @@ -946,7 +966,7 @@ protected IgniteCheckedException concurrentStateChangeError(ClusterState state, * @param forceChangeBaselineTopology Force change BLT. * @param isAutoAdjust Auto adjusting flag. * @return Global change state future. - * @deprecated Use {@link #changeGlobalState(ClusterState, Collection, boolean, boolean)} instead. + * @deprecated Use {@link #changeGlobalState(ClusterState, boolean, Collection, boolean, boolean)} instead. */ @Deprecated public IgniteInternalFuture changeGlobalState( @@ -955,11 +975,13 @@ public IgniteInternalFuture changeGlobalState( boolean forceChangeBaselineTopology, boolean isAutoAdjust ) { - return changeGlobalState(activate ? ACTIVE : INACTIVE, baselineNodes, forceChangeBaselineTopology, isAutoAdjust); + return changeGlobalState(activate ? ACTIVE : INACTIVE, true, baselineNodes, forceChangeBaselineTopology, + isAutoAdjust); } /** * @param state New activate state. + * @param forceDeactivation If {@code true}, cluster deactivation will be forced. * @param baselineNodes New BLT nodes. * @param forceChangeBaselineTopology Force change BLT. * @param isAutoAdjust Auto adjusting flag. @@ -967,6 +989,7 @@ public IgniteInternalFuture changeGlobalState( */ public IgniteInternalFuture changeGlobalState( ClusterState state, + boolean forceDeactivation, Collection baselineNodes, boolean forceChangeBaselineTopology, boolean isAutoAdjust @@ -975,7 +998,7 @@ public IgniteInternalFuture changeGlobalState( null : calculateNewBaselineTopology(state, baselineNodes, forceChangeBaselineTopology); - return changeGlobalState0(state, newBlt, forceChangeBaselineTopology, isAutoAdjust); + return changeGlobalState0(state, forceDeactivation, newBlt, forceChangeBaselineTopology, isAutoAdjust); } /** */ @@ -1045,6 +1068,7 @@ private Collection baselineNodes() { /** */ private IgniteInternalFuture changeGlobalState0( ClusterState state, + boolean forceDeactivation, BaselineTopology blt, boolean forceChangeBaselineTopology, boolean isAutoAdjust @@ -1055,7 +1079,7 @@ private IgniteInternalFuture changeGlobalState0( throw new BaselineAdjustForbiddenException(isBaselineAutoAdjustEnabled); if (ctx.isDaemon() || ctx.clientNode()) - return sendComputeChangeGlobalState(state, blt, forceChangeBaselineTopology); + return sendComputeChangeGlobalState(state, forceDeactivation, blt, forceChangeBaselineTopology); if (cacheProc.transactions().tx() != null || sharedCtx.lockedTopologyVersion(null) != null) { return new GridFinishedFuture<>( @@ -1135,6 +1159,7 @@ private IgniteInternalFuture changeGlobalState0( ctx.localNodeId(), storedCfgs, state, + forceDeactivation, blt, forceChangeBaselineTopology, System.currentTimeMillis() @@ -1286,11 +1311,13 @@ protected IgniteInternalFuture wrapStateChangeFuture(IgniteInternalFuture fut /** * @param state New cluster state. + * @param forceDeactivation If {@code true}, cluster deactivation will be forced. * @param blt New cluster state. * @param forceBlt New cluster state. */ private IgniteInternalFuture sendComputeChangeGlobalState( ClusterState state, + boolean forceDeactivation, BaselineTopology blt, boolean forceBlt ) { @@ -1307,7 +1334,8 @@ private IgniteInternalFuture sendComputeChangeGlobalState( IgniteCompute comp = ((ClusterGroupAdapter)ctx.cluster().get().forServers()).compute(); - IgniteFuture fut = comp.runAsync(new ClientSetGlobalStateComputeRequest(state, blt, forceBlt)); + IgniteFuture fut = comp.runAsync(new ClientSetGlobalStateComputeRequest(state, forceDeactivation, blt, + forceBlt)); return ((IgniteFutureImpl)fut).internalFuture(); } @@ -1577,6 +1605,7 @@ public boolean autoAdjustInMemoryClusterState( nodeId, null, oldState.state(), + true, newBlt, true, System.currentTimeMillis() @@ -1617,6 +1646,7 @@ public ExchangeActions autoAdjustExchangeActions(ExchangeActions exchActs) { ctx.localNodeId(), null, ClusterState.active(clusterState.state()) ? clusterState.state() : ACTIVE, + true, blt, true, System.currentTimeMillis() @@ -1800,6 +1830,16 @@ private boolean getBooleanFieldFromConfig(IgniteConfiguration cfg, String fieldN return defaultValue; } + /** + * @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())); + } + /** {@inheritDoc} */ @Override public String toString() { return S.toString(GridClusterStateProcessor.class, this); @@ -1966,6 +2006,9 @@ private static class ClientSetGlobalStateComputeRequest implements IgniteRunnabl /** */ private final ClusterState state; + /** If {@code true}, cluster deactivation will be forced. */ + private final boolean forceDeactivation; + /** */ private final BaselineTopology baselineTopology; @@ -1978,17 +2021,20 @@ private static class ClientSetGlobalStateComputeRequest implements IgniteRunnabl /** * @param state New cluster state. + * @param forceDeactivation If {@code true}, cluster deactivation will be forced. * @param blt New baseline topology. * @param forceBlt Force change cluster state. */ private ClientSetGlobalStateComputeRequest( ClusterState state, + boolean forceDeactivation, BaselineTopology blt, boolean forceBlt ) { this.state = state; this.baselineTopology = blt; this.forceChangeBaselineTopology = forceBlt; + this.forceDeactivation = forceDeactivation; } /** {@inheritDoc} */ @@ -1996,6 +2042,7 @@ private ClientSetGlobalStateComputeRequest( try { ig.context().state().changeGlobalState( state, + forceDeactivation, baselineTopology != null ? baselineTopology.currentBaseline() : null, forceChangeBaselineTopology ).get(); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cluster/IGridClusterStateProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cluster/IGridClusterStateProcessor.java index f4e6850570eae..7118c281efd04 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cluster/IGridClusterStateProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cluster/IGridClusterStateProcessor.java @@ -113,7 +113,7 @@ boolean onStateChangeMessage(AffinityTopologyVersion topVer, * @param baselineNodes New baseline nodes. * @param forceChangeBaselineTopology Force change baseline topology. * @return State change future. - * @deprecated Use {@link #changeGlobalState(ClusterState, Collection, boolean)} instead. + * @deprecated Use {@link #changeGlobalState(ClusterState, boolean, Collection, boolean)} instead. */ @Deprecated IgniteInternalFuture changeGlobalState( @@ -125,11 +125,14 @@ IgniteInternalFuture changeGlobalState( /** * @param state New cluster state. * @param baselineNodes New baseline nodes. + * @param forceDeactivation If {@code true}, cluster deactivation will be forced. * @param forceChangeBaselineTopology Force change baseline topology. * @return State change future. + * @see ClusterState#INACTIVE */ IgniteInternalFuture changeGlobalState( ClusterState state, + boolean forceDeactivation, Collection baselineNodes, boolean forceChangeBaselineTopology ); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/client/message/GridClientClusterStateRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/client/message/GridClientClusterStateRequest.java index 319a4361102b2..122fdebd78e0b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/client/message/GridClientClusterStateRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/client/message/GridClientClusterStateRequest.java @@ -24,8 +24,9 @@ import org.apache.ignite.internal.util.typedef.internal.U; /** - * + * @deprecated Use {@link GridClientClusterStateRequestV2} */ +@Deprecated public class GridClientClusterStateRequest extends GridClientAbstractMessage { /** */ private static final long serialVersionUID = 0L; @@ -34,7 +35,7 @@ public class GridClientClusterStateRequest extends GridClientAbstractMessage { private boolean reqCurrentState; /** New cluster state. */ - private ClusterState state; + protected ClusterState state; /** */ public boolean isReqCurrentState() { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/client/message/GridClientClusterStateRequestV2.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/client/message/GridClientClusterStateRequestV2.java new file mode 100644 index 0000000000000..884b94bb7235f --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/client/message/GridClientClusterStateRequestV2.java @@ -0,0 +1,73 @@ +/* + * 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.internal.processors.rest.client.message; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import org.apache.ignite.cluster.ClusterState; + +/** + * Enhanced version of {@link GridClientClusterStateRequest}. + * Introduced to support forced version of the change state command and keep backward compatibility + * with nodes of old version that may occur in cluster at the rolling updates. + */ +public class GridClientClusterStateRequestV2 extends GridClientClusterStateRequest { + /** */ + private static final long serialVersionUID = 0L; + + /** If {@code true}, cluster deactivation will be forced. */ + private boolean forceDeactivation; + + /** + * @return {@code True} if cluster deactivation will be forced. {@code False} otherwise. + * @see ClusterState#INACTIVE + */ + public boolean forceDeactivation() { + return forceDeactivation; + } + + /** + * @param state New cluster state. + * @param forceDeactivation If {@code true}, cluster deactivation will be forced. + * @return Cluster state change request. + */ + public static GridClientClusterStateRequestV2 state(ClusterState state, boolean forceDeactivation) { + GridClientClusterStateRequestV2 req = new GridClientClusterStateRequestV2(); + + req.state = state; + + req.forceDeactivation = forceDeactivation; + + return req; + } + + /** {@inheritDoc} */ + @Override public void writeExternal(ObjectOutput out) throws IOException { + super.writeExternal(out); + + out.writeBoolean(forceDeactivation); + } + + /** {@inheritDoc} */ + @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + super.readExternal(in); + + forceDeactivation = in.readBoolean(); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cluster/GridChangeClusterStateCommandHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cluster/GridChangeClusterStateCommandHandler.java index 7de6d1426a255..eb38477153dd8 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cluster/GridChangeClusterStateCommandHandler.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cluster/GridChangeClusterStateCommandHandler.java @@ -73,7 +73,8 @@ public GridChangeClusterStateCommandHandler(GridKernalContext ctx) { U.log(log, "Received cluster state change request to " + req.state() + " state from client node with ID: " + req.clientId()); - ctx.grid().cluster().state(req.state()); + ctx.state().changeGlobalState(req.state(), req.forceDeactivation(), + ctx.cluster().get().forServers().nodes(), false, false).get(); res.setResponse(req.command().key() + " done"); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cluster/GridChangeStateCommandHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cluster/GridChangeStateCommandHandler.java index 3bea4fd57ee67..d766ff737acdf 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cluster/GridChangeStateCommandHandler.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/handlers/cluster/GridChangeStateCommandHandler.java @@ -28,6 +28,8 @@ import org.apache.ignite.internal.util.future.GridFutureAdapter; import org.apache.ignite.internal.util.typedef.internal.U; +import static org.apache.ignite.cluster.ClusterState.ACTIVE; +import static org.apache.ignite.cluster.ClusterState.INACTIVE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CLUSTER_ACTIVATE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CLUSTER_ACTIVE; import static org.apache.ignite.internal.processors.rest.GridRestCommand.CLUSTER_CURRENT_STATE; @@ -73,7 +75,8 @@ public GridChangeStateCommandHandler(GridKernalContext ctx) { case CLUSTER_INACTIVE: log.warning(req.command().key() + " is deprecated. Use newer commands."); default: - ctx.grid().cluster().active(req.active()); + ctx.state().changeGlobalState(req.active() ? ACTIVE : INACTIVE, req.forceDeactivation(), + ctx.cluster().get().forServers().nodes(), false, false).get(); res.setResponse(req.command().key() + " started"); break; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/GridTcpRestNioListener.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/GridTcpRestNioListener.java index 9e4f0a9f8888b..6ad9431fd25d6 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/GridTcpRestNioListener.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/GridTcpRestNioListener.java @@ -38,6 +38,7 @@ import org.apache.ignite.internal.processors.rest.client.message.GridClientCacheRequest; import org.apache.ignite.internal.processors.rest.client.message.GridClientClusterNameRequest; import org.apache.ignite.internal.processors.rest.client.message.GridClientClusterStateRequest; +import org.apache.ignite.internal.processors.rest.client.message.GridClientClusterStateRequestV2; import org.apache.ignite.internal.processors.rest.client.message.GridClientHandshakeRequest; import org.apache.ignite.internal.processors.rest.client.message.GridClientHandshakeResponse; import org.apache.ignite.internal.processors.rest.client.message.GridClientMessage; @@ -389,6 +390,9 @@ else if (msg instanceof GridClientStateRequest) { else if (msg instanceof GridClientClusterStateRequest) { GridClientClusterStateRequest req = (GridClientClusterStateRequest)msg; + boolean forceDeactivation = !(msg instanceof GridClientClusterStateRequestV2) || + ((GridClientClusterStateRequestV2)msg).forceDeactivation(); + GridRestClusterStateRequest restChangeReq = new GridRestClusterStateRequest(); if (req.isReqCurrentState()) { @@ -398,6 +402,8 @@ else if (msg instanceof GridClientClusterStateRequest) { else { restChangeReq.state(req.state()); restChangeReq.command(CLUSTER_SET_STATE); + + restChangeReq.forceDeactivation(forceDeactivation); } restReq = restChangeReq; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestChangeStateRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestChangeStateRequest.java index 8bbfedd1a27c6..a9ba95b15b1f3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestChangeStateRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestChangeStateRequest.java @@ -17,6 +17,8 @@ package org.apache.ignite.internal.processors.rest.request; +import org.apache.ignite.cluster.ClusterState; + /** * */ @@ -27,6 +29,9 @@ public class GridRestChangeStateRequest extends GridRestRequest { /** Request current state. */ private boolean reqCurrentState; + /** If {@code true}, cluster deactivation will be forced. */ + private boolean forceDeactivation; + /** * */ @@ -54,4 +59,19 @@ public boolean isReqCurrentState() { public void reqCurrentState() { reqCurrentState = true; } + + /** + * @param forceDeactivation If {@code true}, cluster deactivation will be forced. + */ + public void forceDeactivation(boolean forceDeactivation) { + this.forceDeactivation = forceDeactivation; + } + + /** + * @return {@code True} if cluster deactivation will be forced. {@code False} otherwise. + * @see ClusterState#INACTIVE + */ + public boolean forceDeactivation() { + return forceDeactivation; + } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestClusterStateRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestClusterStateRequest.java index 82de9a3c888c4..caadfb83f3fdb 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestClusterStateRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestClusterStateRequest.java @@ -23,12 +23,25 @@ * */ public class GridRestClusterStateRequest extends GridRestRequest { + /** Flag of forced cluster deactivation. */ + public static final String ARG_FORCE = "force"; + /** Request current state. */ private boolean reqCurrentMode; /** New state. */ private ClusterState state; + /** If {@code true}, cluster deactivation will be forced. */ + private boolean forceDeactivation; + + /** + * @param forceDeactivation If {@code true}, cluster deactivation will be forced. + */ + public void forceDeactivation(boolean forceDeactivation) { + this.forceDeactivation = forceDeactivation; + } + /** */ public void reqCurrentMode() { reqCurrentMode = true; @@ -39,6 +52,14 @@ public boolean isReqCurrentMode() { return reqCurrentMode; } + /** + * @return {@code True} if cluster deactivation will be forced. {@code False} otherwise. + * @see ClusterState#INACTIVE + */ + public boolean forceDeactivation() { + return forceDeactivation; + } + /** */ public ClusterState state() { return state; diff --git a/modules/core/src/main/java/org/apache/ignite/mxbean/IgniteMXBean.java b/modules/core/src/main/java/org/apache/ignite/mxbean/IgniteMXBean.java index 47bb9254277d7..0fd25b74ece43 100644 --- a/modules/core/src/main/java/org/apache/ignite/mxbean/IgniteMXBean.java +++ b/modules/core/src/main/java/org/apache/ignite/mxbean/IgniteMXBean.java @@ -382,6 +382,11 @@ public boolean pingNode( ); /** + * Changes Ignite grid state to active or inactive. + *

+ * NOTE: + * Deactivation clears in-memory caches (without persistence) including the system caches. + * * @param active Activate/DeActivate flag. */ @MXBeanDescription( @@ -658,9 +663,11 @@ void runIoTest( /** * Changes current cluster state. + *

+ * NOTE: + * Deactivation clears in-memory caches (without persistence) including the system caches. * * @param state String representation of new cluster state. - * See {@link ClusterState} */ @MXBeanDescription("Changes current cluster state.") public void clusterState( diff --git a/modules/core/src/main/resources/META-INF/classnames.properties b/modules/core/src/main/resources/META-INF/classnames.properties index 6abb02ed4be23..c45abc9c4a1de 100644 --- a/modules/core/src/main/resources/META-INF/classnames.properties +++ b/modules/core/src/main/resources/META-INF/classnames.properties @@ -1661,6 +1661,7 @@ org.apache.ignite.internal.processors.rest.client.message.GridClientMessage org.apache.ignite.internal.processors.rest.client.message.GridClientNodeBean org.apache.ignite.internal.processors.rest.client.message.GridClientNodeMetricsBean org.apache.ignite.internal.processors.rest.client.message.GridClientPingPacket +org.apache.ignite.internal.processors.rest.client.message.GridClientClusterStateRequestV2 org.apache.ignite.internal.processors.rest.client.message.GridClientClusterStateRequest org.apache.ignite.internal.processors.rest.client.message.GridClientResponse org.apache.ignite.internal.processors.rest.client.message.GridClientStateRequest diff --git a/modules/core/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java b/modules/core/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java index 0348fd8ee6173..0d2eee5c35c03 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java @@ -365,6 +365,10 @@ public void testParseAutoConfirmationFlag() { checkCommonParametersCorrectlyParsed(cmdL, args, true); + args = parseArgs(asList(cmdL.text(), "--force", "--yes")); + + checkCommonParametersCorrectlyParsed(cmdL, args, true); + break; } case SET_STATE: { @@ -378,6 +382,16 @@ public void testParseAutoConfirmationFlag() { assertEquals(newState, argState.toString()); } + for (String newState : asList("ACTIVE_READ_ONLY", "ACTIVE", "INACTIVE")) { + args = parseArgs(asList(cmdL.text(), newState, "--force", "--yes")); + + checkCommonParametersCorrectlyParsed(cmdL, args, true); + + ClusterState argState = ((ClusterStateChangeCommand)args.command()).arg(); + + assertEquals(newState, argState.toString()); + } + break; } case BASELINE: { diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/ClusterStateThinClientAbstractTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/ClusterStateThinClientAbstractTest.java index 56e4ab22418db..49d8fcc8033f0 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/ClusterStateThinClientAbstractTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/ClusterStateThinClientAbstractTest.java @@ -71,7 +71,7 @@ public abstract class ClusterStateThinClientAbstractTest extends ClusterStateAbs /** {@inheritDoc} */ @Override protected void changeState(ClusterState state) { try { - gridClient.state().state(state); + gridClient.state().state(state, true); } catch (GridClientException e) { throw new RuntimeException("Can't change state to " + state, e); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/GridCommandHandlerSslWithSecurityTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/GridCommandHandlerSslWithSecurityTest.java index 721412b072b2b..0849b1f1bdeeb 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/GridCommandHandlerSslWithSecurityTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/GridCommandHandlerSslWithSecurityTest.java @@ -93,6 +93,7 @@ else if (fmt.contains("truststore")) { List args = new ArrayList<>(); args.add(DEACTIVATE.text()); + args.add("--force"); args.add("--yes"); args.add("--user"); diff --git a/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java b/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java index a9c8620cdbc1d..7442ae0914cb9 100644 --- a/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java +++ b/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java @@ -81,6 +81,7 @@ import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry; import org.apache.ignite.internal.processors.cache.transactions.TransactionProxyImpl; import org.apache.ignite.internal.processors.cache.version.GridCacheVersion; +import org.apache.ignite.internal.processors.cluster.GridClusterStateProcessor; import org.apache.ignite.internal.util.lang.GridAbsPredicate; import org.apache.ignite.internal.util.typedef.G; import org.apache.ignite.internal.util.typedef.T2; @@ -253,6 +254,63 @@ public void testDeactivate() throws Exception { assertContains(log, testOut.toString(), "Command deprecated. Use --set-state instead."); } + /** + * Test "deactivate" via control.sh when a non-persistent cache involved. + * + * @throws Exception If failed. + */ + @Test + public void testDeactivateNonPersistent() throws Exception { + checkDeactivateNonPersistent("--deactivate"); + } + + /** + * Test "set-state inactive" via control.sh when a non-persistent cache involved. + * + * @throws Exception If failed. + */ + @Test + public void testSetInactiveNonPersistent() throws Exception { + checkDeactivateNonPersistent("--set-state", "inactive"); + } + + /** + * Launches cluster deactivation. Works via control.sh when a non-persistent cache involved. + * + * @param cmd Certain command to deactivate cluster. + */ + private void checkDeactivateNonPersistent(String... cmd) throws Exception { + dataRegionConfiguration = new DataRegionConfiguration() + .setName("non-persistent-dataRegion") + .setPersistenceEnabled(false); + + Ignite ignite = startGrids(1); + + ignite.cluster().state(ACTIVE); + + assertTrue(ignite.cluster().active()); + assertEquals(ACTIVE, ignite.cluster().state()); + + ignite.createCache(new CacheConfiguration<>("non-persistent-cache") + .setDataRegionName("non-persistent-dataRegion")); + + injectTestSystemOut(); + + assertEquals(EXIT_CODE_UNEXPECTED_ERROR, execute(cmd)); + + assertTrue(ignite.cluster().active()); + assertEquals(ACTIVE, ignite.cluster().state()); + assertContains(log, testOut.toString(), GridClusterStateProcessor.DATA_LOST_ON_DEACTIVATION_WARNING); + + List forceCmd = new ArrayList<>(Arrays.asList(cmd)); + forceCmd.add("--force"); + + assertEquals(EXIT_CODE_OK, execute(forceCmd)); + + assertFalse(ignite.cluster().active()); + assertEquals(INACTIVE, ignite.cluster().state()); + } + /** * Test the deactivation command on the active and no cluster with checking * the cluster name(which is set through the system property) in diff --git a/modules/core/src/test/resources/org.apache.ignite.util/control.sh_help.output b/modules/core/src/test/resources/org.apache.ignite.util/control.sh_help.output index c3c825fb017f2..17755838a9c2b 100644 --- a/modules/core/src/test/resources/org.apache.ignite.util/control.sh_help.output +++ b/modules/core/src/test/resources/org.apache.ignite.util/control.sh_help.output @@ -13,13 +13,13 @@ This utility can do the following commands: control.(sh|bat) --activate Deactivate cluster (deprecated. Use --set-state instead): - control.(sh|bat) --deactivate [--yes] + control.(sh|bat) --deactivate [--force] [--yes] Print current cluster state: control.(sh|bat) --state Change cluster state: - control.(sh|bat) --set-state INACTIVE|ACTIVE|ACTIVE_READ_ONLY [--yes] + control.(sh|bat) --set-state INACTIVE|ACTIVE|ACTIVE_READ_ONLY [--force] [--yes] Parameters: ACTIVE - Activate cluster. Cache updates are allowed. diff --git a/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java b/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java index d1627c71ce457..20ada42089031 100644 --- a/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java +++ b/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java @@ -56,8 +56,8 @@ import org.apache.ignite.internal.processors.rest.request.GridRestCacheRequest; import org.apache.ignite.internal.processors.rest.request.GridRestChangeStateRequest; import org.apache.ignite.internal.processors.rest.request.GridRestClusterNameRequest; -import org.apache.ignite.internal.processors.rest.request.GridRestLogRequest; import org.apache.ignite.internal.processors.rest.request.GridRestClusterStateRequest; +import org.apache.ignite.internal.processors.rest.request.GridRestLogRequest; import org.apache.ignite.internal.processors.rest.request.GridRestRequest; import org.apache.ignite.internal.processors.rest.request.GridRestTaskRequest; import org.apache.ignite.internal.processors.rest.request.GridRestTopologyRequest; @@ -212,6 +212,22 @@ public class GridJettyRestHandler extends AbstractHandler { } } + /** + * Retrieves boolean value from parameters map. + * + * @param key Key. + * @param params Parameters map. + * @param dfltVal Default value. + * @return Boolean value from parameters map or {@code dfltVal} if null or not exists. + */ + private static boolean booleanValue(String key, Map params, boolean dfltVal) { + assert key != null; + + String val = (String)params.get(key); + + return val == null ? dfltVal : Boolean.parseBoolean(val); + } + /** * Retrieves int value from parameters map. * @@ -790,6 +806,8 @@ else if (cmd == CLUSTER_ACTIVE || cmd == CLUSTER_ACTIVATE) else restReq0.active(false); + restReq0.forceDeactivation(booleanValue(GridRestClusterStateRequest.ARG_FORCE, params, false)); + restReq = restReq0; break; @@ -805,6 +823,8 @@ else if (cmd == CLUSTER_ACTIVE || cmd == CLUSTER_ACTIVATE) ClusterState newState = enumValue("state", params, ClusterState.class); restReq0.state(newState); + + restReq0.forceDeactivation(booleanValue(GridRestClusterStateRequest.ARG_FORCE, params, false)); } restReq = restReq0;