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

sergeychugunov 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 78f1043  IGNITE-13550 Persistence CLEAN command implementation - Fixes 
#8408.
78f1043 is described below

commit 78f1043330e8c92fc330c1e851e6ce8c4f17e739
Author: Sergey Chugunov <sergey.chugu...@gmail.com>
AuthorDate: Tue Nov 3 17:15:24 2020 +0300

    IGNITE-13550 Persistence CLEAN command implementation - Fixes #8408.
    
    Signed-off-by: Sergey Chugunov <sergey.chugu...@gmail.com>
---
 .../ignite/internal/commandline/CommandList.java   |   5 +-
 .../internal/commandline/PersistenceCommand.java   | 290 +++++++++++++++
 .../persistence/CleanAndBackupSubcommandArg.java   |  45 +++
 .../persistence/PersistenceArguments.java          |  98 +++++
 .../persistence/PersistenceSubcommands.java        |  73 ++++
 .../apache/ignite/util/GridCommandHandlerTest.java | 366 +++++++++++++++++++
 .../internal/maintenance/MaintenanceProcessor.java |  15 +-
 ...a => CheckCorruptedCacheStoresCleanAction.java} |  49 +--
 .../CorruptedPdsMaintenanceCallback.java           |   4 +-
 .../PersistenceCleanAndBackupSettings.java         |  71 ++++
 .../persistence/PersistenceCleanAndBackupType.java |  41 +++
 .../visor/persistence/PersistenceOperation.java    |  43 +++
 .../visor/persistence/PersistenceTask.java         | 401 +++++++++++++++++++++
 .../visor/persistence/PersistenceTaskArg.java      |  82 +++++
 .../visor/persistence/PersistenceTaskResult.java   | 123 +++++++
 .../apache/ignite/maintenance/MaintenanceTask.java |   4 +-
 .../main/resources/META-INF/classnames.properties  |  22 +-
 ...ridCommandHandlerClusterByClassTest_help.output |  24 ++
 ...andHandlerClusterByClassWithSSLTest_help.output |  24 ++
 19 files changed, 1723 insertions(+), 57 deletions(-)

diff --git 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandList.java
 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandList.java
index 2daaf86..e16acaa 100644
--- 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandList.java
+++ 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/CommandList.java
@@ -89,7 +89,10 @@ public enum CommandList {
     SYSTEM_VIEW("--system-view", new SystemViewCommand()),
 
     /** Command for printing metric values. */
-    METRIC("--metric", new MetricCommand());
+    METRIC("--metric", new MetricCommand()),
+
+    /** */
+    PERSISTENCE("--persistence", new PersistenceCommand());
 
     /** Private values copy so there's no need in cloning it every time. */
     private static final CommandList[] VALUES = CommandList.values();
diff --git 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/PersistenceCommand.java
 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/PersistenceCommand.java
new file mode 100644
index 0000000..d41269a
--- /dev/null
+++ 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/PersistenceCommand.java
@@ -0,0 +1,290 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.logging.Logger;
+
+import org.apache.ignite.internal.client.GridClient;
+import org.apache.ignite.internal.client.GridClientConfiguration;
+import org.apache.ignite.internal.client.GridClientNode;
+import org.apache.ignite.internal.commandline.argument.CommandArgUtils;
+import 
org.apache.ignite.internal.commandline.persistence.CleanAndBackupSubcommandArg;
+import org.apache.ignite.internal.commandline.persistence.PersistenceArguments;
+import 
org.apache.ignite.internal.commandline.persistence.PersistenceSubcommands;
+import org.apache.ignite.internal.util.typedef.F;
+import 
org.apache.ignite.internal.visor.persistence.PersistenceCleanAndBackupSettings;
+import 
org.apache.ignite.internal.visor.persistence.PersistenceCleanAndBackupType;
+import org.apache.ignite.internal.visor.persistence.PersistenceTask;
+import org.apache.ignite.internal.visor.persistence.PersistenceTaskArg;
+import org.apache.ignite.internal.visor.persistence.PersistenceTaskResult;
+import org.apache.ignite.lang.IgniteBiTuple;
+
+import static org.apache.ignite.internal.commandline.Command.usage;
+import static org.apache.ignite.internal.commandline.CommandList.PERSISTENCE;
+import static org.apache.ignite.internal.commandline.CommandLogger.INDENT;
+import static 
org.apache.ignite.internal.commandline.TaskExecutor.executeTaskByNameOnNode;
+import static 
org.apache.ignite.internal.commandline.persistence.CleanAndBackupSubcommandArg.ALL;
+import static 
org.apache.ignite.internal.commandline.persistence.CleanAndBackupSubcommandArg.CACHES;
+import static 
org.apache.ignite.internal.commandline.persistence.CleanAndBackupSubcommandArg.CORRUPTED;
+import static 
org.apache.ignite.internal.commandline.persistence.PersistenceSubcommands.BACKUP;
+import static 
org.apache.ignite.internal.commandline.persistence.PersistenceSubcommands.CLEAN;
+import static 
org.apache.ignite.internal.commandline.persistence.PersistenceSubcommands.INFO;
+import static 
org.apache.ignite.internal.commandline.persistence.PersistenceSubcommands.of;
+
+/** */
+public class PersistenceCommand implements Command<PersistenceArguments> {
+    /** */
+    private PersistenceArguments cleaningArgs;
+
+    /** {@inheritDoc} */
+    @Override public Object execute(GridClientConfiguration clientCfg, Logger 
logger) throws Exception {
+        try (GridClient client = Command.startClient(clientCfg)) {
+            Optional<GridClientNode> firstNodeOpt = 
client.compute().nodes().stream().findFirst();
+
+            if (firstNodeOpt.isPresent()) {
+                UUID uuid = firstNodeOpt.get().nodeId();
+
+                PersistenceTaskResult res = executeTaskByNameOnNode(client,
+                    PersistenceTask.class.getName(),
+                    convertArguments(cleaningArgs),
+                    uuid,
+                    clientCfg
+                );
+
+                printResult(res, logger);
+            }
+            else
+                logger.warning("No nodes found in topology, command won't be 
executed.");
+        }
+        catch (Throwable t) {
+            logger.severe("Failed to execute persistence command='" + 
cleaningArgs.subcommand().text() + "'");
+            logger.severe(CommandLogger.errorMessage(t));
+
+            throw t;
+        }
+
+        return null;
+    }
+
+    /**
+     * Prints result of command execution: information about caches or result 
of clean/backup command.
+     *
+     * @param res {@link PersistenceTaskResult} object with results of command 
execution.
+     * @param logger {@link Logger} to print output to.
+     */
+    private void printResult(PersistenceTaskResult res, Logger logger) {
+        if (!res.inMaintenanceMode()) {
+            logger.warning("Persistence command can be sent only to node in 
Maintenance Mode.");
+
+            return;
+        }
+        //info command
+        else if (res.cachesInfo() != null) {
+            logger.info("Persistent caches found on node:");
+
+            //sort results so corrupted caches occur in the list at the top
+            res.cachesInfo().entrySet().stream().sorted((ci0, ci1) -> {
+                IgniteBiTuple<Boolean, Boolean> t0 = ci0.getValue();
+                IgniteBiTuple<Boolean, Boolean> t1 = ci1.getValue();
+
+                boolean corrupted0 = t0.get1() || t0.get2();
+                boolean corrupted1 = t1.get1() || t1.get2();
+
+                if (corrupted0 && corrupted1)
+                    return 0;
+                else if (!corrupted0 && !corrupted1)
+                    return 0;
+                else if (corrupted0 && !corrupted1)
+                    return -1;
+                else
+                    return 1;
+            }).forEach(
+                e -> {
+                    IgniteBiTuple<Boolean, Boolean> t = e.getValue();
+
+                    String status;
+
+                    if (!t.get1())
+                        status = "corrupted - WAL disabled globally.";
+                    else if (!t.get1())
+                        status = "corrupted - WAL disabled locally.";
+                    else
+                        status = "no corruption.";
+
+                    logger.info(INDENT + "cache name: " + e.getKey() + ". 
Status: " + status);
+                }
+            );
+        }
+        //clean command
+        else if (cleaningArgs != null && cleaningArgs.subcommand() == CLEAN) {
+            logger.info("Maintenance task is " + 
(!res.maintenanceTaskCompleted() ? "not " : "") + "fixed.");
+
+            List<String> cleanedCaches = res.handledCaches();
+
+            if (cleanedCaches != null && !cleanedCaches.isEmpty()) {
+                String cacheDirNames = String.join(", ", cleanedCaches);
+
+                logger.info("Cache directories were cleaned: [" + 
cacheDirNames + ']');
+            }
+
+            List<String> failedToHandleCaches = res.failedCaches();
+
+            if (failedToHandleCaches != null && 
!failedToHandleCaches.isEmpty()) {
+                String failedToHandleCachesStr = String.join(", ", 
failedToHandleCaches);
+
+                logger.info("Failed to clean following directories: [" + 
failedToHandleCachesStr + ']');
+            }
+        }
+        // backup command
+        else {
+            List<String> backupCompletedCaches = res.handledCaches();
+
+            if (backupCompletedCaches != null && 
!backupCompletedCaches.isEmpty()) {
+                String cacheDirNames = String.join(", ", 
backupCompletedCaches);
+
+                logger.info("Cache data files was backed up to the following 
directories in node's work directory: [" +
+                    cacheDirNames + ']');
+            }
+
+            List<String> backupFailedCaches = res.failedCaches();
+
+            if (backupFailedCaches != null && !backupFailedCaches.isEmpty()) {
+                String backupFailedCachesStr = String.join(", ", 
backupFailedCaches);
+
+                logger.info("Failed to backup the following directories in 
node's work directory: [" +
+                    backupFailedCachesStr + ']');
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public PersistenceArguments arg() {
+        return cleaningArgs;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void printUsage(Logger logger) {
+        final String cacheNames = "cache1,cache2,cache3";
+
+        usage(logger, "Print information about potentially corrupted caches on 
local node:",
+            PERSISTENCE);
+        usage(logger, "The same information is printed when info subcommand is 
passed:", PERSISTENCE,
+            INFO.text());
+
+        usage(logger, "Clean directories of caches with corrupted data 
files:", PERSISTENCE, CLEAN.text(),
+            CORRUPTED.argName());
+        usage(logger, "Clean directories of all caches:", PERSISTENCE, 
CLEAN.text(),
+            ALL.argName());
+        usage(logger, "Clean directories of only given caches:", PERSISTENCE, 
CLEAN.text(),
+            CACHES.argName(), cacheNames);
+
+        usage(logger, "Backup data files of corrupted caches only:", 
PERSISTENCE, BACKUP.text(),
+            CORRUPTED.argName());
+        usage(logger, "Backup data files of all caches:", PERSISTENCE, 
BACKUP.text(), ALL.argName());
+        usage(logger, "Backup data files of only given caches:", PERSISTENCE, 
BACKUP.text(),
+            CACHES.argName(), cacheNames);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void parseArguments(CommandArgIterator argIter) {
+        if (!argIter.hasNextSubArg()) {
+            cleaningArgs = new PersistenceArguments.Builder(INFO).build();
+
+            return;
+        }
+
+        PersistenceSubcommands cmd = of(argIter.nextArg("Expected persistence 
maintenance action"));
+
+        if (cmd == null)
+            throw new IllegalArgumentException("Expected correct persistence 
maintenance action");
+
+        PersistenceArguments.Builder bldr = new 
PersistenceArguments.Builder(cmd);
+
+        switch (cmd) {
+            case BACKUP:
+            case CLEAN:
+                CleanAndBackupSubcommandArg cleanAndBackupSubcommandArg = 
CommandArgUtils.of(
+                    argIter.nextArg("Expected one of subcommand arguments"), 
CleanAndBackupSubcommandArg.class
+                );
+
+                if (cleanAndBackupSubcommandArg == null)
+                    throw new IllegalArgumentException("Expected one of 
subcommand arguments");
+
+                
bldr.withCleanAndBackupSubcommandArg(cleanAndBackupSubcommandArg);
+
+                if (cleanAndBackupSubcommandArg == ALL || 
cleanAndBackupSubcommandArg == CORRUPTED)
+                    break;
+
+                if (cleanAndBackupSubcommandArg == CACHES) {
+                    Set<String> caches = argIter.nextStringSet("list of cache 
names");
+
+                    if (F.isEmpty(caches))
+                        throw new IllegalArgumentException("Empty list of 
cache names");
+
+                    bldr.withCacheNames(new ArrayList<>(caches));
+                }
+
+                break;
+        }
+
+        cleaningArgs = bldr.build();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String name() {
+        return PERSISTENCE.toCommandName();
+    }
+
+    /** */
+    private PersistenceTaskArg convertArguments(PersistenceArguments args) {
+        PersistenceCleanAndBackupSettings cleanSettings = 
convertCleanAndBackupSettings(args);
+
+        PersistenceTaskArg taskArgs = new 
PersistenceTaskArg(args.subcommand().operation(), cleanSettings);
+
+        return taskArgs;
+    }
+
+    /** */
+    private PersistenceCleanAndBackupSettings 
convertCleanAndBackupSettings(PersistenceArguments args) {
+        if (args.subcommand() == INFO)
+            return null;
+
+        PersistenceCleanAndBackupType type;
+
+        switch (args.cleanArg()) {
+            case ALL:
+                type = PersistenceCleanAndBackupType.ALL;
+
+                break;
+            case CORRUPTED:
+                type = PersistenceCleanAndBackupType.CORRUPTED;
+
+                break;
+
+            default:
+                type = PersistenceCleanAndBackupType.CACHES;
+        }
+
+        return new PersistenceCleanAndBackupSettings(type, args.cachesList());
+    }
+}
diff --git 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/persistence/CleanAndBackupSubcommandArg.java
 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/persistence/CleanAndBackupSubcommandArg.java
new file mode 100644
index 0000000..08a0336
--- /dev/null
+++ 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/persistence/CleanAndBackupSubcommandArg.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline.persistence;
+
+import org.apache.ignite.internal.commandline.argument.CommandArg;
+
+/**
+ * {@link PersistenceSubcommands#CLEAN} subcommand arguments.
+ */
+public enum CleanAndBackupSubcommandArg implements CommandArg {
+    /** Clean all caches data files. */
+    ALL("all"),
+    /** Clean corrupted caches data files. */
+    CORRUPTED("corrupted"),
+    /** Clean only specified caches data files. */
+    CACHES("caches");
+
+    /** */
+    private final String name;
+
+    /** */
+    CleanAndBackupSubcommandArg(String name) {
+        this.name = name;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String argName() {
+        return name;
+    }
+}
diff --git 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/persistence/PersistenceArguments.java
 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/persistence/PersistenceArguments.java
new file mode 100644
index 0000000..8971680
--- /dev/null
+++ 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/persistence/PersistenceArguments.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline.persistence;
+
+import java.util.List;
+
+/**
+ * Arguments of "persistence cleaning" command.
+ */
+public class PersistenceArguments {
+    /** */
+    private PersistenceSubcommands cmd;
+
+    /** */
+    private CleanAndBackupSubcommandArg cleanArg;
+
+    /** */
+    private List<String> cachesList;
+
+    /**
+     * @param cmd
+     */
+    public PersistenceArguments(PersistenceSubcommands cmd, 
CleanAndBackupSubcommandArg cleanArg, List<String> cachesList) {
+        this.cmd = cmd;
+        this.cleanArg = cleanArg;
+        this.cachesList = cachesList;
+    }
+
+    /** */
+    public PersistenceSubcommands subcommand() {
+        return cmd;
+    }
+
+    /** */
+    public CleanAndBackupSubcommandArg cleanArg() {
+        return cleanArg;
+    }
+
+    /** */
+    public List<String> cachesList() {
+        return cachesList;
+    }
+
+    /** Builder of {@link PersistenceArguments}. */
+    public static class Builder {
+        /** */
+        private PersistenceSubcommands subCmd;
+
+        /** */
+        private CleanAndBackupSubcommandArg cleanSubCmdArg;
+
+        /** */
+        private List<String> cacheNames;
+
+        /**
+         * @param subCmd Subcommand.
+         */
+        public Builder(PersistenceSubcommands subCmd) {
+            this.subCmd = subCmd;
+        }
+
+        /** */
+        public Builder 
withCleanAndBackupSubcommandArg(CleanAndBackupSubcommandArg cleanSubCmdArg) {
+            this.cleanSubCmdArg = cleanSubCmdArg;
+
+            return this;
+        }
+
+        public Builder withCacheNames(List<String> cacheNames) {
+            this.cacheNames = cacheNames;
+
+            return this;
+        }
+
+        public PersistenceArguments build() {
+            return new PersistenceArguments(
+                subCmd,
+                cleanSubCmdArg,
+                cacheNames
+            );
+        }
+    }
+}
diff --git 
a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/persistence/PersistenceSubcommands.java
 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/persistence/PersistenceSubcommands.java
new file mode 100644
index 0000000..d674316
--- /dev/null
+++ 
b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/persistence/PersistenceSubcommands.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.commandline.persistence;
+
+import org.apache.ignite.internal.visor.persistence.PersistenceOperation;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ *
+ */
+public enum PersistenceSubcommands {
+    /** Collects information about corrupted caches and cache groups and their 
file system paths. */
+    INFO("info", PersistenceOperation.INFO),
+
+    /** Cleans partition files of corrupted caches and cache groups. */
+    CLEAN("clean", PersistenceOperation.CLEAN),
+
+    /** */
+    BACKUP("backup", PersistenceOperation.BACKUP);
+
+    /** Subcommand name. */
+    private final String name;
+
+    /** Operation this subcommand triggers. */
+    private final PersistenceOperation operation;
+
+    /**
+     * @param name String representation of subcommand.
+     * @param operation Operation this command triggers.
+     */
+    PersistenceSubcommands(String name, PersistenceOperation operation) {
+        this.name = name;
+        this.operation = operation;
+    }
+
+    /**
+     * @param strRep String representation of subcommand.
+     * @return Subcommand for its string representation.
+     */
+    public static @Nullable PersistenceSubcommands of(String strRep) {
+        for (PersistenceSubcommands cmd : values()) {
+            if (cmd.text().equals(strRep))
+                return cmd;
+        }
+
+        return null;
+    }
+
+    /** */
+    public String text() {
+        return name;
+    }
+
+    /** */
+    public PersistenceOperation operation() {
+        return operation;
+    }
+}
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 5970ae4..5557b5e 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
@@ -23,6 +23,7 @@ import java.io.RandomAccessFile;
 import java.io.Serializable;
 import java.lang.reflect.Field;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -33,6 +34,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.TreeSet;
 import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ThreadLocalRandom;
@@ -41,6 +43,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.LongAdder;
+import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -65,6 +68,7 @@ import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.internal.GridJobExecuteResponse;
 import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.IgniteNodeAttributes;
 import org.apache.ignite.internal.TestRecordingCommunicationSpi;
 import org.apache.ignite.internal.client.GridClientFactory;
 import org.apache.ignite.internal.client.impl.GridClientImpl;
@@ -84,6 +88,7 @@ import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxFini
 import 
org.apache.ignite.internal.processors.cache.distributed.near.GridNearLockResponse;
 import 
org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxFinishRequest;
 import 
org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxLocal;
+import org.apache.ignite.internal.processors.cache.persistence.CheckpointState;
 import 
org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager;
 import 
org.apache.ignite.internal.processors.cache.persistence.db.IgniteCacheGroupsWithRestartsTest;
 import 
org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToFileDumpProcessor;
@@ -114,6 +119,7 @@ import org.apache.ignite.transactions.Transaction;
 import org.apache.ignite.transactions.TransactionRollbackException;
 import org.apache.ignite.transactions.TransactionTimeoutException;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.junit.Test;
 
 import static java.io.File.separatorChar;
@@ -141,6 +147,7 @@ import static 
org.apache.ignite.internal.processors.cache.persistence.snapshot.I
 import static 
org.apache.ignite.internal.processors.cache.verify.IdleVerifyUtility.GRID_NOT_IDLE_MSG;
 import static 
org.apache.ignite.internal.processors.diagnostic.DiagnosticProcessor.DEFAULT_TARGET_FOLDER;
 import static org.apache.ignite.testframework.GridTestUtils.assertContains;
+import static org.apache.ignite.testframework.GridTestUtils.assertThrows;
 import static org.apache.ignite.testframework.GridTestUtils.runAsync;
 import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
 import static org.apache.ignite.transactions.TransactionConcurrency.OPTIMISTIC;
@@ -248,6 +255,365 @@ public class GridCommandHandlerTest extends 
GridCommandHandlerClusterPerMethodAb
         assertTrue("Still opened clients: " + new ArrayList<>(clnts.values()), 
clntsBefore.equals(clntsAfter2));
     }
 
+    private CacheConfiguration cacheConfiguration(String cacheName) {
+        CacheConfiguration ccfg = new CacheConfiguration(cacheName)
+            .setAtomicityMode(TRANSACTIONAL)
+            .setAffinity(new RendezvousAffinityFunction(false, 32))
+            .setBackups(1);
+
+        return ccfg;
+    }
+
+    /**
+     * Starts cluster of two nodes and prepares situation of corrupted PDS on 
node2
+     * so it enters maintenance mode on restart.
+     *
+     * @param cachesToStart Configurations of caches that should be started in 
cluster.
+     * @param cacheToCorrupt Function determining should cache with given name 
be corrupted or not.
+     */
+    private File startGridAndPutNodeToMaintenance(CacheConfiguration[] 
cachesToStart,
+                                                  @Nullable Function<String, 
Boolean> cacheToCorrupt) throws Exception {
+        assert cachesToStart != null && cachesToStart.length > 0;
+
+        IgniteEx ig0 = startGrid(0);
+        IgniteEx ig1 = startGrid(1);
+
+        String ig1Folder = 
ig1.context().pdsFolderResolver().resolveFolders().folderName();
+        File dbDir = 
U.resolveWorkDirectory(ig1.configuration().getWorkDirectory(), "db", false);
+
+        File ig1LfsDir = new File(dbDir, ig1Folder);
+
+        ig0.cluster().baselineAutoAdjustEnabled(false);
+        ig0.cluster().state(ACTIVE);
+
+        IgniteCache dfltCache = ig0.getOrCreateCache(cachesToStart[0]);
+
+        if (cachesToStart.length > 1) {
+            for (int i = 1; i < cachesToStart.length; i++)
+                ig0.getOrCreateCache(cachesToStart[i]);
+        }
+
+        for (int k = 0; k < 1000; k++)
+            dfltCache.put(k, k);
+
+        GridCacheDatabaseSharedManager dbMrg0 = 
(GridCacheDatabaseSharedManager) ig0.context().cache().context().database();
+        GridCacheDatabaseSharedManager dbMrg1 = 
(GridCacheDatabaseSharedManager) ig1.context().cache().context().database();
+
+        dbMrg0.forceCheckpoint("cp").futureFor(CheckpointState.FINISHED).get();
+        dbMrg1.forceCheckpoint("cp").futureFor(CheckpointState.FINISHED).get();
+
+        Arrays.stream(cachesToStart)
+            .map(ccfg -> ccfg.getName())
+            .filter(name -> cacheToCorrupt.apply(name))
+            .forEach(name -> ig0.cluster().disableWal(name));
+
+        for (int k = 1000; k < 2000; k++)
+            dfltCache.put(k, k);
+
+        stopGrid(1);
+
+        File[] cpMarkers = new File(ig1LfsDir, "cp").listFiles();
+
+        for (File cpMark : cpMarkers) {
+            if (cpMark.getName().contains("-END"))
+                cpMark.delete();
+        }
+
+        assertThrows(log, () -> startGrid(1), Exception.class, null);
+
+        return ig1LfsDir;
+    }
+
+    /**
+     * Test verifies persistence clean command with explicit list of caches to 
be cleaned.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testPersistenceCleanSpecifiedCachesCommand() throws Exception {
+        String cacheName0 = DEFAULT_CACHE_NAME + "0";
+        String cacheName1 = DEFAULT_CACHE_NAME + "1";
+        String cacheName2 = DEFAULT_CACHE_NAME + "2";
+        String cacheName3 = DEFAULT_CACHE_NAME + "3";
+
+        String nonExistingCacheName = DEFAULT_CACHE_NAME + "4";
+
+        File mntcNodeWorkDir = startGridAndPutNodeToMaintenance(
+            new CacheConfiguration[]{
+                cacheConfiguration(cacheName0),
+                cacheConfiguration(cacheName1),
+                cacheConfiguration(cacheName2),
+                cacheConfiguration(cacheName3)
+            },
+            s -> !s.equals(cacheName3));
+
+        IgniteEx ig1 = startGrid(1);
+
+        String port = 
ig1.localNode().attribute(IgniteNodeAttributes.ATTR_REST_TCP_PORT).toString();
+
+        assertEquals(EXIT_CODE_INVALID_ARGUMENTS, execute("--persistence", 
"clean", "caches",
+            nonExistingCacheName,
+            "--host", "localhost", "--port", port));
+
+        assertEquals(EXIT_CODE_OK, execute("--persistence", "clean", "caches",
+            cacheName0 + "," + cacheName1,
+            "--host", "localhost", "--port", port));
+
+        boolean cleanedEmpty = Arrays.stream(mntcNodeWorkDir.listFiles())
+            .filter(f -> f.getName().contains(cacheName0) || 
f.getName().contains(cacheName1))
+            .map(f -> f.listFiles().length == 1)
+            .reduce(true, (t, u) -> t && u);
+
+        assertTrue(cleanedEmpty);
+
+        boolean nonCleanedNonEmpty = Arrays.stream(mntcNodeWorkDir.listFiles())
+            .filter(f -> f.getName().contains(cacheName2) || 
f.getName().contains(cacheName3))
+            .map(f -> f.listFiles().length > 1)
+            .reduce(true, (t, u) -> t && u);
+
+        assertTrue(nonCleanedNonEmpty);
+
+        stopGrid(1);
+
+        ig1 = startGrid(1);
+
+        assertTrue(ig1.context().maintenanceRegistry().isMaintenanceMode());
+
+        assertEquals(EXIT_CODE_OK, execute("--persistence", "clean", "caches",
+            cacheName2,
+            "--host", "localhost", "--port", port));
+
+        stopGrid(1);
+
+        ig1 = startGrid(1);
+
+        assertFalse(ig1.context().maintenanceRegistry().isMaintenanceMode());
+    }
+
+    /**
+     * Test verifies persistence clean command cleaning only corrupted caches 
and not touching others.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testPersistenceCleanCorruptedCachesCommand() throws Exception {
+        String cacheName0 = DEFAULT_CACHE_NAME + "0";
+        String cacheName1 = DEFAULT_CACHE_NAME + "1";
+        String cacheName2 = DEFAULT_CACHE_NAME + "2";
+        String cacheName3 = DEFAULT_CACHE_NAME + "3";
+
+        File mntcNodeWorkDir = startGridAndPutNodeToMaintenance(
+            new CacheConfiguration[]{
+                cacheConfiguration(cacheName0),
+                cacheConfiguration(cacheName1),
+                cacheConfiguration(cacheName2),
+                cacheConfiguration(cacheName3)
+            },
+            s -> !s.equals(cacheName3));
+
+        IgniteEx ig1 = startGrid(1);
+
+        String port = 
ig1.localNode().attribute(IgniteNodeAttributes.ATTR_REST_TCP_PORT).toString();
+
+        assertEquals(EXIT_CODE_OK, execute("--persistence", "clean", 
"corrupted",
+            "--host", "localhost", "--port", port));
+
+        boolean cleanedEmpty = Arrays.stream(mntcNodeWorkDir.listFiles())
+            .filter(f ->
+                f.getName().contains(cacheName0)
+                || f.getName().contains(cacheName1)
+                || f.getName().contains(cacheName2)
+            )
+            .map(f -> f.listFiles().length == 1)
+            .reduce(true, (t, u) -> t && u);
+
+        assertTrue(cleanedEmpty);
+
+        stopGrid(1);
+
+        ig1 = startGrid(1);
+
+        assertFalse(ig1.context().maintenanceRegistry().isMaintenanceMode());
+    }
+
+    /**
+     * Test verifies persistence clean all command that cleans all cache 
directories.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testPersistenceCleanAllCachesCommand() throws Exception {
+        String cacheName0 = DEFAULT_CACHE_NAME + "0";
+        String cacheName1 = DEFAULT_CACHE_NAME + "1";
+
+        File mntcNodeWorkDir = startGridAndPutNodeToMaintenance(
+            new CacheConfiguration[]{
+                cacheConfiguration(cacheName0),
+                cacheConfiguration(cacheName1)
+            },
+            s -> s.equals(cacheName0));
+
+        IgniteEx ig1 = startGrid(1);
+
+        String port = 
ig1.localNode().attribute(IgniteNodeAttributes.ATTR_REST_TCP_PORT).toString();
+
+        assertEquals(EXIT_CODE_OK, execute("--persistence", "clean", "all",
+            "--host", "localhost", "--port", port));
+
+        boolean allEmpty = Arrays.stream(mntcNodeWorkDir.listFiles())
+            .filter(File::isDirectory)
+            .filter(f -> f.getName().startsWith("cache-"))
+            .map(f -> f.listFiles().length == 1)
+            .reduce(true, (t, u) -> t && u);
+
+        assertTrue(allEmpty);
+
+        stopGrid(1);
+
+        ig1 = startGrid(1);
+
+        assertFalse(ig1.context().maintenanceRegistry().isMaintenanceMode());
+    }
+
+    /**
+     * Test verifies that persistence backup command to backup all caches 
backs up all cache directories.
+     * 
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testPersistenceBackupAllCachesCommand() throws Exception {
+        String cacheName0 = DEFAULT_CACHE_NAME + "0";
+        String cacheName1 = DEFAULT_CACHE_NAME + "1";
+
+        File mntcNodeWorkDir = startGridAndPutNodeToMaintenance(
+            new CacheConfiguration[]{
+                cacheConfiguration(cacheName0),
+                cacheConfiguration(cacheName1)
+            },
+            s -> s.equals(cacheName0));
+
+        IgniteEx ig1 = startGrid(1);
+
+        String port = 
ig1.localNode().attribute(IgniteNodeAttributes.ATTR_REST_TCP_PORT).toString();
+
+        assertEquals(EXIT_CODE_OK, execute("--persistence", "backup", "all",
+            "--host", "localhost", "--port", port));
+
+        Set<String> backedUpCacheDirs = 
Arrays.stream(mntcNodeWorkDir.listFiles())
+            .filter(File::isDirectory)
+            .filter(f -> f.getName().startsWith("backup_"))
+            .map(f -> f.getName().substring("backup_".length()))
+            .collect(Collectors.toCollection(TreeSet::new));
+
+        Set<String> allCacheDirs = Arrays.stream(mntcNodeWorkDir.listFiles())
+            .filter(File::isDirectory)
+            .filter(f -> f.getName().startsWith("cache-"))
+            .map(File::getName)
+            .collect(Collectors.toCollection(TreeSet::new));
+
+        assertEqualsCollections(backedUpCacheDirs, allCacheDirs);
+
+        checkCacheAndBackupDirsContent(mntcNodeWorkDir);
+    }
+
+    /**
+     * Test verifies that persistence backup command copies all corrupted 
caches content to backup directory
+     * but does not touch other directories.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testPersistenceBackupCorruptedCachesCommand() throws Exception 
{
+        String cacheName0 = DEFAULT_CACHE_NAME + "0";
+        String cacheName1 = DEFAULT_CACHE_NAME + "1";
+
+        File mntcNodeWorkDir = startGridAndPutNodeToMaintenance(
+            new CacheConfiguration[]{
+                cacheConfiguration(cacheName0),
+                cacheConfiguration(cacheName1)
+            },
+            s -> s.equals(cacheName0));
+
+        IgniteEx ig1 = startGrid(1);
+
+        String port = 
ig1.localNode().attribute(IgniteNodeAttributes.ATTR_REST_TCP_PORT).toString();
+
+        assertEquals(EXIT_CODE_OK, execute("--persistence", "backup", 
"corrupted",
+            "--host", "localhost", "--port", port));
+
+        long backedUpCachesCnt = Arrays.stream(mntcNodeWorkDir.listFiles())
+            .filter(File::isDirectory)
+            .filter(f -> f.getName().startsWith("backup_"))
+            .filter(f -> f.getName().contains(cacheName0))
+            .count();
+
+        assertEquals(1, backedUpCachesCnt);
+
+        checkCacheAndBackupDirsContent(mntcNodeWorkDir);
+    }
+
+    /**
+     * Test verifies that persistence backup command with specified caches 
copied only content of that caches and
+     * doesn't touch other directories.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testPersistenceBackupSpecifiedCachesCommand() throws Exception 
{
+        String cacheName0 = DEFAULT_CACHE_NAME + "0";
+        String cacheName1 = DEFAULT_CACHE_NAME + "1";
+        String cacheName2 = DEFAULT_CACHE_NAME + "2";
+
+        String nonExistingCacheName = "nonExistingCache";
+
+        File mntcNodeWorkDir = startGridAndPutNodeToMaintenance(
+            new CacheConfiguration[]{
+                cacheConfiguration(cacheName0),
+                cacheConfiguration(cacheName1),
+                cacheConfiguration(cacheName2)
+            },
+            s -> s.equals(cacheName0) || s.equals(cacheName2));
+
+        IgniteEx ig1 = startGrid(1);
+
+        String port = 
ig1.localNode().attribute(IgniteNodeAttributes.ATTR_REST_TCP_PORT).toString();
+
+        assertEquals(EXIT_CODE_INVALID_ARGUMENTS, execute("--persistence", 
"backup", "caches",
+            nonExistingCacheName,
+            "--host", "localhost", "--port", port));
+
+        assertEquals(EXIT_CODE_OK, execute("--persistence", "backup", "caches",
+            cacheName0 + "," + cacheName2,
+            "--host", "localhost", "--port", port));
+
+        long backedUpCachesCnt = Arrays.stream(mntcNodeWorkDir.listFiles())
+            .filter(File::isDirectory)
+            .filter(f -> f.getName().startsWith("backup_"))
+            .count();
+
+        assertEquals(2, backedUpCachesCnt);
+
+        checkCacheAndBackupDirsContent(mntcNodeWorkDir);
+    }
+
+    /** */
+    private void checkCacheAndBackupDirsContent(File mntcNodeWorkDir) {
+        List<File> backupDirs = Arrays.stream(mntcNodeWorkDir.listFiles())
+            .filter(File::isDirectory)
+            .filter(f -> f.getName().startsWith("backup_"))
+            .collect(Collectors.toList());
+
+        Path mntcNodeWorkDirPath = mntcNodeWorkDir.toPath();
+
+        for (File bDir : backupDirs) {
+            File origCacheDir = 
mntcNodeWorkDirPath.resolve(bDir.getName().substring("backup_".length())).toFile();
+
+            assertTrue(origCacheDir.isDirectory());
+
+            assertEquals(origCacheDir.listFiles().length, 
bDir.listFiles().length);
+        }
+    }
+
     /**
      * Test enabling/disabling read-only mode works via control.sh
      *
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/maintenance/MaintenanceProcessor.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/maintenance/MaintenanceProcessor.java
index 8f85ceb..6bc3e8e 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/maintenance/MaintenanceProcessor.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/maintenance/MaintenanceProcessor.java
@@ -162,12 +162,21 @@ public class MaintenanceProcessor extends 
GridProcessorAdapter implements Mainte
             );
         }
 
-        if (!workflowCallbacks.isEmpty())
+        if (!workflowCallbacks.isEmpty()) {
+            if (log.isInfoEnabled()) {
+                String mntcTasksNames = String.join(", ", 
workflowCallbacks.keySet());
+
+                log.info("Node requires maintenance, non-empty set of 
maintenance tasks is found: [" +
+                    mntcTasksNames + ']');
+            }
+
             proceedWithMaintenance();
-        else {
-            if (log.isInfoEnabled())
+        }
+        else if (isMaintenanceMode()) {
+            if (log.isInfoEnabled()) {
                 log.info("All maintenance tasks are fixed, no need to enter 
maintenance mode. " +
                     "Restart the node to get it back to normal operations.");
+            }
         }
     }
 
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/CorruptedPdsMaintenanceCallback.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/CheckCorruptedCacheStoresCleanAction.java
similarity index 56%
copy from 
modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/CorruptedPdsMaintenanceCallback.java
copy to 
modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/CheckCorruptedCacheStoresCleanAction.java
index 52a8f6f..2073b9b 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/CorruptedPdsMaintenanceCallback.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/CheckCorruptedCacheStoresCleanAction.java
@@ -18,62 +18,53 @@
 package org.apache.ignite.internal.processors.cache.persistence;
 
 import java.io.File;
-import java.util.Arrays;
-import java.util.List;
 
 import org.apache.ignite.maintenance.MaintenanceAction;
-import org.apache.ignite.maintenance.MaintenanceWorkflowCallback;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.CACHE_DATA_FILENAME;
 
-/**
- *
- */
-public class CorruptedPdsMaintenanceCallback implements 
MaintenanceWorkflowCallback {
+/** */
+public class CheckCorruptedCacheStoresCleanAction implements 
MaintenanceAction<Boolean> {
     /** */
-    private final File workDir;
+    public static final String ACTION_NAME = "check_cache_files_cleaned";
 
     /** */
-    private final List<String> cacheStoreDirs;
+    private final File rootStoreDir;
 
-    /**
-     * @param workDir
-     * @param cacheStoreDirs
-     */
-    public CorruptedPdsMaintenanceCallback(@NotNull File workDir,
-                                           @NotNull List<String> 
cacheStoreDirs)
-    {
-        this.workDir = workDir;
+    /** */
+    private final String[] cacheStoreDirs;
+
+    /** */
+    public CheckCorruptedCacheStoresCleanAction(File rootStoreDir, String[] 
cacheStoreDirs) {
+        this.rootStoreDir = rootStoreDir;
         this.cacheStoreDirs = cacheStoreDirs;
     }
 
     /** {@inheritDoc} */
-    @Override public boolean shouldProceedWithMaintenance() {
+    @Override public Boolean execute() {
         for (String cacheStoreDirName : cacheStoreDirs) {
-            File cacheStoreDir = new File(workDir, cacheStoreDirName);
+            File cacheStoreDir = new File(rootStoreDir, cacheStoreDirName);
 
-            if (cacheStoreDir.exists()
-                && cacheStoreDir.isDirectory()
-                && cacheStoreDir.listFiles().length > 0
-            ) {
+            if (cacheStoreDir.exists() && cacheStoreDir.isDirectory()) {
                 for (File f : cacheStoreDir.listFiles()) {
                     if (!f.getName().equals(CACHE_DATA_FILENAME))
-                        return true;
+                        return Boolean.FALSE;
                 }
             }
         }
 
-        return false;
+        return Boolean.TRUE;
     }
 
     /** {@inheritDoc} */
-    @Override public List<MaintenanceAction> allActions() {
-        return Arrays.asList(new CleanCacheStoresMaintenanceAction(workDir, 
cacheStoreDirs.toArray(new String[0])));
+    @Override public @NotNull String name() {
+        return ACTION_NAME;
     }
 
     /** {@inheritDoc} */
-    @Override public MaintenanceAction automaticAction() {
-        return null;
+    @Override public @Nullable String description() {
+        return "Checks if all corrupted data files are cleaned from cache 
store directories";
     }
 }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/CorruptedPdsMaintenanceCallback.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/CorruptedPdsMaintenanceCallback.java
index 52a8f6f..0173bca 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/CorruptedPdsMaintenanceCallback.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/CorruptedPdsMaintenanceCallback.java
@@ -69,7 +69,9 @@ public class CorruptedPdsMaintenanceCallback implements 
MaintenanceWorkflowCallb
 
     /** {@inheritDoc} */
     @Override public List<MaintenanceAction> allActions() {
-        return Arrays.asList(new CleanCacheStoresMaintenanceAction(workDir, 
cacheStoreDirs.toArray(new String[0])));
+        return Arrays.asList(
+            new CleanCacheStoresMaintenanceAction(workDir, 
cacheStoreDirs.toArray(new String[0])),
+            new CheckCorruptedCacheStoresCleanAction(workDir, 
cacheStoreDirs.toArray(new String[0])));
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceCleanAndBackupSettings.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceCleanAndBackupSettings.java
new file mode 100644
index 0000000..a5bf327
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceCleanAndBackupSettings.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.visor.persistence;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.List;
+
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/** */
+public class PersistenceCleanAndBackupSettings extends 
IgniteDataTransferObject {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** */
+    private PersistenceCleanAndBackupType cleanAndBackupType;
+
+    /** */
+    private List<String> cacheNames;
+
+    /** */
+    public PersistenceCleanAndBackupSettings() {
+        // No-op.
+    }
+
+    /** */
+    public PersistenceCleanAndBackupSettings(PersistenceCleanAndBackupType 
cleanAndBackupType, List<String> cacheNames) {
+        this.cleanAndBackupType = cleanAndBackupType;
+        this.cacheNames = cacheNames;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void writeExternalData(ObjectOutput out) throws 
IOException {
+        U.writeEnum(out, cleanAndBackupType);
+        U.writeCollection(out, cacheNames);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in) 
throws IOException, ClassNotFoundException {
+        cleanAndBackupType = 
PersistenceCleanAndBackupType.fromOrdinal(in.readByte());
+        cacheNames = U.readList(in);
+    }
+
+    /** */
+    public PersistenceCleanAndBackupType cleanAndBackupType() {
+        return cleanAndBackupType;
+    }
+
+    /** */
+    public List<String> cacheNames() {
+        return cacheNames;
+    }
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceCleanAndBackupType.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceCleanAndBackupType.java
new file mode 100644
index 0000000..2198851
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceCleanAndBackupType.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.visor.persistence;
+
+import org.jetbrains.annotations.Nullable;
+
+/** */
+public enum PersistenceCleanAndBackupType {
+    /** */
+    ALL,
+    /** */
+    CORRUPTED,
+    /** */
+    CACHES;
+
+    /** */
+    private static final PersistenceCleanAndBackupType[] VALS = values();
+
+    /**
+     * @param ordinal Index of enum value.
+     * @return Value of {@link PersistenceCleanAndBackupType} enum.
+     */
+    @Nullable public static PersistenceCleanAndBackupType fromOrdinal(int 
ordinal) {
+        return ordinal >= 0 && ordinal < VALS.length ? VALS[ordinal] : null;
+    }
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceOperation.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceOperation.java
new file mode 100644
index 0000000..2481af8
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceOperation.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.visor.persistence;
+
+import org.jetbrains.annotations.Nullable;
+
+/** Persistence cleaning operations. */
+public enum PersistenceOperation {
+    /** */
+    INFO,
+
+    /** */
+    CLEAN,
+
+    /** */
+    BACKUP;
+
+    /** */
+    private static final PersistenceOperation[] VALS = values();
+
+    /**
+     * @param ordinal Index of enum value.
+     * @return Value of {@link PersistenceOperation} enum.
+     */
+    @Nullable public static PersistenceOperation fromOrdinal(int ordinal) {
+        return ordinal >= 0 && ordinal < VALS.length ? VALS[ordinal] : null;
+    }
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceTask.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceTask.java
new file mode 100644
index 0000000..1ac23f4
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceTask.java
@@ -0,0 +1,401 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.visor.persistence;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.configuration.DataStorageConfiguration;
+import org.apache.ignite.internal.pagemem.store.IgnitePageStoreManager;
+import org.apache.ignite.internal.processors.cache.CacheGroupDescriptor;
+import org.apache.ignite.internal.processors.cache.DynamicCacheDescriptor;
+import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
+import 
org.apache.ignite.internal.processors.cache.persistence.CheckCorruptedCacheStoresCleanAction;
+import 
org.apache.ignite.internal.processors.cache.persistence.CleanCacheStoresMaintenanceAction;
+import 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager;
+import org.apache.ignite.internal.processors.task.GridInternal;
+import org.apache.ignite.internal.processors.task.GridVisorManagementTask;
+import org.apache.ignite.internal.util.typedef.internal.CU;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.internal.visor.VisorJob;
+import org.apache.ignite.internal.visor.VisorOneNodeTask;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.maintenance.MaintenanceAction;
+import org.apache.ignite.maintenance.MaintenanceRegistry;
+import org.apache.ignite.maintenance.MaintenanceTask;
+import org.jetbrains.annotations.Nullable;
+
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.CORRUPTED_DATA_FILES_MNTC_TASK_NAME;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.cacheDirName;
+
+/** */
+@GridInternal
+@GridVisorManagementTask
+public class PersistenceTask extends VisorOneNodeTask<PersistenceTaskArg, 
PersistenceTaskResult> {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** */
+    private static final String BACKUP_FOLDER_PREFIX = "backup_";
+
+    @Override protected VisorJob<PersistenceTaskArg, PersistenceTaskResult> 
job(PersistenceTaskArg arg) {
+        return new PersistenceJob(arg, debug);
+    }
+
+    /** */
+    private static class PersistenceJob extends VisorJob<PersistenceTaskArg, 
PersistenceTaskResult> {
+        /** */
+        private static final long serialVersionUID = 0L;
+
+        /**
+         * Create job with specified argument.
+         *
+         * @param arg   Job argument.
+         * @param debug Flag indicating whether debug information should be 
printed into node log.
+         */
+        protected PersistenceJob(@Nullable PersistenceTaskArg arg, boolean 
debug) {
+            super(arg, debug);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected PersistenceTaskResult run(@Nullable 
PersistenceTaskArg arg) throws IgniteException {
+            if (!ignite.context().maintenanceRegistry().isMaintenanceMode())
+                return new PersistenceTaskResult(false);
+
+            switch (arg.operation()) {
+                case CLEAN:
+                    return clean(arg);
+
+                case BACKUP:
+                    return backup(arg);
+
+                default:
+                    return info();
+            }
+        }
+
+        /** */
+        private PersistenceTaskResult backup(PersistenceTaskArg arg) {
+            PersistenceCleanAndBackupSettings backupSettings = 
arg.cleanAndBackupSettings();
+
+            MaintenanceRegistry mntcReg = 
ignite.context().maintenanceRegistry();
+            MaintenanceTask task = 
mntcReg.activeMaintenanceTask(CORRUPTED_DATA_FILES_MNTC_TASK_NAME);
+
+            File workDir = ((FilePageStoreManager) 
ignite.context().cache().context().pageStore()).workDir();
+
+            switch (backupSettings.cleanAndBackupType()) {
+                case ALL:
+                    return backupAll(workDir);
+
+                case CORRUPTED:
+                    return backupCaches(workDir, 
corruptedCacheDirectories(task));
+
+                default:
+                    return backupCaches(workDir, 
cacheDirectoriesFromCacheNames(backupSettings.cacheNames()));
+            }
+        }
+
+        /** */
+        private PersistenceTaskResult backupAll(File workDir) {
+            GridCacheProcessor cacheProc = ignite.context().cache();
+
+            List<String> allCacheDirs = cacheProc.cacheDescriptors()
+                .values()
+                .stream()
+                .map(desc -> cacheDirName(desc.cacheConfiguration()))
+                .distinct()
+                .collect(Collectors.toList());
+
+            return backupCaches(workDir, allCacheDirs);
+        }
+
+        /** */
+        private PersistenceTaskResult backupCaches(File workDir, List<String> 
cacheDirs) {
+            PersistenceTaskResult res = new PersistenceTaskResult(true);
+
+            List<String> backupCompletedCaches = new ArrayList<>();
+            List<String> backupFailedCaches = new ArrayList<>();
+
+            for (String dir : cacheDirs) {
+                String backupDirName = BACKUP_FOLDER_PREFIX + dir;
+
+                File backupDir = new File(workDir, backupDirName);
+
+                if (!backupDir.exists()) {
+                    try {
+                        U.ensureDirectory(backupDir, backupDirName, null);
+
+                        copyCacheFiles(workDir.toPath().resolve(dir).toFile(), 
backupDir);
+
+                        backupCompletedCaches.add(backupDirName);
+                    } catch (IgniteCheckedException | IOException e) {
+                        backupFailedCaches.add(dir);
+                    }
+                }
+            }
+
+            res.handledCaches(backupCompletedCaches);
+            res.failedCaches(backupFailedCaches);
+
+            return res;
+        }
+
+        /** */
+        private void copyCacheFiles(File sourceDir, File backupDir) throws 
IOException {
+            for (File f : sourceDir.listFiles())
+                Files.copy(f.toPath(), 
backupDir.toPath().resolve(f.getName()), StandardCopyOption.REPLACE_EXISTING);
+        }
+
+        /** */
+        private PersistenceTaskResult clean(PersistenceTaskArg arg) {
+            PersistenceTaskResult res = new PersistenceTaskResult();
+
+            PersistenceCleanAndBackupSettings cleanSettings = 
arg.cleanAndBackupSettings();
+
+            GridCacheProcessor cacheProc = ignite.context().cache();
+            MaintenanceRegistry mntcReg = 
ignite.context().maintenanceRegistry();
+
+            switch (cleanSettings.cleanAndBackupType()) {
+                case ALL:
+                    return cleanAll(cacheProc, mntcReg);
+
+                case CORRUPTED:
+                    return cleanCorrupted(mntcReg);
+
+                case CACHES:
+                    return cleanCaches(cacheProc, mntcReg, 
cleanSettings.cacheNames());
+            }
+
+            return res;
+        }
+
+        /** */
+        private PersistenceTaskResult cleanCaches(
+            GridCacheProcessor cacheProc,
+            MaintenanceRegistry mntcReg,
+            List<String> cacheNames
+        ) {
+            PersistenceTaskResult res = new PersistenceTaskResult(true);
+
+            List<String> cleanedCaches = new ArrayList<>();
+            List<String> failedToCleanCaches = new ArrayList<>();
+
+            DataStorageConfiguration dsCfg = 
ignite.context().config().getDataStorageConfiguration();
+            IgnitePageStoreManager pageStore = cacheProc.context().pageStore();
+
+            AtomicReference<String> missedCache = new AtomicReference<>();
+
+            Boolean allExist = cacheNames
+                .stream()
+                .map(name -> {
+                    if (cacheProc.cacheDescriptor(name) != null)
+                        return true;
+                    else {
+                        missedCache.set(name);
+
+                        return false;
+                    }
+                })
+                .reduce(true, (t, u) -> t && u);
+
+            if (!allExist)
+                throw new IllegalArgumentException("Cache with name " + 
missedCache.get() +
+                    " not found, no caches will be cleaned.");
+
+            for (String name : cacheNames) {
+                DynamicCacheDescriptor cacheDescr = 
cacheProc.cacheDescriptor(name);
+
+                if (CU.isPersistentCache(cacheDescr.cacheConfiguration(), 
dsCfg)) {
+                    try {
+                        
pageStore.cleanupPersistentSpace(cacheDescr.cacheConfiguration());
+
+                        
cleanedCaches.add(cacheDirName(cacheDescr.cacheConfiguration()));
+                    }
+                    catch (IgniteCheckedException e) {
+                        failedToCleanCaches.add(name);
+                    }
+                }
+            }
+
+            res.handledCaches(cleanedCaches);
+
+            if (!failedToCleanCaches.isEmpty())
+                res.failedCaches(failedToCleanCaches);
+
+            List<MaintenanceAction> actions = 
mntcReg.actionsForMaintenanceTask(CORRUPTED_DATA_FILES_MNTC_TASK_NAME);
+
+            Optional<MaintenanceAction> checkActionOpt = 
actions.stream().filter(a -> 
a.name().equals(CheckCorruptedCacheStoresCleanAction.ACTION_NAME))
+                .findFirst();
+
+            if (checkActionOpt.isPresent()) {
+                MaintenanceAction<Boolean> action = checkActionOpt.get();
+
+                Boolean mntcTaskCompleted = action.execute();
+
+                res.maintenanceTaskCompleted(mntcTaskCompleted);
+
+                if (mntcTaskCompleted)
+                    
mntcReg.unregisterMaintenanceTask(CORRUPTED_DATA_FILES_MNTC_TASK_NAME);
+            }
+
+            return res;
+        }
+
+        /** */
+        private PersistenceTaskResult cleanAll(GridCacheProcessor cacheProc, 
MaintenanceRegistry mntcReg) {
+            PersistenceTaskResult res = new PersistenceTaskResult(true);
+
+            List<String> allCacheDirs = cacheProc.cacheDescriptors()
+                .values()
+                .stream()
+                .map(desc -> cacheDirName(desc.cacheConfiguration()))
+                .collect(Collectors.toList());
+
+            try {
+                cacheProc.cleanupCachesDirectories();
+            } catch (IgniteCheckedException e) {
+                throw U.convertException(e);
+            }
+
+            
mntcReg.unregisterMaintenanceTask(CORRUPTED_DATA_FILES_MNTC_TASK_NAME);
+
+            res.maintenanceTaskCompleted(true);
+            res.handledCaches(allCacheDirs);
+
+            return res;
+        }
+
+        /** */
+        private PersistenceTaskResult cleanCorrupted(MaintenanceRegistry 
mntcReg) {
+            PersistenceTaskResult res = new PersistenceTaskResult(true);
+
+            List<MaintenanceAction> actions = mntcReg
+                
.actionsForMaintenanceTask(CORRUPTED_DATA_FILES_MNTC_TASK_NAME);
+
+            Optional<MaintenanceAction> cleanCorruptedActionOpt = actions
+                .stream()
+                .filter(a -> 
a.name().equals(CleanCacheStoresMaintenanceAction.ACTION_NAME))
+                .findFirst();
+
+            if (cleanCorruptedActionOpt.isPresent()) {
+                cleanCorruptedActionOpt.get().execute();
+
+                MaintenanceTask corruptedTask = 
mntcReg.activeMaintenanceTask(CORRUPTED_DATA_FILES_MNTC_TASK_NAME);
+
+                
mntcReg.unregisterMaintenanceTask(CORRUPTED_DATA_FILES_MNTC_TASK_NAME);
+
+                res.handledCaches(
+                    corruptedCacheDirectories(corruptedTask)
+                );
+
+                res.maintenanceTaskCompleted(true);
+            }
+
+            return res;
+        }
+
+        /** */
+        private PersistenceTaskResult info() {
+            PersistenceTaskResult res = new PersistenceTaskResult(true);
+
+            GridCacheProcessor cacheProc = ignite.context().cache();
+            DataStorageConfiguration dsCfg = 
ignite.context().config().getDataStorageConfiguration();
+
+            List<String> corruptedCacheNames = 
corruptedCacheDirectories(ignite.context().maintenanceRegistry()
+                .activeMaintenanceTask(CORRUPTED_DATA_FILES_MNTC_TASK_NAME));
+
+            Map<String, IgniteBiTuple<Boolean, Boolean>> cachesInfo = new 
HashMap<>();
+
+            for (DynamicCacheDescriptor desc : 
cacheProc.cacheDescriptors().values()) {
+                if (!CU.isPersistentCache(desc.cacheConfiguration(), dsCfg))
+                    continue;
+
+                CacheGroupDescriptor grpDesc = desc.groupDescriptor();
+
+                if (grpDesc != null) {
+                    boolean globalWalEnabled = grpDesc.walEnabled();
+                    boolean localWalEnabled = true;
+
+                    if (globalWalEnabled && 
corruptedCacheNames.contains(desc.cacheName()))
+                        localWalEnabled = false;
+
+                    cachesInfo.put(desc.cacheName(), new 
IgniteBiTuple<>(globalWalEnabled, localWalEnabled));
+                }
+            }
+
+            res.cachesInfo(cachesInfo);
+
+            return res;
+        }
+
+        /** */
+        private List<String> corruptedCacheDirectories(MaintenanceTask task) {
+            String params = task.parameters();
+
+            String[] namesArr = params.split(File.separator);
+
+            return Arrays.asList(namesArr);
+        }
+
+        /** */
+        private List<String> cacheDirectoriesFromCacheNames(List<String> 
cacheNames) {
+            GridCacheProcessor cacheProc = ignite.context().cache();
+
+            DataStorageConfiguration dsCfg = 
ignite.configuration().getDataStorageConfiguration();
+
+            AtomicReference<String> missedCache = new AtomicReference<>();
+
+            Boolean allExist = cacheNames.stream()
+                .map(s -> {
+                    if (cacheProc.cacheDescriptor(s) != null)
+                        return true;
+                    else {
+                        missedCache.set(s);
+
+                        return false;
+                    }
+                })
+                .reduce(true, (u, v) -> u && v);
+
+            if (!allExist)
+                throw new IllegalArgumentException("Cache with name " + 
missedCache.get() +
+                    " not found, no caches will be backed up.");
+
+            return cacheNames.stream()
+                .filter(s -> cacheProc.cacheDescriptor(s) != null)
+                .filter(s ->
+                    
CU.isPersistentCache(cacheProc.cacheDescriptor(s).cacheConfiguration(), dsCfg))
+                .map(s -> cacheProc.cacheDescriptor(s).cacheConfiguration())
+                .map(FilePageStoreManager::cacheDirName)
+                .distinct()
+                .collect(Collectors.toList());
+        }
+    }
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceTaskArg.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceTaskArg.java
new file mode 100644
index 0000000..c48f936
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceTaskArg.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.visor.persistence;
+
+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.U;
+
+/**
+ *
+ */
+public class PersistenceTaskArg extends IgniteDataTransferObject {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** */
+    private PersistenceOperation op;
+
+    /** */
+    private PersistenceCleanAndBackupSettings cleanAndBackupSettings;
+
+    /**
+     * Default constructor.
+     */
+    public PersistenceTaskArg() {
+        // No-op.
+    }
+
+    /**
+     * @param op {@link PersistenceOperation} requested for execution.
+     * @param cleanAndBackupSettings {@link PersistenceCleanAndBackupSettings} 
specific settings for clean and backup
+     *                                                                        
commands.
+     */
+    public PersistenceTaskArg(PersistenceOperation op, 
PersistenceCleanAndBackupSettings cleanAndBackupSettings) {
+        this.op = op;
+        this.cleanAndBackupSettings = cleanAndBackupSettings;
+    }
+
+    /**
+     * @return {@link PersistenceOperation} operation requested for execution.
+     */
+    public PersistenceOperation operation() {
+        return op;
+    }
+
+    /**
+     * @return {@link PersistenceCleanAndBackupSettings} specific settings for 
clean and backup commands.
+     */
+    public PersistenceCleanAndBackupSettings cleanAndBackupSettings() {
+        return cleanAndBackupSettings;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void writeExternalData(ObjectOutput out) throws 
IOException {
+        U.writeEnum(out, op);
+        out.writeObject(cleanAndBackupSettings);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in) 
throws IOException, ClassNotFoundException {
+        op = PersistenceOperation.fromOrdinal(in.readByte());
+        cleanAndBackupSettings = (PersistenceCleanAndBackupSettings) 
in.readObject();
+    }
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceTaskResult.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceTaskResult.java
new file mode 100644
index 0000000..5a0a0fe
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/visor/persistence/PersistenceTaskResult.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.visor.persistence;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgniteBiTuple;
+
+public class PersistenceTaskResult extends IgniteDataTransferObject {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** */
+    private boolean inMaintenanceMode;
+
+    /** */
+    private boolean maintenanceTaskCompleted;
+
+    /** */
+    private List<String> handledCaches;
+
+    /** */
+    private List<String> failedToHandleCaches;
+
+    /** */
+    private Map<String, IgniteBiTuple<Boolean, Boolean>> cachesInfo;
+
+    /** */
+    public PersistenceTaskResult() {
+        // No-op.
+    }
+
+    /**
+     *
+     */
+    public PersistenceTaskResult(boolean inMaintenanceMode) {
+        this.inMaintenanceMode = inMaintenanceMode;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void writeExternalData(ObjectOutput out) throws 
IOException {
+        out.writeBoolean(inMaintenanceMode);
+        out.writeBoolean(maintenanceTaskCompleted);
+        U.writeCollection(out, handledCaches);
+        U.writeCollection(out, failedToHandleCaches);
+        U.writeMap(out, cachesInfo);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in) 
throws IOException, ClassNotFoundException {
+        inMaintenanceMode = in.readBoolean();
+        maintenanceTaskCompleted = in.readBoolean();
+        handledCaches = U.readList(in);
+        failedToHandleCaches = U.readList(in);
+        cachesInfo = U.readMap(in);
+    }
+
+    /** */
+    public boolean inMaintenanceMode() {
+        return inMaintenanceMode;
+    }
+
+    /** */
+    public boolean maintenanceTaskCompleted() {
+        return maintenanceTaskCompleted;
+    }
+
+    /** */
+    public void maintenanceTaskCompleted(boolean maintenanceTaskCompleted) {
+        this.maintenanceTaskCompleted = maintenanceTaskCompleted;
+    }
+
+    /** */
+    public List<String> handledCaches() {
+        return handledCaches;
+    }
+
+    /** */
+    public void handledCaches(List<String> handledCaches) {
+        this.handledCaches = handledCaches;
+    }
+
+    /** */
+    public List<String> failedCaches() {
+        return failedToHandleCaches;
+    }
+
+    /** */
+    public void failedCaches(List<String> failedToHandleCaches) {
+        this.failedToHandleCaches = failedToHandleCaches;
+    }
+
+    /** */
+    public Map<String, IgniteBiTuple<Boolean, Boolean>> cachesInfo() {
+        return cachesInfo;
+    }
+
+    /** */
+    public void cachesInfo(Map<String, IgniteBiTuple<Boolean, Boolean>> 
cachesInfo) {
+        this.cachesInfo = cachesInfo;
+    }
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/maintenance/MaintenanceTask.java 
b/modules/core/src/main/java/org/apache/ignite/maintenance/MaintenanceTask.java
index 49795f1..c600952 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/maintenance/MaintenanceTask.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/maintenance/MaintenanceTask.java
@@ -24,8 +24,8 @@ import org.jetbrains.annotations.Nullable;
 /**
  * Represents request to handle maintenance situation.
  *
- * It can be created automatically or by user request by any component needed 
maintenance and should be registered
- * in Maintenance Registry with the method {@link 
MaintenanceRegistry#registerMaintenanceTask(MaintenanceTask)}.
+ * Maintenance request can be created programmatically
+ * with {@link MaintenanceRegistry#registerMaintenanceTask(MaintenanceTask)} 
public API call.
  *
  * Lifecycle of Maintenance Task is managed by {@link MaintenanceRegistry}.
  *
diff --git a/modules/core/src/main/resources/META-INF/classnames.properties 
b/modules/core/src/main/resources/META-INF/classnames.properties
index deac9f8..92d01dd 100644
--- a/modules/core/src/main/resources/META-INF/classnames.properties
+++ b/modules/core/src/main/resources/META-INF/classnames.properties
@@ -350,20 +350,7 @@ 
org.apache.ignite.internal.commandline.cache.argument.IdleVerifyCommandArg
 org.apache.ignite.internal.commandline.cache.argument.ListCommandArg
 
org.apache.ignite.internal.commandline.cache.argument.PartitionReconciliationCommandArg
 org.apache.ignite.internal.commandline.cache.argument.ValidateIndexesCommandArg
-org.apache.ignite.internal.commandline.cache.check_indexes_inline_size.CheckIndexInlineSizesResult
-org.apache.ignite.internal.commandline.cache.check_indexes_inline_size.CheckIndexInlineSizesTask
 
org.apache.ignite.internal.commandline.cache.check_indexes_inline_size.CheckIndexInlineSizesTask$CheckIndexInlineSizesJob
-org.apache.ignite.internal.commandline.cache.distribution.CacheDistributionGroup
-org.apache.ignite.internal.commandline.cache.distribution.CacheDistributionNode
-org.apache.ignite.internal.commandline.cache.distribution.CacheDistributionPartition
-org.apache.ignite.internal.commandline.cache.distribution.CacheDistributionTask
-org.apache.ignite.internal.commandline.cache.distribution.CacheDistributionTask$CacheDistributionJob
-org.apache.ignite.internal.commandline.cache.distribution.CacheDistributionTaskArg
-org.apache.ignite.internal.commandline.cache.distribution.CacheDistributionTaskResult
-org.apache.ignite.internal.commandline.cache.reset_lost_partitions.CacheResetLostPartitionsTask
-org.apache.ignite.internal.commandline.cache.reset_lost_partitions.CacheResetLostPartitionsTask$CacheResetLostPartitionsJob
-org.apache.ignite.internal.commandline.cache.reset_lost_partitions.CacheResetLostPartitionsTaskArg
-org.apache.ignite.internal.commandline.cache.reset_lost_partitions.CacheResetLostPartitionsTaskResult
 org.apache.ignite.internal.commandline.diagnostic.DiagnosticSubCommand
 
org.apache.ignite.internal.commandline.diagnostic.PageLocksCommand$PageLocksCommandArg
 org.apache.ignite.internal.commandline.dr.DrSubCommandsList
@@ -373,16 +360,8 @@ 
org.apache.ignite.internal.commandline.dr.subcommands.DrCacheCommand$SenderGroup
 org.apache.ignite.internal.commandline.management.ManagementCommandList
 org.apache.ignite.internal.commandline.management.ManagementURLCommandArg
 
org.apache.ignite.internal.commandline.meta.subcommands.MetadataAbstractSubCommand.VoidDto
-org.apache.ignite.internal.commandline.meta.tasks.MetadataListResult
-org.apache.ignite.internal.commandline.meta.tasks.MetadataInfoTask
-org.apache.ignite.internal.commandline.meta.tasks.MetadataInfoTask.MetadataListJob
-org.apache.ignite.internal.commandline.meta.tasks.MetadataMarshalled
-org.apache.ignite.internal.commandline.meta.tasks.MetadataRemoveTask
 
org.apache.ignite.internal.commandline.meta.tasks.MetadataRemoveTask$MetadataRemoveJob
 
org.apache.ignite.internal.commandline.meta.tasks.MetadataRemoveTask$DropAllThinSessionsJob
-org.apache.ignite.internal.commandline.meta.tasks.MetadataTypeArgs
-org.apache.ignite.internal.commandline.meta.tasks.MetadataUpdateTask
-org.apache.ignite.internal.commandline.meta.tasks.MetadataUpdateTask.MetadataUpdateJob
 org.apache.ignite.internal.commandline.property.tasks.PropertiesListResult
 org.apache.ignite.internal.commandline.property.tasks.PropertiesListTask
 org.apache.ignite.internal.commandline.property.tasks.PropertyOperationResult
@@ -2285,6 +2264,7 @@ org.apache.ignite.internal.visor.query.VisorRunningQuery
 org.apache.ignite.internal.visor.query.VisorScanQueryTask
 org.apache.ignite.internal.visor.query.VisorScanQueryTask$VisorScanQueryJob
 org.apache.ignite.internal.visor.query.VisorScanQueryTaskArg
+org.apache.ignite.internal.visor.persistence.PersistenceTaskResult
 org.apache.ignite.internal.visor.service.VisorCancelServiceTask
 
org.apache.ignite.internal.visor.service.VisorCancelServiceTask$VisorCancelServiceJob
 org.apache.ignite.internal.visor.service.VisorCancelServiceTaskArg
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 2806cd6..60ef386 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
@@ -197,6 +197,30 @@ If the file name isn't specified the output file name is: 
'<typeId>.bin'
       name     - Name of the metric which value should be printed. If name of 
the metric registry is specified, value of all its metrics will be printed.
       node_id  - ID of the node to get the metric values from. If not set, 
random node will be chosen.
 
+  Print information about potentially corrupted caches on local node:
+    control.(sh|bat) --persistence
+
+  The same information is printed when info subcommand is passed:
+    control.(sh|bat) --persistence info
+
+  Clean directories of caches with corrupted data files:
+    control.(sh|bat) --persistence clean corrupted
+
+  Clean directories of all caches:
+    control.(sh|bat) --persistence clean all
+
+  Clean directories of only given caches:
+    control.(sh|bat) --persistence clean caches cache1,cache2,cache3
+
+  Backup data files of corrupted caches only:
+    control.(sh|bat) --persistence backup corrupted
+
+  Backup data files of all caches:
+    control.(sh|bat) --persistence backup all
+
+  Backup data files of only given caches:
+    control.(sh|bat) --persistence backup caches cache1,cache2,cache3
+
 By default commands affecting the cluster require interactive confirmation.
 Use --yes option to disable it.
 
diff --git 
a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output
 
b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output
index 2806cd6..60ef386 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
@@ -197,6 +197,30 @@ If the file name isn't specified the output file name is: 
'<typeId>.bin'
       name     - Name of the metric which value should be printed. If name of 
the metric registry is specified, value of all its metrics will be printed.
       node_id  - ID of the node to get the metric values from. If not set, 
random node will be chosen.
 
+  Print information about potentially corrupted caches on local node:
+    control.(sh|bat) --persistence
+
+  The same information is printed when info subcommand is passed:
+    control.(sh|bat) --persistence info
+
+  Clean directories of caches with corrupted data files:
+    control.(sh|bat) --persistence clean corrupted
+
+  Clean directories of all caches:
+    control.(sh|bat) --persistence clean all
+
+  Clean directories of only given caches:
+    control.(sh|bat) --persistence clean caches cache1,cache2,cache3
+
+  Backup data files of corrupted caches only:
+    control.(sh|bat) --persistence backup corrupted
+
+  Backup data files of all caches:
+    control.(sh|bat) --persistence backup all
+
+  Backup data files of only given caches:
+    control.(sh|bat) --persistence backup caches cache1,cache2,cache3
+
 By default commands affecting the cluster require interactive confirmation.
 Use --yes option to disable it.
 

Reply via email to