You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sd...@apache.org on 2022/07/26 07:51:20 UTC
[ignite-3] branch main updated: IGNITE-17092: Implement topology command
This is an automated email from the ASF dual-hosted git repository.
sdanilov 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 7fffd484b IGNITE-17092: Implement topology command
7fffd484b is described below
commit 7fffd484b3d55ac7428d970e051bba1fd9d92e20
Author: Aleksandr Pakhomov <ap...@gmail.com>
AuthorDate: Thu Jun 23 16:53:34 2022 +0300
IGNITE-17092: Implement topology command
New commands are added: `ignite cluster topology physical`
and `ignite cluster topology logical`. To support these
commands REST endpoints added as well.
Also, added REST integration tests to runner and cli modules.
---
.../org/apache/ignite/cli/IntegrationTestBase.java | 20 +--
...ava => CallInitializedIntegrationTestBase.java} | 21 ++-
.../configuration/ItShowConfigurationCallTest.java | 4 +-
.../ItUpdateConfigurationCallTest.java | 4 +-
... CliCommandTestInitializedIntegrationBase.java} | 34 ++--
...iCommandTestNotInitializedIntegrationBase.java} | 35 +++-
...ItClusterStatusReplCommandInitializedTest.java} | 28 ++-
...lusterStatusReplCommandNotInitializedTest.java} | 15 +-
.../ItTopologyCommandInitializedClusterTest.java} | 33 ++--
...tTopologyCommandNotInitializedClusterTest.java} | 34 ++--
.../configuration/ItConfigCommandTest.java | 4 +-
.../cli/commands/connect/ItConnectCommandTest.java | 4 +-
.../ignite/cli/commands/sql/ItSqlCommandTest.java | 4 +-
.../cli/commands/version/ItVersionCommandTest.java | 4 +-
.../ignite/rest/ItGeneratedRestClientTest.java | 28 ++-
.../cli/call/cluster/status/ClusterStatusCall.java | 29 ++--
.../call/cluster/topology/LogicalTopologyCall.java | 49 ++++++
.../cluster/topology/PhysicalTopologyCall.java | 49 ++++++
.../call/cluster/topology/TopologyCallInput.java | 67 ++++++++
.../call/cluster/topology/TopologyCallOutput.java | 70 ++++++++
.../cli/commands/cluster/ClusterCommand.java | 6 +-
.../cli/commands/cluster/ClusterReplCommand.java | 4 +-
.../cli/commands/decorators/TopologyDecorator.java | 51 ++++++
.../node/status/NodeStatusReplSubCommand.java | 2 +-
.../commands/node/status/NodeStatusSubCommand.java | 2 +-
.../LogicalTopologyReplSubCommand.java} | 48 +++---
.../LogicalTopologySubCommand.java} | 31 ++--
.../PhysicalTopologyReplSubCommand.java} | 46 +++--
.../PhysicalTopologySubCommand.java} | 31 ++--
.../cli/commands/topology/TopologyCommand.java | 16 +-
.../commands/topology/TopologyReplCommand.java} | 20 ++-
.../rest/ItClusterManagementControllerTest.java | 32 +---
.../cluster/management/rest/RestTestBase.java | 94 ++++++++++
.../rest/ClusterManagementController.java | 3 +-
.../rest/ClusterManagementRestFactory.java | 18 +-
.../management/rest/TopologyController.java | 76 ++++++++
.../rest/api/cluster/ClusterManagementApi.java | 28 ++-
.../internal/rest/api/cluster/ClusterNodeDto.java | 86 ++++++++++
.../rest/api/cluster/NetworkAddressDto.java | 89 ++++++++++
.../internal/rest/api/cluster/TopologyApi.java | 60 +++++++
.../api/configuration/ClusterConfigurationApi.java | 47 +++--
.../api/configuration/NodeConfigurationApi.java | 52 +++---
modules/rest/openapi/openapi.yaml | 70 ++++++++
.../apache/ignite/internal/rest/RestComponent.java | 4 +-
modules/runner/pom.xml | 6 +
.../ignite/internal/rest/AbstractRestTestBase.java | 173 +++++++++++++++++++
.../rest/ItInitializedClusterRestTest.java | 191 +++++++++++++++++++++
.../rest/ItNotInitializedClusterRestTest.java | 157 +++++++++++++++++
parent/pom.xml | 6 +
49 files changed, 1642 insertions(+), 343 deletions(-)
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/IntegrationTestBase.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/IntegrationTestBase.java
index 5dc36c0e1..13f036e3b 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/IntegrationTestBase.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/IntegrationTestBase.java
@@ -57,10 +57,7 @@ import org.apache.ignite.schema.definition.builder.TableDefinitionBuilder;
import org.apache.ignite.table.RecordView;
import org.apache.ignite.table.Table;
import org.apache.ignite.table.Tuple;
-import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -80,6 +77,9 @@ public class IntegrationTestBase extends BaseIgniteAbstractTest {
/** Cluster nodes. */
protected static final List<Ignite> CLUSTER_NODES = new ArrayList<>();
+ /** Futures that is going to be completed when all nodes are started and the cluster is inintialized. */
+ private static List<CompletableFuture<Ignite>> futures = new ArrayList<>();
+
private static final IgniteLogger LOG = Loggers.forClass(IntegrationTestBase.class);
/** Base port number. */
@@ -239,11 +239,10 @@ public class IntegrationTestBase extends BaseIgniteAbstractTest {
*
* @param testInfo Test information oject.
*/
- @BeforeAll
- void startNodes(TestInfo testInfo) throws ExecutionException, InterruptedException {
+ protected void startNodes(TestInfo testInfo) throws ExecutionException, InterruptedException {
String connectNodeAddr = "\"localhost:" + BASE_PORT + '\"';
- List<CompletableFuture<Ignite>> futures = IntStream.range(0, nodes())
+ futures = IntStream.range(0, nodes())
.mapToObj(i -> {
String nodeName = testNodeName(testInfo, i);
@@ -252,9 +251,9 @@ public class IntegrationTestBase extends BaseIgniteAbstractTest {
return IgnitionManager.start(nodeName, config, WORK_DIR.resolve(nodeName));
})
.collect(toList());
+ }
- String metaStorageNodeName = testNodeName(testInfo, 0);
-
+ protected void initializeCluster(String metaStorageNodeName) {
IgnitionManager.init(metaStorageNodeName, List.of(metaStorageNodeName), "cluster");
for (CompletableFuture<Ignite> future : futures) {
@@ -276,8 +275,7 @@ public class IntegrationTestBase extends BaseIgniteAbstractTest {
/**
* After all.
*/
- @AfterAll
- void stopNodes(TestInfo testInfo) throws Exception {
+ protected void stopNodes(TestInfo testInfo) throws Exception {
LOG.info("Start tearDown()");
CLUSTER_NODES.clear();
@@ -326,7 +324,6 @@ public class IntegrationTestBase extends BaseIgniteAbstractTest {
* @param testInfo Test information object.
* @throws Exception If failed.
*/
- @BeforeEach
public void setUp(TestInfo testInfo) throws Exception {
setupBase(testInfo, WORK_DIR);
}
@@ -339,7 +336,6 @@ public class IntegrationTestBase extends BaseIgniteAbstractTest {
@AfterEach
public void tearDown(TestInfo testInfo) {
tearDownBase(testInfo);
- dropAllTables();
}
}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/CallIntegrationTestBase.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/CallInitializedIntegrationTestBase.java
similarity index 52%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/CallIntegrationTestBase.java
copy to modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/CallInitializedIntegrationTestBase.java
index d5c0f37b3..483246698 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/CallIntegrationTestBase.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/CallInitializedIntegrationTestBase.java
@@ -17,10 +17,27 @@
package org.apache.ignite.cli.call;
+import static org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
+
+import java.util.concurrent.ExecutionException;
import org.apache.ignite.cli.IntegrationTestBase;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.TestInfo;
/**
- * Base class for call integration tests. Contains common methods and useful assertions.
+ * Base class for call integration tests that needs initialized ignite cluster. Contains common methods and useful assertions.
*/
-public class CallIntegrationTestBase extends IntegrationTestBase {
+public class CallInitializedIntegrationTestBase extends IntegrationTestBase {
+ @BeforeAll
+ void beforeAll(TestInfo testInfo) throws ExecutionException, InterruptedException {
+ startNodes(testInfo);
+ String metaStorageNodeName = testNodeName(testInfo, 0);
+ initializeCluster(metaStorageNodeName);
+ }
+
+ @AfterAll
+ void afterAll(TestInfo testInfo) throws Exception {
+ stopNodes(testInfo);
+ }
}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItShowConfigurationCallTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItShowConfigurationCallTest.java
index a19f5182c..3b6f67ff9 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItShowConfigurationCallTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItShowConfigurationCallTest.java
@@ -20,7 +20,7 @@ package org.apache.ignite.cli.call.configuration;
import static org.assertj.core.api.Assertions.assertThat;
import jakarta.inject.Inject;
-import org.apache.ignite.cli.call.CallIntegrationTestBase;
+import org.apache.ignite.cli.call.CallInitializedIntegrationTestBase;
import org.apache.ignite.cli.core.call.DefaultCallOutput;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -28,7 +28,7 @@ import org.junit.jupiter.api.Test;
/**
* Tests for {@link NodeConfigShowCall}.
*/
-class ItShowConfigurationCallTest extends CallIntegrationTestBase {
+class ItShowConfigurationCallTest extends CallInitializedIntegrationTestBase {
@Inject
NodeConfigShowCall nodeConfigShowCall;
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItUpdateConfigurationCallTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItUpdateConfigurationCallTest.java
index 46055736b..cc5f8cc8d 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItUpdateConfigurationCallTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItUpdateConfigurationCallTest.java
@@ -20,7 +20,7 @@ package org.apache.ignite.cli.call.configuration;
import static org.assertj.core.api.Assertions.assertThat;
import jakarta.inject.Inject;
-import org.apache.ignite.cli.call.CallIntegrationTestBase;
+import org.apache.ignite.cli.call.CallInitializedIntegrationTestBase;
import org.apache.ignite.cli.core.call.DefaultCallOutput;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -28,7 +28,7 @@ import org.junit.jupiter.api.Test;
/**
* Tests for {@link NodeConfigUpdateCall}.
*/
-public class ItUpdateConfigurationCallTest extends CallIntegrationTestBase {
+public class ItUpdateConfigurationCallTest extends CallInitializedIntegrationTestBase {
@Inject
ClusterConfigUpdateCall updateCall;
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/version/ItVersionCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/CliCommandTestInitializedIntegrationBase.java
similarity index 51%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/version/ItVersionCommandTest.java
copy to modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/CliCommandTestInitializedIntegrationBase.java
index 99ebea901..0933f75ba 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/version/ItVersionCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/CliCommandTestInitializedIntegrationBase.java
@@ -15,27 +15,27 @@
* limitations under the License.
*/
-package org.apache.ignite.cli.commands.version;
+package org.apache.ignite.cli.commands;
-import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
-import org.apache.ignite.cli.commands.CliCommandTestIntegrationBase;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
+import java.util.concurrent.ExecutionException;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.TestInfo;
-class ItVersionCommandTest extends CliCommandTestIntegrationBase {
+/**
+ * Extends {@link CliCommandTestNotInitializedIntegrationBase} and initializes ignite cluster.
+ */
+public class CliCommandTestInitializedIntegrationBase extends CliCommandTestNotInitializedIntegrationBase {
- @Test
- @DisplayName("Should print cli version that is got from pom.xml")
- void printVersion() {
- // When
- execute("--version");
+ @BeforeAll
+ @Override
+ void beforeAll(TestInfo testInfo) throws ExecutionException, InterruptedException {
+ startNodes(testInfo);
+ super.initializeCluster(metaStorageNodeName(testInfo));
+ }
- // Then
- assertAll(
- this::assertExitCodeIsZero,
- this::assertErrOutputIsEmpty,
- () -> assertOutputContains("Apache Ignite CLI ver")
- );
+ protected String metaStorageNodeName(TestInfo testInfo) {
+ return testNodeName(testInfo, 0);
}
}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/CliCommandTestIntegrationBase.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/CliCommandTestNotInitializedIntegrationBase.java
similarity index 83%
rename from modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/CliCommandTestIntegrationBase.java
rename to modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/CliCommandTestNotInitializedIntegrationBase.java
index 7d3a86ce0..4012df058 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/CliCommandTestIntegrationBase.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/CliCommandTestNotInitializedIntegrationBase.java
@@ -24,34 +24,37 @@ import io.micronaut.context.ApplicationContext;
import jakarta.inject.Inject;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.concurrent.ExecutionException;
import org.apache.ignite.cli.IntegrationTestBase;
import org.apache.ignite.cli.commands.cliconfig.TestConfigManagerHelper;
import org.apache.ignite.cli.commands.cliconfig.TestConfigManagerProvider;
import org.apache.ignite.cli.config.ConfigDefaultValueProvider;
import org.apache.ignite.cli.config.ini.IniConfigManager;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo;
import picocli.CommandLine;
/**
- * Integration test base for cli commands. Setup commands, ignite cluster, and provides useful fixtures and assertions.
+ * Integration test base for cli commands. Setup commands, ignite cluster, and provides useful fixtures and assertions. Note: ignite cluster
+ * won't be initialized. If you want to use initialized cluster use {@link CliCommandTestInitializedIntegrationBase}.
*/
-public class CliCommandTestIntegrationBase extends IntegrationTestBase {
+public class CliCommandTestNotInitializedIntegrationBase extends IntegrationTestBase {
/** Correct ignite jdbc url. */
protected static final String JDBC_URL = "jdbc:ignite:thin://127.0.0.1:10800";
-
- @Inject
- private ApplicationContext context;
-
@Inject
ConfigDefaultValueProvider configDefaultValueProvider;
-
@Inject
TestConfigManagerProvider configManagerProvider;
-
+ @Inject
+ private ApplicationContext context;
private CommandLine cmd;
+
private StringWriter sout;
+
private StringWriter serr;
+
private int exitCode = Integer.MIN_VALUE;
/**
@@ -72,6 +75,16 @@ public class CliCommandTestIntegrationBase extends IntegrationTestBase {
cmd.setErr(new PrintWriter(serr));
}
+ @BeforeAll
+ void beforeAll(TestInfo testInfo) throws ExecutionException, InterruptedException {
+ startNodes(testInfo);
+ }
+
+ @AfterAll
+ void afterAll(TestInfo testInfo) throws Exception {
+ stopNodes(testInfo);
+ }
+
protected Class<?> getCommandClass() {
return TopLevelCliCommand.class;
}
@@ -131,4 +144,10 @@ public class CliCommandTestIntegrationBase extends IntegrationTestBase {
.as("Expected command error output to be equal to: " + expectedErrOutput)
.isEqualTo(expectedErrOutput);
}
+
+ protected void assertErrOutputContains(String expectedErrOutput) {
+ assertThat(serr.toString())
+ .as("Expected command error output to contain: " + expectedErrOutput)
+ .contains(expectedErrOutput);
+ }
}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/ItClusterStatusReplCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/status/ItClusterStatusReplCommandInitializedTest.java
similarity index 56%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/ItClusterStatusReplCommandTest.java
copy to modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/status/ItClusterStatusReplCommandInitializedTest.java
index f01917ffa..3891d663a 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/ItClusterStatusReplCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/status/ItClusterStatusReplCommandInitializedTest.java
@@ -15,31 +15,41 @@
* limitations under the License.
*/
-package org.apache.ignite.cli.commands.cluster;
+package org.apache.ignite.cli.commands.cluster.status;
import static org.junit.jupiter.api.Assertions.assertAll;
-import org.apache.ignite.cli.commands.CliCommandTestIntegrationBase;
-import org.apache.ignite.cli.commands.cluster.status.ClusterStatusReplSubCommand;
-import org.junit.jupiter.api.Disabled;
+import org.apache.ignite.cli.commands.CliCommandTestInitializedIntegrationBase;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
/**
- * Tests for {@link ClusterStatusReplSubCommand}.
+ * Tests for {@link ClusterStatusReplSubCommand} for the cluster that is initialized.
*/
-class ItClusterStatusReplCommandTest extends CliCommandTestIntegrationBase {
+class ItClusterStatusReplCommandInitializedTest extends CliCommandTestInitializedIntegrationBase {
+
+ String metaStorageNodeName;
+
+ @BeforeAll
+ void setUpMsNodeName(TestInfo testInfo) {
+ metaStorageNodeName = metaStorageNodeName(testInfo);
+ }
@Test
- @DisplayName("Should print status when valid cluster url is given")
- @Disabled("Use topology call to get the number of nodes https://issues.apache.org/jira/browse/IGNITE-17092")
+ @DisplayName("Should print status when valid cluster url is given but cluster is initialized")
void printStatus() {
execute("cluster", "status", "--cluster-url", NODE_URL);
assertAll(
this::assertExitCodeIsZero,
this::assertErrOutputIsEmpty,
- () -> assertOutputContains("Cluster status:")
+ () -> assertOutputContains("name: cluster"),
+ () -> assertOutputContains("nodes: 3"),
+ () -> assertOutputContains("status: active"),
+ () -> assertOutputContains("cmgNodes: [" + metaStorageNodeName + "]"),
+ () -> assertOutputContains("msNodes: [" + metaStorageNodeName + "]")
);
}
}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/ItClusterStatusReplCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/status/ItClusterStatusReplCommandNotInitializedTest.java
similarity index 69%
rename from modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/ItClusterStatusReplCommandTest.java
rename to modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/status/ItClusterStatusReplCommandNotInitializedTest.java
index f01917ffa..c1f4ab7f0 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/ItClusterStatusReplCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/status/ItClusterStatusReplCommandNotInitializedTest.java
@@ -15,31 +15,28 @@
* limitations under the License.
*/
-package org.apache.ignite.cli.commands.cluster;
+package org.apache.ignite.cli.commands.cluster.status;
import static org.junit.jupiter.api.Assertions.assertAll;
-import org.apache.ignite.cli.commands.CliCommandTestIntegrationBase;
-import org.apache.ignite.cli.commands.cluster.status.ClusterStatusReplSubCommand;
-import org.junit.jupiter.api.Disabled;
+import org.apache.ignite.cli.commands.CliCommandTestNotInitializedIntegrationBase;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
/**
- * Tests for {@link ClusterStatusReplSubCommand}.
+ * Tests for {@link ClusterStatusReplSubCommand} for the cluster that is not initialized.
*/
-class ItClusterStatusReplCommandTest extends CliCommandTestIntegrationBase {
+class ItClusterStatusReplCommandNotInitializedTest extends CliCommandTestNotInitializedIntegrationBase {
@Test
- @DisplayName("Should print status when valid cluster url is given")
- @Disabled("Use topology call to get the number of nodes https://issues.apache.org/jira/browse/IGNITE-17092")
+ @DisplayName("Should print status when valid cluster url is given but cluster is not initialized")
void printStatus() {
execute("cluster", "status", "--cluster-url", NODE_URL);
assertAll(
this::assertExitCodeIsZero,
this::assertErrOutputIsEmpty,
- () -> assertOutputContains("Cluster status:")
+ () -> assertOutputContains("[nodes: 3, status: not initialized]")
);
}
}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/topology/ItTopologyCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/topology/ItTopologyCommandInitializedClusterTest.java
similarity index 56%
copy from modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/topology/ItTopologyCommandTest.java
copy to modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/topology/ItTopologyCommandInitializedClusterTest.java
index 95cca688a..c7c556b8e 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/topology/ItTopologyCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/topology/ItTopologyCommandInitializedClusterTest.java
@@ -15,26 +15,39 @@
* limitations under the License.
*/
-package org.apache.ignite.cli.commands.topology;
+package org.apache.ignite.cli.commands.cluster.topology;
import static org.junit.jupiter.api.Assertions.assertAll;
-import org.apache.ignite.cli.commands.CliCommandTestIntegrationBase;
-import org.junit.jupiter.api.Disabled;
+import org.apache.ignite.cli.commands.CliCommandTestInitializedIntegrationBase;
+import org.apache.ignite.cli.commands.topology.TopologyCommand;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
/**
* Tests for {@link TopologyCommand}.
*/
-@Disabled("https://issues.apache.org/jira/browse/IGNITE-17092")
-class ItTopologyCommandTest extends CliCommandTestIntegrationBase {
+class ItTopologyCommandInitializedClusterTest extends CliCommandTestInitializedIntegrationBase {
@Test
- @DisplayName("Should print topology when valid cluster url is provided")
- void printTopology() {
+ @DisplayName("Should print physical topology when valid cluster url is provided")
+ void printPhysicalTopology() {
// When
- execute("topology", "--cluster-url", NODE_URL);
+ execute("cluster", "topology", "physical", "--cluster-url", NODE_URL);
+
+ // Then
+ assertAll(
+ this::assertExitCodeIsZero,
+ this::assertErrOutputIsEmpty,
+ this::assertOutputIsNotEmpty
+ );
+ }
+
+ @Test
+ @DisplayName("Should print logical topology when valid cluster url is provided but cluster")
+ void printLogicalTopology() {
+ // When
+ execute("cluster", "topology", "logical", "--cluster-url", NODE_URL);
// Then
assertAll(
@@ -42,9 +55,5 @@ class ItTopologyCommandTest extends CliCommandTestIntegrationBase {
this::assertErrOutputIsEmpty,
this::assertOutputIsNotEmpty
);
- // TODO: https://issues.apache.org/jira/browse/IGNITE-17092
- //consistent ID, ID, address, status
- //node 1, e2d4988a-b836-4e7e-a888-2639e6f79ef0, 127.0.0.1, RUNNING
- //node 2, 5cb561fc-1963-4f95-98f8-deb407669a86, 127.0.0.2, RECOVERY
}
}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/topology/ItTopologyCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/topology/ItTopologyCommandNotInitializedClusterTest.java
similarity index 53%
rename from modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/topology/ItTopologyCommandTest.java
rename to modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/topology/ItTopologyCommandNotInitializedClusterTest.java
index 95cca688a..0c660969c 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/topology/ItTopologyCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/topology/ItTopologyCommandNotInitializedClusterTest.java
@@ -15,26 +15,25 @@
* limitations under the License.
*/
-package org.apache.ignite.cli.commands.topology;
+package org.apache.ignite.cli.commands.cluster.topology;
import static org.junit.jupiter.api.Assertions.assertAll;
-import org.apache.ignite.cli.commands.CliCommandTestIntegrationBase;
-import org.junit.jupiter.api.Disabled;
+import org.apache.ignite.cli.commands.CliCommandTestNotInitializedIntegrationBase;
+import org.apache.ignite.cli.commands.topology.TopologyCommand;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
/**
* Tests for {@link TopologyCommand}.
*/
-@Disabled("https://issues.apache.org/jira/browse/IGNITE-17092")
-class ItTopologyCommandTest extends CliCommandTestIntegrationBase {
+class ItTopologyCommandNotInitializedClusterTest extends CliCommandTestNotInitializedIntegrationBase {
@Test
- @DisplayName("Should print topology when valid cluster url is provided")
- void printTopology() {
+ @DisplayName("Should print physical topology when valid cluster url is provided")
+ void printPhysicalTopology() {
// When
- execute("topology", "--cluster-url", NODE_URL);
+ execute("cluster", "topology", "physical", "--cluster-url", NODE_URL);
// Then
assertAll(
@@ -42,9 +41,20 @@ class ItTopologyCommandTest extends CliCommandTestIntegrationBase {
this::assertErrOutputIsEmpty,
this::assertOutputIsNotEmpty
);
- // TODO: https://issues.apache.org/jira/browse/IGNITE-17092
- //consistent ID, ID, address, status
- //node 1, e2d4988a-b836-4e7e-a888-2639e6f79ef0, 127.0.0.1, RUNNING
- //node 2, 5cb561fc-1963-4f95-98f8-deb407669a86, 127.0.0.2, RECOVERY
+ }
+
+ @Test
+ @DisplayName("Should not print logical topology when valid cluster url is provided but cluster is not initialized")
+ void printLogicalTopology() {
+ // When
+ execute("cluster", "topology", "logical", "--cluster-url", NODE_URL);
+
+ // Then prints nothing
+ assertAll(
+ this::assertOutputIsEmpty,
+ () -> assertErrOutputContains(
+ "Cluster not initialized. Call /management/v1/cluster/init in order to initialize cluster"
+ )
+ );
}
}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/configuration/ItConfigCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/configuration/ItConfigCommandTest.java
index 3ced546a6..f596b5973 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/configuration/ItConfigCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/configuration/ItConfigCommandTest.java
@@ -20,14 +20,14 @@ package org.apache.ignite.cli.commands.configuration;
import static org.junit.jupiter.api.Assertions.assertAll;
-import org.apache.ignite.cli.commands.CliCommandTestIntegrationBase;
+import org.apache.ignite.cli.commands.CliCommandTestInitializedIntegrationBase;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
/**
* Tests for cluster/node config commands.
*/
-class ItConfigCommandTest extends CliCommandTestIntegrationBase {
+class ItConfigCommandTest extends CliCommandTestInitializedIntegrationBase {
@Test
@DisplayName("Should read config when valid cluster-url is given")
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/connect/ItConnectCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/connect/ItConnectCommandTest.java
index f948f30bf..d49e0699f 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/connect/ItConnectCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/connect/ItConnectCommandTest.java
@@ -21,14 +21,14 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import jakarta.inject.Inject;
-import org.apache.ignite.cli.commands.CliCommandTestIntegrationBase;
+import org.apache.ignite.cli.commands.CliCommandTestInitializedIntegrationBase;
import org.apache.ignite.cli.commands.TopLevelCliReplCommand;
import org.apache.ignite.cli.core.repl.prompt.PromptProvider;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import picocli.CommandLine.Help.Ansi;
-class ItConnectCommandTest extends CliCommandTestIntegrationBase {
+class ItConnectCommandTest extends CliCommandTestInitializedIntegrationBase {
@Inject
PromptProvider promptProvider;
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/sql/ItSqlCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/sql/ItSqlCommandTest.java
index 5cb924b30..861e9f8b3 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/sql/ItSqlCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/sql/ItSqlCommandTest.java
@@ -20,7 +20,7 @@ package org.apache.ignite.cli.commands.sql;
import static org.apache.ignite.cli.core.exception.handler.SqlExceptionHandler.CLIENT_CONNECTION_FAILED_MESSAGE;
import static org.junit.jupiter.api.Assertions.assertAll;
-import org.apache.ignite.cli.commands.CliCommandTestIntegrationBase;
+import org.apache.ignite.cli.commands.CliCommandTestInitializedIntegrationBase;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@@ -30,7 +30,7 @@ import org.junit.jupiter.api.TestInfo;
/**
* Tests for {@link SqlCommand}.
*/
-class ItSqlCommandTest extends CliCommandTestIntegrationBase {
+class ItSqlCommandTest extends CliCommandTestInitializedIntegrationBase {
@BeforeEach
public void setUp(TestInfo testInfo) throws Exception {
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/version/ItVersionCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/version/ItVersionCommandTest.java
index 99ebea901..b22ccba13 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/version/ItVersionCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/version/ItVersionCommandTest.java
@@ -19,11 +19,11 @@ package org.apache.ignite.cli.commands.version;
import static org.junit.jupiter.api.Assertions.assertAll;
-import org.apache.ignite.cli.commands.CliCommandTestIntegrationBase;
+import org.apache.ignite.cli.commands.CliCommandTestInitializedIntegrationBase;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-class ItVersionCommandTest extends CliCommandTestIntegrationBase {
+class ItVersionCommandTest extends CliCommandTestInitializedIntegrationBase {
@Test
@DisplayName("Should print cli version that is got from pom.xml")
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/rest/ItGeneratedRestClientTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/rest/ItGeneratedRestClientTest.java
index 500c9aa53..ed0b89ee0 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/rest/ItGeneratedRestClientTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/rest/ItGeneratedRestClientTest.java
@@ -24,6 +24,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
@@ -48,6 +49,7 @@ import org.apache.ignite.rest.client.api.ClusterConfigurationApi;
import org.apache.ignite.rest.client.api.ClusterManagementApi;
import org.apache.ignite.rest.client.api.NodeConfigurationApi;
import org.apache.ignite.rest.client.api.NodeManagementApi;
+import org.apache.ignite.rest.client.api.TopologyApi;
import org.apache.ignite.rest.client.invoker.ApiClient;
import org.apache.ignite.rest.client.invoker.ApiException;
import org.apache.ignite.rest.client.invoker.Configuration;
@@ -89,6 +91,8 @@ public class ItGeneratedRestClientTest {
private NodeManagementApi nodeManagementApi;
+ private TopologyApi topologyApi;
+
private ObjectMapper objectMapper;
private String firstNodeName;
@@ -130,6 +134,7 @@ public class ItGeneratedRestClientTest {
nodeConfigurationApi = new NodeConfigurationApi(client);
clusterManagementApi = new ClusterManagementApi(client);
nodeManagementApi = new NodeManagementApi(client);
+ topologyApi = new TopologyApi(client);
objectMapper = new ObjectMapper();
}
@@ -281,14 +286,23 @@ public class ItGeneratedRestClientTest {
}
@Test
- void nodeState() {
- assertDoesNotThrow(() -> {
- NodeState nodeState = nodeManagementApi.nodeState();
+ void nodeState() throws ApiException {
+ NodeState nodeState = nodeManagementApi.nodeState();
- assertThat(nodeState, is(notNullValue()));
- assertThat(nodeState.getState(), is(notNullValue()));
- assertThat(nodeState.getName(), is(firstNodeName));
- });
+ assertThat(nodeState, is(notNullValue()));
+ assertThat(nodeState.getState(), is(notNullValue()));
+ assertThat(nodeState.getName(), is(firstNodeName));
+
+ }
+
+ @Test
+ void logicalTopology() throws ApiException {
+ assertThat(topologyApi.logical(), hasSize(3));
+ }
+
+ @Test
+ void physicalTopology() throws ApiException {
+ assertThat(topologyApi.physical(), hasSize(3));
}
private CompletableFuture<Ignite> startNodeAsync(TestInfo testInfo, int index) {
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/status/ClusterStatusCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/status/ClusterStatusCall.java
index 059822bba..6d216b8f4 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/status/ClusterStatusCall.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/status/ClusterStatusCall.java
@@ -19,14 +19,13 @@ package org.apache.ignite.cli.call.cluster.status;
import jakarta.inject.Singleton;
import org.apache.ignite.cli.call.cluster.status.ClusterStatus.ClusterStatusBuilder;
+import org.apache.ignite.cli.call.cluster.topology.PhysicalTopologyCall;
+import org.apache.ignite.cli.call.cluster.topology.TopologyCallInput;
import org.apache.ignite.cli.core.call.Call;
import org.apache.ignite.cli.core.call.CallOutput;
import org.apache.ignite.cli.core.call.DefaultCallOutput;
import org.apache.ignite.cli.core.call.StatusCallInput;
import org.apache.ignite.cli.core.exception.IgniteCliApiException;
-import org.apache.ignite.cli.deprecated.CliPathsConfigLoader;
-import org.apache.ignite.cli.deprecated.IgnitePaths;
-import org.apache.ignite.cli.deprecated.builtins.node.NodeManager;
import org.apache.ignite.rest.client.api.ClusterManagementApi;
import org.apache.ignite.rest.client.invoker.ApiClient;
import org.apache.ignite.rest.client.invoker.ApiException;
@@ -38,34 +37,26 @@ import org.apache.ignite.rest.client.model.ClusterState;
@Singleton
public class ClusterStatusCall implements Call<StatusCallInput, ClusterStatus> {
- private final NodeManager nodeManager;
+ private final PhysicalTopologyCall physicalTopologyCall;
- private final CliPathsConfigLoader cliPathsCfgLdr;
-
- /**
- * Default constructor.
- */
- public ClusterStatusCall(NodeManager nodeManager, CliPathsConfigLoader cliPathsCfgLdr) {
- this.nodeManager = nodeManager;
- this.cliPathsCfgLdr = cliPathsCfgLdr;
+ public ClusterStatusCall(PhysicalTopologyCall physicalTopologyCall) {
+ this.physicalTopologyCall = physicalTopologyCall;
}
@Override
public CallOutput<ClusterStatus> execute(StatusCallInput input) {
- IgnitePaths paths = cliPathsCfgLdr.loadIgnitePathsOrThrowError();
- ClusterStatusBuilder clusterStatusBuilder = ClusterStatus.builder()
- .nodeCount(nodeManager.getRunningNodes(paths.logDir, paths.cliPidsDir()).size());
-
+ ClusterStatusBuilder clusterStatusBuilder = ClusterStatus.builder();
try {
ClusterState clusterState = fetchClusterState(input.getClusterUrl());
clusterStatusBuilder
+ .nodeCount(fetchNumberOfAllNodes(input.getClusterUrl()))
.initialized(true)
.name(clusterState.getClusterTag().getClusterName())
.metadataStorageNodes(clusterState.getMsNodes())
.cmgNodes(clusterState.getCmgNodes());
} catch (ApiException e) {
if (e.getCode() == 404) { // NOT_FOUND means the cluster is not initialized yet
- clusterStatusBuilder.initialized(false);
+ clusterStatusBuilder.initialized(false).nodeCount(fetchNumberOfAllNodes(input.getClusterUrl()));
} else {
return DefaultCallOutput.failure(new IgniteCliApiException(e, input.getClusterUrl()));
}
@@ -74,6 +65,10 @@ public class ClusterStatusCall implements Call<StatusCallInput, ClusterStatus> {
return DefaultCallOutput.success(clusterStatusBuilder.build());
}
+ private int fetchNumberOfAllNodes(String url) {
+ return physicalTopologyCall.execute(TopologyCallInput.builder().clusterUrl(url).build()).body().size();
+ }
+
private ClusterState fetchClusterState(String url) throws ApiException {
return new ClusterManagementApi(new ApiClient().setBasePath(url)).clusterState();
}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/topology/LogicalTopologyCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/topology/LogicalTopologyCall.java
new file mode 100644
index 000000000..453b58b1f
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/topology/LogicalTopologyCall.java
@@ -0,0 +1,49 @@
+/*
+ * 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.cli.call.cluster.topology;
+
+import jakarta.inject.Singleton;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.ignite.cli.core.call.Call;
+import org.apache.ignite.cli.core.exception.IgniteCliApiException;
+import org.apache.ignite.rest.client.api.TopologyApi;
+import org.apache.ignite.rest.client.invoker.ApiException;
+import org.apache.ignite.rest.client.invoker.Configuration;
+import org.apache.ignite.rest.client.model.ClusterNode;
+
+/**
+ * Shows logical cluster topology.
+ */
+@Singleton
+public class LogicalTopologyCall implements Call<TopologyCallInput, List<ClusterNode>> {
+
+ /** {@inheritDoc} */
+ @Override
+ public TopologyCallOutput execute(TopologyCallInput input) {
+ try {
+ return TopologyCallOutput.success(fetchLogicalTopology(input));
+ } catch (ApiException | IllegalArgumentException e) {
+ return TopologyCallOutput.failure(new IgniteCliApiException(e, input.getClusterUrl()));
+ }
+ }
+
+ private List<ClusterNode> fetchLogicalTopology(TopologyCallInput input) throws ApiException {
+ return new ArrayList<>(new TopologyApi(Configuration.getDefaultApiClient().setBasePath(input.getClusterUrl())).logical());
+ }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/topology/PhysicalTopologyCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/topology/PhysicalTopologyCall.java
new file mode 100644
index 000000000..314607065
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/topology/PhysicalTopologyCall.java
@@ -0,0 +1,49 @@
+/*
+ * 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.cli.call.cluster.topology;
+
+import jakarta.inject.Singleton;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.ignite.cli.core.call.Call;
+import org.apache.ignite.cli.core.exception.IgniteCliApiException;
+import org.apache.ignite.rest.client.api.TopologyApi;
+import org.apache.ignite.rest.client.invoker.ApiException;
+import org.apache.ignite.rest.client.invoker.Configuration;
+import org.apache.ignite.rest.client.model.ClusterNode;
+
+/**
+ * Shows physical cluster topology.
+ */
+@Singleton
+public class PhysicalTopologyCall implements Call<TopologyCallInput, List<ClusterNode>> {
+
+ /** {@inheritDoc} */
+ @Override
+ public TopologyCallOutput execute(TopologyCallInput input) {
+ try {
+ return TopologyCallOutput.success(fetchPhysicalTopology(input));
+ } catch (ApiException | IllegalArgumentException e) {
+ return TopologyCallOutput.failure(new IgniteCliApiException(e, input.getClusterUrl()));
+ }
+ }
+
+ private List<ClusterNode> fetchPhysicalTopology(TopologyCallInput input) throws ApiException {
+ return new ArrayList<>(new TopologyApi(Configuration.getDefaultApiClient().setBasePath(input.getClusterUrl())).physical());
+ }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/topology/TopologyCallInput.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/topology/TopologyCallInput.java
new file mode 100644
index 000000000..4d9470d95
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/topology/TopologyCallInput.java
@@ -0,0 +1,67 @@
+/*
+ * 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.cli.call.cluster.topology;
+
+import org.apache.ignite.cli.core.call.CallInput;
+
+/**
+ * Input for physical or logical topology call.
+ */
+public class TopologyCallInput implements CallInput {
+ /**
+ * Cluster url.
+ */
+ private final String clusterUrl;
+
+ private TopologyCallInput(String clusterUrl) {
+ this.clusterUrl = clusterUrl;
+ }
+
+ /**
+ * Builder for {@link TopologyCallInput}.
+ */
+ public static TopologyCallInputBuilder builder() {
+ return new TopologyCallInputBuilder();
+ }
+
+ /**
+ * Get cluster URL.
+ *
+ * @return Cluster URL.
+ */
+ public String getClusterUrl() {
+ return clusterUrl;
+ }
+
+ /**
+ * Builder for {@link org.apache.ignite.cli.call.configuration.ClusterConfigShowCallInput}.
+ */
+ public static class TopologyCallInputBuilder {
+
+ private String clusterUrl;
+
+ public TopologyCallInputBuilder clusterUrl(String clusterUrl) {
+ this.clusterUrl = clusterUrl;
+ return this;
+ }
+
+ public TopologyCallInput build() {
+ return new TopologyCallInput(clusterUrl);
+ }
+ }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/topology/TopologyCallOutput.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/topology/TopologyCallOutput.java
new file mode 100644
index 000000000..525d0aa3e
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/topology/TopologyCallOutput.java
@@ -0,0 +1,70 @@
+/*
+ * 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.cli.call.cluster.topology;
+
+import java.util.List;
+import org.apache.ignite.cli.core.call.CallOutput;
+import org.apache.ignite.cli.core.exception.IgniteCliApiException;
+import org.apache.ignite.rest.client.model.ClusterNode;
+
+/**
+ * Output fot the topology calls.
+ */
+public class TopologyCallOutput implements CallOutput<List<ClusterNode>> {
+ private final Throwable error;
+
+ private final List<ClusterNode> topology;
+
+ private TopologyCallOutput(List<ClusterNode> topology) {
+ this.topology = topology;
+ error = null;
+ }
+
+ private TopologyCallOutput(Exception error) {
+ this.error = error;
+ this.topology = null;
+ }
+
+ public static TopologyCallOutput success(List<ClusterNode> topology) {
+ return new TopologyCallOutput(topology);
+ }
+
+ public static TopologyCallOutput failure(IgniteCliApiException e) {
+ return new TopologyCallOutput(e);
+ }
+
+ @Override
+ public List<ClusterNode> body() {
+ return topology;
+ }
+
+ @Override
+ public boolean hasError() {
+ return error != null;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return topology != null && topology.isEmpty();
+ }
+
+ @Override
+ public Throwable errorCause() {
+ return error;
+ }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/ClusterCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/ClusterCommand.java
index e985709ec..fb73a8be4 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/ClusterCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/ClusterCommand.java
@@ -20,16 +20,18 @@ package org.apache.ignite.cli.commands.cluster;
import org.apache.ignite.cli.commands.cluster.config.ClusterConfigSubCommand;
import org.apache.ignite.cli.commands.cluster.init.ClusterInitSubCommand;
import org.apache.ignite.cli.commands.cluster.status.ClusterStatusSubCommand;
+import org.apache.ignite.cli.commands.topology.TopologyCommand;
import picocli.CommandLine.Command;
/**
- * Node command.
+ * Cluster command.
*/
@Command(name = "cluster",
subcommands = {
ClusterConfigSubCommand.class,
ClusterInitSubCommand.class,
- ClusterStatusSubCommand.class},
+ ClusterStatusSubCommand.class,
+ TopologyCommand.class},
description = "Manages an Ignite cluster.")
public class ClusterCommand {
}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/ClusterReplCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/ClusterReplCommand.java
index 461a2a9b0..44e517bb7 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/ClusterReplCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/ClusterReplCommand.java
@@ -20,6 +20,7 @@ package org.apache.ignite.cli.commands.cluster;
import org.apache.ignite.cli.commands.cluster.config.ClusterConfigReplSubCommand;
import org.apache.ignite.cli.commands.cluster.init.ClusterInitReplSubCommand;
import org.apache.ignite.cli.commands.cluster.status.ClusterStatusReplSubCommand;
+import org.apache.ignite.cli.commands.topology.TopologyReplCommand;
import picocli.CommandLine.Command;
/**
@@ -29,7 +30,8 @@ import picocli.CommandLine.Command;
subcommands = {
ClusterConfigReplSubCommand.class,
ClusterInitReplSubCommand.class,
- ClusterStatusReplSubCommand.class
+ ClusterStatusReplSubCommand.class,
+ TopologyReplCommand.class
},
description = "Manages an Ignite cluster.")
public class ClusterReplCommand {
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/TopologyDecorator.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/TopologyDecorator.java
new file mode 100644
index 000000000..af5426eb8
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/TopologyDecorator.java
@@ -0,0 +1,51 @@
+/*
+ * 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.cli.commands.decorators;
+
+import com.jakewharton.fliptables.FlipTable;
+import java.util.List;
+import org.apache.ignite.cli.core.decorator.Decorator;
+import org.apache.ignite.cli.core.decorator.TerminalOutput;
+import org.apache.ignite.rest.client.model.ClusterNode;
+
+/**
+ * Implementation of {@link Decorator} for the list of {@link ClusterNode}.
+ */
+public class TopologyDecorator implements Decorator<List<ClusterNode>, TerminalOutput> {
+
+ /**
+ * Transform list of {@link ClusterNode} to {@link TerminalOutput}.
+ *
+ * @param topology incoming list of {@link ClusterNode}.
+ * @return User-friendly interpretation of list of {@link ClusterNode} in {@link TerminalOutput}.
+ */
+ @Override
+ public TerminalOutput decorate(List<ClusterNode> topology) {
+ String[] headers = {"name", "host", "port", "consistent id", "id"};
+ String[][] content = topology.stream().map(
+ node -> new String[]{
+ node.getName(),
+ node.getAddress().getHost(),
+ String.valueOf(node.getAddress().getPort()),
+ node.getAddress().getConsistentId() == null ? "(empty)" : node.getAddress().getConsistentId(),
+ node.getId()
+ }
+ ).toArray(String[][]::new);
+ return () -> FlipTable.of(headers, content);
+ }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusReplSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusReplSubCommand.java
index 58e6fd321..ec526518e 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusReplSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusReplSubCommand.java
@@ -42,7 +42,7 @@ public class NodeStatusReplSubCommand extends BaseCommand implements Callable<In
*/
@SuppressWarnings("PMD.UnusedPrivateField")
@Option(
- names = {"--node-url"}, description = "Url to node.", descriptionKey = "ignite.node-url"
+ names = {"--node-url"}, description = "Url to node.", descriptionKey = "ignite.cluster-url"
)
private String nodeUrl;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusSubCommand.java
index d3eae42ce..78abc3620 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusSubCommand.java
@@ -40,7 +40,7 @@ public class NodeStatusSubCommand extends BaseCommand implements Callable<Intege
* Node url option.
*/
@SuppressWarnings("PMD.UnusedPrivateField")
- @Option(names = {"--node-url"}, description = "Url to node.", descriptionKey = "ignite.node-url")
+ @Option(names = {"--node-url"}, description = "Url to node.", descriptionKey = "ignite.cluster-url")
private String nodeUrl;
@Inject
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusReplSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/LogicalTopologyReplSubCommand.java
similarity index 56%
copy from modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusReplSubCommand.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/LogicalTopologyReplSubCommand.java
index 58e6fd321..2cf9ccec6 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusReplSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/LogicalTopologyReplSubCommand.java
@@ -15,62 +15,56 @@
* limitations under the License.
*/
-package org.apache.ignite.cli.commands.node.status;
+package org.apache.ignite.cli.commands.topology;
import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
import java.util.concurrent.Callable;
-import org.apache.ignite.cli.call.node.status.NodeStatusCall;
+import org.apache.ignite.cli.call.cluster.topology.LogicalTopologyCall;
+import org.apache.ignite.cli.call.cluster.topology.TopologyCallInput;
+import org.apache.ignite.cli.call.cluster.topology.TopologyCallInput.TopologyCallInputBuilder;
import org.apache.ignite.cli.commands.BaseCommand;
-import org.apache.ignite.cli.commands.decorators.NodeStatusDecorator;
+import org.apache.ignite.cli.commands.decorators.TopologyDecorator;
import org.apache.ignite.cli.core.call.CallExecutionPipeline;
-import org.apache.ignite.cli.core.call.StatusCallInput;
import org.apache.ignite.cli.core.repl.Session;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
/**
- * Display the node status in REPL.
+ * Command that show logical cluster topology in REPL mode.
*/
-@Command(name = "status",
- description = "Prints status of the node.")
-@Singleton
-public class NodeStatusReplSubCommand extends BaseCommand implements Callable<Integer> {
-
+@Command(name = "logical")
+public class LogicalTopologyReplSubCommand extends BaseCommand implements Callable<Integer> {
/**
* Node url option.
*/
- @SuppressWarnings("PMD.UnusedPrivateField")
- @Option(
- names = {"--node-url"}, description = "Url to node.", descriptionKey = "ignite.node-url"
- )
- private String nodeUrl;
+ @Option(names = {"--cluster-url"}, description = "Url to ignite node.", descriptionKey = "ignite.cluster-url")
+ private String clusterUrl;
@Inject
- private NodeStatusCall nodeStatusCall;
+ private Session session;
@Inject
- private Session session;
+ private LogicalTopologyCall call;
/** {@inheritDoc} */
@Override
public Integer call() {
- String inputUrl;
+ TopologyCallInputBuilder inputBuilder = TopologyCallInput.builder();
- if (nodeUrl != null) {
- inputUrl = nodeUrl;
- } else if (session.isConnectedToNode()) {
- inputUrl = session.nodeUrl();
+ if (clusterUrl != null) {
+ inputBuilder.clusterUrl(clusterUrl);
+ } else if (session.isConnectedToNode()) {
+ inputBuilder.clusterUrl(session.nodeUrl());
} else {
- spec.commandLine().getErr().println("You are not connected to node. Run 'connect' command or use '--node-url' option.");
+ spec.commandLine().getErr().println("You are not connected to node. Run 'connect' command or use '--cluster-url' option.");
return 2;
}
- return CallExecutionPipeline.builder(nodeStatusCall)
- .inputProvider(() -> new StatusCallInput(inputUrl))
+ return CallExecutionPipeline.builder(call)
+ .inputProvider(inputBuilder::build)
.output(spec.commandLine().getOut())
.errOutput(spec.commandLine().getErr())
- .decorator(new NodeStatusDecorator())
+ .decorator(new TopologyDecorator())
.build()
.runPipeline();
}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/LogicalTopologySubCommand.java
similarity index 60%
copy from modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusSubCommand.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/LogicalTopologySubCommand.java
index d3eae42ce..08de59243 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/LogicalTopologySubCommand.java
@@ -15,45 +15,40 @@
* limitations under the License.
*/
-package org.apache.ignite.cli.commands.node.status;
+package org.apache.ignite.cli.commands.topology;
import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
import java.util.concurrent.Callable;
-import org.apache.ignite.cli.call.node.status.NodeStatusCall;
+import org.apache.ignite.cli.call.cluster.topology.LogicalTopologyCall;
+import org.apache.ignite.cli.call.cluster.topology.TopologyCallInput;
import org.apache.ignite.cli.commands.BaseCommand;
-import org.apache.ignite.cli.commands.decorators.NodeStatusDecorator;
+import org.apache.ignite.cli.commands.decorators.TopologyDecorator;
import org.apache.ignite.cli.core.call.CallExecutionPipeline;
-import org.apache.ignite.cli.core.call.StatusCallInput;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
/**
- * Display the node status.
+ * Command that show logical cluster topology.
*/
-@Command(name = "status",
- description = "Prints status of the node.")
-@Singleton
-public class NodeStatusSubCommand extends BaseCommand implements Callable<Integer> {
-
+@Command(name = "logical")
+public class LogicalTopologySubCommand extends BaseCommand implements Callable<Integer> {
/**
* Node url option.
*/
- @SuppressWarnings("PMD.UnusedPrivateField")
- @Option(names = {"--node-url"}, description = "Url to node.", descriptionKey = "ignite.node-url")
- private String nodeUrl;
+ @Option(names = {"--cluster-url"}, description = "Url to ignite node.", descriptionKey = "ignite.cluster-url")
+ private String clusterUrl;
@Inject
- private NodeStatusCall nodeStatusCall;
+ private LogicalTopologyCall call;
/** {@inheritDoc} */
@Override
public Integer call() {
- return CallExecutionPipeline.builder(nodeStatusCall)
- .inputProvider(() -> new StatusCallInput(nodeUrl))
+ return CallExecutionPipeline.builder(call)
+ .inputProvider(() -> TopologyCallInput.builder().clusterUrl(clusterUrl).build())
.output(spec.commandLine().getOut())
.errOutput(spec.commandLine().getErr())
- .decorator(new NodeStatusDecorator())
+ .decorator(new TopologyDecorator())
.build()
.runPipeline();
}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusReplSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/PhysicalTopologyReplSubCommand.java
similarity index 56%
copy from modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusReplSubCommand.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/PhysicalTopologyReplSubCommand.java
index 58e6fd321..988433c6b 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusReplSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/PhysicalTopologyReplSubCommand.java
@@ -15,39 +15,33 @@
* limitations under the License.
*/
-package org.apache.ignite.cli.commands.node.status;
+package org.apache.ignite.cli.commands.topology;
import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
import java.util.concurrent.Callable;
-import org.apache.ignite.cli.call.node.status.NodeStatusCall;
+import org.apache.ignite.cli.call.cluster.topology.PhysicalTopologyCall;
+import org.apache.ignite.cli.call.cluster.topology.TopologyCallInput;
+import org.apache.ignite.cli.call.cluster.topology.TopologyCallInput.TopologyCallInputBuilder;
import org.apache.ignite.cli.commands.BaseCommand;
-import org.apache.ignite.cli.commands.decorators.NodeStatusDecorator;
+import org.apache.ignite.cli.commands.decorators.TopologyDecorator;
import org.apache.ignite.cli.core.call.CallExecutionPipeline;
-import org.apache.ignite.cli.core.call.StatusCallInput;
import org.apache.ignite.cli.core.repl.Session;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
/**
- * Display the node status in REPL.
+ * Command that show physical cluster topology in REPL mode.
*/
-@Command(name = "status",
- description = "Prints status of the node.")
-@Singleton
-public class NodeStatusReplSubCommand extends BaseCommand implements Callable<Integer> {
-
+@Command(name = "physical")
+public class PhysicalTopologyReplSubCommand extends BaseCommand implements Callable<Integer> {
/**
* Node url option.
*/
- @SuppressWarnings("PMD.UnusedPrivateField")
- @Option(
- names = {"--node-url"}, description = "Url to node.", descriptionKey = "ignite.node-url"
- )
- private String nodeUrl;
+ @Option(names = {"--cluster-url"}, description = "Url to ignite node.", descriptionKey = "ignite.cluster-url")
+ private String clusterUrl;
@Inject
- private NodeStatusCall nodeStatusCall;
+ private PhysicalTopologyCall call;
@Inject
private Session session;
@@ -55,22 +49,22 @@ public class NodeStatusReplSubCommand extends BaseCommand implements Callable<In
/** {@inheritDoc} */
@Override
public Integer call() {
- String inputUrl;
+ TopologyCallInputBuilder inputBuilder = TopologyCallInput.builder();
- if (nodeUrl != null) {
- inputUrl = nodeUrl;
- } else if (session.isConnectedToNode()) {
- inputUrl = session.nodeUrl();
+ if (clusterUrl != null) {
+ inputBuilder.clusterUrl(clusterUrl);
+ } else if (session.isConnectedToNode()) {
+ inputBuilder.clusterUrl(session.nodeUrl());
} else {
- spec.commandLine().getErr().println("You are not connected to node. Run 'connect' command or use '--node-url' option.");
+ spec.commandLine().getErr().println("You are not connected to node. Run 'connect' command or use '--cluster-url' option.");
return 2;
}
- return CallExecutionPipeline.builder(nodeStatusCall)
- .inputProvider(() -> new StatusCallInput(inputUrl))
+ return CallExecutionPipeline.builder(call)
+ .inputProvider(inputBuilder::build)
.output(spec.commandLine().getOut())
.errOutput(spec.commandLine().getErr())
- .decorator(new NodeStatusDecorator())
+ .decorator(new TopologyDecorator())
.build()
.runPipeline();
}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/PhysicalTopologySubCommand.java
similarity index 60%
copy from modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusSubCommand.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/PhysicalTopologySubCommand.java
index d3eae42ce..a76be9dc9 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/PhysicalTopologySubCommand.java
@@ -15,45 +15,40 @@
* limitations under the License.
*/
-package org.apache.ignite.cli.commands.node.status;
+package org.apache.ignite.cli.commands.topology;
import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
import java.util.concurrent.Callable;
-import org.apache.ignite.cli.call.node.status.NodeStatusCall;
+import org.apache.ignite.cli.call.cluster.topology.PhysicalTopologyCall;
+import org.apache.ignite.cli.call.cluster.topology.TopologyCallInput;
import org.apache.ignite.cli.commands.BaseCommand;
-import org.apache.ignite.cli.commands.decorators.NodeStatusDecorator;
+import org.apache.ignite.cli.commands.decorators.TopologyDecorator;
import org.apache.ignite.cli.core.call.CallExecutionPipeline;
-import org.apache.ignite.cli.core.call.StatusCallInput;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
/**
- * Display the node status.
+ * Command that show physical cluster topology.
*/
-@Command(name = "status",
- description = "Prints status of the node.")
-@Singleton
-public class NodeStatusSubCommand extends BaseCommand implements Callable<Integer> {
-
+@Command(name = "physical")
+public class PhysicalTopologySubCommand extends BaseCommand implements Callable<Integer> {
/**
* Node url option.
*/
- @SuppressWarnings("PMD.UnusedPrivateField")
- @Option(names = {"--node-url"}, description = "Url to node.", descriptionKey = "ignite.node-url")
- private String nodeUrl;
+ @Option(names = {"--cluster-url"}, description = "Url to ignite node.", descriptionKey = "ignite.cluster-url")
+ private String clusterUrl;
@Inject
- private NodeStatusCall nodeStatusCall;
+ private PhysicalTopologyCall call;
/** {@inheritDoc} */
@Override
public Integer call() {
- return CallExecutionPipeline.builder(nodeStatusCall)
- .inputProvider(() -> new StatusCallInput(nodeUrl))
+ return CallExecutionPipeline.builder(call)
+ .inputProvider(() -> TopologyCallInput.builder().clusterUrl(clusterUrl).build())
.output(spec.commandLine().getOut())
.errOutput(spec.commandLine().getErr())
- .decorator(new NodeStatusDecorator())
+ .decorator(new TopologyDecorator())
.build()
.runPipeline();
}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyCommand.java
index e01a888a4..5da14720f 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyCommand.java
@@ -21,30 +21,18 @@ import jakarta.inject.Singleton;
import java.util.concurrent.Callable;
import org.apache.ignite.cli.commands.BaseCommand;
import picocli.CommandLine.Command;
-import picocli.CommandLine.Option;
/**
* Command that prints ignite cluster topology.
*/
-@Command(name = "topology", description = "Prints topology information.")
+@Command(name = "topology", description = "Prints topology information.",
+ subcommands = {PhysicalTopologySubCommand.class, LogicalTopologySubCommand.class })
@Singleton
public class TopologyCommand extends BaseCommand implements Callable<Integer> {
- /**
- * Cluster url option.
- */
- @SuppressWarnings("PMD.UnusedPrivateField")
- @Option(
- names = {"--cluster-url"}, description = "Url to cluster node.",
- descriptionKey = "ignite.cluster-url", defaultValue = "http://localhost:10300"
- )
- private String clusterUrl;
-
/** {@inheritDoc} */
@Override
public Integer call() {
- //TODO: https://issues.apache.org/jira/browse/IGNITE-17092
- spec.commandLine().getOut().println("Topology command is not implemented yet.");
return 0;
}
}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/CallIntegrationTestBase.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyReplCommand.java
similarity index 56%
rename from modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/CallIntegrationTestBase.java
rename to modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyReplCommand.java
index d5c0f37b3..fa2c9ed05 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/CallIntegrationTestBase.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyReplCommand.java
@@ -15,12 +15,24 @@
* limitations under the License.
*/
-package org.apache.ignite.cli.call;
+package org.apache.ignite.cli.commands.topology;
-import org.apache.ignite.cli.IntegrationTestBase;
+import jakarta.inject.Singleton;
+import java.util.concurrent.Callable;
+import org.apache.ignite.cli.commands.BaseCommand;
+import picocli.CommandLine.Command;
/**
- * Base class for call integration tests. Contains common methods and useful assertions.
+ * Command that prints ignite cluster topology in REPL mode.
*/
-public class CallIntegrationTestBase extends IntegrationTestBase {
+@Command(name = "topology", description = "Prints topology information.",
+ subcommands = {PhysicalTopologyReplSubCommand.class, LogicalTopologyReplSubCommand.class })
+@Singleton
+public class TopologyReplCommand extends BaseCommand implements Callable<Integer> {
+
+ /** {@inheritDoc} */
+ @Override
+ public Integer call() {
+ return 0;
+ }
}
diff --git a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/rest/ItClusterManagementControllerTest.java b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/rest/ItClusterManagementControllerTest.java
index bd805bc6d..5d48c86ce 100644
--- a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/rest/ItClusterManagementControllerTest.java
+++ b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/rest/ItClusterManagementControllerTest.java
@@ -34,49 +34,23 @@ import io.micronaut.http.HttpStatus;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.http.client.exceptions.HttpClientResponseException;
-import io.micronaut.runtime.server.EmbeddedServer;
-import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import java.io.IOException;
-import java.nio.file.Path;
-import java.util.ArrayList;
import java.util.List;
-import org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
import org.apache.ignite.internal.cluster.management.MockNode;
-import org.apache.ignite.internal.rest.api.Problem;
import org.apache.ignite.internal.rest.api.cluster.ClusterManagementApi;
import org.apache.ignite.internal.rest.api.cluster.ClusterStateDto;
-import org.apache.ignite.internal.testframework.WorkDirectory;
-import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
-import org.apache.ignite.network.ClusterService;
import org.apache.ignite.network.NetworkAddress;
import org.apache.ignite.network.StaticNodeFinder;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
-import org.junit.jupiter.api.extension.ExtendWith;
/**
* Cluster management REST test.
*/
-@MicronautTest
-@ExtendWith(WorkDirectoryExtension.class)
-public class ItClusterManagementControllerTest {
-
- private static final int PORT_BASE = 10000;
-
- private static final List<MockNode> cluster = new ArrayList<>();
-
- static ClusterService clusterService;
-
- static ClusterManagementGroupManager clusterManager;
-
- @WorkDirectory
- private static Path workDir;
-
- @Inject
- private EmbeddedServer server;
+public class ItClusterManagementControllerTest extends RestTestBase {
@Inject
@Client("/management/v1/cluster")
@@ -194,8 +168,4 @@ public class ItClusterManagementControllerTest {
public ClusterManagementRestFactory clusterManagementRestFactory() {
return new ClusterManagementRestFactory(clusterService, clusterManager);
}
-
- private Problem getProblem(HttpClientResponseException exception) {
- return exception.getResponse().getBody(Problem.class).orElseThrow();
- }
}
diff --git a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/rest/RestTestBase.java b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/rest/RestTestBase.java
new file mode 100644
index 000000000..83d7b80f8
--- /dev/null
+++ b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/rest/RestTestBase.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.cluster.management.rest;
+
+import io.micronaut.http.client.exceptions.HttpClientResponseException;
+import io.micronaut.runtime.server.EmbeddedServer;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
+import org.apache.ignite.internal.cluster.management.MockNode;
+import org.apache.ignite.internal.rest.api.Problem;
+import org.apache.ignite.internal.testframework.WorkDirectory;
+import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
+import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.NetworkAddress;
+import org.apache.ignite.network.StaticNodeFinder;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Cluster management REST test.
+ */
+@MicronautTest
+@ExtendWith(WorkDirectoryExtension.class)
+public class RestTestBase {
+
+ static final int PORT_BASE = 10000;
+
+ static final List<MockNode> cluster = new ArrayList<>();
+
+ static ClusterService clusterService;
+
+ static ClusterManagementGroupManager clusterManager;
+
+ @WorkDirectory
+ static Path workDir;
+
+ @Inject
+ EmbeddedServer server;
+
+ @BeforeAll
+ static void setUp(TestInfo testInfo) throws IOException {
+ var addr1 = new NetworkAddress("localhost", PORT_BASE);
+ var addr2 = new NetworkAddress("localhost", PORT_BASE + 1);
+
+ var nodeFinder = new StaticNodeFinder(List.of(addr1, addr2));
+
+ cluster.add(new MockNode(testInfo, addr1, nodeFinder, workDir.resolve("node0")));
+ cluster.add(new MockNode(testInfo, addr2, nodeFinder, workDir.resolve("node1")));
+
+ for (MockNode node : cluster) {
+ node.start();
+ }
+
+ clusterService = cluster.get(0).clusterService();
+ clusterManager = cluster.get(0).clusterManager();
+ }
+
+ @AfterAll
+ static void tearDown() {
+ for (MockNode node : cluster) {
+ node.beforeNodeStop();
+ }
+
+ for (MockNode node : cluster) {
+ node.stop();
+ }
+ }
+
+ Problem getProblem(HttpClientResponseException exception) {
+ return exception.getResponse().getBody(Problem.class).orElseThrow();
+ }
+}
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementController.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementController.java
index 1aea9f91a..83aec6c4a 100644
--- a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementController.java
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementController.java
@@ -54,8 +54,7 @@ public class ClusterManagementController implements ClusterManagementApi {
* @param clusterInitializer cluster initializer.
* @param clusterManagementGroupManager cluster management group manager.
*/
- public ClusterManagementController(ClusterInitializer clusterInitializer,
- ClusterManagementGroupManager clusterManagementGroupManager) {
+ public ClusterManagementController(ClusterInitializer clusterInitializer, ClusterManagementGroupManager clusterManagementGroupManager) {
this.clusterInitializer = clusterInitializer;
this.clusterManagementGroupManager = clusterManagementGroupManager;
}
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementRestFactory.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementRestFactory.java
index b124e1768..b0348f6a5 100644
--- a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementRestFactory.java
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementRestFactory.java
@@ -24,6 +24,7 @@ import org.apache.ignite.internal.cluster.management.ClusterInitializer;
import org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
import org.apache.ignite.internal.rest.RestFactory;
import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.TopologyService;
/**
* Factory that creates beans that are needed for {@link ClusterManagementController}.
@@ -31,11 +32,12 @@ import org.apache.ignite.network.ClusterService;
@Factory
public class ClusterManagementRestFactory implements RestFactory {
private final ClusterService clusterService;
- private final ClusterManagementGroupManager clusterManagementGroupManager;
- public ClusterManagementRestFactory(ClusterService clusterService, ClusterManagementGroupManager clusterManagementGroupManager) {
+ private final ClusterManagementGroupManager cmgManager;
+
+ public ClusterManagementRestFactory(ClusterService clusterService, ClusterManagementGroupManager cmgManager) {
this.clusterService = clusterService;
- this.clusterManagementGroupManager = clusterManagementGroupManager;
+ this.cmgManager = cmgManager;
}
@Bean
@@ -46,7 +48,13 @@ public class ClusterManagementRestFactory implements RestFactory {
@Bean
@Singleton
- public ClusterManagementGroupManager clusterManagementGroupManager() {
- return clusterManagementGroupManager;
+ public ClusterManagementGroupManager cmgManager() {
+ return cmgManager;
+ }
+
+ @Bean
+ @Singleton
+ public TopologyService topologyService() {
+ return clusterService.topologyService();
}
}
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/TopologyController.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/TopologyController.java
new file mode 100644
index 000000000..4833ffd45
--- /dev/null
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/TopologyController.java
@@ -0,0 +1,76 @@
+/*
+ * 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.cluster.management.rest;
+
+import io.micronaut.http.annotation.Controller;
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
+import org.apache.ignite.internal.cluster.management.rest.exception.ClusterNotInitializedException;
+import org.apache.ignite.internal.rest.api.cluster.ClusterNodeDto;
+import org.apache.ignite.internal.rest.api.cluster.NetworkAddressDto;
+import org.apache.ignite.internal.rest.api.cluster.TopologyApi;
+import org.apache.ignite.lang.IgniteException;
+import org.apache.ignite.network.TopologyService;
+
+/**
+ * Cluster topology endpoint implementation.
+ */
+@Controller("/management/v1/cluster/topology")
+public class TopologyController implements TopologyApi {
+ private final TopologyService topologyService;
+
+ private final ClusterManagementGroupManager cmgManager;
+
+ public TopologyController(TopologyService topologyService, ClusterManagementGroupManager cmgManager) {
+ this.topologyService = topologyService;
+ this.cmgManager = cmgManager;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Collection<ClusterNodeDto> physicalTopology() {
+ return topologyService.allMembers()
+ .stream().map(cn -> new ClusterNodeDto(cn.id(), cn.name(),
+ new NetworkAddressDto(cn.address().host(), cn.address().port(), cn.address().consistentId()))
+ ).collect(Collectors.toList());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Collection<ClusterNodeDto> logicalTopology() {
+ try {
+ return cmgManager.clusterState()
+ .thenApply(state -> {
+ if (state == null) {
+ throw new ClusterNotInitializedException();
+ }
+ return cmgManager.logicalTopology();
+ }).get().get().stream()
+ .map(cn -> new ClusterNodeDto(cn.id(), cn.name(),
+ new NetworkAddressDto(cn.address().host(), cn.address().port(), cn.address().consistentId()))
+ ).collect(Collectors.toList());
+ } catch (ExecutionException | InterruptedException e) {
+ if (e.getCause() instanceof ClusterNotInitializedException) {
+ throw (ClusterNotInitializedException) e.getCause();
+ }
+ throw new IgniteException(e);
+ }
+ }
+}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/cluster/ClusterManagementApi.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/cluster/ClusterManagementApi.java
index 91d01fca1..2c5ac222b 100644
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/cluster/ClusterManagementApi.java
+++ b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/cluster/ClusterManagementApi.java
@@ -27,7 +27,6 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
-import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.concurrent.CompletableFuture;
import org.apache.ignite.internal.rest.api.Problem;
@@ -45,14 +44,12 @@ public interface ClusterManagementApi {
*/
@Get("state")
@Operation(operationId = "clusterState")
- @ApiResponses({
- @ApiResponse(responseCode = "200", description = "Return cluster state",
- content = @Content(mediaType = MediaType.APPLICATION_JSON,
- schema = @Schema(implementation = ClusterStateDto.class))),
- @ApiResponse(responseCode = "404", description = "Cluster state not found, it means that the cluster is not initialized"),
- @ApiResponse(responseCode = "500", description = "Internal error",
- content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
- })
+ @ApiResponse(responseCode = "200", description = "Return cluster state",
+ content = @Content(mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = ClusterStateDto.class)))
+ @ApiResponse(responseCode = "404", description = "Cluster state not found, it means that the cluster is not initialized")
+ @ApiResponse(responseCode = "500", description = "Internal error",
+ content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
@Produces({
MediaType.APPLICATION_JSON,
MediaType.PROBLEM_JSON
@@ -66,14 +63,11 @@ public interface ClusterManagementApi {
*/
@Post("init")
@Operation(operationId = "init")
- @ApiResponses({
- @ApiResponse(responseCode = "200", description = "Cluster initialized"),
- @ApiResponse(responseCode = "500", description = "Internal error",
- content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class))),
- @ApiResponse(responseCode = "400", description = "Incorrect configuration",
- content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
-
- })
+ @ApiResponse(responseCode = "200", description = "Cluster initialized")
+ @ApiResponse(responseCode = "500", description = "Internal error",
+ content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
+ @ApiResponse(responseCode = "400", description = "Incorrect configuration",
+ content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
@Consumes(MediaType.APPLICATION_JSON)
CompletableFuture<Void> init(@Body InitCommand initCommand);
}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/cluster/ClusterNodeDto.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/cluster/ClusterNodeDto.java
new file mode 100644
index 000000000..05cd8c2b4
--- /dev/null
+++ b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/cluster/ClusterNodeDto.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.rest.api.cluster;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonGetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.ignite.network.ClusterNode;
+
+/**
+ * REST representation of {@link ClusterNode}.
+ */
+@Schema(name = "ClusterNode")
+public class ClusterNodeDto {
+ /** Local id assigned to this node instance. Changes between restarts. */
+ private final String id;
+
+ /** Unique name of member in the cluster. Consistent between restarts. */
+ private final String name;
+
+ /** Network address of this node. */
+ private final NetworkAddressDto address;
+
+ /**
+ * Constructor.
+ *
+ * @param id Local id that changes between restarts.
+ * @param name Unique name of a member in a cluster.
+ * @param address Node address.
+ */
+ @JsonCreator
+ public ClusterNodeDto(
+ @JsonProperty("id") String id,
+ @JsonProperty("name") String name,
+ @JsonProperty("address") NetworkAddressDto address) {
+ this.id = id;
+ this.name = name;
+ this.address = address;
+ }
+
+ /**
+ * Returns this node's local ID.
+ *
+ * @return Node's local id.
+ */
+ @JsonGetter("id")
+ public String id() {
+ return id;
+ }
+
+ /**
+ * Returns the unique name (consistent id) of this node in a cluster. Doesn't change between restarts.
+ *
+ * @return Unique name of the member in a cluster.
+ */
+ @JsonGetter("name")
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns the network address of this node.
+ *
+ * @return Network address of this node.
+ */
+ @JsonGetter("address")
+ public NetworkAddressDto address() {
+ return address;
+ }
+}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/cluster/NetworkAddressDto.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/cluster/NetworkAddressDto.java
new file mode 100644
index 000000000..90e7bb616
--- /dev/null
+++ b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/cluster/NetworkAddressDto.java
@@ -0,0 +1,89 @@
+/*
+ * 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.rest.api.cluster;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonGetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.ignite.network.NetworkAddress;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * REST representation of {@link NetworkAddress}.
+ */
+@Schema(name = "NetworkAddress")
+public class NetworkAddressDto {
+ /** Host. */
+ private final String host;
+
+ /** Port. */
+ private final int port;
+
+ /** Consistent id. TODO: IGNITE-16373 Temporary until ticket is not resolved. */
+ @Nullable
+ private final String consistentId;
+
+ /**
+ * Constructor.
+ *
+ * @param host Host.
+ * @param port Port.
+ * @param consistentId Consistent id.
+ */
+ @JsonCreator
+ public NetworkAddressDto(
+ @JsonProperty("host") String host,
+ @JsonProperty("port") int port,
+ @JsonProperty("consistentId") String consistentId) {
+ this.host = host;
+ this.port = port;
+ this.consistentId = consistentId;
+ }
+
+ /**
+ * Returns the host name.
+ *
+ * @return Host name.
+ */
+ @JsonGetter("host")
+ public String host() {
+ return host;
+ }
+
+ /**
+ * Returns the network port.
+ *
+ * @return Port.
+ */
+ @JsonGetter("port")
+ public int port() {
+ return port;
+ }
+
+ /**
+ * Returns the consistent id.
+ *
+ * @return Consistent id.
+ */
+ @Nullable
+ @JsonGetter("consistentId")
+ public String consistentId() {
+ return consistentId;
+ }
+}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/cluster/TopologyApi.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/cluster/TopologyApi.java
new file mode 100644
index 000000000..3741ee395
--- /dev/null
+++ b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/cluster/TopologyApi.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.rest.api.cluster;
+
+import io.micronaut.http.annotation.Controller;
+import io.micronaut.http.annotation.Get;
+import io.micronaut.http.annotation.Produces;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import java.util.Collection;
+import org.apache.ignite.internal.rest.api.Problem;
+import org.apache.ignite.internal.rest.constants.MediaType;
+
+/**
+ * Cluster topology endpoint.
+ */
+@Controller("/management/v1/cluster/topology")
+@Tag(name = "topology")
+public interface TopologyApi {
+
+ /**
+ * Cluster physical topology.
+ */
+ @Get("physical")
+ @Operation(operationId = "physical")
+ @ApiResponse(responseCode = "200", description = "Physical topology returned")
+ @ApiResponse(responseCode = "500", description = "Internal error",
+ content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
+ @Produces(MediaType.APPLICATION_JSON)
+ Collection<ClusterNodeDto> physicalTopology();
+
+ /**
+ * Cluster logical topology.
+ */
+ @Get("logical")
+ @Operation(operationId = "logical")
+ @ApiResponse(responseCode = "200", description = "Logical topology returned")
+ @ApiResponse(responseCode = "500", description = "Internal error",
+ content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
+ @Produces(MediaType.APPLICATION_JSON)
+ Collection<ClusterNodeDto> logicalTopology();
+}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/configuration/ClusterConfigurationApi.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/configuration/ClusterConfigurationApi.java
index 8a79027fd..e3c033ca6 100644
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/configuration/ClusterConfigurationApi.java
+++ b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/configuration/ClusterConfigurationApi.java
@@ -28,7 +28,6 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
-import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.concurrent.CompletableFuture;
import org.apache.ignite.internal.rest.api.Problem;
@@ -47,16 +46,14 @@ public interface ClusterConfigurationApi {
* @return the whole cluster configuration in HOCON format.
*/
@Operation(operationId = "getClusterConfiguration")
- @ApiResponses({
- @ApiResponse(
- responseCode = "200",
- content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
- description = "Get cluster configuration"),
- @ApiResponse(responseCode = "500", description = "Internal error",
- content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class))),
- @ApiResponse(responseCode = "400", description = "Incorrect configuration",
- content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
- })
+ @ApiResponse(
+ responseCode = "200",
+ content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
+ description = "Get cluster configuration")
+ @ApiResponse(responseCode = "500", description = "Internal error",
+ content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
+ @ApiResponse(responseCode = "400", description = "Incorrect configuration",
+ content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
@Produces({
MediaType.TEXT_PLAIN, // todo: IGNITE-17082
MediaType.PROBLEM_JSON
@@ -70,13 +67,11 @@ public interface ClusterConfigurationApi {
* @param updatedConfiguration the cluster configuration to update.
*/
@Operation(operationId = "updateClusterConfiguration")
- @ApiResponses({
- @ApiResponse(responseCode = "200", description = "Configuration updated"),
- @ApiResponse(responseCode = "500", description = "Internal error",
- content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class))),
- @ApiResponse(responseCode = "400", description = "Incorrect configuration",
- content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
- })
+ @ApiResponse(responseCode = "200", description = "Configuration updated")
+ @ApiResponse(responseCode = "500", description = "Internal error",
+ content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
+ @ApiResponse(responseCode = "400", description = "Incorrect configuration",
+ content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
@Consumes(MediaType.TEXT_PLAIN) // todo: IGNITE-17082
@Produces(MediaType.PROBLEM_JSON)
@Patch
@@ -89,15 +84,13 @@ public interface ClusterConfigurationApi {
* @return system configuration in HOCON format represented by given path.
*/
@Operation(operationId = "getClusterConfigurationByPath")
- @ApiResponses({
- @ApiResponse(responseCode = "200",
- content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
- description = "Configuration represented by path"),
- @ApiResponse(responseCode = "500", description = "Internal error",
- content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class))),
- @ApiResponse(responseCode = "400", description = "Incorrect configuration",
- content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
- })
+ @ApiResponse(responseCode = "200",
+ content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
+ description = "Configuration represented by path")
+ @ApiResponse(responseCode = "500", description = "Internal error",
+ content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
+ @ApiResponse(responseCode = "400", description = "Incorrect configuration",
+ content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
@Produces({
MediaType.TEXT_PLAIN, // todo: IGNITE-17082
MediaType.PROBLEM_JSON
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/configuration/NodeConfigurationApi.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/configuration/NodeConfigurationApi.java
index b5507582d..de001d57e 100644
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/configuration/NodeConfigurationApi.java
+++ b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/configuration/NodeConfigurationApi.java
@@ -28,7 +28,6 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
-import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.concurrent.CompletableFuture;
import org.apache.ignite.internal.rest.api.Problem;
@@ -47,17 +46,14 @@ public interface NodeConfigurationApi {
* @return the whole node configuration in HOCON format.
*/
@Operation(operationId = "getNodeConfiguration")
- @ApiResponses({
- @ApiResponse(responseCode = "200",
- content = @Content(mediaType = MediaType.TEXT_PLAIN,
- schema = @Schema(type = "string")),
- description = "Whole node configuration"),
- @ApiResponse(responseCode = "500", description = "Internal error",
- content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class))),
- @ApiResponse(responseCode = "400", description = "Incorrect configuration",
- content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
-
- })
+ @ApiResponse(responseCode = "200",
+ content = @Content(mediaType = MediaType.TEXT_PLAIN,
+ schema = @Schema(type = "string")),
+ description = "Whole node configuration")
+ @ApiResponse(responseCode = "500", description = "Internal error",
+ content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
+ @ApiResponse(responseCode = "400", description = "Incorrect configuration",
+ content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
@Produces({
MediaType.TEXT_PLAIN, // todo: IGNITE-17082
MediaType.PROBLEM_JSON
@@ -72,17 +68,14 @@ public interface NodeConfigurationApi {
* @return system configuration in HOCON format represented by given path.
*/
@Operation(operationId = "getNodeConfigurationByPath")
- @ApiResponses({
- @ApiResponse(responseCode = "200",
- content = @Content(mediaType = MediaType.TEXT_PLAIN,
- schema = @Schema(type = "string")),
- description = "Configuration represented by path"),
- @ApiResponse(responseCode = "500", description = "Internal error",
- content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class))),
- @ApiResponse(responseCode = "400", description = "Incorrect configuration",
- content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
-
- })
+ @ApiResponse(responseCode = "200",
+ content = @Content(mediaType = MediaType.TEXT_PLAIN,
+ schema = @Schema(type = "string")),
+ description = "Configuration represented by path")
+ @ApiResponse(responseCode = "500", description = "Internal error",
+ content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
+ @ApiResponse(responseCode = "400", description = "Incorrect configuration",
+ content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
@Produces({
MediaType.TEXT_PLAIN, // todo: IGNITE-17082
MediaType.PROBLEM_JSON
@@ -96,14 +89,11 @@ public interface NodeConfigurationApi {
* @param updatedConfiguration the node configuration to update. This is represented as a plain text.
*/
@Operation(operationId = "updateNodeConfiguration")
- @ApiResponses({
- @ApiResponse(responseCode = "200", description = "Configuration updated"),
- @ApiResponse(responseCode = "500", description = "Internal error",
- content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class))),
- @ApiResponse(responseCode = "400", description = "Incorrect configuration",
- content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
-
- })
+ @ApiResponse(responseCode = "200", description = "Configuration updated")
+ @ApiResponse(responseCode = "500", description = "Internal error",
+ content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
+ @ApiResponse(responseCode = "400", description = "Incorrect configuration",
+ content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = @Schema(implementation = Problem.class)))
@Consumes(MediaType.TEXT_PLAIN) // todo: IGNITE-17082
@Produces(MediaType.PROBLEM_JSON)
@Patch
diff --git a/modules/rest/openapi/openapi.yaml b/modules/rest/openapi/openapi.yaml
index 797c2e4ce..c609314a7 100644
--- a/modules/rest/openapi/openapi.yaml
+++ b/modules/rest/openapi/openapi.yaml
@@ -61,6 +61,48 @@ paths:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
+ /management/v1/cluster/topology/logical:
+ get:
+ tags:
+ - topology
+ operationId: logical
+ parameters: []
+ responses:
+ "200":
+ description: Logical topology returned
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/ClusterNode'
+ "500":
+ description: Internal error
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/Problem'
+ /management/v1/cluster/topology/physical:
+ get:
+ tags:
+ - topology
+ operationId: physical
+ parameters: []
+ responses:
+ "200":
+ description: Physical topology returned
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/ClusterNode'
+ "500":
+ description: Internal error
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/Problem'
/management/v1/configuration/cluster:
get:
tags:
@@ -252,6 +294,19 @@ paths:
$ref: '#/components/schemas/Problem'
components:
schemas:
+ ClusterNode:
+ required:
+ - address
+ - id
+ - name
+ type: object
+ properties:
+ id:
+ type: string
+ name:
+ type: string
+ address:
+ $ref: '#/components/schemas/NetworkAddress'
ClusterState:
required:
- clusterTag
@@ -317,6 +372,21 @@ components:
type: string
clusterName:
type: string
+ NetworkAddress:
+ required:
+ - consistentId
+ - host
+ - port
+ type: object
+ properties:
+ host:
+ type: string
+ port:
+ type: integer
+ format: int32
+ consistentId:
+ type: string
+ nullable: true
NodeState:
required:
- name
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java b/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java
index be5f4b1bf..b1d7b2747 100644
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java
+++ b/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java
@@ -35,6 +35,7 @@ import org.apache.ignite.internal.logger.IgniteLogger;
import org.apache.ignite.internal.logger.Loggers;
import org.apache.ignite.internal.manager.IgniteComponent;
import org.apache.ignite.internal.rest.api.cluster.ClusterManagementApi;
+import org.apache.ignite.internal.rest.api.cluster.TopologyApi;
import org.apache.ignite.internal.rest.api.configuration.ClusterConfigurationApi;
import org.apache.ignite.internal.rest.api.configuration.NodeConfigurationApi;
import org.apache.ignite.internal.rest.api.node.NodeManagementApi;
@@ -56,7 +57,8 @@ import org.jetbrains.annotations.Nullable;
ClusterConfigurationApi.class,
NodeConfigurationApi.class,
ClusterManagementApi.class,
- NodeManagementApi.class
+ NodeManagementApi.class,
+ TopologyApi.class
})
public class RestComponent implements IgniteComponent {
/** Default port. */
diff --git a/modules/runner/pom.xml b/modules/runner/pom.xml
index 33b3158c2..0565870ae 100644
--- a/modules/runner/pom.xml
+++ b/modules/runner/pom.xml
@@ -221,6 +221,12 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
+
+ <dependency>
+ <groupId>com.jayway.jsonpath</groupId>
+ <artifactId>json-path-assert</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/AbstractRestTestBase.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/AbstractRestTestBase.java
new file mode 100644
index 000000000..fea4aa8bb
--- /dev/null
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/AbstractRestTestBase.java
@@ -0,0 +1,173 @@
+/*
+ * 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.rest;
+
+import static org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
+import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgnitionManager;
+import org.apache.ignite.internal.testframework.WorkDirectory;
+import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Base class for integration REST test.
+ */
+@ExtendWith(WorkDirectoryExtension.class)
+abstract class AbstractRestTestBase {
+ /** Network ports of the test nodes. */
+ static final int[] PORTS = {3344, 3345, 3346};
+
+ /** HTTP host and port url part. */
+ static final String HTTP_HOST_PORT = "http://localhost:10300";
+
+ /** Nodes bootstrap configuration. */
+ final Map<String, String> nodesBootstrapCfg = new LinkedHashMap<>();
+
+ /** Node names that is starting (before init) or started (after init). */
+ final List<String> nodeNames = new ArrayList<>();
+
+ /** Collection of started nodes. */
+ final List<Ignite> startedNodes = new ArrayList<>();
+
+ /** Collection of starting nodes. */
+ final List<CompletableFuture<Ignite>> startingNodes = new ArrayList<CompletableFuture<Ignite>>();
+
+ /** HTTP client that is expected to be defined in subclasses. */
+ HttpClient client;
+
+ /** Path to the working directory. */
+ @WorkDirectory
+ private Path workDir;
+
+ static HttpRequest get(String path) {
+ return HttpRequest.newBuilder(URI.create(HTTP_HOST_PORT + path)).build();
+ }
+
+ static HttpRequest patch(String path, String body) {
+ return HttpRequest.newBuilder(URI.create(HTTP_HOST_PORT + path))
+ .method("PATCH", BodyPublishers.ofString(body))
+ .build();
+ }
+
+ static HttpRequest post(String path, String body) {
+ return HttpRequest.newBuilder(URI.create(HTTP_HOST_PORT + path))
+ .header("content-type", "application/json")
+ .POST(BodyPublishers.ofString(body))
+ .build();
+ }
+
+ /**
+ * Before each.
+ */
+ @BeforeEach
+ void setUp(TestInfo testInfo) throws IOException, InterruptedException {
+ client = HttpClient.newBuilder().build();
+
+ startAllNodesWithoutInit(testInfo);
+ }
+
+ private void startAllNodesWithoutInit(TestInfo testInfo) {
+ String node0Name = testNodeName(testInfo, PORTS[0]);
+ String node1Name = testNodeName(testInfo, PORTS[1]);
+ String node2Name = testNodeName(testInfo, PORTS[2]);
+
+ nodesBootstrapCfg.put(
+ node0Name,
+ "{\n"
+ + " network: {\n"
+ + " port: " + PORTS[0] + ",\n"
+ + " nodeFinder: {\n"
+ + " netClusterNodes: [ \"localhost:3344\", \"localhost:3345\", \"localhost:3346\" ]\n"
+ + " }\n"
+ + " }\n"
+ + "}"
+ );
+
+ nodesBootstrapCfg.put(
+ node1Name,
+ "{\n"
+ + " network: {\n"
+ + " port: " + PORTS[1] + ",\n"
+ + " nodeFinder: {\n"
+ + " netClusterNodes: [ \"localhost:3344\", \"localhost:3345\", \"localhost:3346\" ]\n"
+ + " }\n"
+ + " }\n"
+ + "}"
+ );
+
+ nodesBootstrapCfg.put(
+ node2Name,
+ "{\n"
+ + " network: {\n"
+ + " port: " + PORTS[2] + ",\n"
+ + " nodeFinder: {\n"
+ + " netClusterNodes: [ \"localhost:3344\", \"localhost:3345\", \"localhost:3346\" ]\n"
+ + " }\n"
+ + " }\n"
+ + "}"
+ );
+
+ for (Map.Entry<String, String> e : nodesBootstrapCfg.entrySet()) {
+ startNodeWithoutInit(e.getKey(), name -> IgnitionManager.start(name, e.getValue(), workDir.resolve(name)));
+ }
+ }
+
+ /**
+ * After each.
+ */
+ @AfterEach
+ void tearDown() throws Exception {
+ List<AutoCloseable> closeables = nodeNames.stream()
+ .map(name -> (AutoCloseable) () -> IgnitionManager.stop(name))
+ .collect(Collectors.toList());
+
+ IgniteUtils.closeAll(closeables);
+ }
+
+ void startNodeWithoutInit(String nodeName, Function<String, CompletableFuture<Ignite>> starter) {
+ nodeNames.add(nodeName);
+
+ CompletableFuture<Ignite> future = starter.apply(nodeName);
+
+ startingNodes.add(future);
+ }
+
+ void checkAllNodesStarted() {
+ startingNodes.forEach(future -> assertThat(future, willCompleteSuccessfully()));
+ }
+}
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java
new file mode 100644
index 000000000..f1ad9091d
--- /dev/null
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java
@@ -0,0 +1,191 @@
+/*
+ * 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.rest;
+
+import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigFactory;
+import java.io.IOException;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+
+/**
+ * Test for the REST endpoints in case cluster is initialized.
+ */
+public class ItInitializedClusterRestTest extends AbstractRestTestBase {
+ @BeforeEach
+ void setUp(TestInfo testInfo) throws IOException, InterruptedException {
+ super.setUp(testInfo);
+
+ // For each test case the cluster is already initialized
+ HttpResponse<String> response = client.send(
+ post("/management/v1/cluster/init",
+ "{\"metaStorageNodes\": [\"" + nodeNames.get(0) + "\"], \"clusterName\": \"cluster\"}"),
+ BodyHandlers.ofString()
+ );
+
+ assertThat(response.statusCode(), is(200));
+ checkAllNodesStarted();
+ }
+
+ @Test
+ @DisplayName("Node configuration is available when the cluster is initialized")
+ void nodeConfiguration() throws IOException, InterruptedException {
+ // When GET /management/v1/configuration/node
+ HttpResponse<String> response = client.send(
+ get("/management/v1/configuration/node"),
+ BodyHandlers.ofString()
+ );
+
+ // Expect node configuration can be parsed to hocon format
+ Config config = ConfigFactory.parseString(response.body());
+ // And has rest.port config value
+ assertThat(config.getInt("rest.port"), is(equalTo(10300)));
+ }
+
+ @Test
+ @DisplayName("Node configuration can be changed when the cluster is initialized")
+ void nodeConfigurationUpdate() throws IOException, InterruptedException {
+ // When PATCH /management/v1/configuration/node rest.port=10333
+ HttpResponse<String> pathResponce = client.send(
+ patch("/management/v1/configuration/node", "rest.port=10333"),
+ BodyHandlers.ofString()
+ );
+ // Then
+ assertThat(pathResponce.statusCode(), is(200));
+
+ // And GET /management/v1/configuration/node
+ HttpResponse<String> getResponse = client.send(
+ get("/management/v1/configuration/node"),
+ BodyHandlers.ofString()
+ );
+
+ // Then node configuration can be parsed to hocon format
+ Config config = ConfigFactory.parseString(getResponse.body());
+ // And rest.port is updated
+ assertThat(config.getInt("rest.port"), is(equalTo(10333)));
+ }
+
+ @Test
+ @DisplayName("Cluster configuration is available when the cluster is initialized")
+ void clusterConfiguration() throws IOException, InterruptedException {
+ // When GET /management/v1/configuration/cluster
+ HttpResponse<String> response = client.send(get("/management/v1/configuration/cluster"), BodyHandlers.ofString());
+
+ // Then cluster configuration is not available
+ assertThat(response.statusCode(), is(200));
+ // And configuration can be parsed to hocon format
+ Config config = ConfigFactory.parseString(response.body());
+ // And rocksDb.defaultRegion.cache can be read
+ assertThat(config.getString("rocksDb.defaultRegion.cache"), is(equalTo("lru")));
+ }
+
+ @Test
+ @DisplayName("Cluster configuration can be updated when the cluster is initialized")
+ void clusterConfigurationUpdate() throws IOException, InterruptedException {
+ // When PATCH /management/v1/configuration/cluster
+ HttpResponse<String> patchRequest = client.send(
+ patch("/management/v1/configuration/cluster", "rocksDb.defaultRegion.writeBufferSize=1024"),
+ BodyHandlers.ofString()
+ );
+
+ // Then
+ assertThat(patchRequest.statusCode(), is(200));
+ // And value was updated
+ HttpResponse<String> getResponse = client.send(get("/management/v1/configuration/cluster"), BodyHandlers.ofString());
+ assertThat(getResponse.statusCode(), is(200));
+ // And
+ Config config = ConfigFactory.parseString(getResponse.body());
+ assertThat(config.getInt("rocksDb.defaultRegion.writeBufferSize"), is(1024));
+ }
+
+ @Test
+ @DisplayName("Logical topology is available on initialized cluster")
+ void logicalTopology() throws IOException, InterruptedException {
+ // When GET /management/v1/cluster/topology/logical
+ HttpResponse<String> response = client.send(get("/management/v1/cluster/topology/logical"), BodyHandlers.ofString());
+
+ // Then
+ assertThat(response.statusCode(), is(200));
+ assertAll(
+ () -> assertThat(response.body(), hasJsonPath("$", hasSize(3))),
+ () -> assertThat(response.body(), hasJsonPath("$[0:2].name")),
+ () -> assertThat(response.body(), hasJsonPath("$[0:2].id")),
+ () -> assertThat(response.body(), hasJsonPath("$[0:2].address.host")),
+ () -> assertThat(response.body(), hasJsonPath("$[0:2].address.port"))
+ );
+ }
+
+ @Test
+ @DisplayName("Physical topology is available on initialized cluster")
+ void physicalTopology() throws IOException, InterruptedException {
+ // When GET /management/v1/cluster/topology/physical
+ HttpResponse<String> response = client.send(get("/management/v1/cluster/topology/physical"), BodyHandlers.ofString());
+
+ // Then
+ assertThat(response.statusCode(), is(200));
+ assertAll(
+ () -> assertThat(response.body(), hasJsonPath("$", hasSize(3))),
+ () -> assertThat(response.body(), hasJsonPath("$[0:2].name")),
+ () -> assertThat(response.body(), hasJsonPath("$[0:2].id")),
+ () -> assertThat(response.body(), hasJsonPath("$[0:2].address.host")),
+ () -> assertThat(response.body(), hasJsonPath("$[0:2].address.port"))
+ );
+ }
+
+ @Test
+ @DisplayName("Cluster state is available on initialized cluster")
+ void clusterState() throws IOException, InterruptedException {
+ // When GET /management/v1/cluster/status
+ HttpResponse<String> response = client.send(get("/management/v1/cluster/state"), BodyHandlers.ofString());
+
+ // Then
+ assertThat(response.statusCode(), is(200));
+ assertAll(
+ () -> assertThat(response.body(), hasJsonPath("$.clusterTag.clusterName", is(equalTo("cluster")))),
+ () -> assertThat(response.body(), hasJsonPath("$.clusterTag.clusterId")),
+ () -> assertThat(response.body(), hasJsonPath("$.igniteVersion")),
+ () -> assertThat(response.body(), hasJsonPath("$.msNodes")),
+ () -> assertThat(response.body(), hasJsonPath("$.cmgNodes"))
+ );
+ }
+
+ @Test
+ @DisplayName("Node state is available on initialized cluster")
+ void nodeState() throws IOException, InterruptedException {
+ // When GET /management/v1/node/status
+ HttpResponse<String> response = client.send(get("/management/v1/node/state"), BodyHandlers.ofString());
+
+ // Then
+ assertThat(response.statusCode(), is(200));
+ assertAll(
+ () -> assertThat(response.body(), hasJsonPath("$.name")),
+ () -> assertThat(response.body(), hasJsonPath("$.state", is(equalTo("STARTED"))))
+ );
+ }
+}
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItNotInitializedClusterRestTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItNotInitializedClusterRestTest.java
new file mode 100644
index 000000000..a6a97ec03
--- /dev/null
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItNotInitializedClusterRestTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.rest;
+
+import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import com.typesafe.config.Config;
+import com.typesafe.config.ConfigFactory;
+import java.io.IOException;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Test for the REST endpoints in case cluster is not initialized.
+ */
+@ExtendWith(WorkDirectoryExtension.class)
+public class ItNotInitializedClusterRestTest extends AbstractRestTestBase {
+
+ @Test
+ @DisplayName("Node configuration is available when the cluster in not initialized")
+ void nodeConfiguration() throws IOException, InterruptedException {
+ // When GET /management/v1/configuration/node
+ HttpResponse<String> response = client.send(
+ get("/management/v1/configuration/node"),
+ BodyHandlers.ofString()
+ );
+
+ // Expect node configuration can be parsed to hocon format
+ Config config = ConfigFactory.parseString(response.body());
+ // And has rest.port config value
+ assertThat(config.getInt("rest.port"), is(equalTo(10300)));
+ }
+
+ @Test
+ @DisplayName("Node configuration can be changed when the cluster in not initialized")
+ void nodeConfigurationUpdate() throws IOException, InterruptedException {
+ // When PATCH /management/v1/configuration/node rest.port=10333
+ HttpResponse<String> pathResponce = client.send(
+ patch("/management/v1/configuration/node", "rest.port=10333"),
+ BodyHandlers.ofString()
+ );
+ // Then
+ assertThat(pathResponce.statusCode(), is(200));
+
+ // And GET /management/v1/configuration/node
+ HttpResponse<String> getResponse = client.send(
+ get("/management/v1/configuration/node"),
+ BodyHandlers.ofString()
+ );
+
+ // Then node configuration can be parsed to hocon format
+ Config config = ConfigFactory.parseString(getResponse.body());
+ // And rest.port is updated
+ assertThat(config.getInt("rest.port"), is(equalTo(10333)));
+ }
+
+ @Test
+ @DisplayName("Cluster configuration is not available on not initialized cluster")
+ void clusterConfiguration() throws IOException, InterruptedException {
+ // When GET /management/v1/configuration/cluster
+ HttpResponse<String> response = client.send(get("/management/v1/configuration/cluster"), BodyHandlers.ofString());
+
+ // Expect cluster configuration is not available
+ assertThat(response.statusCode(), is(500));
+ // todo: check that human-readable problem json is returned
+ // "{\"message\":\"Internal Server Error\",\"_embedded\":{\"errors\":[{\"message\":\"Internal Server Error: null\"}]},
+ // \"_links\":{\"self\":{\"href\":\"/management/v1/configuration/cluster\",\"templated\":false}}}"
+ }
+
+ @Test
+ @DisplayName("Cluster configuration could not be updated on not initialized cluster")
+ void clusterConfigurationUpdate() throws IOException, InterruptedException {
+ // When PATCH /management/v1/configuration/cluster
+ HttpResponse<String> response = client.send(
+ patch("/management/v1/configuration/cluster", "any.key=any-value"),
+ BodyHandlers.ofString()
+ );
+
+ // Expect
+ assertThat(response.statusCode(), is(500)); //todo
+ }
+
+ @Test
+ @DisplayName("Logical topology is not available on not initialized cluster")
+ void logicalTopology() throws IOException, InterruptedException {
+ // When GET /management/v1/cluster/topology/logical
+ HttpResponse<String> response = client.send(get("/management/v1/cluster/topology/logical"), BodyHandlers.ofString());
+
+ // Then
+ assertThat(response.statusCode(), is(404));
+ }
+
+ @Test
+ @DisplayName("Physical topology is available on not initialized cluster")
+ void physicalTopology() throws IOException, InterruptedException {
+ // When GET /management/v1/cluster/topology/physical
+ HttpResponse<String> response = client.send(get("/management/v1/cluster/topology/physical"), BodyHandlers.ofString());
+
+ // Then
+ assertThat(response.statusCode(), is(200));
+ assertAll(
+ () -> assertThat(response.body(), hasJsonPath("$", hasSize(3))),
+ () -> assertThat(response.body(), hasJsonPath("$[0:2].name")),
+ () -> assertThat(response.body(), hasJsonPath("$[0:2].id")),
+ () -> assertThat(response.body(), hasJsonPath("$[0:2].address.host")),
+ () -> assertThat(response.body(), hasJsonPath("$[0:2].address.port"))
+ );
+ }
+
+ @Test
+ @DisplayName("Node state is available on not initialized cluster and it is STARTING")
+ void nodeState() throws IOException, InterruptedException {
+ // When GET /management/v1/node/state
+ HttpResponse<String> response = client.send(get("/management/v1/node/state"), BodyHandlers.ofString());
+
+ // Then
+ assertThat(response.statusCode(), is(200));
+ assertAll(
+ () -> assertThat(response.body(), hasJsonPath("$.name")),
+ () -> assertThat(response.body(), hasJsonPath("$.state", is(equalTo("STARTING"))))
+ );
+ }
+
+ @Test
+ @DisplayName("Cluster state is not available on not initialized cluster")
+ void clusterState() throws IOException, InterruptedException {
+ // When GET /management/v1/cluster/state
+ HttpResponse<String> response = client.send(get("/management/v1/cluster/state"), BodyHandlers.ofString());
+
+ // Then
+ assertThat(response.statusCode(), is(404));
+ }
+}
diff --git a/parent/pom.xml b/parent/pom.xml
index 96f1d6d65..c849c3346 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -514,6 +514,12 @@
<version>${jsonpath.version}</version>
</dependency>
+ <dependency>
+ <groupId>com.jayway.jsonpath</groupId>
+ <artifactId>json-path-assert</artifactId>
+ <version>${jsonpath.version}</version>
+ </dependency>
+
<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>