This is an automated email from the ASF dual-hosted git repository.
rpuch 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 ac8696610d IGNITE-22879 Add CLI for migrating running nodes to new CMG
(#4458)
ac8696610d is described below
commit ac8696610d3cb423dff06573b6c3d01c9b0c9306
Author: Roman Puchkovskiy <[email protected]>
AuthorDate: Fri Sep 27 12:53:00 2024 +0400
IGNITE-22879 Add CLI for migrating running nodes to new CMG (#4458)
---
.../cluster/ItMigrateToClusterCommandTest.java | 94 ++++++++++++++++++++++
.../recovery/cluster/ItResetClusterTest.java | 29 +------
...t.java => ItSystemDisasterRecoveryCliTest.java} | 47 +----------
.../recovery/cluster/MigrateToClusterCall.java | 75 +++++++++++++++++
.../cluster/MigrateToClusterCallInput.java | 86 ++++++++++++++++++++
.../ignite/internal/cli/commands/Options.java | 14 ++++
.../recovery/cluster/RecoveryClusterCommand.java | 4 +-
.../MigrateToClusterCommand.java} | 25 +++---
.../cluster/migrate/MigrateToClusterMixin.java | 56 +++++++++++++
.../cluster/reset/ResetClusterCommand.java | 2 -
.../recovery/cluster/reset/ResetClusterMixin.java | 4 +-
.../cluster/reset/ResetClusterReplCommand.java | 2 -
.../internal/rest/api/cluster/ClusterState.java | 32 +++++---
.../apache/ignite/internal/rest/RestManager.java | 33 ++++++--
.../rest/cluster/ClusterManagementController.java | 3 +-
.../system/SystemDisasterRecoveryController.java | 2 +
.../ignite/internal/rest/RestManagerTest.java | 21 ++++-
.../disaster/system/ItCmgDisasterRecoveryTest.java | 2 +-
.../system/ItSystemGroupDisasterRecoveryTest.java | 16 ++--
.../system/SystemDisasterRecoveryClient.java | 36 ++++++---
20 files changed, 452 insertions(+), 131 deletions(-)
diff --git
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/cluster/ItMigrateToClusterCommandTest.java
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/cluster/ItMigrateToClusterCommandTest.java
new file mode 100644
index 0000000000..ebf25d8e28
--- /dev/null
+++
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/cluster/ItMigrateToClusterCommandTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.cluster;
+
+import static org.apache.ignite.internal.TestWrappers.unwrapIgniteImpl;
+import static
org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_URL_OPTION;
+import static
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_CMG_NODES_OPTION;
+import static
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_NEW_CLUSTER_URL_OPTION;
+import static
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_OLD_CLUSTER_URL_OPTION;
+
+import org.apache.ignite.InitParametersBuilder;
+import
org.apache.ignite.internal.cli.commands.recovery.cluster.migrate.MigrateToClusterCommand;
+import org.apache.ignite.network.NodeMetadata;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+
+/** Test class for {@link MigrateToClusterCommand}. */
+class ItMigrateToClusterCommandTest extends ItSystemDisasterRecoveryCliTest {
+ @Override
+ protected int initialNodes() {
+ return 2;
+ }
+
+ @Override
+ protected void configureInitParameters(InitParametersBuilder builder) {
+ builder.cmgNodeNames(CLUSTER.nodeName(0));
+ builder.metaStorageNodeNames(CLUSTER.nodeName(1));
+ }
+
+ @Test
+ @Order(Integer.MAX_VALUE) // This test is run last as it restarts the node.
+ void initiatesNodesMigration() throws Exception {
+ String restUrl0 = restUrl(0);
+ String restUrl1 = restUrl(1);
+
+ // Stop the CMG majority.
+ CLUSTER.stopNode(0);
+
+ // First just initiate repair (we need it to have ability to trigger a
migration).
+ repairClusterOnNode1(restUrl1);
+
+ CLUSTER.startEmbeddedNode(0);
+
+ // Now actually do migration.
+ execute(
+ "recovery", "cluster", "migrate",
+ RECOVERY_OLD_CLUSTER_URL_OPTION, restUrl0,
+ RECOVERY_NEW_CLUSTER_URL_OPTION, restUrl1
+ );
+
+ try {
+ assertErrOutputIsEmpty();
+ assertOutputContains("Node has gone, this most probably means that
migration is initiated and the node restarts.");
+ } finally {
+ waitTillNodeRestartsInternally(0);
+ }
+ }
+
+ private void repairClusterOnNode1(String restUrl1) throws
InterruptedException {
+ execute(
+ "recovery", "cluster", "reset",
+ CLUSTER_URL_OPTION, restUrl1,
+ RECOVERY_CMG_NODES_OPTION, CLUSTER.nodeName(1)
+ );
+
+ try {
+ assertErrOutputIsEmpty();
+ assertOutputContains("Node has gone, this most probably means that
cluster repair is initiated and the node restarts.");
+ } finally {
+ waitTillNodeRestartsInternally(1);
+ }
+ }
+
+ private static String restUrl(int nodeIndex) {
+ NodeMetadata nodeMetadata =
unwrapIgniteImpl(CLUSTER.node(nodeIndex)).node().nodeMetadata();
+
+ return "http://" + nodeMetadata.restHost() + ":" +
nodeMetadata.httpPort();
+ }
+}
diff --git
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/cluster/ItResetClusterTest.java
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/cluster/ItResetClusterTest.java
index e5b2a023b4..2d24c194ac 100644
---
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/cluster/ItResetClusterTest.java
+++
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/cluster/ItResetClusterTest.java
@@ -17,19 +17,9 @@
package org.apache.ignite.internal.cli.commands.recovery.cluster;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.apache.ignite.internal.TestWrappers.unwrapIgniteImpl;
import static
org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_URL_OPTION;
import static
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_CMG_NODES_OPTION;
-import static
org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCondition;
-import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import java.util.concurrent.CompletableFuture;
-import org.apache.ignite.internal.app.IgniteServerImpl;
-import org.apache.ignite.internal.cli.CliIntegrationTest;
-import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
@@ -37,7 +27,7 @@ import org.junit.jupiter.api.TestMethodOrder;
/** Base test class for reset cluster commands. */
@TestMethodOrder(OrderAnnotation.class)
-abstract class ItResetClusterTest extends CliIntegrationTest {
+abstract class ItResetClusterTest extends ItSystemDisasterRecoveryCliTest {
@Override
protected int initialNodes() {
return 1;
@@ -59,23 +49,6 @@ abstract class ItResetClusterTest extends CliIntegrationTest
{
}
}
- private static void waitTillNodeRestartsInternally(int nodeIndex) throws
InterruptedException {
- // restartOrShutdownFuture() becomes non-null when restart or shutdown
is initiated; we know it's restart.
-
- assertTrue(
- waitForCondition(() -> restartOrShutdownFuture(nodeIndex) !=
null, SECONDS.toMillis(20)),
- "Node did not attempt to be restarted (or shut down) in time"
- );
- assertThat(restartOrShutdownFuture(nodeIndex),
willCompleteSuccessfully());
-
- unwrapIgniteImpl(CLUSTER.server(nodeIndex).api());
- }
-
- @Nullable
- private static CompletableFuture<Void> restartOrShutdownFuture(int
nodeIndex) {
- return ((IgniteServerImpl)
CLUSTER.server(nodeIndex)).restartOrShutdownFuture();
- }
-
@Test
void handlesErrors() {
execute(
diff --git
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/cluster/ItResetClusterTest.java
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/cluster/ItSystemDisasterRecoveryCliTest.java
similarity index 54%
copy from
modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/cluster/ItResetClusterTest.java
copy to
modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/cluster/ItSystemDisasterRecoveryCliTest.java
index e5b2a023b4..1250042d18 100644
---
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/cluster/ItResetClusterTest.java
+++
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/recovery/cluster/ItSystemDisasterRecoveryCliTest.java
@@ -18,9 +18,6 @@
package org.apache.ignite.internal.cli.commands.recovery.cluster;
import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.apache.ignite.internal.TestWrappers.unwrapIgniteImpl;
-import static
org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_URL_OPTION;
-import static
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_CMG_NODES_OPTION;
import static
org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCondition;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -30,36 +27,9 @@ import java.util.concurrent.CompletableFuture;
import org.apache.ignite.internal.app.IgniteServerImpl;
import org.apache.ignite.internal.cli.CliIntegrationTest;
import org.jetbrains.annotations.Nullable;
-import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
-import org.junit.jupiter.api.Order;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestMethodOrder;
-/** Base test class for reset cluster commands. */
-@TestMethodOrder(OrderAnnotation.class)
-abstract class ItResetClusterTest extends CliIntegrationTest {
- @Override
- protected int initialNodes() {
- return 1;
- }
-
- @Test
- @Order(Integer.MAX_VALUE) // This test is run last as it restarts the node.
- void initiatesCmgRepair() throws Exception {
- execute(
- CLUSTER_URL_OPTION, NODE_URL,
- RECOVERY_CMG_NODES_OPTION, CLUSTER.aliveNode().name()
- );
-
- try {
- assertErrOutputIsEmpty();
- assertOutputContains("Node has gone, this most probably means that
cluster repair is initiated and the node restarts.");
- } finally {
- waitTillNodeRestartsInternally(0);
- }
- }
-
- private static void waitTillNodeRestartsInternally(int nodeIndex) throws
InterruptedException {
+abstract class ItSystemDisasterRecoveryCliTest extends CliIntegrationTest {
+ static void waitTillNodeRestartsInternally(int nodeIndex) throws
InterruptedException {
// restartOrShutdownFuture() becomes non-null when restart or shutdown
is initiated; we know it's restart.
assertTrue(
@@ -67,23 +37,10 @@ abstract class ItResetClusterTest extends
CliIntegrationTest {
"Node did not attempt to be restarted (or shut down) in time"
);
assertThat(restartOrShutdownFuture(nodeIndex),
willCompleteSuccessfully());
-
- unwrapIgniteImpl(CLUSTER.server(nodeIndex).api());
}
@Nullable
private static CompletableFuture<Void> restartOrShutdownFuture(int
nodeIndex) {
return ((IgniteServerImpl)
CLUSTER.server(nodeIndex)).restartOrShutdownFuture();
}
-
- @Test
- void handlesErrors() {
- execute(
- CLUSTER_URL_OPTION, NODE_URL,
- RECOVERY_CMG_NODES_OPTION, "no-such-node"
- );
-
- assertErrOutputContains("Current node is not contained in the new CMG,
so it cannot conduct a cluster reset.");
- assertOutputIsEmpty();
- }
}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/recovery/cluster/MigrateToClusterCall.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/recovery/cluster/MigrateToClusterCall.java
new file mode 100644
index 0000000000..1f2b56a489
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/recovery/cluster/MigrateToClusterCall.java
@@ -0,0 +1,75 @@
+/*
+ * 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.cluster;
+
+import jakarta.inject.Singleton;
+import java.io.IOException;
+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.ClusterManagementApi;
+import org.apache.ignite.rest.client.api.RecoveryApi;
+import org.apache.ignite.rest.client.invoker.ApiException;
+import org.apache.ignite.rest.client.model.ClusterState;
+import org.apache.ignite.rest.client.model.MigrateRequest;
+
+/** Call to migrate nodes from old to new cluster. */
+@Singleton
+public class MigrateToClusterCall implements Call<MigrateToClusterCallInput,
String> {
+ private final ApiClientFactory clientFactory;
+
+ public MigrateToClusterCall(ApiClientFactory clientFactory) {
+ this.clientFactory = clientFactory;
+ }
+
+ @Override
+ public DefaultCallOutput<String> execute(MigrateToClusterCallInput input) {
+ ClusterManagementApi newClusterManagementClient = new
ClusterManagementApi(clientFactory.getClient(input.newClusterUrl()));
+ RecoveryApi oldRecoveryClient = new
RecoveryApi(clientFactory.getClient(input.oldClusterUrl()));
+
+ ClusterState newClusterState;
+ try {
+ newClusterState = newClusterManagementClient.clusterState();
+ } catch (ApiException e) {
+ return DefaultCallOutput.failure(new IgniteCliApiException(e,
input.newClusterUrl()));
+ }
+
+ MigrateRequest command = new MigrateRequest();
+
+ command.setCmgNodes(newClusterState.getCmgNodes());
+ command.setMetaStorageNodes(newClusterState.getMsNodes());
+ command.setVersion(newClusterState.getIgniteVersion());
+ command.setClusterId(newClusterState.getClusterTag().getClusterId());
+
command.setClusterName(newClusterState.getClusterTag().getClusterName());
+ command.setFormerClusterIds(newClusterState.getFormerClusterIds());
+
+ try {
+ oldRecoveryClient.migrate(command);
+ } catch (ApiException e) {
+ if (e.getCause() instanceof IOException) {
+ return DefaultCallOutput.success("Node has gone, this most
probably means that migration is initiated and "
+ + "the node restarts.");
+ }
+
+ return DefaultCallOutput.failure(new IgniteCliApiException(e,
input.oldClusterUrl()));
+ }
+
+ return DefaultCallOutput.success("Successfully initiated migration.");
+ }
+}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/recovery/cluster/MigrateToClusterCallInput.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/recovery/cluster/MigrateToClusterCallInput.java
new file mode 100644
index 0000000000..c8e70f2480
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/recovery/cluster/MigrateToClusterCallInput.java
@@ -0,0 +1,86 @@
+/*
+ * 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.cluster;
+
+import java.util.Objects;
+import
org.apache.ignite.internal.cli.commands.recovery.cluster.migrate.MigrateToClusterMixin;
+import org.apache.ignite.internal.cli.core.call.CallInput;
+
+/** Input for the {@link MigrateToClusterCall} call. */
+public class MigrateToClusterCallInput implements CallInput {
+ private final String oldClusterUrl;
+ private final String newClusterUrl;
+
+ /** Old cluster url. */
+ public String oldClusterUrl() {
+ return oldClusterUrl;
+ }
+
+ /** New cluster url. */
+ public String newClusterUrl() {
+ return newClusterUrl;
+ }
+
+ private MigrateToClusterCallInput(String oldClusterUrl, String
newClusterUrl) {
+ Objects.requireNonNull(oldClusterUrl);
+ Objects.requireNonNull(newClusterUrl);
+
+ this.oldClusterUrl = oldClusterUrl;
+ this.newClusterUrl = newClusterUrl;
+ }
+
+ /** Returns {@link MigrateToClusterCallInput} with specified arguments. */
+ public static MigrateToClusterCallInput of(MigrateToClusterMixin
statesArgs) {
+ return builder()
+ .oldClusterUrl(statesArgs.getOldClusterUrl())
+ .newClusterUrl(statesArgs.getNewClusterUrl())
+ .build();
+ }
+
+ /**
+ * Builder method provider.
+ *
+ * @return new instance of {@link MigrateToClusterCallInput}.
+ */
+ private static MigrateToClusterCallInputBuilder builder() {
+ return new MigrateToClusterCallInputBuilder();
+ }
+
+ /** Builder for {@link MigrateToClusterCallInput}. */
+ private static class MigrateToClusterCallInputBuilder {
+ private String oldClusterUrl;
+ private String newClusterUrl;
+
+ /** Sets old cluster URL. */
+ MigrateToClusterCallInputBuilder oldClusterUrl(String oldClusterUrl) {
+ this.oldClusterUrl = oldClusterUrl;
+ return this;
+ }
+
+ /** Sets new cluster URL. */
+ MigrateToClusterCallInputBuilder newClusterUrl(String newClusterUrl) {
+ this.newClusterUrl = newClusterUrl;
+ return this;
+ }
+
+ /** Builds {@link MigrateToClusterCallInput}. */
+ MigrateToClusterCallInput build() {
+ return new MigrateToClusterCallInput(oldClusterUrl, newClusterUrl);
+ }
+ }
+}
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 58372d6072..68b27cf92f 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,6 +298,20 @@ public enum Options {
+ "'--cluster-management-group node1, node2' "
+ "to specify more than one node) that will host the Cluster
Management Group.";
+ /** Old cluster endpoint URL option long name. */
+ public static final String RECOVERY_OLD_CLUSTER_URL_OPTION =
"--old-cluster-url";
+
+ /** Old cluster endpoint URL option description. */
+ public static final String RECOVERY_OLD_CLUSTER_URL_OPTION_DESC = "URL
of old cluster endpoint (nodes of this cluster will be "
+ + "migrated to a new cluster). It can be URL of any node of
the old cluster.";
+
+ /** New cluster endpoint URL option long name. */
+ public static final String RECOVERY_NEW_CLUSTER_URL_OPTION =
"--new-cluster-url";
+
+ /** New cluster endpoint URL option description. */
+ public static final String RECOVERY_NEW_CLUSTER_URL_OPTION_DESC = "URL
of new cluster endpoint (nodes of old cluster will be "
+ + "migrated to this cluster). It can be URL of any node of the
new cluster.";
+
public static final String CONFIG_FORMAT_OPTION = "--format";
public static final String CONFIG_FORMAT_OPTION_DESC = "Output format.
Valid values: ${COMPLETION-CANDIDATES}";
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/RecoveryClusterCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/RecoveryClusterCommand.java
index 8b5f9ca368..aac33eccce 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/RecoveryClusterCommand.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/RecoveryClusterCommand.java
@@ -18,13 +18,15 @@
package org.apache.ignite.internal.cli.commands.recovery.cluster;
import org.apache.ignite.internal.cli.commands.BaseCommand;
+import
org.apache.ignite.internal.cli.commands.recovery.cluster.migrate.MigrateToClusterCommand;
import
org.apache.ignite.internal.cli.commands.recovery.cluster.reset.ResetClusterCommand;
import picocli.CommandLine.Command;
/** Cluster disaster recovery commands. */
@Command(name = "cluster",
subcommands = {
- ResetClusterCommand.class
+ ResetClusterCommand.class,
+ MigrateToClusterCommand.class
},
description = "Manages disaster recovery of CMG/Metastorage group")
public class RecoveryClusterCommand extends BaseCommand {
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/reset/ResetClusterCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/migrate/MigrateToClusterCommand.java
similarity index 57%
copy from
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/reset/ResetClusterCommand.java
copy to
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/migrate/MigrateToClusterCommand.java
index 199dd1ba5d..868f59b763 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/reset/ResetClusterCommand.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/migrate/MigrateToClusterCommand.java
@@ -15,37 +15,30 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.cli.commands.recovery.cluster.reset;
+package org.apache.ignite.internal.cli.commands.recovery.cluster.migrate;
import jakarta.inject.Inject;
import java.util.concurrent.Callable;
-import org.apache.ignite.internal.cli.call.recovery.cluster.ResetClusterCall;
-import
org.apache.ignite.internal.cli.call.recovery.cluster.ResetClusterCallInput;
+import
org.apache.ignite.internal.cli.call.recovery.cluster.MigrateToClusterCall;
+import
org.apache.ignite.internal.cli.call.recovery.cluster.MigrateToClusterCallInput;
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 reset cluster (that is, initiate CMG/Metastorage group repair).
*/
-@Command(name = "reset", description = "Resets cluster.")
-public class ResetClusterCommand extends BaseCommand implements
Callable<Integer> {
- /** Cluster endpoint URL option. */
+/** Command to initiate migration of nodes from old (unrepaired) cluster to
the new (repaired) one. */
+@Command(name = "migrate", description = "Migrates nodes missed during repair
to repaired cluster.")
+public class MigrateToClusterCommand extends BaseCommand implements
Callable<Integer> {
@Mixin
- private ClusterUrlProfileMixin clusterUrl;
-
- @Mixin
- private ResetClusterMixin options;
+ private MigrateToClusterMixin options;
@Inject
- private ResetClusterCall call;
+ private MigrateToClusterCall call;
@Override
public Integer call() {
return runPipeline(CallExecutionPipeline.builder(call)
- .inputProvider(() -> ResetClusterCallInput.of(options,
clusterUrl.getClusterUrl()))
-
.exceptionHandler(ClusterNotInitializedExceptionHandler.createHandler("Cannot
reset cluster"))
+ .inputProvider(() -> MigrateToClusterCallInput.of(options))
);
}
}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/migrate/MigrateToClusterMixin.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/migrate/MigrateToClusterMixin.java
new file mode 100644
index 0000000000..30de5bd17d
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/migrate/MigrateToClusterMixin.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.commands.recovery.cluster.migrate;
+
+import static
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_NEW_CLUSTER_URL_OPTION;
+import static
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_NEW_CLUSTER_URL_OPTION_DESC;
+import static
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_OLD_CLUSTER_URL_OPTION;
+import static
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY_OLD_CLUSTER_URL_OPTION_DESC;
+
+import java.net.URL;
+import org.apache.ignite.internal.cli.core.converters.UrlConverter;
+import picocli.CommandLine.Option;
+
+/** Arguments for 'recovery cluster migrate' command. */
+public class MigrateToClusterMixin {
+ /** Cluster endpoint URL option for the old cluster (nodes of this cluster
will be migrated to a new cluster). */
+ @Option(
+ names = RECOVERY_OLD_CLUSTER_URL_OPTION,
+ description = RECOVERY_OLD_CLUSTER_URL_OPTION_DESC,
+ converter = UrlConverter.class,
+ required = true
+ )
+ private URL oldClusterUrl;
+
+ /** Cluster endpoint URL option for the new cluster (nodes of old cluster
will be migrated to this cluster). */
+ @Option(
+ names = RECOVERY_NEW_CLUSTER_URL_OPTION,
+ description = RECOVERY_NEW_CLUSTER_URL_OPTION_DESC,
+ converter = UrlConverter.class,
+ required = true
+ )
+ private URL newClusterUrl;
+
+ public String getOldClusterUrl() {
+ return oldClusterUrl.toString();
+ }
+
+ public String getNewClusterUrl() {
+ return newClusterUrl.toString();
+ }
+}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/reset/ResetClusterCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/reset/ResetClusterCommand.java
index 199dd1ba5d..df1f079def 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/reset/ResetClusterCommand.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/reset/ResetClusterCommand.java
@@ -24,7 +24,6 @@ import
org.apache.ignite.internal.cli.call.recovery.cluster.ResetClusterCallInpu
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;
@@ -45,7 +44,6 @@ public class ResetClusterCommand extends BaseCommand
implements Callable<Integer
public Integer call() {
return runPipeline(CallExecutionPipeline.builder(call)
.inputProvider(() -> ResetClusterCallInput.of(options,
clusterUrl.getClusterUrl()))
-
.exceptionHandler(ClusterNotInitializedExceptionHandler.createHandler("Cannot
reset cluster"))
);
}
}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/reset/ResetClusterMixin.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/reset/ResetClusterMixin.java
index 7846c129ef..65dd687c1b 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/reset/ResetClusterMixin.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/reset/ResetClusterMixin.java
@@ -23,9 +23,9 @@ import static
org.apache.ignite.internal.cli.commands.Options.Constants.RECOVERY
import java.util.List;
import picocli.CommandLine.Option;
-/** Arguments for 'recovery reset cluster' command. */
+/** Arguments for 'recovery cluster reset' command. */
public class ResetClusterMixin {
- @Option(names = RECOVERY_CMG_NODES_OPTION, description =
RECOVERY_CMG_NODES_OPTION_DESC, split = ",")
+ @Option(names = RECOVERY_CMG_NODES_OPTION, description =
RECOVERY_CMG_NODES_OPTION_DESC, split = ",", required = true)
private List<String> cmgNodeNames;
/** Returns names of the proposed CMG nodes. */
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/reset/ResetClusterReplCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/reset/ResetClusterReplCommand.java
index f0e233ad05..a84279ea3f 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/reset/ResetClusterReplCommand.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/recovery/cluster/reset/ResetClusterReplCommand.java
@@ -23,7 +23,6 @@ import
org.apache.ignite.internal.cli.call.recovery.cluster.ResetClusterCallInpu
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;
@@ -49,7 +48,6 @@ public class ResetClusterReplCommand extends BaseCommand
implements Runnable {
runFlow(question.askQuestionIfNotConnected(clusterUrl.getClusterUrl())
.map(url -> ResetClusterCallInput.of(options, url))
.then(Flows.fromCall(call))
-
.exceptionHandler(ClusterNotInitializedExceptionHandler.createReplHandler("Cannot
reset cluster"))
.print()
);
}
diff --git
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/cluster/ClusterState.java
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/cluster/ClusterState.java
index 4fc341f8a7..e70f2ece32 100644
---
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/cluster/ClusterState.java
+++
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/cluster/ClusterState.java
@@ -22,7 +22,12 @@ import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.Collection;
+import java.util.List;
import java.util.Objects;
+import java.util.UUID;
+import org.apache.ignite.internal.tostring.IgniteToStringInclude;
+import org.apache.ignite.internal.tostring.S;
+import org.jetbrains.annotations.Nullable;
/**
* REST representation of internal ClusterState.
@@ -30,9 +35,11 @@ import java.util.Objects;
@Schema(description = "Information about current cluster state.")
public class ClusterState {
@Schema(description = "List of cluster management group nodes. These nodes
are responsible for maintaining RAFT cluster topology.")
+ @IgniteToStringInclude
private final Collection<String> cmgNodes;
@Schema(description = "List of metastorage nodes. These nodes are
responsible for storing RAFT cluster metadata.")
+ @IgniteToStringInclude
private final Collection<String> msNodes;
@Schema(description = "Version of Apache Ignite that the cluster was
created on.")
@@ -41,6 +48,10 @@ public class ClusterState {
@Schema(description = "Unique tag that identifies the cluster.")
private final ClusterTag clusterTag;
+ @Schema(description = "IDs the cluster had before.")
+ @IgniteToStringInclude
+ private final @Nullable List<UUID> formerClusterIds;
+
/**
* Creates a new cluster state.
*
@@ -48,18 +59,21 @@ public class ClusterState {
* @param msNodes Node names that host the Meta Storage.
* @param igniteVersion Version of Ignite nodes that comprise this cluster.
* @param clusterTag Cluster tag.
+ * @param formerClusterIds Former cluster IDs.
*/
@JsonCreator
public ClusterState(
@JsonProperty("cmgNodes") Collection<String> cmgNodes,
@JsonProperty("msNodes") Collection<String> msNodes,
@JsonProperty("igniteVersion") String igniteVersion,
- @JsonProperty("clusterTag") ClusterTag clusterTag
+ @JsonProperty("clusterTag") ClusterTag clusterTag,
+ @JsonProperty("formerClusterIds") @Nullable List<UUID>
formerClusterIds
) {
- this.cmgNodes = cmgNodes;
- this.msNodes = msNodes;
+ this.cmgNodes = List.copyOf(cmgNodes);
+ this.msNodes = List.copyOf(msNodes);
this.igniteVersion = igniteVersion;
this.clusterTag = clusterTag;
+ this.formerClusterIds = formerClusterIds == null ? null :
List.copyOf(formerClusterIds);
}
@JsonGetter("cmgNodes")
@@ -82,6 +96,11 @@ public class ClusterState {
return clusterTag;
}
+ @JsonGetter("formerClusterIds")
+ public @Nullable List<UUID> formerClusterIds() {
+ return formerClusterIds;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -102,11 +121,6 @@ public class ClusterState {
@Override
public String toString() {
- return "ClusterState{"
- + "cmgNodes=" + cmgNodes
- + ", msNodes=" + msNodes
- + ", igniteVersion=" + igniteVersion
- + ", clusterTag=" + clusterTag
- + '}';
+ return S.toString(this);
}
}
diff --git
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestManager.java
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestManager.java
index 044d7ebf25..73d42b42f8 100644
---
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestManager.java
+++
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestManager.java
@@ -38,7 +38,7 @@ public class RestManager {
private static final String CLUSTER_NOT_INITIALIZED_REASON = "Cluster is
not initialized. "
+ "Call /management/v1/cluster/init in order to initialize
cluster.";
- private static final String[] DEFAULT_ENDPOINTS = {
+ private static final String[] DEFAULT_AVAILABLE_ON_START_ENDPOINTS = {
"/management/v1/configuration/node",
"/management/v1/cluster/init",
"/management/v1/cluster/topology/physical",
@@ -46,16 +46,22 @@ public class RestManager {
"/management/v1/recovery/cluster"
};
+ private static final String[]
DEFAULT_AVAILABLE_DURING_INITIALIZATION_ENDPOINTS = {
+ "/management/v1/recovery/cluster"
+ };
+
private final String[] availableOnStartEndpoints;
+ private final String[] availableDuringInitializationEndpoints;
private RestState state = RestState.NOT_INITIALIZED;
public RestManager() {
- this(DEFAULT_ENDPOINTS);
+ this(DEFAULT_AVAILABLE_ON_START_ENDPOINTS,
DEFAULT_AVAILABLE_DURING_INITIALIZATION_ENDPOINTS);
}
- public RestManager(String[] availableOnStartEndpoints) {
+ public RestManager(String[] availableOnStartEndpoints, String[]
availableDuringInitializationEndpoints) {
this.availableOnStartEndpoints = availableOnStartEndpoints;
+ this.availableDuringInitializationEndpoints =
availableDuringInitializationEndpoints;
}
/**
@@ -69,7 +75,9 @@ public class RestManager {
case INITIALIZED:
return available();
case INITIALIZATION:
- return unavailable(DURING_INITIALIZATION_TITLE,
DURING_INITIALIZATION_REASON);
+ return pathDisabledForInitializationPhase(requestPath)
+ ? unavailable(DURING_INITIALIZATION_TITLE,
DURING_INITIALIZATION_REASON)
+ : available();
case NOT_INITIALIZED:
return pathDisabledForNotInitializedCluster(requestPath)
? unavailable(CLUSTER_NOT_INITIALIZED_TITLE,
CLUSTER_NOT_INITIALIZED_REASON)
@@ -80,7 +88,22 @@ public class RestManager {
}
/**
- * Returns disabled or not path of REST method.
+ * Returns disabled or not path of REST method during {@link
RestState#INITIALIZATION} phase.
+ *
+ * @param path REST method path.
+ * @return {@code true} in case when path disable or {@code false} if not.
+ */
+ private boolean pathDisabledForInitializationPhase(String path) {
+ for (String enabledPath : availableDuringInitializationEndpoints) {
+ if (path.startsWith(enabledPath)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns disabled or not path of REST method during {@link
RestState#NOT_INITIALIZED} phase.
*
* @param path REST method path.
* @return {@code true} in case when path disable or {@code false} if not.
diff --git
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/cluster/ClusterManagementController.java
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/cluster/ClusterManagementController.java
index bf9264ae3d..a9ae750979 100644
---
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/cluster/ClusterManagementController.java
+++
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/cluster/ClusterManagementController.java
@@ -89,7 +89,8 @@ public class ClusterManagementController implements
ClusterManagementApi, Resour
clusterState.cmgNodes(),
clusterState.metaStorageNodes(),
clusterState.igniteVersion().toString(),
- new ClusterTag(clusterState.clusterTag().clusterName(),
clusterState.clusterTag().clusterId())
+ new ClusterTag(clusterState.clusterTag().clusterName(),
clusterState.clusterTag().clusterId()),
+ clusterState.formerClusterIds()
);
}
diff --git
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/recovery/system/SystemDisasterRecoveryController.java
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/recovery/system/SystemDisasterRecoveryController.java
index 8063a0abd1..c982e7ec75 100644
---
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/recovery/system/SystemDisasterRecoveryController.java
+++
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/recovery/system/SystemDisasterRecoveryController.java
@@ -66,6 +66,8 @@ public class SystemDisasterRecoveryController implements
SystemDisasterRecoveryA
@Override
public CompletableFuture<Void> migrate(MigrateRequest command) {
+ LOG.info("Migrate command is {}", command);
+
return
systemDisasterRecoveryManager.migrate(migrateRequestToClusterState(command));
}
diff --git
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/RestManagerTest.java
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/RestManagerTest.java
index 22b60554a7..6c07763be5 100644
---
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/RestManagerTest.java
+++
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/RestManagerTest.java
@@ -40,9 +40,22 @@ public class RestManagerTest {
"cluster/v2/method"
};
+ private static final String[] AVAILABLE_DURING_INITIALIZATION = {
+ "cluster/v1",
+ };
+
+ private static final String[] UNAVAILABLE_DURING_INITIALIZATION = {
+ "node/v1/test",
+ "cluster/v2/test",
+ "cluster/v2",
+ "node/v1",
+ "node",
+ "cluster/v2/method"
+ };
+
@Test
public void pathAvailabilityTest() {
- RestManager restManager = new RestManager(AVAILABLE_ON_START);
+ RestManager restManager = new RestManager(AVAILABLE_ON_START,
AVAILABLE_DURING_INITIALIZATION);
for (String availablePath : AVAILABLE_ON_START) {
assertThat(restManager.pathAvailability(availablePath),
is(available()));
@@ -54,11 +67,11 @@ public class RestManagerTest {
restManager.setState(RestState.INITIALIZATION);
- for (String availablePath : AVAILABLE_ON_START) {
-
assertThat(restManager.pathAvailability(availablePath).isAvailable(),
is(false));
+ for (String availablePath : AVAILABLE_DURING_INITIALIZATION) {
+ assertThat(restManager.pathAvailability(availablePath),
is(available()));
}
- for (String unavailablePath : UNAVAILABLE_ON_START) {
+ for (String unavailablePath : UNAVAILABLE_DURING_INITIALIZATION) {
assertThat(restManager.pathAvailability(unavailablePath).isAvailable(),
is(false));
}
diff --git
a/modules/system-disaster-recovery/src/integrationTest/java/org/apache/ignite/internal/disaster/system/ItCmgDisasterRecoveryTest.java
b/modules/system-disaster-recovery/src/integrationTest/java/org/apache/ignite/internal/disaster/system/ItCmgDisasterRecoveryTest.java
index 0949367a07..8e667ee3a9 100644
---
a/modules/system-disaster-recovery/src/integrationTest/java/org/apache/ignite/internal/disaster/system/ItCmgDisasterRecoveryTest.java
+++
b/modules/system-disaster-recovery/src/integrationTest/java/org/apache/ignite/internal/disaster/system/ItCmgDisasterRecoveryTest.java
@@ -85,7 +85,7 @@ class ItCmgDisasterRecoveryTest extends
ItSystemGroupDisasterRecoveryTest {
private void initiateCmgRepairVia(IgniteImpl conductor, int...
newCmgIndexes) throws InterruptedException {
NodeMetadata nodeMetadata = conductor.node().nodeMetadata();
- recoveryClient.initiateCmgRepairVia(nodeMetadata.restHost(),
nodeMetadata.httpPort(), nodeNames(newCmgIndexes));
+ recoveryClient.initiateCmgRepair(nodeMetadata.restHost(),
nodeMetadata.httpPort(), nodeNames(newCmgIndexes));
}
@Test
diff --git
a/modules/system-disaster-recovery/src/integrationTest/java/org/apache/ignite/internal/disaster/system/ItSystemGroupDisasterRecoveryTest.java
b/modules/system-disaster-recovery/src/integrationTest/java/org/apache/ignite/internal/disaster/system/ItSystemGroupDisasterRecoveryTest.java
index 26798353dd..a00573cf38 100644
---
a/modules/system-disaster-recovery/src/integrationTest/java/org/apache/ignite/internal/disaster/system/ItSystemGroupDisasterRecoveryTest.java
+++
b/modules/system-disaster-recovery/src/integrationTest/java/org/apache/ignite/internal/disaster/system/ItSystemGroupDisasterRecoveryTest.java
@@ -34,6 +34,7 @@ import
org.apache.ignite.internal.ClusterPerTestIntegrationTest;
import org.apache.ignite.internal.app.IgniteImpl;
import org.apache.ignite.internal.app.IgniteServerImpl;
import org.apache.ignite.internal.cluster.management.ClusterState;
+import org.apache.ignite.network.NodeMetadata;
import org.jetbrains.annotations.Nullable;
/**
@@ -116,12 +117,15 @@ abstract class ItSystemGroupDisasterRecoveryTest extends
ClusterPerTestIntegrati
waitTillNodeRestartsInternally(oldClusterNodeIndex);
}
- static void initiateMigrationToNewCluster(IgniteImpl nodeMissingRepair,
IgniteImpl repairedNode) throws Exception {
- // TODO: IGNITE-22879 - initiate migration via CLI.
+ void initiateMigrationToNewCluster(IgniteImpl nodeMissingRepair,
IgniteImpl repairedNode) throws Exception {
+ NodeMetadata missingRepairMetadata =
nodeMissingRepair.node().nodeMetadata();
+ NodeMetadata repairedMetadata = repairedNode.node().nodeMetadata();
- ClusterState newClusterState = clusterState(repairedNode);
-
- CompletableFuture<Void> migrationFuture =
nodeMissingRepair.systemDisasterRecoveryManager().migrate(newClusterState);
- assertThat(migrationFuture, willCompleteSuccessfully());
+ recoveryClient.initiateMigration(
+ missingRepairMetadata.restHost(),
+ missingRepairMetadata.httpPort(),
+ repairedMetadata.restHost(),
+ repairedMetadata.httpPort()
+ );
}
}
diff --git
a/modules/system-disaster-recovery/src/integrationTest/java/org/apache/ignite/internal/disaster/system/SystemDisasterRecoveryClient.java
b/modules/system-disaster-recovery/src/integrationTest/java/org/apache/ignite/internal/disaster/system/SystemDisasterRecoveryClient.java
index 1528f0669d..90fc69da66 100644
---
a/modules/system-disaster-recovery/src/integrationTest/java/org/apache/ignite/internal/disaster/system/SystemDisasterRecoveryClient.java
+++
b/modules/system-disaster-recovery/src/integrationTest/java/org/apache/ignite/internal/disaster/system/SystemDisasterRecoveryClient.java
@@ -22,7 +22,9 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Arrays;
import java.util.List;
+import java.util.stream.Stream;
import org.apache.ignite.internal.cli.Main;
import org.apache.ignite.internal.logger.IgniteLogger;
import org.apache.ignite.internal.logger.Loggers;
@@ -34,23 +36,28 @@ import org.apache.ignite.internal.logger.Loggers;
class SystemDisasterRecoveryClient {
private static final IgniteLogger LOG =
Loggers.forClass(SystemDisasterRecoveryClient.class);
- void initiateCmgRepairVia(String httpHost, int httpPort, String...
newCmgNodeNames) throws InterruptedException {
+ void initiateCmgRepair(String httpHost, int httpPort, String...
newCmgNodeNames) throws InterruptedException {
LOG.info("Initiating CMG repair via {}:{}, new CMG {}", httpHost,
httpPort, List.of(newCmgNodeNames));
+ executeWithSameJavaBinaryAndClasspath(
+ Main.class.getName(),
+ "recovery", "cluster", "reset",
+ "--url", "http://" + httpHost + ":" + httpPort,
+ "--cluster-management-group", String.join(",", newCmgNodeNames)
+ );
+ }
+
+ private static void executeWithSameJavaBinaryAndClasspath(String... args)
throws InterruptedException {
String javaBinaryPath =
ProcessHandle.current().info().command().orElseThrow();
String javaClassPath = System.getProperty("java.class.path");
LOG.info("Java binary is {}, classpath is {}", javaBinaryPath,
javaClassPath);
+ String[] fullArgs = Stream.concat(Stream.of(javaBinaryPath, "-cp",
javaClassPath), Arrays.stream(args))
+ .toArray(String[]::new);
+
//noinspection UseOfProcessBuilder
- ProcessBuilder processBuilder = new ProcessBuilder(
- javaBinaryPath,
- "-cp", javaClassPath,
- Main.class.getName(),
- "recovery", "cluster", "reset",
- "--url", "http://" + httpHost + ":" + httpPort,
- "--cluster-management-group", String.join(",", newCmgNodeNames)
- );
+ ProcessBuilder processBuilder = new ProcessBuilder(fullArgs);
executeProcessFrom(processBuilder);
}
@@ -88,4 +95,15 @@ class SystemDisasterRecoveryClient {
throw new RuntimeException(e);
}
}
+
+ void initiateMigration(String oldHttpHost, int oldHttpPort, String
newHttpHost, int newHttpPort) throws InterruptedException {
+ LOG.info("Initiating migration, old {}:{}, new {}:{}", oldHttpHost,
oldHttpPort, newHttpHost, newHttpPort);
+
+ executeWithSameJavaBinaryAndClasspath(
+ Main.class.getName(),
+ "recovery", "cluster", "migrate",
+ "--old-cluster-url", "http://" + oldHttpHost + ":" +
oldHttpPort,
+ "--new-cluster-url", "http://" + newHttpHost + ":" +
newHttpPort
+ );
+ }
}