This is an automated email from the ASF dual-hosted git repository.
sk0x50 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 d1885cb IGNITE-14579 Start REST API module. Fixes #219
d1885cb is described below
commit d1885cb29a777e7250b598c875e462edda20d40b
Author: Kirill Gusakov <[email protected]>
AuthorDate: Tue Jul 20 19:19:14 2021 +0300
IGNITE-14579 Start REST API module. Fixes #219
Signed-off-by: Slava Koptilin <[email protected]>
---
modules/cli/pom.xml | 12 ++
.../org/apache/ignite/cli/ConfigCommandTest.java | 195 +++++++++++++++++++++
.../cli/builtins/config/ConfigurationClient.java | 4 +-
.../apache/ignite/cli/IgniteCliInterfaceTest.java | 4 +-
.../java/org/apache/ignite/rest/RestModule.java | 15 +-
.../apache/ignite/internal/app/IgnitionImpl.java | 14 +-
6 files changed, 232 insertions(+), 12 deletions(-)
diff --git a/modules/cli/pom.xml b/modules/cli/pom.xml
index 82ad111..909a147 100644
--- a/modules/cli/pom.xml
+++ b/modules/cli/pom.xml
@@ -113,6 +113,18 @@
<artifactId>micronaut-test-junit5</artifactId>
<scope>test</scope>
</dependency>
+
+ <dependency>
+ <groupId>org.apache.ignite</groupId>
+ <artifactId>ignite-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.ignite</groupId>
+ <artifactId>ignite-runner</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git
a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ConfigCommandTest.java
b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ConfigCommandTest.java
new file mode 100644
index 0000000..777d2cb
--- /dev/null
+++
b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ConfigCommandTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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 java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.ServerSocket;
+import java.nio.file.Path;
+
+import io.micronaut.context.ApplicationContext;
+import io.micronaut.context.env.Environment;
+import org.apache.ignite.app.Ignite;
+import org.apache.ignite.app.IgnitionManager;
+import org.apache.ignite.cli.spec.IgniteCliSpec;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import picocli.CommandLine;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Integration test for {@code ignite config} commands.
+ */
+public class ConfigCommandTest extends AbstractCliTest {
+ /** DI context. */
+ private ApplicationContext ctx;
+
+ /** stderr. */
+ private ByteArrayOutputStream err;
+
+ /** stdout. */
+ private ByteArrayOutputStream out;
+
+ /** Port for REST communication */
+ private int restPort;
+
+ /** Network port. */
+ private int networkPort;
+
+ /** Ignite node. */
+ private Ignite node;
+
+ /** */
+ @BeforeEach
+ private void setup(@TempDir Path workDir) throws IOException {
+ // TODO: IGNITE-15131 Must be replaced by receiving the actual port
configs from the started node.
+ // This approach still can produce the port, which will be unavailable
at the moment of node start.
+ restPort = getAvailablePort();
+ networkPort = getAvailablePort();
+
+ String cfgStr = "network.port=" + networkPort + "\n" +
+ "rest.port=" + restPort;
+
+ node = IgnitionManager.start("node1", cfgStr, workDir);
+
+ ctx = ApplicationContext.run(Environment.TEST);
+
+ err = new ByteArrayOutputStream();
+ out = new ByteArrayOutputStream();
+ }
+
+ /**
+ * Cleans environment after each test.
+ *
+ * @throws Exception If failed to close ignite node or application context.
+ */
+ // TODO: IGNITE-14581 Node must be stopped here.
+ @AfterEach
+ private void tearDown() throws Exception {
+ node.close();
+
+ ctx.stop();
+ }
+
+ /**
+ * Tests 'config set' and 'config get' commands.
+ */
+ @Test
+ public void setAndGetWithManualHost() {
+ int exitCode = cmd(ctx).execute(
+ "config",
+ "set",
+ "--node-endpoint",
+ "localhost:" + restPort,
+ "node.metastorageNodes=[\"localhost1\"]");
+
+ assertEquals(0, exitCode, "The command 'config set' failed [code=" +
exitCode + ']');
+ assertEquals(
+ unescapeQuotes("Configuration was updated successfully.\n" + "\n" +
+ "Use the ignite config get command to view the updated
configuration.\n"),
+ unescapeQuotes(out.toString()),
+ "The command 'config set' was successfully completed, " +
+ "but the server response does not match with expected.");
+
+ resetStreams();
+
+ exitCode = cmd(ctx).execute(
+ "config",
+ "get",
+ "--node-endpoint",
+ "localhost:" + restPort);
+
+ assertEquals(0, exitCode, "The command 'config get' failed [exitCode="
+ exitCode + ']');
+ assertEquals(
+ "\"{\"network\":{\"port\":" + networkPort +
",\"netClusterNodes\":[]}," +
+ "\"node\":{\"metastorageNodes\":[\"localhost1\"]}," +
+ "\"rest\":{\"port\":" + restPort + ",\"portRange\":0}}\"",
+ unescapeQuotes(out.toString()),
+ "The command 'config get' was successfully completed, " +
+ "but the server response does not match with expected.");
+ }
+
+ /**
+ * Tests partial 'config get' command.
+ */
+ @Test
+ public void partialGet() {
+ int exitCode = cmd(ctx).execute(
+ "config",
+ "get",
+ "--node-endpoint",
+ "localhost:" + restPort,
+ "--selector",
+ "network");
+
+ assertEquals(0, exitCode, "The command 'config get' failed [exitCode="
+ exitCode + ']');
+ assertEquals(
+ "\"{\"port\":"+ networkPort + ",\"netClusterNodes\":[]}\"",
+ unescapeQuotes(out.toString()),
+ "The command 'config get' was successfully completed, " +
+ "but the server response does not match with expected.");
+ }
+
+ /**
+ * @return Any available port.
+ * @throws IOException if can't allocate port to open socket.
+ */
+ // TODO: Must be removed after IGNITE-15131.
+ private int getAvailablePort() throws IOException {
+ ServerSocket s = new ServerSocket(0);
+ s.close();
+ return s.getLocalPort();
+ }
+
+ /**
+ * @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.
+ */
+ private void resetStreams() {
+ err.reset();
+ out.reset();
+ }
+
+ /**
+ * Removes unescaped quotes and new line symbols.
+ *
+ * @param input Input string.
+ * @return New string without new lines and unescaped quotes.
+ */
+ private String unescapeQuotes(String input) {
+ return input
+ .replace("\\\"", "\"")
+ .replaceAll("\\r", "")
+ .replaceAll("\\n", "");
+ }
+}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/ConfigurationClient.java
b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/ConfigurationClient.java
index 25e2092..4a87abe 100644
---
a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/ConfigurationClient.java
+++
b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/ConfigurationClient.java
@@ -113,7 +113,7 @@ public class ConfigurationClient {
public void set(String host, int port, String rawHoconData, PrintWriter
out, ColorScheme cs) {
var req = HttpRequest
.newBuilder()
-
.POST(HttpRequest.BodyPublishers.ofString(renderJsonFromHocon(rawHoconData)))
+
.PUT(HttpRequest.BodyPublishers.ofString(renderJsonFromHocon(rawHoconData)))
.header("Content-Type", "application/json")
.uri(URI.create("http://" + host + ":" + port + SET_URL))
.build();
@@ -131,7 +131,7 @@ public class ConfigurationClient {
throw error("Failed to set configuration", res);
}
catch (IOException | InterruptedException e) {
- throw new IgniteCLIException("Connection issues while trying to
send http request");
+ throw new IgniteCLIException("Connection issues while trying to
send http request", e);
}
}
diff --git
a/modules/cli/src/test/java/org/apache/ignite/cli/IgniteCliInterfaceTest.java
b/modules/cli/src/test/java/org/apache/ignite/cli/IgniteCliInterfaceTest.java
index c24231d..8be4aca 100644
---
a/modules/cli/src/test/java/org/apache/ignite/cli/IgniteCliInterfaceTest.java
+++
b/modules/cli/src/test/java/org/apache/ignite/cli/IgniteCliInterfaceTest.java
@@ -551,7 +551,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest
{
Assertions.assertEquals(0, exitCode);
verify(httpClient).send(
argThat(r ->
"http://localhost:8081/management/v1/configuration/".equals(r.uri().toString())
&&
- "POST".equals(r.method()) &&
+ "PUT".equals(r.method()) &&
r.bodyPublisher().get().contentLength() ==
expSentContent.getBytes().length &&
"application/json".equals(r.headers().firstValue("Content-Type").get())),
any());
@@ -578,7 +578,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest
{
Assertions.assertEquals(0, exitCode);
verify(httpClient).send(
argThat(r ->
"http://localhost:8081/management/v1/configuration/".equals(r.uri().toString())
&&
- "POST".equals(r.method()) &&
+ "PUT".equals(r.method()) &&
r.bodyPublisher().get().contentLength() ==
expSentContent.getBytes().length &&
"application/json".equals(r.headers().firstValue("Content-Type").get())),
any());
diff --git a/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java
b/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java
index 453d129..b882bfa 100644
--- a/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java
+++ b/modules/rest/src/main/java/org/apache/ignite/rest/RestModule.java
@@ -36,6 +36,7 @@ import
org.apache.ignite.configuration.schemas.rest.RestConfiguration;
import org.apache.ignite.configuration.schemas.rest.RestView;
import
org.apache.ignite.configuration.validation.ConfigurationValidationException;
import org.apache.ignite.internal.configuration.ConfigurationRegistry;
+import org.apache.ignite.lang.IgniteLogger;
import org.apache.ignite.rest.netty.RestApiInitializer;
import org.apache.ignite.rest.presentation.ConfigurationPresentation;
import org.apache.ignite.rest.presentation.json.JsonPresentation;
@@ -63,6 +64,9 @@ public class RestModule {
/** */
private ConfigurationRegistry sysConf;
+ /** Ignite logger. */
+ private final IgniteLogger LOG = IgniteLogger.forClass(RestModule.class);
+
/** */
private volatile ConfigurationPresentation<String> presentation;
@@ -87,9 +91,8 @@ public class RestModule {
/**
* @return REST channel future.
- * @throws InterruptedException If thread has been interupted during the
start.
*/
- public ChannelFuture start() throws InterruptedException {
+ public ChannelFuture start() {
var router = new Router();
router
.get(CONF_URL, (req, resp) -> {
@@ -148,7 +151,7 @@ public class RestModule {
}
/** */
- private ChannelFuture startRestEndpoint(Router router) throws
InterruptedException {
+ private ChannelFuture startRestEndpoint(Router router) {
RestView restConfigurationView =
sysConf.getConfiguration(RestConfiguration.KEY).value();
int desiredPort = restConfigurationView.port();
@@ -163,6 +166,7 @@ public class RestModule {
var hnd = new RestApiInitializer(router);
+ // TODO: IGNITE-15132 Rest module must reuse netty infrastructure from
network module
ServerBootstrap b = new ServerBootstrap();
b.option(ChannelOption.SO_BACKLOG, 1024);
b.group(parentGrp, childGrp)
@@ -170,8 +174,8 @@ public class RestModule {
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(hnd);
- for (int portCandidate = desiredPort; portCandidate < desiredPort +
portRange; portCandidate++) {
- ChannelFuture bindRes = b.bind(portCandidate).await();
+ for (int portCandidate = desiredPort; portCandidate <= desiredPort +
portRange; portCandidate++) {
+ ChannelFuture bindRes =
b.bind(portCandidate).awaitUninterruptibly();
if (bindRes.isSuccess()) {
ch = bindRes.channel();
@@ -179,6 +183,7 @@ public class RestModule {
@Override public void operationComplete(ChannelFuture fut)
{
parentGrp.shutdownGracefully();
childGrp.shutdownGracefully();
+ LOG.error("REST component was stopped", fut.cause());
}
});
port = portCandidate;
diff --git
a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgnitionImpl.java
b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgnitionImpl.java
index bf6f300..e46f510 100644
---
a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgnitionImpl.java
+++
b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgnitionImpl.java
@@ -34,6 +34,7 @@ import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.configuration.annotation.ConfigurationType;
import org.apache.ignite.configuration.schemas.network.NetworkConfiguration;
import org.apache.ignite.configuration.schemas.network.NetworkView;
+import org.apache.ignite.configuration.schemas.rest.RestConfiguration;
import org.apache.ignite.configuration.schemas.runner.ClusterConfiguration;
import org.apache.ignite.configuration.schemas.runner.NodeConfiguration;
import org.apache.ignite.configuration.schemas.table.TablesConfiguration;
@@ -58,10 +59,12 @@ import org.apache.ignite.network.ClusterService;
import org.apache.ignite.network.MessageSerializationRegistryImpl;
import org.apache.ignite.network.NetworkAddress;
import org.apache.ignite.network.scalecube.ScaleCubeClusterServiceFactory;
+import org.apache.ignite.rest.RestModule;
import org.apache.ignite.table.manager.IgniteTables;
import org.apache.ignite.utils.IgniteProperties;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.slf4j.LoggerFactory;
/**
* Implementation of an entry point for handling grid lifecycle.
@@ -152,7 +155,8 @@ public class IgnitionImpl implements Ignition {
NetworkConfiguration.KEY,
NodeConfiguration.KEY,
ClusterConfiguration.KEY,
- TablesConfiguration.KEY
+ TablesConfiguration.KEY,
+ RestConfiguration.KEY
);
List<ConfigurationStorage> cfgStorages =
@@ -229,11 +233,15 @@ public class IgnitionImpl implements Ignition {
vaultMgr
);
- // TODO IGNITE-14579 Start rest manager.
-
// Deploy all resisted watches cause all components are ready and have
registered their listeners.
metaStorageMgr.deployWatches();
+ RestModule restModule = new
RestModule(LoggerFactory.getLogger(RestModule.class.getName()));
+
+ restModule.prepareStart(locConfigurationMgr.configurationRegistry());
+
+ restModule.start();
+
ackSuccessStart();
return new IgniteImpl(distributedTblMgr, vaultMgr);