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/05/04 09:29:53 UTC
[ignite-3] branch main updated: IGNITE-16513 Add an integration test for 'cluster init' CLI command (#794)
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 dda822680 IGNITE-16513 Add an integration test for 'cluster init' CLI command (#794)
dda822680 is described below
commit dda822680855dc1986510ee39474d7688f52fa0d
Author: Roman Puchkovskiy <ro...@gmail.com>
AuthorDate: Wed May 4 13:29:48 2022 +0400
IGNITE-16513 Add an integration test for 'cluster init' CLI command (#794)
---
.../src/main/java/org/apache/ignite/Ignition.java | 15 ++
.../java/org/apache/ignite/IgnitionManager.java | 6 +
.../ignite/cli/AbstractCliIntegrationTest.java | 49 +++++
.../apache/ignite/cli/ItClusterCommandTest.java | 198 +++++++++++++++++++++
.../org/apache/ignite/cli/ItConfigCommandTest.java | 26 +--
.../java/org/apache/ignite/cli/NoOpHandler.java} | 21 ++-
.../resources/hardcoded-ports-config.json | 13 ++
.../org/apache/ignite/cli/AbstractCliTest.java | 4 +-
.../ignite/internal/compute/ItComputeTest.java | 1 +
.../org/apache/ignite/internal/app/IgniteImpl.java | 3 +
10 files changed, 300 insertions(+), 36 deletions(-)
diff --git a/modules/api/src/main/java/org/apache/ignite/Ignition.java b/modules/api/src/main/java/org/apache/ignite/Ignition.java
index cba3b8b33..2ea9ea63a 100644
--- a/modules/api/src/main/java/org/apache/ignite/Ignition.java
+++ b/modules/api/src/main/java/org/apache/ignite/Ignition.java
@@ -32,6 +32,9 @@ public interface Ignition {
/**
* Starts an Ignite node with an optional bootstrap configuration from a HOCON file.
*
+ * <p>When this method returns, the node is partially started and ready to accept the init command (that is, its
+ * REST endpoint is functional).
+ *
* @param name Name of the node. Must not be {@code null}.
* @param configPath Path to the node configuration in the HOCON format. Can be {@code null}.
* @param workDir Work directory for the started node. Must not be {@code null}.
@@ -44,6 +47,9 @@ public interface Ignition {
* Starts an Ignite node with an optional bootstrap configuration from a HOCON file, with an optional class loader for further usage by
* {@link java.util.ServiceLoader}.
*
+ * <p>When this method returns, the node is partially started and ready to accept the init command (that is, its
+ * REST endpoint is functional).
+ *
* @param name Name of the node. Must not be {@code null}.
* @param configPath Path to the node configuration in the HOCON format. Can be {@code null}.
* @param workDir Work directory for the started node. Must not be {@code null}.
@@ -57,6 +63,9 @@ public interface Ignition {
/**
* Starts an Ignite node with an optional bootstrap configuration from a URL linking to HOCON configs.
*
+ * <p>When this method returns, the node is partially started and ready to accept the init command (that is, its
+ * REST endpoint is functional).
+ *
* @param name Name of the node. Must not be {@code null}.
* @param cfgUrl URL linking to the node configuration in the HOCON format. Can be {@code null}.
* @param workDir Work directory for the started node. Must not be {@code null}.
@@ -68,6 +77,9 @@ public interface Ignition {
/**
* Starts an Ignite node with an optional bootstrap configuration from an input stream with HOCON configs.
*
+ * <p>When this method returns, the node is partially started and ready to accept the init command (that is, its
+ * REST endpoint is functional).
+ *
* @param name Name of the node. Must not be {@code null}.
* @param config Optional node configuration based on
* {@link org.apache.ignite.configuration.schemas.network.NetworkConfigurationSchema}.
@@ -94,6 +106,9 @@ public interface Ignition {
/**
* Starts an Ignite node with the default configuration.
*
+ * <p>When this method returns, the node is partially started and ready to accept the init command (that is, its
+ * REST endpoint is functional).
+ *
* @param name Name of the node. Must not be {@code null}.
* @param workDir Work directory for the started node. Must not be {@code null}.
* @return Completable future that resolves into an Ignite node after all components are started and the cluster initialization is
diff --git a/modules/api/src/main/java/org/apache/ignite/IgnitionManager.java b/modules/api/src/main/java/org/apache/ignite/IgnitionManager.java
index 946210c29..87ac37bb6 100644
--- a/modules/api/src/main/java/org/apache/ignite/IgnitionManager.java
+++ b/modules/api/src/main/java/org/apache/ignite/IgnitionManager.java
@@ -39,6 +39,9 @@ public class IgnitionManager {
/**
* Starts an Ignite node with an optional bootstrap configuration from an input stream with HOCON configs.
*
+ * <p>When this method returns, the node is partially started and ready to accept the init command (that is, its
+ * REST endpoint is functional).
+ *
* @param nodeName Name of the node. Must not be {@code null}.
* @param configStr Optional node configuration based on
* {@link org.apache.ignite.configuration.schemas.network.NetworkConfigurationSchema}.
@@ -79,6 +82,9 @@ public class IgnitionManager {
/**
* Starts an Ignite node with an optional bootstrap configuration from a HOCON file.
*
+ * <p>When this method returns, the node is partially started and ready to accept the init command (that is, its
+ * REST endpoint is functional).
+ *
* @param nodeName Name of the node. Must not be {@code null}.
* @param cfgPath Path to the node configuration in the HOCON format. Can be {@code null}.
* @param workDir Work directory for the started node. Must not be {@code null}.
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/AbstractCliIntegrationTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/AbstractCliIntegrationTest.java
new file mode 100644
index 000000000..5247474ad
--- /dev/null
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/AbstractCliIntegrationTest.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;
+
+import io.micronaut.context.ApplicationContext;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import org.apache.ignite.cli.spec.IgniteCliSpec;
+import picocli.CommandLine;
+
+/**
+ * Base class for CLI-related integration tests.
+ */
+public abstract class AbstractCliIntegrationTest extends AbstractCliTest {
+ /** stderr. */
+ protected final ByteArrayOutputStream err = new ByteArrayOutputStream();
+
+ /** stdout. */
+ protected final ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ /**
+ * Creates a new command line interpreter.
+ *
+ * @param applicationCtx DI context.
+ * @return New command line instance.
+ */
+ protected final CommandLine cmd(ApplicationContext applicationCtx) {
+ CommandLine.IFactory factory = new CommandFactory(applicationCtx);
+
+ return new CommandLine(IgniteCliSpec.class, factory)
+ .setErr(new PrintWriter(err, true))
+ .setOut(new PrintWriter(out, true));
+ }
+}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItClusterCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItClusterCommandTest.java
new file mode 100644
index 000000000..fbfd6a1ea
--- /dev/null
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItClusterCommandTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static java.util.stream.Collectors.joining;
+import static org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import io.micronaut.context.ApplicationContext;
+import io.micronaut.context.env.Environment;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import org.apache.ignite.IgnitionManager;
+import org.apache.ignite.internal.testframework.WorkDirectory;
+import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Integration test for {@code ignite cluster} commands.
+ */
+@ExtendWith(WorkDirectoryExtension.class)
+class ItClusterCommandTest extends AbstractCliIntegrationTest {
+ private static final Node FIRST_NODE = new Node(0, 10100, 10300);
+ private static final Node SECOND_NODE = new Node(1, 11100, 11300);
+ private static final Node THIRD_NODE = new Node(2, 12100, 12300);
+ private static final Node FOURTH_NODE = new Node(3, 13100, 13300);
+
+ private static final List<Node> NODES = List.of(FIRST_NODE, SECOND_NODE, THIRD_NODE, FOURTH_NODE);
+
+ private static final String NL = System.lineSeparator();
+
+ private static final Logger topologyLogger = Logger.getLogger("org.apache.ignite.network.scalecube.ScaleCubeTopologyService");
+
+ /** DI context. */
+ private ApplicationContext ctx;
+
+ @BeforeEach
+ void setup(@WorkDirectory Path workDir, TestInfo testInfo) throws Exception {
+ CountDownLatch allNodesAreInPhysicalTopology = new CountDownLatch(1);
+
+ Handler physicalTopologyWaiter = physicalTopologyWaiter(allNodesAreInPhysicalTopology);
+ topologyLogger.addHandler(physicalTopologyWaiter);
+
+ try {
+ startClusterWithoutInit(workDir, testInfo);
+
+ waitTillAllNodesJoinPhysicalTopology(allNodesAreInPhysicalTopology);
+ } finally {
+ topologyLogger.removeHandler(physicalTopologyWaiter);
+ }
+
+ ctx = ApplicationContext.run(Environment.TEST);
+ }
+
+ private Handler physicalTopologyWaiter(CountDownLatch physicalTopologyIsFull) {
+ return new NoOpHandler() {
+ @Override
+ public void publish(LogRecord record) {
+ if (record.getMessage().contains("Topology snapshot [nodes=" + NODES.size() + "]")) {
+ physicalTopologyIsFull.countDown();
+ }
+ }
+ };
+ }
+
+ private void startClusterWithoutInit(Path workDir, TestInfo testInfo) {
+ NODES.parallelStream().forEach(node -> startNodeWithoutInit(node, workDir, testInfo));
+ }
+
+ private void waitTillAllNodesJoinPhysicalTopology(CountDownLatch allNodesAreInPhysicalTopology) throws InterruptedException {
+ assertTrue(allNodesAreInPhysicalTopology.await(10, SECONDS), "Physical topology was not formed in time");
+ }
+
+ /**
+ * Initiates node start and waits till it makes its REST endpoints available, but does NOT invoke init.
+ *
+ * @param node node
+ * @param workDir working directory
+ * @param testInfo test info
+ */
+ private void startNodeWithoutInit(Node node, Path workDir, TestInfo testInfo) {
+ String nodeName = testNodeName(testInfo, node.nodeIndex);
+
+ String config;
+ try {
+ config = configJsonFor(node);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot load config", e);
+ }
+
+ IgnitionManager.start(nodeName, config, workDir.resolve(nodeName));
+ }
+
+ private String configJsonFor(Node node) throws IOException {
+ String config = Files.readString(Path.of("src/integrationTest/resources/hardcoded-ports-config.json"));
+ config = config.replaceAll("<NETWORK_PORT>", String.valueOf(node.networkPort));
+ config = config.replaceAll("<REST_PORT>", String.valueOf(node.restPort));
+ config = config.replaceAll("<NET_CLUSTER_NODES>", netClusterNodes());
+
+ return config;
+ }
+
+ private String netClusterNodes() {
+ return NODES.stream()
+ .map(Node::networkHostPort)
+ .map(s -> "\"" + s + "\"")
+ .collect(joining(", ", "[", "]"));
+ }
+
+ @AfterEach
+ void tearDown(TestInfo testInfo) {
+ for (int i = 0; i < NODES.size(); i++) {
+ IgnitionManager.stop(testNodeName(testInfo, i));
+ }
+
+ if (ctx != null) {
+ ctx.stop();
+ }
+ }
+
+ /**
+ * Starts a cluster of 4 nodes and executes init command on it. First node is used to issue the command via REST endpoint,
+ * second will host the meta-storage RAFT group, third will host the Cluster Management RAFT Group (CMG), fourth
+ * will be just a node.
+ *
+ * @param testInfo test info (used to derive node names)
+ */
+ @Test
+ void initClusterWithNodesOfDifferentRoles(TestInfo testInfo) {
+ int exitCode = cmd(ctx).execute(
+ "cluster", "init",
+ "--node-endpoint", FIRST_NODE.restHostPort(),
+ "--meta-storage-node", SECOND_NODE.nodeName(testInfo),
+ "--cmg-node", THIRD_NODE.nodeName(testInfo)
+ );
+
+ assertThat(
+ String.format("Wrong exit code; std is '%s', stderr is '%s'", out.toString(UTF_8), err.toString(UTF_8)),
+ exitCode, is(0)
+ );
+ assertThat(out.toString(UTF_8), is("Cluster was initialized successfully." + NL));
+
+ // TODO: when IGNITE-16526 is implemented, also check that the logical topology contains all 4 nodes
+ }
+
+ private static class Node {
+ private final int nodeIndex;
+ private final int networkPort;
+ private final int restPort;
+
+ private Node(int nodeIndex, int networkPort, int restPort) {
+ this.nodeIndex = nodeIndex;
+ this.networkPort = networkPort;
+ this.restPort = restPort;
+ }
+
+ String nodeName(TestInfo testInfo) {
+ return testNodeName(testInfo, nodeIndex);
+ }
+
+ String networkHostPort() {
+ return "localhost:" + networkPort;
+ }
+
+ String restHostPort() {
+ return "localhost:" + restPort;
+ }
+ }
+}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItConfigCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItConfigCommandTest.java
index 41c5ef34f..8a9b404ca 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItConfigCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItConfigCommandTest.java
@@ -32,8 +32,6 @@ import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import io.micronaut.context.ApplicationContext;
import io.micronaut.context.env.Environment;
-import java.io.ByteArrayOutputStream;
-import java.io.PrintWriter;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -41,7 +39,6 @@ import net.minidev.json.JSONObject;
import net.minidev.json.JSONValue;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgnitionManager;
-import org.apache.ignite.cli.spec.IgniteCliSpec;
import org.apache.ignite.internal.app.IgniteImpl;
import org.apache.ignite.internal.testframework.WorkDirectory;
import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
@@ -50,22 +47,15 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.extension.ExtendWith;
-import picocli.CommandLine;
/**
* Integration test for {@code ignite config} commands.
*/
@ExtendWith(WorkDirectoryExtension.class)
-public class ItConfigCommandTest extends AbstractCliTest {
+public class ItConfigCommandTest extends AbstractCliIntegrationTest {
/** DI context. */
private ApplicationContext ctx;
- /** stderr. */
- private final ByteArrayOutputStream err = new ByteArrayOutputStream();
-
- /** stdout. */
- private final ByteArrayOutputStream out = new ByteArrayOutputStream();
-
/** Node. */
private IgniteImpl node;
@@ -186,20 +176,6 @@ public class ItConfigCommandTest extends AbstractCliTest {
assertFalse(outResult.containsKey("node"));
}
- /**
- * Creates a new command line interpreter.
- *
- * @param applicationCtx DI context.
- * @return New command line instance.
- */
- private CommandLine cmd(ApplicationContext applicationCtx) {
- CommandLine.IFactory factory = new CommandFactory(applicationCtx);
-
- return new CommandLine(IgniteCliSpec.class, factory)
- .setErr(new PrintWriter(err, true))
- .setOut(new PrintWriter(out, true));
- }
-
/**
* Reset stderr and stdout streams.
*/
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
similarity index 69%
copy from modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java
copy to modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
index 151ba472a..32fbdbfdb 100644
--- a/modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/NoOpHandler.java
@@ -17,17 +17,20 @@
package org.apache.ignite.cli;
-import org.junit.jupiter.api.BeforeAll;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
/**
- * Base class for any CLI tests.
+ * Adapter for {@link Handler} with empty implementations of all methods but {@link Handler#publish(LogRecord)}.
*/
-public class AbstractCliTest {
- /**
- * Sets up a dumb terminal before tests.
- */
- @BeforeAll
- private static void beforeAll() {
- System.setProperty("org.jline.terminal.dumb", "true");
+public abstract class NoOpHandler extends Handler {
+ @Override
+ public void flush() {
+ // no-op
+ }
+
+ @Override
+ public void close() throws SecurityException {
+ // no-op
}
}
diff --git a/modules/cli/src/integrationTest/resources/hardcoded-ports-config.json b/modules/cli/src/integrationTest/resources/hardcoded-ports-config.json
new file mode 100644
index 000000000..3873c574f
--- /dev/null
+++ b/modules/cli/src/integrationTest/resources/hardcoded-ports-config.json
@@ -0,0 +1,13 @@
+{
+ "network": {
+ "port": <NETWORK_PORT>,
+ "portRange": 0,
+ "nodeFinder": {
+ "netClusterNodes": <NET_CLUSTER_NODES>
+ }
+ },
+ "rest": {
+ "port": <REST_PORT>,
+ "portRange": 0
+ }
+}
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java
index 151ba472a..7387f57aa 100644
--- a/modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java
+++ b/modules/cli/src/test/java/org/apache/ignite/cli/AbstractCliTest.java
@@ -22,12 +22,12 @@ import org.junit.jupiter.api.BeforeAll;
/**
* Base class for any CLI tests.
*/
-public class AbstractCliTest {
+public abstract class AbstractCliTest {
/**
* Sets up a dumb terminal before tests.
*/
@BeforeAll
- private static void beforeAll() {
+ static void beforeAll() {
System.setProperty("org.jline.terminal.dumb", "true");
}
}
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeTest.java
index cc554e6e9..bfec096e6 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/compute/ItComputeTest.java
@@ -52,6 +52,7 @@ import org.junit.jupiter.api.Test;
/**
* Integration tests for Compute functionality.
*/
+@SuppressWarnings("resource")
class ItComputeTest extends AbstractClusterIntegrationTest {
@Test
void executesJobLocally() throws Exception {
diff --git a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
index c92185f47..6c08c585d 100644
--- a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
+++ b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
@@ -352,6 +352,9 @@ public class IgniteImpl implements Ignite {
/**
* Starts ignite node.
*
+ * <p>When this method returns, the node is partially started and ready to accept the init command (that is, its
+ * REST endpoint is functional).
+ *
* @param cfg Optional node configuration based on
* {@link org.apache.ignite.configuration.schemas.network.NetworkConfigurationSchema}. Following rules are used for applying the
* configuration properties: