You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pp...@apache.org on 2022/06/03 18:45:15 UTC

[ignite] branch master updated: IGNITE-15067 Added the ability to define a custom path for the snapshot operation (#10052)

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

ppa 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 024df082f67 IGNITE-15067 Added the ability to define a custom path for the snapshot operation (#10052)
024df082f67 is described below

commit 024df082f6747cdb92dede6b6c85270f51c0475d
Author: Pavel Pereslegin <xx...@gmail.com>
AuthorDate: Fri Jun 3 21:45:05 2022 +0300

    IGNITE-15067 Added the ability to define a custom path for the snapshot operation (#10052)
---
 docs/_docs/snapshots/snapshots.adoc                |  22 ++-
 .../commandline/snapshot/SnapshotCheckCommand.java |  45 ++++-
 ...Option.java => SnapshotCheckCommandOption.java} |  35 ++--
 .../snapshot/SnapshotCreateCommand.java            |  29 +++-
 .../snapshot/SnapshotCreateCommandOption.java      |  34 ++--
 .../snapshot/SnapshotRestoreCommand.java           |  38 ++++-
 .../snapshot/SnapshotRestoreCommandOption.java     |  39 +++--
 .../apache/ignite/util/GridCommandHandlerTest.java |  62 ++++++-
 .../snapshot/AbstractSnapshotVerificationTask.java |   6 +-
 .../snapshot/IgniteSnapshotManager.java            | 184 +++++++++++++--------
 .../snapshot/SnapshotFilesRequestMessage.java      |  38 ++++-
 .../snapshot/SnapshotHandlerContext.java           |  15 +-
 .../snapshot/SnapshotHandlerRestoreTask.java       |  24 ++-
 .../persistence/snapshot/SnapshotMXBeanImpl.java   |   8 +-
 .../snapshot/SnapshotMetadataCollectorTask.java    |   8 +-
 ....java => SnapshotMetadataCollectorTaskArg.java} |  49 +++---
 .../snapshot/SnapshotOperationRequest.java         |  13 ++
 .../snapshot/SnapshotPartitionsVerifyHandler.java  |  13 +-
 .../snapshot/SnapshotPartitionsVerifyTask.java     |  31 ++--
 .../snapshot/SnapshotPartitionsVerifyTaskArg.java  |  21 ++-
 .../snapshot/SnapshotResponseRemoteFutureTask.java |  11 +-
 .../snapshot/SnapshotRestoreProcess.java           |  28 ++--
 .../visor/snapshot/VisorSnapshotCheckTask.java     |  20 ++-
 ...TaskArg.java => VisorSnapshotCheckTaskArg.java} |  28 ++--
 .../visor/snapshot/VisorSnapshotCreateTask.java    |   3 +-
 .../visor/snapshot/VisorSnapshotCreateTaskArg.java |  24 +--
 .../visor/snapshot/VisorSnapshotRestoreTask.java   |   4 +-
 .../snapshot/VisorSnapshotRestoreTaskArg.java      |   3 +-
 .../org/apache/ignite/mxbean/SnapshotMXBean.java   |  11 +-
 .../main/resources/META-INF/classnames.properties  |   1 +
 .../snapshot/AbstractSnapshotSelfTest.java         |   6 +-
 .../snapshot/EncryptedSnapshotTest.java            |   6 +-
 .../snapshot/IgniteClusterSnapshotCheckTest.java   |  28 ++--
 .../snapshot/IgniteClusterSnapshotHandlerTest.java |  61 ++++++-
 .../IgniteClusterSnapshotRestoreSelfTest.java      |  51 ++++++
 .../snapshot/IgniteClusterSnapshotSelfTest.java    |  39 +++--
 .../snapshot/IgniteSnapshotMXBeanTest.java         |  12 +-
 .../snapshot/IgniteSnapshotManagerSelfTest.java    |   8 +-
 .../snapshot/IgniteSnapshotRemoteRequestTest.java  |  10 +-
 .../IgniteSnapshotRestoreFromRemoteTest.java       |   2 +-
 .../persistence/snapshot/PlainSnapshotTest.java    |   2 +-
 ...ridCommandHandlerClusterByClassTest_help.output |  17 +-
 ...andHandlerClusterByClassWithSSLTest_help.output |  17 +-
 .../IgniteClusterSnapshotCheckWithIndexesTest.java |   6 +-
 44 files changed, 788 insertions(+), 324 deletions(-)

diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc
index 4764fc20f2b..b3f03cdc766 100644
--- a/docs/_docs/snapshots/snapshots.adoc
+++ b/docs/_docs/snapshots/snapshots.adoc
@@ -105,17 +105,20 @@ Ignite ships the link:tools/control-script[Control Script] that supports snapsho
 
 [source,shell]
 ----
-#Create a cluster snapshot in the background:
-control.(sh|bat) --snapshot create snapshot_name
+# Create a cluster snapshot named "snapshot_09062021" in the background:
+control.(sh|bat) --snapshot create snapshot_09062021
 
-#Create a cluster snapshot (wait for the entire operation to complete):
-control.(sh|bat) --snapshot create snapshot_name --sync
+# Create a cluster snapshot named "snapshot_09062021" and wait for the entire operation to complete:
+control.(sh|bat) --snapshot create snapshot_09062021 --sync
 
-#Cancel a running snapshot:
-control.(sh|bat) --snapshot cancel snapshot_name
+# Create a cluster snapshot named "snapshot_09062021" in the "/tmp/ignite/snapshots" folder (the full path to the snapshot files will be /tmp/ignite/snapshots/snapshot_09062021):
+control.(sh|bat) --snapshot create snapshot_09062021 -dest /tmp/ignite/snapshots
 
-#Kill a running snapshot:
-control.(sh|bat) --kill SNAPSHOT snapshot_name
+# Cancel a running snapshot named "snapshot_09062021":
+control.(sh|bat) --snapshot cancel snapshot_09062021
+
+# Kill a running snapshot named "snapshot_09062021":
+control.(sh|bat) --kill SNAPSHOT snapshot_09062021
 ----
 
 === Using JMX
@@ -225,6 +228,9 @@ control.(sh|bat) --snapshot restore snapshot_09062021 --start
 # Start restoring all user-created cache groups from the snapshot "snapshot_09062021" and wait for the entire operation to complete.
 control.(sh|bat) --snapshot restore snapshot_09062021 --start --sync
 
+# Start restoring all user-created cache groups from the snapshot "snapshot_09062021" located in the "/tmp/ignite/snapshots" folder (the full path to the snapshot files should be /tmp/ignite/snapshots/snapshot_09062021):
+control.(sh|bat) --snapshot restore snapshot_09062021 --src /tmp/ignite/snapshots
+
 # Start restoring only "cache-group1" and "cache-group2" from the snapshot "snapshot_09062021" in the background.
 control.(sh|bat) --snapshot restore snapshot_09062021 --start --groups cache-group1,cache-group2
 
diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCheckCommand.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCheckCommand.java
index 9bbf9ea76f1..d298c87a6f6 100644
--- a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCheckCommand.java
+++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCheckCommand.java
@@ -17,11 +17,19 @@
 
 package org.apache.ignite.internal.commandline.snapshot;
 
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.logging.Logger;
+import org.apache.ignite.internal.commandline.CommandArgIterator;
+import org.apache.ignite.internal.commandline.argument.CommandArgUtils;
 import org.apache.ignite.internal.processors.cache.verify.IdleVerifyResultV2;
+import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.visor.snapshot.VisorSnapshotCheckTask;
+import org.apache.ignite.internal.visor.snapshot.VisorSnapshotCheckTaskArg;
 
 import static org.apache.ignite.internal.commandline.CommandList.SNAPSHOT;
+import static org.apache.ignite.internal.commandline.CommandLogger.optional;
+import static org.apache.ignite.internal.commandline.snapshot.SnapshotCheckCommandOption.SOURCE;
 
 /**
  * Sub-command to check snapshot.
@@ -32,9 +40,44 @@ public class SnapshotCheckCommand extends SnapshotSubcommand {
         super("check", VisorSnapshotCheckTask.class);
     }
 
+    /** {@inheritDoc} */
+    @Override public void parseArguments(CommandArgIterator argIter) {
+        String snpName = argIter.nextArg("Expected snapshot name.");
+        String snpPath = null;
+
+        while (argIter.hasNextSubArg()) {
+            String arg = argIter.nextArg(null);
+
+            SnapshotCheckCommandOption option = CommandArgUtils.of(arg, SnapshotCheckCommandOption.class);
+
+            if (option == null) {
+                throw new IllegalArgumentException("Invalid argument: " + arg + ". " +
+                    "Possible options: " + F.concat(F.asList(SnapshotCheckCommandOption.values()), ", ") + '.');
+            }
+            else if (option == SOURCE) {
+                if (snpPath != null)
+                    throw new IllegalArgumentException(SOURCE.argName() + " arg specified twice.");
+
+                String errMsg = "Expected path to the snapshot directory.";
+
+                if (CommandArgIterator.isCommandOrOption(argIter.peekNextArg()))
+                    throw new IllegalArgumentException(errMsg);
+
+                snpPath = argIter.nextArg(errMsg);
+            }
+        }
+
+        cmdArg = new VisorSnapshotCheckTaskArg(snpName, snpPath);
+    }
+
     /** {@inheritDoc} */
     @Override public void printUsage(Logger log) {
-        usage(log, "Check snapshot:", SNAPSHOT, generalUsageOptions(), name(), SNAPSHOT_NAME_ARG);
+        Map<String, String> params = new LinkedHashMap<>(generalUsageOptions());
+
+        params.put(SOURCE.argName() + " " + SOURCE.arg(), SOURCE.description());
+
+        usage(log, "Check snapshot:", SNAPSHOT, params, name(), SNAPSHOT_NAME_ARG,
+            optional(SOURCE.argName(), SOURCE.arg()));
     }
 
     /** {@inheritDoc} */
diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCreateCommandOption.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCheckCommandOption.java
similarity index 65%
copy from modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCreateCommandOption.java
copy to modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCheckCommandOption.java
index 4647a4829cb..c3847ea4e49 100644
--- a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCreateCommandOption.java
+++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCheckCommandOption.java
@@ -20,39 +20,44 @@ package org.apache.ignite.internal.commandline.snapshot;
 import org.apache.ignite.internal.commandline.argument.CommandArg;
 
 /**
- * Snapshot create command options.
+ * Snapshot check command options.
  */
-public enum SnapshotCreateCommandOption implements CommandArg {
-    /** Synchronous execution flag. */
-    SYNC("sync", "Run the operation synchronously, the command will wait for the entire operation to complete. " +
-        "Otherwise, it will be performed in the background, and the command will immediately return control.");
+public enum SnapshotCheckCommandOption implements CommandArg {
+    /** Snapshot directory location. */
+    SOURCE("--src", "path", "Path to the directory where the snapshot files are located. If not specified, " +
+        "the default configured snapshot directory will be used.");
 
-    /** Option name. */
+    /** Name. */
     private final String name;
 
-    /** Option description. */
+    /** Argument. */
+    private final String arg;
+
+    /** Description. */
     private final String desc;
 
     /**
-     * @param name Option name.
-     * @param desc Option description.
+     * @param name Name.
+     * @param arg Argument.
+     * @param desc Description.
      */
-    SnapshotCreateCommandOption(String name, String desc) {
+    SnapshotCheckCommandOption(String name, String arg, String desc) {
         this.name = name;
+        this.arg = arg;
         this.desc = desc;
     }
 
     /** {@inheritDoc} */
     @Override public String argName() {
-        return "--" + name;
+        return name;
     }
 
-    /** @return Option name. */
-    public String optionName() {
-        return name;
+    /** @return Argument. */
+    public String arg() {
+        return arg;
     }
 
-    /** @return Option description. */
+    /** @return Description. */
     public String description() {
         return desc;
     }
diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCreateCommand.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCreateCommand.java
index 6c2e10a8336..7582bd3fcfb 100644
--- a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCreateCommand.java
+++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCreateCommand.java
@@ -28,6 +28,7 @@ import org.apache.ignite.internal.visor.snapshot.VisorSnapshotCreateTaskArg;
 
 import static org.apache.ignite.internal.commandline.CommandList.SNAPSHOT;
 import static org.apache.ignite.internal.commandline.CommandLogger.optional;
+import static org.apache.ignite.internal.commandline.snapshot.SnapshotCreateCommandOption.DESTINATION;
 import static org.apache.ignite.internal.commandline.snapshot.SnapshotCreateCommandOption.SYNC;
 
 /**
@@ -42,6 +43,7 @@ public class SnapshotCreateCommand extends SnapshotSubcommand {
     /** {@inheritDoc} */
     @Override public void parseArguments(CommandArgIterator argIter) {
         String snpName = argIter.nextArg("Expected snapshot name.");
+        String snpPath = null;
         boolean sync = false;
 
         while (argIter.hasNextSubArg()) {
@@ -53,22 +55,37 @@ public class SnapshotCreateCommand extends SnapshotSubcommand {
                 throw new IllegalArgumentException("Invalid argument: " + arg + ". " +
                     "Possible options: " + F.concat(F.asList(SnapshotCreateCommandOption.values()), ", ") + '.');
             }
+            else if (option == DESTINATION) {
+                if (snpPath != null)
+                    throw new IllegalArgumentException(DESTINATION.argName() + " arg specified twice.");
 
-            // The snapshot create command currently supports only one optional argument.
-            assert option == SYNC;
+                String errMsg = "Expected path to the snapshot directory.";
+
+                if (CommandArgIterator.isCommandOrOption(argIter.peekNextArg()))
+                    throw new IllegalArgumentException(errMsg);
+
+                snpPath = argIter.nextArg(errMsg);
+            }
+            else if (option == SYNC) {
+                if (sync)
+                    throw new IllegalArgumentException(SYNC.argName() + " arg specified twice.");
+
+                sync = true;
+            }
 
-            sync = true;
         }
 
-        cmdArg = new VisorSnapshotCreateTaskArg(snpName, sync);
+        cmdArg = new VisorSnapshotCreateTaskArg(snpName, snpPath, sync);
     }
 
     /** {@inheritDoc} */
     @Override public void printUsage(Logger log) {
         Map<String, String> params = new LinkedHashMap<>(generalUsageOptions());
 
-        params.put(SYNC.optionName(), SYNC.description());
+        params.put(DESTINATION.argName() + " " + DESTINATION.arg(), DESTINATION.description());
+        params.put(SYNC.argName(), SYNC.description());
 
-        usage(log, "Create cluster snapshot:", SNAPSHOT, params, name(), SNAPSHOT_NAME_ARG, optional(SYNC.argName()));
+        usage(log, "Create cluster snapshot:", SNAPSHOT, params, name(), SNAPSHOT_NAME_ARG,
+            optional(DESTINATION.argName(), DESTINATION.arg()), optional(SYNC.argName()));
     }
 }
diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCreateCommandOption.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCreateCommandOption.java
index 4647a4829cb..a6d216d1781 100644
--- a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCreateCommandOption.java
+++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotCreateCommandOption.java
@@ -18,41 +18,51 @@
 package org.apache.ignite.internal.commandline.snapshot;
 
 import org.apache.ignite.internal.commandline.argument.CommandArg;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Snapshot create command options.
  */
 public enum SnapshotCreateCommandOption implements CommandArg {
     /** Synchronous execution flag. */
-    SYNC("sync", "Run the operation synchronously, the command will wait for the entire operation to complete. " +
-        "Otherwise, it will be performed in the background, and the command will immediately return control.");
+    SYNC("--sync", null, "Run the operation synchronously, the command will wait for the entire operation to complete. " +
+        "Otherwise, it will be performed in the background, and the command will immediately return control."),
 
-    /** Option name. */
+    /** Snapshot directory path. */
+    DESTINATION("--dest", "path", "Path to the directory where the snapshot will be saved. If not specified, " +
+        "the default configured snapshot directory will be used.");
+
+    /** Name. */
     private final String name;
 
-    /** Option description. */
+    /** Argument. */
+    private final String arg;
+
+    /** Description. */
     private final String desc;
 
     /**
-     * @param name Option name.
-     * @param desc Option description.
+     * @param name Name.
+     * @param arg Argument.
+     * @param desc Description.
      */
-    SnapshotCreateCommandOption(String name, String desc) {
+    SnapshotCreateCommandOption(String name, @Nullable String arg, String desc) {
         this.name = name;
+        this.arg = arg;
         this.desc = desc;
     }
 
     /** {@inheritDoc} */
     @Override public String argName() {
-        return "--" + name;
+        return name;
     }
 
-    /** @return Option name. */
-    public String optionName() {
-        return name;
+    /** @return Argument. */
+    public String arg() {
+        return arg;
     }
 
-    /** @return Option description. */
+    /** @return Description. */
     public String description() {
         return desc;
     }
diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotRestoreCommand.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotRestoreCommand.java
index e154d58357e..0fa21259a54 100644
--- a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotRestoreCommand.java
+++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotRestoreCommand.java
@@ -33,8 +33,10 @@ import org.apache.ignite.internal.visor.snapshot.VisorSnapshotRestoreTaskArg;
 import static org.apache.ignite.internal.commandline.CommandList.SNAPSHOT;
 import static org.apache.ignite.internal.commandline.CommandLogger.optional;
 import static org.apache.ignite.internal.commandline.snapshot.SnapshotRestoreCommandOption.GROUPS;
+import static org.apache.ignite.internal.commandline.snapshot.SnapshotRestoreCommandOption.SOURCE;
 import static org.apache.ignite.internal.commandline.snapshot.SnapshotRestoreCommandOption.SYNC;
 import static org.apache.ignite.internal.commandline.snapshot.SnapshotSubcommands.RESTORE;
+import static org.apache.ignite.internal.visor.snapshot.VisorSnapshotRestoreTaskAction.START;
 
 /**
  * Sub-command to restore snapshot.
@@ -58,13 +60,14 @@ public class SnapshotRestoreCommand extends SnapshotSubcommand {
     @Override public void parseArguments(CommandArgIterator argIter) {
         String snpName = argIter.nextArg("Expected snapshot name.");
         VisorSnapshotRestoreTaskAction restoreAction = parseAction(argIter);
+        String snpPath = null;
         Set<String> grpNames = null;
         boolean sync = false;
 
         while (argIter.hasNextSubArg()) {
             String arg = argIter.nextArg(null);
 
-            if (restoreAction != VisorSnapshotRestoreTaskAction.START) {
+            if (restoreAction != START) {
                 throw new IllegalArgumentException("Invalid argument: " + arg + ". " +
                     "Action \"--" + restoreAction.name().toLowerCase() + "\" does not support specified option.");
             }
@@ -75,7 +78,10 @@ public class SnapshotRestoreCommand extends SnapshotSubcommand {
                 throw new IllegalArgumentException("Invalid argument: " + arg + ". " +
                     "Possible options: " + F.concat(F.asList(SnapshotRestoreCommandOption.values()), ", ") + '.');
             }
-            else if (option == SnapshotRestoreCommandOption.GROUPS) {
+            else if (option == GROUPS) {
+                if (grpNames != null)
+                    throw new IllegalArgumentException(GROUPS.argName() + " arg specified twice.");
+
                 String argDesc = "a comma-separated list of cache group names.";
 
                 grpNames = argIter.nextStringSet(argDesc);
@@ -83,11 +89,26 @@ public class SnapshotRestoreCommand extends SnapshotSubcommand {
                 if (grpNames.isEmpty())
                     throw new IllegalArgumentException("Expected " + argDesc);
             }
-            else if (option == SYNC)
+            else if (option == SOURCE) {
+                if (snpPath != null)
+                    throw new IllegalArgumentException(SOURCE.argName() + " arg specified twice.");
+
+                String errMsg = "Expected path to the snapshot directory.";
+
+                if (CommandArgIterator.isCommandOrOption(argIter.peekNextArg()))
+                    throw new IllegalArgumentException(errMsg);
+
+                snpPath = argIter.nextArg(errMsg);
+            }
+            else if (option == SYNC) {
+                if (sync)
+                    throw new IllegalArgumentException(SYNC.argName() + " arg specified twice.");
+
                 sync = true;
+            }
         }
 
-        cmdArg = new VisorSnapshotRestoreTaskArg(snpName, sync, restoreAction, grpNames);
+        cmdArg = new VisorSnapshotRestoreTaskArg(snpName, snpPath, sync, restoreAction, grpNames);
     }
 
     /** {@inheritDoc} */
@@ -95,11 +116,12 @@ public class SnapshotRestoreCommand extends SnapshotSubcommand {
         Map<String, String> params = generalUsageOptions();
         Map<String, String> startParams = new LinkedHashMap<>(params);
 
-        startParams.put(GROUPS.optionName(), GROUPS.description());
-        startParams.put(SYNC.optionName(), SYNC.description());
+        startParams.put(GROUPS.argName() + " " + GROUPS.arg(), GROUPS.description());
+        startParams.put(SOURCE.argName() + " " + SOURCE.arg(), SOURCE.description());
+        startParams.put(SYNC.argName(), SYNC.description());
 
         usage(log, "Restore snapshot:", SNAPSHOT, startParams, RESTORE.toString(), SNAPSHOT_NAME_ARG, "--start",
-            optional(GROUPS.argName(), GROUPS.optionName()), optional(SYNC.argName()));
+            optional(GROUPS.argName(), GROUPS.arg()), optional(SOURCE.argName(), SOURCE.arg()), optional(SYNC.argName()));
         usage(log, "Snapshot restore operation status:", SNAPSHOT, params, RESTORE.toString(), SNAPSHOT_NAME_ARG, "--status");
         usage(log, "Cancel snapshot restore operation:", SNAPSHOT, params, RESTORE.toString(), SNAPSHOT_NAME_ARG, "--cancel");
     }
@@ -108,7 +130,7 @@ public class SnapshotRestoreCommand extends SnapshotSubcommand {
     @Override public String confirmationPrompt() {
         VisorSnapshotRestoreTaskArg arg = (VisorSnapshotRestoreTaskArg)cmdArg;
 
-        return arg.jobAction() != VisorSnapshotRestoreTaskAction.START || arg.groupNames() != null ? null :
+        return arg.jobAction() != START || arg.groupNames() != null ? null :
             "Warning: command will restore ALL USER-CREATED CACHE GROUPS from the snapshot " + arg.snapshotName() + '.';
     }
 
diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotRestoreCommandOption.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotRestoreCommandOption.java
index b558a6be67a..eb66ccfde01 100644
--- a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotRestoreCommandOption.java
+++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/snapshot/SnapshotRestoreCommandOption.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.commandline.snapshot;
 
 import org.apache.ignite.internal.commandline.argument.CommandArg;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Snapshot restore command options.
@@ -26,41 +27,45 @@ public enum SnapshotRestoreCommandOption implements CommandArg {
     /** Cache group names. */
     GROUPS("--groups", "group1,...groupN", "Cache group names."),
 
+    /** Snapshot directory location. */
+    SOURCE(SnapshotCheckCommandOption.SOURCE.argName(), SnapshotCheckCommandOption.SOURCE.arg(),
+        SnapshotCheckCommandOption.SOURCE.description()),
+
     /** Synchronous execution flag. */
-    SYNC(SnapshotCreateCommandOption.SYNC.argName(), SnapshotCreateCommandOption.SYNC.optionName(),
+    SYNC(SnapshotCreateCommandOption.SYNC.argName(), SnapshotCreateCommandOption.SYNC.arg(),
         SnapshotCreateCommandOption.SYNC.description());
 
-    /** Argument name. */
-    private final String argName;
+    /** Name. */
+    private final String name;
 
-    /** Option name. */
-    private final String optionName;
+    /** Argument. */
+    private final String arg;
 
-    /** Option description. */
+    /** Description. */
     private final String desc;
 
     /**
-     * @param argName Argument name.
-     * @param optionName Option name.
-     * @param desc Option description.
+     * @param name Name.
+     * @param arg Argument.
+     * @param desc Description.
      */
-    SnapshotRestoreCommandOption(String argName, String optionName, String desc) {
-        this.argName = argName;
-        this.optionName = optionName;
+    SnapshotRestoreCommandOption(String name, @Nullable String arg, String desc) {
+        this.name = name;
+        this.arg = arg;
         this.desc = desc;
     }
 
     /** {@inheritDoc} */
     @Override public String argName() {
-        return argName;
+        return name;
     }
 
-    /** @return Option name. */
-    public String optionName() {
-        return optionName;
+    /** @return Argument. */
+    public String arg() {
+        return arg;
     }
 
-    /** @return Option description. */
+    /** @return Description. */
     public String description() {
         return desc;
     }
diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java
index 1e01c88587e..1669e2251e6 100644
--- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java
+++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java
@@ -3075,7 +3075,7 @@ public class GridCommandHandlerTest extends GridCommandHandlerClusterPerMethodAb
 
         // Invalid command syntax check.
         assertEquals(EXIT_CODE_INVALID_ARGUMENTS, execute(h, "--snapshot", "create", snpName, "blah"));
-        assertContains(log, testOut.toString(), "Invalid argument: blah. Possible options: --sync.");
+        assertContains(log, testOut.toString(), "Invalid argument: blah. Possible options: --sync, --dest.");
 
         assertEquals(EXIT_CODE_INVALID_ARGUMENTS, execute(h, "--snapshot", "create", snpName, "--sync", "blah"));
         assertContains(log, testOut.toString(), "Invalid argument: blah.");
@@ -3205,7 +3205,7 @@ public class GridCommandHandlerTest extends GridCommandHandlerClusterPerMethodAb
         assertContains(log, testOut.toString(), "Invalid argument: --sync.");
 
         assertEquals(EXIT_CODE_INVALID_ARGUMENTS, execute(h, "--snapshot", "restore", snpName, "--start", "blah"));
-        assertContains(log, testOut.toString(), "Invalid argument: blah. Possible options: --groups, --sync.");
+        assertContains(log, testOut.toString(), "Invalid argument: blah. Possible options: --groups, --src, --sync.");
 
         autoConfirmation = true;
 
@@ -3269,7 +3269,8 @@ public class GridCommandHandlerTest extends GridCommandHandlerClusterPerMethodAb
         CommandHandler h = new CommandHandler();
 
         assertEquals(EXIT_CODE_INVALID_ARGUMENTS, execute(h, "--snapshot", "restore", snpName, "--start", cacheName1));
-        assertContains(log, testOut.toString(), "Invalid argument: " + cacheName1 + ". Possible options: --groups, --sync.");
+        assertContains(log, testOut.toString(),
+            "Invalid argument: " + cacheName1 + ". Possible options: --groups, --src, --sync.");
 
         // Restore single cache group.
         assertEquals(EXIT_CODE_OK, execute(h, "--snapshot", "restore", snpName, "--start", "--groups", cacheName1));
@@ -3348,6 +3349,61 @@ public class GridCommandHandlerTest extends GridCommandHandlerClusterPerMethodAb
         }
     }
 
+    /** @throws Exception If fails. */
+    @Test
+    public void testSnapshotCreateCheckAndRestoreCustomDir() throws Exception {
+        int keysCnt = 100;
+        String snpName = "snapshot_30052022";
+        File snpDir = U.resolveWorkDirectory(U.defaultWorkDirectory(), "ex_snapshots", true);
+
+        assertTrue("Target directory is not empty: " + snpDir, F.isEmpty(snpDir.list()));
+
+        try {
+            Ignite ignite = startGrids(2);
+            ignite.cluster().state(ACTIVE);
+
+            createCacheAndPreload(ignite, keysCnt);
+
+            injectTestSystemOut();
+            CommandHandler h = new CommandHandler();
+
+            assertEquals(EXIT_CODE_INVALID_ARGUMENTS, execute(h, "--snapshot", "create", snpName, "--dest", "A", "--dest", "B"));
+            assertContains(log, testOut.toString(), "--dest arg specified twice.");
+
+            assertEquals(EXIT_CODE_OK,
+                execute(h, "--snapshot", "create", snpName, "--sync", "--dest", snpDir.getAbsolutePath()));
+
+            ignite.destroyCache(DEFAULT_CACHE_NAME);
+
+            assertEquals(EXIT_CODE_INVALID_ARGUMENTS, execute(h, "--snapshot", "restore", snpName, "--start", "--sync"));
+            assertContains(log, testOut.toString(), "Snapshot does not exists [snapshot=" + snpName);
+
+            assertEquals(EXIT_CODE_INVALID_ARGUMENTS,
+                execute(h, "--snapshot", "restore", snpName, "--start", "--src", "A", "--src", "B"));
+            assertContains(log, testOut.toString(), "--src arg specified twice.");
+
+            // The check command simply prints the results of the check, it always ends with a zero exit code.
+            assertEquals(EXIT_CODE_OK, execute(h, "--snapshot", "check", snpName));
+            assertContains(log, testOut.toString(), "Snapshot does not exists [snapshot=" + snpName);
+
+            assertEquals(EXIT_CODE_OK, execute(h, "--snapshot", "check", snpName, "--src", snpDir.getAbsolutePath()));
+            assertContains(log, testOut.toString(), "The check procedure has finished, no conflicts have been found.");
+
+            assertEquals(EXIT_CODE_OK,
+                execute(h, "--snapshot", "restore", snpName, "--start", "--sync", "--src", snpDir.getAbsolutePath()));
+
+            IgniteCache<Integer, Integer> cache = ignite.cache(DEFAULT_CACHE_NAME);
+
+            assertEquals(keysCnt, cache.size());
+
+            for (int i = 0; i < keysCnt; i++)
+                assertEquals(Integer.valueOf(i), cache.get(i));
+        }
+        finally {
+            U.delete(snpDir);
+        }
+    }
+
     /** @throws Exception If fails. */
     @Test
     public void testSnapshotRestoreCancelAndStatus() throws Exception {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotVerificationTask.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotVerificationTask.java
index addf246f954..8bfcab0c7d2 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotVerificationTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotVerificationTask.java
@@ -33,6 +33,7 @@ import org.apache.ignite.compute.ComputeTaskAdapter;
 import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.resources.IgniteInstanceResource;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * The task for checking the consistency of snapshots in the cluster.
@@ -80,7 +81,7 @@ public abstract class AbstractSnapshotVerificationTask extends
                 if (meta == null)
                     continue;
 
-                jobs.put(createJob(meta.snapshotName(), meta.consistentId(), arg.cacheGroupNames()),
+                jobs.put(createJob(meta.snapshotName(), arg.snapshotPath(), meta.consistentId(), arg.cacheGroupNames()),
                     e.getKey());
 
                 if (allMetas.isEmpty())
@@ -122,9 +123,10 @@ public abstract class AbstractSnapshotVerificationTask extends
 
     /**
      * @param name Snapshot name.
+     * @param path Snapshot directory path.
      * @param constId Snapshot metadata file name.
      * @param groups Cache groups to be restored from the snapshot. May be empty if all cache groups are being restored.
      * @return Compute job.
      */
-    protected abstract ComputeJob createJob(String name, String constId, Collection<String> groups);
+    protected abstract ComputeJob createJob(String name, @Nullable String path, String constId, Collection<String> groups);
 }
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 ae9d339da6f..7647eb4b736 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
@@ -65,7 +65,6 @@ import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.BooleanSupplier;
 import java.util.function.Consumer;
-import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import org.apache.ignite.IgniteCheckedException;
@@ -259,9 +258,6 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
     /** Error message to finalize snapshot tasks. */
     public static final String SNP_NODE_STOPPING_ERR_MSG = "The operation is cancelled due to the local node is stopping";
 
-    /** Metastorage key to save currently running snapshot. */
-    public static final String SNP_RUNNING_KEY = "snapshot-running";
-
     /** Snapshot metrics prefix. */
     public static final String SNAPSHOT_METRICS = "snapshot";
 
@@ -280,6 +276,13 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
     /** Maximum block size for limited snapshot transfer (64KB by default). */
     public static final int SNAPSHOT_LIMITED_TRANSFER_BLOCK_SIZE_BYTES = 64 * 1024;
 
+    /** Metastorage key to save currently running snapshot directory path. */
+    private static final String SNP_RUNNING_DIR_KEY = "snapshot-running-dir";
+
+    /** @deprecated Use #SNP_RUNNING_DIR_KEY instead. */
+    @Deprecated
+    private static final String SNP_RUNNING_KEY = "snapshot-running";
+
     /** Snapshot operation finish log message. */
     private static final String SNAPSHOT_FINISHED_MSG = "Cluster-wide snapshot operation finished successfully: ";
 
@@ -346,7 +349,7 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
     private volatile ReadWriteMetastorage metaStorage;
 
     /** Local snapshot sender factory. */
-    private Function<String, SnapshotSender> locSndrFactory = LocalSnapshotSender::new;
+    private BiFunction<String, String, SnapshotSender> locSndrFactory = LocalSnapshotSender::new;
 
     /** Remote snapshot sender factory. */
     private BiFunction<String, UUID, SnapshotSender> rmtSndrFactory = this::remoteSnapshotSenderFactory;
@@ -569,7 +572,7 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
             SNAPSHOT_SYS_VIEW,
             SNAPSHOT_SYS_VIEW_DESC,
             new SnapshotViewWalker(),
-            () -> F.flatCollections(F.transform(localSnapshotNames(), this::readSnapshotMetadatas)),
+            () -> F.flatCollections(F.transform(localSnapshotNames(), name -> readSnapshotMetadatas(name, null))),
             this::snapshotViewSupplier);
     }
 
@@ -686,10 +689,19 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
      * @return Local snapshot directory for snapshot with given name.
      */
     public File snapshotLocalDir(String snpName) {
+        return snapshotLocalDir(snpName, null);
+    }
+
+    /**
+     * @param snpName Snapshot name.
+     * @param snpPath Snapshot directory path.
+     * @return Local snapshot directory where snapshot files are located.
+     */
+    public File snapshotLocalDir(String snpName, @Nullable String snpPath) {
         assert locSnpDir != null;
         assert U.alphanumericUnderscore(snpName) : snpName;
 
-        return new File(locSnpDir, snpName);
+        return snpPath == null ? new File(locSnpDir, snpName) : new File(snpPath, snpName);
     }
 
     /**
@@ -697,8 +709,8 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
      * @return Local snapshot directory for snapshot with given name.
      * @throws IgniteCheckedException If directory doesn't exist.
      */
-    private File resolveSnapshotDir(String snpName) throws IgniteCheckedException {
-        File snpDir = snapshotLocalDir(snpName);
+    private File resolveSnapshotDir(String snpName, @Nullable String snpPath) throws IgniteCheckedException {
+        File snpDir = snapshotLocalDir(snpName, snpPath);
 
         if (!snpDir.exists())
             throw new IgniteCheckedException("Snapshot directory doesn't exists: " + snpDir.getAbsolutePath());
@@ -781,7 +793,7 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
                 req.operationalNodeId(),
                 parts,
                 withMetaStorage,
-                locSndrFactory.apply(req.snapshotName()));
+                locSndrFactory.apply(req.snapshotName(), req.snapshotPath()));
 
             if (withMetaStorage && task0 instanceof SnapshotFutureTask) {
                 ((DistributedMetaStorageImpl)cctx.kernalContext().distributedMetastorage())
@@ -800,12 +812,14 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
                     .map(n -> cctx.discovery().node(n).consistentId().toString())
                     .collect(Collectors.toSet());
 
-                File smf = new File(snapshotLocalDir(req.snapshotName()), snapshotMetaFileName(cctx.localNode().consistentId().toString()));
+                File snpDir = snapshotLocalDir(req.snapshotName(), req.snapshotPath());
+
+                File smf = new File(snpDir, snapshotMetaFileName(cctx.localNode().consistentId().toString()));
 
                 if (smf.exists())
                     throw new GridClosureException(new IgniteException("Snapshot metafile must not exist: " + smf.getAbsolutePath()));
 
-                smf.getParentFile().mkdirs();
+                snpDir.mkdirs();
 
                 SnapshotMetadata meta = new SnapshotMetadata(req.requestId(),
                     req.snapshotName(),
@@ -824,7 +838,7 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
                     log.info("Snapshot metafile has been created: " + smf.getAbsolutePath());
                 }
 
-                SnapshotHandlerContext ctx = new SnapshotHandlerContext(meta, req.groups(), cctx.localNode());
+                SnapshotHandlerContext ctx = new SnapshotHandlerContext(meta, req.groups(), cctx.localNode(), snpDir);
 
                 return new SnapshotOperationResponse(handlers.invokeAll(SnapshotHandlerType.CREATE, ctx));
             }
@@ -958,7 +972,7 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
 
         try {
             if (req.error() != null)
-                deleteSnapshot(snapshotLocalDir(req.snapshotName()), pdsSettings.folderName());
+                deleteSnapshot(snapshotLocalDir(req.snapshotName(), req.snapshotPath()), pdsSettings.folderName());
 
             removeLastMetaStorageKey();
         }
@@ -1162,16 +1176,17 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
 
     /**
      * @param name Snapshot name.
+     * @param snpPath Snapshot directory path.
      * @return Future with the result of execution snapshot partitions verify task, which besides calculating partition
      *         hashes of {@link IdleVerifyResultV2} also contains the snapshot metadata distribution across the cluster.
      */
-    public IgniteInternalFuture<IdleVerifyResultV2> checkSnapshot(String name) {
+    public IgniteInternalFuture<IdleVerifyResultV2> checkSnapshot(String name, @Nullable String snpPath) {
         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_");
 
         cctx.kernalContext().security().authorize(ADMIN_SNAPSHOT);
 
-        return checkSnapshot(name, null, false).chain(f -> {
+        return checkSnapshot(name, snpPath, null, false).chain(f -> {
             try {
                 return f.get().idleVerifyResult();
             }
@@ -1187,6 +1202,7 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
      * exception is not related to the check procedure, and will be completed normally with the {@code IdleVerifyResult}.
      *
      * @param name Snapshot name.
+     * @param snpPath Snapshot directory path.
      * @param grps Collection of cache group names to check.
      * @param includeCustomHandlers {@code True} to invoke all user-defined {@link SnapshotHandlerType#RESTORE}
      *                              handlers, otherwise only system consistency check will be performed.
@@ -1195,6 +1211,7 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
      */
     public IgniteInternalFuture<SnapshotPartitionsVerifyTaskResult> checkSnapshot(
         String name,
+        @Nullable String snpPath,
         @Nullable Collection<String> grps,
         boolean includeCustomHandlers
     ) {
@@ -1213,7 +1230,9 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
         kctx0.task().setThreadContext(TC_SKIP_AUTH, true);
         kctx0.task().setThreadContext(TC_SUBGRID, bltNodes);
 
-        kctx0.task().execute(SnapshotMetadataCollectorTask.class, name).listen(f0 -> {
+        SnapshotMetadataCollectorTaskArg taskArg = new SnapshotMetadataCollectorTaskArg(name, snpPath);
+
+        kctx0.task().execute(SnapshotMetadataCollectorTask.class, taskArg).listen(f0 -> {
             if (f0.error() == null) {
                 Map<ClusterNode, List<SnapshotMetadata>> metas = f0.result();
 
@@ -1258,13 +1277,22 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
                     return;
                 }
 
+                if (metas.isEmpty()) {
+                    res.onDone(new SnapshotPartitionsVerifyTaskResult(metas,
+                        new IdleVerifyResultV2(Collections.singletonMap(cctx.localNode(),
+                            new IllegalArgumentException("Snapshot does not exists [snapshot=" + name +
+                                (snpPath != null ? ", baseDir=" + snpPath : "") + ']')))));
+
+                    return;
+                }
+
                 kctx0.task().setThreadContext(TC_SKIP_AUTH, true);
                 kctx0.task().setThreadContext(TC_SUBGRID, new ArrayList<>(metas.keySet()));
 
                 Class<? extends AbstractSnapshotVerificationTask> cls =
                     includeCustomHandlers ? SnapshotHandlerRestoreTask.class : SnapshotPartitionsVerifyTask.class;
 
-                kctx0.task().execute(cls, new SnapshotPartitionsVerifyTaskArg(grps, metas))
+                kctx0.task().execute(cls, new SnapshotPartitionsVerifyTaskArg(grps, metas, snpPath))
                     .listen(f1 -> {
                         if (f1.error() == null)
                             res.onDone(f1.result());
@@ -1287,23 +1315,14 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
         return res;
     }
 
-    /**
-     * @param snpName Snapshot name.
-     * @param folderName The name of a directory for the cache group.
-     * @return The list of cache or cache group names in given snapshot on local node.
-     */
-    public List<File> snapshotCacheDirectories(String snpName, String folderName) {
-        return snapshotCacheDirectories(snpName, folderName, name -> true);
-    }
-
     /**
      * @param snpName Snapshot name.
      * @param folderName The name of a directory for the cache group.
      * @param names Cache group names to filter.
      * @return The list of cache or cache group names in given snapshot on local node.
      */
-    public List<File> snapshotCacheDirectories(String snpName, String folderName, Predicate<String> names) {
-        File snpDir = snapshotLocalDir(snpName);
+    public List<File> snapshotCacheDirectories(String snpName, @Nullable String snpPath, String folderName, Predicate<String> names) {
+        File snpDir = snapshotLocalDir(snpName, snpPath);
 
         if (!snpDir.exists())
             return Collections.emptyList();
@@ -1312,12 +1331,12 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
     }
 
     /**
-     * @param snpName Snapshot name.
+     * @param snpDir The full path to the snapshot files.
      * @param consId Node consistent id to read metadata for.
      * @return Snapshot metadata instance.
      */
-    public SnapshotMetadata readSnapshotMetadata(String snpName, String consId) {
-        return readSnapshotMetadata(new File(snapshotLocalDir(snpName), snapshotMetaFileName(consId)));
+    public SnapshotMetadata readSnapshotMetadata(File snpDir, String consId) {
+        return readSnapshotMetadata(new File(snpDir, snapshotMetaFileName(consId)));
     }
 
     /**
@@ -1349,15 +1368,16 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
 
     /**
      * @param snpName Snapshot name.
+     * @param snpPath Snapshot directory path.
      * @return List of snapshot metadata for the given snapshot name on local node.
      * If snapshot has been taken from local node the snapshot metadata for given
      * local node will be placed on the first place.
      */
-    public List<SnapshotMetadata> readSnapshotMetadatas(String snpName) {
+    public List<SnapshotMetadata> readSnapshotMetadatas(String snpName, @Nullable String snpPath) {
         A.notNullOrEmpty(snpName, "Snapshot name cannot be null or empty.");
         A.ensure(U.alphanumericUnderscore(snpName), "Snapshot name must satisfy the following name pattern: a-zA-Z0-9_");
 
-        File snpDir = snapshotLocalDir(snpName);
+        File snpDir = snapshotLocalDir(snpName, snpPath);
 
         if (!(snpDir.exists() && snpDir.isDirectory()))
             return Collections.emptyList();
@@ -1408,6 +1428,17 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
 
     /** {@inheritDoc} */
     @Override public IgniteFuture<Void> createSnapshot(String name) {
+        return createSnapshot(name, null);
+    }
+
+    /**
+     * Create a consistent copy of all persistence cache groups from the whole cluster.
+     *
+     * @param name Snapshot unique name which satisfies the following name pattern [a-zA-Z0-9_].
+     * @param snpPath Snapshot directory path.
+     * @return Future which will be completed when a process ends.
+     */
+    public IgniteFuture<Void> createSnapshot(String name, @Nullable String snpPath) {
         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_");
 
@@ -1491,13 +1522,11 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
                     recordSnapshotEvent(name, SNAPSHOT_FAILED_MSG + f.error().getMessage(), EVT_CLUSTER_SNAPSHOT_FAILED);
             });
 
-            startSnpProc.start(snpFut0.rqId, new SnapshotOperationRequest(snpFut0.rqId,
-                cctx.localNodeId(),
-                name,
-                grps,
-                new HashSet<>(F.viewReadOnly(srvNodes,
-                    F.node2id(),
-                    (node) -> CU.baselineNode(node, clusterState)))));
+            Set<UUID> bltNodeIds =
+                new HashSet<>(F.viewReadOnly(srvNodes, F.node2id(), (node) -> CU.baselineNode(node, clusterState)));
+
+            startSnpProc.start(snpFut0.rqId,
+                new SnapshotOperationRequest(snpFut0.rqId, cctx.localNodeId(), name, snpPath, grps, bltNodeIds));
 
             String msg = "Cluster-wide snapshot operation started [snpName=" + name + ", grps=" + grps + ']';
 
@@ -1521,11 +1550,25 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
 
     /** {@inheritDoc} */
     @Override public IgniteFuture<Void> restoreSnapshot(String name, @Nullable Collection<String> grpNames) {
+        return restoreSnapshot(name, null, grpNames);
+    }
+
+    /**
+     * Restore cache group(s) from the snapshot.
+     *
+     * @param name Snapshot name.
+     * @param snpPath Snapshot directory path.
+     * @param grpNames Cache groups to be restored or {@code null} to restore all cache groups from the snapshot.
+     * @return Future which will be completed when restore operation finished.
+     */
+    public IgniteFuture<Void> restoreSnapshot(String name, @Nullable String snpPath, @Nullable Collection<String> grpNames) {
         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_");
         A.ensure(grpNames == null || !grpNames.isEmpty(), "List of cache group names cannot be empty.");
 
-        return restoreCacheGrpProc.start(name, grpNames);
+        cctx.kernalContext().security().authorize(ADMIN_SNAPSHOT);
+
+        return restoreCacheGrpProc.start(name, snpPath, grpNames);
     }
 
     /** {@inheritDoc} */
@@ -1546,8 +1589,11 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
 
         // Snapshot which has not been completed due to the local node crashed must be deleted.
         String snpName = (String)metaStorage.read(SNP_RUNNING_KEY);
+        String snpDirName = snpName == null ? (String)metaStorage.read(SNP_RUNNING_DIR_KEY) : null;
+
+        File snpDir = snpName != null ? snapshotLocalDir(snpName, null) : snpDirName != null ? new File(snpDirName) : null;
 
-        if (snpName == null)
+        if (snpDir == null)
             return;
 
         recovered = true;
@@ -1555,11 +1601,11 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
         for (File tmp : snapshotTmpDir().listFiles())
             U.delete(tmp);
 
-        deleteSnapshot(snapshotLocalDir(snpName), pdsSettings.folderName());
+        deleteSnapshot(snpDir, pdsSettings.folderName());
 
         if (log.isInfoEnabled()) {
             log.info("Previous attempt to create snapshot fail due to the local node crash. All resources " +
-                "related to snapshot operation have been deleted: " + snpName);
+                "related to snapshot operation have been deleted: " + snpDir.getName());
         }
     }
 
@@ -1606,13 +1652,17 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
     }
 
     /**
-     * @param parts Collection of pairs group and appropriate cache partition to be snapshot.
      * @param rmtNodeId The remote node to connect to.
+     * @param snpName Snapshot name to request.
+     * @param rmtSnpPath Snapshot directory path on the remote node.
+     * @param parts Collection of pairs group and appropriate cache partition to be snapshot.
+     * @param stopChecker Node stop or prcoess interrupt checker.
      * @param partHnd Received partition handler.
      */
     public IgniteInternalFuture<Void> requestRemoteSnapshotFiles(
         UUID rmtNodeId,
         String snpName,
+        @Nullable String rmtSnpPath,
         Map<Integer, Set<Integer>> parts,
         BooleanSupplier stopChecker,
         BiConsumer<@Nullable File, @Nullable Throwable> partHnd
@@ -1630,7 +1680,8 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
         if (!nodeSupports(rmtNode, PERSISTENCE_CACHE_SNAPSHOT))
             throw new IgniteCheckedException("Snapshot on remote node is not supported: " + rmtNode.id());
 
-        RemoteSnapshotFilesRecevier fut = new RemoteSnapshotFilesRecevier(this, rmtNodeId, snpName, parts, stopChecker, partHnd);
+        RemoteSnapshotFilesRecevier fut =
+            new RemoteSnapshotFilesRecevier(this, rmtNodeId, snpName, rmtSnpPath, parts, stopChecker, partHnd);
 
         snpRmtMgr.submit(fut);
 
@@ -1662,14 +1713,12 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
     }
 
     /**
-     * @param snpName Snapshot name.
+     * @param snpDir The full path to the snapshot files.
      * @param folderName The node folder name, usually it's the same as the U.maskForFileName(consistentId).
      * @return Standalone kernal context related to the snapshot.
      * @throws IgniteCheckedException If fails.
      */
-    public StandaloneGridKernalContext createStandaloneKernalContext(String snpName, String folderName) throws IgniteCheckedException {
-        File snpDir = resolveSnapshotDir(snpName);
-
+    public StandaloneGridKernalContext createStandaloneKernalContext(File snpDir, String folderName) throws IgniteCheckedException {
         return new StandaloneGridKernalContext(log,
             resolveBinaryWorkDir(snpDir.getAbsolutePath(), folderName),
             resolveMappingFileStoreWorkDir(snpDir.getAbsolutePath()));
@@ -1713,7 +1762,7 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
         int partId,
         @Nullable EncryptionCacheKeyProvider encrKeyProvider
     ) throws IgniteCheckedException {
-        File snpDir = resolveSnapshotDir(snpName);
+        File snpDir = resolveSnapshotDir(snpName, null);
 
         File nodePath = new File(snpDir, databaseRelativePath(folderName));
 
@@ -1735,7 +1784,7 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
             );
         }
 
-        File snpPart = getPartitionFile(new File(snapshotLocalDir(snpName), databaseRelativePath(folderName)),
+        File snpPart = getPartitionFile(new File(snapshotLocalDir(snpName, null), databaseRelativePath(folderName)),
             grps.get(0).getName(), partId);
 
         int grpId = CU.cacheId(grpName);
@@ -1855,14 +1904,14 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
     /**
      * @param factory Factory which produces {@link LocalSnapshotSender} implementation.
      */
-    void localSnapshotSenderFactory(Function<String, SnapshotSender> factory) {
+    void localSnapshotSenderFactory(BiFunction<String, String, SnapshotSender> factory) {
         locSndrFactory = factory;
     }
 
     /**
      * @return Factory which produces {@link LocalSnapshotSender} implementation.
      */
-    Function<String, SnapshotSender> localSnapshotSenderFactory() {
+    BiFunction<String, String, SnapshotSender> localSnapshotSenderFactory() {
         return locSndrFactory;
     }
 
@@ -1891,6 +1940,7 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
         cctx.database().checkpointReadLock();
 
         try {
+            metaStorage.remove(SNP_RUNNING_DIR_KEY);
             metaStorage.remove(SNP_RUNNING_KEY);
         }
         finally {
@@ -2053,8 +2103,8 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
      * @return Snapshot view.
      */
     private SnapshotView snapshotViewSupplier(SnapshotMetadata meta) {
-        Collection<String> cacheGrps = F.viewReadOnly(snapshotCacheDirectories(meta.snapshotName(), meta.folderName()),
-            FilePageStoreManager::cacheGroupName);
+        List<File> dirs = snapshotCacheDirectories(meta.snapshotName(), null, meta.folderName(), name -> true);
+        Collection<String> cacheGrps = F.viewReadOnly(dirs, FilePageStoreManager::cacheGroupName);
 
         return new SnapshotView(meta.snapshotName(), meta.consistentId(), F.concat(meta.baselineNodes(), ","), F.concat(cacheGrps, ","));
     }
@@ -2440,6 +2490,7 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
          * @param snpMgr Ignite snapshot manager.
          * @param rmtNodeId Remote node to request snapshot from.
          * @param snpName Snapshot name to request.
+         * @param rmtSnpPath Snapshot directory path on the remote node.
          * @param parts Cache group and partitions to request.
          * @param stopChecker Process interrupt checker.
          * @param partHnd Partition handler.
@@ -2448,12 +2499,13 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
             IgniteSnapshotManager snpMgr,
             UUID rmtNodeId,
             String snpName,
+            @Nullable String rmtSnpPath,
             Map<Integer, Set<Integer>> parts,
             BooleanSupplier stopChecker,
             BiConsumer<@Nullable File, @Nullable Throwable> partHnd
         ) {
             dir = Paths.get(snpMgr.tmpWorkDir.getAbsolutePath(), reqId);
-            initMsg = new SnapshotFilesRequestMessage(reqId, snpName, parts);
+            initMsg = new SnapshotFilesRequestMessage(reqId, snpName, rmtSnpPath, parts);
 
             this.snpMgr = snpMgr;
             this.rmtNodeId = rmtNodeId;
@@ -2691,6 +2743,7 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
                             new SnapshotResponseRemoteFutureTask(cctx,
                                 nodeId,
                                 snpName,
+                                reqMsg0.snapshotPath(),
                                 tmpWorkDir,
                                 ioFactory,
                                 rmtSndrFactory.apply(rqId, nodeId),
@@ -3050,9 +3103,6 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
      * Snapshot sender which writes all data to local directory.
      */
     private class LocalSnapshotSender extends SnapshotSender {
-        /** Snapshot name. */
-        private final String snpName;
-
         /** Local snapshot directory. */
         private final File snpLocDir;
 
@@ -3064,12 +3114,12 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
 
         /**
          * @param snpName Snapshot name.
+         * @param snpPath Snapshot directory path.
          */
-        public LocalSnapshotSender(String snpName) {
+        public LocalSnapshotSender(String snpName, @Nullable String snpPath) {
             super(IgniteSnapshotManager.this.log, cctx.kernalContext().pools().getSnapshotExecutorService());
 
-            this.snpName = snpName;
-            snpLocDir = snapshotLocalDir(snpName);
+            snpLocDir = snapshotLocalDir(snpName, snpPath);
             pageSize = cctx.kernalContext().config().getDataStorageConfiguration().getPageSize();
         }
 
@@ -3079,16 +3129,16 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter
 
             if (dbDir.exists()) {
                 throw new IgniteException("Snapshot with given name already exists " +
-                    "[snpName=" + snpName + ", absPath=" + dbDir.getAbsolutePath() + ']');
+                    "[snpName=" + snpLocDir.getName() + ", absPath=" + dbDir.getAbsolutePath() + ']');
             }
 
             cctx.database().checkpointReadLock();
 
             try {
-                assert metaStorage != null && metaStorage.read(SNP_RUNNING_KEY) == null :
+                assert metaStorage != null && metaStorage.read(SNP_RUNNING_DIR_KEY) == null :
                     "The previous snapshot hasn't been completed correctly";
 
-                metaStorage.write(SNP_RUNNING_KEY, snpName);
+                metaStorage.write(SNP_RUNNING_DIR_KEY, snpLocDir.getAbsolutePath());
 
                 U.ensureDirectory(dbDir, "snapshot work directory for a local snapshot sender", log);
             }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotFilesRequestMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotFilesRequestMessage.java
index 65d13459eaf..81c9870477b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotFilesRequestMessage.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotFilesRequestMessage.java
@@ -31,6 +31,7 @@ import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.plugin.extensions.communication.MessageCollectionItemType;
 import org.apache.ignite.plugin.extensions.communication.MessageReader;
 import org.apache.ignite.plugin.extensions.communication.MessageWriter;
+import org.jetbrains.annotations.Nullable;
 
 /**
  *
@@ -45,6 +46,9 @@ public class SnapshotFilesRequestMessage extends AbstractSnapshotMessage {
     /** Snapshot name to request. */
     private String snpName;
 
+    /** Snapshot directory path. */
+    private String snpPath;
+
     /** Map of cache group ids and corresponding set of its partition ids. */
     @GridDirectMap(keyType = Integer.class, valueType = int[].class)
     private Map<Integer, int[]> parts;
@@ -58,15 +62,22 @@ public class SnapshotFilesRequestMessage extends AbstractSnapshotMessage {
 
     /**
      * @param reqId Unique request id.
-     * @param snpName Snapshot name.
+     * @param snpName Snapshot name to request.
+     * @param snpPath Snapshot directory path.
      * @param parts Map of cache group ids and corresponding set of its partition ids to be snapshot.
      */
-    public SnapshotFilesRequestMessage(String reqId, String snpName, Map<Integer, Set<Integer>> parts) {
+    public SnapshotFilesRequestMessage(
+        String reqId,
+        String snpName,
+        @Nullable String snpPath,
+        Map<Integer, Set<Integer>> parts
+    ) {
         super(reqId);
 
         assert parts != null && !parts.isEmpty();
 
         this.snpName = snpName;
+        this.snpPath = snpPath;
         this.parts = new HashMap<>();
 
         for (Map.Entry<Integer, Set<Integer>> e : parts.entrySet())
@@ -95,6 +106,13 @@ public class SnapshotFilesRequestMessage extends AbstractSnapshotMessage {
         return snpName;
     }
 
+    /**
+     * @return Snapshot directory path.
+     */
+    public String snapshotPath() {
+        return snpPath;
+    }
+
     /** {@inheritDoc} */
     @Override public boolean writeTo(ByteBuffer buf, MessageWriter writer) {
         writer.setBuffer(buf);
@@ -123,6 +141,13 @@ public class SnapshotFilesRequestMessage extends AbstractSnapshotMessage {
             writer.incrementState();
         }
 
+        if (writer.state() == 3) {
+            if (!writer.writeString("snpPath", snpPath))
+                return false;
+
+            writer.incrementState();
+        }
+
         return true;
     }
 
@@ -154,6 +179,15 @@ public class SnapshotFilesRequestMessage extends AbstractSnapshotMessage {
             reader.incrementState();
         }
 
+        if (reader.state() == 3) {
+            snpPath = reader.readString("snpPath");
+
+            if (!reader.isLastRead())
+                return false;
+
+            reader.incrementState();
+        }
+
         return reader.afterMessageRead(SnapshotFilesRequestMessage.class);
     }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotHandlerContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotHandlerContext.java
index 3cdf7153150..ffe18ec3c4e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotHandlerContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotHandlerContext.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.processors.cache.persistence.snapshot;
 
+import java.io.File;
 import java.util.Collection;
 import org.apache.ignite.cluster.ClusterNode;
 import org.jetbrains.annotations.Nullable;
@@ -28,6 +29,9 @@ public class SnapshotHandlerContext {
     /** Snapshot metadata. */
     private final SnapshotMetadata metadata;
 
+    /** The full path to the snapshot files. */
+    private final File snpDir;
+
     /** The names of the cache groups on which the operation is performed. */
     private final Collection<String> grps;
 
@@ -38,11 +42,13 @@ public class SnapshotHandlerContext {
      * @param metadata Snapshot metadata.
      * @param grps The names of the cache groups on which the operation is performed.
      * @param locNode Local node.
+     * @param snpDir The full path to the snapshot files.
      */
-    public SnapshotHandlerContext(SnapshotMetadata metadata, @Nullable Collection<String> grps, ClusterNode locNode) {
+    public SnapshotHandlerContext(SnapshotMetadata metadata, @Nullable Collection<String> grps, ClusterNode locNode, File snpDir) {
         this.metadata = metadata;
         this.grps = grps;
         this.locNode = locNode;
+        this.snpDir = snpDir;
     }
 
     /**
@@ -52,6 +58,13 @@ public class SnapshotHandlerContext {
         return metadata;
     }
 
+    /**
+     * @return The full path to the snapshot files.
+     */
+    public File snapshotDirectory() {
+        return snpDir;
+    }
+
     /**
      * @return The names of the cache groups on which the operation is performed. May be {@code null} if the operation
      * is performed on all available cache groups.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotHandlerRestoreTask.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotHandlerRestoreTask.java
index 8db322c9048..947085ab87f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotHandlerRestoreTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotHandlerRestoreTask.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.processors.cache.persistence.snapshot;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -47,8 +48,13 @@ public class SnapshotHandlerRestoreTask extends AbstractSnapshotVerificationTask
     private IgniteLogger log;
 
     /** {@inheritDoc} */
-    @Override protected ComputeJob createJob(String snpName, String constId, Collection<String> groups) {
-        return new SnapshotHandlerRestoreJob(snpName, constId, groups);
+    @Override protected ComputeJob createJob(
+        String name,
+        @Nullable String path,
+        String constId,
+        Collection<String> groups
+    ) {
+        return new SnapshotHandlerRestoreJob(name, path, constId, groups);
     }
 
     /** {@inheritDoc} */
@@ -112,13 +118,18 @@ public class SnapshotHandlerRestoreTask extends AbstractSnapshotVerificationTask
         /** Cache group names. */
         private final Collection<String> grps;
 
+        /** Snapshot directory path. */
+        private final String snpPath;
+
         /**
          * @param snpName Snapshot name.
+         * @param snpPath Snapshot directory path.
          * @param consistentId String representation of the consistent node ID.
          * @param grps Cache group names.
          */
-        public SnapshotHandlerRestoreJob(String snpName, String consistentId, Collection<String> grps) {
+        public SnapshotHandlerRestoreJob(String snpName, @Nullable String snpPath, String consistentId, Collection<String> grps) {
             this.snpName = snpName;
+            this.snpPath = snpPath;
             this.consistentId = consistentId;
             this.grps = grps;
         }
@@ -127,10 +138,11 @@ public class SnapshotHandlerRestoreTask extends AbstractSnapshotVerificationTask
         @Override public Map<String, SnapshotHandlerResult<Object>> execute() {
             try {
                 IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr();
-                SnapshotMetadata meta = snpMgr.readSnapshotMetadata(snpName, consistentId);
-                SnapshotHandlerContext ctx = new SnapshotHandlerContext(meta, grps, ignite.localNode());
+                File snpDir = snpMgr.snapshotLocalDir(snpName, snpPath);
+                SnapshotMetadata meta = snpMgr.readSnapshotMetadata(snpDir, consistentId);
 
-                return snpMgr.handlers().invokeAll(SnapshotHandlerType.RESTORE, ctx);
+                return snpMgr.handlers().invokeAll(SnapshotHandlerType.RESTORE,
+                    new SnapshotHandlerContext(meta, grps, ignite.localNode(), snpDir));
             }
             catch (IgniteCheckedException e) {
                 throw new IgniteException(e);
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 60abf07606a..33389881a26 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
@@ -40,8 +40,8 @@ public class SnapshotMXBeanImpl implements SnapshotMXBean {
     }
 
     /** {@inheritDoc} */
-    @Override public void createSnapshot(String snpName) {
-        IgniteFuture<Void> fut = mgr.createSnapshot(snpName);
+    @Override public void createSnapshot(String snpName, String snpPath) {
+        IgniteFuture<Void> fut = mgr.createSnapshot(snpName, F.isEmpty(snpPath) ? null : snpPath);
 
         if (fut.isDone())
             fut.get();
@@ -53,11 +53,11 @@ public class SnapshotMXBeanImpl implements SnapshotMXBean {
     }
 
     /** {@inheritDoc} */
-    @Override public void restoreSnapshot(String name, String grpNames) {
+    @Override public void restoreSnapshot(String name, String path, String grpNames) {
         Set<String> grpNamesSet = F.isEmpty(grpNames) ? null :
             Arrays.stream(grpNames.split(",")).map(String::trim).filter(s -> !s.isEmpty()).collect(Collectors.toSet());
 
-        IgniteFuture<Void> fut = mgr.restoreSnapshot(name, grpNamesSet);
+        IgniteFuture<Void> fut = mgr.restoreSnapshot(name, F.isEmpty(path) ? null : path, grpNamesSet);
 
         if (fut.isDone())
             fut.get();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataCollectorTask.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataCollectorTask.java
index c2c3b5c4ba2..76fb7b31750 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataCollectorTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataCollectorTask.java
@@ -38,25 +38,25 @@ import org.jetbrains.annotations.Nullable;
 /** Snapshot task to collect snapshot metadata from the baseline nodes for given snapshot name. */
 @GridInternal
 public class SnapshotMetadataCollectorTask
-    extends ComputeTaskAdapter<String, Map<ClusterNode, List<SnapshotMetadata>>> {
+    extends ComputeTaskAdapter<SnapshotMetadataCollectorTaskArg, Map<ClusterNode, List<SnapshotMetadata>>> {
     /** Serial version uid. */
     private static final long serialVersionUID = 0L;
 
     /** {@inheritDoc} */
     @Override public @NotNull Map<? extends ComputeJob, ClusterNode> map(
         List<ClusterNode> subgrid,
-        @Nullable String snpName
+        SnapshotMetadataCollectorTaskArg arg
     ) throws IgniteException {
         Map<ComputeJob, ClusterNode> map = U.newHashMap(subgrid.size());
 
         for (ClusterNode node : subgrid) {
-            map.put(new ComputeJobAdapter(snpName) {
+            map.put(new ComputeJobAdapter() {
                 @IgniteInstanceResource
                 private transient IgniteEx ignite;
 
                 @Override public List<SnapshotMetadata> execute() throws IgniteException {
                     return ignite.context().cache().context().snapshotMgr()
-                        .readSnapshotMetadatas(snpName);
+                        .readSnapshotMetadatas(arg.snapshotName(), arg.snapshotPath());
                 }
             }, node);
         }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotPartitionsVerifyTaskArg.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataCollectorTaskArg.java
similarity index 56%
copy from modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotPartitionsVerifyTaskArg.java
copy to modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataCollectorTaskArg.java
index 64a2426339a..70d416428fd 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotPartitionsVerifyTaskArg.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataCollectorTaskArg.java
@@ -19,69 +19,66 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot;
 import java.io.IOException;
 import java.io.ObjectInput;
 import java.io.ObjectOutput;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import org.apache.ignite.cluster.ClusterNode;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.internal.visor.VisorDataTransferObject;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Input parameters for checking snapshot partitions consistency task.
  */
-public class SnapshotPartitionsVerifyTaskArg extends VisorDataTransferObject {
+public class SnapshotMetadataCollectorTaskArg extends VisorDataTransferObject {
     /** Serial version UID. */
     private static final long serialVersionUID = 0L;
 
-    /** Cache group names to be verified. */
-    private Collection<String> grpNames;
+    /** Snapshot name. */
+    private String snpName;
 
-    /** The map of distribution of snapshot metadata pieces across the cluster. */
-    private Map<ClusterNode, List<SnapshotMetadata>> clusterMetas;
+    /** Snapshot directory path. */
+    private String snpPath;
 
     /** Default constructor. */
-    public SnapshotPartitionsVerifyTaskArg() {
+    public SnapshotMetadataCollectorTaskArg() {
         // No-op.
     }
 
     /**
-     * @param grpNames Cache group names to be verified.
-     * @param clusterMetas The map of distribution of snapshot metadata pieces across the cluster.
+     * @param snpName Snapshot name.
+     * @param snpPath Snapshot directory path.
      */
-    public SnapshotPartitionsVerifyTaskArg(Collection<String> grpNames, Map<ClusterNode, List<SnapshotMetadata>> clusterMetas) {
-        this.grpNames = grpNames;
-        this.clusterMetas = clusterMetas;
+    public SnapshotMetadataCollectorTaskArg(String snpName, @Nullable String snpPath) {
+        this.snpName = snpName;
+        this.snpPath = snpPath;
     }
 
     /**
-     * @return Cache group names to be verified.
+     * @return Snapshot name.
      */
-    public Collection<String> cacheGroupNames() {
-        return grpNames;
+    public String snapshotName() {
+        return snpName;
     }
 
     /**
-     * @return The map of distribution of snapshot metadata pieces across the cluster.
+     * @return Snapshot directory path.
      */
-    public Map<ClusterNode, List<SnapshotMetadata>> clusterMetadata() {
-        return clusterMetas;
+    public String snapshotPath() {
+        return snpPath;
     }
 
     /** {@inheritDoc} */
     @Override protected void writeExternalData(ObjectOutput out) throws IOException {
-        U.writeCollection(out, grpNames);
-        U.writeMap(out, clusterMetas);
+        U.writeString(out, snpName);
+        U.writeString(out, snpPath);
     }
 
     /** {@inheritDoc} */
     @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
-        grpNames = U.readCollection(in);
-        clusterMetas = U.readMap(in);
+        snpName = U.readString(in);
+        snpPath = U.readString(in);
     }
 
     /** {@inheritDoc} */
     @Override public String toString() {
-        return S.toString(SnapshotPartitionsVerifyTaskArg.class, this);
+        return S.toString(SnapshotMetadataCollectorTaskArg.class, this);
     }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotOperationRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotOperationRequest.java
index 4cbcb5783f9..6c20c6d2910 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotOperationRequest.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotOperationRequest.java
@@ -40,6 +40,9 @@ public class SnapshotOperationRequest implements Serializable {
     /** Snapshot name. */
     private final String snpName;
 
+    /** Snapshot directory path. */
+    private final String snpPath;
+
     /** Baseline node IDs that must be alive to complete the operation. */
     @GridToStringInclude
     private final Set<UUID> nodes;
@@ -61,6 +64,7 @@ public class SnapshotOperationRequest implements Serializable {
      * @param reqId Request ID.
      * @param opNodeId Operational node ID.
      * @param snpName Snapshot name.
+     * @param snpPath Snapshot directory path.
      * @param grps List of cache group names.
      * @param nodes Baseline node IDs that must be alive to complete the operation.
      */
@@ -68,6 +72,7 @@ public class SnapshotOperationRequest implements Serializable {
         UUID reqId,
         UUID opNodeId,
         String snpName,
+        String snpPath,
         @Nullable Collection<String> grps,
         Set<UUID> nodes
     ) {
@@ -76,6 +81,7 @@ public class SnapshotOperationRequest implements Serializable {
         this.snpName = snpName;
         this.grps = grps;
         this.nodes = nodes;
+        this.snpPath = snpPath;
     }
 
     /**
@@ -92,6 +98,13 @@ public class SnapshotOperationRequest implements Serializable {
         return snpName;
     }
 
+    /**
+     * @return Snapshot directory path.
+     */
+    public String snapshotPath() {
+        return snpPath;
+    }
+
     /**
      * @return List of cache group names.
      */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotPartitionsVerifyHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotPartitionsVerifyHandler.java
index f6c2b78f3a0..6c408491d66 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotPartitionsVerifyHandler.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotPartitionsVerifyHandler.java
@@ -67,10 +67,12 @@ import static org.apache.ignite.internal.pagemem.PageIdAllocator.INDEX_PARTITION
 import static org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState.OWNING;
 import static org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState.fromOrdinal;
 import static org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.CACHE_DATA_FILENAME;
+import static org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.cacheDirectories;
 import static org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.cacheGroupName;
 import static org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.cachePartitionFiles;
 import static org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.partId;
 import static org.apache.ignite.internal.processors.cache.persistence.partstate.GroupPartitionId.getTypeByPartId;
+import static org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.databaseRelativePath;
 import static org.apache.ignite.internal.processors.cache.verify.IdleVerifyUtility.calculatePartitionHash;
 import static org.apache.ignite.internal.processors.cache.verify.IdleVerifyUtility.checkPartitionsPageCrcSum;
 
@@ -98,6 +100,9 @@ public class SnapshotPartitionsVerifyHandler implements SnapshotHandler<Map<Part
 
     /** {@inheritDoc} */
     @Override public Map<PartitionKeyV2, PartitionHashRecordV2> invoke(SnapshotHandlerContext opCtx) throws IgniteCheckedException {
+        if (!opCtx.snapshotDirectory().exists())
+            throw new IgniteCheckedException("Snapshot directory doesn't exists: " + opCtx.snapshotDirectory());;
+
         SnapshotMetadata meta = opCtx.metadata();
 
         Set<Integer> grps = F.isEmpty(opCtx.groups()) ? new HashSet<>(meta.partitions().keySet()) :
@@ -107,9 +112,7 @@ public class SnapshotPartitionsVerifyHandler implements SnapshotHandler<Map<Part
 
         Map<Integer, File> grpDirs = new HashMap<>();
 
-        IgniteSnapshotManager snpMgr = cctx.snapshotMgr();
-
-        for (File dir : snpMgr.snapshotCacheDirectories(meta.snapshotName(), meta.folderName())) {
+        for (File dir : cacheDirectories(new File(opCtx.snapshotDirectory(), databaseRelativePath(meta.folderName())), name -> true)) {
             int grpId = CU.cacheId(cacheGroupName(dir));
 
             if (!grps.remove(grpId))
@@ -146,7 +149,9 @@ public class SnapshotPartitionsVerifyHandler implements SnapshotHandler<Map<Part
         ThreadLocal<ByteBuffer> buff = ThreadLocal.withInitial(() -> ByteBuffer.allocateDirect(meta.pageSize())
             .order(ByteOrder.nativeOrder()));
 
-        GridKernalContext snpCtx = snpMgr.createStandaloneKernalContext(meta.snapshotName(), meta.folderName());
+        IgniteSnapshotManager snpMgr = cctx.snapshotMgr();
+
+        GridKernalContext snpCtx = snpMgr.createStandaloneKernalContext(opCtx.snapshotDirectory(), meta.folderName());
 
         FilePageStoreManager storeMgr = (FilePageStoreManager)cctx.pageStore();
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotPartitionsVerifyTask.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotPartitionsVerifyTask.java
index 040802f9fc7..05f5856b903 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotPartitionsVerifyTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotPartitionsVerifyTask.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.processors.cache.persistence.snapshot;
 
+import java.io.File;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -49,13 +50,9 @@ public class SnapshotPartitionsVerifyTask extends AbstractSnapshotVerificationTa
     /** Serial version uid. */
     private static final long serialVersionUID = 0L;
 
-    /** Ignite instance. */
-    @IgniteInstanceResource
-    private IgniteEx ignite;
-
     /** {@inheritDoc} */
-    @Override protected ComputeJob createJob(String name, String constId, Collection<String> groups) {
-        return new VisorVerifySnapshotPartitionsJob(name, constId, groups);
+    @Override protected ComputeJob createJob(String name, String path, String constId, Collection<String> groups) {
+        return new VisorVerifySnapshotPartitionsJob(name, path, constId, groups);
     }
 
     /** {@inheritDoc} */
@@ -79,6 +76,9 @@ public class SnapshotPartitionsVerifyTask extends AbstractSnapshotVerificationTa
         /** Snapshot name to validate. */
         private final String snpName;
 
+        /** Snapshot directory path. */
+        private final String snpPath;
+
         /** Consistent snapshot metadata file name. */
         private final String consId;
 
@@ -89,11 +89,18 @@ public class SnapshotPartitionsVerifyTask extends AbstractSnapshotVerificationTa
          * @param snpName Snapshot name to validate.
          * @param consId Consistent snapshot metadata file name.
          * @param rqGrps Set of cache groups to be checked in the snapshot or {@code empty} to check everything.
+         * @param snpPath Snapshot directory path.
          */
-        public VisorVerifySnapshotPartitionsJob(String snpName, String consId, Collection<String> rqGrps) {
+        public VisorVerifySnapshotPartitionsJob(
+            String snpName,
+            @Nullable String snpPath,
+            String consId,
+            Collection<String> rqGrps
+        ) {
             this.snpName = snpName;
             this.consId = consId;
             this.rqGrps = rqGrps;
+            this.snpPath = snpPath;
         }
 
         /** {@inheritDoc} */
@@ -106,10 +113,11 @@ public class SnapshotPartitionsVerifyTask extends AbstractSnapshotVerificationTa
             }
 
             try {
-                SnapshotMetadata meta = cctx.snapshotMgr().readSnapshotMetadata(snpName, consId);
+                File snpDir = cctx.snapshotMgr().snapshotLocalDir(snpName, snpPath);
+                SnapshotMetadata meta = cctx.snapshotMgr().readSnapshotMetadata(snpDir, consId);
 
                 return new SnapshotPartitionsVerifyHandler(cctx)
-                    .invoke(new SnapshotHandlerContext(meta, rqGrps, ignite.localNode()));
+                    .invoke(new SnapshotHandlerContext(meta, rqGrps, ignite.localNode(), snpDir));
             }
             catch (IgniteCheckedException e) {
                 throw new IgniteException(e);
@@ -126,12 +134,13 @@ public class SnapshotPartitionsVerifyTask extends AbstractSnapshotVerificationTa
 
             VisorVerifySnapshotPartitionsJob job = (VisorVerifySnapshotPartitionsJob)o;
 
-            return snpName.equals(job.snpName) && consId.equals(job.consId);
+            return snpName.equals(job.snpName) && consId.equals(job.consId) &&
+                Objects.equals(rqGrps, job.rqGrps) && Objects.equals(snpPath, job.snpPath);
         }
 
         /** {@inheritDoc} */
         @Override public int hashCode() {
-            return Objects.hash(snpName, consId);
+            return Objects.hash(snpName, consId, rqGrps, snpPath);
         }
     }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotPartitionsVerifyTaskArg.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotPartitionsVerifyTaskArg.java
index 64a2426339a..bcf6343a2a5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotPartitionsVerifyTaskArg.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotPartitionsVerifyTaskArg.java
@@ -26,6 +26,7 @@ import org.apache.ignite.cluster.ClusterNode;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.internal.visor.VisorDataTransferObject;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Input parameters for checking snapshot partitions consistency task.
@@ -40,6 +41,9 @@ public class SnapshotPartitionsVerifyTaskArg extends VisorDataTransferObject {
     /** The map of distribution of snapshot metadata pieces across the cluster. */
     private Map<ClusterNode, List<SnapshotMetadata>> clusterMetas;
 
+    /** Snapshot directory path. */
+    private String snpPath;
+
     /** Default constructor. */
     public SnapshotPartitionsVerifyTaskArg() {
         // No-op.
@@ -48,10 +52,16 @@ public class SnapshotPartitionsVerifyTaskArg extends VisorDataTransferObject {
     /**
      * @param grpNames Cache group names to be verified.
      * @param clusterMetas The map of distribution of snapshot metadata pieces across the cluster.
+     * @param snpPath Snapshot directory path.
      */
-    public SnapshotPartitionsVerifyTaskArg(Collection<String> grpNames, Map<ClusterNode, List<SnapshotMetadata>> clusterMetas) {
+    public SnapshotPartitionsVerifyTaskArg(
+        Collection<String> grpNames,
+        Map<ClusterNode, List<SnapshotMetadata>> clusterMetas,
+        @Nullable String snpPath
+    ) {
         this.grpNames = grpNames;
         this.clusterMetas = clusterMetas;
+        this.snpPath = snpPath;
     }
 
     /**
@@ -68,16 +78,25 @@ public class SnapshotPartitionsVerifyTaskArg extends VisorDataTransferObject {
         return clusterMetas;
     }
 
+    /**
+     * @return Snapshot directory path.
+     */
+    public String snapshotPath() {
+        return snpPath;
+    }
+
     /** {@inheritDoc} */
     @Override protected void writeExternalData(ObjectOutput out) throws IOException {
         U.writeCollection(out, grpNames);
         U.writeMap(out, clusterMetas);
+        U.writeString(out, snpPath);
     }
 
     /** {@inheritDoc} */
     @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
         grpNames = U.readCollection(in);
         clusterMetas = U.readMap(in);
+        snpPath = U.readString(in);
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotResponseRemoteFutureTask.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotResponseRemoteFutureTask.java
index 4763ce64e74..6ccf2000a55 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotResponseRemoteFutureTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotResponseRemoteFutureTask.java
@@ -39,10 +39,14 @@ import static org.apache.ignite.internal.processors.cache.persistence.snapshot.I
 
 /** */
 public class SnapshotResponseRemoteFutureTask extends AbstractSnapshotFutureTask<Void> {
+    /** Snapshot directory path. */
+    private final String snpPath;
+
     /**
      * @param cctx Shared context.
      * @param srcNodeId Node id which cause snapshot task creation.
      * @param snpName Unique identifier of snapshot process.
+     * @param snpPath Snapshot directory path.
      * @param tmpWorkDir Working directory for intermediate snapshot results.
      * @param ioFactory Factory to working with snapshot files.
      * @param snpSndr Factory which produces snapshot receiver instance.
@@ -51,12 +55,15 @@ public class SnapshotResponseRemoteFutureTask extends AbstractSnapshotFutureTask
         GridCacheSharedContext<?, ?> cctx,
         UUID srcNodeId,
         String snpName,
+        String snpPath,
         File tmpWorkDir,
         FileIOFactory ioFactory,
         SnapshotSender snpSndr,
         Map<Integer, Set<Integer>> parts
     ) {
         super(cctx, srcNodeId, snpName, tmpWorkDir, ioFactory, snpSndr, parts);
+
+        this.snpPath = snpPath;
     }
 
     /** {@inheritDoc} */
@@ -74,10 +81,10 @@ public class SnapshotResponseRemoteFutureTask extends AbstractSnapshotFutureTask
 
             snpSndr.init(handled.size());
 
-            File snpDir = cctx.snapshotMgr().snapshotLocalDir(snpName);
+            File snpDir = cctx.snapshotMgr().snapshotLocalDir(snpName, snpPath);
 
             List<CompletableFuture<Void>> futs = new ArrayList<>();
-            List<SnapshotMetadata> metas = cctx.snapshotMgr().readSnapshotMetadatas(snpName);
+            List<SnapshotMetadata> metas = cctx.snapshotMgr().readSnapshotMetadatas(snpName, snpPath);
 
             for (SnapshotMetadata meta : metas) {
                 Map<Integer, Set<Integer>> parts0 = meta.partitions();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java
index 01489646426..1b0a176c932 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java
@@ -208,10 +208,11 @@ public class SnapshotRestoreProcess {
      * Start cache group restore operation.
      *
      * @param snpName Snapshot name.
+     * @param snpPath Snapshot directory path.
      * @param cacheGrpNames Cache groups to be restored or {@code null} to restore all cache groups from the snapshot.
      * @return Future that will be completed when the restore operation is complete and the cache groups are started.
      */
-    public IgniteFuture<Void> start(String snpName, @Nullable Collection<String> cacheGrpNames) {
+    public IgniteFuture<Void> start(String snpName, @Nullable String snpPath, @Nullable Collection<String> cacheGrpNames) {
         IgniteSnapshotManager snpMgr = ctx.cache().context().snapshotMgr();
         ClusterSnapshotFuture fut0;
 
@@ -277,7 +278,7 @@ public class SnapshotRestoreProcess {
 
         snpMgr.recordSnapshotEvent(snpName, msg, EventType.EVT_CLUSTER_SNAPSHOT_RESTORE_STARTED);
 
-        snpMgr.checkSnapshot(snpName, cacheGrpNames, true).listen(f -> {
+        snpMgr.checkSnapshot(snpName, snpPath, cacheGrpNames, true).listen(f -> {
             if (f.error() != null) {
                 finishProcess(fut0.rqId, f.error());
 
@@ -331,8 +332,8 @@ public class SnapshotRestoreProcess {
 
             Collection<UUID> bltNodes = F.viewReadOnly(ctx.discovery().discoCache().aliveBaselineNodes(), F.node2id());
 
-            SnapshotOperationRequest req =
-                new SnapshotOperationRequest(fut0.rqId, F.first(dataNodes), snpName, cacheGrpNames, new HashSet<>(bltNodes));
+            SnapshotOperationRequest req = new SnapshotOperationRequest(
+                fut0.rqId, F.first(dataNodes), snpName, snpPath, cacheGrpNames, new HashSet<>(bltNodes));
 
             prepareRestoreProc.start(req.requestId(), req);
         });
@@ -582,7 +583,7 @@ public class SnapshotRestoreProcess {
                     ", caches=" + req.groups() + ']');
             }
 
-            List<SnapshotMetadata> locMetas = snpMgr.readSnapshotMetadatas(req.snapshotName());
+            List<SnapshotMetadata> locMetas = snpMgr.readSnapshotMetadatas(req.snapshotName(), req.snapshotPath());
 
             SnapshotRestoreContext opCtx0 = prepareContext(req, locMetas);
 
@@ -666,7 +667,7 @@ public class SnapshotRestoreProcess {
         // Collect the cache configurations and prepare a temporary directory for copying files.
         // Metastorage can be restored only manually by directly copying files.
         for (SnapshotMetadata meta : metas) {
-            for (File snpCacheDir : cctx.snapshotMgr().snapshotCacheDirectories(req.snapshotName(), meta.folderName(),
+            for (File snpCacheDir : cctx.snapshotMgr().snapshotCacheDirectories(req.snapshotName(), req.snapshotPath(), meta.folderName(),
                 name -> !METASTORAGE_CACHE_NAME.equals(name))) {
                 String grpName = FilePageStoreManager.cacheGroupName(snpCacheDir);
 
@@ -844,14 +845,15 @@ public class SnapshotRestoreProcess {
                     ", caches=" + F.transform(opCtx0.dirs, FilePageStoreManager::cacheGroupName) + ']');
             }
 
+            File snpDir = snpMgr.snapshotLocalDir(opCtx0.snpName, opCtx0.snpPath);
+
             CompletableFuture<Void> metaFut = ctx.localNodeId().equals(opCtx0.opNodeId) ?
                 CompletableFuture.runAsync(
                     () -> {
                         try {
                             SnapshotMetadata meta = F.first(opCtx0.metasPerNode.get(opCtx0.opNodeId));
 
-                            File binDir = binaryWorkDir(snpMgr.snapshotLocalDir(opCtx0.snpName).getAbsolutePath(),
-                                meta.folderName());
+                            File binDir = binaryWorkDir(snpDir.getAbsolutePath(), meta.folderName());
 
                             ctx.cacheObjects().updateMetadata(binDir, opCtx0.stopChecker);
                         }
@@ -895,6 +897,7 @@ public class SnapshotRestoreProcess {
                 }
 
                 List<List<ClusterNode>> assignment = affCache.get(cacheOrGrpName).idealAssignment().assignment();
+
                 Set<PartitionRestoreFuture> partFuts = availParts
                     .stream()
                     .filter(p -> p != INDEX_PARTITION && assignment.get(p).contains(locNode))
@@ -902,7 +905,6 @@ public class SnapshotRestoreProcess {
                     .collect(Collectors.toSet());
 
                 allParts.put(grpId, partFuts);
-
                 rmtLoadParts.put(grpId, leftParts = new HashSet<>(partFuts));
 
                 if (leftParts.isEmpty())
@@ -916,7 +918,7 @@ public class SnapshotRestoreProcess {
                     if (leftParts.isEmpty())
                         break;
 
-                    File snpCacheDir = new File(ctx.cache().context().snapshotMgr().snapshotLocalDir(opCtx0.snpName),
+                    File snpCacheDir = new File(snpDir,
                         Paths.get(databaseRelativePath(meta.folderName()), dir.getName()).toString());
 
                     leftParts.removeIf(partFut -> {
@@ -993,6 +995,7 @@ public class SnapshotRestoreProcess {
                     ctx.cache().context().snapshotMgr()
                         .requestRemoteSnapshotFiles(m.getKey(),
                             opCtx0.snpName,
+                            opCtx0.snpPath,
                             m.getValue(),
                             opCtx0.stopChecker,
                             (snpFile, t) -> {
@@ -1383,6 +1386,9 @@ public class SnapshotRestoreProcess {
         /** Snapshot name. */
         private final String snpName;
 
+        /** Snapshot directory path. */
+        private final String snpPath;
+
         /** Baseline discovery cache for node IDs that must be alive to complete the operation.*/
         private final DiscoCache discoCache;
 
@@ -1432,6 +1438,7 @@ public class SnapshotRestoreProcess {
             startTime = 0;
             opNodeId = null;
             discoCache = null;
+            snpPath = null;
         }
 
         /**
@@ -1442,6 +1449,7 @@ public class SnapshotRestoreProcess {
         protected SnapshotRestoreContext(SnapshotOperationRequest req, DiscoCache discoCache, Map<Integer, StoredCacheData> cfgs) {
             reqId = req.requestId();
             snpName = req.snapshotName();
+            snpPath = req.snapshotPath();
             opNodeId = req.operationalNodeId();
             startTime = U.currentTimeMillis();
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCheckTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCheckTask.java
index 5bd01d0d4fa..202fea06de8 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCheckTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCheckTask.java
@@ -25,34 +25,36 @@ import org.apache.ignite.internal.util.future.IgniteFutureImpl;
 import org.apache.ignite.internal.visor.VisorJob;
 
 /**
- * @see IgniteSnapshotManager#checkSnapshot(String)
+ * @see IgniteSnapshotManager#checkSnapshot(String, String)
  */
 @GridInternal
-public class VisorSnapshotCheckTask extends VisorSnapshotOneNodeTask<String, IdleVerifyResultV2> {
+public class VisorSnapshotCheckTask extends VisorSnapshotOneNodeTask<VisorSnapshotCheckTaskArg, IdleVerifyResultV2> {
     /** Serial version uid. */
     private static final long serialVersionUID = 0L;
 
     /** {@inheritDoc} */
-    @Override protected VisorJob<String, IdleVerifyResultV2> job(String arg) {
+    @Override protected VisorJob<VisorSnapshotCheckTaskArg, IdleVerifyResultV2> job(VisorSnapshotCheckTaskArg arg) {
         return new VisorSnapshotCheckJob(arg, debug);
     }
 
     /** */
-    private static class VisorSnapshotCheckJob extends VisorJob<String, IdleVerifyResultV2> {
+    private static class VisorSnapshotCheckJob extends VisorJob<VisorSnapshotCheckTaskArg, IdleVerifyResultV2> {
         /** Serial version uid. */
         private static final long serialVersionUID = 0L;
 
         /**
-         * @param name Snapshot name.
+         * @param arg Snapshot check task argument.
          * @param debug Flag indicating whether debug information should be printed into node log.
          */
-        protected VisorSnapshotCheckJob(String name, boolean debug) {
-            super(name, debug);
+        protected VisorSnapshotCheckJob(VisorSnapshotCheckTaskArg arg, boolean debug) {
+            super(arg, debug);
         }
 
         /** {@inheritDoc} */
-        @Override protected IdleVerifyResultV2 run(String name) throws IgniteException {
-            return new IgniteFutureImpl<>(ignite.context().cache().context().snapshotMgr().checkSnapshot(name))
+        @Override protected IdleVerifyResultV2 run(VisorSnapshotCheckTaskArg arg) throws IgniteException {
+            IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr();
+
+            return new IgniteFutureImpl<>(snpMgr.checkSnapshot(arg.snapshotName(), arg.snapshotPath()))
                 .get();
         }
     }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCreateTaskArg.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCheckTaskArg.java
similarity index 75%
copy from modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCreateTaskArg.java
copy to modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCheckTaskArg.java
index 04855257cb6..c3e708ce116 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCreateTaskArg.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCheckTaskArg.java
@@ -25,30 +25,30 @@ import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
 
 /**
- * Argument for the task to create snapshot.
+ * Argument for the task to check snapshot.
  */
-public class VisorSnapshotCreateTaskArg extends IgniteDataTransferObject {
+public class VisorSnapshotCheckTaskArg extends IgniteDataTransferObject {
     /** Serial version uid. */
     private static final long serialVersionUID = 0L;
 
     /** Snapshot name. */
     private String snpName;
 
-    /** Synchronous execution flag. */
-    private boolean sync;
+    /** Snapshot directory path. */
+    private String snpPath;
 
     /** Default constructor. */
-    public VisorSnapshotCreateTaskArg() {
+    public VisorSnapshotCheckTaskArg() {
         // No-op.
     }
 
     /**
      * @param snpName Snapshot name.
-     * @param sync Synchronous execution flag.
+     * @param snpPath Snapshot directory path.
      */
-    public VisorSnapshotCreateTaskArg(String snpName, boolean sync) {
+    public VisorSnapshotCheckTaskArg(String snpName, String snpPath) {
         this.snpName = snpName;
-        this.sync = sync;
+        this.snpPath = snpPath;
     }
 
     /** @return Snapshot name. */
@@ -56,25 +56,25 @@ public class VisorSnapshotCreateTaskArg extends IgniteDataTransferObject {
         return snpName;
     }
 
-    /** @return Synchronous execution flag. */
-    public boolean sync() {
-        return sync;
+    /** @return Snapshot directory path. */
+    public String snapshotPath() {
+        return snpPath;
     }
 
     /** {@inheritDoc} */
     @Override protected void writeExternalData(ObjectOutput out) throws IOException {
         U.writeString(out, snpName);
-        out.writeBoolean(sync);
+        U.writeString(out, snpPath);
     }
 
     /** {@inheritDoc} */
     @Override protected void readExternalData(byte ver, ObjectInput in) throws IOException, ClassNotFoundException {
         snpName = U.readString(in);
-        sync = in.readBoolean();
+        snpPath = U.readString(in);
     }
 
     /** {@inheritDoc} */
     @Override public String toString() {
-        return S.toString(VisorSnapshotCreateTaskArg.class, this);
+        return S.toString(VisorSnapshotCheckTaskArg.class, this);
     }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCreateTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCreateTask.java
index 45232bc9549..2879c4aeceb 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCreateTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCreateTask.java
@@ -51,7 +51,8 @@ public class VisorSnapshotCreateTask extends VisorSnapshotOneNodeTask<VisorSnaps
 
         /** {@inheritDoc} */
         @Override protected String run(VisorSnapshotCreateTaskArg arg) throws IgniteException {
-            IgniteFuture<Void> fut = ignite.snapshot().createSnapshot(arg.snapshotName());
+            IgniteFuture<Void> fut =
+                ignite.context().cache().context().snapshotMgr().createSnapshot(arg.snapshotName(), arg.snapshotPath());
 
             if (arg.sync() || fut.isDone())
                 fut.get();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCreateTaskArg.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCreateTaskArg.java
index 04855257cb6..a828cf9ec9d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCreateTaskArg.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotCreateTaskArg.java
@@ -20,20 +20,15 @@ package org.apache.ignite.internal.visor.snapshot;
 import java.io.IOException;
 import java.io.ObjectInput;
 import java.io.ObjectOutput;
-import org.apache.ignite.internal.dto.IgniteDataTransferObject;
 import org.apache.ignite.internal.util.typedef.internal.S;
-import org.apache.ignite.internal.util.typedef.internal.U;
 
 /**
  * Argument for the task to create snapshot.
  */
-public class VisorSnapshotCreateTaskArg extends IgniteDataTransferObject {
+public class VisorSnapshotCreateTaskArg extends VisorSnapshotCheckTaskArg {
     /** Serial version uid. */
     private static final long serialVersionUID = 0L;
 
-    /** Snapshot name. */
-    private String snpName;
-
     /** Synchronous execution flag. */
     private boolean sync;
 
@@ -44,16 +39,13 @@ public class VisorSnapshotCreateTaskArg extends IgniteDataTransferObject {
 
     /**
      * @param snpName Snapshot name.
+     * @param snpPath Snapshot directory path.
      * @param sync Synchronous execution flag.
      */
-    public VisorSnapshotCreateTaskArg(String snpName, boolean sync) {
-        this.snpName = snpName;
-        this.sync = sync;
-    }
+    public VisorSnapshotCreateTaskArg(String snpName, String snpPath, boolean sync) {
+        super(snpName, snpPath);
 
-    /** @return Snapshot name. */
-    public String snapshotName() {
-        return snpName;
+        this.sync = sync;
     }
 
     /** @return Synchronous execution flag. */
@@ -63,13 +55,15 @@ public class VisorSnapshotCreateTaskArg extends IgniteDataTransferObject {
 
     /** {@inheritDoc} */
     @Override protected void writeExternalData(ObjectOutput out) throws IOException {
-        U.writeString(out, snpName);
+        super.writeExternalData(out);
+
         out.writeBoolean(sync);
     }
 
     /** {@inheritDoc} */
     @Override protected void readExternalData(byte ver, ObjectInput in) throws IOException, ClassNotFoundException {
-        snpName = U.readString(in);
+        super.readExternalData(ver, in);
+
         sync = in.readBoolean();
     }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotRestoreTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotRestoreTask.java
index 56d35b5c9e3..49e5e62d166 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotRestoreTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotRestoreTask.java
@@ -63,8 +63,8 @@ public class VisorSnapshotRestoreTask extends VisorSnapshotOneNodeTask<VisorSnap
 
         /** {@inheritDoc} */
         @Override protected String run(VisorSnapshotRestoreTaskArg arg) throws IgniteException {
-            IgniteFuture<Void> fut =
-                ignite.context().cache().context().snapshotMgr().restoreSnapshot(arg.snapshotName(), arg.groupNames());
+            IgniteFuture<Void> fut = ignite.context().cache().context().snapshotMgr()
+                .restoreSnapshot(arg.snapshotName(), arg.snapshotPath(), arg.groupNames());
 
             if (arg.sync() || fut.isDone())
                 fut.get();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotRestoreTaskArg.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotRestoreTaskArg.java
index efad1bfc1de..86c07c4740a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotRestoreTaskArg.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/snapshot/VisorSnapshotRestoreTaskArg.java
@@ -51,11 +51,12 @@ public class VisorSnapshotRestoreTaskArg extends VisorSnapshotCreateTaskArg {
      */
     public VisorSnapshotRestoreTaskArg(
         String snpName,
+        String snpPath,
         boolean sync,
         VisorSnapshotRestoreTaskAction action,
         @Nullable Collection<String> grpNames
     ) {
-        super(snpName, sync);
+        super(snpName, snpPath, sync);
 
         this.action = action;
         this.grpNames = grpNames;
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 fcb7a399c98..b18ac51dc1b 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
@@ -29,10 +29,16 @@ public interface SnapshotMXBean {
      * Create the cluster-wide snapshot with given name asynchronously.
      *
      * @param snpName Snapshot name to be created.
+     * @param snpPath Snapshot directory path.
      * @see IgniteSnapshot#createSnapshot(String) (String)
      */
     @MXBeanDescription("Create cluster-wide snapshot.")
-    public void createSnapshot(@MXBeanParameter(name = "snpName", description = "Snapshot name.") String snpName);
+    public void createSnapshot(
+        @MXBeanParameter(name = "snpName", description = "Snapshot name.")
+            String snpName,
+        @MXBeanParameter(name = "snpPath", description = "Optional snapshot directory path.")
+            String snpPath
+    );
 
     /**
      * Cancel previously started snapshot operation on the node initiator.
@@ -46,6 +52,7 @@ public interface SnapshotMXBean {
      * Restore cluster-wide snapshot.
      *
      * @param name Snapshot name.
+     * @param path Snapshot directory path.
      * @param cacheGroupNames Optional comma-separated list of cache group names.
      * @see IgniteSnapshot#restoreSnapshot(String, Collection)
      */
@@ -53,6 +60,8 @@ public interface SnapshotMXBean {
     public void restoreSnapshot(
         @MXBeanParameter(name = "snpName", description = "Snapshot name.")
             String name,
+        @MXBeanParameter(name = "snpPath", description = "Optional snapshot directory path.")
+            String path,
         @MXBeanParameter(name = "cacheGroupNames", description = "Optional comma-separated list of cache group names.")
             String cacheGroupNames
     );
diff --git a/modules/core/src/main/resources/META-INF/classnames.properties b/modules/core/src/main/resources/META-INF/classnames.properties
index 5aeb89669a7..ebeac9d3791 100644
--- a/modules/core/src/main/resources/META-INF/classnames.properties
+++ b/modules/core/src/main/resources/META-INF/classnames.properties
@@ -2285,6 +2285,7 @@ org.apache.ignite.internal.visor.snapshot.VisorSnapshotRestoreTask
 org.apache.ignite.internal.visor.snapshot.VisorSnapshotRestoreTask$VisorSnapshotStartRestoreJob
 org.apache.ignite.internal.visor.snapshot.VisorSnapshotRestoreTask$VisorSnapshotRestoreCancelJob
 org.apache.ignite.internal.visor.snapshot.VisorSnapshotRestoreTask$VisorSnapshotRestoreStatusJob
+org.apache.ignite.internal.visor.snapshot.VisorSnapshotCheckTaskArg
 org.apache.ignite.internal.visor.snapshot.VisorSnapshotCreateTaskArg
 org.apache.ignite.internal.visor.snapshot.VisorSnapshotRestoreTaskArg
 org.apache.ignite.internal.visor.snapshot.VisorSnapshotRestoreTaskAction
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 5a9551374ec..0e5fc1f091f 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
@@ -562,13 +562,13 @@ public abstract class AbstractSnapshotSelfTest extends GridCommonAbstractTest {
 
         for (Ignite grid : grids) {
             IgniteSnapshotManager mgr = snp((IgniteEx)grid);
-            Function<String, SnapshotSender> old = mgr.localSnapshotSenderFactory();
+            BiFunction<String, String, SnapshotSender> old = mgr.localSnapshotSenderFactory();
 
             BlockingExecutor block = new BlockingExecutor(mgr.snapshotExecutorService());
             execs.add(block);
 
-            mgr.localSnapshotSenderFactory((snpName) ->
-                new DelegateSnapshotSender(log, block, old.apply(snpName)));
+            mgr.localSnapshotSenderFactory((snpName, snpPath) ->
+                new DelegateSnapshotSender(log, block, old.apply(snpName, snpPath)));
         }
 
         return execs;
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/EncryptedSnapshotTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/EncryptedSnapshotTest.java
index 7e8421ce6d6..8912e8e565e 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/EncryptedSnapshotTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/EncryptedSnapshotTest.java
@@ -194,7 +194,7 @@ public class EncryptedSnapshotTest extends AbstractSnapshotSelfTest {
 
         ig = startGrids(2);
 
-        IdleVerifyResultV2 snpCheckRes = snp(ig).checkSnapshot(SNAPSHOT_NAME).get();
+        IdleVerifyResultV2 snpCheckRes = snp(ig).checkSnapshot(SNAPSHOT_NAME, null).get();
 
         for (Exception e : snpCheckRes.exceptions().values()) {
             if (e.getMessage().contains("different master key digest"))
@@ -236,7 +236,7 @@ public class EncryptedSnapshotTest extends AbstractSnapshotSelfTest {
 
             ig.cluster().state(ACTIVE);
 
-            IdleVerifyResultV2 snpCheckRes = snp(ig).checkSnapshot(SNAPSHOT_NAME).get();
+            IdleVerifyResultV2 snpCheckRes = snp(ig).checkSnapshot(SNAPSHOT_NAME, null).get();
 
             for (Exception e : snpCheckRes.exceptions().values()) {
                 if (e.getMessage().contains("has encrypted caches while encryption is disabled"))
@@ -284,7 +284,7 @@ public class EncryptedSnapshotTest extends AbstractSnapshotSelfTest {
         GridTestUtils.assertThrowsAnyCause(log,
             () -> snp(ig).registerSnapshotTask(SNAPSHOT_NAME, ig.localNode().id(),
                 F.asMap(CU.cacheId(dfltCacheCfg.getName()), null), false,
-                snp(ig).localSnapshotSenderFactory().apply(SNAPSHOT_NAME)).get(TIMEOUT),
+                snp(ig).localSnapshotSenderFactory().apply(SNAPSHOT_NAME, null)).get(TIMEOUT),
             IgniteCheckedException.class,
             "Metastore is required because it holds encryption keys");
     }
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java
index 974fc8d11df..d477e2533c6 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java
@@ -122,7 +122,7 @@ public class IgniteClusterSnapshotCheckTest extends AbstractSnapshotSelfTest {
 
         ignite.snapshot().createSnapshot(SNAPSHOT_NAME).get();
 
-        IdleVerifyResultV2 res = snp(ignite).checkSnapshot(SNAPSHOT_NAME).get();
+        IdleVerifyResultV2 res = snp(ignite).checkSnapshot(SNAPSHOT_NAME, null).get();
 
         StringBuilder b = new StringBuilder();
         res.print(b::append, true);
@@ -147,7 +147,7 @@ public class IgniteClusterSnapshotCheckTest extends AbstractSnapshotSelfTest {
         assertTrue(part0.toString(), part0.toFile().exists());
         assertTrue(part0.toFile().delete());
 
-        IdleVerifyResultV2 res = snp(ignite).checkSnapshot(SNAPSHOT_NAME).get();
+        IdleVerifyResultV2 res = snp(ignite).checkSnapshot(SNAPSHOT_NAME, null).get();
 
         StringBuilder b = new StringBuilder();
         res.print(b::append, true);
@@ -172,7 +172,7 @@ public class IgniteClusterSnapshotCheckTest extends AbstractSnapshotSelfTest {
         assertTrue(dir.toString(), dir.toFile().exists());
         assertTrue(U.delete(dir));
 
-        IdleVerifyResultV2 res = snp(ignite).checkSnapshot(SNAPSHOT_NAME).get();
+        IdleVerifyResultV2 res = snp(ignite).checkSnapshot(SNAPSHOT_NAME, null).get();
 
         StringBuilder b = new StringBuilder();
         res.print(b::append, true);
@@ -196,7 +196,7 @@ public class IgniteClusterSnapshotCheckTest extends AbstractSnapshotSelfTest {
         assertTrue(smfs[0].toString(), smfs[0].exists());
         assertTrue(U.delete(smfs[0]));
 
-        IdleVerifyResultV2 res = snp(ignite).checkSnapshot(SNAPSHOT_NAME).get();
+        IdleVerifyResultV2 res = snp(ignite).checkSnapshot(SNAPSHOT_NAME, null).get();
 
         StringBuilder b = new StringBuilder();
         res.print(b::append, true);
@@ -217,7 +217,7 @@ public class IgniteClusterSnapshotCheckTest extends AbstractSnapshotSelfTest {
 
         ig0.snapshot().createSnapshot(SNAPSHOT_NAME).get();
 
-        IdleVerifyResultV2 res = snp(ig0).checkSnapshot(SNAPSHOT_NAME).get();
+        IdleVerifyResultV2 res = snp(ig0).checkSnapshot(SNAPSHOT_NAME, null).get();
 
         StringBuilder b = new StringBuilder();
         res.print(b::append, true);
@@ -270,7 +270,7 @@ public class IgniteClusterSnapshotCheckTest extends AbstractSnapshotSelfTest {
             pageStore.finishRecover();
         }
 
-        IdleVerifyResultV2 res = snp(ignite).checkSnapshot(SNAPSHOT_NAME).get();
+        IdleVerifyResultV2 res = snp(ignite).checkSnapshot(SNAPSHOT_NAME, null).get();
 
         StringBuilder b = new StringBuilder();
         res.print(b::append, true);
@@ -318,7 +318,7 @@ public class IgniteClusterSnapshotCheckTest extends AbstractSnapshotSelfTest {
         ignite.cluster().baselineAutoAdjustEnabled(false);
         ignite.cluster().state(ACTIVE);
 
-        IdleVerifyResultV2 res = snp(ignite).checkSnapshot(SNAPSHOT_NAME).get();
+        IdleVerifyResultV2 res = snp(ignite).checkSnapshot(SNAPSHOT_NAME, null).get();
 
         StringBuilder b = new StringBuilder();
         res.print(b::append, true);
@@ -342,7 +342,7 @@ public class IgniteClusterSnapshotCheckTest extends AbstractSnapshotSelfTest {
 
         corruptPartitionFile(ignite, SNAPSHOT_NAME, dfltCacheCfg, PART_ID);
 
-        IdleVerifyResultV2 res = snp(ignite).checkSnapshot(SNAPSHOT_NAME).get();
+        IdleVerifyResultV2 res = snp(ignite).checkSnapshot(SNAPSHOT_NAME, null).get();
 
         StringBuilder b = new StringBuilder();
         res.print(b::append, true);
@@ -448,7 +448,7 @@ public class IgniteClusterSnapshotCheckTest extends AbstractSnapshotSelfTest {
         assertNotNull(part0);
         assertTrue(part0.toString(), part0.toFile().exists());
 
-        IdleVerifyResultV2 res = snp(ignite).checkSnapshot(SNAPSHOT_NAME).get();
+        IdleVerifyResultV2 res = snp(ignite).checkSnapshot(SNAPSHOT_NAME, null).get();
 
         StringBuilder b = new StringBuilder();
         res.print(b::append, true);
@@ -477,8 +477,8 @@ public class IgniteClusterSnapshotCheckTest extends AbstractSnapshotSelfTest {
 
         IdleVerifyResultV2 snpVerifyRes = ignite.compute().execute(new TestSnapshotPartitionsVerifyTask(),
             new SnapshotPartitionsVerifyTaskArg(new HashSet<>(), Collections.singletonMap(ignite.cluster().localNode(),
-                Collections.singletonList(snp(ignite).readSnapshotMetadata(SNAPSHOT_NAME,
-                    (String)ignite.configuration().getConsistentId())))))
+                Collections.singletonList(snp(ignite).readSnapshotMetadata(snp(ignite).snapshotLocalDir(SNAPSHOT_NAME),
+                    (String)ignite.configuration().getConsistentId()))), null))
             .idleVerifyResult();
 
         Map<PartitionKeyV2, List<PartitionHashRecordV2>> idleVerifyHashes = jobResults.get(TestVisorBackupPartitionsTask.class);
@@ -548,12 +548,12 @@ public class IgniteClusterSnapshotCheckTest extends AbstractSnapshotSelfTest {
 
         // Warmup.
         for (int i = 0; i < iterations; i++)
-            snp(ignite).checkSnapshot(SNAPSHOT_NAME).get();
+            snp(ignite).checkSnapshot(SNAPSHOT_NAME, null).get();
 
         int activeThreadsCntBefore = Thread.activeCount();
 
         for (int i = 0; i < iterations; i++)
-            snp(ignite).checkSnapshot(SNAPSHOT_NAME).get();
+            snp(ignite).checkSnapshot(SNAPSHOT_NAME, null).get();
 
         int createdThreads = Thread.activeCount() - activeThreadsCntBefore;
 
@@ -599,7 +599,7 @@ public class IgniteClusterSnapshotCheckTest extends AbstractSnapshotSelfTest {
 
         corruptPartitionFile(ignite, SNAPSHOT_NAME, ccfg1, PART_ID);
 
-        return snp(ignite).checkSnapshot(SNAPSHOT_NAME, cachesToCheck, false).get(TIMEOUT);
+        return snp(ignite).checkSnapshot(SNAPSHOT_NAME, null, cachesToCheck, false).get(TIMEOUT);
     }
 
     /**
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotHandlerTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotHandlerTest.java
index 6f87fd502f9..a3b4600b5b0 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotHandlerTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotHandlerTest.java
@@ -150,9 +150,10 @@ public class IgniteClusterSnapshotHandlerTest extends IgniteClusterSnapshotResto
         for (Ignite grid : G.allGrids()) {
             IgniteSnapshotManager snpMgr = ((IgniteEx)grid).context().cache().context().snapshotMgr();
             String constId = grid.cluster().localNode().consistentId().toString();
+            File snpDir = snpMgr.snapshotLocalDir(SNAPSHOT_NAME);
 
-            SnapshotMetadata metadata = snpMgr.readSnapshotMetadata(SNAPSHOT_NAME, constId);
-            File smf = new File(snpMgr.snapshotLocalDir(SNAPSHOT_NAME), U.maskForFileName(constId) + SNAPSHOT_METAFILE_EXT);
+            SnapshotMetadata metadata = snpMgr.readSnapshotMetadata(snpDir, constId);
+            File smf = new File(snpDir, U.maskForFileName(constId) + SNAPSHOT_METAFILE_EXT);
 
             try (OutputStream out = new BufferedOutputStream(new FileOutputStream(smf))) {
                 GridTestUtils.setFieldValue(metadata, "rqId", newReqId);
@@ -348,4 +349,60 @@ public class IgniteClusterSnapshotHandlerTest extends IgniteClusterSnapshotResto
         startGrid(0);
         grid(0).snapshot().createSnapshot(SNAPSHOT_NAME);
     }
+
+    /**
+     * Test ensures that the snapshot path is set correctly in the handler context.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testHandlerSnapshotLocation() throws Exception {
+        String snpName = "snapshot_30052022";
+        File snpDir = U.resolveWorkDirectory(U.defaultWorkDirectory(), "ex_snapshots", true);
+        String expFullPath = new File(snpDir, snpName).getAbsolutePath();
+
+        SnapshotHandler<Void> createHnd = new SnapshotHandler<Void>() {
+            @Override public SnapshotHandlerType type() {
+                return SnapshotHandlerType.CREATE;
+            }
+
+            @Override public Void invoke(SnapshotHandlerContext ctx) {
+                if (!expFullPath.equals(ctx.snapshotDirectory().getAbsolutePath()))
+                    throw new IllegalStateException("Expected " + expFullPath + ", actual " + ctx.snapshotDirectory());
+
+                return null;
+            }
+        };
+
+        SnapshotHandler<Void> restoreHnd = new SnapshotHandler<Void>() {
+            @Override public SnapshotHandlerType type() {
+                return SnapshotHandlerType.RESTORE;
+            }
+
+            @Override public Void invoke(SnapshotHandlerContext ctx) throws Exception {
+                createHnd.invoke(ctx);
+
+                return null;
+            }
+        };
+
+        handlers.add(createHnd);
+        handlers.add(restoreHnd);
+
+        try {
+            IgniteEx ignite = startGridsWithCache(1, CACHE_KEYS_RANGE, valueBuilder(), dfltCacheCfg);
+
+            IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr();
+
+            snpMgr.createSnapshot(snpName, snpDir.getAbsolutePath()).get(TIMEOUT);
+
+            ignite.destroyCache(DEFAULT_CACHE_NAME);
+            awaitPartitionMapExchange();
+
+            snpMgr.restoreSnapshot(snpName, snpDir.getAbsolutePath(), null).get(TIMEOUT);
+        }
+        finally {
+            U.delete(snpDir);
+        }
+    }
 }
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java
index af70b8efe1c..8cb9049fd49 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java
@@ -56,8 +56,10 @@ import org.apache.ignite.internal.processors.cache.persistence.file.FileIO;
 import org.apache.ignite.internal.processors.cache.persistence.file.FileIOFactory;
 import org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager;
 import org.apache.ignite.internal.processors.cache.persistence.file.RandomAccessFileIOFactory;
+import org.apache.ignite.internal.processors.cache.verify.IdleVerifyResultV2;
 import org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType;
 import org.apache.ignite.internal.util.distributed.SingleNodeMessage;
+import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.G;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.lang.IgniteFuture;
@@ -66,6 +68,7 @@ import org.apache.ignite.testframework.GridTestUtils;
 import org.jetbrains.annotations.Nullable;
 import org.junit.Test;
 
+import static org.apache.ignite.cluster.ClusterState.ACTIVE;
 import static org.apache.ignite.events.EventType.EVT_CLUSTER_SNAPSHOT_RESTORE_FAILED;
 import static org.apache.ignite.events.EventType.EVT_CLUSTER_SNAPSHOT_RESTORE_FINISHED;
 import static org.apache.ignite.events.EventType.EVT_CLUSTER_SNAPSHOT_RESTORE_STARTED;
@@ -77,6 +80,7 @@ import static org.apache.ignite.internal.processors.cache.persistence.snapshot.S
 import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PRELOAD;
 import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE;
 import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_START;
+import static org.apache.ignite.testframework.GridTestUtils.assertContains;
 import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause;
 import static org.apache.ignite.testframework.GridTestUtils.runAsync;
 
@@ -116,6 +120,53 @@ public class IgniteClusterSnapshotRestoreSelfTest extends IgniteClusterSnapshotR
         assertCacheKeys(ignite.cache(DEFAULT_CACHE_NAME), keysCnt);
     }
 
+    /**
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testClusterSnapshotRestoreFromCustomDir() throws Exception {
+        File snpDir = U.resolveWorkDirectory(U.defaultWorkDirectory(), "ex_snapshots", true);
+
+        assertTrue("Target directory is not empty: " + snpDir, F.isEmpty(snpDir.list()));
+
+        try {
+            IgniteEx ignite = startGrids(2);
+            ignite.cluster().state(ACTIVE);
+
+            for (int i = 0; i < CACHE_KEYS_RANGE; i++)
+                ignite.cache(DEFAULT_CACHE_NAME).put(i, i);
+
+            ignite.context().cache().context().snapshotMgr().createSnapshot(SNAPSHOT_NAME, snpDir.toString()).get(TIMEOUT);
+
+            // Check snapshot.
+            IdleVerifyResultV2 res = snp(ignite).checkSnapshot(SNAPSHOT_NAME, snpDir.getAbsolutePath()).get(TIMEOUT);
+
+            StringBuilder sb = new StringBuilder();
+            res.print(sb::append, true);
+
+            assertTrue(F.isEmpty(res.exceptions()));
+            assertPartitionsSame(res);
+            assertContains(log, sb.toString(), "The check procedure has finished, no conflicts have been found");
+
+            ignite.destroyCache(DEFAULT_CACHE_NAME);
+            awaitPartitionMapExchange();
+
+            ignite.context().cache().context().snapshotMgr().restoreSnapshot(SNAPSHOT_NAME, snpDir.getAbsolutePath(), null)
+                .get();
+
+            IgniteCache<Object, Object> cache = ignite.cache(DEFAULT_CACHE_NAME);
+
+            assert cache != null;
+
+            assertSnapshotCacheKeys(cache);
+        }
+        finally {
+            stopAllGrids();
+
+            U.delete(snpDir);
+        }
+    }
+
     /**
      * Ensures that system partition verification task is invoked before restoring the snapshot.
      *
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 170774daa40..3342619fe38 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
@@ -35,7 +35,7 @@ import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Function;
+import java.util.function.BiFunction;
 import java.util.function.Predicate;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteCache;
@@ -72,6 +72,7 @@ import org.apache.ignite.internal.processors.metric.impl.ObjectGauge;
 import org.apache.ignite.internal.util.distributed.DistributedProcess;
 import org.apache.ignite.internal.util.distributed.FullMessage;
 import org.apache.ignite.internal.util.distributed.SingleNodeMessage;
+import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.G;
 import org.apache.ignite.internal.util.typedef.T2;
 import org.apache.ignite.internal.util.typedef.internal.CU;
@@ -711,7 +712,18 @@ public class IgniteClusterSnapshotSelfTest extends AbstractSnapshotSelfTest {
     /** @throws Exception If fails. */
     @Test
     public void testClusterSnapshotWithExplicitPath() throws Exception {
-        File exSnpDir = U.resolveWorkDirectory(U.defaultWorkDirectory(), "ex_snapshots", true);
+        doTestClusterSnapshotWithExplicitPath(true);
+        doTestClusterSnapshotWithExplicitPath(false);
+    }
+
+    /**
+     * @param cfgPath {@code True} to set destination path using configuration, {@code False} using runtime parameter.
+     * @throws Exception If failed.
+     */
+    private void doTestClusterSnapshotWithExplicitPath(boolean cfgPath) throws Exception {
+        File snpDir = U.resolveWorkDirectory(U.defaultWorkDirectory(), "ex_snapshots", true);
+
+        assertTrue("Target directory is not empty: " + snpDir, F.isEmpty(snpDir.list()));
 
         try {
             IgniteEx ignite = null;
@@ -719,7 +731,8 @@ public class IgniteClusterSnapshotSelfTest extends AbstractSnapshotSelfTest {
             for (int i = 0; i < 2; i++) {
                 IgniteConfiguration cfg = optimize(getConfiguration(getTestIgniteInstanceName(i)));
 
-                cfg.setSnapshotPath(exSnpDir.getAbsolutePath());
+                if (cfgPath)
+                    cfg.setSnapshotPath(snpDir.getAbsolutePath());
 
                 ignite = startGrid(cfg);
             }
@@ -730,19 +743,19 @@ public class IgniteClusterSnapshotSelfTest extends AbstractSnapshotSelfTest {
             for (int i = 0; i < CACHE_KEYS_RANGE; i++)
                 ignite.cache(DEFAULT_CACHE_NAME).put(i, i);
 
-            ignite.snapshot().createSnapshot(SNAPSHOT_NAME)
-                .get();
+            ignite.context().cache().context().snapshotMgr()
+                .createSnapshot(SNAPSHOT_NAME, cfgPath ? null : snpDir.getAbsolutePath()).get();
 
             stopAllGrids();
 
-            IgniteEx snp = startGridsFromSnapshot(2, cfg -> exSnpDir.getAbsolutePath(), SNAPSHOT_NAME, true);
+            IgniteEx snp = startGridsFromSnapshot(2, cfg -> snpDir.getAbsolutePath(), SNAPSHOT_NAME, true);
 
             assertSnapshotCacheKeys(snp.cache(dfltCacheCfg.getName()));
         }
         finally {
             stopAllGrids();
 
-            U.delete(exSnpDir);
+            U.delete(snpDir);
         }
     }
 
@@ -1094,12 +1107,12 @@ public class IgniteClusterSnapshotSelfTest extends AbstractSnapshotSelfTest {
         IgniteEx clnt = startClientGrid(2);
 
         IgniteSnapshotManager mgr = snp(grid);
-        Function<String, SnapshotSender> old = mgr.localSnapshotSenderFactory();
+        BiFunction<String, String, SnapshotSender> old = mgr.localSnapshotSenderFactory();
 
         BlockingExecutor block = new BlockingExecutor(mgr.snapshotExecutorService());
 
-        mgr.localSnapshotSenderFactory((snpName) ->
-            new DelegateSnapshotSender(log, block, old.apply(snpName)));
+        mgr.localSnapshotSenderFactory((snpName, snpPath) ->
+            new DelegateSnapshotSender(log, block, old.apply(snpName, snpPath)));
 
         IgniteFuture<Void> fut = grid.snapshot().createSnapshot(SNAPSHOT_NAME);
 
@@ -1160,13 +1173,13 @@ public class IgniteClusterSnapshotSelfTest extends AbstractSnapshotSelfTest {
      * @param blocked Latch to await delta partition processing.
      * @return Factory which produces local snapshot senders.
      */
-    private Function<String, SnapshotSender> blockingLocalSnapshotSender(IgniteEx ignite,
+    private BiFunction<String, String, SnapshotSender> blockingLocalSnapshotSender(IgniteEx ignite,
         CountDownLatch started,
         CountDownLatch blocked
     ) {
-        Function<String, SnapshotSender> old = snp(ignite).localSnapshotSenderFactory();
+        BiFunction<String, String, SnapshotSender> old = snp(ignite).localSnapshotSenderFactory();
 
-        return (snpName) -> new DelegateSnapshotSender(log, snp(ignite).snapshotExecutorService(), old.apply(snpName)) {
+        return (snpName, snpPath) -> new DelegateSnapshotSender(log, snp(ignite).snapshotExecutorService(), old.apply(snpName, null)) {
             @Override public void sendDelta0(File delta, String cacheDirName, GroupPartitionId pair) {
                 if (log.isInfoEnabled())
                     log.info("Processing delta file has been blocked: " + delta.getName());
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 194008b568d..fd68a57b5f8 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
@@ -64,7 +64,7 @@ public class IgniteSnapshotMXBeanTest extends AbstractSnapshotSelfTest {
 
         SnapshotMXBean mxBean = getMxBean(ignite.name(), METRIC_GROUP, SnapshotMXBeanImpl.class, SnapshotMXBean.class);
 
-        mxBean.createSnapshot(SNAPSHOT_NAME);
+        mxBean.createSnapshot(SNAPSHOT_NAME, "");
 
         assertTrue("Waiting for snapshot operation failed.",
             GridTestUtils.waitForCondition(() -> (long)getMetric("LastSnapshotEndTime", snpMBean) > 0, TIMEOUT));
@@ -95,10 +95,6 @@ public class IgniteSnapshotMXBeanTest extends AbstractSnapshotSelfTest {
     /** @throws Exception If fails. */
     @Test
     public void testRestoreSnapshot() throws Exception {
-        // TODO IGNITE-14999 Support dynamic restoration of encrypted snapshots.
-        if (encryption)
-            return;
-
         IgniteEx ignite = startGridsWithSnapshot(2, CACHE_KEYS_RANGE, false);
 
         DynamicMBean mReg0 = metricRegistry(grid(0).name(), null, SNAPSHOT_RESTORE_METRICS);
@@ -108,7 +104,7 @@ public class IgniteSnapshotMXBeanTest extends AbstractSnapshotSelfTest {
         assertEquals(0, (long)getMetric("endTime", mReg1));
 
         getMxBean(ignite.name(), METRIC_GROUP, SnapshotMXBeanImpl.class, SnapshotMXBean.class)
-            .restoreSnapshot(SNAPSHOT_NAME, null);
+            .restoreSnapshot(SNAPSHOT_NAME, "", "");
 
         assertTrue(GridTestUtils.waitForCondition(() -> (long)getMetric("endTime", mReg0) > 0, TIMEOUT));
         assertTrue(GridTestUtils.waitForCondition(() -> (long)getMetric("endTime", mReg1) > 0, TIMEOUT));
@@ -119,10 +115,6 @@ public class IgniteSnapshotMXBeanTest extends AbstractSnapshotSelfTest {
     /** @throws Exception If fails. */
     @Test
     public void testCancelRestoreSnapshot() throws Exception {
-        // TODO IGNITE-14999 Support dynamic restoration of encrypted snapshots.
-        if (encryption)
-            return;
-
         IgniteEx ignite = startGridsWithSnapshot(2, CACHE_KEYS_RANGE, false);
         SnapshotMXBean mxBean = getMxBean(ignite.name(), METRIC_GROUP, SnapshotMXBeanImpl.class, SnapshotMXBean.class);
         DynamicMBean mReg0 = metricRegistry(grid(0).name(), null, SNAPSHOT_RESTORE_METRICS);
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 60bf32fd2fb..ee34e16da4e 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
@@ -146,7 +146,7 @@ public class IgniteSnapshotManagerSelfTest extends AbstractSnapshotSelfTest {
             cctx.localNodeId(),
             F.asMap(CU.cacheId(DEFAULT_CACHE_NAME), null),
             encryption,
-            new DelegateSnapshotSender(log, mgr.snapshotExecutorService(), mgr.localSnapshotSenderFactory().apply(SNAPSHOT_NAME)) {
+            new DelegateSnapshotSender(log, mgr.snapshotExecutorService(), mgr.localSnapshotSenderFactory().apply(SNAPSHOT_NAME, null)) {
                 @Override public void sendPart0(File part, String cacheDirName, GroupPartitionId pair, Long length) {
                     try {
                         U.await(slowCopy);
@@ -268,7 +268,7 @@ public class IgniteSnapshotManagerSelfTest extends AbstractSnapshotSelfTest {
             SNAPSHOT_NAME,
             F.asMap(CU.cacheId(DEFAULT_CACHE_NAME), null),
             encryption,
-            mgr.localSnapshotSenderFactory().apply(SNAPSHOT_NAME));
+            mgr.localSnapshotSenderFactory().apply(SNAPSHOT_NAME, null));
 
         // Check the right exception thrown.
         assertThrowsAnyCause(log,
@@ -293,7 +293,7 @@ public class IgniteSnapshotManagerSelfTest extends AbstractSnapshotSelfTest {
             parts,
             encryption,
             new DelegateSnapshotSender(log, mgr0.snapshotExecutorService(),
-                mgr0.localSnapshotSenderFactory().apply(SNAPSHOT_NAME)) {
+                mgr0.localSnapshotSenderFactory().apply(SNAPSHOT_NAME, null)) {
                 @Override public void sendPart0(File part, String cacheDirName, GroupPartitionId pair, Long length) {
                     if (pair.getPartitionId() == 0)
                         throw new IgniteException(err_msg + pair);
@@ -328,7 +328,7 @@ public class IgniteSnapshotManagerSelfTest extends AbstractSnapshotSelfTest {
             SNAPSHOT_NAME,
             F.asMap(CU.cacheId(DEFAULT_CACHE_NAME), null),
             encryption,
-            new DelegateSnapshotSender(log, mgr.snapshotExecutorService(), mgr.localSnapshotSenderFactory().apply(SNAPSHOT_NAME)) {
+            new DelegateSnapshotSender(log, mgr.snapshotExecutorService(), mgr.localSnapshotSenderFactory().apply(SNAPSHOT_NAME, null)) {
                 @Override public void sendPart0(File part, String cacheDirName, GroupPartitionId pair, Long length) {
                     try {
                         U.await(cpLatch);
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotRemoteRequestTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotRemoteRequestTest.java
index 8d788360ae4..0ef0740743c 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotRemoteRequestTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotRemoteRequestTest.java
@@ -79,10 +79,11 @@ public class IgniteSnapshotRemoteRequestTest extends IgniteClusterSnapshotRestor
                 for (Map.Entry<Integer, Set<Integer>> e : parts.entrySet())
                     parts0.computeIfAbsent(e.getKey(), k -> new HashSet<>()).addAll(e.getValue());
 
-                IgniteInternalFuture<Void> locFut = null;
+                IgniteInternalFuture<Void> locFut;
 
                 compFut.add(locFut = snp(ignite).requestRemoteSnapshotFiles(grid(1).localNode().id(),
                     SNAPSHOT_NAME,
+                    null,
                     parts,
                     () -> false,
                     defaultPartitionConsumer(parts0, latch)));
@@ -123,9 +124,9 @@ public class IgniteSnapshotRemoteRequestTest extends IgniteClusterSnapshotRestor
             fromNode0.values().stream().mapToInt(Set::size).sum());
 
         // Snapshot must be taken on node1 and transmitted to node0.
-        IgniteInternalFuture<?> futFrom1To0 = mgr0.requestRemoteSnapshotFiles(node1, SNAPSHOT_NAME, fromNode1, () -> false,
+        IgniteInternalFuture<?> futFrom1To0 = mgr0.requestRemoteSnapshotFiles(node1, SNAPSHOT_NAME, null, fromNode1, () -> false,
             defaultPartitionConsumer(fromNode1, latch));
-        IgniteInternalFuture<?> futFrom0To1 = mgr1.requestRemoteSnapshotFiles(node0, SNAPSHOT_NAME, fromNode0, () -> false,
+        IgniteInternalFuture<?> futFrom0To1 = mgr1.requestRemoteSnapshotFiles(node0, SNAPSHOT_NAME, null, fromNode0, () -> false,
             defaultPartitionConsumer(fromNode0, latch));
 
         G.allGrids().forEach(g -> TestRecordingCommunicationSpi.spi(g).stopBlock());
@@ -174,6 +175,7 @@ public class IgniteSnapshotRemoteRequestTest extends IgniteClusterSnapshotRestor
 
         snp(ignite).requestRemoteSnapshotFiles(grid(1).localNode().id(),
             SNAPSHOT_NAME,
+            null,
             parts,
             () -> false,
             (part, t) -> {
@@ -215,6 +217,7 @@ public class IgniteSnapshotRemoteRequestTest extends IgniteClusterSnapshotRestor
 
         IgniteInternalFuture<?> fut = snp(ignite).requestRemoteSnapshotFiles(grid(1).localNode().id(),
             SNAPSHOT_NAME,
+            null,
             parts,
             () -> false,
             (part, t) -> {
@@ -261,6 +264,7 @@ public class IgniteSnapshotRemoteRequestTest extends IgniteClusterSnapshotRestor
 
         IgniteInternalFuture<Void> fut = snp(ignite).requestRemoteSnapshotFiles(grid(1).localNode().id(),
             SNAPSHOT_NAME,
+            null,
             parts,
             stopChecker::get,
             (part, t) -> {
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotRestoreFromRemoteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotRestoreFromRemoteTest.java
index fa75b5628f8..3c4d3ae3221 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotRestoreFromRemoteTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotRestoreFromRemoteTest.java
@@ -196,7 +196,7 @@ public class IgniteSnapshotRestoreFromRemoteTest extends IgniteClusterSnapshotRe
 
         // Ensure that the snapshot check command succeeds.
         IdleVerifyResultV2 res =
-            emptyNode.context().cache().context().snapshotMgr().checkSnapshot(SNAPSHOT_NAME).get(TIMEOUT);
+            emptyNode.context().cache().context().snapshotMgr().checkSnapshot(SNAPSHOT_NAME, null).get(TIMEOUT);
 
         StringBuilder buf = new StringBuilder();
         res.print(buf::append, true);
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/PlainSnapshotTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/PlainSnapshotTest.java
index 21cdfdb3311..3e0a32c3c26 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/PlainSnapshotTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/PlainSnapshotTest.java
@@ -85,7 +85,7 @@ public class PlainSnapshotTest extends AbstractSnapshotSelfTest {
         IgniteInternalFuture<?> snpFut = startLocalSnapshotTask(cctx,
             SNAPSHOT_NAME,
             F.asMap(CU.cacheId(DEFAULT_CACHE_NAME), null),
-            false, mgr.localSnapshotSenderFactory().apply(SNAPSHOT_NAME));
+            false, mgr.localSnapshotSenderFactory().apply(SNAPSHOT_NAME, null));
 
         snpFut.get();
 
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 b70f2955cbe..39024a5c748 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
@@ -140,11 +140,12 @@ This utility can do the following commands:
       snapshot_name  - Snapshot name.
 
   Create cluster snapshot:
-    control.(sh|bat) --snapshot create snapshot_name [--sync]
+    control.(sh|bat) --snapshot create snapshot_name [--dest path] [--sync]
 
     Parameters:
       snapshot_name  - Snapshot name.
-      sync           - Run the operation synchronously, the command will wait for the entire operation to complete. Otherwise, it will be performed in the background, and the command will immediately return control.
+      --dest path    - Path to the directory where the snapshot will be saved. If not specified, the default configured snapshot directory will be used.
+      --sync         - Run the operation synchronously, the command will wait for the entire operation to complete. Otherwise, it will be performed in the background, and the command will immediately return control.
 
   Cancel running snapshot:
     control.(sh|bat) --snapshot cancel snapshot_name
@@ -153,18 +154,20 @@ This utility can do the following commands:
       snapshot_name  - Snapshot name.
 
   Check snapshot:
-    control.(sh|bat) --snapshot check snapshot_name
+    control.(sh|bat) --snapshot check snapshot_name [--src path]
 
     Parameters:
       snapshot_name  - Snapshot name.
+      --src path     - Path to the directory where the snapshot files are located. If not specified, the default configured snapshot directory will be used.
 
   Restore snapshot:
-    control.(sh|bat) --snapshot restore snapshot_name --start [--groups group1,...groupN] [--sync]
+    control.(sh|bat) --snapshot restore snapshot_name --start [--groups group1,...groupN] [--src path] [--sync]
 
     Parameters:
-      snapshot_name     - Snapshot name.
-      group1,...groupN  - Cache group names.
-      sync              - Run the operation synchronously, the command will wait for the entire operation to complete. Otherwise, it will be performed in the background, and the command will immediately return control.
+      snapshot_name              - Snapshot name.
+      --groups group1,...groupN  - Cache group names.
+      --src path                 - Path to the directory where the snapshot files are located. If not specified, the default configured snapshot directory will be used.
+      --sync                     - Run the operation synchronously, the command will wait for the entire operation to complete. Otherwise, it will be performed in the background, and the command will immediately return control.
 
   Snapshot restore operation status:
     control.(sh|bat) --snapshot restore snapshot_name --status
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 b70f2955cbe..39024a5c748 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
@@ -140,11 +140,12 @@ This utility can do the following commands:
       snapshot_name  - Snapshot name.
 
   Create cluster snapshot:
-    control.(sh|bat) --snapshot create snapshot_name [--sync]
+    control.(sh|bat) --snapshot create snapshot_name [--dest path] [--sync]
 
     Parameters:
       snapshot_name  - Snapshot name.
-      sync           - Run the operation synchronously, the command will wait for the entire operation to complete. Otherwise, it will be performed in the background, and the command will immediately return control.
+      --dest path    - Path to the directory where the snapshot will be saved. If not specified, the default configured snapshot directory will be used.
+      --sync         - Run the operation synchronously, the command will wait for the entire operation to complete. Otherwise, it will be performed in the background, and the command will immediately return control.
 
   Cancel running snapshot:
     control.(sh|bat) --snapshot cancel snapshot_name
@@ -153,18 +154,20 @@ This utility can do the following commands:
       snapshot_name  - Snapshot name.
 
   Check snapshot:
-    control.(sh|bat) --snapshot check snapshot_name
+    control.(sh|bat) --snapshot check snapshot_name [--src path]
 
     Parameters:
       snapshot_name  - Snapshot name.
+      --src path     - Path to the directory where the snapshot files are located. If not specified, the default configured snapshot directory will be used.
 
   Restore snapshot:
-    control.(sh|bat) --snapshot restore snapshot_name --start [--groups group1,...groupN] [--sync]
+    control.(sh|bat) --snapshot restore snapshot_name --start [--groups group1,...groupN] [--src path] [--sync]
 
     Parameters:
-      snapshot_name     - Snapshot name.
-      group1,...groupN  - Cache group names.
-      sync              - Run the operation synchronously, the command will wait for the entire operation to complete. Otherwise, it will be performed in the background, and the command will immediately return control.
+      snapshot_name              - Snapshot name.
+      --groups group1,...groupN  - Cache group names.
+      --src path                 - Path to the directory where the snapshot files are located. If not specified, the default configured snapshot directory will be used.
+      --sync                     - Run the operation synchronously, the command will wait for the entire operation to complete. Otherwise, it will be performed in the background, and the command will immediately return control.
 
   Snapshot restore operation status:
     control.(sh|bat) --snapshot restore snapshot_name --status
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckWithIndexesTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckWithIndexesTest.java
index cd2b0caa5f4..54819c6bce5 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckWithIndexesTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckWithIndexesTest.java
@@ -44,7 +44,7 @@ public class IgniteClusterSnapshotCheckWithIndexesTest extends AbstractSnapshotS
 
         ignite.snapshot().createSnapshot(SNAPSHOT_NAME).get(TIMEOUT);
 
-        IdleVerifyResultV2 res = ignite.context().cache().context().snapshotMgr().checkSnapshot(SNAPSHOT_NAME).get();
+        IdleVerifyResultV2 res = ignite.context().cache().context().snapshotMgr().checkSnapshot(SNAPSHOT_NAME, null).get();
 
         StringBuilder b = new StringBuilder();
         res.print(b::append, true);
@@ -61,7 +61,7 @@ public class IgniteClusterSnapshotCheckWithIndexesTest extends AbstractSnapshotS
 
         ignite.snapshot().createSnapshot(SNAPSHOT_NAME).get(TIMEOUT);
 
-        IdleVerifyResultV2 res = ignite.context().cache().context().snapshotMgr().checkSnapshot(SNAPSHOT_NAME).get();
+        IdleVerifyResultV2 res = ignite.context().cache().context().snapshotMgr().checkSnapshot(SNAPSHOT_NAME, null).get();
 
         StringBuilder b = new StringBuilder();
         res.print(b::append, true);
@@ -87,7 +87,7 @@ public class IgniteClusterSnapshotCheckWithIndexesTest extends AbstractSnapshotS
 
         grid(0).snapshot().createSnapshot(SNAPSHOT_NAME).get(TIMEOUT);
 
-        IdleVerifyResultV2 res = grid(0).context().cache().context().snapshotMgr().checkSnapshot(SNAPSHOT_NAME).get();
+        IdleVerifyResultV2 res = grid(0).context().cache().context().snapshotMgr().checkSnapshot(SNAPSHOT_NAME, null).get();
 
         StringBuilder b = new StringBuilder();
         res.print(b::append, true);