This is an automated email from the ASF dual-hosted git repository.
mpochatkin 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 48e2f0e03fc IGNITE-27063 Wait for node join in CLI (#6994)
48e2f0e03fc is described below
commit 48e2f0e03fc000b19a720f7e559410f89073d5be
Author: Vadim Pakhnushev <[email protected]>
AuthorDate: Fri Nov 21 15:42:29 2025 +0300
IGNITE-27063 Wait for node join in CLI (#6994)
---
gradle/libs.versions.toml | 2 +-
.../cluster/init/ItClusterInitOneNodeTest.java | 66 ++++++++++++
.../internal/cli/call/cluster/ClusterInitCall.java | 59 +++++++----
...allFactory.java => ClusterInitCallFactory.java} | 15 +--
.../call/cluster/unit/DeployUnitCallFactory.java | 7 +-
.../cli/call/cluster/unit/DeployUnitReplCall.java | 4 +-
.../commands/cluster/init/ClusterInitCommand.java | 14 ++-
.../cluster/init/ClusterInitConstants.java} | 22 ++--
.../cluster/init/ClusterInitReplCommand.java | 33 +++++-
.../cluster/unit/ClusterUnitDeployCommand.java | 5 +-
.../cli/core/call/AsyncCallExecutionPipeline.java | 8 +-
.../call/AsyncCallExecutionPipelineBuilder.java | 31 ++++--
.../internal/cli/core/call/AsyncCallFactory.java} | 26 ++---
.../cli/core/call/CallExecutionPipeline.java | 6 +-
.../call/SpinnerRenderer.java} | 33 +++---
.../cli/core/repl/ConnectionHeartBeat.java | 14 ++-
.../internal/cli/commands/CliCommandTestBase.java | 113 ++++++++++++++++++--
.../internal/cli/commands/ProfileMixinTest.java | 116 ++++++++++++++++++---
.../cli/commands/UrlOptionsNegativeTest.java | 34 +++---
.../cli/commands/cluster/ClusterInitReplTest.java | 11 ++
.../cli/commands/cluster/ClusterInitTest.java | 16 +--
.../cli/core/call/SpinnerRendererTest.java} | 30 +++---
.../rest/cluster/ClusterManagementController.java | 31 ++++--
.../rest/cluster/ClusterManagementRestFactory.java | 13 ++-
.../internal/rest/cluster/JoinFutureProvider.java} | 24 ++---
.../ignite/internal/rest/RestComponentTest.java | 3 -
.../org/apache/ignite/internal/app/IgniteImpl.java | 23 +++-
27 files changed, 557 insertions(+), 202 deletions(-)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index fcdebdf13ab..87add84c91c 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -80,7 +80,7 @@ testkit = "1.14.0"
openapi = "4.10.0"
autoService = "1.1.1"
awaitility = "4.3.0"
-progressBar = "0.10.0"
+progressBar = "0.10.1"
guava = "33.5.0-jre"
jna = "5.18.1"
tree-sitter = "0.25.3"
diff --git
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/cluster/init/ItClusterInitOneNodeTest.java
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/cluster/init/ItClusterInitOneNodeTest.java
new file mode 100644
index 00000000000..35f70e588cb
--- /dev/null
+++
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/cluster/init/ItClusterInitOneNodeTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.cluster.init;
+
+import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import
org.apache.ignite.internal.cli.commands.CliCommandTestNotInitializedIntegrationBase;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests that node is immediately available after the cluster init command.
+ *
+ * <p>Because the {@link org.apache.ignite.internal.cli.CliIntegrationTest}
extends
+ * {@link org.apache.ignite.internal.ClusterPerClassIntegrationTest}, each CLI
test case for init has to be placed in a separate
+ * test class. It'd ideal to refactor the base classes to have a CLI init test
class with multiple test cases.
+ * This may be needed if more tests are added.
+ */
+public class ItClusterInitOneNodeTest extends
CliCommandTestNotInitializedIntegrationBase {
+ @Override
+ protected int initialNodes() {
+ return 1;
+ }
+
+ @Test
+ void initCluster() {
+ // when
+ connect(NODE_URL);
+
+ execute("cluster", "init", "--name", "cluster");
+
+ assertAll(
+ this::assertExitCodeIsZero,
+ this::assertErrOutputIsEmpty,
+ () -> assertOutputContains("Cluster was initialized
successfully")
+ );
+
+ // then status is immediately available and initialized
+ execute("cluster", "status");
+
+ assertAll(
+ this::assertExitCodeIsZero,
+ this::assertErrOutputIsEmpty,
+ () -> assertOutputIs(format(
+ "[name: cluster, nodes: 1, status: active, cmgNodes:
[{}], msNodes: [{}]]" + System.lineSeparator(),
+ CLUSTER.nodeName(0),
+ CLUSTER.nodeName(0)
+ ))
+ );
+ }
+}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/ClusterInitCall.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/ClusterInitCall.java
index 5ebf016d51d..690458894c3 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/ClusterInitCall.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/ClusterInitCall.java
@@ -17,45 +17,64 @@
package org.apache.ignite.internal.cli.call.cluster;
-import jakarta.inject.Singleton;
-import org.apache.ignite.internal.cli.core.call.Call;
-import org.apache.ignite.internal.cli.core.call.DefaultCallOutput;
+import static java.util.concurrent.CompletableFuture.supplyAsync;
+import static
org.apache.ignite.internal.cli.core.call.DefaultCallOutput.failure;
+import static
org.apache.ignite.internal.cli.core.call.DefaultCallOutput.success;
+
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.internal.cli.core.call.AsyncCall;
+import org.apache.ignite.internal.cli.core.call.CallOutput;
+import org.apache.ignite.internal.cli.core.call.ProgressTracker;
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.invoker.ApiClient;
import org.apache.ignite.rest.client.invoker.ApiException;
import org.apache.ignite.rest.client.model.InitCommand;
/**
* Inits cluster.
*/
-@Singleton
-public class ClusterInitCall implements Call<ClusterInitCallInput, String> {
+public class ClusterInitCall implements AsyncCall<ClusterInitCallInput,
String> {
+ private final ProgressTracker tracker;
+
private final ApiClientFactory clientFactory;
- public ClusterInitCall(ApiClientFactory clientFactory) {
+ /**
+ * Custom read timeout for the init operation, default is 10 seconds which
could be not enough.
+ */
+ private static final int READ_TIMEOUT_MILLIS = 60_000;
+
+ ClusterInitCall(ProgressTracker tracker, ApiClientFactory clientFactory) {
+ this.tracker = tracker;
this.clientFactory = clientFactory;
}
- /** {@inheritDoc} */
@Override
- public DefaultCallOutput<String> execute(ClusterInitCallInput input) {
+ public CompletableFuture<CallOutput<String>> execute(ClusterInitCallInput
input) {
ClusterManagementApi client = createApiClient(input);
- try {
- client.init(new InitCommand()
- .metaStorageNodes(input.getMetaStorageNodes())
- .cmgNodes(input.getCmgNodes())
- .clusterName(input.getClusterName())
- .clusterConfiguration(input.clusterConfiguration())
- );
- return DefaultCallOutput.success("Cluster was initialized
successfully");
- } catch (ApiException | IllegalArgumentException e) {
- return DefaultCallOutput.failure(new IgniteCliApiException(e,
input.getClusterUrl()));
- }
+ tracker.maxSize(-1);
+ return supplyAsync(() -> {
+ try {
+ client.init(new InitCommand()
+ .metaStorageNodes(input.getMetaStorageNodes())
+ .cmgNodes(input.getCmgNodes())
+ .clusterName(input.getClusterName())
+ .clusterConfiguration(input.clusterConfiguration())
+ );
+ return success("Cluster was initialized successfully.");
+ } catch (ApiException | IllegalArgumentException e) {
+ return failure(new IgniteCliApiException(e,
input.getClusterUrl()));
+ } finally {
+ tracker.done();
+ }
+ });
}
private ClusterManagementApi createApiClient(ClusterInitCallInput input) {
- return new
ClusterManagementApi(clientFactory.getClient(input.getClusterUrl()));
+ ApiClient client = clientFactory.getClient(input.getClusterUrl());
+ client.setReadTimeout(READ_TIMEOUT_MILLIS);
+ return new ClusterManagementApi(client);
}
}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/unit/DeployUnitCallFactory.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/ClusterInitCallFactory.java
similarity index 66%
copy from
modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/unit/DeployUnitCallFactory.java
copy to
modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/ClusterInitCallFactory.java
index 5e208d1b665..d3a09f5317a 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/unit/DeployUnitCallFactory.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/ClusterInitCallFactory.java
@@ -15,23 +15,26 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.cli.call.cluster.unit;
+package org.apache.ignite.internal.cli.call.cluster;
import jakarta.inject.Singleton;
+import org.apache.ignite.internal.cli.core.call.AsyncCall;
+import org.apache.ignite.internal.cli.core.call.AsyncCallFactory;
import org.apache.ignite.internal.cli.core.call.ProgressTracker;
import org.apache.ignite.internal.cli.core.rest.ApiClientFactory;
-/** Factory for {@link DeployUnitCall}. */
+/** Factory for {@link ClusterInitCall}. */
@Singleton
-public class DeployUnitCallFactory {
+public class ClusterInitCallFactory implements
AsyncCallFactory<ClusterInitCallInput, String> {
private final ApiClientFactory factory;
- public DeployUnitCallFactory(ApiClientFactory factory) {
+ public ClusterInitCallFactory(ApiClientFactory factory) {
this.factory = factory;
}
- public DeployUnitCall create(ProgressTracker tracker) {
- return new DeployUnitCall(tracker, factory);
+ @Override
+ public AsyncCall<ClusterInitCallInput, String> create(ProgressTracker
tracker) {
+ return new ClusterInitCall(tracker, factory);
}
}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/unit/DeployUnitCallFactory.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/unit/DeployUnitCallFactory.java
index 5e208d1b665..d92b62e641d 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/unit/DeployUnitCallFactory.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/unit/DeployUnitCallFactory.java
@@ -18,12 +18,14 @@
package org.apache.ignite.internal.cli.call.cluster.unit;
import jakarta.inject.Singleton;
+import org.apache.ignite.internal.cli.core.call.AsyncCall;
+import org.apache.ignite.internal.cli.core.call.AsyncCallFactory;
import org.apache.ignite.internal.cli.core.call.ProgressTracker;
import org.apache.ignite.internal.cli.core.rest.ApiClientFactory;
/** Factory for {@link DeployUnitCall}. */
@Singleton
-public class DeployUnitCallFactory {
+public class DeployUnitCallFactory implements
AsyncCallFactory<DeployUnitCallInput, String> {
private final ApiClientFactory factory;
@@ -31,7 +33,8 @@ public class DeployUnitCallFactory {
this.factory = factory;
}
- public DeployUnitCall create(ProgressTracker tracker) {
+ @Override
+ public AsyncCall<DeployUnitCallInput, String> create(ProgressTracker
tracker) {
return new DeployUnitCall(tracker, factory);
}
}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/unit/DeployUnitReplCall.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/unit/DeployUnitReplCall.java
index ff3256722c2..dac5ca056f6 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/unit/DeployUnitReplCall.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/unit/DeployUnitReplCall.java
@@ -24,11 +24,11 @@ import
org.apache.ignite.internal.cli.core.repl.registry.UnitsRegistry;
/** Call to deploy a unit and refresh units registry. */
public class DeployUnitReplCall implements AsyncCall<DeployUnitCallInput,
String> {
- private final DeployUnitCall deployUnitCall;
+ private final AsyncCall<DeployUnitCallInput, String> deployUnitCall;
private final UnitsRegistry unitsRegistry;
- DeployUnitReplCall(DeployUnitCall deployUnitCall, UnitsRegistry
unitsRegistry) {
+ DeployUnitReplCall(AsyncCall<DeployUnitCallInput, String> deployUnitCall,
UnitsRegistry unitsRegistry) {
this.deployUnitCall = deployUnitCall;
this.unitsRegistry = unitsRegistry;
}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/init/ClusterInitCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/init/ClusterInitCommand.java
index 63b0faac3c7..52953ab9132 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/init/ClusterInitCommand.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/init/ClusterInitCommand.java
@@ -17,13 +17,16 @@
package org.apache.ignite.internal.cli.commands.cluster.init;
+import static
org.apache.ignite.internal.cli.commands.cluster.init.ClusterInitConstants.SPINNER_PREFIX;
+import static
org.apache.ignite.internal.cli.commands.cluster.init.ClusterInitConstants.SPINNER_UPDATE_INTERVAL_MILLIS;
+import static
org.apache.ignite.internal.cli.core.call.CallExecutionPipeline.asyncBuilder;
+
import jakarta.inject.Inject;
import java.util.concurrent.Callable;
-import org.apache.ignite.internal.cli.call.cluster.ClusterInitCall;
+import org.apache.ignite.internal.cli.call.cluster.ClusterInitCallFactory;
import org.apache.ignite.internal.cli.call.cluster.ClusterInitCallInput;
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 picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
@@ -40,13 +43,14 @@ public class ClusterInitCommand extends BaseCommand
implements Callable<Integer>
private ClusterUrlProfileMixin clusterUrl;
@Inject
- private ClusterInitCall call;
+ private ClusterInitCallFactory callFactory;
- /** {@inheritDoc} */
@Override
public Integer call() {
- return runPipeline(CallExecutionPipeline.builder(call)
+ return runPipeline(asyncBuilder(callFactory)
.inputProvider(this::buildCallInput)
+ .enableSpinner(SPINNER_PREFIX)
+ .updateIntervalMillis(SPINNER_UPDATE_INTERVAL_MILLIS)
);
}
diff --git
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/cluster/ClusterInitReplTest.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/init/ClusterInitConstants.java
similarity index 61%
copy from
modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/cluster/ClusterInitReplTest.java
copy to
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/init/ClusterInitConstants.java
index 18f900e6857..651bcf56e66 100644
---
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/cluster/ClusterInitReplTest.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/init/ClusterInitConstants.java
@@ -15,21 +15,13 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.cli.commands.cluster;
+package org.apache.ignite.internal.cli.commands.cluster.init;
-import
org.apache.ignite.internal.cli.commands.cluster.init.ClusterInitReplCommand;
-import org.junit.jupiter.api.DisplayName;
-
-/** Tests "cluster init" command in REPL mode. */
-@DisplayName("cluster init repl")
-public class ClusterInitReplTest extends ClusterInitTest {
- @Override
- protected Class<?> getCommandClass() {
- return ClusterInitReplCommand.class;
- }
+/**
+ * Constants for cluster init command.
+ */
+class ClusterInitConstants {
+ static final String SPINNER_PREFIX = "Initializing";
- @Override
- protected int errorExitCode() {
- return 0;
- }
+ static final int SPINNER_UPDATE_INTERVAL_MILLIS = 500;
}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/init/ClusterInitReplCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/init/ClusterInitReplCommand.java
index ee70428f36e..e04f94dddb0 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/init/ClusterInitReplCommand.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/init/ClusterInitReplCommand.java
@@ -18,17 +18,23 @@
package org.apache.ignite.internal.cli.commands.cluster.init;
import static
org.apache.ignite.internal.cli.commands.Options.Constants.CLUSTER_CONFIG_OPTION;
+import static
org.apache.ignite.internal.cli.commands.cluster.init.ClusterInitConstants.SPINNER_PREFIX;
+import static
org.apache.ignite.internal.cli.commands.cluster.init.ClusterInitConstants.SPINNER_UPDATE_INTERVAL_MILLIS;
+import static
org.apache.ignite.internal.cli.core.call.CallExecutionPipeline.asyncBuilder;
import static
org.apache.ignite.internal.cli.core.style.component.QuestionUiComponent.fromYesNoQuestion;
import static picocli.CommandLine.Command;
import jakarta.inject.Inject;
-import org.apache.ignite.internal.cli.call.cluster.ClusterInitCall;
+import org.apache.ignite.internal.cli.call.cluster.ClusterInitCallFactory;
import org.apache.ignite.internal.cli.call.cluster.ClusterInitCallInput;
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.call.AsyncCall;
+import org.apache.ignite.internal.cli.core.call.ProgressTracker;
import org.apache.ignite.internal.cli.core.flow.builder.FlowBuilder;
import org.apache.ignite.internal.cli.core.flow.builder.Flows;
+import org.apache.ignite.internal.cli.core.repl.ConnectionHeartBeat;
import org.apache.ignite.internal.cli.core.style.component.QuestionUiComponent;
import picocli.CommandLine.Mixin;
@@ -45,18 +51,19 @@ public class ClusterInitReplCommand extends BaseCommand
implements Runnable {
private ClusterInitOptions clusterInitOptions;
@Inject
- private ClusterInitCall call;
+ private ClusterInitCallFactory callFactory;
@Inject
private ConnectToClusterQuestion question;
- /** {@inheritDoc} */
+ @Inject
+ private ConnectionHeartBeat connectionHeartBeat;
+
@Override
public void run() {
runFlow(question.askQuestionIfNotConnected(clusterUrl.getClusterUrl())
.then(askQuestionIfConfigIsPath().build())
- .then(Flows.fromCall(call))
- .print()
+ .then(Flows.mono(this::runAsync))
);
}
@@ -89,4 +96,20 @@ public class ClusterInitReplCommand extends BaseCommand
implements Runnable {
.fromClusterInitOptions(clusterInitOptions)
.build();
}
+
+ private int runAsync(ClusterInitCallInput input) {
+ return runPipeline(
+ asyncBuilder(this::createCall)
+ .inputProvider(() -> input)
+ .enableSpinner(SPINNER_PREFIX)
+ .updateIntervalMillis(SPINNER_UPDATE_INTERVAL_MILLIS)
+ );
+ }
+
+ private AsyncCall<ClusterInitCallInput, String> createCall(ProgressTracker
tracker) {
+ AsyncCall<ClusterInitCallInput, String> delegate =
callFactory.create(tracker);
+ return input -> delegate.execute(input)
+ // Refresh connected state immediately after execution because
node state is unavailable during cluster initialization
+ .whenComplete((output, throwable) ->
connectionHeartBeat.pingConnection());
+ }
}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/unit/ClusterUnitDeployCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/unit/ClusterUnitDeployCommand.java
index 3cb47049261..0ae4f30a5e8 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/unit/ClusterUnitDeployCommand.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/unit/ClusterUnitDeployCommand.java
@@ -17,12 +17,13 @@
package org.apache.ignite.internal.cli.commands.cluster.unit;
+import static
org.apache.ignite.internal.cli.core.call.CallExecutionPipeline.asyncBuilder;
+
import jakarta.inject.Inject;
import java.util.concurrent.Callable;
import org.apache.ignite.internal.cli.call.cluster.unit.DeployUnitCallFactory;
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;
@@ -42,7 +43,7 @@ public class ClusterUnitDeployCommand extends BaseCommand
implements Callable<In
@Override
public Integer call() throws Exception {
- return
runPipeline(CallExecutionPipeline.asyncBuilder(callFactory::create)
+ return runPipeline(asyncBuilder(callFactory)
.inputProvider(() ->
options.toDeployUnitCallInput(clusterUrl.getClusterUrl()))
.exceptionHandler(ClusterNotInitializedExceptionHandler.createHandler("Cannot
deploy unit"))
);
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/AsyncCallExecutionPipeline.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/AsyncCallExecutionPipeline.java
index baa31d91b05..104b804d06b 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/AsyncCallExecutionPipeline.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/AsyncCallExecutionPipeline.java
@@ -19,7 +19,6 @@ package org.apache.ignite.internal.cli.core.call;
import java.io.PrintWriter;
import java.util.concurrent.CompletionException;
-import java.util.function.Function;
import java.util.function.Supplier;
import me.tongfei.progressbar.DelegatingProgressBarConsumer;
import me.tongfei.progressbar.ProgressBarBuilder;
@@ -30,13 +29,13 @@ import
org.apache.ignite.internal.cli.core.exception.ExceptionHandlers;
/** Call execution pipeline that executes an async call and displays progress
bar. */
public class AsyncCallExecutionPipeline<I extends CallInput, T> extends
AbstractCallExecutionPipeline<I, T> {
/** Async call factory. */
- private final Function<ProgressTracker, AsyncCall<I, T>> callFactory;
+ private final AsyncCallFactory<I, T> callFactory;
/** Builder for progress bar rendering. */
private final ProgressBarBuilder progressBarBuilder;
AsyncCallExecutionPipeline(
- Function<ProgressTracker, AsyncCall<I, T>> callFactory,
+ AsyncCallFactory<I, T> callFactory,
ProgressBarBuilder progressBarBuilder,
PrintWriter output,
PrintWriter errOutput,
@@ -62,11 +61,10 @@ public class AsyncCallExecutionPipeline<I extends
CallInput, T> extends Abstract
output.flush();
}
});
- progressBarBuilder.setUpdateIntervalMillis(60);
try {
ProgressBarTracker tracker = new
ProgressBarTracker(progressBarBuilder);
- CallOutput<T> result = callFactory.apply(tracker)
+ CallOutput<T> result = callFactory.create(tracker)
.execute(callInput)
.whenComplete((el, err) -> tracker.close())
.join();
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/AsyncCallExecutionPipelineBuilder.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/AsyncCallExecutionPipelineBuilder.java
index f98be0fe89e..5354d896048 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/AsyncCallExecutionPipelineBuilder.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/AsyncCallExecutionPipelineBuilder.java
@@ -21,7 +21,6 @@ import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.time.temporal.ChronoUnit;
-import java.util.function.Function;
import java.util.function.Supplier;
import me.tongfei.progressbar.ProgressBarBuilder;
import me.tongfei.progressbar.ProgressBarStyle;
@@ -34,7 +33,7 @@ import
org.apache.ignite.internal.cli.core.exception.handler.DefaultExceptionHan
/** Builder for {@link AsyncCallExecutionPipeline}. */
public class AsyncCallExecutionPipelineBuilder<I extends CallInput, T>
implements CallExecutionPipelineBuilder<I, T> {
- private final Function<ProgressTracker, AsyncCall<I, T>> callFactory;
+ private final AsyncCallFactory<I, T> callFactory;
private final ProgressBarBuilder progressBarBuilder = new
ProgressBarBuilder()
.setStyle(ProgressBarStyle.UNICODE_BLOCK)
@@ -42,8 +41,8 @@ public class AsyncCallExecutionPipelineBuilder<I extends
CallInput, T> implement
.setSpeedUnit(ChronoUnit.SECONDS)
.setInitialMax(100)
.hideEta()
- .setTaskName("")
- .showSpeed();
+ .showSpeed()
+ .setUpdateIntervalMillis(60);
private final ExceptionHandlers exceptionHandlers = new
DefaultExceptionHandlers();
@@ -57,7 +56,7 @@ public class AsyncCallExecutionPipelineBuilder<I extends
CallInput, T> implement
private boolean[] verbose;
- AsyncCallExecutionPipelineBuilder(Function<ProgressTracker, AsyncCall<I,
T>> callFactory) {
+ AsyncCallExecutionPipelineBuilder(AsyncCallFactory<I, T> callFactory) {
this.callFactory = callFactory;
}
@@ -110,14 +109,26 @@ public class AsyncCallExecutionPipelineBuilder<I extends
CallInput, T> implement
return this;
}
- @Override
- public AsyncCallExecutionPipelineBuilder<I, T> verbose(boolean[] verbose) {
- this.verbose = verbose;
+ /**
+ * Changes default progress bar to simple spinner, which prints 1 to 3
dots after the prefix.
+ *
+ * @param prefix Prefix.
+ * @return This builder.
+ */
+ public AsyncCallExecutionPipelineBuilder<I, T> enableSpinner(String
prefix) {
+ SpinnerRenderer renderer = new SpinnerRenderer(prefix);
+ this.progressBarBuilder.setRenderer((progress, maxLength) ->
renderer.render(maxLength));
return this;
}
- public AsyncCallExecutionPipelineBuilder<I, T> name(String name) {
- this.progressBarBuilder.setTaskName(name);
+ public AsyncCallExecutionPipelineBuilder<I, T> updateIntervalMillis(int
updateIntervalMillis) {
+ this.progressBarBuilder.setUpdateIntervalMillis(updateIntervalMillis);
+ return this;
+ }
+
+ @Override
+ public AsyncCallExecutionPipelineBuilder<I, T> verbose(boolean[] verbose) {
+ this.verbose = verbose;
return this;
}
diff --git
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/cluster/ClusterInitReplTest.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/AsyncCallFactory.java
similarity index 61%
copy from
modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/cluster/ClusterInitReplTest.java
copy to
modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/AsyncCallFactory.java
index 18f900e6857..0d13f5211cf 100644
---
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/cluster/ClusterInitReplTest.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/AsyncCallFactory.java
@@ -15,21 +15,15 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.cli.commands.cluster;
+package org.apache.ignite.internal.cli.core.call;
-import
org.apache.ignite.internal.cli.commands.cluster.init.ClusterInitReplCommand;
-import org.junit.jupiter.api.DisplayName;
-
-/** Tests "cluster init" command in REPL mode. */
-@DisplayName("cluster init repl")
-public class ClusterInitReplTest extends ClusterInitTest {
- @Override
- protected Class<?> getCommandClass() {
- return ClusterInitReplCommand.class;
- }
-
- @Override
- protected int errorExitCode() {
- return 0;
- }
+/**
+ * Async call factory.
+ *
+ * @param <IT> Input type.
+ * @param <OT> Output type.
+ */
+@FunctionalInterface
+public interface AsyncCallFactory<IT extends CallInput, OT> {
+ AsyncCall<IT, OT> create(ProgressTracker tracker);
}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/CallExecutionPipeline.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/CallExecutionPipeline.java
index 2665640a9c6..3cda3daaa68 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/CallExecutionPipeline.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/CallExecutionPipeline.java
@@ -17,8 +17,6 @@
package org.apache.ignite.internal.cli.core.call;
-import java.util.function.Function;
-
/** Pipeline that executes a call. */
@FunctionalInterface
public interface CallExecutionPipeline<I extends CallInput, T> {
@@ -32,9 +30,7 @@ public interface CallExecutionPipeline<I extends CallInput,
T> {
}
/** Builder helper method. */
- static <I extends CallInput, T> AsyncCallExecutionPipelineBuilder<I, T>
asyncBuilder(
- Function<ProgressTracker, AsyncCall<I, T>> callFactory
- ) {
+ static <I extends CallInput, T> AsyncCallExecutionPipelineBuilder<I, T>
asyncBuilder(AsyncCallFactory<I, T> callFactory) {
return new AsyncCallExecutionPipelineBuilder<>(callFactory);
}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/unit/DeployUnitCallFactory.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/SpinnerRenderer.java
similarity index 54%
copy from
modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/unit/DeployUnitCallFactory.java
copy to
modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/SpinnerRenderer.java
index 5e208d1b665..4f970a000fe 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/unit/DeployUnitCallFactory.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/call/SpinnerRenderer.java
@@ -15,23 +15,30 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.cli.call.cluster.unit;
+package org.apache.ignite.internal.cli.core.call;
-import jakarta.inject.Singleton;
-import org.apache.ignite.internal.cli.core.call.ProgressTracker;
-import org.apache.ignite.internal.cli.core.rest.ApiClientFactory;
-
-/** Factory for {@link DeployUnitCall}. */
-@Singleton
-public class DeployUnitCallFactory {
+/**
+ * Simple spinner renderer which adds up to 3 dots to the prefix text on each
render call.
+ */
+class SpinnerRenderer {
+ private final String prefix;
- private final ApiClientFactory factory;
+ private int current;
- public DeployUnitCallFactory(ApiClientFactory factory) {
- this.factory = factory;
+ SpinnerRenderer(String prefix) {
+ this.prefix = prefix;
}
- public DeployUnitCall create(ProgressTracker tracker) {
- return new DeployUnitCall(tracker, factory);
+ public String render(int maxLength) {
+ if (maxLength <= 0) {
+ return "";
+ }
+
+ String res = prefix + ".".repeat(current + 1)
+ + " ".repeat(2 - current)
+ + "\b".repeat(2 - current); // Make the cursor appear after
the last dot
+ current = (current + 1) % 3;
+
+ return res.substring(0, Math.min(res.length(), maxLength));
}
}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/ConnectionHeartBeat.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/ConnectionHeartBeat.java
index 7e25c11faa1..61cfda05bc5 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/ConnectionHeartBeat.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/ConnectionHeartBeat.java
@@ -23,6 +23,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.ignite.internal.cli.core.rest.ApiClientFactory;
@@ -57,6 +58,8 @@ public class ConnectionHeartBeat implements
ConnectionEventListener {
private final AtomicBoolean connected = new AtomicBoolean(false);
+ private final AtomicReference<String> lastKnownUrl = new
AtomicReference<>(null);
+
private final Lock lock = new ReentrantLock();
/**
@@ -84,6 +87,8 @@ public class ConnectionHeartBeat implements
ConnectionEventListener {
eventPublisher.publish(Events.connectionRestored());
}
+ lastKnownUrl.set(sessionInfo.nodeUrl());
+
lock.lock();
try {
if (scheduledConnectionHeartbeatExecutor == null) {
@@ -92,7 +97,7 @@ public class ConnectionHeartBeat implements
ConnectionEventListener {
// Start connection heart beat
scheduledConnectionHeartbeatExecutor.scheduleAtFixedRate(
- () -> pingConnection(sessionInfo.nodeUrl()),
+ this::pingConnection,
0,
cliCheckConnectionPeriodSecond,
TimeUnit.SECONDS
@@ -119,9 +124,12 @@ public class ConnectionHeartBeat implements
ConnectionEventListener {
}
}
- private void pingConnection(String nodeUrl) {
+ /**
+ * Checks connection to last connected node.
+ */
+ public void pingConnection() {
try {
- new
NodeManagementApi(clientFactory.getClient(nodeUrl)).nodeState();
+ new
NodeManagementApi(clientFactory.getClient(lastKnownUrl.get())).nodeState();
if (connected.compareAndSet(false, true)) {
eventPublisher.publish(Events.connectionRestored());
}
diff --git
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/CliCommandTestBase.java
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/CliCommandTestBase.java
index 1ab5f40adda..47ba427771e 100644
---
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/CliCommandTestBase.java
+++
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/CliCommandTestBase.java
@@ -17,17 +17,17 @@
package org.apache.ignite.internal.cli.commands;
+import static java.util.concurrent.CompletableFuture.completedFuture;
+import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
-import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.emptyString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.matchesRegex;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertAll;
-import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -42,6 +42,8 @@ import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
+import org.apache.ignite.internal.cli.core.call.AsyncCall;
+import org.apache.ignite.internal.cli.core.call.AsyncCallFactory;
import org.apache.ignite.internal.cli.core.call.Call;
import org.apache.ignite.internal.cli.core.call.CallInput;
import org.apache.ignite.internal.cli.core.call.DefaultCallOutput;
@@ -162,8 +164,6 @@ public abstract class CliCommandTestBase extends
BaseIgniteAbstractTest {
* @param expectedOutput Expected command output.
*/
protected void assertSuccessfulOutputIs(String expectedOutput) {
- log.info(sout.toString());
- log.info(serr.toString());
assertAll(
this::assertExitCodeIsZero,
() -> assertOutputIs(expectedOutput),
@@ -171,15 +171,30 @@ public abstract class CliCommandTestBase extends
BaseIgniteAbstractTest {
);
}
+ /**
+ * Asserts that the command's exit code is zero, output contains expected
output and the error output is empty.
+ *
+ * @param expectedOutput Expected command output.
+ */
+ protected void assertSuccessfulOutputContains(String expectedOutput) {
+ assertAll(
+ this::assertExitCodeIsZero,
+ () -> assertOutputContains(expectedOutput),
+ this::assertErrOutputIsEmpty
+ );
+ }
+
/**
* Asserts that {@code expected} and {@code actual} are equals ignoring
differences in line separators.
*
* @param reason Description of the assertion.
- * @param exp Expected result.
* @param actual Actual result.
+ * @param exp Expected result.
*/
- private static void assertEqualsIgnoreLineSeparators(String reason, String
exp, String actual) {
- assertThat(reason, exp.lines().collect(toList()),
contains(actual.lines().toArray(String[]::new)));
+ private static void assertEqualsIgnoreLineSeparators(String reason, String
actual, String exp) {
+ String actualJoined =
actual.lines().collect(joining(System.lineSeparator()));
+ String expJoined =
exp.lines().collect(joining(System.lineSeparator()));
+ assertThat(reason, actualJoined, is(expJoined));
}
/**
@@ -206,9 +221,52 @@ public abstract class CliCommandTestBase extends
BaseIgniteAbstractTest {
T call = registerMockCall(callClass);
// Recreate the CommandLine object so that the registered mocks are
available to this command.
createCommand();
+
execute(command + " " + parameters);
+ assertAll(
+ this::assertExitCodeIsZero,
+ this::assertErrOutputIsEmpty
+ );
+
IT callInput = verifyCallInput(call, callInputClass);
- assertEquals(expected, inputTransformer.apply(callInput));
+ assertThat(inputTransformer.apply(callInput), is(expected));
+ }
+
+ /**
+ * Runs the command with the mock call and verifies that the call was
executed with the expected input.
+ *
+ * @param command Command string.
+ * @param callFactoryClass Call factory class.
+ * @param callClass Call class.
+ * @param callInputClass Call input class.
+ * @param inputTransformer Function which transforms the call input to
string.
+ * @param parameters Command arguments.
+ * @param expected Expected call input string.
+ * @param <IT> Input for the call.
+ * @param <OT> Output of the call.
+ * @param <T> Call type.
+ */
+ protected <IT extends CallInput, OT, T extends AsyncCall<IT, OT>, FT
extends AsyncCallFactory<IT, OT>> void checkParametersAsync(
+ String command,
+ Class<FT> callFactoryClass,
+ Class<T> callClass,
+ Class<IT> callInputClass,
+ Function<IT, String> inputTransformer,
+ String parameters,
+ String expected
+ ) {
+ AsyncCall<IT, OT> call = registerMockCallAsync(callFactoryClass,
callClass);
+ // Recreate the CommandLine object so that the registered mocks are
available to this command.
+ createCommand();
+
+ execute(command + " " + parameters);
+ assertAll(
+ this::assertExitCodeIsZero,
+ this::assertErrOutputIsEmpty
+ );
+
+ IT callInput = verifyCallInputAsync(call, callInputClass);
+ assertThat(inputTransformer.apply(callInput), is(expected));
}
/**
@@ -227,6 +285,29 @@ public abstract class CliCommandTestBase extends
BaseIgniteAbstractTest {
return mock;
}
+ /**
+ * Registers mock async call factory of the specified class into the
Micronaut's context. Mock factory creates mock calls. Mock call
+ * returns empty output when executed.
+ *
+ * @param callFactoryClass Call class.
+ * @param <IT> Input for the call.
+ * @param <OT> Output of the call.
+ * @param <FT> Call factory type.
+ * @param <T> Call type.
+ * @return Created mock.
+ */
+ private <IT extends CallInput, OT, T extends AsyncCall<IT, OT>, FT extends
AsyncCallFactory<IT, OT>>
+ T registerMockCallAsync(Class<FT> callFactoryClass, Class<T>
callClass) {
+ FT mockCallFactory = mock(callFactoryClass);
+ context.registerSingleton(mockCallFactory);
+
+ T mockCall = mock(callClass);
+
when(mockCall.execute(any())).thenReturn(completedFuture(DefaultCallOutput.empty()));
+
+ when(mockCallFactory.create(any())).thenReturn(mockCall);
+ return mockCall;
+ }
+
/**
* Verifies that the call was executed and returns its input.
*
@@ -242,4 +323,20 @@ public abstract class CliCommandTestBase extends
BaseIgniteAbstractTest {
verify(call).execute(captor.capture());
return captor.getValue();
}
+
+ /**
+ * Verifies that the async call was executed and returns its input.
+ *
+ * @param call Call mock.
+ * @param inputClass Call input class.
+ * @param <IT> Input for the call.
+ * @param <OT> Output of the call.
+ * @param <T> Call type.
+ * @return Call input.
+ */
+ private static <IT extends CallInput, OT, T extends AsyncCall<IT, OT>> IT
verifyCallInputAsync(T call, Class<IT> inputClass) {
+ ArgumentCaptor<IT> captor = ArgumentCaptor.forClass(inputClass);
+ verify(call).execute(captor.capture());
+ return captor.getValue();
+ }
}
diff --git
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/ProfileMixinTest.java
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/ProfileMixinTest.java
index e1f8bcfa1d0..ff1cf433df8 100644
---
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/ProfileMixinTest.java
+++
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/ProfileMixinTest.java
@@ -19,14 +19,22 @@ package org.apache.ignite.internal.cli.commands;
import static org.junit.jupiter.params.provider.Arguments.arguments;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.function.Function;
import java.util.stream.Stream;
import org.apache.ignite.internal.cli.call.cluster.ClusterInitCall;
+import org.apache.ignite.internal.cli.call.cluster.ClusterInitCallFactory;
import org.apache.ignite.internal.cli.call.cluster.ClusterInitCallInput;
import org.apache.ignite.internal.cli.call.cluster.status.ClusterStatusCall;
import
org.apache.ignite.internal.cli.call.cluster.topology.LogicalTopologyCall;
import
org.apache.ignite.internal.cli.call.cluster.topology.PhysicalTopologyCall;
import org.apache.ignite.internal.cli.call.cluster.unit.ClusterListUnitCall;
+import org.apache.ignite.internal.cli.call.cluster.unit.DeployUnitCall;
+import org.apache.ignite.internal.cli.call.cluster.unit.DeployUnitCallFactory;
+import org.apache.ignite.internal.cli.call.cluster.unit.DeployUnitCallInput;
import org.apache.ignite.internal.cli.call.cluster.unit.UndeployUnitCall;
import org.apache.ignite.internal.cli.call.cluster.unit.UndeployUnitCallInput;
import org.apache.ignite.internal.cli.call.configuration.ClusterConfigShowCall;
@@ -46,10 +54,16 @@ import
org.apache.ignite.internal.cli.call.recovery.restart.RestartPartitionsCal
import org.apache.ignite.internal.cli.call.recovery.states.PartitionStatesCall;
import
org.apache.ignite.internal.cli.call.recovery.states.PartitionStatesCallInput;
import org.apache.ignite.internal.cli.call.unit.ListUnitCallInput;
+import org.apache.ignite.internal.cli.core.call.AsyncCall;
+import org.apache.ignite.internal.cli.core.call.AsyncCallFactory;
import org.apache.ignite.internal.cli.core.call.Call;
import org.apache.ignite.internal.cli.core.call.CallInput;
import org.apache.ignite.internal.cli.core.call.UrlCallInput;
+import org.apache.ignite.internal.testframework.WorkDirectory;
+import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -57,6 +71,8 @@ import org.junit.jupiter.params.provider.MethodSource;
/**
* Test for --profile override for --url options.
*/
+@MicronautTest(resolveParameters = false)
+@ExtendWith(WorkDirectoryExtension.class)
public class ProfileMixinTest extends CliCommandTestBase {
/**
* Cluster URL from default profile in integration_tests.ini.
@@ -73,6 +89,16 @@ public class ProfileMixinTest extends CliCommandTestBase {
*/
private static final String URL_FROM_CMD = "http://localhost:10302";
+ @WorkDirectory
+ private static Path WORK_DIR;
+
+ private static String TEMP_FILE_PATH;
+
+ @BeforeAll
+ public static void createTempFile() throws IOException {
+ TEMP_FILE_PATH =
Files.createFile(WORK_DIR.resolve("temp.txt")).toString();
+ }
+
@ParameterizedTest
@DisplayName("Should take URL from default profile")
@MethodSource("allCallsProvider")
@@ -85,6 +111,19 @@ public class ProfileMixinTest extends CliCommandTestBase {
checkParameters(command, callClass, callInputClass, urlSupplier, "",
DEFAULT_URL);
}
+ @ParameterizedTest
+ @DisplayName("Should take URL from default profile")
+ @MethodSource("allAsyncCallsProvider")
+ <IT extends CallInput, OT, T extends AsyncCall<IT, OT>, FT extends
AsyncCallFactory<IT, OT>> void defaultUrlAsync(
+ String command,
+ Class<FT> callFactoryClass,
+ Class<T> callClass,
+ Class<IT> callInputClass,
+ Function<IT, String> urlSupplier
+ ) {
+ checkParametersAsync(command, callFactoryClass, callClass,
callInputClass, urlSupplier, "", DEFAULT_URL);
+ }
+
@ParameterizedTest
@DisplayName("Should take URL from specified profile")
@MethodSource("allCallsProvider")
@@ -97,6 +136,19 @@ public class ProfileMixinTest extends CliCommandTestBase {
checkParameters(command, callClass, callInputClass, urlSupplier,
"--profile test", URL_FROM_PROFILE);
}
+ @ParameterizedTest
+ @DisplayName("Should take URL from specified profile")
+ @MethodSource("allAsyncCallsProvider")
+ <IT extends CallInput, OT, T extends AsyncCall<IT, OT>, FT extends
AsyncCallFactory<IT, OT>> void profileUrlAsync(
+ String command,
+ Class<FT> callFactoryClass,
+ Class<T> callClass,
+ Class<IT> callInputClass,
+ Function<IT, String> urlSupplier
+ ) {
+ checkParametersAsync(command, callFactoryClass, callClass,
callInputClass, urlSupplier, "--profile test", URL_FROM_PROFILE);
+ }
+
@ParameterizedTest
@DisplayName("Should take node URL from command line")
@MethodSource("nodeCallsProvider")
@@ -121,6 +173,19 @@ public class ProfileMixinTest extends CliCommandTestBase {
checkParameters(command, callClass, callInputClass, urlSupplier,
"--url " + URL_FROM_CMD, URL_FROM_CMD);
}
+ @ParameterizedTest
+ @DisplayName("Should take cluster endpoint URL from command line")
+ @MethodSource("clusterAsyncCallsProvider")
+ <IT extends CallInput, OT, T extends AsyncCall<IT, OT>, FT extends
AsyncCallFactory<IT, OT>> void commandClusterUrlAsync(
+ String command,
+ Class<FT> callFactoryClass,
+ Class<T> callClass,
+ Class<IT> callInputClass,
+ Function<IT, String> urlSupplier
+ ) {
+ checkParametersAsync(command, callFactoryClass, callClass,
callInputClass, urlSupplier, "--url " + URL_FROM_CMD, URL_FROM_CMD);
+ }
+
@ParameterizedTest
@DisplayName("Node URL from command line should override specified
profile")
@MethodSource("nodeCallsProvider")
@@ -145,6 +210,21 @@ public class ProfileMixinTest extends CliCommandTestBase {
checkParameters(command, callClass, callInputClass, urlSupplier,
"--profile test --url " + URL_FROM_CMD, URL_FROM_CMD);
}
+ @ParameterizedTest
+ @DisplayName("Cluster endpoint URL from command line should override
specified profile")
+ @MethodSource("clusterAsyncCallsProvider")
+ <IT extends CallInput, OT, T extends AsyncCall<IT, OT>, FT extends
AsyncCallFactory<IT, OT>>
+ void commandClusterUrlOverridesProfileAsync(
+ String command,
+ Class<FT> callFactoryClass,
+ Class<T> callClass,
+ Class<IT> callInputClass,
+ Function<IT, String> urlSupplier
+ ) {
+ checkParametersAsync(command, callFactoryClass, callClass,
callInputClass, urlSupplier, "--profile test --url " + URL_FROM_CMD,
+ URL_FROM_CMD);
+ }
+
private static Stream<Arguments> nodeCallsProvider() {
return Stream.of(
arguments(
@@ -188,12 +268,6 @@ public class ProfileMixinTest extends CliCommandTestBase {
ClusterConfigUpdateCallInput.class,
(Function<ClusterConfigUpdateCallInput, String>)
ClusterConfigUpdateCallInput::getClusterUrl
),
- arguments(
- "cluster init --name cluster --metastorage-group node",
- ClusterInitCall.class,
- ClusterInitCallInput.class,
- (Function<ClusterInitCallInput, String>)
ClusterInitCallInput::getClusterUrl
- ),
arguments(
"cluster topology physical",
PhysicalTopologyCall.class,
@@ -212,13 +286,6 @@ public class ProfileMixinTest extends CliCommandTestBase {
UrlCallInput.class,
(Function<UrlCallInput, String>) UrlCallInput::getUrl
),
- // Doesn't work because this command is special - it uses
AsyncCall and call factory
- // arguments(
- // "cluster unit deploy",
- // DeployUnitCall.class,
- // DeployUnitCallInput.class,
- // (Function<DeployUnitCallInput, String>)
DeployUnitCallInput::clusterUrl
- // ),
arguments(
"cluster unit list",
ClusterListUnitCall.class,
@@ -252,10 +319,33 @@ public class ProfileMixinTest extends CliCommandTestBase {
);
}
+ private static Stream<Arguments> clusterAsyncCallsProvider() {
+ return Stream.of(
+ arguments(
+ "cluster init --name cluster --metastorage-group node",
+ ClusterInitCallFactory.class,
+ ClusterInitCall.class,
+ ClusterInitCallInput.class,
+ (Function<ClusterInitCallInput, String>)
ClusterInitCallInput::getClusterUrl
+ ),
+ arguments(
+ "cluster unit deploy foo --version=1 --path=" +
TEMP_FILE_PATH,
+ DeployUnitCallFactory.class,
+ DeployUnitCall.class,
+ DeployUnitCallInput.class,
+ (Function<DeployUnitCallInput, String>)
DeployUnitCallInput::clusterUrl
+ )
+ );
+ }
+
private static Stream<Arguments> allCallsProvider() {
return Stream.concat(nodeCallsProvider(), clusterCallsProvider());
}
+ private static Stream<Arguments> allAsyncCallsProvider() {
+ return clusterAsyncCallsProvider();
+ }
+
@Override
protected Class<?> getCommandClass() {
return TopLevelCliCommand.class;
diff --git
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/UrlOptionsNegativeTest.java
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/UrlOptionsNegativeTest.java
index aea7b052219..7904bdbb24f 100644
---
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/UrlOptionsNegativeTest.java
+++
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/UrlOptionsNegativeTest.java
@@ -20,9 +20,12 @@ package org.apache.ignite.internal.cli.commands;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.params.provider.Arguments.arguments;
+import static org.mockito.Mockito.mock;
import io.micronaut.configuration.picocli.MicronautFactory;
import io.micronaut.context.ApplicationContext;
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.context.annotation.Replaces;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import java.io.IOException;
@@ -70,8 +73,9 @@ import
org.apache.ignite.internal.cli.commands.node.status.NodeStatusCommand;
import
org.apache.ignite.internal.cli.commands.node.status.NodeStatusReplCommand;
import org.apache.ignite.internal.cli.commands.node.unit.NodeUnitListCommand;
import
org.apache.ignite.internal.cli.commands.node.unit.NodeUnitListReplCommand;
+import org.apache.ignite.internal.cli.core.repl.ConnectionHeartBeat;
import
org.apache.ignite.internal.cli.core.repl.context.CommandLineContextProvider;
-import org.apache.ignite.internal.cli.core.repl.registry.NodeNameRegistry;
+import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
import org.apache.ignite.internal.testframework.WorkDirectory;
import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
import org.junit.jupiter.api.BeforeAll;
@@ -88,7 +92,7 @@ import picocli.CommandLine;
*/
@MicronautTest
@ExtendWith(WorkDirectoryExtension.class)
-public class UrlOptionsNegativeTest {
+public class UrlOptionsNegativeTest extends BaseIgniteAbstractTest {
private static final String NODE_URL = "http://localhost:10300";
private static final String NODE_URL_OPTION = "--url=";
@@ -107,9 +111,6 @@ public class UrlOptionsNegativeTest {
@Inject
TestConfigManagerProvider configManagerProvider;
- @Inject
- NodeNameRegistry nodeNameRegistry;
-
@WorkDirectory
protected static Path WORK_DIR;
@@ -139,7 +140,7 @@ public class UrlOptionsNegativeTest {
exitCode = cmd.execute(options.toArray(new String[0]));
}
- static List<Arguments> cmdClassAndOptionsProvider() {
+ private static List<Arguments> cmdClassAndOptionsProvider() {
return List.of(
arguments(NodeConfigShowCommand.class, NODE_URL_OPTION,
List.of()),
arguments(NodeConfigUpdateCommand.class, NODE_URL_OPTION,
List.of("{key: value}")),
@@ -165,7 +166,7 @@ public class UrlOptionsNegativeTest {
);
}
- static List<Arguments> cmdReplClassAndOptionsProvider() {
+ private static List<Arguments> cmdReplClassAndOptionsProvider() {
return List.of(
arguments(NodeConfigShowReplCommand.class, NODE_URL_OPTION,
List.of()),
arguments(NodeConfigUpdateReplCommand.class, NODE_URL_OPTION,
List.of("{key: value}")),
@@ -229,7 +230,6 @@ public class UrlOptionsNegativeTest {
assertAll(
this::assertExitCodeIsFailure,
- this::assertOutputIsEmpty,
() -> assertErrOutputIs(
"Unknown host: http://no-such-host.com" +
System.lineSeparator())
);
@@ -243,7 +243,6 @@ public class UrlOptionsNegativeTest {
assertAll(
this::assertExitCodeIsFailure,
- this::assertOutputIsEmpty,
() -> assertErrOutputIs("Node unavailable" +
System.lineSeparator()
+ "Could not connect to node with URL " + NODE_URL +
System.lineSeparator())
);
@@ -283,10 +282,7 @@ public class UrlOptionsNegativeTest {
void invalidUrlRepl(Class<?> cmdClass, String urlOptionName, List<String>
additionalOptions) {
execute(cmdClass, urlOptionName, "http://no-such-host.com",
additionalOptions);
- assertAll(
- this::assertOutputIsEmpty,
- () -> assertErrOutputIs("Unknown host:
http://no-such-host.com" + System.lineSeparator())
- );
+ assertErrOutputIs("Unknown host: http://no-such-host.com" +
System.lineSeparator());
}
@ParameterizedTest
@@ -295,11 +291,8 @@ public class UrlOptionsNegativeTest {
void connectErrorRepl(Class<?> cmdClass, String urlOptionName,
List<String> additionalOptions) {
execute(cmdClass, urlOptionName, NODE_URL, additionalOptions);
- assertAll(
- this::assertOutputIsEmpty,
- () -> assertErrOutputIs("Node unavailable" +
System.lineSeparator()
- + "Could not connect to node with URL " + NODE_URL +
System.lineSeparator())
- );
+ assertErrOutputIs("Node unavailable" + System.lineSeparator()
+ + "Could not connect to node with URL " + NODE_URL +
System.lineSeparator());
}
@Test
@@ -344,4 +337,9 @@ public class UrlOptionsNegativeTest {
.contains(expectedErrOutput);
}
+ @Bean
+ @Replaces(ConnectionHeartBeat.class)
+ public static ConnectionHeartBeat connectionHeartBeat() {
+ return mock(ConnectionHeartBeat.class);
+ }
}
diff --git
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/cluster/ClusterInitReplTest.java
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/cluster/ClusterInitReplTest.java
index 18f900e6857..cd25c036dea 100644
---
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/cluster/ClusterInitReplTest.java
+++
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/cluster/ClusterInitReplTest.java
@@ -17,7 +17,12 @@
package org.apache.ignite.internal.cli.commands.cluster;
+import static org.mockito.Mockito.mock;
+
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.context.annotation.Replaces;
import
org.apache.ignite.internal.cli.commands.cluster.init.ClusterInitReplCommand;
+import org.apache.ignite.internal.cli.core.repl.ConnectionHeartBeat;
import org.junit.jupiter.api.DisplayName;
/** Tests "cluster init" command in REPL mode. */
@@ -32,4 +37,10 @@ public class ClusterInitReplTest extends ClusterInitTest {
protected int errorExitCode() {
return 0;
}
+
+ @Bean
+ @Replaces(ConnectionHeartBeat.class)
+ public static ConnectionHeartBeat connectionHeartBeat() {
+ return mock(ConnectionHeartBeat.class);
+ }
}
diff --git
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/cluster/ClusterInitTest.java
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/cluster/ClusterInitTest.java
index 7c45eda673d..2fd16bcb8f6 100644
---
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/cluster/ClusterInitTest.java
+++
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/cluster/ClusterInitTest.java
@@ -85,7 +85,7 @@ class ClusterInitTest extends IgniteCliInterfaceTestBase {
"--name", "cluster"
);
- assertSuccessfulOutputIs("Cluster was initialized successfully");
+ assertSuccessfulOutputContains("Cluster was initialized
successfully.");
}
@Test
@@ -131,7 +131,7 @@ class ClusterInitTest extends IgniteCliInterfaceTestBase {
"--name", "cluster"
);
- assertSuccessfulOutputIs("Cluster was initialized successfully");
+ assertSuccessfulOutputContains("Cluster was initialized
successfully.");
}
@Test
@@ -155,7 +155,7 @@ class ClusterInitTest extends IgniteCliInterfaceTestBase {
"--name", "cluster"
);
- assertSuccessfulOutputIs("Cluster was initialized successfully");
+ assertSuccessfulOutputContains("Cluster was initialized
successfully.");
}
@Test
@@ -197,7 +197,7 @@ class ClusterInitTest extends IgniteCliInterfaceTestBase {
"--config-files", clusterConfigurationFile.toString()
);
- assertSuccessfulOutputIs("Cluster was initialized successfully");
+ assertSuccessfulOutputContains("Cluster was initialized
successfully.");
}
@Test
@@ -221,7 +221,7 @@ class ClusterInitTest extends IgniteCliInterfaceTestBase {
assertAll(
this::assertExitCodeIsError,
- this::assertOutputIsEmpty,
+ () -> assertOutputContains("Initializing"), // Spinner output
() -> assertErrOutputIs("Oops")
);
}
@@ -249,7 +249,7 @@ class ClusterInitTest extends IgniteCliInterfaceTestBase {
"--name", "cluster"
);
- assertSuccessfulOutputIs("Cluster was initialized successfully");
+ assertSuccessfulOutputContains("Cluster was initialized
successfully.");
}
@Test
@@ -263,7 +263,7 @@ class ClusterInitTest extends IgniteCliInterfaceTestBase {
"--name", "cluster"
);
- assertSuccessfulOutputIs("Cluster was initialized successfully");
+ assertSuccessfulOutputContains("Cluster was initialized
successfully.");
}
@Test
@@ -338,7 +338,7 @@ class ClusterInitTest extends IgniteCliInterfaceTestBase {
"--config-files", String.join(",", clusterConfigurationFile1,
clusterConfigurationFile2)
);
- assertSuccessfulOutputIs("Cluster was initialized successfully");
+ assertSuccessfulOutputContains("Cluster was initialized
successfully.");
}
private static String escapedJson(String configuration) {
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/unit/DeployUnitCallFactory.java
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/call/SpinnerRendererTest.java
similarity index 53%
copy from
modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/unit/DeployUnitCallFactory.java
copy to
modules/cli/src/test/java/org/apache/ignite/internal/cli/core/call/SpinnerRendererTest.java
index 5e208d1b665..18e2badf0ba 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/cluster/unit/DeployUnitCallFactory.java
+++
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/core/call/SpinnerRendererTest.java
@@ -15,23 +15,27 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.cli.call.cluster.unit;
+package org.apache.ignite.internal.cli.core.call;
-import jakarta.inject.Singleton;
-import org.apache.ignite.internal.cli.core.call.ProgressTracker;
-import org.apache.ignite.internal.cli.core.rest.ApiClientFactory;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.emptyString;
+import static org.hamcrest.Matchers.is;
-/** Factory for {@link DeployUnitCall}. */
-@Singleton
-public class DeployUnitCallFactory {
+import org.junit.jupiter.api.Test;
- private final ApiClientFactory factory;
+class SpinnerRendererTest {
- public DeployUnitCallFactory(ApiClientFactory factory) {
- this.factory = factory;
- }
+ @Test
+ void render() {
+ SpinnerRenderer renderer = new SpinnerRenderer("prefix");
+
+ assertThat(renderer.render(0), is(emptyString()));
+
+ assertThat(renderer.render(11), is("prefix. \b\b"));
+ assertThat(renderer.render(11), is("prefix.. \b"));
+ assertThat(renderer.render(11), is("prefix..."));
+ assertThat(renderer.render(11), is("prefix. \b\b"));
- public DeployUnitCall create(ProgressTracker tracker) {
- return new DeployUnitCall(tracker, factory);
+ assertThat(renderer.render(1), is("p"));
}
}
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 e0113fce6d4..009bbcb5370 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
@@ -50,18 +50,23 @@ public class ClusterManagementController implements
ClusterManagementApi, Resour
private ClusterManagementGroupManager clusterManagementGroupManager;
+ private JoinFutureProvider joinFutureProvider;
+
/**
* Cluster management controller constructor.
*
- * @param clusterInitializer cluster initializer.
- * @param clusterManagementGroupManager cluster management group manager.
+ * @param clusterInitializer Cluster initializer.
+ * @param clusterManagementGroupManager Cluster management group manager.
+ * @param joinFutureProvider Node join future provider.
*/
public ClusterManagementController(
ClusterInitializer clusterInitializer,
- ClusterManagementGroupManager clusterManagementGroupManager
+ ClusterManagementGroupManager clusterManagementGroupManager,
+ JoinFutureProvider joinFutureProvider
) {
this.clusterInitializer = clusterInitializer;
this.clusterManagementGroupManager = clusterManagementGroupManager;
+ this.joinFutureProvider = joinFutureProvider;
}
@Override
@@ -80,13 +85,18 @@ public class ClusterManagementController implements
ClusterManagementApi, Resour
);
return clusterInitializer.initCluster(
- initCommand.metaStorageNodes(),
- initCommand.cmgNodes(),
- initCommand.clusterName(),
- initCommand.clusterConfiguration()
- ).exceptionally(ex -> {
- throw mapException(ex);
- });
+ initCommand.metaStorageNodes(),
+ initCommand.cmgNodes(),
+ initCommand.clusterName(),
+ initCommand.clusterConfiguration()
+ )
+ .thenCompose(unused -> joinFutureProvider.joinFuture())
+ .handle((unused, ex) -> {
+ if (ex != null) {
+ throw mapException(ex);
+ }
+ return null;
+ });
}
private static ClusterState mapClusterState(@Nullable
org.apache.ignite.internal.cluster.management.ClusterState clusterState) {
@@ -124,5 +134,6 @@ public class ClusterManagementController implements
ClusterManagementApi, Resour
public void cleanResources() {
clusterInitializer = null;
clusterManagementGroupManager = null;
+ joinFutureProvider = null;
}
}
diff --git
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/cluster/ClusterManagementRestFactory.java
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/cluster/ClusterManagementRestFactory.java
index 0fb47d94c15..7636eafc304 100644
---
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/cluster/ClusterManagementRestFactory.java
+++
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/cluster/ClusterManagementRestFactory.java
@@ -37,15 +37,19 @@ public class ClusterManagementRestFactory implements
RestFactory {
private ClusterManagementGroupManager cmgManager;
+ private JoinFutureProvider joinFutureProvider;
+
/** Constructor. */
public ClusterManagementRestFactory(
ClusterService clusterService,
ClusterInitializer clusterInitializer,
- ClusterManagementGroupManager cmgManager
+ ClusterManagementGroupManager cmgManager,
+ JoinFutureProvider joinFutureProvider
) {
this.clusterService = clusterService;
this.clusterInitializer = clusterInitializer;
this.cmgManager = cmgManager;
+ this.joinFutureProvider = joinFutureProvider;
}
@Bean
@@ -66,10 +70,17 @@ public class ClusterManagementRestFactory implements
RestFactory {
return clusterService.topologyService();
}
+ @Bean
+ @Singleton
+ public JoinFutureProvider joinFutureProvider() {
+ return joinFutureProvider;
+ }
+
@Override
public void cleanResources() {
clusterService = null;
clusterInitializer = null;
cmgManager = null;
+ joinFutureProvider = null;
}
}
diff --git
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/cluster/ClusterInitReplTest.java
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/cluster/JoinFutureProvider.java
similarity index 61%
copy from
modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/cluster/ClusterInitReplTest.java
copy to
modules/rest/src/main/java/org/apache/ignite/internal/rest/cluster/JoinFutureProvider.java
index 18f900e6857..d9652e56e57 100644
---
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/cluster/ClusterInitReplTest.java
+++
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/cluster/JoinFutureProvider.java
@@ -15,21 +15,15 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.cli.commands.cluster;
+package org.apache.ignite.internal.rest.cluster;
-import
org.apache.ignite.internal.cli.commands.cluster.init.ClusterInitReplCommand;
-import org.junit.jupiter.api.DisplayName;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.Ignite;
-/** Tests "cluster init" command in REPL mode. */
-@DisplayName("cluster init repl")
-public class ClusterInitReplTest extends ClusterInitTest {
- @Override
- protected Class<?> getCommandClass() {
- return ClusterInitReplCommand.class;
- }
-
- @Override
- protected int errorExitCode() {
- return 0;
- }
+/**
+ * Provides node join future for the rest components.
+ */
+@FunctionalInterface
+public interface JoinFutureProvider {
+ CompletableFuture<Ignite> joinFuture();
}
diff --git
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/RestComponentTest.java
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/RestComponentTest.java
index 5ec36a173c8..62ff1c37f1f 100644
---
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/RestComponentTest.java
+++
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/RestComponentTest.java
@@ -49,7 +49,6 @@ import
org.apache.ignite.internal.network.configuration.MulticastNodeFinderConfi
import
org.apache.ignite.internal.network.configuration.NetworkExtensionConfigurationSchema;
import
org.apache.ignite.internal.network.configuration.StaticNodeFinderConfigurationSchema;
import
org.apache.ignite.internal.rest.authentication.AuthenticationProviderFactory;
-import org.apache.ignite.internal.rest.cluster.ClusterManagementRestFactory;
import org.apache.ignite.internal.rest.configuration.PresentationsFactory;
import org.apache.ignite.internal.rest.configuration.RestConfiguration;
import
org.apache.ignite.internal.rest.configuration.RestExtensionConfiguration;
@@ -107,13 +106,11 @@ public class RestComponentTest extends
BaseIgniteAbstractTest {
configurationManager,
mock(ConfigurationManager.class)
);
- Supplier<RestFactory> clusterManagementRestFactory = () -> new
ClusterManagementRestFactory(null, null, cmg);
Supplier<RestFactory> restManagerFactory = () -> new
RestManagerFactory(restManager);
restComponent = new RestComponent(
List.of(restPresentationFactory,
authProviderFactory,
- clusterManagementRestFactory,
restManagerFactory),
restManager,
restConfiguration
diff --git
a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
index c5200c53d69..4d4a188c36b 100644
---
a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
+++
b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
@@ -515,6 +515,9 @@ public class IgniteImpl implements Ignite {
private final PartitionModificationCounterFactory
partitionModificationCounterFactory;
+ /** Future that completes when the node has joined the cluster. */
+ private final CompletableFuture<Ignite> joinFuture = new
CompletableFuture<>();
+
/**
* The Constructor.
*
@@ -1404,7 +1407,12 @@ public class IgniteImpl implements Ignite {
private RestComponent createRestComponent(String name) {
RestManager restManager = new RestManager();
Supplier<RestFactory> presentationsFactory = () -> new
PresentationsFactory(nodeCfgMgr, clusterCfgMgr);
- Supplier<RestFactory> clusterManagementRestFactory = () -> new
ClusterManagementRestFactory(clusterSvc, clusterInitializer, cmgMgr);
+ Supplier<RestFactory> clusterManagementRestFactory = () -> new
ClusterManagementRestFactory(
+ clusterSvc,
+ clusterInitializer,
+ cmgMgr,
+ () -> joinFuture
+ );
Supplier<RestFactory> nodeManagementRestFactory = () -> new
NodeManagementRestFactory(lifecycleManager, () -> name,
new JdbcPortProviderImpl(nodeCfgMgr.configurationRegistry()));
Supplier<RestFactory> metricRestFactory = () -> new
MetricRestFactory(metricManager, metricMessaging);
@@ -1553,7 +1561,7 @@ public class IgniteImpl implements Ignite {
);
ComponentContext componentContext = new ComponentContext(joinExecutor);
- return cmgMgr.joinFuture()
+ cmgMgr.joinFuture()
.thenComposeAsync(unused -> cmgMgr.clusterState(),
joinExecutor)
.thenAcceptAsync(clusterState -> {
this.clusterState = clusterState;
@@ -1668,7 +1676,16 @@ public class IgniteImpl implements Ignite {
return (Ignite) this;
}, joinExecutor)
// Moving to the common pool on purpose to close the join pool
and proceed with user's code in the common pool.
- .whenCompleteAsync((res, ex) -> joinExecutor.shutdownNow());
+ .whenCompleteAsync((res, ex) -> {
+ joinExecutor.shutdownNow();
+ if (ex != null) {
+ joinFuture.completeExceptionally(ex);
+ } else {
+ joinFuture.complete(res);
+ }
+ });
+
+ return joinFuture;
}
private CompletableFuture<Void> awaitSelfInLocalLogicalTopology() {