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

apolovtsev pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 6c5fadcaa1 IGNITE-22166 CLI for disaster recovery: 
reset-lost-partitions (#3788)
6c5fadcaa1 is described below

commit 6c5fadcaa196e08028b0c65a923cb77d70b54b74
Author: Phillippko <[email protected]>
AuthorDate: Tue May 21 12:33:38 2024 +0400

    IGNITE-22166 CLI for disaster recovery: reset-lost-partitions (#3788)
---
 .../java/org/apache/ignite/lang/ErrorGroups.java   |   2 +-
 .../recovery/ItPartitionStatesCommandTest.java     |   5 +-
 ...Test.java => ItResetPartitionsCommandTest.java} |  11 +-
 .../ItResetPartitionsReplCommandTest.java}         |  18 ++-
 .../commands/recovery/ItResetPartitionsTest.java   | 118 +++++++++++++++++++
 .../call/recovery/PartitionStatesCallInput.java    |  24 ++--
 .../call/recovery/reset/ResetPartitionsCall.java   |  56 +++++++++
 .../recovery/reset/ResetPartitionsCallInput.java   | 130 +++++++++++++++++++++
 .../ignite/internal/cli/commands/Options.java      |  14 ++-
 .../cli/commands/recovery/RecoveryCommand.java     |   4 +-
 .../cli/commands/recovery/RecoveryReplCommand.java |   4 +-
 .../recovery/reset/ResetPartitionsCommand.java     |  48 ++++++++
 .../recovery/reset/ResetPartitionsMixin.java       |  65 +++++++++++
 .../recovery/reset/ResetPartitionsReplCommand.java |  50 ++++++++
 .../DistributionZoneNotFoundException.java         |   6 +-
 .../rest/api/recovery/DisasterRecoveryApi.java     |   4 +-
 .../recovery/ItDisasterRecoveryControllerTest.java |   2 +-
 17 files changed, 522 insertions(+), 39 deletions(-)

diff --git a/modules/api/src/main/java/org/apache/ignite/lang/ErrorGroups.java 
b/modules/api/src/main/java/org/apache/ignite/lang/ErrorGroups.java
index 6ef49bb962..960f6e7821 100755
--- a/modules/api/src/main/java/org/apache/ignite/lang/ErrorGroups.java
+++ b/modules/api/src/main/java/org/apache/ignite/lang/ErrorGroups.java
@@ -431,7 +431,7 @@ public class ErrorGroups {
         /** Distribution zones group. */
         public static final ErrorGroup DISTRIBUTION_ZONES_ERR_GROUP = 
registerGroup("DISTRZONES", (short) 10);
 
-        /** Distribution zone is not found. */
+        /** Distribution zone was not found. */
         public static final int ZONE_NOT_FOUND_ERR = 
DISTRIBUTION_ZONES_ERR_GROUP.registerErrorCode((short) 1);
     }
 
diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/ItPartitionStatesCommandTest.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/ItPartitionStatesCommandTest.java
index 1b62ee971d..d512d0248e 100644
--- 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/ItPartitionStatesCommandTest.java
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/ItPartitionStatesCommandTest.java
@@ -17,16 +17,15 @@
 
 package org.apache.ignite.internal.cli.commands.recovery;
 
-import java.util.List;
 import 
org.apache.ignite.internal.cli.commands.recovery.partitions.PartitionStatesCommand;
-import org.apache.ignite.internal.util.CollectionUtils;
+import org.apache.ignite.internal.util.ArrayUtils;
 
 /** Test class for {@link PartitionStatesCommand}. */
 public class ItPartitionStatesCommandTest extends ItPartitionStatesTest {
 
     @Override
     protected void execute(String... args) {
-        String[] fullArgs = CollectionUtils.concat(List.of("recovery", 
"partition-states"), List.of(args)).toArray(String[]::new);
+        String[] fullArgs = ArrayUtils.concat(new String[] {"recovery", 
"partition-states"}, args);
 
         super.execute(fullArgs);
     }
diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/ItPartitionStatesCommandTest.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/ItResetPartitionsCommandTest.java
similarity index 69%
copy from 
modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/ItPartitionStatesCommandTest.java
copy to 
modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/ItResetPartitionsCommandTest.java
index 1b62ee971d..df483572a3 100644
--- 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/ItPartitionStatesCommandTest.java
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/ItResetPartitionsCommandTest.java
@@ -17,16 +17,15 @@
 
 package org.apache.ignite.internal.cli.commands.recovery;
 
-import java.util.List;
-import 
org.apache.ignite.internal.cli.commands.recovery.partitions.PartitionStatesCommand;
-import org.apache.ignite.internal.util.CollectionUtils;
+import 
org.apache.ignite.internal.cli.commands.recovery.reset.ResetPartitionsCommand;
+import org.apache.ignite.internal.util.ArrayUtils;
 
-/** Test class for {@link PartitionStatesCommand}. */
-public class ItPartitionStatesCommandTest extends ItPartitionStatesTest {
+/** Test class for {@link ResetPartitionsCommand}. */
+public class ItResetPartitionsCommandTest extends ItResetPartitionsTest {
 
     @Override
     protected void execute(String... args) {
-        String[] fullArgs = CollectionUtils.concat(List.of("recovery", 
"partition-states"), List.of(args)).toArray(String[]::new);
+        String[] fullArgs = ArrayUtils.concat(new String[] {"recovery", 
"reset-partitions"}, args);
 
         super.execute(fullArgs);
     }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/RecoveryCommand.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/ItResetPartitionsReplCommandTest.java
similarity index 65%
copy from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/RecoveryCommand.java
copy to 
modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/ItResetPartitionsReplCommandTest.java
index bba19e69cf..b0e476ab23 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/RecoveryCommand.java
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/ItResetPartitionsReplCommandTest.java
@@ -17,15 +17,13 @@
 
 package org.apache.ignite.internal.cli.commands.recovery;
 
-import org.apache.ignite.internal.cli.commands.BaseCommand;
-import 
org.apache.ignite.internal.cli.commands.recovery.partitions.PartitionStatesCommand;
-import picocli.CommandLine.Command;
+import 
org.apache.ignite.internal.cli.commands.recovery.reset.ResetPartitionsReplCommand;
 
-/** Disaster recovery command. */
-@Command(name = "recovery",
-        subcommands = {
-                PartitionStatesCommand.class
-        },
-        description = "Managers disaster recovery of Ignite cluster")
-public class RecoveryCommand extends BaseCommand {
+/** Test class for {@link ResetPartitionsReplCommand}. */
+public class ItResetPartitionsReplCommandTest extends ItResetPartitionsTest {
+
+    @Override
+    protected Class<?> getCommandClass() {
+        return ResetPartitionsReplCommand.class;
+    }
 }
diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/ItResetPartitionsTest.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/ItResetPartitionsTest.java
new file mode 100644
index 0000000000..474de59819
--- /dev/null
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/ItResetPartitionsTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.cli.commands.recovery;
+
+import static 
org.apache.ignite.internal.TestDefaultProfilesNames.DEFAULT_AIPERSIST_PROFILE_NAME;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_URL_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_PARTITION_IDS_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_TABLE_NAME_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_ZONE_NAME_OPTION;
+
+import org.apache.ignite.internal.cli.CliIntegrationTest;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/** Base test class for Recovery reset partitions commands. */
+public abstract class ItResetPartitionsTest extends CliIntegrationTest {
+    private static final String ZONE = "first_ZONE";
+
+    private static final String TABLE_NAME = "first_ZONE_table";
+
+    private static final String QUALIFIED_TABLE_NAME = "PUBLIC." + TABLE_NAME;
+
+    private static final int DEFAULT_PARTITION_COUNT = 25;
+
+    @BeforeAll
+    public void createTables() {
+        sql(String.format("CREATE ZONE \"%s\" WITH storage_profiles='%s'", 
ZONE, DEFAULT_AIPERSIST_PROFILE_NAME));
+        sql(String.format("CREATE TABLE PUBLIC.\"%s\" (id INT PRIMARY KEY, val 
INT) WITH PRIMARY_ZONE = '%s'", TABLE_NAME, ZONE));
+    }
+
+    @Test
+    public void testResetAllPartitions() {
+        execute(CLUSTER_URL_OPTION, NODE_URL,
+                RECOVERY_TABLE_NAME_OPTION, QUALIFIED_TABLE_NAME,
+                RECOVERY_ZONE_NAME_OPTION, ZONE);
+
+        assertErrOutputIsEmpty();
+        assertOutputContains("Successfully started resetting partitions.");
+    }
+
+    @Test
+    public void testResetSpecifiedPartitions() {
+        execute(CLUSTER_URL_OPTION, NODE_URL,
+                RECOVERY_TABLE_NAME_OPTION, QUALIFIED_TABLE_NAME,
+                RECOVERY_ZONE_NAME_OPTION, ZONE,
+                RECOVERY_PARTITION_IDS_OPTION, "1,2");
+
+        assertErrOutputIsEmpty();
+        assertOutputContains("Successfully started resetting partitions.");
+    }
+
+    @Test
+    public void testResetPartitionZoneNotFound() {
+        String unknownZone = "unknown_zone";
+
+        execute(CLUSTER_URL_OPTION, NODE_URL,
+                RECOVERY_TABLE_NAME_OPTION, QUALIFIED_TABLE_NAME,
+                RECOVERY_ZONE_NAME_OPTION, unknownZone,
+                RECOVERY_PARTITION_IDS_OPTION, "1,2");
+
+        assertErrOutputContains("Distribution zone was not found [zoneName=" + 
unknownZone + "]");
+        assertOutputIsEmpty();
+    }
+
+    @Test
+    public void testResetPartitionTableNotFound() {
+        String unknownTable = "PUBLIC.unknown_table";
+
+        execute(CLUSTER_URL_OPTION, NODE_URL,
+                RECOVERY_TABLE_NAME_OPTION, unknownTable,
+                RECOVERY_ZONE_NAME_OPTION, ZONE);
+
+        assertErrOutputContains("The table does not exist [name=" + 
unknownTable + "]");
+        assertOutputIsEmpty();
+    }
+
+    @Test
+    public void testResetPartitionsIllegalPartitionNegative() {
+        execute(CLUSTER_URL_OPTION, NODE_URL,
+                RECOVERY_TABLE_NAME_OPTION, QUALIFIED_TABLE_NAME,
+                RECOVERY_ZONE_NAME_OPTION, ZONE,
+                RECOVERY_PARTITION_IDS_OPTION, "0,5,-1,-10");
+
+        assertErrOutputContains("Partition ID can't be negative, found: -10");
+        assertOutputIsEmpty();
+    }
+
+    @Test
+    public void testResetPartitionsPartitionsOutOfRange() {
+        execute(CLUSTER_URL_OPTION, NODE_URL,
+                RECOVERY_TABLE_NAME_OPTION, QUALIFIED_TABLE_NAME,
+                RECOVERY_ZONE_NAME_OPTION, ZONE,
+                RECOVERY_PARTITION_IDS_OPTION, 
String.valueOf(DEFAULT_PARTITION_COUNT));
+
+        assertErrOutputContains(String.format(
+                "Partition IDs should be in range [0, %d] for zone %s, found: 
%d",
+                DEFAULT_PARTITION_COUNT - 1,
+                ZONE,
+                DEFAULT_PARTITION_COUNT
+        ));
+        assertOutputIsEmpty();
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/recovery/PartitionStatesCallInput.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/recovery/PartitionStatesCallInput.java
index 4da4275864..41d0749849 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/recovery/PartitionStatesCallInput.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/recovery/PartitionStatesCallInput.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.cli.call.recovery;
 import java.util.List;
 import 
org.apache.ignite.internal.cli.commands.recovery.partitions.PartitionStatesMixin;
 import org.apache.ignite.internal.cli.core.call.CallInput;
+import org.jetbrains.annotations.Nullable;
 
 /** Input for the {@link PartitionStatesCall} call. */
 public class PartitionStatesCallInput implements CallInput {
@@ -61,9 +62,9 @@ public class PartitionStatesCallInput implements CallInput {
     private PartitionStatesCallInput(
             String clusterUrl,
             boolean local,
-            List<String> nodeNames,
-            List<String> zoneNames,
-            List<Integer> partitionIds
+            @Nullable List<String> nodeNames,
+            @Nullable List<String> zoneNames,
+            @Nullable List<Integer> partitionIds
     ) {
         this.clusterUrl = clusterUrl;
         this.local = local;
@@ -102,10 +103,13 @@ public class PartitionStatesCallInput implements 
CallInput {
 
         private boolean local;
 
+        @Nullable
         private List<String> nodeNames;
 
+        @Nullable
         private List<String> zoneNames;
 
+        @Nullable
         private List<Integer> partitionIds;
 
         /** Set cluster URL. */
@@ -120,25 +124,25 @@ public class PartitionStatesCallInput implements 
CallInput {
             return this;
         }
 
-        /** Set names of zones to get partition states of. */
-        PartitionStatesCallInputBuilder nodeNames(List<String> nodeNames) {
+        /** Set names of zones to get partition states of. All if empty or 
null. */
+        PartitionStatesCallInputBuilder nodeNames(@Nullable List<String> 
nodeNames) {
             this.nodeNames = nodeNames;
             return this;
         }
 
-        /** Set names of zones to get partition states of. */
-        PartitionStatesCallInputBuilder zoneNames(List<String> zoneNames) {
+        /** Set names of zones to get partition states of. All if empty or 
null. */
+        PartitionStatesCallInputBuilder zoneNames(@Nullable List<String> 
zoneNames) {
             this.zoneNames = zoneNames;
             return this;
         }
 
-        /** Names of zones to get partition states of. */
-        PartitionStatesCallInputBuilder partitionIds(List<Integer> 
partitionIds) {
+        /** Names of zones to get partition states of. All if empty or null. */
+        PartitionStatesCallInputBuilder partitionIds(@Nullable List<Integer> 
partitionIds) {
             this.partitionIds = partitionIds;
             return this;
         }
 
-        /** Set IDs of partitions to get states of. */
+        /** Build {@link PartitionStatesCallInput}. */
         PartitionStatesCallInput build() {
             return new PartitionStatesCallInput(clusterUrl, local, nodeNames, 
zoneNames, partitionIds);
         }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/recovery/reset/ResetPartitionsCall.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/recovery/reset/ResetPartitionsCall.java
new file mode 100644
index 0000000000..4813ef9996
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/recovery/reset/ResetPartitionsCall.java
@@ -0,0 +1,56 @@
+/*
+ * 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.cli.call.recovery.reset;
+
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.cli.core.call.Call;
+import org.apache.ignite.internal.cli.core.call.DefaultCallOutput;
+import org.apache.ignite.internal.cli.core.exception.IgniteCliApiException;
+import org.apache.ignite.internal.cli.core.rest.ApiClientFactory;
+import org.apache.ignite.rest.client.api.RecoveryApi;
+import org.apache.ignite.rest.client.invoker.ApiException;
+import org.apache.ignite.rest.client.model.ResetPartitionsRequest;
+
+/** Call to reset partitions. */
+@Singleton
+public class ResetPartitionsCall implements Call<ResetPartitionsCallInput, 
String> {
+    private final ApiClientFactory clientFactory;
+
+    public ResetPartitionsCall(ApiClientFactory clientFactory) {
+        this.clientFactory = clientFactory;
+    }
+
+    @Override
+    public DefaultCallOutput<String> execute(ResetPartitionsCallInput input) {
+        RecoveryApi client = new 
RecoveryApi(clientFactory.getClient(input.clusterUrl()));
+
+        ResetPartitionsRequest command = new ResetPartitionsRequest();
+
+        command.setPartitionIds(input.partitionIds());
+        command.setTableName(input.tableName().trim());
+        command.setZoneName(input.zoneName().trim());
+
+        try {
+            client.resetPartitions(command);
+
+            return DefaultCallOutput.success("Successfully started resetting 
partitions.");
+        } catch (ApiException e) {
+            return DefaultCallOutput.failure(new IgniteCliApiException(e, 
input.clusterUrl()));
+        }
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/recovery/reset/ResetPartitionsCallInput.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/recovery/reset/ResetPartitionsCallInput.java
new file mode 100644
index 0000000000..e1a9b9f23c
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/recovery/reset/ResetPartitionsCallInput.java
@@ -0,0 +1,130 @@
+/*
+ * 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.cli.call.recovery.reset;
+
+import java.util.List;
+import 
org.apache.ignite.internal.cli.commands.recovery.reset.ResetPartitionsMixin;
+import org.apache.ignite.internal.cli.core.call.CallInput;
+import org.jetbrains.annotations.Nullable;
+
+/** Input for the {@link ResetPartitionsCall} call. */
+public class ResetPartitionsCallInput implements CallInput {
+    private final String clusterUrl;
+
+    private final String zoneName;
+
+    private final String tableName;
+
+    private final List<Integer> partitionIds;
+
+    /** Cluster url. */
+    public String clusterUrl() {
+        return clusterUrl;
+    }
+
+    /** Returns zone name to reset partitions of. */
+    public String zoneName() {
+        return zoneName;
+    }
+
+    /** Returns table name to reset partitions of. */
+    public String tableName() {
+        return tableName;
+    }
+
+    /** IDs of partitions to reset. */
+    public List<Integer> partitionIds() {
+        return partitionIds;
+    }
+
+    private ResetPartitionsCallInput(
+            String clusterUrl,
+            String zoneName,
+            String tableName,
+            @Nullable List<Integer> partitionIds
+    ) {
+        this.clusterUrl = clusterUrl;
+        this.zoneName = zoneName;
+        this.tableName = tableName;
+        this.partitionIds = partitionIds == null ? List.of() : 
List.copyOf(partitionIds);
+    }
+
+    public static ResetPartitionsCallInput of(ResetPartitionsMixin statesArgs) 
{
+        return of(statesArgs, statesArgs.clusterUrl());
+    }
+
+    /** Returns {@link ResetPartitionsCallInput} with specified arguments. */
+    public static ResetPartitionsCallInput of(ResetPartitionsMixin statesArgs, 
String clusterUrl) {
+        return builder()
+                .zoneName(statesArgs.zoneName())
+                .tableName(statesArgs.tableName())
+                .partitionIds(statesArgs.partitionIds())
+                .clusterUrl(clusterUrl)
+                .build();
+    }
+
+    /**
+     * Builder method provider.
+     *
+     * @return new instance of {@link ResetPartitionsCallInput}.
+     */
+    private static ResetPartitionsCallInputBuilder builder() {
+        return new ResetPartitionsCallInputBuilder();
+    }
+
+    /** Builder for {@link ResetPartitionsCallInput}. */
+    private static class ResetPartitionsCallInputBuilder {
+        private String clusterUrl;
+
+        private String zoneName;
+
+        private String tableName;
+
+        @Nullable
+        private List<Integer> partitionIds;
+
+        /** Set cluster URL. */
+        ResetPartitionsCallInputBuilder clusterUrl(String clusterUrl) {
+            this.clusterUrl = clusterUrl;
+            return this;
+        }
+
+        /** Set name of zone to reset partitions of. */
+        ResetPartitionsCallInputBuilder zoneName(String zoneName) {
+            this.zoneName = zoneName;
+            return this;
+        }
+
+        /** Set name of table to reset partitions of. */
+        ResetPartitionsCallInputBuilder tableName(String tableName) {
+            this.tableName = tableName;
+            return this;
+        }
+
+        /** Names of zones to reset partitions of. */
+        ResetPartitionsCallInputBuilder partitionIds(@Nullable List<Integer> 
partitionIds) {
+            this.partitionIds = partitionIds;
+            return this;
+        }
+
+        /** Build {@link ResetPartitionsCallInput}. */
+        ResetPartitionsCallInput build() {
+            return new ResetPartitionsCallInput(clusterUrl, zoneName, 
tableName, partitionIds);
+        }
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/Options.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/Options.java
index 1d3ad48685..6db9a0d744 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/Options.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/Options.java
@@ -298,11 +298,21 @@ public enum Options {
         public static final String RECOVERY_ZONE_NAMES_OPTION = "--zones";
 
         public static final String RECOVERY_ZONE_NAMES_OPTION_DESC = "Names 
specifying zones to get partition states from. "
-                + "Case-sensitive, all zones if not set";
+                + "Case-sensitive, without quotes, all zones if not set";
+
+        public static final String RECOVERY_ZONE_NAME_OPTION = "--zone";
+
+        public static final String RECOVERY_ZONE_NAME_OPTION_DESC = "Name of 
the zone to reset partitions of. "
+                + "Case-sensitive, without quotes";
+
+        public static final String RECOVERY_TABLE_NAME_OPTION = "--table";
+
+        public static final String RECOVERY_TABLE_NAME_OPTION_DESC = 
"Fully-qualified name of the table to reset partitions of. "
+                + "Case-sensitive, without quotes";
 
         public static final String RECOVERY_NODE_NAMES_OPTION = "--nodes";
 
         public static final String RECOVERY_NODE_NAMES_OPTION_DESC = "Names 
specifying nodes to get partition states from. "
-                + "Case-sensitive, all nodes if not set";
+                + "Case-sensitive, without quotes, all nodes if not set";
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/RecoveryCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/RecoveryCommand.java
index bba19e69cf..c36e854bc4 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/RecoveryCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/RecoveryCommand.java
@@ -19,12 +19,14 @@ package org.apache.ignite.internal.cli.commands.recovery;
 
 import org.apache.ignite.internal.cli.commands.BaseCommand;
 import 
org.apache.ignite.internal.cli.commands.recovery.partitions.PartitionStatesCommand;
+import 
org.apache.ignite.internal.cli.commands.recovery.reset.ResetPartitionsCommand;
 import picocli.CommandLine.Command;
 
 /** Disaster recovery command. */
 @Command(name = "recovery",
         subcommands = {
-                PartitionStatesCommand.class
+                PartitionStatesCommand.class,
+                ResetPartitionsCommand.class
         },
         description = "Managers disaster recovery of Ignite cluster")
 public class RecoveryCommand extends BaseCommand {
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/RecoveryReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/RecoveryReplCommand.java
index dfc26a2bfa..120895bdfa 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/RecoveryReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/RecoveryReplCommand.java
@@ -19,12 +19,14 @@ package org.apache.ignite.internal.cli.commands.recovery;
 
 import org.apache.ignite.internal.cli.commands.BaseCommand;
 import 
org.apache.ignite.internal.cli.commands.recovery.partitions.PartitionStatesReplCommand;
+import 
org.apache.ignite.internal.cli.commands.recovery.reset.ResetPartitionsReplCommand;
 import picocli.CommandLine.Command;
 
 /** Disaster recovery command. */
 @Command(name = "recovery",
         subcommands = {
-                PartitionStatesReplCommand.class
+                PartitionStatesReplCommand.class,
+                ResetPartitionsReplCommand.class
         },
         description = "Managers disaster recovery of Ignite cluster")
 public class RecoveryReplCommand extends BaseCommand {
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/reset/ResetPartitionsCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/reset/ResetPartitionsCommand.java
new file mode 100644
index 0000000000..0eaf277ad3
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/reset/ResetPartitionsCommand.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cli.commands.recovery.reset;
+
+import jakarta.inject.Inject;
+import java.util.concurrent.Callable;
+import org.apache.ignite.internal.cli.call.recovery.reset.ResetPartitionsCall;
+import 
org.apache.ignite.internal.cli.call.recovery.reset.ResetPartitionsCallInput;
+import org.apache.ignite.internal.cli.commands.BaseCommand;
+import org.apache.ignite.internal.cli.core.call.CallExecutionPipeline;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
+
+/** Command to reset partitions. */
+@Command(name = "reset-partitions", description = "Resets partitions.")
+public class ResetPartitionsCommand extends BaseCommand implements 
Callable<Integer> {
+    @Mixin
+    private ResetPartitionsMixin options;
+
+    @Inject
+    private ResetPartitionsCall call;
+
+    @Override
+    public Integer call() {
+        return CallExecutionPipeline.builder(call)
+                .inputProvider(() -> ResetPartitionsCallInput.of(options))
+                .output(spec.commandLine().getOut())
+                .errOutput(spec.commandLine().getErr())
+                .verbose(verbose)
+                .build()
+                .runPipeline();
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/reset/ResetPartitionsMixin.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/reset/ResetPartitionsMixin.java
new file mode 100644
index 0000000000..3a58e5c118
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/reset/ResetPartitionsMixin.java
@@ -0,0 +1,65 @@
+/*
+ * 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.cli.commands.recovery.reset;
+
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_PARTITION_IDS_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_PARTITION_IDS_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_TABLE_NAME_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_TABLE_NAME_OPTION_DESC;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_ZONE_NAME_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_ZONE_NAME_OPTION_DESC;
+
+import java.util.List;
+import org.apache.ignite.internal.cli.commands.cluster.ClusterUrlMixin;
+import picocli.CommandLine.Mixin;
+import picocli.CommandLine.Option;
+
+/** Arguments for recovery reset partitions command. */
+public class ResetPartitionsMixin {
+    @Mixin
+    private ClusterUrlMixin clusterUrl;
+
+    @Option(names = RECOVERY_PARTITION_IDS_OPTION, description = 
RECOVERY_PARTITION_IDS_OPTION_DESC, split = ",")
+    private List<Integer> partitionIds;
+
+    @Option(names = RECOVERY_ZONE_NAME_OPTION, description = 
RECOVERY_ZONE_NAME_OPTION_DESC, required = true)
+    private String zoneName;
+
+    @Option(names = RECOVERY_TABLE_NAME_OPTION, description = 
RECOVERY_TABLE_NAME_OPTION_DESC, required = true)
+    private String tableName;
+
+    /** Returns name of the zone to reset partitions of. */
+    public String zoneName() {
+        return zoneName;
+    }
+
+    /** Returns name of the table to reset partitions of. */
+    public String tableName() {
+        return tableName;
+    }
+
+    /** Returns IDs of partitions to reset. */
+    public List<Integer> partitionIds() {
+        return partitionIds;
+    }
+
+    /** Returns cluster endpoint URL. */
+    public String clusterUrl() {
+        return clusterUrl.getClusterUrl();
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/reset/ResetPartitionsReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/reset/ResetPartitionsReplCommand.java
new file mode 100644
index 0000000000..ce8d4fda8c
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/reset/ResetPartitionsReplCommand.java
@@ -0,0 +1,50 @@
+/*
+ * 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.cli.commands.recovery.reset;
+
+import jakarta.inject.Inject;
+import org.apache.ignite.internal.cli.call.recovery.reset.ResetPartitionsCall;
+import 
org.apache.ignite.internal.cli.call.recovery.reset.ResetPartitionsCallInput;
+import org.apache.ignite.internal.cli.commands.BaseCommand;
+import 
org.apache.ignite.internal.cli.commands.questions.ConnectToClusterQuestion;
+import org.apache.ignite.internal.cli.core.flow.builder.Flows;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
+
+/** Command to reset partitions. */
+@Command(name = "reset-partitions", description = "Resets partitions.")
+public class ResetPartitionsReplCommand extends BaseCommand implements 
Runnable {
+    @Mixin
+    private ResetPartitionsMixin options;
+
+    @Inject
+    private ConnectToClusterQuestion question;
+
+    @Inject
+    private ResetPartitionsCall call;
+
+    @Override
+    public void run() {
+        question.askQuestionIfNotConnected(options.clusterUrl())
+                .map(url -> ResetPartitionsCallInput.of(options, url))
+                .then(Flows.fromCall(call))
+                .verbose(verbose)
+                .print()
+                .start();
+    }
+}
diff --git 
a/modules/distribution-zones/src/main/java/org/apache/ignite/internal/distributionzones/exception/DistributionZoneNotFoundException.java
 
b/modules/distribution-zones/src/main/java/org/apache/ignite/internal/distributionzones/exception/DistributionZoneNotFoundException.java
index 113753824a..5e148cf135 100644
--- 
a/modules/distribution-zones/src/main/java/org/apache/ignite/internal/distributionzones/exception/DistributionZoneNotFoundException.java
+++ 
b/modules/distribution-zones/src/main/java/org/apache/ignite/internal/distributionzones/exception/DistributionZoneNotFoundException.java
@@ -35,7 +35,7 @@ public class DistributionZoneNotFoundException extends 
IgniteInternalException {
      * @param zoneId Zone id.
      */
     public DistributionZoneNotFoundException(int zoneId) {
-        super(ZONE_NOT_FOUND_ERR, "Distribution zone is not found [zoneId=" + 
zoneId + ']');
+        super(ZONE_NOT_FOUND_ERR, "Distribution zone was not found [zoneId=" + 
zoneId + ']');
     }
 
     /**
@@ -44,7 +44,7 @@ public class DistributionZoneNotFoundException extends 
IgniteInternalException {
      * @param zoneName Zone name.
      */
     public DistributionZoneNotFoundException(String zoneName) {
-        super(ZONE_NOT_FOUND_ERR, "Distribution zone is not found [zoneName=" 
+ zoneName + ']');
+        super(ZONE_NOT_FOUND_ERR, "Distribution zone was not found [zoneName=" 
+ zoneName + ']');
     }
 
     /**
@@ -54,7 +54,7 @@ public class DistributionZoneNotFoundException extends 
IgniteInternalException {
      * @param cause Optional nested exception (can be {@code null}).
      */
     public DistributionZoneNotFoundException(String zoneName, @Nullable 
Throwable cause) {
-        super(ZONE_NOT_FOUND_ERR, "Distribution zone is not found [zoneName=" 
+ zoneName + ']', cause);
+        super(ZONE_NOT_FOUND_ERR, "Distribution zone was not found [zoneName=" 
+ zoneName + ']', cause);
     }
 
     /**
diff --git 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/recovery/DisasterRecoveryApi.java
 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/recovery/DisasterRecoveryApi.java
index 4cc26d2ac5..7d28f8d05f 100644
--- 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/recovery/DisasterRecoveryApi.java
+++ 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/recovery/DisasterRecoveryApi.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.rest.api.recovery;
 
 import io.micronaut.http.annotation.Body;
+import io.micronaut.http.annotation.Consumes;
 import io.micronaut.http.annotation.Controller;
 import io.micronaut.http.annotation.Get;
 import io.micronaut.http.annotation.Post;
@@ -87,6 +88,7 @@ public interface DisasterRecoveryApi {
             content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = 
@Schema(implementation = Problem.class)))
     @ApiResponse(responseCode = "400", description = "Bad request.",
             content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = 
@Schema(implementation = Problem.class)))
-    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.PROBLEM_JSON)
     CompletableFuture<Void> resetPartitions(@Body ResetPartitionsRequest 
command);
 }
diff --git 
a/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/recovery/ItDisasterRecoveryControllerTest.java
 
b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/recovery/ItDisasterRecoveryControllerTest.java
index fe6cbe693e..967728e8c2 100644
--- 
a/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/recovery/ItDisasterRecoveryControllerTest.java
+++ 
b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/recovery/ItDisasterRecoveryControllerTest.java
@@ -374,7 +374,7 @@ public class ItDisasterRecoveryControllerTest extends 
ClusterPerClassIntegration
 
         assertThat(e.getResponse().code(), is(BAD_REQUEST.code()));
 
-        assertThat(e.getMessage(), containsString("Distribution zone is not 
found [zoneName=" + unknownZone + "]"));
+        assertThat(e.getMessage(), containsString("Distribution zone was not 
found [zoneName=" + unknownZone + "]"));
     }
 
     @Test


Reply via email to