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 <kgusa...@gmail.com>
AuthorDate: Tue Jul 20 19:19:14 2021 +0300

    IGNITE-14579 Start REST API module. Fixes #219
    
    Signed-off-by: Slava Koptilin <slava.kopti...@gmail.com>
---
 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);

Reply via email to