You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by vv...@apache.org on 2022/06/02 17:09:13 UTC
[ignite-3] branch main updated: IGNITE-16943 Implement Micronaut REST (#816)
This is an automated email from the ASF dual-hosted git repository.
vveider 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 8e5410d5e IGNITE-16943 Implement Micronaut REST (#816)
8e5410d5e is described below
commit 8e5410d5e7bbb305b79758b80b21ff3e3799f7b4
Author: Alexandr <Sa...@icloud.com>
AuthorDate: Thu Jun 2 20:09:09 2022 +0300
IGNITE-16943 Implement Micronaut REST (#816)
---
.../org/apache/ignite/cli/ItConfigCommandTest.java | 21 +--
.../apache/ignite/cli/CliPathsConfigLoader.java | 4 +-
.../java/org/apache/ignite/cli/CliVersionInfo.java | 2 +-
.../java/org/apache/ignite/cli/ErrorHandler.java | 2 +-
.../org/apache/ignite/cli/VersionProvider.java | 4 +-
.../ignite/cli/builtins/SystemPathResolver.java | 2 +-
.../cli/builtins/cluster/ClusterApiClient.java | 4 +-
.../cli/builtins/config/ConfigurationClient.java | 8 +-
.../cli/builtins/config/HttpClientFactory.java | 2 +-
.../cli/builtins/init/InitIgniteCommand.java | 2 +-
.../cli/builtins/module/MavenArtifactResolver.java | 4 +-
.../ignite/cli/builtins/module/ModuleManager.java | 4 +-
.../ignite/cli/builtins/module/ModuleRegistry.java | 4 +-
.../ignite/cli/builtins/node/NodeManager.java | 4 +-
.../apache/ignite/cli/spec/ClusterCommandSpec.java | 2 +-
.../apache/ignite/cli/spec/ConfigCommandSpec.java | 2 +-
.../org/apache/ignite/cli/spec/IgniteCliSpec.java | 2 +-
.../ignite/cli/spec/InitIgniteCommandSpec.java | 2 +-
.../apache/ignite/cli/spec/ModuleCommandSpec.java | 2 +-
.../apache/ignite/cli/spec/NodeCommandSpec.java | 2 +-
.../org/apache/ignite/cli/ui/TerminalFactory.java | 2 +-
.../apache/ignite/cli/IgniteCliInterfaceTest.java | 8 +-
.../cli/builtins/init/InitIgniteCommandTest.java | 2 +-
.../cli/builtins/module/ModuleMangerTest.java | 2 +-
modules/cluster-management/pom.xml | 36 +++-
.../internal/cluster/management/MockNode.java | 45 +++--
.../rest/ItClusterManagementControllerTest.java | 175 ++++++++++++++++++
.../cluster/management/ClusterInitializer.java | 2 +-
.../management/ClusterManagementGroupManager.java | 14 --
.../rest/ClusterManagementController.java | 93 ++++++++++
.../rest/ClusterManagementRestFactory.java} | 34 ++--
.../management/rest/InitCommandHandler.java | 94 ----------
...lidArgumentClusterInitializationException.java} | 17 +-
.../handler/IgniteInternalExceptionHandler.java | 42 +++++
...umentClusterInitializationExceptionHandler.java | 43 +++++
modules/configuration/pom.xml | 35 ++++
.../rest/ConfigurationHttpHandlers.java | 175 ------------------
.../AbstractConfigurationController.java | 81 +++++++++
.../ClusterConfigurationController.java | 98 +++++++++++
.../configuration/NodeConfigurationController.java | 98 +++++++++++
.../rest/configuration/PresentationsFactory.java | 55 ++++++
.../ConfigPathUnrecognizedException.java} | 17 +-
.../exception/InvalidConfigFormatException.java} | 11 +-
.../ConfigPathUnrecognizedExceptionHandler.java | 44 +++++
.../ConfigurationValidationExceptionHandler.java | 50 ++++++
.../exception/handler/IgniteExceptionHandler.java | 43 +++++
.../InvalidConfigFormatExceptionHandler.java | 43 +++++
.../exception/handler}/package-info.java | 5 +-
.../configuration/exception}/package-info.java | 4 +-
.../ClusterConfigurationControllerTest.java | 55 ++++++
.../ConfigurationControllerBaseTest.java | 145 +++++++++++++++
.../NodeConfigurationControllerTest.java | 55 ++++++
.../internal/rest/configuration/TestFactory.java | 70 ++++++++
.../TestRootConfigurationSchema.java} | 27 +--
.../configuration/TestSubConfigurationSchema.java} | 15 +-
modules/rest-api/pom.xml | 35 +---
.../ignite/internal/rest/api/ErrorResult.java | 9 +-
.../ignite/internal/rest/api/RequestHandler.java | 35 ----
.../internal/rest/api/RestApiHttpRequest.java | 62 -------
.../internal/rest/api/RestApiHttpResponse.java | 150 ----------------
.../ignite/internal/rest/api/RestFactory.java} | 8 +-
.../org/apache/ignite/internal/rest/api/Route.java | 161 -----------------
.../apache/ignite/internal/rest/api/Routes.java | 96 ----------
.../ignite/internal/rest/api}/package-info.java | 4 +-
.../internal/rest/api/RestApiHttpResponseTest.java | 53 ------
modules/rest/openapi/openapi.yaml | 145 +++++++++++++++
modules/rest/pom.xml | 56 +++---
.../apache/ignite/internal/rest/RestComponent.java | 195 +++++++++++----------
.../ignite/internal/rest/netty/RestApiHandler.java | 126 -------------
.../internal/rest/netty/RestApiInitializer.java | 51 ------
.../apache/ignite/internal/rest/package-info.java | 2 +-
.../ignite/internal/rest/routes/SimpleRouter.java | 71 --------
.../ignite/internal/rest/routes/RouteTest.java | 93 ----------
.../ItDistributedConfigurationPropertiesTest.java | 3 -
.../ItDistributedConfigurationStorageTest.java | 3 -
.../runner/app/ItIgniteNodeRestartTest.java | 2 -
.../internal/runner/app/ItNoThreadsLeftTest.java | 2 +-
.../org/apache/ignite/internal/app/IgniteImpl.java | 19 +-
parent/pom.xml | 65 ++++++-
79 files changed, 1774 insertions(+), 1486 deletions(-)
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 d97b084b7..222876d18 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
@@ -26,17 +26,12 @@ import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import com.jayway.jsonpath.DocumentContext;
-import com.jayway.jsonpath.JsonPath;
import io.micronaut.context.ApplicationContext;
import io.micronaut.context.env.Environment;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CompletableFuture;
-import net.minidev.json.JSONObject;
-import net.minidev.json.JSONValue;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgnitionManager;
import org.apache.ignite.internal.app.IgniteImpl;
@@ -113,9 +108,10 @@ public class ItConfigCommandTest extends AbstractCliIntegrationTest {
assertEquals(0, exitCode);
- DocumentContext document = JsonPath.parse(removeTrailingQuotes(unescapeQuotes(out.toString(UTF_8))));
-
- assertEquals(1, document.read("$.network.shutdownQuietPeriod", Integer.class));
+ assertThat(
+ out.toString(UTF_8),
+ containsString("\"shutdownQuietPeriod\" : 1")
+ );
}
@Test
@@ -169,11 +165,12 @@ public class ItConfigCommandTest extends AbstractCliIntegrationTest {
assertEquals(0, exitCode);
- JSONObject outResult = (JSONObject) JSONValue.parse(removeTrailingQuotes(unescapeQuotes(out.toString(UTF_8))));
-
- assertTrue(outResult.containsKey("inbound"));
+ assertThat(
+ out.toString(UTF_8),
+ containsString("\"inbound\"")
+ );
- assertFalse(outResult.containsKey("node"));
+ assertFalse(out.toString(UTF_8).contains("\"node\""));
}
/**
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/CliPathsConfigLoader.java b/modules/cli/src/main/java/org/apache/ignite/cli/CliPathsConfigLoader.java
index 3233540d9..45ad77c21 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/CliPathsConfigLoader.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/CliPathsConfigLoader.java
@@ -17,14 +17,14 @@
package org.apache.ignite.cli;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Optional;
import java.util.Properties;
-import javax.inject.Inject;
-import javax.inject.Singleton;
import org.apache.ignite.cli.builtins.SystemPathResolver;
/**
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/CliVersionInfo.java b/modules/cli/src/main/java/org/apache/ignite/cli/CliVersionInfo.java
index 30f1e3efb..987b05766 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/CliVersionInfo.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/CliVersionInfo.java
@@ -17,10 +17,10 @@
package org.apache.ignite.cli;
+import jakarta.inject.Singleton;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
-import javax.inject.Singleton;
/**
* Provider of current Ignite CLI version info from the builtin properties file.
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/ErrorHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/ErrorHandler.java
index 7d2cb86e0..aebb5deb3 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/ErrorHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/ErrorHandler.java
@@ -17,7 +17,7 @@
package org.apache.ignite.cli;
-import javax.inject.Singleton;
+import jakarta.inject.Singleton;
import org.apache.ignite.cli.spec.CategorySpec;
import org.apache.ignite.lang.IgniteLogger;
import picocli.CommandLine;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java b/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
index 27b87b2e0..9099a22db 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
@@ -18,8 +18,8 @@
package org.apache.ignite.cli;
import io.micronaut.core.annotation.Introspected;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import picocli.CommandLine;
/**
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/SystemPathResolver.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/SystemPathResolver.java
index 9153b024a..5b9768eb3 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/SystemPathResolver.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/SystemPathResolver.java
@@ -18,10 +18,10 @@
package org.apache.ignite.cli.builtins;
import io.micronaut.core.annotation.Introspected;
+import jakarta.inject.Singleton;
import java.io.File;
import java.net.URISyntaxException;
import java.nio.file.Path;
-import javax.inject.Singleton;
import org.apache.ignite.cli.IgniteCliApp;
import org.apache.ignite.cli.IgniteCliException;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/cluster/ClusterApiClient.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/cluster/ClusterApiClient.java
index 5c040f120..b6a5f29c2 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/cluster/ClusterApiClient.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/cluster/ClusterApiClient.java
@@ -20,6 +20,8 @@ package org.apache.ignite.cli.builtins.cluster;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
@@ -30,8 +32,6 @@ import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.List;
-import javax.inject.Inject;
-import javax.inject.Singleton;
import org.apache.ignite.cli.IgniteCliException;
/**
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 f48053947..e08962082 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
@@ -22,6 +22,8 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigRenderOptions;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
@@ -29,8 +31,6 @@ import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
-import javax.inject.Inject;
-import javax.inject.Singleton;
import org.apache.ignite.cli.IgniteCliException;
import org.jetbrains.annotations.Nullable;
import picocli.CommandLine.Help.ColorScheme;
@@ -81,7 +81,7 @@ public class ConfigurationClient {
) {
var req = HttpRequest
.newBuilder()
- .header("Content-Type", "application/json");
+ .header("Content-Type", "text/plain");
if (rawHoconPath == null) {
req.uri(URI.create("http://" + host + ":" + port + GET_URL + type + "/"));
@@ -120,7 +120,7 @@ public class ConfigurationClient {
var req = HttpRequest
.newBuilder()
.method("PATCH", HttpRequest.BodyPublishers.ofString(renderJsonFromHocon(rawHoconData)))
- .header("Content-Type", "application/json")
+ .header("Content-Type", "text/plain")
.uri(URI.create("http://" + host + ":" + port + PATCH_URL + type + "/"))
.build();
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java
index d123df4b3..178725423 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java
@@ -18,8 +18,8 @@
package org.apache.ignite.cli.builtins.config;
import io.micronaut.context.annotation.Factory;
+import jakarta.inject.Singleton;
import java.net.http.HttpClient;
-import javax.inject.Singleton;
/**
* Factory for producing simple HTTP clients.
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/init/InitIgniteCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/init/InitIgniteCommand.java
index 64e9446a4..a45818545 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/init/InitIgniteCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/init/InitIgniteCommand.java
@@ -19,6 +19,7 @@ package org.apache.ignite.cli.builtins.init;
import static java.nio.charset.StandardCharsets.UTF_8;
+import jakarta.inject.Inject;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
@@ -30,7 +31,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import java.util.Properties;
-import javax.inject.Inject;
import org.apache.ignite.cli.CliPathsConfigLoader;
import org.apache.ignite.cli.IgniteCliException;
import org.apache.ignite.cli.IgnitePaths;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/MavenArtifactResolver.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/MavenArtifactResolver.java
index 03182511a..324e5b087 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/MavenArtifactResolver.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/MavenArtifactResolver.java
@@ -17,6 +17,8 @@
package org.apache.ignite.cli.builtins.module;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
@@ -26,8 +28,6 @@ import java.nio.file.Path;
import java.text.ParseException;
import java.util.List;
import java.util.stream.Collectors;
-import javax.inject.Inject;
-import javax.inject.Singleton;
import org.apache.ignite.cli.IgniteCliException;
import org.apache.ignite.cli.builtins.SystemPathResolver;
import org.apache.ignite.cli.ui.ProgressBar;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ModuleManager.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ModuleManager.java
index 1959aaab4..19203a9d1 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ModuleManager.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ModuleManager.java
@@ -20,6 +20,8 @@ package org.apache.ignite.cli.builtins.module;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigValue;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
@@ -31,8 +33,6 @@ import java.util.List;
import java.util.Map;
import java.util.jar.JarInputStream;
import java.util.stream.Collectors;
-import javax.inject.Inject;
-import javax.inject.Singleton;
import org.apache.ignite.cli.CliVersionInfo;
import org.apache.ignite.cli.IgniteCliException;
import org.apache.ignite.cli.IgnitePaths;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ModuleRegistry.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ModuleRegistry.java
index 1010af25c..9cf8104bb 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ModuleRegistry.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ModuleRegistry.java
@@ -21,13 +21,13 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
-import javax.inject.Inject;
-import javax.inject.Singleton;
import org.apache.ignite.cli.CliPathsConfigLoader;
import org.apache.ignite.cli.IgniteCliException;
import org.apache.ignite.internal.tostring.IgniteToStringInclude;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/node/NodeManager.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/node/NodeManager.java
index 6e2887af7..c02204163 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/node/NodeManager.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/node/NodeManager.java
@@ -19,6 +19,8 @@ package org.apache.ignite.cli.builtins.node;
import static java.nio.charset.StandardCharsets.UTF_8;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
@@ -32,8 +34,6 @@ import java.util.Optional;
import java.util.concurrent.locks.LockSupport;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import javax.inject.Inject;
-import javax.inject.Singleton;
import org.apache.ignite.cli.IgniteCliException;
import org.apache.ignite.cli.builtins.module.ModuleRegistry;
import org.apache.ignite.cli.ui.Spinner;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/ClusterCommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/spec/ClusterCommandSpec.java
index 683eecfe4..acbf26fe1 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/ClusterCommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/spec/ClusterCommandSpec.java
@@ -17,9 +17,9 @@
package org.apache.ignite.cli.spec;
+import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.List;
-import javax.inject.Inject;
import org.apache.ignite.cli.builtins.cluster.ClusterApiClient;
import picocli.CommandLine;
import picocli.CommandLine.Option;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/ConfigCommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/spec/ConfigCommandSpec.java
index e8c23c156..7c34903c1 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/ConfigCommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/spec/ConfigCommandSpec.java
@@ -17,7 +17,7 @@
package org.apache.ignite.cli.spec;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import org.apache.ignite.cli.builtins.config.ConfigurationClient;
import picocli.CommandLine;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/IgniteCliSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/spec/IgniteCliSpec.java
index 53c1f83b7..063d2ed72 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/IgniteCliSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/spec/IgniteCliSpec.java
@@ -18,6 +18,7 @@
package org.apache.ignite.cli.spec;
import io.micronaut.context.ApplicationContext;
+import jakarta.inject.Inject;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
@@ -25,7 +26,6 @@ import java.nio.file.Path;
import java.util.List;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
-import javax.inject.Inject;
import org.apache.ignite.cli.CliPathsConfigLoader;
import org.apache.ignite.cli.CommandFactory;
import org.apache.ignite.cli.ErrorHandler;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/InitIgniteCommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/spec/InitIgniteCommandSpec.java
index d6dec52d0..cb21e9e86 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/InitIgniteCommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/spec/InitIgniteCommandSpec.java
@@ -17,8 +17,8 @@
package org.apache.ignite.cli.spec;
+import jakarta.inject.Inject;
import java.net.URL;
-import javax.inject.Inject;
import org.apache.ignite.cli.builtins.init.InitIgniteCommand;
import org.apache.ignite.cli.common.IgniteCommand;
import picocli.CommandLine;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/ModuleCommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/spec/ModuleCommandSpec.java
index c4f5b616a..92d87d109 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/ModuleCommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/spec/ModuleCommandSpec.java
@@ -17,13 +17,13 @@
package org.apache.ignite.cli.spec;
+import jakarta.inject.Inject;
import java.io.PrintWriter;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.stream.Collectors;
-import javax.inject.Inject;
import org.apache.ignite.cli.CliPathsConfigLoader;
import org.apache.ignite.cli.Table;
import org.apache.ignite.cli.builtins.module.MavenCoordinates;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/NodeCommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/spec/NodeCommandSpec.java
index f699f4276..bcbdd7ce0 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/NodeCommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/spec/NodeCommandSpec.java
@@ -17,11 +17,11 @@
package org.apache.ignite.cli.spec;
+import jakarta.inject.Inject;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.util.List;
-import javax.inject.Inject;
import org.apache.ignite.cli.CliPathsConfigLoader;
import org.apache.ignite.cli.IgniteCliException;
import org.apache.ignite.cli.IgnitePaths;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/ui/TerminalFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/ui/TerminalFactory.java
index db9c3bd21..3f1f38829 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/ui/TerminalFactory.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/ui/TerminalFactory.java
@@ -19,8 +19,8 @@ package org.apache.ignite.cli.ui;
import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Factory;
+import jakarta.inject.Singleton;
import java.io.IOException;
-import javax.inject.Singleton;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
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 c1f08f04d..384461f85 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
@@ -529,7 +529,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
assertThat(capturedRequest.uri().toString(), is("http://localhost:8081/management/v1/configuration/node/"));
assertThat(capturedRequest.method(), is("GET"));
- assertThat(capturedRequest.headers().firstValue("Content-Type"), isPresentAnd(is("application/json")));
+ assertThat(capturedRequest.headers().firstValue("Content-Type"), isPresentAnd(is("text/plain")));
assertOutputEqual("{\n"
+ " \"baseline\" : {\n"
@@ -560,7 +560,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
assertThat(capturedRequest.uri().toString(), is("http://localhost:8081/management/v1/configuration/node/local.baseline"));
assertThat(capturedRequest.method(), is("GET"));
- assertThat(capturedRequest.headers().firstValue("Content-Type"), isPresentAnd(is("application/json")));
+ assertThat(capturedRequest.headers().firstValue("Content-Type"), isPresentAnd(is("text/plain")));
assertOutputEqual("{\n"
+ " \"autoAdjust\" : {\n"
@@ -593,7 +593,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
assertThat(capturedRequest.uri().toString(), is("http://localhost:8081/management/v1/configuration/node/"));
assertThat(capturedRequest.method(), is("PATCH"));
assertThat(requestBodyBytes(capturedRequest), is(expSentContent.getBytes(UTF_8)));
- assertThat(capturedRequest.headers().firstValue("Content-Type"), isPresentAnd(is("application/json")));
+ assertThat(capturedRequest.headers().firstValue("Content-Type"), isPresentAnd(is("text/plain")));
assertOutputEqual("Configuration was updated successfully.\n\n"
+ "Use the " + cmd.getColorScheme().commandText("ignite config get")
@@ -627,7 +627,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
assertThat(capturedRequest.uri().toString(), is("http://localhost:8081/management/v1/configuration/node/"));
assertThat(capturedRequest.method(), is("PATCH"));
assertThat(requestBodyBytes(capturedRequest), is(expSentContent.getBytes(UTF_8)));
- assertThat(capturedRequest.headers().firstValue("Content-Type"), isPresentAnd(is("application/json")));
+ assertThat(capturedRequest.headers().firstValue("Content-Type"), isPresentAnd(is("text/plain")));
assertOutputEqual("Configuration was updated successfully.\n\n"
+ "Use the " + cmd.getColorScheme().commandText("ignite config get")
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/builtins/init/InitIgniteCommandTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/builtins/init/InitIgniteCommandTest.java
index 1fa33b742..fbf2a8f27 100644
--- a/modules/cli/src/test/java/org/apache/ignite/cli/builtins/init/InitIgniteCommandTest.java
+++ b/modules/cli/src/test/java/org/apache/ignite/cli/builtins/init/InitIgniteCommandTest.java
@@ -25,6 +25,7 @@ import static org.mockito.Mockito.when;
import io.micronaut.test.annotation.MockBean;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
@@ -32,7 +33,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Comparator;
-import javax.inject.Inject;
import org.apache.ignite.cli.AbstractCliTest;
import org.apache.ignite.cli.CliPathsConfigLoader;
import org.apache.ignite.cli.builtins.SystemPathResolver;
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/builtins/module/ModuleMangerTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/builtins/module/ModuleMangerTest.java
index 359e3ca96..9f41da99d 100644
--- a/modules/cli/src/test/java/org/apache/ignite/cli/builtins/module/ModuleMangerTest.java
+++ b/modules/cli/src/test/java/org/apache/ignite/cli/builtins/module/ModuleMangerTest.java
@@ -25,6 +25,7 @@ import static org.mockito.Mockito.when;
import io.micronaut.test.annotation.MockBean;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
@@ -34,7 +35,6 @@ import java.util.Collections;
import java.util.jar.Attributes;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
-import javax.inject.Inject;
import org.apache.ignite.cli.AbstractCliTest;
import org.apache.ignite.cli.IgnitePaths;
import org.junit.jupiter.api.Test;
diff --git a/modules/cluster-management/pom.xml b/modules/cluster-management/pom.xml
index 405e4ad3c..250de9423 100644
--- a/modules/cluster-management/pom.xml
+++ b/modules/cluster-management/pom.xml
@@ -49,17 +49,22 @@
<dependency>
<groupId>org.apache.ignite</groupId>
- <artifactId>ignite-rest</artifactId>
+ <artifactId>ignite-vault</artifactId>
</dependency>
<dependency>
<groupId>org.apache.ignite</groupId>
- <artifactId>ignite-vault</artifactId>
+ <artifactId>ignite-rest-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-http</artifactId>
</dependency>
<dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-http-server</artifactId>
</dependency>
<!-- Test dependencies -->
@@ -113,6 +118,24 @@
<artifactId>slf4j-jdk14</artifactId>
<scope>test</scope>
</dependency>
+
+ <dependency>
+ <groupId>io.micronaut.test</groupId>
+ <artifactId>micronaut-test-junit5</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-http-client</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-http-server-netty</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
@@ -134,6 +157,11 @@
<artifactId>ignite-network-annotation-processor</artifactId>
<version>${project.version}</version>
</path>
+ <path>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-inject-java</artifactId>
+ <version>${micronaut.version}</version>
+ </path>
</annotationProcessorPaths>
</configuration>
</plugin>
diff --git a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/MockNode.java b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/MockNode.java
index 2199f6268..4be44456e 100644
--- a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/MockNode.java
+++ b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/MockNode.java
@@ -17,7 +17,6 @@
package org.apache.ignite.internal.cluster.management;
-import static org.mockito.Mockito.mock;
import java.io.IOException;
import java.nio.file.Files;
@@ -28,7 +27,6 @@ import java.util.concurrent.CompletableFuture;
import org.apache.ignite.internal.cluster.management.raft.RocksDbClusterStateStorage;
import org.apache.ignite.internal.manager.IgniteComponent;
import org.apache.ignite.internal.raft.Loza;
-import org.apache.ignite.internal.rest.RestComponent;
import org.apache.ignite.internal.util.ReverseIterator;
import org.apache.ignite.internal.vault.VaultManager;
import org.apache.ignite.internal.vault.persistence.PersistentVaultService;
@@ -42,7 +40,7 @@ import org.junit.jupiter.api.TestInfo;
/**
* Fake node for integration tests.
*/
-class MockNode {
+public class MockNode {
private ClusterManagementGroupManager clusterManager;
private ClusterService clusterService;
@@ -57,7 +55,10 @@ class MockNode {
private CompletableFuture<Void> startFuture;
- MockNode(TestInfo testInfo, NetworkAddress addr, NodeFinder nodeFinder, Path workDir) throws IOException {
+ /**
+ * Fake node constructor.
+ */
+ public MockNode(TestInfo testInfo, NetworkAddress addr, NodeFinder nodeFinder, Path workDir) throws IOException {
this.testInfo = testInfo;
this.nodeFinder = nodeFinder;
this.workDir = workDir;
@@ -78,7 +79,6 @@ class MockNode {
vaultManager,
clusterService,
raftManager,
- mock(RestComponent.class),
new RocksDbClusterStateStorage(workDir.resolve("cmg"))
);
@@ -88,23 +88,35 @@ class MockNode {
components.add(clusterManager);
}
- void startComponents() {
+ /**
+ * Start fake node.
+ */
+ public void startComponents() {
components.forEach(IgniteComponent::start);
}
- void start() {
+ /**
+ * Start fake node.
+ */
+ public void start() {
startComponents();
startFuture = clusterManager.onJoinReady();
}
- void beforeNodeStop() {
+ /**
+ * Method should be called before node stop.
+ */
+ public void beforeNodeStop() {
ReverseIterator<IgniteComponent> it = new ReverseIterator<>(components);
it.forEachRemaining(IgniteComponent::beforeNodeStop);
}
- void stop() {
+ /**
+ * Stop fake node.
+ */
+ public void stop() {
ReverseIterator<IgniteComponent> it = new ReverseIterator<>(components);
it.forEachRemaining(component -> {
@@ -116,7 +128,10 @@ class MockNode {
});
}
- void restart() throws Exception {
+ /**
+ * Restart fake node.
+ */
+ public void restart() throws Exception {
int port = localMember().address().port();
beforeNodeStop();
@@ -129,15 +144,19 @@ class MockNode {
start();
}
- ClusterNode localMember() {
+ public ClusterNode localMember() {
return clusterService.topologyService().localMember();
}
- ClusterManagementGroupManager clusterManager() {
+ public ClusterManagementGroupManager clusterManager() {
return clusterManager;
}
- CompletableFuture<Void> startFuture() {
+ public CompletableFuture<Void> startFuture() {
return startFuture;
}
+
+ public ClusterService clusterService() {
+ return clusterService;
+ }
}
diff --git a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/rest/ItClusterManagementControllerTest.java b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/rest/ItClusterManagementControllerTest.java
new file mode 100644
index 000000000..7e02245ed
--- /dev/null
+++ b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/rest/ItClusterManagementControllerTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.internal.cluster.management.rest;
+
+import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.context.annotation.Factory;
+import io.micronaut.context.annotation.Replaces;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.HttpStatus;
+import io.micronaut.http.client.HttpClient;
+import io.micronaut.http.client.annotation.Client;
+import io.micronaut.http.client.exceptions.HttpClientResponseException;
+import io.micronaut.runtime.server.EmbeddedServer;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.ignite.internal.cluster.management.MockNode;
+import org.apache.ignite.internal.rest.api.ErrorResult;
+import org.apache.ignite.internal.testframework.WorkDirectory;
+import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
+import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.NetworkAddress;
+import org.apache.ignite.network.StaticNodeFinder;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Cluster management REST test.
+ */
+@MicronautTest
+@ExtendWith(WorkDirectoryExtension.class)
+public class ItClusterManagementControllerTest {
+
+ private static final int PORT_BASE = 10000;
+
+ private static final List<MockNode> cluster = new ArrayList<>();
+
+ static ClusterService clusterService;
+
+ @WorkDirectory
+ private static Path workDir;
+
+ @Inject
+ private EmbeddedServer server;
+
+ @Inject
+ @Client("/management/v1/cluster/init/")
+ private HttpClient client;
+
+ @BeforeAll
+ static void setUp(TestInfo testInfo) throws IOException {
+ var addr1 = new NetworkAddress("localhost", PORT_BASE);
+ var addr2 = new NetworkAddress("localhost", PORT_BASE + 1);
+
+ var nodeFinder = new StaticNodeFinder(List.of(addr1, addr2));
+
+ cluster.add(new MockNode(testInfo, addr1, nodeFinder, workDir.resolve("node0")));
+ cluster.add(new MockNode(testInfo, addr2, nodeFinder, workDir.resolve("node1")));
+
+ for (MockNode node : cluster) {
+ node.start();
+ }
+
+ clusterService = cluster.get(0).clusterService();
+ }
+
+ @AfterAll
+ static void tearDown() {
+ for (MockNode node : cluster) {
+ node.beforeNodeStop();
+ }
+
+ for (MockNode node : cluster) {
+ node.stop();
+ }
+ }
+
+ @Test
+ void testControllerLoaded() {
+ assertNotNull(server.getApplicationContext().getBean(ClusterManagementController.class));
+ }
+
+ @Test
+ void testInitNoSuchNode() {
+ // Given body with nodename that does not exist
+ String givenInvalidBody = "{\"metaStorageNodes\": [\"nodename\"], \"cmgNodes\": [], \"clusterName\": \"cluster\"}";
+
+ // When
+ var thrown = assertThrows(
+ HttpClientResponseException.class,
+ () -> client.toBlocking().exchange(HttpRequest.POST("", givenInvalidBody))
+ );
+
+ // Then
+ assertThat(thrown.getResponse().getStatus(), is(equalTo((HttpStatus.BAD_REQUEST))));
+ // And
+ var errorResult = getErrorResult(thrown);
+ assertEquals("INVALID_ARGUMENT", errorResult.type());
+ }
+
+ @Test
+ void testInitAlreadyInitializedWithAnotherNodes() {
+ // Given cluster initialized
+ String givenFirstRequestBody =
+ "{\"metaStorageNodes\": [\"" + cluster.get(0).clusterService().localConfiguration().getName() + "\"], \"cmgNodes\": [], "
+ + "\"clusterName\": \"cluster\"}";
+
+ // When
+ HttpResponse<Object> response = client.toBlocking().exchange(HttpRequest.POST("", givenFirstRequestBody));
+
+ // Then
+ assertThat(response.getStatus(), is(equalTo((HttpStatus.OK))));
+ // And
+ assertThat(cluster.get(0).startFuture(), willCompleteSuccessfully());
+
+ // Given second request with different node name
+ String givenSecondRequestBody =
+ "{\"metaStorageNodes\": [\"" + cluster.get(1).clusterService().localConfiguration().getName() + "\"], \"cmgNodes\": [], "
+ + "\"clusterName\": \"cluster\" }";
+
+ // When
+ var thrown = assertThrows(
+ HttpClientResponseException.class,
+ () -> client.toBlocking().exchange(HttpRequest.POST("", givenSecondRequestBody))
+ );
+
+ // Then
+ assertThat(thrown.getResponse().getStatus(), is(equalTo((HttpStatus.INTERNAL_SERVER_ERROR))));
+ // And
+ var errorResult = getErrorResult(thrown);
+ assertEquals("SERVER_ERROR", errorResult.type());
+
+ }
+
+ @Factory
+ @Bean
+ @Replaces(ClusterManagementRestFactory.class)
+ public ClusterManagementRestFactory clusterManagementRestFactory() {
+ return new ClusterManagementRestFactory(clusterService);
+ }
+
+ private ErrorResult getErrorResult(HttpClientResponseException exception) {
+ return exception.getResponse().getBody(ErrorResult.class).orElseThrow();
+ }
+}
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterInitializer.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterInitializer.java
index 42f6e4249..dafd51da7 100644
--- a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterInitializer.java
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterInitializer.java
@@ -48,7 +48,7 @@ public class ClusterInitializer {
private final CmgMessagesFactory msgFactory = new CmgMessagesFactory();
/** Constructor. */
- ClusterInitializer(ClusterService clusterService) {
+ public ClusterInitializer(ClusterService clusterService) {
this.clusterService = clusterService;
}
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterManagementGroupManager.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterManagementGroupManager.java
index 3159300c3..8ea630f1c 100644
--- a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterManagementGroupManager.java
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterManagementGroupManager.java
@@ -17,7 +17,6 @@
package org.apache.ignite.internal.cluster.management;
-import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.concurrent.CompletableFuture.failedFuture;
import static java.util.stream.Collectors.toSet;
@@ -48,11 +47,9 @@ import org.apache.ignite.internal.cluster.management.raft.CmgRaftService;
import org.apache.ignite.internal.cluster.management.raft.IllegalInitArgumentException;
import org.apache.ignite.internal.cluster.management.raft.JoinDeniedException;
import org.apache.ignite.internal.cluster.management.raft.commands.JoinReadyCommand;
-import org.apache.ignite.internal.cluster.management.rest.InitCommandHandler;
import org.apache.ignite.internal.manager.IgniteComponent;
import org.apache.ignite.internal.properties.IgniteProductVersion;
import org.apache.ignite.internal.raft.Loza;
-import org.apache.ignite.internal.rest.RestComponent;
import org.apache.ignite.internal.thread.NamedThreadFactory;
import org.apache.ignite.internal.util.IgniteSpinBusyLock;
import org.apache.ignite.internal.util.IgniteUtils;
@@ -85,9 +82,6 @@ public class ClusterManagementGroupManager implements IgniteComponent {
/** CMG Raft group name. */
private static final String CMG_RAFT_GROUP_NAME = "cmg_raft_group";
- /** Init REST endpoint path. */
- private static final String REST_ENDPOINT = "/management/v1/cluster/init";
-
/** Busy lock to stop synchronously. */
private final IgniteSpinBusyLock busyLock = new IgniteSpinBusyLock();
@@ -117,8 +111,6 @@ public class ClusterManagementGroupManager implements IgniteComponent {
private final Loza raftManager;
- private final RestComponent restComponent;
-
private final ClusterStateStorage clusterStateStorage;
/** Local state. */
@@ -132,12 +124,10 @@ public class ClusterManagementGroupManager implements IgniteComponent {
VaultManager vault,
ClusterService clusterService,
Loza raftManager,
- RestComponent restComponent,
ClusterStateStorage clusterStateStorage
) {
this.clusterService = clusterService;
this.raftManager = raftManager;
- this.restComponent = restComponent;
this.clusterStateStorage = clusterStateStorage;
this.localStateStorage = new LocalStateStorage(vault);
this.clusterInitializer = new ClusterInitializer(clusterService);
@@ -196,10 +186,6 @@ public class ClusterManagementGroupManager implements IgniteComponent {
}
})
);
-
- restComponent.registerHandlers(routes ->
- routes.post(REST_ENDPOINT, APPLICATION_JSON.toString(), new InitCommandHandler(clusterInitializer))
- );
}
/**
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementController.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementController.java
new file mode 100644
index 000000000..2f15425af
--- /dev/null
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementController.java
@@ -0,0 +1,93 @@
+/*
+ * 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.internal.cluster.management.rest;
+
+import io.micronaut.http.MediaType;
+import io.micronaut.http.annotation.Body;
+import io.micronaut.http.annotation.Consumes;
+import io.micronaut.http.annotation.Controller;
+import io.micronaut.http.annotation.Post;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import org.apache.ignite.internal.cluster.management.ClusterInitializer;
+import org.apache.ignite.internal.cluster.management.rest.exception.InvalidArgumentClusterInitializationException;
+import org.apache.ignite.lang.IgniteException;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+
+/**
+ * Cluster management controller.
+ */
+@Controller("/management/v1/cluster/init")
+@ApiResponse(responseCode = "400", description = "Bad request")
+@ApiResponse(responseCode = "500", description = "Internal error")
+@Tag(name = "clusterManagement")
+public class ClusterManagementController {
+ private static final IgniteLogger log = IgniteLogger.forClass(ClusterManagementController.class);
+
+ private final ClusterInitializer clusterInitializer;
+
+ /**
+ * Cluster management controller constructor.
+ *
+ * @param clusterInitializer cluster initializer.
+ */
+ public ClusterManagementController(ClusterInitializer clusterInitializer) {
+ this.clusterInitializer = clusterInitializer;
+ }
+
+ /**
+ * Initializes cluster.
+ *
+ * @return Completable future that will be completed whet cluster is initialized.
+ */
+ @Post
+ @Operation(operationId = "init")
+ @ApiResponse(responseCode = "200", description = "Cluster initialized")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public CompletableFuture<Void> init(@Body InitCommand initCommand) throws ExecutionException, InterruptedException {
+ if (log.isInfoEnabled()) {
+ log.info("Received init command:\n\tMeta Storage nodes: {}\n\tCMG nodes: {}", initCommand.metaStorageNodes(),
+ initCommand.cmgNodes());
+ }
+
+ return clusterInitializer.initCluster(initCommand.metaStorageNodes(), initCommand.cmgNodes(), initCommand.clusterName())
+ .exceptionally(ex -> {
+ throw mapException(ex);
+ });
+ }
+
+ private RuntimeException mapException(Throwable ex) {
+ if (ex instanceof CompletionException) {
+ var cause = ex.getCause();
+ if (cause instanceof IgniteInternalException) {
+ return (IgniteInternalException) cause;
+ }
+ }
+
+ if (ex instanceof IllegalArgumentException) {
+ return new InvalidArgumentClusterInitializationException(ex);
+ }
+
+ return new IgniteException(ex);
+ }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/ui/TerminalFactory.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementRestFactory.java
similarity index 56%
copy from modules/cli/src/main/java/org/apache/ignite/cli/ui/TerminalFactory.java
copy to modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementRestFactory.java
index db9c3bd21..6f8f1bd82 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/ui/TerminalFactory.java
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementRestFactory.java
@@ -15,31 +15,29 @@
* limitations under the License.
*/
-package org.apache.ignite.cli.ui;
+package org.apache.ignite.internal.cluster.management.rest;
import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Factory;
-import java.io.IOException;
-import javax.inject.Singleton;
-import org.jline.terminal.Terminal;
-import org.jline.terminal.TerminalBuilder;
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.cluster.management.ClusterInitializer;
+import org.apache.ignite.internal.rest.api.RestFactory;
+import org.apache.ignite.network.ClusterService;
/**
- * Factory for producing JLine {@link Terminal} instances.
+ * Factory that creates beans that are needed for {@link ClusterManagementController}.
*/
@Factory
-public class TerminalFactory {
- /**
- * Produce terminal instances.
- *
- * <p>Important: It's always must be a singleton bean. JLine has an issues with building more than 1 terminal instance per process.
- *
- * @return Terminal instance.
- * @throws IOException if an error occurs.
- */
- @Bean(preDestroy = "close")
+public class ClusterManagementRestFactory implements RestFactory {
+ private final ClusterService clusterService;
+
+ public ClusterManagementRestFactory(ClusterService clusterService) {
+ this.clusterService = clusterService;
+ }
+
+ @Bean
@Singleton
- public Terminal terminal() throws IOException {
- return TerminalBuilder.terminal();
+ public ClusterInitializer clusterInitializer() {
+ return new ClusterInitializer(clusterService);
}
}
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/InitCommandHandler.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/InitCommandHandler.java
deleted file mode 100644
index 9737b894a..000000000
--- a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/InitCommandHandler.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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.internal.cluster.management.rest;
-
-import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
-import org.apache.ignite.internal.cluster.management.ClusterInitializer;
-import org.apache.ignite.internal.rest.api.ErrorResult;
-import org.apache.ignite.internal.rest.api.RequestHandler;
-import org.apache.ignite.internal.rest.api.RestApiHttpRequest;
-import org.apache.ignite.internal.rest.api.RestApiHttpResponse;
-import org.apache.ignite.lang.IgniteLogger;
-
-/**
- * REST handler for the {@link InitCommand}.
- */
-public class InitCommandHandler implements RequestHandler {
- private static final IgniteLogger LOG = IgniteLogger.forClass(InitCommandHandler.class);
-
- private final ObjectMapper objectMapper = new ObjectMapper();
-
- private final ClusterInitializer clusterInitializer;
-
- public InitCommandHandler(ClusterInitializer clusterInitializer) {
- this.clusterInitializer = clusterInitializer;
- }
-
- @Override
- public CompletableFuture<RestApiHttpResponse> handle(RestApiHttpRequest request, RestApiHttpResponse response) {
- try {
- InitCommand command = readContent(request);
-
- if (LOG.isInfoEnabled()) {
- LOG.info("Received init command:\n\t{}}", command);
- }
-
- return clusterInitializer.initCluster(command.metaStorageNodes(), command.cmgNodes(), command.clusterName())
- .thenApply(v -> successResponse(response))
- .exceptionally(e -> errorResponse(response, e));
- } catch (Exception e) {
- return CompletableFuture.completedFuture(errorResponse(response, e));
- }
- }
-
- private InitCommand readContent(RestApiHttpRequest restApiHttpRequest) throws IOException {
- ByteBuf content = restApiHttpRequest.request().content();
-
- try (InputStream is = new ByteBufInputStream(content)) {
- return objectMapper.readValue(is, InitCommand.class);
- }
- }
-
- private static RestApiHttpResponse successResponse(RestApiHttpResponse response) {
- LOG.info("Init command executed successfully");
-
- return response;
- }
-
- private static RestApiHttpResponse errorResponse(RestApiHttpResponse response, Throwable e) {
- if (e instanceof CompletionException) {
- e = e.getCause();
- }
-
- LOG.error("Init command failure", e);
-
- response.status(INTERNAL_SERVER_ERROR);
- response.json(Map.of("error", new ErrorResult("APPLICATION_EXCEPTION", e.getMessage())));
-
- return response;
- }
-}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestHandlersRegister.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/InvalidArgumentClusterInitializationException.java
similarity index 69%
copy from modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestHandlersRegister.java
copy to modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/InvalidArgumentClusterInitializationException.java
index c55e7d6a7..f92daa173 100644
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestHandlersRegister.java
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/InvalidArgumentClusterInitializationException.java
@@ -15,18 +15,13 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.rest.api;
-
-import java.util.function.Consumer;
+package org.apache.ignite.internal.cluster.management.rest.exception;
/**
- * Register for REST handlers.
+ * Exception that is thrown when the wrong arguments are passed to the init cluster method.
*/
-public interface RestHandlersRegister {
- /**
- * Registers handlers on the provided {@link Routes}.
- *
- * @param registerAction registration action
- */
- void registerHandlers(Consumer<Routes> registerAction);
+public class InvalidArgumentClusterInitializationException extends RuntimeException {
+ public InvalidArgumentClusterInitializationException(Throwable cause) {
+ super(cause);
+ }
}
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/IgniteInternalExceptionHandler.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/IgniteInternalExceptionHandler.java
new file mode 100644
index 000000000..2f2e3f315
--- /dev/null
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/IgniteInternalExceptionHandler.java
@@ -0,0 +1,42 @@
+/*
+ * 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.internal.cluster.management.rest.exception.handler;
+
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.annotation.Produces;
+import io.micronaut.http.server.exceptions.ExceptionHandler;
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.rest.api.ErrorResult;
+import org.apache.ignite.lang.IgniteInternalException;
+
+/**
+ * Handles {@link IgniteInternalException} and represents it as a rest response.
+ */
+@Produces
+@Singleton
+@Requires(classes = {IgniteInternalException.class, ExceptionHandler.class})
+public class IgniteInternalExceptionHandler implements ExceptionHandler<IgniteInternalException, HttpResponse<ErrorResult>> {
+
+ @Override
+ public HttpResponse<ErrorResult> handle(HttpRequest request, IgniteInternalException exception) {
+ ErrorResult errorResult = new ErrorResult("SERVER_ERROR", exception.getMessage());
+ return HttpResponse.serverError().body(errorResult);
+ }
+}
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/InvalidArgumentClusterInitializationExceptionHandler.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/InvalidArgumentClusterInitializationExceptionHandler.java
new file mode 100644
index 000000000..d22774b41
--- /dev/null
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/InvalidArgumentClusterInitializationExceptionHandler.java
@@ -0,0 +1,43 @@
+/*
+ * 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.internal.cluster.management.rest.exception.handler;
+
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.annotation.Produces;
+import io.micronaut.http.server.exceptions.ExceptionHandler;
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.cluster.management.rest.exception.InvalidArgumentClusterInitializationException;
+import org.apache.ignite.internal.rest.api.ErrorResult;
+
+/**
+ * Handles {@link InvalidArgumentClusterInitializationException} and represents it as a rest response.
+ */
+@Produces
+@Singleton
+@Requires(classes = {InvalidArgumentClusterInitializationException.class, ExceptionHandler.class})
+public class InvalidArgumentClusterInitializationExceptionHandler implements
+ ExceptionHandler<InvalidArgumentClusterInitializationException, HttpResponse<ErrorResult>> {
+
+ @Override
+ public HttpResponse<ErrorResult> handle(HttpRequest request, InvalidArgumentClusterInitializationException exception) {
+ ErrorResult errorResult = new ErrorResult("INVALID_ARGUMENT", exception.getCause().getMessage());
+ return HttpResponse.badRequest().body(errorResult);
+ }
+}
diff --git a/modules/configuration/pom.xml b/modules/configuration/pom.xml
index 1063894f6..d97d770f9 100644
--- a/modules/configuration/pom.xml
+++ b/modules/configuration/pom.xml
@@ -54,6 +54,16 @@
</dependency>
<!-- 3rd party dependencies -->
+ <dependency>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-http</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-http-server</artifactId>
+ </dependency>
+
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
@@ -90,6 +100,26 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
+
+
+ <dependency>
+ <groupId>io.micronaut.test</groupId>
+ <artifactId>micronaut-test-junit5</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-http-client</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-http-server-netty</artifactId>
+ <scope>test</scope>
+ </dependency>
+
</dependencies>
<build>
@@ -123,6 +153,11 @@
<artifactId>ignite-configuration-annotation-processor</artifactId>
<version>${project.version}</version>
</path>
+ <path>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-inject-java</artifactId>
+ <version>${micronaut.version}</version>
+ </path>
</annotationProcessorPaths>
</configuration>
</plugin>
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/rest/ConfigurationHttpHandlers.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/rest/ConfigurationHttpHandlers.java
deleted file mode 100644
index 09e9dda4b..000000000
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/rest/ConfigurationHttpHandlers.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * 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.internal.configuration.rest;
-
-import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON;
-import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
-import org.apache.ignite.configuration.validation.ConfigurationValidationException;
-import org.apache.ignite.internal.configuration.ConfigurationManager;
-import org.apache.ignite.internal.configuration.rest.presentation.ConfigurationPresentation;
-import org.apache.ignite.internal.configuration.rest.presentation.hocon.HoconPresentation;
-import org.apache.ignite.internal.rest.api.ErrorResult;
-import org.apache.ignite.internal.rest.api.RestApiHttpRequest;
-import org.apache.ignite.internal.rest.api.RestApiHttpResponse;
-import org.apache.ignite.internal.rest.api.RestHandlersRegister;
-import org.apache.ignite.internal.rest.api.Routes;
-import org.apache.ignite.lang.IgniteException;
-
-/**
- * HTTP handlers for configuration-related REST endpoints.
- */
-public class ConfigurationHttpHandlers {
- /**
- * Node configuration route.
- */
- private static final String NODE_CFG_URL = "/management/v1/configuration/node/";
-
- /**
- * Cluster configuration route.
- */
- private static final String CLUSTER_CFG_URL = "/management/v1/configuration/cluster/";
-
- /**
- * Path parameter.
- */
- private static final String PATH_PARAM = "selector";
-
- private final ConfigurationManager nodeConfigurationManager;
- private final ConfigurationManager clusterConfigurationManager;
-
- public ConfigurationHttpHandlers(ConfigurationManager nodeConfigurationManager, ConfigurationManager clusterConfigurationManager) {
- this.nodeConfigurationManager = nodeConfigurationManager;
- this.clusterConfigurationManager = clusterConfigurationManager;
- }
-
- public void registerHandlers(RestHandlersRegister register) {
- register.registerHandlers(this::registerOn);
- }
-
- private void registerOn(Routes routes) {
- var nodeCfgPresentation = new HoconPresentation(nodeConfigurationManager.configurationRegistry());
- var clusterCfgPresentation = new HoconPresentation(clusterConfigurationManager.configurationRegistry());
-
- routes
- .get(
- NODE_CFG_URL,
- (req, resp) -> {
- resp.json(nodeCfgPresentation.represent());
-
- return CompletableFuture.completedFuture(resp);
- }
- )
- .get(
- CLUSTER_CFG_URL,
- (req, resp) -> {
- resp.json(clusterCfgPresentation.represent());
-
- return CompletableFuture.completedFuture(resp);
- }
- )
- .get(
- NODE_CFG_URL + ":" + PATH_PARAM,
- (req, resp) -> handleRepresentByPath(req, resp, nodeCfgPresentation)
- )
- .get(
- CLUSTER_CFG_URL + ":" + PATH_PARAM,
- (req, resp) -> handleRepresentByPath(req, resp, clusterCfgPresentation)
- )
- .patch(
- NODE_CFG_URL,
- APPLICATION_JSON.toString(),
- (req, resp) -> handleUpdate(req, resp, nodeCfgPresentation)
- )
- .patch(
- CLUSTER_CFG_URL,
- APPLICATION_JSON.toString(),
- (req, resp) -> handleUpdate(req, resp, clusterCfgPresentation)
- );
- }
-
- /**
- * Handle a request to get the configuration by {@link #PATH_PARAM path}.
- *
- * @param req Rest request.
- * @param res Rest response.
- * @param presentation Configuration presentation.
- */
- private static CompletableFuture<RestApiHttpResponse> handleRepresentByPath(
- RestApiHttpRequest req,
- RestApiHttpResponse res,
- ConfigurationPresentation<String> presentation
- ) {
- try {
- String cfgPath = req.queryParams().get(PATH_PARAM);
-
- res.json(presentation.representByPath(cfgPath));
- } catch (IllegalArgumentException pathE) {
- ErrorResult errRes = new ErrorResult("CONFIG_PATH_UNRECOGNIZED", pathE.getMessage());
-
- res.status(BAD_REQUEST);
- res.json(Map.of("error", errRes));
- }
-
- return CompletableFuture.completedFuture(res);
- }
-
- /**
- * Handle a configuration update request as json.
- *
- * @param req Rest request.
- * @param res Rest response.
- * @param presentation Configuration presentation.
- */
- private static CompletableFuture<RestApiHttpResponse> handleUpdate(
- RestApiHttpRequest req,
- RestApiHttpResponse res,
- ConfigurationPresentation<String> presentation
- ) {
- String updateReq = req.request().content().toString(StandardCharsets.UTF_8);
-
- return presentation.update(updateReq)
- .thenApply(v -> res)
- .exceptionally(e -> {
- if (e instanceof CompletionException) {
- e = e.getCause();
- }
-
- ErrorResult errRes;
-
- if (e instanceof IllegalArgumentException) {
- errRes = new ErrorResult("INVALID_CONFIG_FORMAT", e.getMessage());
- } else if (e instanceof ConfigurationValidationException) {
- errRes = new ErrorResult("VALIDATION_EXCEPTION", e.getMessage());
- } else if (e instanceof IgniteException) {
- errRes = new ErrorResult("APPLICATION_EXCEPTION", e.getMessage());
- } else {
- throw new CompletionException(e);
- }
-
- res.status(BAD_REQUEST);
- res.json(Map.of("error", errRes));
-
- return res;
- });
- }
-}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/AbstractConfigurationController.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/AbstractConfigurationController.java
new file mode 100644
index 000000000..fa86a3480
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/AbstractConfigurationController.java
@@ -0,0 +1,81 @@
+/*
+ * 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.internal.rest.configuration;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.internal.configuration.rest.presentation.ConfigurationPresentation;
+import org.apache.ignite.internal.rest.configuration.exception.ConfigPathUnrecognizedException;
+import org.apache.ignite.internal.rest.configuration.exception.InvalidConfigFormatException;
+import org.apache.ignite.lang.IgniteException;
+
+/**
+ * Base configuration controller.
+ */
+public abstract class AbstractConfigurationController {
+ /** Presentation of the configuration. */
+ private final ConfigurationPresentation<String> cfgPresentation;
+
+ public AbstractConfigurationController(ConfigurationPresentation<String> cfgPresentation) {
+ this.cfgPresentation = cfgPresentation;
+ }
+
+ /**
+ * Returns configuration.
+ *
+ * @return the presentation of configuration.
+ */
+ public String getConfiguration() {
+ return this.cfgPresentation.represent();
+ }
+
+ /**
+ * Returns configuration represented by path.
+ *
+ * @param path to represent a configuration.
+ * @return system configuration represented by given path.
+ */
+ public String getConfigurationByPath(String path) {
+ try {
+ return cfgPresentation.representByPath(path);
+ } catch (IllegalArgumentException ex) {
+ throw new ConfigPathUnrecognizedException(ex);
+ }
+ }
+
+ /**
+ * Updates configuration.
+ *
+ * @param updatedConfiguration the configuration to update.
+ */
+ public CompletableFuture<Void> updateConfiguration(String updatedConfiguration) throws Throwable {
+ return cfgPresentation.update(updatedConfiguration)
+ .exceptionally(ex -> {
+ if (ex instanceof CompletionException) {
+ var cause = ex.getCause();
+ if (cause instanceof IllegalArgumentException) {
+ throw new InvalidConfigFormatException(ex);
+ } else if (cause instanceof ConfigurationValidationException) {
+ throw (ConfigurationValidationException) cause;
+ }
+ }
+ throw new IgniteException(ex);
+ });
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/ClusterConfigurationController.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/ClusterConfigurationController.java
new file mode 100644
index 000000000..53586ba0f
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/ClusterConfigurationController.java
@@ -0,0 +1,98 @@
+/*
+ * 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.internal.rest.configuration;
+
+import io.micronaut.http.MediaType;
+import io.micronaut.http.annotation.Body;
+import io.micronaut.http.annotation.Consumes;
+import io.micronaut.http.annotation.Controller;
+import io.micronaut.http.annotation.Get;
+import io.micronaut.http.annotation.Patch;
+import io.micronaut.http.annotation.PathVariable;
+import io.micronaut.http.annotation.Produces;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Named;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.internal.configuration.rest.presentation.ConfigurationPresentation;
+
+/**
+ * Cluster configuration controller.
+ */
+@Controller("/management/v1/configuration/cluster/")
+@ApiResponse(responseCode = "400", description = "Incorrect configuration")
+@ApiResponse(responseCode = "500", description = "Internal error")
+@Tag(name = "clusterConfiguration")
+public class ClusterConfigurationController extends AbstractConfigurationController {
+
+ public ClusterConfigurationController(@Named("clusterCfgPresentation") ConfigurationPresentation<String> clusterCfgPresentation) {
+ super(clusterCfgPresentation);
+ }
+
+ /**
+ * Returns cluster configuration in HOCON format. This is represented as a plain text.
+ *
+ * @return the whole cluster configuration in HOCON format.
+ */
+ @Operation(operationId = "getClusterConfiguration")
+ @ApiResponse(
+ responseCode = "200",
+ content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
+ description = "Get cluster configuration")
+ @Produces(MediaType.TEXT_PLAIN) // todo: IGNITE-17082
+ @Get
+ @Override
+ public String getConfiguration() {
+ return super.getConfiguration();
+ }
+
+ /**
+ * Returns configuration in HOCON format represented by path. This is represented as a plain text.
+ *
+ * @param path to represent a cluster configuration.
+ * @return system configuration in HOCON format represented by given path.
+ */
+ @Operation(operationId = "getClusterConfigurationByPath")
+ @ApiResponse(responseCode = "200",
+ content = @Content(mediaType = MediaType.TEXT_PLAIN,
+ schema = @Schema(type = "string")),
+ description = "Configuration represented by path")
+ @Produces(MediaType.TEXT_PLAIN) // todo: IGNITE-17082
+ @Get("/{path}")
+ @Override
+ public String getConfigurationByPath(@PathVariable String path) {
+ return super.getConfigurationByPath(path);
+ }
+
+ /**
+ * Updates cluster configuration in HOCON format. This is represented as a plain text.
+ *
+ * @param updatedConfiguration the cluster configuration to update.
+ */
+ @Operation(operationId = "updateClusterConfiguration")
+ @ApiResponse(responseCode = "200", description = "Configuration updated")
+ @Consumes(MediaType.TEXT_PLAIN) // todo: IGNITE-17082
+ @Patch
+ @Override
+ public CompletableFuture<Void> updateConfiguration(@Body String updatedConfiguration) throws Throwable {
+ return super.updateConfiguration(updatedConfiguration);
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/NodeConfigurationController.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/NodeConfigurationController.java
new file mode 100644
index 000000000..9eafa2672
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/NodeConfigurationController.java
@@ -0,0 +1,98 @@
+/*
+ * 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.internal.rest.configuration;
+
+import io.micronaut.http.MediaType;
+import io.micronaut.http.annotation.Body;
+import io.micronaut.http.annotation.Consumes;
+import io.micronaut.http.annotation.Controller;
+import io.micronaut.http.annotation.Get;
+import io.micronaut.http.annotation.Patch;
+import io.micronaut.http.annotation.PathVariable;
+import io.micronaut.http.annotation.Produces;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Named;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.internal.configuration.rest.presentation.ConfigurationPresentation;
+
+/**
+ * Node configuration controller.
+ */
+@Controller("/management/v1/configuration/node")
+@ApiResponse(responseCode = "400", description = "Incorrect configuration")
+@ApiResponse(responseCode = "500", description = "Internal error")
+@Tag(name = "nodeConfiguration")
+public class NodeConfigurationController extends AbstractConfigurationController {
+
+ public NodeConfigurationController(@Named("nodeCfgPresentation") ConfigurationPresentation<String> nodeCfgPresentation) {
+ super(nodeCfgPresentation);
+ }
+
+ /**
+ * Returns node configuration in HOCON format. This is represented as a plain text.
+ *
+ * @return the whole node configuration in HOCON format.
+ */
+ @Operation(operationId = "getNodeConfiguration")
+ @ApiResponse(responseCode = "200",
+ content = @Content(mediaType = MediaType.TEXT_PLAIN,
+ schema = @Schema(type = "string")),
+ description = "Whole node configuration")
+ @Produces(MediaType.TEXT_PLAIN) // todo: IGNITE-17082
+ @Get
+ @Override
+ public String getConfiguration() {
+ return super.getConfiguration();
+ }
+
+ /**
+ * Returns configuration in HOCON format represented by path. This is represented as a plain text.
+ *
+ * @param path to represent a node configuration.
+ * @return system configuration in HOCON format represented by given path.
+ */
+ @Operation(operationId = "getNodeConfigurationByPath")
+ @ApiResponse(responseCode = "200",
+ content = @Content(mediaType = MediaType.TEXT_PLAIN,
+ schema = @Schema(type = "string")),
+ description = "Configuration represented by path")
+ @Produces(MediaType.TEXT_PLAIN) // todo: IGNITE-17082
+ @Get("/{path}")
+ @Override
+ public String getConfigurationByPath(@PathVariable String path) {
+ return super.getConfigurationByPath(path);
+ }
+
+ /**
+ * Updates node configuration in HOCON format. This is represented as a plain text.
+ *
+ * @param updatedConfiguration the node configuration to update. This is represented as a plain text.
+ */
+ @Operation(operationId = "updateNodeConfiguration")
+ @ApiResponse(responseCode = "200", description = "Configuration updated")
+ @Consumes(MediaType.TEXT_PLAIN) // todo: IGNITE-17082
+ @Patch
+ @Override
+ public CompletableFuture<Void> updateConfiguration(@Body String updatedConfiguration) throws Throwable {
+ return super.updateConfiguration(updatedConfiguration);
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/PresentationsFactory.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/PresentationsFactory.java
new file mode 100644
index 000000000..399f74d15
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/PresentationsFactory.java
@@ -0,0 +1,55 @@
+/*
+ * 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.internal.rest.configuration;
+
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.context.annotation.Factory;
+import jakarta.inject.Named;
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.configuration.ConfigurationManager;
+import org.apache.ignite.internal.configuration.rest.presentation.ConfigurationPresentation;
+import org.apache.ignite.internal.configuration.rest.presentation.hocon.HoconPresentation;
+import org.apache.ignite.internal.rest.api.RestFactory;
+
+/**
+ * Factory that defines beans required for the rest module.
+ */
+@Factory
+public class PresentationsFactory implements RestFactory {
+ private final ConfigurationPresentation<String> nodeCfgPresentation;
+ private final ConfigurationPresentation<String> clusterCfgPresentation;
+
+ public PresentationsFactory(ConfigurationManager nodeCfgMgr, ConfigurationManager clusterCfgMgr) {
+ this.nodeCfgPresentation = new HoconPresentation(nodeCfgMgr.configurationRegistry());
+ this.clusterCfgPresentation = new HoconPresentation(clusterCfgMgr.configurationRegistry());
+ }
+
+ @Bean
+ @Singleton
+ @Named("clusterCfgPresentation")
+ public ConfigurationPresentation<String> clusterCfgPresentation() {
+ return clusterCfgPresentation;
+ }
+
+ @Bean
+ @Singleton
+ @Named("nodeCfgPresentation")
+ public ConfigurationPresentation<String> nodeCfgPresentation() {
+ return nodeCfgPresentation;
+ }
+}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestHandlersRegister.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/ConfigPathUnrecognizedException.java
similarity index 70%
rename from modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestHandlersRegister.java
rename to modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/ConfigPathUnrecognizedException.java
index c55e7d6a7..dc05c79fe 100644
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestHandlersRegister.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/ConfigPathUnrecognizedException.java
@@ -15,18 +15,13 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.rest.api;
-
-import java.util.function.Consumer;
+package org.apache.ignite.internal.rest.configuration.exception;
/**
- * Register for REST handlers.
+ * Exception that is thrown when the wrong configuration path is given.
*/
-public interface RestHandlersRegister {
- /**
- * Registers handlers on the provided {@link Routes}.
- *
- * @param registerAction registration action
- */
- void registerHandlers(Consumer<Routes> registerAction);
+public class ConfigPathUnrecognizedException extends RuntimeException {
+ public ConfigPathUnrecognizedException(Throwable cause) {
+ super(cause);
+ }
}
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/InvalidConfigFormatException.java
old mode 100755
new mode 100644
similarity index 73%
copy from modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
copy to modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/InvalidConfigFormatException.java
index 81b40c399..7c754286e
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/InvalidConfigFormatException.java
@@ -15,8 +15,13 @@
* limitations under the License.
*/
+package org.apache.ignite.internal.rest.configuration.exception;
+
/**
- * This package contains an implementation of Ignite component related to REST interface.
+ * Exception that is thrown when the wrong configuration is given.
*/
-
-package org.apache.ignite.internal.rest;
+public class InvalidConfigFormatException extends RuntimeException {
+ public InvalidConfigFormatException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/ConfigPathUnrecognizedExceptionHandler.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/ConfigPathUnrecognizedExceptionHandler.java
new file mode 100644
index 000000000..f15cba486
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/ConfigPathUnrecognizedExceptionHandler.java
@@ -0,0 +1,44 @@
+/*
+ * 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.internal.rest.configuration.exception.handler;
+
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.MediaType;
+import io.micronaut.http.annotation.Produces;
+import io.micronaut.http.server.exceptions.ExceptionHandler;
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.rest.api.ErrorResult;
+import org.apache.ignite.internal.rest.configuration.exception.ConfigPathUnrecognizedException;
+
+/**
+ * Handles {@link ConfigPathUnrecognizedException} and represents it as a rest response.
+ */
+@Produces(MediaType.APPLICATION_JSON)
+@Singleton
+@Requires(classes = {ConfigPathUnrecognizedException.class, ExceptionHandler.class})
+public class ConfigPathUnrecognizedExceptionHandler implements
+ ExceptionHandler<ConfigPathUnrecognizedException, HttpResponse<ErrorResult>> {
+
+ @Override
+ public HttpResponse<ErrorResult> handle(HttpRequest request, ConfigPathUnrecognizedException exception) {
+ ErrorResult errorResult = new ErrorResult("CONFIG_PATH_UNRECOGNIZED", exception.getCause().getMessage());
+ return HttpResponse.badRequest().body(errorResult);
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/ConfigurationValidationExceptionHandler.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/ConfigurationValidationExceptionHandler.java
new file mode 100644
index 000000000..d9fe8cb98
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/ConfigurationValidationExceptionHandler.java
@@ -0,0 +1,50 @@
+/*
+ * 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.internal.rest.configuration.exception.handler;
+
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.MediaType;
+import io.micronaut.http.annotation.Produces;
+import io.micronaut.http.server.exceptions.ExceptionHandler;
+import jakarta.inject.Singleton;
+import java.util.stream.Collectors;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.ValidationIssue;
+import org.apache.ignite.internal.rest.api.ErrorResult;
+
+/**
+ * Handles {@link ConfigurationValidationException} and represents it as a rest response.
+ */
+@Produces(MediaType.APPLICATION_JSON)
+@Singleton
+@Requires(classes = {ConfigurationValidationException.class, ExceptionHandler.class})
+public class ConfigurationValidationExceptionHandler implements
+ ExceptionHandler<ConfigurationValidationException, HttpResponse<ErrorResult>> {
+
+ @Override
+ public HttpResponse<ErrorResult> handle(HttpRequest request, ConfigurationValidationException exception) {
+ String joinedMessage = exception.getIssues()
+ .stream()
+ .map(ValidationIssue::message)
+ .collect(Collectors.joining(","));
+ ErrorResult errorResult = new ErrorResult("VALIDATION_EXCEPTION", joinedMessage);
+ return HttpResponse.badRequest().body(errorResult);
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/IgniteExceptionHandler.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/IgniteExceptionHandler.java
new file mode 100644
index 000000000..df2f4704a
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/IgniteExceptionHandler.java
@@ -0,0 +1,43 @@
+/*
+ * 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.internal.rest.configuration.exception.handler;
+
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.annotation.Produces;
+import io.micronaut.http.server.exceptions.ExceptionHandler;
+import jakarta.inject.Singleton;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.internal.rest.api.ErrorResult;
+import org.apache.ignite.lang.IgniteException;
+
+/**
+ * Handles {@link ConfigurationValidationException} and represents it as a rest response.
+ */
+@Produces
+@Singleton
+@Requires(classes = {IgniteException.class, ExceptionHandler.class})
+public class IgniteExceptionHandler implements ExceptionHandler<IgniteException, HttpResponse<ErrorResult>> {
+
+ @Override
+ public HttpResponse<ErrorResult> handle(HttpRequest request, IgniteException exception) {
+ ErrorResult errorResult = new ErrorResult("APPLICATION_EXCEPTION", exception.getMessage());
+ return HttpResponse.serverError().body(errorResult);
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/InvalidConfigFormatExceptionHandler.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/InvalidConfigFormatExceptionHandler.java
new file mode 100644
index 000000000..cc71e1a4c
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/InvalidConfigFormatExceptionHandler.java
@@ -0,0 +1,43 @@
+/*
+ * 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.internal.rest.configuration.exception.handler;
+
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.MediaType;
+import io.micronaut.http.annotation.Produces;
+import io.micronaut.http.server.exceptions.ExceptionHandler;
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.rest.api.ErrorResult;
+import org.apache.ignite.internal.rest.configuration.exception.InvalidConfigFormatException;
+
+/**
+ * Handles {@link InvalidConfigFormatException} and represents it as a rest response.
+ */
+@Produces(MediaType.APPLICATION_JSON)
+@Singleton
+@Requires(classes = {InvalidConfigFormatException.class, ExceptionHandler.class})
+public class InvalidConfigFormatExceptionHandler implements ExceptionHandler<InvalidConfigFormatException, HttpResponse<ErrorResult>> {
+
+ @Override
+ public HttpResponse<ErrorResult> handle(HttpRequest request, InvalidConfigFormatException exception) {
+ ErrorResult errorResult = new ErrorResult("INVALID_CONFIG_FORMAT", exception.getCause().getCause().getMessage());
+ return HttpResponse.badRequest().body(errorResult);
+ }
+}
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/package-info.java
old mode 100755
new mode 100644
similarity index 78%
copy from modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
copy to modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/package-info.java
index 81b40c399..80af2d764
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/package-info.java
@@ -16,7 +16,8 @@
*/
/**
- * This package contains an implementation of Ignite component related to REST interface.
+ * This package contains classes that implement a number of ExceptionHandlers. They wrap plain exceptions into human-readable rest
+ * responses.
*/
-package org.apache.ignite.internal.rest;
+package org.apache.ignite.internal.rest.configuration.exception.handler;
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/package-info.java
old mode 100755
new mode 100644
similarity index 83%
copy from modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
copy to modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/package-info.java
index 81b40c399..33aa0d5ae
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/package-info.java
@@ -16,7 +16,7 @@
*/
/**
- * This package contains an implementation of Ignite component related to REST interface.
+ * This package contains classes that represent exceptions that can be thrown and their handlers.
*/
-package org.apache.ignite.internal.rest;
+package org.apache.ignite.internal.rest.configuration.exception;
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/ClusterConfigurationControllerTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/ClusterConfigurationControllerTest.java
new file mode 100644
index 000000000..98100e327
--- /dev/null
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/ClusterConfigurationControllerTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.internal.rest.configuration;
+
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.context.annotation.Replaces;
+import io.micronaut.http.client.HttpClient;
+import io.micronaut.http.client.annotation.Client;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
+import org.apache.ignite.internal.configuration.ConfigurationRegistry;
+import org.apache.ignite.internal.configuration.rest.presentation.ConfigurationPresentation;
+import org.apache.ignite.internal.configuration.rest.presentation.hocon.HoconPresentation;
+
+/**
+ * Functional test for {@link ClusterConfigurationController}.
+ */
+@MicronautTest
+class ClusterConfigurationControllerTest extends ConfigurationControllerBaseTest {
+
+ @Inject
+ @Client("/management/v1/configuration/cluster/")
+ HttpClient client;
+
+ @Override
+ HttpClient client() {
+ return client;
+ }
+
+ /**
+ * Creates test hocon configuration representation.
+ */
+ @Bean
+ @Named("clusterCfgPresentation")
+ @Replaces(factory = PresentationsFactory.class)
+ public ConfigurationPresentation<String> cfgPresentation(ConfigurationRegistry configurationRegistry) {
+ return new HoconPresentation(configurationRegistry);
+ }
+}
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/ConfigurationControllerBaseTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/ConfigurationControllerBaseTest.java
new file mode 100644
index 000000000..dd2864882
--- /dev/null
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/ConfigurationControllerBaseTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.internal.rest.configuration;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import io.micronaut.context.ApplicationContext;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpStatus;
+import io.micronaut.http.MediaType;
+import io.micronaut.http.client.HttpClient;
+import io.micronaut.http.client.exceptions.HttpClientResponseException;
+import io.micronaut.runtime.server.EmbeddedServer;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
+import org.apache.ignite.internal.configuration.ConfigurationRegistry;
+import org.apache.ignite.internal.configuration.rest.presentation.ConfigurationPresentation;
+import org.apache.ignite.internal.configuration.rest.presentation.TestRootConfiguration;
+import org.apache.ignite.internal.rest.api.ErrorResult;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The base test for configuration controllers.
+ */
+@MicronautTest
+public abstract class ConfigurationControllerBaseTest {
+ @Inject
+ private EmbeddedServer server;
+
+ @Inject
+ private ConfigurationPresentation<String> cfgPresentation;
+
+ @Inject
+ private ConfigurationRegistry configurationRegistry;
+
+ @Inject
+ private ApplicationContext context;
+
+ abstract HttpClient client();
+
+ @BeforeEach
+ void beforeEach() throws Exception {
+ var cfg = configurationRegistry.getConfiguration(TestRootConfiguration.KEY);
+ cfg.change(c -> c.changeFoo("foo").changeSubCfg(subCfg -> subCfg.changeBar("bar"))).get(1, SECONDS);
+ }
+
+ @Test
+ void testGetConfig() {
+ var response = client().toBlocking().exchange("", String.class);
+
+ assertEquals(HttpStatus.OK, response.status());
+ assertEquals(cfgPresentation.represent(), response.body());
+ }
+
+ @Test
+ void testGetConfigByPath() {
+ var response = client().toBlocking().exchange("/root.subCfg", String.class);
+
+ assertEquals(HttpStatus.OK, response.status());
+ assertEquals(cfgPresentation.representByPath("root.subCfg"), response.body());
+ }
+
+ @Test
+ void testUpdateConfig() {
+ String givenChangedConfig = "{root:{foo:foo,subCfg:{bar:changed}}}";
+
+ var response = client().toBlocking().exchange(
+ HttpRequest.PATCH("", givenChangedConfig).contentType(MediaType.TEXT_PLAIN)
+ );
+ assertEquals(response.status(), HttpStatus.OK);
+
+ String changedConfigValue = client().toBlocking().exchange("/root.subCfg.bar", String.class).body();
+ assertEquals("\"changed\"", changedConfigValue);
+ }
+
+ @Test
+ void testUnrecognizedConfigPath() {
+ var thrown = assertThrows(
+ HttpClientResponseException.class,
+ () -> client().toBlocking().exchange("/no-such-root.some-value")
+ );
+
+ assertEquals(HttpStatus.BAD_REQUEST, thrown.getResponse().status());
+
+ var errorResult = getErrorResult(thrown);
+ assertEquals("CONFIG_PATH_UNRECOGNIZED", errorResult.type());
+ assertEquals("Configuration value 'no-such-root' has not been found", errorResult.message());
+ }
+
+ @Test
+ void testUnrecognizedConfigPathForUpdate() {
+ String givenBrokenConfig = "{root:{foo:foo,subCfg:{no-such-bar:bar}}}";
+
+ var thrown = assertThrows(
+ HttpClientResponseException.class,
+ () -> client().toBlocking().exchange(HttpRequest.PATCH("", givenBrokenConfig).contentType(MediaType.TEXT_PLAIN))
+ );
+
+ assertEquals(HttpStatus.BAD_REQUEST, thrown.getResponse().status());
+
+ var errorResult = getErrorResult(thrown);
+ assertEquals("INVALID_CONFIG_FORMAT", errorResult.type());
+ assertEquals("'root.subCfg' configuration doesn't have the 'no-such-bar' sub-configuration", errorResult.message());
+ }
+
+ @Test
+ void testValidationForUpdate() {
+ String givenConfigWithError = "{root:{foo:error,subCfg:{bar:bar}}}";
+
+ var thrown = assertThrows(
+ HttpClientResponseException.class,
+ () -> client().toBlocking().exchange(HttpRequest.PATCH("", givenConfigWithError).contentType(MediaType.TEXT_PLAIN))
+ );
+
+ assertEquals(HttpStatus.BAD_REQUEST, thrown.getResponse().status());
+
+ var errorResult = getErrorResult(thrown);
+ assertEquals("VALIDATION_EXCEPTION", errorResult.type());
+ assertEquals("Error word", errorResult.message());
+ }
+
+ @NotNull
+ private ErrorResult getErrorResult(HttpClientResponseException exception) {
+ return exception.getResponse().getBody(ErrorResult.class).orElseThrow();
+ }
+}
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/NodeConfigurationControllerTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/NodeConfigurationControllerTest.java
new file mode 100644
index 000000000..679183995
--- /dev/null
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/NodeConfigurationControllerTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.internal.rest.configuration;
+
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.context.annotation.Replaces;
+import io.micronaut.http.client.HttpClient;
+import io.micronaut.http.client.annotation.Client;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
+import org.apache.ignite.internal.configuration.ConfigurationRegistry;
+import org.apache.ignite.internal.configuration.rest.presentation.ConfigurationPresentation;
+import org.apache.ignite.internal.configuration.rest.presentation.hocon.HoconPresentation;
+
+/**
+ * Functional test for {@link NodeConfigurationController}.
+ */
+@MicronautTest
+class NodeConfigurationControllerTest extends ConfigurationControllerBaseTest {
+
+ @Inject
+ @Client("/management/v1/configuration/node/")
+ HttpClient client;
+
+ @Override
+ HttpClient client() {
+ return client;
+ }
+
+ /**
+ * Creates test hocon configuration representation.
+ */
+ @Bean
+ @Named("nodeCfgPresentation")
+ @Replaces(factory = PresentationsFactory.class)
+ public ConfigurationPresentation<String> cfgPresentation(ConfigurationRegistry configurationRegistry) {
+ return new HoconPresentation(configurationRegistry);
+ }
+}
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestFactory.java b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestFactory.java
new file mode 100644
index 000000000..2c581753d
--- /dev/null
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestFactory.java
@@ -0,0 +1,70 @@
+/*
+ * 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.internal.rest.configuration;
+
+import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
+
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.context.annotation.Factory;
+import jakarta.inject.Singleton;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.validation.ValidationContext;
+import org.apache.ignite.configuration.validation.ValidationIssue;
+import org.apache.ignite.configuration.validation.Validator;
+import org.apache.ignite.internal.configuration.ConfigurationRegistry;
+import org.apache.ignite.internal.configuration.rest.presentation.TestRootConfiguration;
+import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
+
+/**
+ * Factory that creates beans needed for unit tests.
+ */
+@Factory
+public class TestFactory {
+ /**
+ * Creates test configuration registry.
+ */
+ @Singleton
+ @Bean(preDestroy = "stop")
+ public ConfigurationRegistry configurationRegistry() {
+ Validator<Value, Object> validator = new Validator<>() {
+ /** {@inheritDoc} */
+ @Override
+ public void validate(Value annotation, ValidationContext<Object> ctx) {
+ if (Objects.equals("error", ctx.getNewValue())) {
+ ctx.addIssue(new ValidationIssue("Error word"));
+ }
+ }
+ };
+
+ var configurationRegistry = new ConfigurationRegistry(
+ List.of(TestRootConfiguration.KEY),
+ Map.of(Value.class, Set.of(validator)),
+ new TestConfigurationStorage(LOCAL),
+ List.of(),
+ List.of()
+ );
+
+ configurationRegistry.start();
+
+ return configurationRegistry;
+ }
+}
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/Router.java b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestRootConfigurationSchema.java
similarity index 60%
rename from modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/Router.java
rename to modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestRootConfigurationSchema.java
index 6f3f4af42..fc180b8b7 100644
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/Router.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestRootConfigurationSchema.java
@@ -15,21 +15,22 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.rest.routes;
+package org.apache.ignite.internal.rest.configuration;
-import io.netty.handler.codec.http.HttpRequest;
-import java.util.Optional;
-import org.apache.ignite.internal.rest.api.Route;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.Value;
/**
- * Dispatcher of http requests.
+ * Test root configuration schema.
*/
-public interface Router {
- /**
- * Finds the route by request.
- *
- * @param req Request.
- * @return Route if found.
- */
- Optional<Route> route(HttpRequest req);
+@ConfigurationRoot(rootName = "root")
+public class TestRootConfigurationSchema {
+ /** Foo field. */
+ @Value(hasDefault = true)
+ public String foo = "foo";
+
+ /** Sub configuration schema. */
+ @ConfigValue
+ public TestSubConfigurationSchema subCfg;
}
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestSubConfigurationSchema.java
old mode 100755
new mode 100644
similarity index 70%
copy from modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
copy to modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestSubConfigurationSchema.java
index 81b40c399..28bff35c1
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestSubConfigurationSchema.java
@@ -15,8 +15,17 @@
* limitations under the License.
*/
+package org.apache.ignite.internal.rest.configuration;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.Value;
+
/**
- * This package contains an implementation of Ignite component related to REST interface.
+ * Test sub configuration schema.
*/
-
-package org.apache.ignite.internal.rest;
+@Config
+public class TestSubConfigurationSchema {
+ /** Bar field. */
+ @Value(hasDefault = true)
+ public String bar = "bar";
+}
diff --git a/modules/rest-api/pom.xml b/modules/rest-api/pom.xml
index 08a442150..6cf400020 100644
--- a/modules/rest-api/pom.xml
+++ b/modules/rest-api/pom.xml
@@ -33,46 +33,29 @@
<version>3.0.0-SNAPSHOT</version>
<dependencies>
- <!-- 3rd party dependencies -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
+ <artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
- <groupId>io.netty</groupId>
- <artifactId>netty-codec-http</artifactId>
- </dependency>
-
- <dependency>
- <groupId>org.jetbrains</groupId>
- <artifactId>annotations</artifactId>
- </dependency>
-
- <!-- Test dependencies -->
- <dependency>
- <groupId>org.hamcrest</groupId>
- <artifactId>hamcrest</artifactId>
- <scope>test</scope>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
- <groupId>org.junit.jupiter</groupId>
- <artifactId>junit-jupiter-api</artifactId>
- <scope>test</scope>
+ <groupId>jakarta.annotation</groupId>
+ <artifactId>jakarta.annotation-api</artifactId>
</dependency>
<dependency>
- <groupId>org.junit.jupiter</groupId>
- <artifactId>junit-jupiter-engine</artifactId>
- <scope>test</scope>
+ <groupId>jakarta.inject</groupId>
+ <artifactId>jakarta.inject-api</artifactId>
</dependency>
- <!-- Logging in tests -->
<dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-jdk14</artifactId>
- <scope>test</scope>
+ <groupId>io.swagger.core.v3</groupId>
+ <artifactId>swagger-annotations</artifactId>
</dependency>
</dependencies>
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/ErrorResult.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/ErrorResult.java
index 41b400adb..8dd6e570b 100644
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/ErrorResult.java
+++ b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/ErrorResult.java
@@ -17,6 +17,10 @@
package org.apache.ignite.internal.rest.api;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonGetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
/**
* Error result represent a tuple of error type and user-friendly error message.
*/
@@ -33,7 +37,8 @@ public class ErrorResult {
* @param type Error type describing the class of the error occurred.
* @param message User-friendly error message.
*/
- public ErrorResult(String type, String message) {
+ @JsonCreator
+ public ErrorResult(@JsonProperty("type") String type, @JsonProperty("message") String message) {
this.type = type;
this.message = message;
}
@@ -43,6 +48,7 @@ public class ErrorResult {
*
* @return Error type describing the class of the error occurred.
*/
+ @JsonGetter("type")
public String type() {
return type;
}
@@ -52,6 +58,7 @@ public class ErrorResult {
*
* @return User-friendly error message.
*/
+ @JsonGetter("message")
public String message() {
return message;
}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RequestHandler.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RequestHandler.java
deleted file mode 100644
index 22d0d9441..000000000
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RequestHandler.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.internal.rest.api;
-
-import java.util.concurrent.CompletableFuture;
-
-/**
- * Interface for defining REST API request handlers.
- */
-public interface RequestHandler {
- /**
- * Handles the given request and writes the response into the provided {@link RestApiHttpResponse}.
- *
- * @param request REST API request
- * @param response REST API response
- * @return Future that resolves as soon as the response is ready to be sent to the caller. It may return either a new response object
- * or the same as the provided via the {@code response} parameter.
- */
- CompletableFuture<RestApiHttpResponse> handle(RestApiHttpRequest request, RestApiHttpResponse response);
-}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestApiHttpRequest.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestApiHttpRequest.java
deleted file mode 100644
index af492526f..000000000
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestApiHttpRequest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.internal.rest.api;
-
-import io.netty.handler.codec.http.FullHttpRequest;
-import java.util.Collections;
-import java.util.Map;
-
-/**
- * HTTP request wrapper with GET query params if exists.
- */
-public class RestApiHttpRequest {
- /** Request. */
- private final FullHttpRequest req;
-
- /** Query params. */
- private final Map<String, String> qryParams;
-
- /**
- * Creates a new instance of http request.
- *
- * @param req Request.
- * @param qryParams Query params.
- */
- public RestApiHttpRequest(FullHttpRequest req, Map<String, String> qryParams) {
- this.req = req;
- this.qryParams = Collections.unmodifiableMap(qryParams);
- }
-
- /**
- * Returns complete HTTP request.
- *
- * @return Complete HTTP request.
- */
- public FullHttpRequest request() {
- return req;
- }
-
- /**
- * Returns query parameters associated with the request.
- *
- * @return Query parameters.
- */
- public Map<String, String> queryParams() {
- return qryParams;
- }
-}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestApiHttpResponse.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestApiHttpResponse.java
deleted file mode 100644
index daa8ead5b..000000000
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestApiHttpResponse.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * 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.internal.rest.api;
-
-import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpHeaderValues;
-import io.netty.handler.codec.http.HttpHeaders;
-import io.netty.handler.codec.http.HttpResponse;
-import io.netty.handler.codec.http.HttpResponseStatus;
-import io.netty.handler.codec.http.HttpVersion;
-
-/**
- * Simple wrapper of HTTP response with some helper methods for filling it with headers and content.
- */
-public class RestApiHttpResponse {
- private static final ObjectMapper objectMapper = new ObjectMapper()
- .setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
-
- /** Response. */
- private final HttpResponse res;
-
- /** Content. */
- private byte[] content;
-
- /**
- * Creates a new HTTP response with the given message body.
- *
- * @param res Response.
- * @param content Content.
- */
- public RestApiHttpResponse(HttpResponse res, byte[] content) {
- this.res = res;
- this.content = content;
- }
-
- /**
- * Creates a new HTTP response with the given headers and status.
- *
- * @param res Response.
- */
- public RestApiHttpResponse(HttpResponse res) {
- this.res = res;
- }
-
- /**
- * Set raw bytes as response body.
- *
- * @param content Content data.
- * @return Updated response.
- */
- public RestApiHttpResponse content(byte[] content) {
- this.content = content;
- return this;
- }
-
- /**
- * Returns content in a form of raw bytes.
- *
- * @return Content.
- */
- public byte[] content() {
- return content;
- }
-
- /**
- * Set JSON representation of input object as response body.
- *
- * @param content Content object.
- * @return Updated response.
- */
- public RestApiHttpResponse json(Object content) {
- try {
- this.content = objectMapper.writeValueAsBytes(content);
- } catch (JsonProcessingException e) {
- throw new IllegalArgumentException("Unable to serialize JSON content", e);
- }
-
- headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON.toString());
-
- return this;
- }
-
- /**
- * Returns HTTP status of this response.
- *
- * @return HTTP Status.
- */
- public HttpResponseStatus status() {
- return res.status();
- }
-
- /**
- * Sets HTTP status.
- *
- * @param status Status.
- * @return Updated response.
- */
- public RestApiHttpResponse status(HttpResponseStatus status) {
- res.setStatus(status);
- return this;
- }
-
- /**
- * Returns protocol version.
- *
- * @return Protocol version
- */
- public HttpVersion protocolVersion() {
- return res.protocolVersion();
- }
-
- /**
- * Sets protocol version.
- *
- * @param httpVer HTTP version.
- * @return Updated response.
- */
- public RestApiHttpResponse protocolVersion(HttpVersion httpVer) {
- res.setProtocolVersion(httpVer);
- return this;
- }
-
- /**
- * Returns mutable response headers.
- *
- * @return Mutable response headers.
- */
- public HttpHeaders headers() {
- return res.headers();
- }
-}
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/package-info.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestFactory.java
old mode 100755
new mode 100644
similarity index 83%
rename from modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/package-info.java
rename to modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestFactory.java
index c7ab2540c..e1e89c775
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/package-info.java
+++ b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestFactory.java
@@ -15,8 +15,10 @@
* limitations under the License.
*/
+package org.apache.ignite.internal.rest.api;
+
/**
- * This package contains classes that represent inbound requests, outbound responses and main REST handler.
+ * Factory that produces all beans that is necessary for the controller class.
*/
-
-package org.apache.ignite.internal.rest.netty;
+public interface RestFactory {
+}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Route.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Route.java
deleted file mode 100644
index 90c3b474d..000000000
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Route.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * 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.internal.rest.api;
-
-import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpMethod;
-import io.netty.handler.codec.http.HttpRequest;
-import java.util.ArrayDeque;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * URI route with appropriate handler for request.
- */
-public class Route {
- /** Route. */
- private final String route;
-
- /** Method. */
- private final HttpMethod method;
-
- /** Accept type. */
- @Nullable
- private final String acceptType;
-
- /** Handler. */
- private final RequestHandler hnd;
-
- /**
- * Create a new URI route with the given parameters.
- *
- * @param route Route.
- * @param method Method.
- * @param acceptType Accept type.
- * @param hnd Request handler.
- */
- public Route(
- String route,
- HttpMethod method,
- @Nullable String acceptType,
- RequestHandler hnd
- ) {
- this.route = route;
- this.method = method;
- this.acceptType = acceptType;
- this.hnd = hnd;
- }
-
- /**
- * Handles the query by populating the received response object.
- *
- * @param req Request.
- * @param resp Response.
- */
- public CompletableFuture<RestApiHttpResponse> handle(FullHttpRequest req, RestApiHttpResponse resp) {
- return hnd.handle(new RestApiHttpRequest(req, paramsDecode(req.uri())), resp);
- }
-
- /**
- * Checks if the current route matches the request.
- *
- * @param req Request.
- * @return true if route matches the request, else otherwise.
- */
- public boolean match(HttpRequest req) {
- return req.method().equals(method)
- && matchUri(req.uri())
- && matchContentType(req.headers().get(HttpHeaderNames.CONTENT_TYPE));
- }
-
- /**
- * Returns {@code true} if route matches the request, else otherwise.
- *
- * @param s Content type.
- * @return {@code true} if route matches the request, else otherwise.
- */
- private boolean matchContentType(String s) {
- return (acceptType == null) || (acceptType.equals(s));
- }
-
- /**
- * Checks the current route matches input uri. REST API like URIs "/user/:user" is also supported.
- *
- * @param uri Input URI
- * @return true if route matches the request, else otherwise.
- */
- private boolean matchUri(String uri) {
- var receivedParts = new ArrayDeque<>(Arrays.asList(uri.split("/")));
- var realParts = new ArrayDeque<>(Arrays.asList(route.split("/")));
-
- String part;
- while ((part = realParts.pollFirst()) != null) {
- String receivedPart = receivedParts.pollFirst();
- if (receivedPart == null) {
- return false;
- }
-
- if (part.startsWith(":")) {
- continue;
- }
-
- if (!part.equals(receivedPart)) {
- return false;
- }
- }
-
- return receivedParts.isEmpty();
- }
-
- /**
- * Decodes params from REST like URIs "/user/:user".
- *
- * @param uri Input URI.
- * @return Map of decoded params.
- * @throws IllegalArgumentException if provided URI is incorrect.
- */
- private Map<String, String> paramsDecode(String uri) {
- var receivedParts = new ArrayDeque<>(Arrays.asList(uri.split("/")));
- var realParts = new ArrayDeque<>(Arrays.asList(route.split("/")));
-
- Map<String, String> res = new HashMap<>();
-
- String part;
- while ((part = realParts.pollFirst()) != null) {
- String receivedPart = receivedParts.pollFirst();
- if (receivedPart == null) {
- throw new IllegalArgumentException("URI is incorrect");
- }
-
- if (part.startsWith(":")) {
- res.put(part.substring(1), receivedPart);
- continue;
- }
-
- if (!part.equals(receivedPart)) {
- throw new IllegalArgumentException("URI is incorrect");
- }
- }
-
- return res;
- }
-}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Routes.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Routes.java
deleted file mode 100644
index 2dc53f33d..000000000
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Routes.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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.internal.rest.api;
-
-import io.netty.handler.codec.http.HttpMethod;
-
-/**
- * Allows to configure routes to REST handlers mapping.
- */
-public interface Routes {
- /**
- * Adds the route to the router chain.
- *
- * @param route Route
- */
- void addRoute(Route route);
-
- /**
- * Adds a GET handler.
- *
- * @param route Route.
- * @param acceptType Accept type.
- * @param hnd Actual handler of the request.
- * @return Router
- */
- default Routes get(String route, String acceptType, RequestHandler hnd) {
- addRoute(new Route(route, HttpMethod.GET, acceptType, hnd));
- return this;
- }
-
- /**
- * Adds a GET handler.
- *
- * @param route Route.
- * @param hnd Actual handler of the request.
- * @return Router
- */
- default Routes get(String route, RequestHandler hnd) {
- addRoute(new Route(route, HttpMethod.GET, null, hnd));
- return this;
- }
-
- /**
- * Adds a PUT handler.
- *
- * @param route Route.
- * @param acceptType Accept type.
- * @param hnd Actual handler of the request.
- * @return Router
- */
- default Routes put(String route, String acceptType, RequestHandler hnd) {
- addRoute(new Route(route, HttpMethod.PUT, acceptType, hnd));
- return this;
- }
-
- /**
- * Adds a PATCH handler.
- *
- * @param route Route.
- * @param acceptType Accept type.
- * @param hnd Actual handler of the request.
- * @return Router
- */
- default Routes patch(String route, String acceptType, RequestHandler hnd) {
- addRoute(new Route(route, HttpMethod.PATCH, acceptType, hnd));
- return this;
- }
-
- /**
- * Adds a POST handler.
- *
- * @param route Route.
- * @param acceptType Accept type.
- * @param hnd Actual handler of the request.
- * @return Router
- */
- default Routes post(String route, String acceptType, RequestHandler hnd) {
- addRoute(new Route(route, HttpMethod.POST, acceptType, hnd));
- return this;
- }
-}
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/package-info.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/package-info.java
old mode 100755
new mode 100644
similarity index 86%
rename from modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/package-info.java
rename to modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/package-info.java
index abad541e4..678a10d96
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/package-info.java
+++ b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/package-info.java
@@ -16,7 +16,7 @@
*/
/**
- * This package contains classes that represent URI routers and their dispatching.
+ * This package contains common DTOs and interfaces for REST API.
*/
-package org.apache.ignite.internal.rest.routes;
+package org.apache.ignite.internal.rest.api;
diff --git a/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/api/RestApiHttpResponseTest.java b/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/api/RestApiHttpResponseTest.java
deleted file mode 100644
index 241909940..000000000
--- a/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/api/RestApiHttpResponseTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.internal.rest.api;
-
-import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
-import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON;
-import static io.netty.handler.codec.http.HttpResponseStatus.OK;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import io.netty.handler.codec.http.DefaultHttpResponse;
-import io.netty.handler.codec.http.HttpVersion;
-import java.util.Map;
-import org.junit.jupiter.api.Test;
-
-/**
- * Testing the {@link RestApiHttpResponse}.
- */
-public class RestApiHttpResponseTest {
- @Test
- void testToJson() {
- RestApiHttpResponse res = new RestApiHttpResponse(new DefaultHttpResponse(HttpVersion.HTTP_1_1, OK));
-
- Map<?, String> value = Map.of(
- "{root:{foo:foo,subCfg:{bar:bar}}}", "\"{root:{foo:foo,subCfg:{bar:bar}}}\"",
- Map.of("err", new ErrorResult("t", "m")), "{\"err\":{\"type\":\"t\",\"message\":\"m\"}}"
- );
-
- for (Map.Entry<?, String> e : value.entrySet()) {
- res.headers().clear();
-
- String act = new String(res.json(e.getKey()).content(), UTF_8);
-
- assertEquals(e.getValue(), act);
- assertEquals(res.headers().get(CONTENT_TYPE), APPLICATION_JSON.toString());
- }
- }
-}
diff --git a/modules/rest/openapi/openapi.yaml b/modules/rest/openapi/openapi.yaml
new file mode 100644
index 000000000..cf7b681b1
--- /dev/null
+++ b/modules/rest/openapi/openapi.yaml
@@ -0,0 +1,145 @@
+openapi: 3.0.1
+info:
+ title: Ignite REST module
+ contact:
+ email: user@ignite.apache.org
+ license:
+ name: Apache 2.0
+ url: https://ignite.apache.org
+ version: 3.0.0-alpha
+paths:
+ /management/v1/cluster/init:
+ post:
+ tags:
+ - clusterManagement
+ operationId: init
+ parameters: []
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/InitCommand'
+ required: true
+ responses:
+ "200":
+ description: Cluster initialized
+ "409":
+ description: Cluster already initialized
+ /management/v1/configuration/cluster:
+ get:
+ tags:
+ - clusterConfiguration
+ operationId: getClusterConfiguration
+ parameters: []
+ responses:
+ "200":
+ description: Get cluster configuration
+ content:
+ text/plain:
+ schema:
+ type: string
+ patch:
+ tags:
+ - clusterConfiguration
+ operationId: updateClusterConfiguration
+ parameters: []
+ requestBody:
+ content:
+ text/plain:
+ schema:
+ type: string
+ required: true
+ responses:
+ "200":
+ description: Configuration updated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Void'
+ /management/v1/configuration/cluster/{path}:
+ get:
+ tags:
+ - clusterConfiguration
+ operationId: getClusterConfigurationByPath
+ parameters:
+ - name: path
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ description: Configuration represented by path
+ content:
+ text/plain:
+ schema:
+ type: string
+ /management/v1/configuration/node:
+ get:
+ tags:
+ - nodeConfiguration
+ operationId: getNodeConfiguration
+ parameters: []
+ responses:
+ "200":
+ description: Whole node configuration
+ content:
+ text/plain:
+ schema:
+ type: string
+ patch:
+ tags:
+ - nodeConfiguration
+ operationId: updateNodeConfiguration
+ parameters: []
+ requestBody:
+ content:
+ text/plain:
+ schema:
+ type: string
+ required: true
+ responses:
+ "200":
+ description: Configuration updated
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Void'
+ /management/v1/configuration/node/{path}:
+ get:
+ tags:
+ - nodeConfiguration
+ operationId: getNodeConfigurationByPath
+ parameters:
+ - name: path
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ description: Configuration represented by path
+ content:
+ text/plain:
+ schema:
+ type: string
+components:
+ schemas:
+ InitCommand:
+ required:
+ - clusterName
+ - metaStorageNodes
+ type: object
+ properties:
+ metaStorageNodes:
+ type: array
+ items:
+ type: string
+ cmgNodes:
+ type: array
+ items:
+ type: string
+ clusterName:
+ type: string
+ Void:
+ type: object
diff --git a/modules/rest/pom.xml b/modules/rest/pom.xml
index cd924ee0c..8836e8ab1 100644
--- a/modules/rest/pom.xml
+++ b/modules/rest/pom.xml
@@ -45,52 +45,36 @@
<dependency>
<groupId>org.apache.ignite</groupId>
- <artifactId>ignite-api</artifactId>
+ <artifactId>ignite-cluster-management</artifactId>
</dependency>
<dependency>
<groupId>org.apache.ignite</groupId>
- <artifactId>ignite-network</artifactId>
+ <artifactId>ignite-api</artifactId>
</dependency>
<!-- 3rd party dependencies -->
<dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-databind</artifactId>
- </dependency>
-
- <dependency>
- <groupId>io.netty</groupId>
- <artifactId>netty-common</artifactId>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-inject</artifactId>
</dependency>
<dependency>
- <groupId>io.netty</groupId>
- <artifactId>netty-buffer</artifactId>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-http-server-netty</artifactId>
</dependency>
<dependency>
- <groupId>io.netty</groupId>
- <artifactId>netty-codec</artifactId>
+ <groupId>io.micronaut.openapi</groupId>
+ <artifactId>micronaut-openapi</artifactId>
</dependency>
<dependency>
- <groupId>io.netty</groupId>
- <artifactId>netty-handler</artifactId>
- </dependency>
-
- <dependency>
- <groupId>io.netty</groupId>
- <artifactId>netty-codec-http</artifactId>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
</dependency>
<!-- Test dependencies -->
- <dependency>
- <groupId>org.hamcrest</groupId>
- <artifactId>hamcrest</artifactId>
- <scope>test</scope>
- </dependency>
-
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
@@ -110,13 +94,6 @@
<type>test-jar</type>
</dependency>
- <dependency>
- <groupId>org.apache.ignite</groupId>
- <artifactId>ignite-core</artifactId>
- <scope>test</scope>
- <type>test-jar</type>
- </dependency>
-
<!-- Logging in tests -->
<dependency>
<groupId>org.slf4j</groupId>
@@ -151,7 +128,20 @@
<artifactId>ignite-configuration-annotation-processor</artifactId>
<version>${project.version}</version>
</path>
+ <path>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-inject-java</artifactId>
+ <version>${micronaut.version}</version>
+ </path>
+ <path>
+ <groupId>io.micronaut.openapi</groupId>
+ <artifactId>micronaut-openapi</artifactId>
+ <version>${maven.micronaut.openapi.plugin.version}</version>
+ </path>
</annotationProcessorPaths>
+ <compilerArgs>
+ <arg>-J-Dmicronaut.openapi.target.file=openapi/openapi.yaml</arg>
+ </compilerArgs>
</configuration>
</plugin>
</plugins>
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java b/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java
index f660e03d0..49891c692 100644
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java
+++ b/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java
@@ -17,151 +17,166 @@
package org.apache.ignite.internal.rest;
-import io.netty.bootstrap.ServerBootstrap;
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelFuture;
-import io.netty.handler.logging.LogLevel;
-import io.netty.handler.logging.LoggingHandler;
+import io.micronaut.context.ApplicationContext;
+import io.micronaut.http.server.exceptions.ServerStartupException;
+import io.micronaut.openapi.annotation.OpenAPIInclude;
+import io.micronaut.runtime.Micronaut;
+import io.micronaut.runtime.exceptions.ApplicationStartupException;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.info.Contact;
+import io.swagger.v3.oas.annotations.info.Info;
+import io.swagger.v3.oas.annotations.info.License;
import java.net.BindException;
-import java.net.InetSocketAddress;
-import java.util.function.Consumer;
+import java.util.List;
+import java.util.Map;
import org.apache.ignite.configuration.schemas.rest.RestConfiguration;
import org.apache.ignite.configuration.schemas.rest.RestView;
-import org.apache.ignite.internal.configuration.ConfigurationManager;
-import org.apache.ignite.internal.configuration.ConfigurationRegistry;
+import org.apache.ignite.internal.cluster.management.rest.ClusterManagementController;
import org.apache.ignite.internal.manager.IgniteComponent;
-import org.apache.ignite.internal.rest.api.RestHandlersRegister;
-import org.apache.ignite.internal.rest.api.Routes;
-import org.apache.ignite.internal.rest.netty.RestApiInitializer;
-import org.apache.ignite.internal.rest.routes.Router;
-import org.apache.ignite.internal.rest.routes.SimpleRouter;
-import org.apache.ignite.lang.IgniteException;
+import org.apache.ignite.internal.rest.api.RestFactory;
+import org.apache.ignite.internal.rest.configuration.ClusterConfigurationController;
+import org.apache.ignite.internal.rest.configuration.NodeConfigurationController;
import org.apache.ignite.lang.IgniteInternalException;
import org.apache.ignite.lang.IgniteLogger;
-import org.apache.ignite.network.NettyBootstrapFactory;
+import org.jetbrains.annotations.Nullable;
/**
- * Rest component is responsible for starting REST endpoints.
+ * Rest module is responsible for starting REST endpoints for accessing and managing configuration.
*
- * <p>It is started on port 10300 by default, but it is possible to change this in configuration itself. Refer to default config file in
+ * <p>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 RestComponent implements RestHandlersRegister, IgniteComponent {
+@OpenAPIDefinition(info = @Info(title = "Ignite REST module", version = "3.0.0-alpha", license = @License(name = "Apache 2.0", url = "https://ignite.apache.org"), contact = @Contact(email = "user@ignite.apache.org")))
+@OpenAPIInclude(classes = {ClusterConfigurationController.class, NodeConfigurationController.class, ClusterManagementController.class})
+public class RestComponent implements IgniteComponent {
+ /** Default port. */
+ public static final int DFLT_PORT = 10300;
+
+ /** Server host. */
+ private static final String LOCALHOST = "localhost";
+
/** Ignite logger. */
private final IgniteLogger log = IgniteLogger.forClass(RestComponent.class);
- /** Node configuration register. */
- private final ConfigurationRegistry nodeCfgRegistry;
+ /** Factories that produce beans needed for REST controllers. */
+ private final List<RestFactory> restFactories;
- /** Netty bootstrap factory. */
- private final NettyBootstrapFactory bootstrapFactory;
+ private final RestConfiguration restConfiguration;
- private final SimpleRouter router = new SimpleRouter();
+ /** Micronaut application context. */
+ private volatile ApplicationContext context;
- /** Netty channel. */
- private volatile Channel channel;
+ /** Server port. */
+ private int port;
/**
* Creates a new instance of REST module.
- *
- * @param nodeCfgMgr Node configuration manager.
- * @param bootstrapFactory Netty bootstrap factory.
*/
- public RestComponent(ConfigurationManager nodeCfgMgr, NettyBootstrapFactory bootstrapFactory) {
- nodeCfgRegistry = nodeCfgMgr.configurationRegistry();
-
- this.bootstrapFactory = bootstrapFactory;
- }
-
- /** {@inheritDoc} */
- @Override
- public void registerHandlers(Consumer<Routes> registerAction) {
- registerAction.accept(router);
+ public RestComponent(List<RestFactory> restFactories, RestConfiguration restConfiguration) {
+ this.restFactories = restFactories;
+ this.restConfiguration = restConfiguration;
}
/** {@inheritDoc} */
@Override
public void start() {
- if (channel != null) {
- throw new IgniteException("RestModule is already started.");
- }
-
- channel = startRestEndpoint(router).channel();
- }
-
- /**
- * Start endpoint.
- *
- * @param router Dispatcher of http requests.
- * @return Future which will be notified when this channel is closed.
- * @throws RuntimeException if this module cannot be bound to a port.
- */
- private ChannelFuture startRestEndpoint(Router router) {
- RestView restConfigurationView = nodeCfgRegistry.getConfiguration(RestConfiguration.KEY).value();
+ RestView restConfigurationView = restConfiguration.value();
int desiredPort = restConfigurationView.port();
int portRange = restConfigurationView.portRange();
- int port = 0;
- Channel ch = null;
-
- ServerBootstrap bootstrap = bootstrapFactory.createServerBootstrap()
- .handler(new LoggingHandler(LogLevel.INFO))
- .childHandler(new RestApiInitializer(router));
-
for (int portCandidate = desiredPort; portCandidate <= desiredPort + portRange; portCandidate++) {
- ChannelFuture bindRes = bootstrap.bind(portCandidate).awaitUninterruptibly();
-
- if (bindRes.isSuccess()) {
- ch = bindRes.channel();
+ try {
port = portCandidate;
- break;
- } else if (!(bindRes.cause() instanceof BindException)) {
- throw new RuntimeException(bindRes.cause());
+ context = buildMicronautContext(portCandidate).start();
+ log.info("REST protocol started successfully");
+ return;
+ } catch (ApplicationStartupException e) {
+ BindException bindException = findBindException(e);
+ if (bindException != null) {
+ log.error("Got exception " + bindException + " during node start on port "
+ + portCandidate + " , trying again");
+ continue;
+ }
+ throw new RuntimeException(e);
}
}
- if (ch == null) {
- String msg = "Cannot start REST endpoint. "
- + "All ports in range [" + desiredPort + ", " + (desiredPort + portRange) + "] are in use.";
-
- log.error(msg);
+ String msg = "Cannot start REST endpoint. " + "All ports in range [" + desiredPort + ", " + (desiredPort + portRange)
+ + "] are in use.";
+ log.error(msg);
+ throw new RuntimeException(msg);
+ }
- throw new RuntimeException(msg);
+ @Nullable
+ private BindException findBindException(ApplicationStartupException e) {
+ var throwable = e.getCause();
+ while (throwable != null) {
+ if (throwable instanceof BindException) {
+ return (BindException) throwable;
+ }
+ throwable = throwable.getCause();
}
+ return null;
+ }
- if (log.isInfoEnabled()) {
- log.info("REST protocol started successfully on port " + port);
+ private Micronaut buildMicronautContext(int portCandidate) {
+ Micronaut micronaut = Micronaut.build("");
+ setFactories(micronaut);
+ return micronaut
+ .properties(Map.of("micronaut.server.port", portCandidate))
+ .banner(false)
+ .mapError(ServerStartupException.class, this::mapServerStartupException)
+ .mapError(ApplicationStartupException.class, ex -> -1);
+ }
+
+ private void setFactories(Micronaut micronaut) {
+ for (var factory : restFactories) {
+ micronaut.singletons(factory);
}
+ }
- return ch.closeFuture();
+ private int mapServerStartupException(ServerStartupException exception) {
+ if (exception.getCause() instanceof BindException) {
+ return -1; // -1 forces the micronaut to throw an ApplicationStartupException
+ } else {
+ return 1;
+ }
}
/** {@inheritDoc} */
@Override
- public void stop() throws Exception {
+ public synchronized void stop() throws Exception {
// TODO: IGNITE-16636 Use busy-lock approach to guard stopping RestComponent
-
- if (channel != null) {
- channel.close().await();
-
- channel = null;
+ if (context != null) {
+ context.stop();
+ context = null;
}
}
/**
- * Returns the local address that the REST endpoint is bound to.
+ * Returns server port.
*
- * @return local REST address.
* @throws IgniteInternalException if the component has not been started yet.
*/
- public InetSocketAddress localAddress() {
- Channel channel = this.channel;
+ public int port() {
+ if (context == null) {
+ throw new IgniteInternalException("RestComponent has not been started");
+ }
+
+ return port;
+ }
- if (channel == null) {
+ /**
+ * Returns server host.
+ *
+ * @throws IgniteInternalException if the component has not been started yet.
+ */
+ public String host() {
+ if (context == null) {
throw new IgniteInternalException("RestComponent has not been started");
}
- return (InetSocketAddress) channel.localAddress();
+ return LOCALHOST;
}
}
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/RestApiHandler.java b/modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/RestApiHandler.java
deleted file mode 100644
index a392ec221..000000000
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/RestApiHandler.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * 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.internal.rest.netty;
-
-import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
-import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
-import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE;
-import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;
-import static io.netty.handler.codec.http.HttpResponseStatus.OK;
-
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.EmptyByteBuf;
-import io.netty.buffer.Unpooled;
-import io.netty.buffer.UnpooledByteBufAllocator;
-import io.netty.channel.ChannelFuture;
-import io.netty.channel.ChannelFutureListener;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.SimpleChannelInboundHandler;
-import io.netty.handler.codec.http.DefaultFullHttpResponse;
-import io.netty.handler.codec.http.DefaultHttpResponse;
-import io.netty.handler.codec.http.EmptyHttpHeaders;
-import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.HttpResponseStatus;
-import io.netty.handler.codec.http.HttpUtil;
-import io.netty.handler.codec.http.HttpVersion;
-import java.util.concurrent.CompletableFuture;
-import org.apache.ignite.internal.rest.api.RestApiHttpResponse;
-import org.apache.ignite.internal.rest.routes.Router;
-import org.apache.ignite.lang.IgniteLogger;
-
-/**
- * Main handler of REST HTTP chain. It receives http request, process it by {@link Router} and produce http response.
- */
-public class RestApiHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
- /** Ignite logger. */
- private final IgniteLogger log = IgniteLogger.forClass(getClass());
-
- /** Requests' router. */
- private final Router router;
-
- /**
- * Creates a new instance of API handler.
- *
- * @param router Router.
- */
- public RestApiHandler(Router router) {
- this.router = router;
- }
-
- /** {@inheritDoc} */
- @Override
- protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
- CompletableFuture<DefaultFullHttpResponse> responseFuture = router.route(request)
- .map(route -> {
- var response = new RestApiHttpResponse(new DefaultHttpResponse(HttpVersion.HTTP_1_1, OK));
-
- return route.handle(request, response)
- .thenApply(resp -> {
- ByteBuf content = resp.content() != null
- ? Unpooled.wrappedBuffer(resp.content())
- : new EmptyByteBuf(UnpooledByteBufAllocator.DEFAULT);
-
- return new DefaultFullHttpResponse(
- resp.protocolVersion(),
- resp.status(),
- content,
- resp.headers(),
- EmptyHttpHeaders.INSTANCE
- );
- });
- })
- .orElseGet(() -> CompletableFuture.completedFuture(
- new DefaultFullHttpResponse(request.protocolVersion(), HttpResponseStatus.NOT_FOUND)
- ));
-
- responseFuture
- .whenCompleteAsync((response, e) -> {
- if (e != null) {
- exceptionCaught(ctx, e);
-
- return;
- }
-
- response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());
-
- boolean keepAlive = HttpUtil.isKeepAlive(request);
-
- if (keepAlive) {
- if (!request.protocolVersion().isKeepAliveDefault()) {
- response.headers().set(CONNECTION, KEEP_ALIVE);
- }
- } else {
- response.headers().set(CONNECTION, CLOSE);
- }
-
- ChannelFuture f = ctx.writeAndFlush(response);
-
- if (!keepAlive) {
- f.addListener(ChannelFutureListener.CLOSE);
- }
- }, ctx.executor());
- }
-
- /** {@inheritDoc} */
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
- log.error("Failed to process http request:", cause);
- var res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR);
- ctx.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE);
- }
-}
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/RestApiInitializer.java b/modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/RestApiInitializer.java
deleted file mode 100644
index 1fd3ccc6c..000000000
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/RestApiInitializer.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.internal.rest.netty;
-
-import io.netty.channel.ChannelInitializer;
-import io.netty.channel.ChannelPipeline;
-import io.netty.channel.socket.SocketChannel;
-import io.netty.handler.codec.http.HttpObjectAggregator;
-import io.netty.handler.codec.http.HttpServerCodec;
-import org.apache.ignite.internal.rest.routes.Router;
-
-/**
- * Initializes channel with needed handlers for HTTP processing.
- */
-public class RestApiInitializer extends ChannelInitializer<SocketChannel> {
- /** Router. */
- private final Router router;
-
- /**
- * Creates a new instance of initializer.
- *
- * @param router Router.
- */
- public RestApiInitializer(Router router) {
- this.router = router;
- }
-
- /** {@inheritDoc} */
- @Override
- public void initChannel(SocketChannel ch) {
- ChannelPipeline p = ch.pipeline();
- p.addLast(new HttpServerCodec());
- p.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
- p.addLast(new RestApiHandler(router));
- }
-}
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java b/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
old mode 100755
new mode 100644
index 81b40c399..2e2f9e6d6
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
+++ b/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
@@ -16,7 +16,7 @@
*/
/**
- * This package contains an implementation of Ignite component related to REST interface.
+ * This package contains REST component core implementation.
*/
package org.apache.ignite.internal.rest;
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/SimpleRouter.java b/modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/SimpleRouter.java
deleted file mode 100644
index c534085bf..000000000
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/SimpleRouter.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.internal.rest.routes;
-
-import io.netty.handler.codec.http.HttpRequest;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import org.apache.ignite.internal.rest.api.Route;
-import org.apache.ignite.internal.rest.api.Routes;
-
-/**
- * Dispatcher of http requests.
- *
- * <p>Example:
- * <pre>
- * {@code
- * var router = new SimpleRouter();
- * router.get("/user", (req, resp) -> {
- * resp.status(HttpResponseStatus.OK);
- * });
- * }
- * </pre>
- */
-public class SimpleRouter implements Router, Routes {
- /** Routes. */
- private final List<Route> routes;
-
- /**
- * Creates a new empty router.
- */
- public SimpleRouter() {
- routes = new ArrayList<>();
- }
-
- /**
- * Adds the route to router chain.
- *
- * @param route Route
- */
- @Override
- public void addRoute(Route route) {
- routes.add(route);
- }
-
- /**
- * Finds the route by request.
- *
- * @param req Request.
- * @return Route if founded.
- */
- @Override
- public Optional<Route> route(HttpRequest req) {
- return routes.stream().filter(r -> r.match(req)).findFirst();
- }
-}
diff --git a/modules/rest/src/test/java/org/apache/ignite/internal/rest/routes/RouteTest.java b/modules/rest/src/test/java/org/apache/ignite/internal/rest/routes/RouteTest.java
deleted file mode 100644
index 8e0b8eaa0..000000000
--- a/modules/rest/src/test/java/org/apache/ignite/internal/rest/routes/RouteTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.internal.rest.routes;
-
-import static io.netty.handler.codec.http.HttpMethod.GET;
-import static io.netty.handler.codec.http.HttpMethod.PATCH;
-import static io.netty.handler.codec.http.HttpMethod.PUT;
-import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import io.netty.handler.codec.http.DefaultHttpRequest;
-import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpHeaderValues;
-import java.util.concurrent.CompletableFuture;
-import org.apache.ignite.internal.rest.api.RequestHandler;
-import org.apache.ignite.internal.rest.api.Route;
-import org.junit.jupiter.api.Test;
-
-/**
- * Test class for {@link Route}.
- */
-public class RouteTest {
- private static final RequestHandler NO_OP_HANDLER = (request, response) -> CompletableFuture.completedFuture(response);
-
- @Test
- void testMatchByUri() {
- var route = new Route("/user", GET, null, NO_OP_HANDLER);
- var req = new DefaultHttpRequest(HTTP_1_1, GET, "/user");
- assertTrue(route.match(req));
- }
-
- @Test
- void testNonMatchByUri() {
- var route = new Route("/user", GET, null, NO_OP_HANDLER);
- var req = new DefaultHttpRequest(HTTP_1_1, GET, "/user/1");
- assertFalse(route.match(req));
- }
-
- @Test
- void testMatchByContentTypeIfAcceptTypeEmpty() {
- var route = new Route("/user", GET, null, NO_OP_HANDLER);
- var req = new DefaultHttpRequest(HTTP_1_1, GET, "/user/");
- req.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN);
- assertTrue(route.match(req));
- }
-
- @Test
- void testMatchByContentTypeIfAcceptTypeNonEmpty() {
- var route = new Route("/user", PUT, "text/plain", NO_OP_HANDLER);
- var req = new DefaultHttpRequest(HTTP_1_1, PUT, "/user");
- req.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN);
- assertTrue(route.match(req));
- }
-
- @Test
- void testNonMatchByContentTypeIfAcceptTypeNonEmpty() {
- var route = new Route("/user", PUT, "text/plain", NO_OP_HANDLER);
- var req = new DefaultHttpRequest(HTTP_1_1, GET, "/user/");
- req.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
- assertFalse(route.match(req));
- }
-
- @Test
- void testMatchByUriWithParams() {
- var route = new Route("/user/:user", GET, null, NO_OP_HANDLER);
- var req = new DefaultHttpRequest(HTTP_1_1, GET, "/user/John");
- assertTrue(route.match(req));
- }
-
- @Test
- void testPatchVerbMatch() {
- var route = new Route("/user", PATCH, null, NO_OP_HANDLER);
- var req = new DefaultHttpRequest(HTTP_1_1, PATCH, "/user");
-
- assertTrue(route.match(req));
- }
-}
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java
index b6ea675f0..ae937f635 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java
@@ -26,7 +26,6 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.Mockito.mock;
import java.nio.file.Path;
import java.util.List;
@@ -46,7 +45,6 @@ import org.apache.ignite.internal.manager.IgniteComponent;
import org.apache.ignite.internal.metastorage.MetaStorageManager;
import org.apache.ignite.internal.metastorage.server.SimpleInMemoryKeyValueStorage;
import org.apache.ignite.internal.raft.Loza;
-import org.apache.ignite.internal.rest.RestComponent;
import org.apache.ignite.internal.testframework.WorkDirectory;
import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
import org.apache.ignite.internal.util.IgniteUtils;
@@ -117,7 +115,6 @@ public class ItDistributedConfigurationPropertiesTest {
vaultManager,
clusterService,
raftManager,
- mock(RestComponent.class),
new ConcurrentMapClusterStateStorage()
);
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
index 2b90ddbbb..6711a3204 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
@@ -22,7 +22,6 @@ import static org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCo
import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willBe;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
-import static org.mockito.Mockito.mock;
import java.io.Serializable;
import java.nio.file.Path;
@@ -37,7 +36,6 @@ import org.apache.ignite.internal.manager.IgniteComponent;
import org.apache.ignite.internal.metastorage.MetaStorageManager;
import org.apache.ignite.internal.metastorage.server.SimpleInMemoryKeyValueStorage;
import org.apache.ignite.internal.raft.Loza;
-import org.apache.ignite.internal.rest.RestComponent;
import org.apache.ignite.internal.testframework.WorkDirectory;
import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
import org.apache.ignite.internal.vault.VaultManager;
@@ -91,7 +89,6 @@ public class ItDistributedConfigurationStorageTest {
vaultManager,
clusterService,
raftManager,
- mock(RestComponent.class),
new ConcurrentMapClusterStateStorage()
);
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
index d2ac1d9ec..3e90e4979 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
@@ -70,7 +70,6 @@ import org.apache.ignite.internal.metastorage.server.persistence.RocksDbKeyValue
import org.apache.ignite.internal.raft.Loza;
import org.apache.ignite.internal.recovery.ConfigurationCatchUpListener;
import org.apache.ignite.internal.recovery.RecoveryCompletionFutureFactory;
-import org.apache.ignite.internal.rest.RestComponent;
import org.apache.ignite.internal.schema.SchemaManager;
import org.apache.ignite.internal.sql.engine.SqlQueryProcessor;
import org.apache.ignite.internal.storage.DataStorageManager;
@@ -231,7 +230,6 @@ public class ItIgniteNodeRestartTest extends IgniteAbstractTest {
vault,
clusterSvc,
raftMgr,
- mock(RestComponent.class),
new RocksDbClusterStateStorage(dir.resolve("cmg"))
);
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItNoThreadsLeftTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItNoThreadsLeftTest.java
index 2951a043d..0274546a9 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItNoThreadsLeftTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItNoThreadsLeftTest.java
@@ -85,7 +85,7 @@ public class ItNoThreadsLeftTest extends IgniteAbstractTest {
stopNode(testInfo);
}
- boolean threadsKilled = waitForCondition(() -> threadsBefore.size() == getCurrentThreads().size(), 3_000);
+ boolean threadsKilled = waitForCondition(() -> getCurrentThreads().size() <= threadsBefore.size(), 3_000);
if (!threadsKilled) {
String leakedThreadNames = getCurrentThreads().stream()
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 df1408b56..9fd9b5f69 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
@@ -35,11 +35,13 @@ import org.apache.ignite.client.handler.ClientHandlerModule;
import org.apache.ignite.compute.IgniteCompute;
import org.apache.ignite.configuration.schemas.compute.ComputeConfiguration;
import org.apache.ignite.configuration.schemas.network.NetworkConfiguration;
+import org.apache.ignite.configuration.schemas.rest.RestConfiguration;
import org.apache.ignite.configuration.schemas.table.TablesConfiguration;
import org.apache.ignite.internal.baseline.BaselineManager;
import org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
import org.apache.ignite.internal.cluster.management.network.messages.CmgMessagesSerializationRegistryInitializer;
import org.apache.ignite.internal.cluster.management.raft.RocksDbClusterStateStorage;
+import org.apache.ignite.internal.cluster.management.rest.ClusterManagementRestFactory;
import org.apache.ignite.internal.components.LongJvmPauseDetector;
import org.apache.ignite.internal.compute.ComputeComponent;
import org.apache.ignite.internal.compute.ComputeComponentImpl;
@@ -50,7 +52,6 @@ import org.apache.ignite.internal.configuration.ConfigurationModule;
import org.apache.ignite.internal.configuration.ConfigurationModules;
import org.apache.ignite.internal.configuration.ConfigurationRegistry;
import org.apache.ignite.internal.configuration.ServiceLoaderModulesProvider;
-import org.apache.ignite.internal.configuration.rest.ConfigurationHttpHandlers;
import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
import org.apache.ignite.internal.configuration.storage.DistributedConfigurationStorage;
import org.apache.ignite.internal.configuration.storage.LocalConfigurationStorage;
@@ -60,6 +61,8 @@ import org.apache.ignite.internal.raft.Loza;
import org.apache.ignite.internal.recovery.ConfigurationCatchUpListener;
import org.apache.ignite.internal.recovery.RecoveryCompletionFutureFactory;
import org.apache.ignite.internal.rest.RestComponent;
+import org.apache.ignite.internal.rest.api.RestFactory;
+import org.apache.ignite.internal.rest.configuration.PresentationsFactory;
import org.apache.ignite.internal.schema.SchemaManager;
import org.apache.ignite.internal.sql.api.IgniteSqlImpl;
import org.apache.ignite.internal.sql.engine.QueryProcessor;
@@ -230,8 +233,6 @@ public class IgniteImpl implements Ignite {
nettyBootstrapFactory = new NettyBootstrapFactory(networkConfiguration, clusterLocalConfiguration.getName());
- restComponent = new RestComponent(nodeCfgMgr, nettyBootstrapFactory);
-
clusterSvc = new ScaleCubeClusterServiceFactory().createClusterService(
clusterLocalConfiguration,
networkConfiguration,
@@ -252,7 +253,6 @@ public class IgniteImpl implements Ignite {
vaultMgr,
clusterSvc,
raftMgr,
- restComponent,
new RocksDbClusterStateStorage(workDir.resolve(CMG_DB_PATH))
);
@@ -274,6 +274,11 @@ public class IgniteImpl implements Ignite {
modules.distributed().polymorphicSchemaExtensions()
);
+ RestFactory factory = new PresentationsFactory(nodeCfgMgr, clusterCfgMgr);
+ RestFactory clusterManagementRestFactory = new ClusterManagementRestFactory(clusterSvc);
+ RestConfiguration restConfiguration = nodeCfgMgr.configurationRegistry().getConfiguration(RestConfiguration.KEY);
+ restComponent = new RestComponent(List.of(factory, clusterManagementRestFactory), restConfiguration);
+
baselineMgr = new BaselineManager(
clusterCfgMgr,
metaStorageMgr,
@@ -334,8 +339,6 @@ public class IgniteImpl implements Ignite {
sql
);
- new ConfigurationHttpHandlers(nodeCfgMgr, clusterCfgMgr).registerHandlers(restComponent);
-
longJvmPauseDetector = new LongJvmPauseDetector(name);
}
@@ -586,7 +589,7 @@ public class IgniteImpl implements Ignite {
*/
// TODO: should be encapsulated in local properties, see https://issues.apache.org/jira/browse/IGNITE-15131
public NetworkAddress restAddress() {
- return NetworkAddress.from(restComponent.localAddress());
+ return new NetworkAddress(restComponent.host(), restComponent.port());
}
/**
@@ -603,7 +606,7 @@ public class IgniteImpl implements Ignite {
* Initializes the cluster that this node is present in.
*
* @param metaStorageNodeNames names of nodes that will host the Meta Storage.
- * @param cmgNodeNames names of nodes that will host the CMG.
+ * @param cmgNodeNames names of nodes that will host the CMG.
* @param clusterName Human-readable name of a cluster.
* @throws NodeStoppingException If node stopping intention was detected.
*/
diff --git a/parent/pom.xml b/parent/pom.xml
index 287633cae..76bb96b5e 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -53,6 +53,7 @@
<asm.framework.version>9.0</asm.framework.version>
<compile.testing.library.version>0.19</compile.testing.library.version>
<jackson.version>2.13.1</jackson.version>
+ <jakarta.annotations.version>2.0.0</jakarta.annotations.version>
<jansi.version>1.18</jansi.version>
<netty.version>4.1.70.Final</netty.version>
<javapoet.version>1.13.0</javapoet.version>
@@ -60,12 +61,13 @@
<jetbrains.annotations.version>20.1.0</jetbrains.annotations.version>
<jmh.framework.version>1.35</jmh.framework.version>
<junit.version>5.8.1</junit.version>
- <micronaut.version>2.1.2</micronaut.version>
- <micronaut.test.junit5.version>2.3.1</micronaut.test.junit5.version>
+ <micronaut.version>3.4.1</micronaut.version>
+ <micronaut.test.junit5.version>3.3.0</micronaut.test.junit5.version>
<mockito.version>4.3.1</mockito.version>
<picocli.version>4.5.2</picocli.version>
<slf4j.version>1.7.32</slf4j.version>
<spoon.framework.version>8.4.0-beta-18</spoon.framework.version>
+ <swagger.annotations.version>2.1.12</swagger.annotations.version>
<typesafe.version>1.4.1</typesafe.version>
<hamcrest.version>2.2</hamcrest.version>
<hamcrest.optional.version>2.0.0</hamcrest.optional.version>
@@ -113,7 +115,7 @@
<maven.shade.plugin.version>3.2.4</maven.shade.plugin.version>
<maven.source.plugin.version>3.2.1</maven.source.plugin.version>
<maven.surefire.plugin.version>3.0.0-M5</maven.surefire.plugin.version>
-
+ <maven.micronaut.openapi.plugin.version>3.2.0</maven.micronaut.openapi.plugin.version>
<!-- We leave it empty so that, when jacoco does not run, the unreplaced @{argLine} does not impede the build. -->
<additional.test.argLine/>
<common.test.argLine>
@@ -479,6 +481,55 @@
<version>${micronaut.version}</version>
</dependency>
+ <dependency>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-inject</artifactId>
+ <version>${micronaut.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-http</artifactId>
+ <version>${micronaut.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-http-server</artifactId>
+ <version>${micronaut.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-http-server-netty</artifactId>
+ <version>${micronaut.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>io.micronaut.openapi</groupId>
+ <artifactId>micronaut-openapi</artifactId>
+ <version>${maven.micronaut.openapi.plugin.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>jakarta.annotation</groupId>
+ <artifactId>jakarta.annotation-api</artifactId>
+ <scope>compile</scope>
+ <version>${jakarta.annotations.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>jakarta.inject</groupId>
+ <artifactId>jakarta.inject-api</artifactId>
+ <version>${jakarta.annotations.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>io.swagger.core.v3</groupId>
+ <artifactId>swagger-annotations</artifactId>
+ <version>${swagger.annotations.version}</version>
+ </dependency>
+
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
@@ -613,6 +664,13 @@
<version>${micronaut.test.junit5.version}</version>
</dependency>
+ <dependency>
+ <groupId>io.micronaut</groupId>
+ <artifactId>micronaut-http-client</artifactId>
+ <version>${micronaut.version}</version>
+ <scope>test</scope>
+ </dependency>
+
<dependency>
<groupId>com.google.testing.compile</groupId>
<artifactId>compile-testing</artifactId>
@@ -1338,6 +1396,7 @@
<exclude>docs/_plugins/asciidoctor-extensions.rb</exclude> <!-- MIT license -->
<exclude>docs/_sass/rouge-base16-solarized.scss</exclude> <!-- MIT license. -->
<exclude>**/*.json</exclude> <!-- Files in JSON format -->
+ <exclude>modules/rest/openapi/openapi.yaml</exclude> <!-- Autogenerated Open API spec -->
<exclude>**/*.drawio</exclude> <!-- Draw.IO diagrams -->
<exclude>modules/cli/src/**/resources//builtin_modules.conf</exclude> <!-- CLI configuration files -->
<exclude>**/*.iml</exclude> <!-- IntelliJ IDEA project files -->