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();