You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by mm...@apache.org on 2020/05/27 18:42:16 UTC

[ignite] branch master updated: IGNITE-12978: Add cancel snapshot command (#7827)

This is an automated email from the ASF dual-hosted git repository.

mmuzaf 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 9d2824d  IGNITE-12978: Add cancel snapshot command (#7827)
9d2824d is described below

commit 9d2824dd81e14c01a4e325e2ffe28f2608e2826e
Author: Maxim Muzafarov <mm...@apache.org>
AuthorDate: Wed May 27 21:41:52 2020 +0300

    IGNITE-12978: Add cancel snapshot command (#7827)
---
 .../java/org/apache/ignite/IgniteSnapshot.java     |   9 ++
 .../ignite/internal/MarshallerContextImpl.java     |  27 ++--
 .../internal/commandline/query/KillCommand.java    |  22 +++-
 .../internal/commandline/query/KillSubcommand.java |   5 +
 .../commandline/snapshot/SnapshotCommand.java      |  25 +++-
 .../commandline/snapshot/SnapshotSubcommand.java   |   5 +-
 .../binary/CacheObjectBinaryProcessorImpl.java     |  29 +++--
 .../snapshot/IgniteSnapshotManager.java            | 144 ++++++++++++++++++---
 .../persistence/snapshot/SnapshotFutureTask.java   |   8 +-
 .../persistence/snapshot/SnapshotMXBeanImpl.java   |   5 +
 .../visor/snapshot/VisorSnapshotCancelTask.java    |  62 +++++++++
 .../org/apache/ignite/mxbean/SnapshotMXBean.java   |  10 +-
 .../main/resources/META-INF/classnames.properties  |   2 +
 .../snapshot/AbstractSnapshotSelfTest.java         |  84 ++++++++++--
 .../snapshot/IgniteClusterSnapshotSelfTest.java    |  32 ++++-
 .../snapshot/IgniteSnapshotMXBeanTest.java         |  17 +++
 .../snapshot/IgniteSnapshotManagerSelfTest.java    |   6 +-
 .../junits/common/GridCommonAbstractTest.java      |   2 +
 .../apache/ignite/util/GridCommandHandlerTest.java |  20 ++-
 ...ridCommandHandlerClusterByClassTest_help.output |  12 ++
 ...andHandlerClusterByClassWithSSLTest_help.output |  12 ++
 .../IgniteClusterSnapshotWithIndexesTest.java      |  15 +--
 .../processors/query/SqlSystemViewsSelfTest.java   |  20 +--
 .../ignite/util/KillCommandsCommandShTest.java     |  26 +++-
 .../apache/ignite/util/KillCommandsMXBeanTest.java |  51 +++++++-
 .../apache/ignite/util/KillCommandsSQLTest.java    |   5 +-
 .../org/apache/ignite/util/KillCommandsTests.java  |   6 +-
 27 files changed, 562 insertions(+), 99 deletions(-)

diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteSnapshot.java b/modules/core/src/main/java/org/apache/ignite/IgniteSnapshot.java
index 753d427..3623c1f 100644
--- a/modules/core/src/main/java/org/apache/ignite/IgniteSnapshot.java
+++ b/modules/core/src/main/java/org/apache/ignite/IgniteSnapshot.java
@@ -39,4 +39,13 @@ public interface IgniteSnapshot {
      * @return Future which will be completed when a process ends.
      */
     public IgniteFuture<Void> createSnapshot(String name);
+
+    /**
+     * Cancel running snapshot operation. All intermediate results of cancelled snapshot operation will be deleted.
+     * If snapshot already created this command will have no effect.
+     *
+     * @param name Snapshot name to cancel.
+     * @return Future which will be completed when cancel operation finished.
+     */
+    public IgniteFuture<Void> cancelSnapshot(String name);
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/MarshallerContextImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/MarshallerContextImpl.java
index 2897291..a690075 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/MarshallerContextImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/MarshallerContextImpl.java
@@ -191,7 +191,7 @@ public class MarshallerContextImpl implements MarshallerContext {
      */
     public static void saveMappings(GridKernalContext ctx, List<Map<Integer, MappedName>> mappings, File dir) {
         MarshallerMappingFileStore writer = new MarshallerMappingFileStore(ctx,
-            mappingFileStoreWorkDir(dir.getAbsolutePath()));
+            resolveMappingFileStoreWorkDir(dir.getAbsolutePath()));
 
         addPlatformMappings(ctx.log(MarshallerContextImpl.class),
             mappings,
@@ -592,7 +592,7 @@ public class MarshallerContextImpl implements MarshallerContext {
         String workDir = U.workDirectory(cfg.getWorkDirectory(), cfg.getIgniteHome());
 
         fileStore = marshallerMappingFileStoreDir == null ?
-            new MarshallerMappingFileStore(ctx, mappingFileStoreWorkDir(workDir)) :
+            new MarshallerMappingFileStore(ctx, resolveMappingFileStoreWorkDir(workDir)) :
             new MarshallerMappingFileStore(ctx, marshallerMappingFileStoreDir);
 
         this.transport = transport;
@@ -607,13 +607,24 @@ public class MarshallerContextImpl implements MarshallerContext {
      * @param igniteWorkDir Base ignite working directory.
      * @return Resolved directory.
      */
+    public static File resolveMappingFileStoreWorkDir(String igniteWorkDir) {
+        File dir = mappingFileStoreWorkDir(igniteWorkDir);
+
+        if (!U.mkdirs(dir))
+            throw new IgniteException("Could not create directory for marshaller mappings: " + dir);
+
+        return dir;
+    }
+
+    /**
+     * @param igniteWorkDir Base ignite working directory.
+     * @return Work directory for marshaller mappings.
+     */
     public static File mappingFileStoreWorkDir(String igniteWorkDir) {
-        try {
-            return U.resolveWorkDirectory(igniteWorkDir, "marshaller", false);
-        }
-        catch (IgniteCheckedException e) {
-            throw new IgniteException(e);
-        }
+        if (F.isEmpty(igniteWorkDir))
+            throw new IgniteException("Work directory has not been set: " + igniteWorkDir);
+
+        return new File(igniteWorkDir, "marshaller");
     }
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillCommand.java
index 18d024e..38b170d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillCommand.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillCommand.java
@@ -27,6 +27,8 @@ import org.apache.ignite.internal.commandline.Command;
 import org.apache.ignite.internal.commandline.CommandArgIterator;
 import org.apache.ignite.internal.commandline.CommandLogger;
 import org.apache.ignite.internal.util.typedef.T2;
+import org.apache.ignite.internal.visor.compute.VisorComputeCancelSessionTask;
+import org.apache.ignite.internal.visor.compute.VisorComputeCancelSessionTaskArg;
 import org.apache.ignite.internal.visor.query.VisorContinuousQueryCancelTask;
 import org.apache.ignite.internal.visor.query.VisorContinuousQueryCancelTaskArg;
 import org.apache.ignite.internal.visor.query.VisorQueryCancelOnInitiatorTask;
@@ -35,25 +37,25 @@ import org.apache.ignite.internal.visor.query.VisorScanQueryCancelTask;
 import org.apache.ignite.internal.visor.query.VisorScanQueryCancelTaskArg;
 import org.apache.ignite.internal.visor.service.VisorCancelServiceTask;
 import org.apache.ignite.internal.visor.service.VisorCancelServiceTaskArg;
-import org.apache.ignite.mxbean.QueryMXBean;
-import org.apache.ignite.mxbean.ServiceMXBean;
-import org.apache.ignite.internal.visor.compute.VisorComputeCancelSessionTask;
-import org.apache.ignite.internal.visor.compute.VisorComputeCancelSessionTaskArg;
+import org.apache.ignite.internal.visor.snapshot.VisorSnapshotCancelTask;
 import org.apache.ignite.internal.visor.tx.VisorTxOperation;
 import org.apache.ignite.internal.visor.tx.VisorTxTask;
 import org.apache.ignite.internal.visor.tx.VisorTxTaskArg;
 import org.apache.ignite.lang.IgniteUuid;
 import org.apache.ignite.mxbean.ComputeMXBean;
+import org.apache.ignite.mxbean.QueryMXBean;
+import org.apache.ignite.mxbean.ServiceMXBean;
 import org.apache.ignite.mxbean.TransactionsMXBean;
 
 import static java.util.Collections.singletonMap;
 import static org.apache.ignite.internal.QueryMXBeanImpl.EXPECTED_GLOBAL_QRY_ID_FORMAT;
 import static org.apache.ignite.internal.commandline.CommandList.KILL;
 import static org.apache.ignite.internal.commandline.TaskExecutor.executeTaskByNameOnNode;
+import static org.apache.ignite.internal.commandline.query.KillSubcommand.COMPUTE;
 import static org.apache.ignite.internal.commandline.query.KillSubcommand.CONTINUOUS;
 import static org.apache.ignite.internal.commandline.query.KillSubcommand.SCAN;
 import static org.apache.ignite.internal.commandline.query.KillSubcommand.SERVICE;
-import static org.apache.ignite.internal.commandline.query.KillSubcommand.COMPUTE;
+import static org.apache.ignite.internal.commandline.query.KillSubcommand.SNAPSHOT;
 import static org.apache.ignite.internal.commandline.query.KillSubcommand.SQL;
 import static org.apache.ignite.internal.commandline.query.KillSubcommand.TRANSACTION;
 import static org.apache.ignite.internal.sql.command.SqlKillQueryCommand.parseGlobalQueryId;
@@ -171,6 +173,13 @@ public class KillCommand implements Command<Object> {
 
                 break;
 
+            case SNAPSHOT:
+                taskArgs = argIter.nextArg("Expected snapshot name.");
+
+                taskName = VisorSnapshotCancelTask.class.getName();
+
+                break;
+
             default:
                 throw new IllegalArgumentException("Unknown kill subcommand: " + cmd);
         }
@@ -205,6 +214,9 @@ public class KillCommand implements Command<Object> {
 
         Command.usage(log, "Kill continuous query by routine id:", KILL, params, CONTINUOUS.toString(),
             "origin_node_id", "routine_id");
+
+        Command.usage(log, "Kill running snapshot by snapshot name:", KILL, singletonMap("snapshot_name", "Snapshot name."),
+            SNAPSHOT.toString(), "snapshot_name");
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillSubcommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillSubcommand.java
index aa50575..311ab96 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillSubcommand.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/query/KillSubcommand.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.commandline.query;
 import org.apache.ignite.mxbean.ComputeMXBean;
 import org.apache.ignite.mxbean.QueryMXBean;
 import org.apache.ignite.mxbean.ServiceMXBean;
+import org.apache.ignite.mxbean.SnapshotMXBean;
 import org.apache.ignite.mxbean.TransactionsMXBean;
 
 /**
@@ -30,6 +31,7 @@ import org.apache.ignite.mxbean.TransactionsMXBean;
  * @see ComputeMXBean
  * @see TransactionsMXBean
  * @see ServiceMXBean
+ * @see SnapshotMXBean
  */
 public enum KillSubcommand {
     /** Kill compute task. */
@@ -49,4 +51,7 @@ public enum KillSubcommand {
 
     /** Kill continuous query. */
     CONTINUOUS,
+
+    /** Kill snapshot operation. */
+    SNAPSHOT,
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCommand.java
index 7804524..2c77e69 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCommand.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCommand.java
@@ -24,12 +24,14 @@ import org.apache.ignite.internal.commandline.Command;
 import org.apache.ignite.internal.commandline.CommandArgIterator;
 import org.apache.ignite.internal.commandline.CommandLogger;
 import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager;
+import org.apache.ignite.internal.visor.snapshot.VisorSnapshotCancelTask;
 import org.apache.ignite.internal.visor.snapshot.VisorSnapshotCreateTask;
 import org.apache.ignite.mxbean.SnapshotMXBean;
 
 import static java.util.Collections.singletonMap;
 import static org.apache.ignite.internal.commandline.CommandList.SNAPSHOT;
 import static org.apache.ignite.internal.commandline.TaskExecutor.executeTaskByNameOnNode;
+import static org.apache.ignite.internal.commandline.snapshot.SnapshotSubcommand.CANCEL;
 import static org.apache.ignite.internal.commandline.snapshot.SnapshotSubcommand.CREATE;
 import static org.apache.ignite.internal.commandline.snapshot.SnapshotSubcommand.of;
 
@@ -77,20 +79,31 @@ public class SnapshotCommand implements Command<Object> {
         if (cmd == null)
             throw new IllegalArgumentException("Expected correct action.");
 
-        if (cmd == CREATE) {
-            String name = argIter.nextArg("Expected snapshot name.");
+        switch (cmd) {
+            case CREATE:
+                taskName = VisorSnapshotCreateTask.class.getName();
+                taskArgs = argIter.nextArg("Expected snapshot name.");
 
-            taskName = VisorSnapshotCreateTask.class.getName();
-            taskArgs = name;
+                break;
+
+            case CANCEL:
+                taskName = VisorSnapshotCancelTask.class.getName();
+                taskArgs = argIter.nextArg("Expected snapshot name.");
+
+                break;
+
+            default:
+                throw new IllegalArgumentException("Unknown snapshot sub-command: " + cmd);
         }
-        else
-            throw new IllegalArgumentException("Unknown snapshot sub-command: " + cmd);
     }
 
     /** {@inheritDoc} */
     @Override public void printUsage(Logger log) {
         Command.usage(log, "Create cluster snapshot:", SNAPSHOT, singletonMap("snapshot_name", "Snapshot name."),
             CREATE.toString(), "snapshot_name");
+
+        Command.usage(log, "Cancel running snapshot:", SNAPSHOT, singletonMap("snapshot_name", "Snapshot name."),
+            CANCEL.toString(), "snapshot_name");
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotSubcommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotSubcommand.java
index 7c206ad..0d365fa 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotSubcommand.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotSubcommand.java
@@ -26,7 +26,10 @@ import org.jetbrains.annotations.Nullable;
  */
 public enum SnapshotSubcommand {
     /** Sub-command to create a cluster snapshot. */
-    CREATE("create");
+    CREATE("create"),
+
+    /** Sub-command to cancel running snapshot. */
+    CANCEL("cancel");
 
     /** Sub-command name. */
     private final String name;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/CacheObjectBinaryProcessorImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/CacheObjectBinaryProcessorImpl.java
index f114bf7..3fab2ac 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/CacheObjectBinaryProcessorImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/CacheObjectBinaryProcessorImpl.java
@@ -21,6 +21,7 @@ import java.io.File;
 import java.io.Serializable;
 import java.math.BigDecimal;
 import java.nio.ByteBuffer;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -210,20 +211,26 @@ public class CacheObjectBinaryProcessorImpl extends GridProcessorAdapter impleme
      * @return Working directory.
      */
     public static File resolveBinaryWorkDir(String igniteWorkDir, String consId) {
-        try {
-            File workDir = new File(U.resolveWorkDirectory(
-                igniteWorkDir,
-                BINARY_META_FOLDER,
-                false),
-                consId);
+        File workDir = binaryWorkDir(igniteWorkDir, consId);
 
-            U.ensureDirectory(workDir, "directory for serialized binary metadata", null);
+        if (!U.mkdirs(workDir))
+            throw new IgniteException("Could not create directory for binary metadata: " + workDir);
 
-            return workDir;
-        }
-        catch (IgniteCheckedException e) {
-            throw new IgniteException(e);
+        return workDir;
+    }
+
+    /**
+     * @param igniteWorkDir Basic ignite working directory.
+     * @param consId Node consistent id.
+     * @return Working directory.
+     */
+    public static File binaryWorkDir(String igniteWorkDir, String consId) {
+        if (F.isEmpty(igniteWorkDir) || F.isEmpty(consId)) {
+            throw new IgniteException("Work directory or consistent id has not been set " +
+                "[igniteWorkDir=" + igniteWorkDir + ", consId=" + consId + ']');
         }
+
+        return Paths.get(igniteWorkDir, BINARY_META_FOLDER, consId).toFile();
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java
index a1fdf11..18ac4bc 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java
@@ -58,6 +58,7 @@ import org.apache.ignite.internal.GridKernalContext;
 import org.apache.ignite.internal.IgniteClientDisconnectedCheckedException;
 import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.internal.IgniteFeatures;
+import org.apache.ignite.internal.IgniteFutureCancelledCheckedException;
 import org.apache.ignite.internal.IgniteInternalFuture;
 import org.apache.ignite.internal.NodeStoppingException;
 import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException;
@@ -96,6 +97,7 @@ import org.apache.ignite.internal.util.future.IgniteFinishedFutureImpl;
 import org.apache.ignite.internal.util.future.IgniteFutureImpl;
 import org.apache.ignite.internal.util.lang.GridClosureException;
 import org.apache.ignite.internal.util.tostring.GridToStringInclude;
+import org.apache.ignite.internal.util.typedef.CX1;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.A;
 import org.apache.ignite.internal.util.typedef.internal.CU;
@@ -119,7 +121,7 @@ import static org.apache.ignite.internal.events.DiscoveryCustomEvent.EVT_DISCOVE
 import static org.apache.ignite.internal.managers.communication.GridIoPolicy.SYSTEM_POOL;
 import static org.apache.ignite.internal.pagemem.PageIdAllocator.INDEX_PARTITION;
 import static org.apache.ignite.internal.pagemem.PageIdAllocator.MAX_PARTITION_ID;
-import static org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl.resolveBinaryWorkDir;
+import static org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl.binaryWorkDir;
 import static org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.INDEX_FILE_NAME;
 import static org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.PART_FILE_TEMPLATE;
 import static org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.getPartitionFile;
@@ -390,18 +392,18 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
      * @param snpDir Snapshot dir.
      * @param folderName Local node folder name (see {@link U#maskForFileName} with consistent id).
      */
-    public static void deleteSnapshot(File snpDir, String folderName) {
+    public void deleteSnapshot(File snpDir, String folderName) {
         if (!snpDir.exists())
             return;
 
         assert snpDir.isDirectory() : snpDir;
 
         try {
-            File binDir = resolveBinaryWorkDir(snpDir.getAbsolutePath(), folderName);
-            File dbDir = U.resolveWorkDirectory(snpDir.getAbsolutePath(), databaseRelativePath(folderName), false);
+            File binDir = binaryWorkDir(snpDir.getAbsolutePath(), folderName);
+            File nodeDbDir = new File(snpDir.getAbsolutePath(), databaseRelativePath(folderName));
 
             U.delete(binDir);
-            U.delete(dbDir);
+            U.delete(nodeDbDir);
 
             File marshDir = mappingFileStoreWorkDir(snpDir.getAbsolutePath());
 
@@ -417,14 +419,26 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
                     // Skip files which can be concurrently removed from FileTree.
                     return FileVisitResult.CONTINUE;
                 }
+
+                @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
+                    dir.toFile().delete();
+
+                    if (log.isInfoEnabled() && exc != null)
+                        log.info("Marshaller directory cleaned with an exception: " + exc.getMessage());
+
+                    return FileVisitResult.CONTINUE;
+                }
             });
 
             File db = new File(snpDir, DB_DEFAULT_FOLDER);
 
-            if (!db.exists() || db.list().length == 0)
+            if (!db.exists() || db.list() == null || db.list().length == 0) {
+                marshDir.delete();
+                db.delete();
                 U.delete(snpDir);
+            }
         }
-        catch (IOException | IgniteCheckedException e) {
+        catch (IOException e) {
             throw new IgniteException(e);
         }
     }
@@ -522,11 +536,18 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
 
         SnapshotOperationRequest snpReq = clusterSnpReq;
 
+        boolean cancelled = err.values().stream().anyMatch(e -> e instanceof IgniteFutureCancelledCheckedException);
+
         if (snpReq == null || !snpReq.rqId.equals(id)) {
             synchronized (snpOpMux) {
                 if (clusterSnpFut != null && clusterSnpFut.rqId.equals(id)) {
-                    clusterSnpFut.onDone(new IgniteCheckedException("Snapshot operation has not been fully completed " +
-                        "[err=" + err + ", snpReq=" + snpReq + ']'));
+                    if (cancelled) {
+                        clusterSnpFut.onDone(new IgniteFutureCancelledCheckedException("Execution of snapshot tasks " +
+                            "has been cancelled by external process [err=" + err + ", snpReq=" + snpReq + ']'));
+                    } else {
+                        clusterSnpFut.onDone(new IgniteCheckedException("Snapshot operation has not been fully completed " +
+                            "[err=" + err + ", snpReq=" + snpReq + ']'));
+                    }
 
                     clusterSnpFut = null;
                 }
@@ -540,10 +561,12 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
             missed.removeAll(res.keySet());
             missed.removeAll(err.keySet());
 
-            snpReq.hasErr = !F.isEmpty(err) || !missed.isEmpty();
-
-            if (snpReq.hasErr) {
-                U.warn(log, "Execution of local snapshot tasks fails or them haven't been executed " +
+            if (cancelled) {
+                snpReq.err = new IgniteFutureCancelledCheckedException("Execution of snapshot tasks " +
+                    "has been cancelled by external process [err=" + err + ", missed=" + missed + ']');
+            }
+            else if (!F.isEmpty(err) || !missed.isEmpty()) {
+                snpReq.err = new IgniteCheckedException("Execution of local snapshot tasks fails or them haven't been executed " +
                     "due to some of nodes left the cluster. Uncompleted snapshot will be deleted " +
                     "[err=" + err + ", missed=" + missed + ']');
             }
@@ -561,7 +584,7 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
             return new GridFinishedFuture<>(new SnapshotOperationResponse());
 
         try {
-            if (req.hasErr)
+            if (req.err != null)
                 deleteSnapshot(snapshotLocalDir(req.snpName), pdsSettings.folderName());
 
             removeLastMetaStorageKey();
@@ -591,17 +614,19 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
 
         synchronized (snpOpMux) {
             if (clusterSnpFut != null) {
-                if (endFail.isEmpty() && !snpReq.hasErr) {
+                if (endFail.isEmpty() && snpReq.err == null) {
                     clusterSnpFut.onDone();
 
                     if (log.isInfoEnabled())
                         log.info("Cluster-wide snapshot operation finished successfully [req=" + snpReq + ']');
                 }
-                else {
+                else if (snpReq.err == null) {
                     clusterSnpFut.onDone(new IgniteCheckedException("Snapshot creation has been finished with an error. " +
                         "Local snapshot tasks may not finished completely or finalizing results fails " +
-                        "[hasErr=" + snpReq.hasErr + ", fail=" + endFail + ", err=" + err + ']'));
+                        "[fail=" + endFail + ", err=" + err + ']'));
                 }
+                else
+                    clusterSnpFut.onDone(snpReq.err);
 
                 clusterSnpFut = null;
             }
@@ -635,6 +660,66 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
     }
 
     /** {@inheritDoc} */
+    @Override public IgniteFuture<Void> cancelSnapshot(String name) {
+        A.notNullOrEmpty(name, "Snapshot name must be not empty or null");
+
+        IgniteInternalFuture<Void> fut0 = cctx.kernalContext().closure()
+            .broadcast(new CancelSnapshotClosure(),
+                name,
+                cctx.discovery().aliveServerNodes(),
+                null)
+            .chain(new CX1<IgniteInternalFuture<Collection<Void>>, Void>() {
+                @Override public Void applyx(IgniteInternalFuture<Collection<Void>> f) throws IgniteCheckedException {
+                    f.get();
+
+                    return null;
+                }
+            });
+
+        return new IgniteFutureImpl<>(fut0);
+    }
+
+    /**
+     * @param name Snapshot name to cancel operation on local node.
+     */
+    public void cancelLocalSnapshotTask(String name) {
+        A.notNullOrEmpty(name, "Snapshot name must be not null or empty");
+
+        ClusterSnapshotFuture fut0 = null;
+
+        busyLock.enterBusy();
+
+        try {
+            for (SnapshotFutureTask sctx : locSnpTasks.values()) {
+                if (sctx.snapshotName().equals(name))
+                    sctx.cancel();
+            }
+
+            synchronized (snpOpMux) {
+                if (clusterSnpFut != null)
+                    fut0 = clusterSnpFut;
+            }
+        }
+        finally {
+            busyLock.leaveBusy();
+        }
+
+        // Future may be completed with cancelled exception, which is expected.
+        try {
+            if (fut0 != null)
+                fut0.get();
+        }
+        catch (IgniteCheckedException e) {
+            if (e instanceof IgniteFutureCancelledCheckedException) {
+                if (log.isInfoEnabled())
+                    log.info("Expected cancelled exception: " + e.getMessage());
+            }
+            else
+                throw new IgniteException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
     @Override public IgniteFuture<Void> createSnapshot(String name) {
         A.notNullOrEmpty(name, "Snapshot name cannot be null or empty.");
         A.ensure(U.alphanumericUnderscore(name), "Snapshot name must satisfy the following name pattern: a-zA-Z0-9_");
@@ -1105,7 +1190,8 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
             else {
                 deleteSnapshot(snpLocDir, pdsSettings.folderName());
 
-                U.warn(log, "Local snapshot sender closed due to an error occurred", th);
+                if (log.isDebugEnabled())
+                    log.debug("Local snapshot sender closed due to an error occurred: " + th.getMessage());
             }
         }
     }
@@ -1132,8 +1218,8 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
         @GridToStringInclude
         private final Set<UUID> bltNodes;
 
-        /** {@code true} if an execution of local snapshot tasks failed with an error. */
-        private volatile boolean hasErr;
+        /** Exception occurred during snapshot operation processing. */
+        private volatile IgniteCheckedException err;
 
         /**
          * @param snpName Snapshot name.
@@ -1266,6 +1352,24 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
         }
     }
 
+    /** Cancel snapshot operation closure. */
+    @GridInternal
+    private static class CancelSnapshotClosure implements IgniteClosure<String, Void> {
+        /** Serial version uid. */
+        private static final long serialVersionUID = 0L;
+
+        /** Auto-injected grid instance. */
+        @IgniteInstanceResource
+        private transient IgniteEx ignite;
+
+        /** {@inheritDoc} */
+        @Override public Void apply(String snpName) {
+            ignite.context().cache().context().snapshotMgr().cancelLocalSnapshotTask(snpName);
+
+            return null;
+        }
+    }
+
     /** Wrapper of internal checked exceptions. */
     private static class IgniteSnapshotFutureImpl extends IgniteFutureImpl<Void> {
         /** @param fut Internal future. */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotFutureTask.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotFutureTask.java
index 93270e2..04c1962 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotFutureTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotFutureTask.java
@@ -49,6 +49,7 @@ import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteLogger;
 import org.apache.ignite.binary.BinaryType;
 import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.internal.IgniteFutureCancelledCheckedException;
 import org.apache.ignite.internal.pagemem.PageIdUtils;
 import org.apache.ignite.internal.pagemem.store.PageStore;
 import org.apache.ignite.internal.pagemem.store.PageWriteListener;
@@ -262,7 +263,8 @@ class SnapshotFutureTask extends GridFutureAdapter<Boolean> implements DbCheckpo
 
         startedFut.onDone(th);
 
-        U.warn(log, "Snapshot task has accepted exception to stop: " + th);
+        if (!(th instanceof IgniteFutureCancelledCheckedException))
+            U.error(log, "Snapshot task has accepted exception to stop", th);
     }
 
     /** {@inheritDoc} */
@@ -627,8 +629,8 @@ class SnapshotFutureTask extends GridFutureAdapter<Boolean> implements DbCheckpo
 
     /** {@inheritDoc} */
     @Override public boolean cancel() {
-        acceptException(new IgniteCheckedException("Snapshot operation has been cancelled by external process " +
-            "[snpName=" + snpName + ']'));
+        acceptException(new IgniteFutureCancelledCheckedException("Snapshot operation has been cancelled " +
+            "by external process [snpName=" + snpName + ']'));
 
         try {
             closeAsync().get();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMXBeanImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMXBeanImpl.java
index c6b101a..9dc1361 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMXBeanImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMXBeanImpl.java
@@ -42,4 +42,9 @@ public class SnapshotMXBeanImpl implements SnapshotMXBean {
         if (fut.isDone())
             fut.get();
     }
+
+    /** {@inheritDoc} */
+    @Override public void cancelSnapshot(String snpName) {
+        mgr.cancelSnapshot(snpName).get();
+    }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCancelTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCancelTask.java
new file mode 100644
index 0000000..b894e49
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCancelTask.java
@@ -0,0 +1,62 @@
+/*
+ * 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.visor.snapshot;
+
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteSnapshot;
+import org.apache.ignite.internal.commandline.snapshot.SnapshotCommand;
+import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotMXBeanImpl;
+import org.apache.ignite.internal.processors.task.GridInternal;
+import org.apache.ignite.internal.visor.VisorJob;
+import org.apache.ignite.internal.visor.VisorOneNodeTask;
+
+/**
+ * @see SnapshotCommand
+ * @see IgniteSnapshot#cancelSnapshot(String)
+ */
+@GridInternal
+public class VisorSnapshotCancelTask extends VisorOneNodeTask<String, String> {
+    /** Serial version uid. */
+    private static final long serialVersionUID = 0L;
+
+    /** {@inheritDoc} */
+    @Override protected VisorJob<String, String> job(String arg) {
+        return new VisorSnapshotCancelJob(arg, debug);
+    }
+
+    /** */
+    private static class VisorSnapshotCancelJob extends VisorJob<String, String> {
+        /** Serial version uid. */
+        private static final long serialVersionUID = 0L;
+
+        /**
+         * @param name Snapshot name.
+         * @param debug Flag indicating whether debug information should be printed into node log.
+         */
+        protected VisorSnapshotCancelJob(String name, boolean debug) {
+            super(name, debug);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected String run(String name) throws IgniteException {
+            new SnapshotMXBeanImpl(ignite.context()).cancelSnapshot(name);
+
+            return "Snapshot operation cancelled.";
+        }
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/mxbean/SnapshotMXBean.java b/modules/core/src/main/java/org/apache/ignite/mxbean/SnapshotMXBean.java
index 22816d2..e6b42583 100644
--- a/modules/core/src/main/java/org/apache/ignite/mxbean/SnapshotMXBean.java
+++ b/modules/core/src/main/java/org/apache/ignite/mxbean/SnapshotMXBean.java
@@ -27,9 +27,17 @@ public interface SnapshotMXBean {
     /**
      * Create the cluster-wide snapshot with given name asynchronously.
      *
-     * @param snpName Snapshot name to created.
+     * @param snpName Snapshot name to be created.
      * @see IgniteSnapshot#createSnapshot(String) (String)
      */
     @MXBeanDescription("Create cluster-wide snapshot.")
     public void createSnapshot(@MXBeanParameter(name = "snpName", description = "Snapshot name.") String snpName);
+
+    /**
+     * Cancel previously started snapshot operation on the node initiator.
+     *
+     * @param snpName Snapshot name to cancel.
+     */
+    @MXBeanDescription("Cancel started cluster-wide snapshot on the node initiator.")
+    public void cancelSnapshot(@MXBeanParameter(name = "snpName", description = "Snapshot name.") String snpName);
 }
diff --git a/modules/core/src/main/resources/META-INF/classnames.properties b/modules/core/src/main/resources/META-INF/classnames.properties
index 90120c0..486b660 100644
--- a/modules/core/src/main/resources/META-INF/classnames.properties
+++ b/modules/core/src/main/resources/META-INF/classnames.properties
@@ -2338,6 +2338,8 @@ org.apache.ignite.internal.visor.service.VisorServiceTask
 org.apache.ignite.internal.visor.service.VisorServiceTask$VisorServiceJob
 org.apache.ignite.internal.visor.snapshot.VisorSnapshotCreateTask
 org.apache.ignite.internal.visor.snapshot.VisorSnapshotCreateTask$VisorSnapshotCreateJob
+org.apache.ignite.internal.visor.snapshot.VisorSnapshotCancelTask
+org.apache.ignite.internal.visor.snapshot.VisorSnapshotCancelTask$VisorSnapshotCancelJob
 org.apache.ignite.internal.visor.tx.FetchNearXidVersionTask
 org.apache.ignite.internal.visor.tx.FetchNearXidVersionTask$FetchNearXidVersionJob
 org.apache.ignite.internal.visor.tx.TxKeyLockType
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java
index 5d49f1b..44e8e78 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java
@@ -25,6 +25,7 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -34,6 +35,7 @@ import java.util.Optional;
 import java.util.Queue;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
@@ -63,8 +65,11 @@ import org.apache.ignite.internal.processors.cache.persistence.partstate.GroupPa
 import org.apache.ignite.internal.processors.cache.persistence.wal.crc.FastCrc;
 import org.apache.ignite.internal.processors.marshaller.MappedName;
 import org.apache.ignite.internal.util.typedef.G;
+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.IgniteFuture;
+import org.apache.ignite.lang.IgniteFutureCancelledException;
 import org.apache.ignite.lang.IgnitePredicate;
 import org.apache.ignite.spi.discovery.DiscoverySpiCustomMessage;
 import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
@@ -77,11 +82,12 @@ import org.junit.Before;
 import static java.nio.file.Files.newDirectoryStream;
 import static org.apache.ignite.cluster.ClusterState.ACTIVE;
 import static org.apache.ignite.cluster.ClusterState.INACTIVE;
-import static org.apache.ignite.configuration.IgniteConfiguration.DFLT_SNAPSHOT_DIRECTORY;
 import static org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.FILE_SUFFIX;
 import static org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.PART_FILE_PREFIX;
 import static org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.DFLT_SNAPSHOT_TMP_DIR;
 import static org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.resolveSnapshotWorkDirectory;
+import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause;
+import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
 
 /**
  * Base snapshot tests.
@@ -120,13 +126,6 @@ public abstract class AbstractSnapshotSelfTest extends GridCommonAbstractTest {
             .setDiscoverySpi(discoSpi);
     }
 
-    /** {@inheritDoc} */
-    @Override protected void cleanPersistenceDir() throws Exception {
-        super.cleanPersistenceDir();
-
-        U.delete(U.resolveWorkDirectory(U.defaultWorkDirectory(), DFLT_SNAPSHOT_DIRECTORY, false));
-    }
-
     /** @throws Exception If fails. */
     @Before
     public void beforeTestSnapshot() throws Exception {
@@ -358,6 +357,65 @@ public abstract class AbstractSnapshotSelfTest extends GridCommonAbstractTest {
     }
 
     /**
+     * @param grids Grids to block snapshot executors.
+     * @return Wrapped snapshot executor list.
+     */
+    protected static List<BlockingExecutor> setBlockingSnapshotExecutor(List<? extends Ignite> grids) {
+        List<BlockingExecutor> execs = new ArrayList<>();
+
+        for (Ignite grid : grids) {
+            IgniteSnapshotManager mgr = snp((IgniteEx)grid);
+            Function<String, SnapshotSender> old = mgr.localSnapshotSenderFactory();
+
+            BlockingExecutor block = new BlockingExecutor(mgr.snapshotExecutorService());
+            execs.add(block);
+
+            mgr.localSnapshotSenderFactory((snpName) ->
+                new DelegateSnapshotSender(log, block, old.apply(snpName)));
+        }
+
+        return execs;
+    }
+
+    /**
+     * @param startCli Client node to start snapshot.
+     * @param srvs Server nodes.
+     * @param cache Persisted cache.
+     * @param snpCanceller Snapshot cancel closure.
+     */
+    public static void doSnapshotCancellationTest(
+        IgniteEx startCli,
+        List<IgniteEx> srvs,
+        IgniteCache<?, ?> cache,
+        Consumer<String> snpCanceller
+    ) {
+        IgniteEx srv = srvs.get(0);
+
+        CacheConfiguration<?, ?> ccfg = cache.getConfiguration(CacheConfiguration.class);
+
+        assertTrue(CU.isPersistenceEnabled(srv.configuration()));
+        assertTrue(CU.isPersistentCache(ccfg, srv.configuration().getDataStorageConfiguration()));
+
+        File snpDir = resolveSnapshotWorkDirectory(srv.configuration());
+
+        List<BlockingExecutor> execs = setBlockingSnapshotExecutor(srvs);
+
+        IgniteFuture<Void> fut = startCli.snapshot().createSnapshot(SNAPSHOT_NAME);
+
+        for (BlockingExecutor exec : execs)
+            exec.waitForBlocked(30_000L);
+
+        snpCanceller.accept(SNAPSHOT_NAME);
+
+        assertThrowsAnyCause(log,
+            fut::get,
+            IgniteFutureCancelledException.class,
+            "Execution of snapshot tasks has been cancelled by external process");
+
+        assertEquals("Snapshot directory must be empty due to snapshot cancelled", 0, snpDir.list().length);
+    }
+
+    /**
      * @param ignite Ignite instance to resolve discovery spi to.
      * @return BlockingCustomMessageDiscoverySpi instance.
      */
@@ -538,6 +596,16 @@ public abstract class AbstractSnapshotSelfTest extends GridCommonAbstractTest {
                 delegate.execute(cmd);
         }
 
+        /** @param timeout Timeout in milliseconds. */
+        public void waitForBlocked(long timeout) {
+            try {
+                assertTrue(waitForCondition(() -> !tasks.isEmpty(), timeout));
+            }
+            catch (IgniteInterruptedCheckedException e) {
+                throw new IgniteException(e);
+            }
+        }
+
         /** Unblock and schedule tasks for execution. */
         public void unblock() {
             block = false;
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java
index f4c7fc7..34d0762 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java
@@ -100,7 +100,7 @@ public class IgniteClusterSnapshotSelfTest extends AbstractSnapshotSelfTest {
     private static final long REBALANCE_AWAIT_TIME = GridTestUtils.SF.applyLB(10_000, 3_000);
 
     /** Cache configuration for test. */
-    private static CacheConfiguration<Integer, Integer> atomicCcfg = new CacheConfiguration<Integer, Integer>("atomicCacheName")
+    private static final CacheConfiguration<Integer, Integer> atomicCcfg = new CacheConfiguration<Integer, Integer>("atomicCacheName")
         .setAtomicityMode(CacheAtomicityMode.ATOMIC)
         .setBackups(2)
         .setAffinity(new RendezvousAffinityFunction(false, CACHE_PARTS_COUNT));
@@ -578,7 +578,7 @@ public class IgniteClusterSnapshotSelfTest extends AbstractSnapshotSelfTest {
         assertThrowsAnyCause(log,
             fut::get,
             IgniteCheckedException.class,
-            "Snapshot creation has been finished with an error");
+            "Execution of local snapshot tasks fails");
 
         assertTrue("Snapshot directory must be empty for node 0 due to snapshot future fail: " + dirNameIgnite0,
             !searchDirectoryRecursively(locSnpDir.toPath(), dirNameIgnite0).isPresent());
@@ -630,7 +630,7 @@ public class IgniteClusterSnapshotSelfTest extends AbstractSnapshotSelfTest {
         assertThrowsAnyCause(log,
             () -> ignite.snapshot().createSnapshot(SNAPSHOT_NAME).get(),
             IgniteCheckedException.class,
-            "Snapshot creation has been finished with an error");
+            "Execution of local snapshot tasks fails");
 
         assertTrue("Snapshot directory must be empty: " + grid0Dir,
             !searchDirectoryRecursively(locSnpDir.toPath(), grid0Dir).isPresent());
@@ -1101,6 +1101,32 @@ public class IgniteClusterSnapshotSelfTest extends AbstractSnapshotSelfTest {
             "Client disconnected. Snapshot result is unknown");
     }
 
+    /** @throws Exception If fails. */
+    @Test
+    public void testClusterSnapshotInProgressCancelled() throws Exception {
+        IgniteEx srv = startGridsWithCache(1, dfltCacheCfg, CACHE_KEYS_RANGE);
+        IgniteEx startCli = startClientGrid(1);
+        IgniteEx killCli = startClientGrid(2);
+
+        doSnapshotCancellationTest(startCli, Collections.singletonList(srv), srv.cache(dfltCacheCfg.getName()),
+            snpName -> killCli.snapshot().cancelSnapshot(snpName).get());
+    }
+
+    /** @throws Exception If fails. */
+    @Test
+    public void testClusterSnapshotFinishedTryCancel() throws Exception {
+        IgniteEx ignite = startGridsWithCache(2, dfltCacheCfg, CACHE_KEYS_RANGE);
+
+        ignite.snapshot().createSnapshot(SNAPSHOT_NAME).get();
+        ignite.snapshot().cancelSnapshot(SNAPSHOT_NAME).get();
+
+        stopAllGrids();
+
+        IgniteEx snpIg = startGridsFromSnapshot(2, SNAPSHOT_NAME);
+
+        assertSnapshotCacheKeys(snpIg.cache(dfltCacheCfg.getName()));
+    }
+
     /**
      * @param ignite Ignite instance.
      * @param started Latch will be released when delta partition processing starts.
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotMXBeanTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotMXBeanTest.java
index 0aaa789..c662a5a 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotMXBeanTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotMXBeanTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.processors.cache.persistence.snapshot;
 
+import java.util.Collections;
 import javax.management.AttributeNotFoundException;
 import javax.management.DynamicMBean;
 import javax.management.MBeanException;
@@ -64,6 +65,22 @@ public class IgniteSnapshotMXBeanTest extends AbstractSnapshotSelfTest {
         assertSnapshotCacheKeys(snp.cache(dfltCacheCfg.getName()));
     }
 
+    /** @throws Exception If fails. */
+    @Test
+    public void testCancelSnapshot() throws Exception {
+        IgniteEx srv = startGridsWithCache(1, dfltCacheCfg, CACHE_KEYS_RANGE);
+        IgniteEx startCli = startClientGrid(1);
+        IgniteEx killCli = startClientGrid(2);
+
+        SnapshotMXBean mxBean = getMxBean(killCli.name(), "Snapshot", SnapshotMXBeanImpl.class,
+            SnapshotMXBean.class);
+
+        doSnapshotCancellationTest(startCli,
+            Collections.singletonList(srv),
+            srv.cache(dfltCacheCfg.getName()),
+            mxBean::cancelSnapshot);
+    }
+
     /**
 
      * @param mBean Ignite snapshot MBean.
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManagerSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManagerSelfTest.java
index b957d48..6e29aa9 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManagerSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManagerSelfTest.java
@@ -60,7 +60,7 @@ import org.apache.ignite.testframework.GridTestUtils;
 import org.junit.Test;
 
 import static org.apache.ignite.internal.MarshallerContextImpl.mappingFileStoreWorkDir;
-import static org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl.resolveBinaryWorkDir;
+import static org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl.binaryWorkDir;
 import static org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.cacheDirName;
 import static org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.CP_SNAPSHOT_REASON;
 import static org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.databaseRelativePath;
@@ -110,9 +110,9 @@ public class IgniteSnapshotManagerSelfTest extends AbstractSnapshotSelfTest {
         IgniteConfiguration cfg = ig.context().config();
         PdsFolderSettings settings = ig.context().pdsFolderResolver().resolveFolders();
         String nodePath = databaseRelativePath(settings.folderName());
-        File binWorkDir = resolveBinaryWorkDir(cfg.getWorkDirectory(), settings.folderName());
+        File binWorkDir = binaryWorkDir(cfg.getWorkDirectory(), settings.folderName());
         File marshWorkDir = mappingFileStoreWorkDir(U.workDirectory(cfg.getWorkDirectory(), cfg.getIgniteHome()));
-        File snpBinWorkDir = resolveBinaryWorkDir(mgr.snapshotLocalDir(SNAPSHOT_NAME).getAbsolutePath(), settings.folderName());
+        File snpBinWorkDir = binaryWorkDir(mgr.snapshotLocalDir(SNAPSHOT_NAME).getAbsolutePath(), settings.folderName());
         File snpMarshWorkDir = mappingFileStoreWorkDir(mgr.snapshotLocalDir(SNAPSHOT_NAME).getAbsolutePath());
 
         final Map<String, Integer> origPartCRCs = calculateCRC32Partitions(cacheWorkDir);
diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridCommonAbstractTest.java b/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridCommonAbstractTest.java
index 7eae72d..25e44f9 100755
--- a/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridCommonAbstractTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/testframework/junits/common/GridCommonAbstractTest.java
@@ -140,6 +140,7 @@ import static org.apache.ignite.IgniteSystemProperties.IGNITE_EVENT_DRIVEN_SERVI
 import static org.apache.ignite.IgniteSystemProperties.getBoolean;
 import static org.apache.ignite.cache.CacheMode.LOCAL;
 import static org.apache.ignite.cache.CacheRebalanceMode.NONE;
+import static org.apache.ignite.configuration.IgniteConfiguration.DFLT_SNAPSHOT_DIRECTORY;
 import static org.apache.ignite.internal.processors.cache.GridCacheUtils.isNearEnabled;
 import static org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl.BINARY_META_FOLDER;
 import static org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState.OWNING;
@@ -1929,6 +1930,7 @@ public abstract class GridCommonAbstractTest extends GridAbstractTest {
         U.delete(U.resolveWorkDirectory(U.defaultWorkDirectory(), DFLT_STORE_DIR, false));
         U.delete(U.resolveWorkDirectory(U.defaultWorkDirectory(), "marshaller", false));
         U.delete(U.resolveWorkDirectory(U.defaultWorkDirectory(), BINARY_META_FOLDER, false));
+        U.delete(U.resolveWorkDirectory(U.defaultWorkDirectory(), DFLT_SNAPSHOT_DIRECTORY, false));
     }
 
     /**
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 33215f1..998cb12 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
@@ -26,6 +26,7 @@ import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -112,7 +113,6 @@ import static org.apache.ignite.cache.PartitionLossPolicy.READ_ONLY_SAFE;
 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.configuration.IgniteConfiguration.DFLT_SNAPSHOT_DIRECTORY;
 import static org.apache.ignite.events.EventType.EVT_NODE_FAILED;
 import static org.apache.ignite.events.EventType.EVT_NODE_LEFT;
 import static org.apache.ignite.internal.commandline.CommandHandler.CONFIRM_MSG;
@@ -121,6 +121,7 @@ import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_OK
 import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_UNEXPECTED_ERROR;
 import static org.apache.ignite.internal.commandline.CommandList.DEACTIVATE;
 import static org.apache.ignite.internal.encryption.AbstractEncryptionTest.MASTER_KEY_NAME_2;
+import static org.apache.ignite.internal.processors.cache.persistence.snapshot.AbstractSnapshotSelfTest.doSnapshotCancellationTest;
 import static org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.SNAPSHOT_METRICS;
 import static org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.resolveSnapshotWorkDirectory;
 import static org.apache.ignite.internal.processors.cache.verify.IdleVerifyUtility.GRID_NOT_IDLE_MSG;
@@ -164,7 +165,6 @@ public class GridCommandHandlerTest extends GridCommandHandlerClusterPerMethodAb
         super.cleanPersistenceDir();
 
         cleanDiagnosticDir();
-        U.delete(U.resolveWorkDirectory(U.defaultWorkDirectory(), DFLT_SNAPSHOT_DIRECTORY, false));
     }
 
     /**
@@ -2148,4 +2148,20 @@ public class GridCommandHandlerTest extends GridCommandHandlerClusterPerMethodAb
 
         assertContains(log, testOut.toString(), "Snapshot operation has been rejected. The cluster is inactive.");
     }
+
+    /** @throws Exception If fails. */
+    @Test
+    public void testCancelSnapshot() throws Exception {
+        IgniteEx srv = startGrid(0);
+        IgniteEx startCli = startClientGrid(CLIENT_NODE_NAME_PREFIX);
+
+        srv.cluster().state(ACTIVE);
+
+        createCacheAndPreload(startCli, 100);
+
+        CommandHandler h = new CommandHandler();
+
+        doSnapshotCancellationTest(startCli, Collections.singletonList(srv), startCli.cache(DEFAULT_CACHE_NAME),
+            snpName -> assertEquals(EXIT_CODE_OK, execute(h, "--snapshot", "cancel", snpName)));
+    }
 }
diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output
index 2106289..6048c94 100644
--- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output
+++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output
@@ -107,12 +107,24 @@ This utility can do the following commands:
       routine_id      - Routine identifier.
       origin_node_id  - Originating node id.
 
+  Kill running snapshot by snapshot name:
+    control.(sh|bat) --kill SNAPSHOT snapshot_name
+
+    Parameters:
+      snapshot_name  - Snapshot name.
+
   Create cluster snapshot:
     control.(sh|bat) --snapshot create snapshot_name
 
     Parameters:
       snapshot_name  - Snapshot name.
 
+  Cancel running snapshot:
+    control.(sh|bat) --snapshot cancel snapshot_name
+
+    Parameters:
+      snapshot_name  - Snapshot name.
+
 By default commands affecting the cluster require interactive confirmation.
 Use --yes option to disable it.
 
diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output
index 2106289..6048c94 100644
--- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output
+++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output
@@ -107,12 +107,24 @@ This utility can do the following commands:
       routine_id      - Routine identifier.
       origin_node_id  - Originating node id.
 
+  Kill running snapshot by snapshot name:
+    control.(sh|bat) --kill SNAPSHOT snapshot_name
+
+    Parameters:
+      snapshot_name  - Snapshot name.
+
   Create cluster snapshot:
     control.(sh|bat) --snapshot create snapshot_name
 
     Parameters:
       snapshot_name  - Snapshot name.
 
+  Cancel running snapshot:
+    control.(sh|bat) --snapshot cancel snapshot_name
+
+    Parameters:
+      snapshot_name  - Snapshot name.
+
 By default commands affecting the cluster require interactive confirmation.
 Use --yes option to disable it.
 
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotWithIndexesTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotWithIndexesTest.java
index 141b8bc..4f6663d 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotWithIndexesTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotWithIndexesTest.java
@@ -17,12 +17,10 @@
 
 package org.apache.ignite.internal.processors.cache.persistence.snapshot;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
-import java.util.function.Function;
 import java.util.stream.Collectors;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteCache;
@@ -137,18 +135,7 @@ public class IgniteClusterSnapshotWithIndexesTest extends AbstractSnapshotSelfTe
             executeSql(ignite, "INSERT INTO " + tblName + " (id, name, age, city) VALUES(?, 'name', 3, 'city')", i);
 
         // Blocking configuration local snapshot sender.
-        List<BlockingExecutor> execs = new ArrayList<>();
-
-        for (Ignite grid : G.allGrids()) {
-            IgniteSnapshotManager mgr = snp((IgniteEx)grid);
-            Function<String, SnapshotSender> old = mgr.localSnapshotSenderFactory();
-
-            BlockingExecutor block = new BlockingExecutor(mgr.snapshotExecutorService());
-            execs.add(block);
-
-            mgr.localSnapshotSenderFactory((snpName) ->
-                new DelegateSnapshotSender(log, block, old.apply(snpName)));
-        }
+        List<BlockingExecutor> execs = setBlockingSnapshotExecutor(G.allGrids());
 
         IgniteFuture<Void> fut = ignite.snapshot().createSnapshot(SNAPSHOT_NAME);
 
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java
index f07f707..01d2ba6 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java
@@ -60,6 +60,7 @@ import org.apache.ignite.configuration.SqlConfiguration;
 import org.apache.ignite.configuration.TopologyValidator;
 import org.apache.ignite.internal.ClusterMetricsSnapshot;
 import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.IgniteInterruptedCheckedException;
 import org.apache.ignite.internal.IgniteNodeAttributes;
 import org.apache.ignite.internal.managers.discovery.ClusterMetricsImpl;
 import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
@@ -83,6 +84,7 @@ import org.junit.Test;
 
 import static java.util.Arrays.asList;
 import static java.util.stream.Collectors.toSet;
+import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
 import static org.junit.Assert.assertNotEquals;
 
 /**
@@ -391,21 +393,23 @@ public class SqlSystemViewsSelfTest extends AbstractIndexingCommonTest {
      * @param cacheName Cache name.
      * @param rebuild Is indexes rebuild in progress.
      */
-    private void checkIndexRebuild(String cacheName, boolean rebuild) {
+    private void checkIndexRebuild(String cacheName, boolean rebuild) throws IgniteInterruptedCheckedException {
         String idxSql = "SELECT IS_INDEX_REBUILD_IN_PROGRESS FROM " + systemSchemaName() + ".TABLES " +
             "WHERE TABLE_NAME = ?";
 
-        List<List<?>> res = execSql(grid(), idxSql, cacheName);
+        assertTrue(waitForCondition(() -> {
+            List<List<?>> res = execSql(grid(), idxSql, cacheName);
 
-        assertFalse(res.isEmpty());
+            assertFalse(res.isEmpty());
 
-        assertTrue(res.stream().allMatch(row -> {
-            assertEquals(1, row.size());
+            return res.stream().allMatch(row -> {
+                assertEquals(1, row.size());
 
-            Boolean isIndexRebuildInProgress = (Boolean)row.get(0);
+                Boolean isIndexRebuildInProgress = (Boolean)row.get(0);
 
-            return isIndexRebuildInProgress == rebuild;
-        }));
+                return isIndexRebuildInProgress == rebuild;
+            });
+        }, 5_000));
     }
 
     /**
diff --git a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsCommandShTest.java b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsCommandShTest.java
index e8650ed..895f5ae 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsCommandShTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsCommandShTest.java
@@ -28,12 +28,14 @@ import org.apache.ignite.lang.IgniteUuid;
 import org.junit.Test;
 
 import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_OK;
+import static org.apache.ignite.internal.processors.cache.persistence.snapshot.AbstractSnapshotSelfTest.doSnapshotCancellationTest;
+import static org.apache.ignite.util.KillCommandsTests.PAGES_CNT;
 import static org.apache.ignite.util.KillCommandsTests.PAGE_SZ;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelComputeTask;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelContinuousQuery;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelSQLQuery;
-import static org.apache.ignite.util.KillCommandsTests.doTestCancelTx;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelService;
+import static org.apache.ignite.util.KillCommandsTests.doTestCancelTx;
 import static org.apache.ignite.util.KillCommandsTests.doTestScanQueryCancel;
 
 /** Tests cancel of user created entities via control.sh. */
@@ -54,7 +56,9 @@ public class KillCommandsCommandShTest extends GridCommandHandlerClusterByClassA
             new CacheConfiguration<>(DEFAULT_CACHE_NAME).setIndexedTypes(Integer.class, Integer.class)
                 .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL));
 
-        for (int i = 0; i < PAGE_SZ * PAGE_SZ; i++)
+        // There must be enough cache entries to keep scan query cursor opened.
+        // Cursor may be concurrently closed when all the data retrieved.
+        for (int i = 0; i < PAGES_CNT * PAGE_SZ; i++)
             cache.put(i, i);
 
         awaitPartitionMapExchange();
@@ -127,6 +131,24 @@ public class KillCommandsCommandShTest extends GridCommandHandlerClusterByClassA
 
     /** */
     @Test
+    public void testCancelSnapshot() {
+        doSnapshotCancellationTest(client, srvs, client.cache(DEFAULT_CACHE_NAME), snpName -> {
+            int res = execute("--kill", "snapshot", snpName);
+
+            assertEquals(EXIT_CODE_OK, res);
+        });
+    }
+
+    /** */
+    @Test
+    public void testCancelUnknownSnapshot() {
+        int res = execute("--kill", "snapshot", "unknown");
+
+        assertEquals(EXIT_CODE_OK, res);
+    }
+
+    /** */
+    @Test
     public void testCancelUnknownScanQuery() {
         int res = execute("--kill", "scan", srvs.get(0).localNode().id().toString(), "unknown", "1");
 
diff --git a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsMXBeanTest.java b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsMXBeanTest.java
index a8927b4..8bb08bd 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsMXBeanTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsMXBeanTest.java
@@ -23,20 +23,27 @@ import java.util.UUID;
 import org.apache.ignite.IgniteCache;
 import org.apache.ignite.cache.CacheAtomicityMode;
 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.ComputeMXBeanImpl;
 import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.internal.QueryMXBeanImpl;
+import org.apache.ignite.internal.ServiceMXBeanImpl;
 import org.apache.ignite.internal.TransactionsMXBeanImpl;
+import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotMXBeanImpl;
 import org.apache.ignite.lang.IgniteUuid;
 import org.apache.ignite.mxbean.ComputeMXBean;
-import org.apache.ignite.internal.ServiceMXBeanImpl;
 import org.apache.ignite.mxbean.QueryMXBean;
 import org.apache.ignite.mxbean.ServiceMXBean;
+import org.apache.ignite.mxbean.SnapshotMXBean;
 import org.apache.ignite.mxbean.TransactionsMXBean;
 import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.junit.Test;
 
 import static org.apache.ignite.cluster.ClusterState.ACTIVE;
+import static org.apache.ignite.internal.processors.cache.persistence.snapshot.AbstractSnapshotSelfTest.doSnapshotCancellationTest;
+import static org.apache.ignite.util.KillCommandsTests.PAGES_CNT;
 import static org.apache.ignite.util.KillCommandsTests.PAGE_SZ;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelComputeTask;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelContinuousQuery;
@@ -71,8 +78,22 @@ public class KillCommandsMXBeanTest extends GridCommonAbstractTest {
     /** */
     private static ServiceMXBean svcMxBean;
 
+    /** Snapshot control JMX bean. */
+    private static SnapshotMXBean snpMxBean;
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+        return super.getConfiguration(igniteInstanceName)
+            .setDataStorageConfiguration(new DataStorageConfiguration()
+                .setDefaultDataRegionConfiguration(new DataRegionConfiguration()
+                    .setMaxSize(100L * 1024 * 1024)
+                    .setPersistenceEnabled(true)));
+    }
+
     /** {@inheritDoc} */
     @Override protected void beforeTestsStarted() throws Exception {
+        cleanPersistenceDir();
+
         startGridsMultiThreaded(NODES_CNT);
 
         srvs = new ArrayList<>();
@@ -89,7 +110,9 @@ public class KillCommandsMXBeanTest extends GridCommonAbstractTest {
             new CacheConfiguration<>(DEFAULT_CACHE_NAME).setIndexedTypes(Integer.class, Integer.class)
                 .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL));
 
-        for (int i = 0; i < PAGE_SZ * PAGE_SZ; i++)
+        // There must be enough cache entries to keep scan query cursor opened.
+        // Cursor may be concurrently closed when all the data retrieved.
+        for (int i = 0; i < PAGES_CNT * PAGE_SZ; i++)
             cache.put(i, i);
 
         qryMBean = getMxBean(killCli.name(), "Query",
@@ -103,6 +126,17 @@ public class KillCommandsMXBeanTest extends GridCommonAbstractTest {
 
         svcMxBean = getMxBean(killCli.name(), "Service",
             ServiceMXBeanImpl.class.getSimpleName(), ServiceMXBean.class);
+
+        snpMxBean = getMxBean(killCli.name(), "Snapshot",
+            SnapshotMXBeanImpl.class.getSimpleName(), SnapshotMXBean.class);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        super.afterTestsStopped();
+
+        stopAllGrids();
+        cleanPersistenceDir();
     }
 
     /** */
@@ -145,6 +179,19 @@ public class KillCommandsMXBeanTest extends GridCommonAbstractTest {
 
     /** */
     @Test
+    public void testCancelSnapshot() {
+        doSnapshotCancellationTest(startCli, srvs, startCli.cache(DEFAULT_CACHE_NAME),
+            snpName -> snpMxBean.cancelSnapshot(snpName));
+    }
+
+    /** */
+    @Test
+    public void testCancelUnknownSnapshot() {
+        snpMxBean.cancelSnapshot("unknown");
+    }
+
+    /** */
+    @Test
     public void testCancelUnknownScanQuery() {
         qryMBean.cancelScan(srvs.get(0).localNode().id().toString(), "unknown", 1L);
     }
diff --git a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsSQLTest.java b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsSQLTest.java
index 5606175..cc03bc0 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsSQLTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsSQLTest.java
@@ -40,6 +40,7 @@ import static org.apache.ignite.internal.sql.SqlKeyword.SCAN;
 import static org.apache.ignite.internal.sql.SqlKeyword.SERVICE;
 import static org.apache.ignite.internal.sql.SqlKeyword.TRANSACTION;
 import static org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause;
+import static org.apache.ignite.util.KillCommandsTests.PAGES_CNT;
 import static org.apache.ignite.util.KillCommandsTests.PAGE_SZ;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelComputeTask;
 import static org.apache.ignite.util.KillCommandsTests.doTestCancelContinuousQuery;
@@ -98,7 +99,9 @@ public class KillCommandsSQLTest extends GridCommonAbstractTest {
             new CacheConfiguration<>(DEFAULT_CACHE_NAME).setIndexedTypes(Integer.class, Integer.class)
                 .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL));
 
-        for (int i = 0; i < PAGE_SZ * PAGE_SZ; i++)
+        // There must be enough cache entries to keep scan query cursor opened.
+        // Cursor may be concurrently closed when all the data retrieved.
+        for (int i = 0; i < PAGES_CNT * PAGE_SZ; i++)
             cache.put(i, i);
     }
 
diff --git a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsTests.java b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsTests.java
index 520d85a..81b6abe 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsTests.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/util/KillCommandsTests.java
@@ -73,6 +73,9 @@ class KillCommandsTests {
     /** Page size. */
     public static final int PAGE_SZ = 5;
 
+    /** Number of pages to insert. */
+    public static final int PAGES_CNT = 1000;
+
     /** Operations timeout. */
     public static final int TIMEOUT = 10_000;
 
@@ -211,7 +214,8 @@ class KillCommandsTests {
     public static void doTestCancelTx(IgniteEx cli, List<IgniteEx> srvs, Consumer<String> txCanceler) {
         IgniteCache<Object, Object> cache = cli.cache(DEFAULT_CACHE_NAME);
 
-        int testKey = 42;
+        // See e.g. KillCommandsMxBeanTest
+        int testKey = (PAGES_CNT * PAGE_SZ) + 42;
 
         try (Transaction tx = cli.transactions().txStart()) {
             cache.put(testKey, 1);