You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by se...@apache.org on 2020/11/03 14:27:53 UTC
[ignite] branch master updated: IGNITE-13550 Persistence CLEAN
command implementation - Fixes #8408.
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 <se...@gmail.com>
AuthorDate: Tue Nov 3 17:15:24 2020 +0300
IGNITE-13550 Persistence CLEAN command implementation - Fixes #8408.
Signed-off-by: Sergey Chugunov <se...@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.