This is an automated email from the ASF dual-hosted git repository.
sanpwc 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 3d7949b6a3d [IGNITE-26459] Add ability to explicitly trigger dataNodes
recalculation via CLI command (#7120)
3d7949b6a3d is described below
commit 3d7949b6a3d22b37f6e79152b7f4f46b10333bac
Author: Egor <[email protected]>
AuthorDate: Wed Dec 3 18:12:05 2025 +0400
[IGNITE-26459] Add ability to explicitly trigger dataNodes recalculation
via CLI command (#7120)
Co-authored-by: Egor Kuts <[email protected]>
---
.../ItRecalculateDataNodesCommandTest.java | 26 +++++++
.../ItRecalculateDataNodesReplCommandTest.java | 26 +++++++
.../zone/datanodes/ItRecalculateDataNodesTest.java | 87 ++++++++++++++++++++++
.../management/zone/RecalculateDataNodesCall.java | 54 ++++++++++++++
.../zone/RecalculateDataNodesCallInput.java | 56 ++++++++++++++
.../ignite/internal/cli/commands/Options.java | 5 ++
.../internal/cli/commands/TopLevelCliCommand.java | 2 +
.../cli/commands/TopLevelCliReplCommand.java | 2 +
.../internal/cli/commands/zone/ZoneCommand.java | 33 ++++++++
.../cli/commands/zone/ZoneReplCommand.java | 33 ++++++++
.../commands/zone/datanodes/DataNodesCommand.java | 32 ++++++++
.../zone/datanodes/DataNodesReplCommand.java | 32 ++++++++
.../datanodes/RecalculateDataNodesCommand.java | 52 +++++++++++++
.../zone/datanodes/RecalculateDataNodesMixin.java | 39 ++++++++++
.../datanodes/RecalculateDataNodesReplCommand.java | 60 +++++++++++++++
15 files changed, 539 insertions(+)
diff --git
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/zone/datanodes/ItRecalculateDataNodesCommandTest.java
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/zone/datanodes/ItRecalculateDataNodesCommandTest.java
new file mode 100644
index 00000000000..80af525af88
--- /dev/null
+++
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/zone/datanodes/ItRecalculateDataNodesCommandTest.java
@@ -0,0 +1,26 @@
+/*
+ * 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.zone.datanodes;
+
+/** Test class for {@link RecalculateDataNodesCommand}. */
+public class ItRecalculateDataNodesCommandTest extends
ItRecalculateDataNodesTest {
+ @Override
+ protected Class<?> getCommandClass() {
+ return RecalculateDataNodesCommand.class;
+ }
+}
diff --git
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/zone/datanodes/ItRecalculateDataNodesReplCommandTest.java
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/zone/datanodes/ItRecalculateDataNodesReplCommandTest.java
new file mode 100644
index 00000000000..8e98c280a45
--- /dev/null
+++
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/zone/datanodes/ItRecalculateDataNodesReplCommandTest.java
@@ -0,0 +1,26 @@
+/*
+ * 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.zone.datanodes;
+
+/** Test class for {@link RecalculateDataNodesReplCommand}. */
+public class ItRecalculateDataNodesReplCommandTest extends
ItRecalculateDataNodesTest {
+ @Override
+ protected Class<?> getCommandClass() {
+ return RecalculateDataNodesReplCommand.class;
+ }
+}
diff --git
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/zone/datanodes/ItRecalculateDataNodesTest.java
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/zone/datanodes/ItRecalculateDataNodesTest.java
new file mode 100644
index 00000000000..33b712cd07f
--- /dev/null
+++
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/zone/datanodes/ItRecalculateDataNodesTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.zone.datanodes;
+
+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 org.apache.ignite.internal.cli.CliIntegrationTest;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/** Base test class for recalculate data nodes commands. */
+public abstract class ItRecalculateDataNodesTest extends CliIntegrationTest {
+ private static final String ZONE1 = "test_zone_1";
+ private static final String ZONE2 = "test_zone_2";
+
+ @BeforeAll
+ public void createZones() {
+ sql(String.format("CREATE ZONE \"%s\" storage profiles['%s']", ZONE1,
DEFAULT_AIPERSIST_PROFILE_NAME));
+ sql(String.format("CREATE ZONE \"%s\" storage profiles['%s']", ZONE2,
DEFAULT_AIPERSIST_PROFILE_NAME));
+ }
+
+ @Test
+ public void testRecalculateAllZones() {
+ execute(CLUSTER_URL_OPTION, NODE_URL);
+
+ assertErrOutputIsEmpty();
+ assertOutputContains("Done");
+ }
+
+ @Test
+ public void testRecalculateSpecifiedZones() {
+ execute(CLUSTER_URL_OPTION, NODE_URL,
+ "--zone-names", ZONE1 + "," + ZONE2);
+
+ assertErrOutputIsEmpty();
+ assertOutputContains("Done");
+ }
+
+ @Test
+ public void testRecalculateSingleZone() {
+ execute(CLUSTER_URL_OPTION, NODE_URL,
+ "--zone-names", ZONE1);
+
+ assertErrOutputIsEmpty();
+ assertOutputContains("Done");
+ }
+
+ @Test
+ public void testRecalculateNonExistentZone() {
+ String unknownZone = "unknown_zone";
+
+ execute(CLUSTER_URL_OPTION, NODE_URL,
+ "--zone-names", unknownZone);
+
+ assertErrOutputContains("Distribution zones were not found");
+ assertErrOutputContains(unknownZone);
+ assertOutputIsEmpty();
+ }
+
+ @Test
+ public void testRecalculateMixedZones() {
+ String unknownZone = "unknown_zone";
+
+ execute(CLUSTER_URL_OPTION, NODE_URL,
+ "--zone-names", ZONE1 + "," + unknownZone);
+
+ assertErrOutputContains("Distribution zones were not found");
+ assertErrOutputContains(unknownZone);
+ assertOutputIsEmpty();
+ }
+}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/management/zone/RecalculateDataNodesCall.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/management/zone/RecalculateDataNodesCall.java
new file mode 100644
index 00000000000..05633abe0d8
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/management/zone/RecalculateDataNodesCall.java
@@ -0,0 +1,54 @@
+/*
+ * 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.management.zone;
+
+import jakarta.inject.Singleton;
+import java.util.Collections;
+import org.apache.ignite.internal.cli.core.call.Call;
+import org.apache.ignite.internal.cli.core.call.CallOutput;
+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.internal.cli.core.style.component.MessageUiComponent;
+import org.apache.ignite.internal.cli.core.style.element.UiElements;
+import org.apache.ignite.rest.client.api.DataNodesApi;
+import org.apache.ignite.rest.client.invoker.ApiException;
+
+/** Call to recalculate data nodes for distribution zones. */
+@Singleton
+public class RecalculateDataNodesCall implements
Call<RecalculateDataNodesCallInput, String> {
+
+ private final ApiClientFactory clientFactory;
+
+ RecalculateDataNodesCall(ApiClientFactory clientFactory) {
+ this.clientFactory = clientFactory;
+ }
+
+ @Override
+ public CallOutput<String> execute(RecalculateDataNodesCallInput input) {
+ try {
+ DataNodesApi api = new
DataNodesApi(clientFactory.getClient(input.clusterUrl()));
+ // If no zone names specified, pass an empty list (which means
recalculate all zones).
+ api.resetDataNodesForZones(input.zoneNames() == null ?
Collections.emptyList() : input.zoneNames());
+
+ return
DefaultCallOutput.success(MessageUiComponent.from(UiElements.done()).render());
+ } 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/management/zone/RecalculateDataNodesCallInput.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/management/zone/RecalculateDataNodesCallInput.java
new file mode 100644
index 00000000000..d067ad68861
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/management/zone/RecalculateDataNodesCallInput.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.management.zone;
+
+import java.util.List;
+import
org.apache.ignite.internal.cli.commands.zone.datanodes.RecalculateDataNodesMixin;
+import org.apache.ignite.internal.cli.core.call.CallInput;
+
+/**
+ * Input for {@link RecalculateDataNodesCall}.
+ */
+public class RecalculateDataNodesCallInput implements CallInput {
+ private final List<String> zoneNames;
+ private final String clusterUrl;
+
+ private RecalculateDataNodesCallInput(List<String> zoneNames, String
clusterUrl) {
+ this.zoneNames = zoneNames;
+ this.clusterUrl = clusterUrl;
+ }
+
+ /**
+ * Creates input from mixin and cluster URL.
+ *
+ * @param mixin Mixin with command options.
+ * @param clusterUrl Cluster URL.
+ * @return Call input.
+ */
+ public static RecalculateDataNodesCallInput of(RecalculateDataNodesMixin
mixin, String clusterUrl) {
+ return new RecalculateDataNodesCallInput(mixin.zoneNames(),
clusterUrl);
+ }
+
+ /** Returns zone names to recalculate data nodes for. */
+ public List<String> zoneNames() {
+ return zoneNames;
+ }
+
+ /** Returns cluster URL. */
+ public String clusterUrl() {
+ return clusterUrl;
+ }
+}
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 a64ebf215fc..f93e8e3b58d 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
@@ -335,5 +335,10 @@ public enum Options {
/** Config update file option description. */
public static final String CONFIG_UPDATE_FILE_OPTION_DESC = "Path to
file with config update commands to execute";
+
+ public static final String RECALCULATE_ZONE_NAMES_OPTION =
"--zone-names";
+
+ public static final String RECALCULATE_ZONE_NAMES_OPTION_DESC =
"Comma-separated list of zone names to recalculate data nodes for. "
+ + "If not specified, recalculates for all zones.";
}
}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/TopLevelCliCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/TopLevelCliCommand.java
index 764eae83cfe..f53b51ba944 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/TopLevelCliCommand.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/TopLevelCliCommand.java
@@ -28,6 +28,7 @@ import
org.apache.ignite.internal.cli.commands.connect.ConnectCommand;
import org.apache.ignite.internal.cli.commands.node.NodeCommand;
import org.apache.ignite.internal.cli.commands.recovery.RecoveryCommand;
import org.apache.ignite.internal.cli.commands.sql.SqlCommand;
+import org.apache.ignite.internal.cli.commands.zone.ZoneCommand;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
@@ -48,6 +49,7 @@ import picocli.CommandLine.Option;
ConnectCommand.class,
NodeCommand.class,
ClusterCommand.class,
+ ZoneCommand.class,
RecoveryCommand.class
})
public class TopLevelCliCommand extends BaseCommand {
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/TopLevelCliReplCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/TopLevelCliReplCommand.java
index 0f52531fb08..ce1692c21eb 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/TopLevelCliReplCommand.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/TopLevelCliReplCommand.java
@@ -36,6 +36,7 @@ import
org.apache.ignite.internal.cli.commands.node.NodeReplCommand;
import org.apache.ignite.internal.cli.commands.recovery.RecoveryReplCommand;
import org.apache.ignite.internal.cli.commands.sql.SqlReplCommand;
import org.apache.ignite.internal.cli.commands.version.VersionCommand;
+import org.apache.ignite.internal.cli.commands.zone.ZoneReplCommand;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.shell.jline3.PicocliCommands;
@@ -56,6 +57,7 @@ import picocli.shell.jline3.PicocliCommands;
DisconnectCommand.class,
NodeReplCommand.class,
ClusterReplCommand.class,
+ ZoneReplCommand.class,
RecoveryReplCommand.class
},
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/ZoneCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/ZoneCommand.java
new file mode 100644
index 00000000000..ddc84d39a2c
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/ZoneCommand.java
@@ -0,0 +1,33 @@
+/*
+ * 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.zone;
+
+import org.apache.ignite.internal.cli.commands.BaseCommand;
+import org.apache.ignite.internal.cli.commands.zone.datanodes.DataNodesCommand;
+import picocli.CommandLine.Command;
+
+/**
+ * Zone management command.
+ */
+@Command(name = "zone",
+ subcommands = {
+ DataNodesCommand.class
+ },
+ description = "Manages distribution zones")
+public class ZoneCommand extends BaseCommand {
+}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/ZoneReplCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/ZoneReplCommand.java
new file mode 100644
index 00000000000..a66817b9270
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/ZoneReplCommand.java
@@ -0,0 +1,33 @@
+/*
+ * 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.zone;
+
+import org.apache.ignite.internal.cli.commands.BaseCommand;
+import
org.apache.ignite.internal.cli.commands.zone.datanodes.DataNodesReplCommand;
+import picocli.CommandLine.Command;
+
+/**
+ * Zone management REPL command.
+ */
+@Command(name = "zone",
+ subcommands = {
+ DataNodesReplCommand.class
+ },
+ description = "Manages distribution zones")
+public class ZoneReplCommand extends BaseCommand {
+}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/datanodes/DataNodesCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/datanodes/DataNodesCommand.java
new file mode 100644
index 00000000000..8e1da37b105
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/datanodes/DataNodesCommand.java
@@ -0,0 +1,32 @@
+/*
+ * 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.zone.datanodes;
+
+import org.apache.ignite.internal.cli.commands.BaseCommand;
+import picocli.CommandLine.Command;
+
+/**
+ * Data nodes management command.
+ */
+@Command(name = "datanodes",
+ subcommands = {
+ RecalculateDataNodesCommand.class
+ },
+ description = "Manages data nodes for distribution zones")
+public class DataNodesCommand extends BaseCommand {
+}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/datanodes/DataNodesReplCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/datanodes/DataNodesReplCommand.java
new file mode 100644
index 00000000000..64de0618c7e
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/datanodes/DataNodesReplCommand.java
@@ -0,0 +1,32 @@
+/*
+ * 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.zone.datanodes;
+
+import org.apache.ignite.internal.cli.commands.BaseCommand;
+import picocli.CommandLine.Command;
+
+/**
+ * Data nodes management REPL command.
+ */
+@Command(name = "datanodes",
+ subcommands = {
+ RecalculateDataNodesReplCommand.class
+ },
+ description = "Manages data nodes for distribution zones")
+public class DataNodesReplCommand extends BaseCommand {
+}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/datanodes/RecalculateDataNodesCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/datanodes/RecalculateDataNodesCommand.java
new file mode 100644
index 00000000000..369d2b45ff5
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/datanodes/RecalculateDataNodesCommand.java
@@ -0,0 +1,52 @@
+/*
+ * 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.zone.datanodes;
+
+import jakarta.inject.Inject;
+import java.util.concurrent.Callable;
+import
org.apache.ignite.internal.cli.call.management.zone.RecalculateDataNodesCall;
+import
org.apache.ignite.internal.cli.call.management.zone.RecalculateDataNodesCallInput;
+import org.apache.ignite.internal.cli.commands.BaseCommand;
+import org.apache.ignite.internal.cli.commands.cluster.ClusterUrlProfileMixin;
+import org.apache.ignite.internal.cli.core.call.CallExecutionPipeline;
+import
org.apache.ignite.internal.cli.core.exception.handler.ClusterNotInitializedExceptionHandler;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
+
+/** Command to recalculate data nodes for distribution zones. */
+@Command(name = "recalculate", description = "Recalculates data nodes for
distribution zones")
+public class RecalculateDataNodesCommand extends BaseCommand implements
Callable<Integer> {
+
+ /** Cluster endpoint URL option. */
+ @Mixin
+ private ClusterUrlProfileMixin clusterUrl;
+
+ @Mixin
+ private RecalculateDataNodesMixin options;
+
+ @Inject
+ private RecalculateDataNodesCall call;
+
+ @Override
+ public Integer call() {
+ return runPipeline(CallExecutionPipeline.builder(call)
+ .inputProvider(() -> RecalculateDataNodesCallInput.of(options,
clusterUrl.getClusterUrl()))
+
.exceptionHandler(ClusterNotInitializedExceptionHandler.createHandler("Cannot
recalculate data nodes"))
+ );
+ }
+}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/datanodes/RecalculateDataNodesMixin.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/datanodes/RecalculateDataNodesMixin.java
new file mode 100644
index 00000000000..dd4e4493d7b
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/datanodes/RecalculateDataNodesMixin.java
@@ -0,0 +1,39 @@
+/*
+ * 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.zone.datanodes;
+
+import static
org.apache.ignite.internal.cli.commands.Options.Constants.RECALCULATE_ZONE_NAMES_OPTION;
+import static
org.apache.ignite.internal.cli.commands.Options.Constants.RECALCULATE_ZONE_NAMES_OPTION_DESC;
+
+import java.util.List;
+import picocli.CommandLine.Option;
+
+/** Arguments for recalculate data nodes command. */
+public class RecalculateDataNodesMixin {
+ @Option(
+ names = RECALCULATE_ZONE_NAMES_OPTION,
+ description = RECALCULATE_ZONE_NAMES_OPTION_DESC,
+ split = ","
+ )
+ private List<String> zoneNames;
+
+ /** Returns names of zones to recalculate data nodes for. */
+ public List<String> zoneNames() {
+ return zoneNames;
+ }
+}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/datanodes/RecalculateDataNodesReplCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/datanodes/RecalculateDataNodesReplCommand.java
new file mode 100644
index 00000000000..44bb692d2ba
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/zone/datanodes/RecalculateDataNodesReplCommand.java
@@ -0,0 +1,60 @@
+/*
+ * 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.zone.datanodes;
+
+import jakarta.inject.Inject;
+import
org.apache.ignite.internal.cli.call.management.zone.RecalculateDataNodesCall;
+import
org.apache.ignite.internal.cli.call.management.zone.RecalculateDataNodesCallInput;
+import org.apache.ignite.internal.cli.commands.BaseCommand;
+import org.apache.ignite.internal.cli.commands.cluster.ClusterUrlMixin;
+import
org.apache.ignite.internal.cli.commands.questions.ConnectToClusterQuestion;
+import
org.apache.ignite.internal.cli.core.exception.handler.ClusterNotInitializedExceptionHandler;
+import org.apache.ignite.internal.cli.core.flow.builder.Flows;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Mixin;
+
+/** REPL command to recalculate data nodes for distribution zones. */
+@Command(name = "recalculate", description = "Recalculates data nodes for
distribution zones")
+public class RecalculateDataNodesReplCommand extends BaseCommand implements
Runnable {
+
+ @Mixin
+ private ClusterUrlMixin clusterUrl;
+
+ @Mixin
+ private RecalculateDataNodesMixin options;
+
+ @Inject
+ private RecalculateDataNodesCall call;
+
+ @Inject
+ private ConnectToClusterQuestion question;
+
+ @Override
+ public void run() {
+ runFlow(question.askQuestionIfNotConnected(clusterUrl.getClusterUrl())
+ .map(this::recalculateDataNodesCallInput)
+ .then(Flows.fromCall(call))
+
.exceptionHandler(ClusterNotInitializedExceptionHandler.createReplHandler("Cannot
recalculate data nodes"))
+ .print()
+ );
+ }
+
+ private RecalculateDataNodesCallInput recalculateDataNodesCallInput(String
clusterUrl) {
+ return RecalculateDataNodesCallInput.of(options, clusterUrl);
+ }
+}