You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sk...@apache.org on 2021/08/12 18:33:40 UTC
[ignite-3] branch main updated: IGNITE-14579 Start REST API module.
Fixes #264
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 8195c26 IGNITE-14579 Start REST API module. Fixes #264
8195c26 is described below
commit 8195c26fc7e8e89d7ee38df40592aafae4c326ec
Author: Kirill Gusakov <kg...@gmail.com>
AuthorDate: Thu Aug 12 21:33:08 2021 +0300
IGNITE-14579 Start REST API module. Fixes #264
Signed-off-by: Slava Koptilin <sl...@gmail.com>
---
.../schemas/rest/RestConfigurationSchema.java | 2 +-
modules/cli/pom.xml | 12 ++
.../org/apache/ignite/cli/ConfigCommandTest.java | 159 +++++++++++++++++++++
.../cli/builtins/config/ConfigurationClient.java | 4 +-
.../apache/ignite/cli/IgniteCliInterfaceTest.java | 4 +-
.../java/org/apache/ignite/rest/RestModule.java | 55 +++----
.../apache/ignite/internal/app/IgnitionImpl.java | 10 +-
7 files changed, 212 insertions(+), 34 deletions(-)
diff --git a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/rest/RestConfigurationSchema.java b/modules/api/src/main/java/org/apache/ignite/configuration/schemas/rest/RestConfigurationSchema.java
index b6accf3..54a7657 100644
--- a/modules/api/src/main/java/org/apache/ignite/configuration/schemas/rest/RestConfigurationSchema.java
+++ b/modules/api/src/main/java/org/apache/ignite/configuration/schemas/rest/RestConfigurationSchema.java
@@ -38,5 +38,5 @@ public class RestConfigurationSchema {
/** TCP port range. */
@Min(0)
@Value(hasDefault = true)
- public final int portRange = 0;
+ public final int portRange = 100;
}
diff --git a/modules/cli/pom.xml b/modules/cli/pom.xml
index 894d8b4..300466c 100644
--- a/modules/cli/pom.xml
+++ b/modules/cli/pom.xml
@@ -108,6 +108,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..f9e5417
--- /dev/null
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ConfigCommandTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.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;
+
+ /** */
+ @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 configStr = "network.port=" + networkPort + "\n" +
+ "rest.port=" + restPort + "\n" + "rest.portRange=0";
+
+ IgnitionManager.start("node1", configStr, workDir);
+
+ ctx = ApplicationContext.run(Environment.TEST);
+
+ err = new ByteArrayOutputStream();
+ out = new ByteArrayOutputStream();
+ }
+
+ @AfterEach
+ private void tearDown() {
+ IgnitionManager.stop("node1");
+ ctx.stop();
+ }
+
+ @Test
+ public void setAndGetWithManualHost() {
+ int exitCode = cmd(ctx).execute(
+ "config",
+ "set",
+ "--node-endpoint",
+ "localhost:" + restPort,
+ "node.metastorageNodes=[\"localhost1\"]");
+
+ assertEquals(0, exitCode);
+ assertEquals("Configuration was updated successfully.\n" +
+ "\n" +
+ "Use the ignite config get command to view the updated configuration.\n", out.toString());
+
+ resetStreams();
+
+ exitCode = cmd(ctx).execute(
+ "config",
+ "get",
+ "--node-endpoint",
+ "localhost:" + restPort);
+
+ assertEquals(0, exitCode);
+ assertEquals(
+ "\"{\"network\":{\"port\":" + networkPort + ",\"netClusterNodes\":[]}," +
+ "\"node\":{\"metastorageNodes\":[\"localhost1\"]}," +
+ "\"rest\":{\"port\":" + restPort + ",\"portRange\":0}}\"\n",
+ unescapeQuotes(out.toString()));
+ }
+
+ @Test
+ public void partialGet() {
+ int exitCode = cmd(ctx).execute(
+ "config",
+ "get",
+ "--node-endpoint",
+ "localhost:" + restPort,
+ "--selector",
+ "network");
+ assertEquals(0, exitCode);
+ assertEquals("\"{\"port\":"+ networkPort + ",\"netClusterNodes\":[]}\"\n",
+ unescapeQuotes(out.toString()));
+ }
+
+ /**
+ * @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();
+ }
+
+ private String unescapeQuotes(String input) {
+ return input.replace("\\\"", "\"");
+ }
+}
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 0d147a0..d787009 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 11e880e..040c673 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
@@ -35,7 +35,9 @@ import io.netty.handler.logging.LoggingHandler;
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.ConfigurationManager;
import org.apache.ignite.internal.configuration.ConfigurationRegistry;
+import org.apache.ignite.internal.manager.IgniteComponent;
import org.apache.ignite.lang.IgniteLogger;
import org.apache.ignite.rest.netty.RestApiInitializer;
import org.apache.ignite.rest.presentation.ConfigurationPresentation;
@@ -50,7 +52,7 @@ import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
* It is started on port 10300 by default but it is possible to change this in configuration itself.
* Refer to default config file in resources for the example.
*/
-public class RestModule {
+public class RestModule implements IgniteComponent {
/** */
public static final int DFLT_PORT = 10300;
@@ -60,37 +62,30 @@ public class RestModule {
/** */
private static final String PATH_PARAM = "selector";
- /** */
- private ConfigurationRegistry sysConf;
+ /** Ignite logger. */
+ private final IgniteLogger LOG = IgniteLogger.forClass(RestModule.class);
/** */
- private volatile ConfigurationPresentation<String> presentation;
+ private final ConfigurationRegistry sysConf;
/** */
- private final IgniteLogger log;
+ private volatile ConfigurationPresentation<String> presentation;
/**
- * @param log Logger.
+ * Creates a new instance of REST module.
+ *
+ * @param nodeCfgMgr Node configuration manager.
*/
- public RestModule(IgniteLogger log) {
- this.log = log;
+ public RestModule(ConfigurationManager nodeCfgMgr) {
+ sysConf = nodeCfgMgr.configurationRegistry();
}
- /**
- * @param sysCfg Configuration registry.
- */
- public void prepareStart(ConfigurationRegistry sysCfg) {
- sysConf = sysCfg;
-
- presentation = new JsonPresentation(sysCfg);
- }
+ /** {@inheritDoc} */
+ @Override public void start() {
+ presentation = new JsonPresentation(sysConf);
- /**
- * @return REST channel future.
- * @throws InterruptedException If thread has been interupted during the start.
- */
- public ChannelFuture start() throws InterruptedException {
var router = new Router();
+
router
.get(CONF_URL, (req, resp) -> {
resp.json(presentation.represent());
@@ -144,11 +139,11 @@ public class RestModule {
}
});
- return startRestEndpoint(router);
+ startRestEndpoint(router);
}
/** */
- 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 +158,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 +166,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 +175,7 @@ public class RestModule {
@Override public void operationComplete(ChannelFuture fut) {
parentGrp.shutdownGracefully();
childGrp.shutdownGracefully();
+ LOG.error("REST component was stopped", fut.cause());
}
});
port = portCandidate;
@@ -195,7 +192,7 @@ public class RestModule {
String msg = "Cannot start REST endpoint. " +
"All ports in range [" + desiredPort + ", " + (desiredPort + portRange) + "] are in use.";
- log.error(msg);
+ LOG.error(msg);
parentGrp.shutdownGracefully();
childGrp.shutdownGracefully();
@@ -203,8 +200,12 @@ public class RestModule {
throw new RuntimeException(msg);
}
- log.info("REST protocol started successfully on port " + port);
+ LOG.info("REST protocol started successfully on port " + port);
return ch.closeFuture();
}
+
+ /** {@inheritDoc} */
+ @Override public void stop() throws Exception {
+ }
}
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 d1fc7ee..a3407f2 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
@@ -37,6 +37,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;
@@ -65,6 +66,7 @@ import org.apache.ignite.network.ClusterService;
import org.apache.ignite.network.MessageSerializationRegistryImpl;
import org.apache.ignite.network.StaticNodeFinder;
import org.apache.ignite.network.scalecube.ScaleCubeClusterServiceFactory;
+import org.apache.ignite.rest.RestModule;
import org.apache.ignite.utils.IgniteProperties;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -210,7 +212,8 @@ public class IgnitionImpl implements Ignition {
NetworkConfiguration.KEY,
NodeConfiguration.KEY,
ClusterConfiguration.KEY,
- TablesConfiguration.KEY
+ TablesConfiguration.KEY,
+ RestConfiguration.KEY
);
List<ConfigurationStorage> cfgStorages =
@@ -340,7 +343,10 @@ public class IgnitionImpl implements Ignition {
)
);
- // TODO IGNITE-14579 Start rest manager.
+ doStartComponent(
+ nodeName,
+ startedComponents,
+ new RestModule(nodeConfigurationMgr));
// Deploy all resisted watches cause all components are ready and have registered their listeners.
metaStorageMgr.deployWatches();