This is an automated email from the ASF dual-hosted git repository.
apkhmv 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 e104b42b6e IGNITE-18730 REST API for Deployment Code API (#1733)
e104b42b6e is described below
commit e104b42b6eeb0c44fa01c869bb15944ee741b434
Author: Mikhail <[email protected]>
AuthorDate: Mon Mar 6 16:19:17 2023 +0300
IGNITE-18730 REST API for Deployment Code API (#1733)
---
modules/api/build.gradle | 9 +
.../org/apache/ignite/deployment/UnitStatus.java | 4 +-
.../ignite/deployment/version/UnitVersion.java | 12 +-
.../apache/ignite/deployment/version/Version.java | 5 +
.../deployment/version/VersionParseException.java | 17 +-
.../testframework}/IntegrationTestBase.java | 136 +----------
modules/cli/build.gradle | 5 +-
...onTestBase.java => CliIntegrationTestBase.java} | 167 +------------
.../call/CallInitializedIntegrationTestBase.java | 4 +-
...liCommandTestNotInitializedIntegrationBase.java | 4 +-
.../internal/deployunit/DeploymentManagerImpl.java | 1 +
modules/rest-api/openapi/openapi.yaml | 224 +++++++++++++++++
.../rest/api/deployment/DeploymentCodeApi.java | 193 +++++++++++++++
.../rest/api/deployment/UnitStatusDto.java | 90 +++++++
.../ignite/internal/rest/constants/HttpCode.java | 2 +
.../ignite/internal/rest/constants/MediaType.java | 4 +
.../exception/ClusterNotInitializedException.java | 3 -
modules/rest/build.gradle | 5 +-
.../DeploymentManagementControllerTest.java | 270 +++++++++++++++++++++
.../deployment/CodeDeploymentRestFactory.java} | 40 ++-
.../deployment/DeploymentManagementController.java | 99 ++++++++
...DeploymentUnitAlreadyExistExceptionHandler.java | 44 ++++
.../DeploymentUnitNotFoundExceptionHandler.java | 45 ++++
.../handler/VersionParseExceptionHandler.java | 44 ++++
.../org/apache/ignite/internal/app/IgniteImpl.java | 7 +-
25 files changed, 1091 insertions(+), 343 deletions(-)
diff --git a/modules/api/build.gradle b/modules/api/build.gradle
index 2fcef62ce9..0cca13ad05 100644
--- a/modules/api/build.gradle
+++ b/modules/api/build.gradle
@@ -18,6 +18,7 @@
apply from: "$rootDir/buildscripts/java-core.gradle"
apply from: "$rootDir/buildscripts/publishing.gradle"
apply from: "$rootDir/buildscripts/java-junit5.gradle"
+apply from: "$rootDir/buildscripts/java-test-fixtures.gradle"
dependencies {
@@ -30,6 +31,14 @@ dependencies {
testImplementation libs.hamcrest.optional
testImplementation libs.archunit.core
testImplementation libs.archunit.junit5
+
+ testFixturesAnnotationProcessor
project(":ignite-configuration-annotation-processor")
+ testFixturesAnnotationProcessor libs.micronaut.inject.annotation.processor
+ testFixturesImplementation project(":ignite-core")
+ testFixturesImplementation testFixtures(project(":ignite-core"))
+ testFixturesImplementation project(":ignite-configuration")
+ testFixturesImplementation libs.hamcrest.core
+ testFixturesImplementation libs.micronaut.junit5
}
description = 'ignite-api'
diff --git
a/modules/api/src/main/java/org/apache/ignite/deployment/UnitStatus.java
b/modules/api/src/main/java/org/apache/ignite/deployment/UnitStatus.java
index 771a0fd8db..a9ef4f5a4c 100644
--- a/modules/api/src/main/java/org/apache/ignite/deployment/UnitStatus.java
+++ b/modules/api/src/main/java/org/apache/ignite/deployment/UnitStatus.java
@@ -18,11 +18,11 @@
package org.apache.ignite.deployment;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.TreeMap;
import org.apache.ignite.deployment.version.Version;
import org.apache.ignite.internal.tostring.S;
@@ -123,7 +123,7 @@ public class UnitStatus {
public static class UnitStatusBuilder {
private final String id;
- private final Map<Version, List<String>> versionToConsistentIds = new
HashMap<>();
+ private final Map<Version, List<String>> versionToConsistentIds = new
TreeMap<>(Version::compareTo);
/**
* Constructor.
diff --git
a/modules/api/src/main/java/org/apache/ignite/deployment/version/UnitVersion.java
b/modules/api/src/main/java/org/apache/ignite/deployment/version/UnitVersion.java
index 02b2df683d..08bfd8d9e4 100644
---
a/modules/api/src/main/java/org/apache/ignite/deployment/version/UnitVersion.java
+++
b/modules/api/src/main/java/org/apache/ignite/deployment/version/UnitVersion.java
@@ -49,16 +49,16 @@ public class UnitVersion implements Version {
/**
* Parse string representation of version to {@link UnitVersion} if
possible.
*
- * @param s String representation of version.
+ * @param rawVersion String representation of version.
* @return Instance of {@link UnitVersion}.
* @throws VersionParseException in case when string is not required
{@link UnitVersion} format.
*/
- public static UnitVersion parse(String s) {
- Objects.requireNonNull(s);
+ public static UnitVersion parse(String rawVersion) {
+ Objects.requireNonNull(rawVersion);
try {
- String[] split = s.split("\\.", -1);
+ String[] split = rawVersion.split("\\.", -1);
if (split.length > 3 || split.length == 0) {
- throw new VersionParseException("Invalid version format");
+ throw new VersionParseException(rawVersion, "Invalid version
format");
}
short major = Short.parseShort(split[0]);
@@ -67,7 +67,7 @@ public class UnitVersion implements Version {
return new UnitVersion(major, minor, patch);
} catch (NumberFormatException e) {
- throw new VersionParseException(e);
+ throw new VersionParseException(rawVersion, e);
}
}
diff --git
a/modules/api/src/main/java/org/apache/ignite/deployment/version/Version.java
b/modules/api/src/main/java/org/apache/ignite/deployment/version/Version.java
index f9a4e29847..9bb09073a4 100644
---
a/modules/api/src/main/java/org/apache/ignite/deployment/version/Version.java
+++
b/modules/api/src/main/java/org/apache/ignite/deployment/version/Version.java
@@ -47,6 +47,11 @@ public interface Version extends Comparable<Version> {
}
return 1;
}
+
+ @Override
+ public String toString() {
+ return render();
+ }
};
/**
diff --git
a/modules/api/src/main/java/org/apache/ignite/deployment/version/VersionParseException.java
b/modules/api/src/main/java/org/apache/ignite/deployment/version/VersionParseException.java
index 717035a9c0..9ab83eaddd 100644
---
a/modules/api/src/main/java/org/apache/ignite/deployment/version/VersionParseException.java
+++
b/modules/api/src/main/java/org/apache/ignite/deployment/version/VersionParseException.java
@@ -21,20 +21,16 @@ package org.apache.ignite.deployment.version;
* Throws when {@link Version} of deployment unit not parsable.
*/
public class VersionParseException extends RuntimeException {
- /**
- * Constructor.
- */
- public VersionParseException() {
-
- }
+ private final String rawVersion;
/**
* Constructor.
*
* @param cause Cause error.
*/
- public VersionParseException(Throwable cause) {
+ public VersionParseException(String rawVersion, Throwable cause) {
super(cause);
+ this.rawVersion = rawVersion;
}
/**
@@ -42,7 +38,12 @@ public class VersionParseException extends RuntimeException {
*
* @param message Error message.
*/
- public VersionParseException(String message) {
+ public VersionParseException(String rawVersion, String message) {
super(message);
+ this.rawVersion = rawVersion;
+ }
+
+ public String getRawVersion() {
+ return rawVersion;
}
}
diff --git
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/IntegrationTestBase.java
b/modules/api/src/testFixtures/java/org/apache/ignite/internal/testframework/IntegrationTestBase.java
similarity index 57%
copy from
modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/IntegrationTestBase.java
copy to
modules/api/src/testFixtures/java/org/apache/ignite/internal/testframework/IntegrationTestBase.java
index 00c1997818..ffd8c9b06a 100644
---
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/IntegrationTestBase.java
+++
b/modules/api/src/testFixtures/java/org/apache/ignite/internal/testframework/IntegrationTestBase.java
@@ -15,46 +15,28 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.cli;
+package org.apache.ignite.internal.testframework;
import static java.util.stream.Collectors.toList;
-import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
import static
org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
import static org.hamcrest.MatcherAssert.assertThat;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
-import java.io.PrintWriter;
-import java.io.Writer;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
import java.util.stream.IntStream;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgnitionManager;
import org.apache.ignite.InitParameters;
-import org.apache.ignite.internal.app.IgniteImpl;
import org.apache.ignite.internal.logger.IgniteLogger;
import org.apache.ignite.internal.logger.Loggers;
-import org.apache.ignite.internal.sql.engine.AsyncCursor;
-import org.apache.ignite.internal.sql.engine.AsyncCursor.BatchedResult;
-import org.apache.ignite.internal.sql.engine.QueryContext;
-import org.apache.ignite.internal.sql.engine.QueryProperty;
-import org.apache.ignite.internal.sql.engine.property.PropertiesHolder;
-import org.apache.ignite.internal.sql.engine.session.SessionId;
-import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
-import org.apache.ignite.internal.testframework.WorkDirectory;
-import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
import org.apache.ignite.internal.util.IgniteUtils;
import org.apache.ignite.lang.IgniteStringFormatter;
-import org.apache.ignite.table.Table;
-import org.apache.ignite.tx.Transaction;
-import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestInstance;
@@ -63,9 +45,9 @@ import org.junit.jupiter.api.extension.ExtendWith;
/**
* Integration test base. Setups ignite cluster per test class and provides
useful fixtures and assertions.
*/
-@ExtendWith(WorkDirectoryExtension.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@MicronautTest(rebuildContext = true)
+@ExtendWith(WorkDirectoryExtension.class)
public class IntegrationTestBase extends BaseIgniteAbstractTest {
/** Correct ignite cluster url. */
protected static final String NODE_URL = "http://localhost:10300";
@@ -79,10 +61,6 @@ public class IntegrationTestBase extends
BaseIgniteAbstractTest {
/** Node name to its configuration map.*/
protected static final Map<String, String> NODE_CONFIGS = new HashMap<>();
- /** Timeout should be big enough to prevent premature session expiration.
*/
-
- private static final long SESSION_IDLE_TIMEOUT =
TimeUnit.SECONDS.toMillis(60);
-
private static final int DEFAULT_NODES_COUNT = 3;
private static final IgniteLogger LOG =
Loggers.forClass(IntegrationTestBase.class);
@@ -102,112 +80,12 @@ public class IntegrationTestBase extends
BaseIgniteAbstractTest {
+ " }\n"
+ "}";
- /** Template for node bootstrap config with Scalecube and Logical Topology
settings for fast failure detection. */
- protected static final String
FAST_FAILURE_DETECTION_NODE_BOOTSTRAP_CFG_TEMPLATE = "{\n"
- + " network: {\n"
- + " port: {},\n"
- + " nodeFinder: {\n"
- + " netClusterNodes: [ {} ]\n"
- + " },\n"
- + " membership: {\n"
- + " membershipSyncInterval: 1000,\n"
- + " failurePingInterval: 500,\n"
- + " scaleCube: {\n"
- + " membershipSuspicionMultiplier: 1,\n"
- + " failurePingRequestMembers: 1,\n"
- + " gossipInterval: 10\n"
- + " },\n"
- + " }\n"
- + " },"
- + " cluster.failoverTimeout: 100\n"
- + "}";
-
/** Futures that are going to be completed when all nodes are started and
the cluster is initialized. */
private static List<CompletableFuture<Ignite>> futures = new ArrayList<>();
/** Work directory. */
@WorkDirectory
- private static Path WORK_DIR;
-
- protected static void createAndPopulateTable() {
- sql("CREATE TABLE person ( id INT PRIMARY KEY, name VARCHAR, salary
DOUBLE)");
-
- int idx = 0;
-
- for (Object[] args : new Object[][]{
- {idx++, "Igor", 10d},
- {idx++, null, 15d},
- {idx++, "Ilya", 15d},
- {idx++, "Roma", 10d},
- {idx, "Roma", 10d}
- }) {
- sql("INSERT INTO person(id, name, salary) VALUES (?, ?, ?)", args);
- }
- }
-
- protected static List<List<Object>> sql(String sql, Object... args) {
- return sql(null, sql, args);
- }
-
- protected static List<List<Object>> sql(@Nullable Transaction tx, String
sql, Object... args) {
- var queryEngine = ((IgniteImpl) CLUSTER_NODES.get(0)).queryEngine();
-
- SessionId sessionId = queryEngine.createSession(SESSION_IDLE_TIMEOUT,
PropertiesHolder.fromMap(
- Map.of(QueryProperty.DEFAULT_SCHEMA, "PUBLIC")
- ));
-
- try {
- var context = tx != null ? QueryContext.of(tx) : QueryContext.of();
-
- return getAllFromCursor(
- await(queryEngine.querySingleAsync(sessionId, context,
sql, args))
- );
- } finally {
- queryEngine.closeSession(sessionId);
- }
- }
-
- private static <T> List<T> getAllFromCursor(AsyncCursor<T> cur) {
- List<T> res = new ArrayList<>();
- int batchSize = 256;
-
- var consumer = new Consumer<BatchedResult<T>>() {
- @Override
- public void accept(BatchedResult<T> br) {
- res.addAll(br.items());
-
- if (br.hasMore()) {
- cur.requestNextAsync(batchSize).thenAccept(this);
- }
- }
- };
-
- await(cur.requestNextAsync(batchSize).thenAccept(consumer));
- await(cur.closeAsync());
-
- return res;
- }
-
- protected static PrintWriter output(List<Character> buffer) {
- return new PrintWriter(new Writer() {
- @Override
- public void write(char[] cbuf, int off, int len) {
- for (int i = off; i < off + len; i++) {
- buffer.add(cbuf[i]);
- }
- }
-
- @Override
- public void flush() {
-
- }
-
- @Override
- public void close() {
-
- }
- });
- }
+ protected static Path WORK_DIR;
/**
* Before all.
@@ -289,18 +167,10 @@ public class IntegrationTestBase extends
BaseIgniteAbstractTest {
CLUSTER_NODE_NAMES.add(nodeName);
}
- /** Drops all visible tables. */
- protected void dropAllTables() {
- for (Table t : CLUSTER_NODES.get(0).tables().tables()) {
- sql("DROP TABLE " + t.name());
- }
- }
-
/**
* Invokes before the test will start.
*
* @param testInfo Test information object.
- * @throws Exception If failed.
*/
public void setUp(TestInfo testInfo) throws Exception {
setupBase(testInfo, WORK_DIR);
diff --git a/modules/cli/build.gradle b/modules/cli/build.gradle
index 59d5ee5d85..04fd0784f1 100644
--- a/modules/cli/build.gradle
+++ b/modules/cli/build.gradle
@@ -95,8 +95,9 @@ dependencies {
integrationTestImplementation project(':ignite-runner')
integrationTestImplementation project(':ignite-schema')
integrationTestImplementation project(':ignite-sql-engine')
- integrationTestImplementation(testFixtures(project(":ignite-core")))
- integrationTestImplementation(testFixtures(project(":ignite-schema")))
+ integrationTestImplementation testFixtures(project(":ignite-core"))
+ integrationTestImplementation testFixtures(project(":ignite-schema"))
+ integrationTestImplementation testFixtures(project(":ignite-api"))
integrationTestImplementation libs.jetbrains.annotations
integrationTestImplementation libs.micronaut.picocli
integrationTestImplementation libs.mock.server.netty
diff --git
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/IntegrationTestBase.java
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/CliIntegrationTestBase.java
similarity index 50%
rename from
modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/IntegrationTestBase.java
rename to
modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/CliIntegrationTestBase.java
index 00c1997818..0db0d1e8e6 100644
---
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/IntegrationTestBase.java
+++
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/CliIntegrationTestBase.java
@@ -17,46 +17,28 @@
package org.apache.ignite.internal.cli;
-import static java.util.stream.Collectors.toList;
import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
-import static
org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
-import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
-import static org.hamcrest.MatcherAssert.assertThat;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import java.io.PrintWriter;
import java.io.Writer;
-import java.nio.file.Path;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
-import java.util.stream.IntStream;
-import org.apache.ignite.Ignite;
-import org.apache.ignite.IgnitionManager;
-import org.apache.ignite.InitParameters;
import org.apache.ignite.internal.app.IgniteImpl;
-import org.apache.ignite.internal.logger.IgniteLogger;
-import org.apache.ignite.internal.logger.Loggers;
import org.apache.ignite.internal.sql.engine.AsyncCursor;
import org.apache.ignite.internal.sql.engine.AsyncCursor.BatchedResult;
import org.apache.ignite.internal.sql.engine.QueryContext;
import org.apache.ignite.internal.sql.engine.QueryProperty;
import org.apache.ignite.internal.sql.engine.property.PropertiesHolder;
import org.apache.ignite.internal.sql.engine.session.SessionId;
-import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
-import org.apache.ignite.internal.testframework.WorkDirectory;
+import org.apache.ignite.internal.testframework.IntegrationTestBase;
import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
-import org.apache.ignite.internal.util.IgniteUtils;
-import org.apache.ignite.lang.IgniteStringFormatter;
import org.apache.ignite.table.Table;
import org.apache.ignite.tx.Transaction;
import org.jetbrains.annotations.Nullable;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -66,43 +48,16 @@ import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(WorkDirectoryExtension.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@MicronautTest(rebuildContext = true)
-public class IntegrationTestBase extends BaseIgniteAbstractTest {
- /** Correct ignite cluster url. */
- protected static final String NODE_URL = "http://localhost:10300";
-
- /** Cluster nodes. */
- protected static final List<Ignite> CLUSTER_NODES = new ArrayList<>();
-
- /** Cluster node names. */
- protected static final List<String> CLUSTER_NODE_NAMES = new ArrayList<>();
-
- /** Node name to its configuration map.*/
- protected static final Map<String, String> NODE_CONFIGS = new HashMap<>();
-
- /** Timeout should be big enough to prevent premature session expiration.
*/
-
+public class CliIntegrationTestBase extends IntegrationTestBase {
+ /**
+ * Timeout should be big enough to prevent premature session expiration.
+ */
private static final long SESSION_IDLE_TIMEOUT =
TimeUnit.SECONDS.toMillis(60);
- private static final int DEFAULT_NODES_COUNT = 3;
-
- private static final IgniteLogger LOG =
Loggers.forClass(IntegrationTestBase.class);
-
- /** Base port number. */
-
- private static final int BASE_PORT = 3344;
-
- /** Nodes bootstrap configuration pattern. */
- private static final String NODE_BOOTSTRAP_CFG = "{\n"
- + " network: {\n"
- + " port:{},\n"
- + " portRange: 5,\n"
- + " nodeFinder:{\n"
- + " netClusterNodes: [ {} ]\n"
- + " }\n"
- + " }\n"
- + "}";
- /** Template for node bootstrap config with Scalecube and Logical Topology
settings for fast failure detection. */
+ /**
+ * Template for node bootstrap config with Scalecube and Logical Topology
settings for fast failure detection.
+ */
protected static final String
FAST_FAILURE_DETECTION_NODE_BOOTSTRAP_CFG_TEMPLATE = "{\n"
+ " network: {\n"
+ " port: {},\n"
@@ -122,12 +77,6 @@ public class IntegrationTestBase extends
BaseIgniteAbstractTest {
+ " cluster.failoverTimeout: 100\n"
+ "}";
- /** Futures that are going to be completed when all nodes are started and
the cluster is initialized. */
- private static List<CompletableFuture<Ignite>> futures = new ArrayList<>();
- /** Work directory. */
-
- @WorkDirectory
- private static Path WORK_DIR;
protected static void createAndPopulateTable() {
sql("CREATE TABLE person ( id INT PRIMARY KEY, name VARCHAR, salary
DOUBLE)");
@@ -209,111 +158,11 @@ public class IntegrationTestBase extends
BaseIgniteAbstractTest {
});
}
- /**
- * Before all.
- *
- * @param testInfo Test information object.
- */
- protected void startNodes(TestInfo testInfo) {
- String connectNodeAddr = "\"localhost:" + BASE_PORT + '\"';
-
- futures = IntStream.range(0, nodes())
- .mapToObj(i -> {
- String nodeName = testNodeName(testInfo, i);
- CLUSTER_NODE_NAMES.add(nodeName);
-
- String config =
IgniteStringFormatter.format(nodeBootstrapConfigTemplate(), BASE_PORT + i,
connectNodeAddr);
-
- NODE_CONFIGS.put(nodeName, config);
-
- return IgnitionManager.start(nodeName, config,
WORK_DIR.resolve(nodeName));
- })
- .collect(toList());
- }
-
- protected String nodeBootstrapConfigTemplate() {
- return NODE_BOOTSTRAP_CFG;
- }
-
- protected void initializeCluster(String metaStorageNodeName) {
- InitParameters initParameters = InitParameters.builder()
- .destinationNodeName(metaStorageNodeName)
- .metaStorageNodeNames(List.of(metaStorageNodeName))
- .clusterName("cluster")
- .build();
-
- IgnitionManager.init(initParameters);
-
- for (CompletableFuture<Ignite> future : futures) {
- assertThat(future, willCompleteSuccessfully());
-
- CLUSTER_NODES.add(future.join());
- }
- }
-
- /**
- * Get a count of nodes in the Ignite cluster.
- *
- * @return Count of nodes.
- */
- protected int nodes() {
- return DEFAULT_NODES_COUNT;
- }
-
- /**
- * After all.
- */
- protected void stopNodes(TestInfo testInfo) throws Exception {
- LOG.info("Start tearDown()");
-
- CLUSTER_NODES.clear();
- CLUSTER_NODE_NAMES.clear();
-
- List<AutoCloseable> closeables = IntStream.range(0, nodes())
- .mapToObj(i -> testNodeName(testInfo, i))
- .map(nodeName -> (AutoCloseable) () ->
IgnitionManager.stop(nodeName))
- .collect(toList());
-
- IgniteUtils.closeAll(closeables);
-
- LOG.info("End tearDown()");
- }
-
- protected void stopNode(String nodeName) {
- IgnitionManager.stop(nodeName);
- CLUSTER_NODE_NAMES.remove(nodeName);
- }
-
- protected void startNode(String nodeName) {
- IgnitionManager.start(nodeName, NODE_CONFIGS.get(nodeName),
WORK_DIR.resolve(nodeName));
- CLUSTER_NODE_NAMES.add(nodeName);
- }
-
/** Drops all visible tables. */
protected void dropAllTables() {
for (Table t : CLUSTER_NODES.get(0).tables().tables()) {
sql("DROP TABLE " + t.name());
}
}
-
- /**
- * Invokes before the test will start.
- *
- * @param testInfo Test information object.
- * @throws Exception If failed.
- */
- public void setUp(TestInfo testInfo) throws Exception {
- setupBase(testInfo, WORK_DIR);
- }
-
- /**
- * Invokes after the test has finished.
- *
- * @param testInfo Test information object.
- */
- @AfterEach
- public void tearDown(TestInfo testInfo) {
- tearDownBase(testInfo);
- }
}
diff --git
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/call/CallInitializedIntegrationTestBase.java
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/call/CallInitializedIntegrationTestBase.java
index d3396dcfe2..dd37705261 100644
---
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/call/CallInitializedIntegrationTestBase.java
+++
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/call/CallInitializedIntegrationTestBase.java
@@ -19,7 +19,7 @@ package org.apache.ignite.internal.cli.call;
import static
org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
-import org.apache.ignite.internal.cli.IntegrationTestBase;
+import org.apache.ignite.internal.cli.CliIntegrationTestBase;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestInfo;
@@ -27,7 +27,7 @@ import org.junit.jupiter.api.TestInfo;
/**
* Base class for call integration tests that needs initialized ignite
cluster. Contains common methods and useful assertions.
*/
-public class CallInitializedIntegrationTestBase extends IntegrationTestBase {
+public class CallInitializedIntegrationTestBase extends CliIntegrationTestBase
{
@BeforeAll
void beforeAll(TestInfo testInfo) {
startNodes(testInfo);
diff --git
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/CliCommandTestNotInitializedIntegrationBase.java
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/CliCommandTestNotInitializedIntegrationBase.java
index deca8458a8..7b6d7c14e0 100644
---
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/CliCommandTestNotInitializedIntegrationBase.java
+++
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/CliCommandTestNotInitializedIntegrationBase.java
@@ -24,7 +24,7 @@ import io.micronaut.context.ApplicationContext;
import jakarta.inject.Inject;
import java.io.PrintWriter;
import java.io.StringWriter;
-import org.apache.ignite.internal.cli.IntegrationTestBase;
+import org.apache.ignite.internal.cli.CliIntegrationTestBase;
import
org.apache.ignite.internal.cli.commands.cliconfig.TestConfigManagerHelper;
import
org.apache.ignite.internal.cli.commands.cliconfig.TestConfigManagerProvider;
import org.apache.ignite.internal.cli.commands.node.NodeNameOrUrl;
@@ -46,7 +46,7 @@ import picocli.CommandLine;
* Integration test base for cli commands. Setup commands, ignite cluster, and
provides useful fixtures and assertions. Note: ignite cluster
* won't be initialized. If you want to use initialized cluster use {@link
CliCommandTestInitializedIntegrationBase}.
*/
-public class CliCommandTestNotInitializedIntegrationBase extends
IntegrationTestBase {
+public class CliCommandTestNotInitializedIntegrationBase extends
CliIntegrationTestBase {
/** Correct ignite jdbc url. */
protected static final String JDBC_URL =
"jdbc:ignite:thin://127.0.0.1:10800";
diff --git
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/DeploymentManagerImpl.java
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/DeploymentManagerImpl.java
index 2c2869a503..683f4f6878 100644
---
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/DeploymentManagerImpl.java
+++
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/DeploymentManagerImpl.java
@@ -300,6 +300,7 @@ public class DeploymentManagerImpl implements
IgniteDeployment, IgniteComponent
@Override
public void onComplete() {
+ Collections.sort(list);
result.complete(list);
}
});
diff --git a/modules/rest-api/openapi/openapi.yaml
b/modules/rest-api/openapi/openapi.yaml
index cb57668eea..e7b2305b49 100644
--- a/modules/rest-api/openapi/openapi.yaml
+++ b/modules/rest-api/openapi/openapi.yaml
@@ -318,6 +318,215 @@ paths:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
+ /management/v1/deployment/units:
+ get:
+ tags:
+ - deployment
+ description: All units statutes.
+ operationId: units
+ parameters: []
+ responses:
+ "200":
+ description: All statutes returned successful.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/UnitStatus'
+ "500":
+ description: Internal error.
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/Problem'
+ post:
+ tags:
+ - deployment
+ description: Deploy provided unit to the cluster.
+ operationId: deployUnit
+ parameters: []
+ requestBody:
+ content:
+ multipart/form-data:
+ schema:
+ required:
+ - unitContent
+ - unitId
+ type: object
+ properties:
+ unitId:
+ required:
+ - "true"
+ type: string
+ unitVersion:
+ type: string
+ unitContent:
+ required:
+ - "true"
+ type: string
+ format: binary
+ required: true
+ responses:
+ "200":
+ description: Unit deployed successfully.
+ content:
+ application/json:
+ schema:
+ type: boolean
+ "409":
+ description: Unit with same identifier and version already deployed.
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/Problem'
+ "500":
+ description: Internal error.
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/Problem'
+ /management/v1/deployment/units/{unitId}:
+ delete:
+ tags:
+ - deployment
+ description: Undeploy latest unit with provided unitId.
+ operationId: undeployLatestUnit
+ parameters:
+ - name: unitId
+ in: path
+ required: true
+ schema:
+ required:
+ - "true"
+ type: string
+ responses:
+ "200":
+ description: Unit undeployed successfully.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Void'
+ "404":
+ description: Unit with provided identifier and version doesn't exist.
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/Problem'
+ "500":
+ description: Internal error.
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/Problem'
+ /management/v1/deployment/units/{unitId}/status:
+ get:
+ tags:
+ - deployment
+ description: Status of unit with provided identifier.
+ operationId: status
+ parameters:
+ - name: unitId
+ in: path
+ required: true
+ schema:
+ required:
+ - "true"
+ type: string
+ responses:
+ "200":
+ description: Status returned successful.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/UnitStatus'
+ "404":
+ description: Unit with provided identifier doesn't exist.
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/Problem'
+ "500":
+ description: Internal error.
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/Problem'
+ /management/v1/deployment/units/{unitId}/versions:
+ get:
+ tags:
+ - deployment
+ description: All versions of unit with provided unit identifier.
+ operationId: versions
+ parameters:
+ - name: unitId
+ in: path
+ required: true
+ schema:
+ required:
+ - "true"
+ type: string
+ responses:
+ "200":
+ description: Versions returned successful.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
+ "404":
+ description: Unit with provided identifier doesn't exist.
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/Problem'
+ "500":
+ description: Internal error.
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/Problem'
+ /management/v1/deployment/units/{unitId}/{unitVersion}:
+ delete:
+ tags:
+ - deployment
+ description: Undeploy unit with provided unitId and unitVersion.
+ operationId: undeployUnit
+ parameters:
+ - name: unitId
+ in: path
+ required: true
+ schema:
+ required:
+ - "true"
+ type: string
+ - name: unitVersion
+ in: path
+ required: true
+ schema:
+ required:
+ - "true"
+ type: string
+ responses:
+ "200":
+ description: Unit undeployed successfully.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Void'
+ "404":
+ description: Unit with provided identifier and version doesn't exist.
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/Problem'
+ "500":
+ description: Internal error.
+ content:
+ application/problem+json:
+ schema:
+ $ref: '#/components/schemas/Problem'
/management/v1/metric/node:
get:
tags:
@@ -635,5 +844,20 @@ components:
- STARTING
- STARTED
- STOPPING
+ UnitStatus:
+ required:
+ - id
+ - versionToConsistentIds
+ type: object
+ properties:
+ id:
+ type: string
+ versionToConsistentIds:
+ type: object
+ additionalProperties:
+ type: array
+ items:
+ type: string
+ description: Unit status.
Void:
type: object
diff --git
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/deployment/DeploymentCodeApi.java
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/deployment/DeploymentCodeApi.java
new file mode 100644
index 0000000000..67bdd5d3bb
--- /dev/null
+++
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/deployment/DeploymentCodeApi.java
@@ -0,0 +1,193 @@
+/*
+ * 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.deployment;
+
+import static
org.apache.ignite.internal.rest.constants.MediaType.APPLICATION_JSON;
+import static org.apache.ignite.internal.rest.constants.MediaType.PROBLEM_JSON;
+
+import io.micronaut.http.annotation.Consumes;
+import io.micronaut.http.annotation.Controller;
+import io.micronaut.http.annotation.Delete;
+import io.micronaut.http.annotation.Get;
+import io.micronaut.http.annotation.PathVariable;
+import io.micronaut.http.annotation.Post;
+import io.micronaut.http.annotation.Produces;
+import io.micronaut.http.multipart.CompletedFileUpload;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+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 java.util.Collection;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.internal.rest.api.Problem;
+import org.apache.ignite.internal.rest.constants.MediaType;
+
+/**
+ * REST endpoint allows to deployment code service.
+ */
+@Controller("/management/v1/deployment/")
+@Tag(name = "deployment")
+public interface DeploymentCodeApi {
+
+ /**
+ * Deploy unit REST method.
+ */
+ @Operation(operationId = "deployUnit", description = "Deploy provided unit
to the cluster.")
+ @ApiResponse(responseCode = "200", description = "Unit deployed
successfully.",
+ content = @Content(mediaType = APPLICATION_JSON, schema =
@Schema(type = "boolean"))
+ )
+ @ApiResponse(responseCode = "409", description = "Unit with same
identifier and version already deployed.",
+ content = @Content(mediaType = PROBLEM_JSON, schema =
@Schema(implementation = Problem.class))
+ )
+ @ApiResponse(responseCode = "500", description = "Internal error.",
+ content = @Content(mediaType = PROBLEM_JSON, schema =
@Schema(implementation = Problem.class))
+ )
+ @Consumes(MediaType.FORM_DATA)
+ @Produces({
+ APPLICATION_JSON,
+ PROBLEM_JSON
+ })
+ @Post("units")
+ CompletableFuture<Boolean> deploy(
+ @Schema(name = "unitId", required = true) String unitId,
+ @Schema(name = "unitVersion") String unitVersion,
+ @Schema(name = "unitContent", required = true) CompletedFileUpload
unitContent);
+
+ /**
+ * Undeploy unit REST method.
+ */
+ @Operation(operationId = "undeployUnit", description = "Undeploy unit with
provided unitId and unitVersion.")
+ @ApiResponse(responseCode = "200",
+ description = "Unit undeployed successfully.",
+ //DO NOT Remove redundant parameter. It will BREAK generated spec.
+ content = @Content(mediaType = APPLICATION_JSON, schema =
@Schema(implementation = Void.class))
+ )
+ @ApiResponse(responseCode = "404",
+ description = "Unit with provided identifier and version doesn't
exist.",
+ content = @Content(mediaType = PROBLEM_JSON, schema =
@Schema(implementation = Problem.class))
+ )
+ @ApiResponse(responseCode = "500",
+ description = "Internal error.",
+ content = @Content(mediaType = PROBLEM_JSON, schema =
@Schema(implementation = Problem.class))
+ )
+ @Consumes(APPLICATION_JSON)
+ @Produces({
+ APPLICATION_JSON,
+ PROBLEM_JSON
+ })
+ @Delete("units/{unitId}/{unitVersion}")
+ CompletableFuture<Void> undeploy(
+ @PathVariable("unitId") @Schema(name = "unitId", required = true)
String unitId,
+ @PathVariable("unitVersion") @Schema(name = "unitVersion",
required = true) String unitVersion);
+
+ /**
+ * Undeploy latest unit REST method.
+ */
+ @Operation(operationId = "undeployLatestUnit", description = "Undeploy
latest unit with provided unitId.")
+ @ApiResponse(responseCode = "200",
+ description = "Unit undeployed successfully.",
+ //DO NOT Remove redundant parameter. It will BREAK generated spec.
+ content = @Content(mediaType = APPLICATION_JSON, schema =
@Schema(implementation = Void.class))
+ )
+ @ApiResponse(responseCode = "404",
+ description = "Unit with provided identifier and version doesn't
exist.",
+ content = @Content(mediaType = PROBLEM_JSON, schema =
@Schema(implementation = Problem.class))
+ )
+ @ApiResponse(responseCode = "500",
+ description = "Internal error.",
+ content = @Content(mediaType = PROBLEM_JSON, schema =
@Schema(implementation = Problem.class))
+ )
+ @Consumes(APPLICATION_JSON)
+ @Produces({
+ APPLICATION_JSON,
+ PROBLEM_JSON
+ })
+ @Delete("units/{unitId}")
+ CompletableFuture<Void> undeploy(
+ @PathVariable("unitId") @Schema(name = "unitId", required = true)
String unitId);
+
+ /**
+ * All units status REST method.
+ */
+ @Operation(operationId = "units", description = "All units statutes.")
+ @ApiResponse(responseCode = "200",
+ description = "All statutes returned successful.",
+ content = @Content(mediaType = APPLICATION_JSON, array =
@ArraySchema(schema = @Schema(implementation = UnitStatusDto.class)))
+ )
+ @ApiResponse(responseCode = "500",
+ description = "Internal error.",
+ content = @Content(mediaType = PROBLEM_JSON, schema =
@Schema(implementation = Problem.class))
+ )
+ @Consumes(MediaType.TEXT_PLAIN)
+ @Produces({
+ APPLICATION_JSON,
+ PROBLEM_JSON
+ })
+ @Get("units")
+ CompletableFuture<Collection<UnitStatusDto>> units();
+
+ /**
+ * Versions of unit REST method.
+ */
+ @Operation(operationId = "versions", description = "All versions of unit
with provided unit identifier.")
+ @ApiResponse(responseCode = "200",
+ description = "Versions returned successful.",
+ content = @Content(mediaType = APPLICATION_JSON, array =
@ArraySchema(schema = @Schema(implementation = String.class)))
+ )
+ @ApiResponse(responseCode = "404",
+ description = "Unit with provided identifier doesn't exist.",
+ content = @Content(mediaType = PROBLEM_JSON, schema =
@Schema(implementation = Problem.class))
+ )
+ @ApiResponse(responseCode = "500", description = "Internal error.",
+ content = @Content(mediaType = PROBLEM_JSON, schema =
@Schema(implementation = Problem.class)))
+ @Consumes(APPLICATION_JSON)
+ @Produces({
+ APPLICATION_JSON,
+ PROBLEM_JSON
+ })
+ @Get("units/{unitId}/versions")
+ CompletableFuture<Collection<String>> versions(
+ @PathVariable("unitId") @Schema(name = "unitId", required = true)
String unitId);
+
+ /**
+ * Unit status REST method.
+ */
+ @Operation(operationId = "status", description = "Status of unit with
provided identifier.")
+ @ApiResponse(responseCode = "200",
+ description = "Status returned successful.",
+ content = @Content(mediaType = APPLICATION_JSON, schema =
@Schema(implementation = UnitStatusDto.class))
+ )
+ @ApiResponse(responseCode = "404",
+ description = "Unit with provided identifier doesn't exist.",
+ content = @Content(mediaType = PROBLEM_JSON, schema =
@Schema(implementation = Problem.class))
+ )
+ @ApiResponse(responseCode = "500",
+ description = "Internal error.",
+ content = @Content(mediaType = PROBLEM_JSON, schema =
@Schema(implementation = Problem.class))
+ )
+ @Consumes(APPLICATION_JSON)
+ @Produces({
+ APPLICATION_JSON,
+ PROBLEM_JSON
+ })
+ @Get("units/{unitId}/status")
+ CompletableFuture<UnitStatusDto> status(
+ @PathVariable("unitId") @Schema(name = "unitId", required = true)
String unitId);
+}
diff --git
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/deployment/UnitStatusDto.java
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/deployment/UnitStatusDto.java
new file mode 100644
index 0000000000..29af87b47b
--- /dev/null
+++
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/deployment/UnitStatusDto.java
@@ -0,0 +1,90 @@
+/*
+ * 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.deployment;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonGetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.ignite.deployment.UnitStatus;
+import org.apache.ignite.deployment.version.Version;
+
+/**
+ * DTO of {@link UnitStatus}.
+ */
+@Schema(name = "UnitStatus", description = "Unit status.")
+public class UnitStatusDto {
+
+ /**
+ * Unit identifier.
+ */
+ private final String id;
+
+ /**
+ * Map from existing unit version to list of nodes consistent ids where
unit deployed.
+ */
+ private final Map<String, List<String>> versionToConsistentIds;
+
+ @JsonCreator
+ public UnitStatusDto(@JsonProperty("id") String id,
+ @JsonProperty("versionToNodes") Map<String, List<String>>
versionToConsistentIds) {
+ this.id = id;
+ this.versionToConsistentIds = versionToConsistentIds;
+ }
+
+ /**
+ * Returns unit identifier.
+ *
+ * @return Unit identifier.
+ */
+ @JsonGetter("id")
+ public String id() {
+ return id;
+ }
+
+ /**
+ * Returns map from existing unit version to list of nodes consistent ids
where unit deployed.
+ *
+ * @return Map from existing unit version to list of nodes consistent ids
where unit deployed.
+ */
+ @JsonGetter("versionToNodes")
+ public Map<String, List<String>> versionToConsistentIds() {
+ return versionToConsistentIds;
+ }
+
+
+ /**
+ * Mapper method.
+ *
+ * @param status Unit status.
+ * @return Unit status DTO.
+ */
+ public static UnitStatusDto fromUnitStatus(UnitStatus status) {
+ Map<String, List<String>> versionToConsistentIds = new HashMap<>();
+ Set<Version> versions = status.versions();
+ for (Version version : versions) {
+ versionToConsistentIds.put(version.render(),
status.consistentIds(version));
+ }
+ return new UnitStatusDto(status.id(), versionToConsistentIds);
+ }
+
+}
diff --git
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/constants/HttpCode.java
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/constants/HttpCode.java
index 7eed027e89..18771d74c5 100644
---
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/constants/HttpCode.java
+++
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/constants/HttpCode.java
@@ -26,6 +26,8 @@ public enum HttpCode {
UNAUTHORIZED(401, "Unauthorized"),
FORBIDDEN(403, "Forbidden"),
NOT_FOUND(404, "Not Found"),
+ // May be used in case of "Already exists" problem.
+ CONFLICT(409, "Conflict"),
INTERNAL_ERROR(500, "Internal Server Error");
private final int code;
diff --git
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/constants/MediaType.java
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/constants/MediaType.java
index 543ffa0b78..213d3ac8b9 100644
---
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/constants/MediaType.java
+++
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/constants/MediaType.java
@@ -37,4 +37,8 @@ public final class MediaType {
* text/plain media type.
*/
public static final String TEXT_PLAIN = "text/plain";
+
+ public static final String OCTET_STREAM = "application/octet-stream";
+
+ public static final String FORM_DATA = "multipart/form-data";
}
diff --git
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/exception/ClusterNotInitializedException.java
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/exception/ClusterNotInitializedException.java
index a8547f793d..d9e349ec58 100644
---
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/exception/ClusterNotInitializedException.java
+++
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/exception/ClusterNotInitializedException.java
@@ -21,7 +21,4 @@ package org.apache.ignite.internal.rest.exception;
* Exception that is thrown when the cluster is not initialized.
*/
public class ClusterNotInitializedException extends RuntimeException {
- public ClusterNotInitializedException() {
- super();
- }
}
diff --git a/modules/rest/build.gradle b/modules/rest/build.gradle
index d2ab0ff9dd..0d4cf9c255 100644
--- a/modules/rest/build.gradle
+++ b/modules/rest/build.gradle
@@ -35,6 +35,7 @@ dependencies {
implementation project(':ignite-network')
implementation project(':ignite-cluster-management')
implementation project(':ignite-metrics')
+ implementation project(':ignite-code-deployment')
implementation project(':ignite-security')
implementation libs.jetbrains.annotations
implementation libs.micronaut.inject
@@ -64,15 +65,17 @@ dependencies {
integrationTestAnnotationProcessor
libs.micronaut.inject.annotation.processor
integrationTestAnnotationProcessor testFixtures(project(':ignite-core'))
- integrationTestAnnotationProcessor
testFixtures(project(':ignite-cluster-management'))
integrationTestImplementation project(':ignite-rest-api')
integrationTestImplementation project(':ignite-network')
integrationTestImplementation project(':ignite-api')
integrationTestImplementation project(':ignite-security')
+ integrationTestImplementation project(':ignite-code-deployment')
+ integrationTestImplementation project(':ignite-runner')
integrationTestImplementation testFixtures(project(':ignite-core'))
integrationTestImplementation
testFixtures(project(':ignite-cluster-management'))
integrationTestImplementation
testFixtures(project(':ignite-configuration'))
+ integrationTestImplementation testFixtures(project(":ignite-api"))
integrationTestImplementation libs.micronaut.junit5
integrationTestImplementation libs.micronaut.test
integrationTestImplementation libs.micronaut.http.client
diff --git
a/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/deployment/DeploymentManagementControllerTest.java
b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/deployment/DeploymentManagementControllerTest.java
new file mode 100644
index 0000000000..b87e140022
--- /dev/null
+++
b/modules/rest/src/integrationTest/java/org/apache/ignite/internal/rest/deployment/DeploymentManagementControllerTest.java
@@ -0,0 +1,270 @@
+/*
+ * 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.deployment;
+
+import static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.WRITE;
+import static org.apache.ignite.internal.rest.constants.HttpCode.BAD_REQUEST;
+import static org.apache.ignite.internal.rest.constants.HttpCode.CONFLICT;
+import static org.apache.ignite.internal.rest.constants.HttpCode.NOT_FOUND;
+import static org.apache.ignite.internal.rest.constants.HttpCode.OK;
+import static
org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.MediaType;
+import io.micronaut.http.MutableHttpRequest;
+import io.micronaut.http.client.HttpClient;
+import io.micronaut.http.client.annotation.Client;
+import io.micronaut.http.client.exceptions.HttpClientResponseException;
+import io.micronaut.http.client.multipart.MultipartBody;
+import io.micronaut.http.client.multipart.MultipartBody.Builder;
+import jakarta.inject.Inject;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import org.apache.ignite.deployment.version.Version;
+import org.apache.ignite.internal.rest.api.deployment.UnitStatusDto;
+import org.apache.ignite.internal.testframework.IntegrationTestBase;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+
+/**
+ * Integration test for REST controller {@link DeploymentManagementController}.
+ */
+public class DeploymentManagementControllerTest extends IntegrationTestBase {
+ private static Path dummyFile;
+
+ private static final long SIZE_IN_BYTES = 1024L;
+
+ @Inject
+ @Client(NODE_URL + "/management/v1/deployment")
+ HttpClient client;
+
+ @BeforeEach
+ public void setup(TestInfo testInfo) throws IOException {
+ startNodes(testInfo);
+ String metaStorageNodeName = testNodeName(testInfo, 0);
+ initializeCluster(metaStorageNodeName);
+
+ dummyFile = WORK_DIR.resolve("dummy.txt");
+
+ if (!Files.exists(dummyFile)) {
+ try (SeekableByteChannel channel = Files.newByteChannel(dummyFile,
WRITE, CREATE)) {
+ channel.position(SIZE_IN_BYTES - 4);
+
+ ByteBuffer buf = ByteBuffer.allocate(4).putInt(2);
+ buf.rewind();
+ channel.write(buf);
+ }
+ }
+ }
+
+ @AfterEach
+ public void cleanup(TestInfo testInfo) throws Exception {
+ stopNodes(testInfo);
+ }
+
+ @Test
+ public void testDeploySuccessful() {
+ String id = "testId";
+ String version = "1.1.1";
+ HttpResponse<Object> response = deploy(id, version);
+
+ assertThat(response.code(), is(OK.code()));
+
+ MutableHttpRequest<Object> get = HttpRequest.GET("units");
+ UnitStatusDto status = client.toBlocking().retrieve(get,
UnitStatusDto.class);
+
+ assertThat(status.id(), is(id));
+ assertThat(status.versionToConsistentIds().keySet(),
equalTo(Set.of(version)));
+ assertThat(status.versionToConsistentIds().get(version),
hasItem(CLUSTER_NODE_NAMES.get(0)));
+ }
+
+ @Test
+ public void testDeployFailedWithoutId() {
+ HttpClientResponseException e = assertThrows(
+ HttpClientResponseException.class,
+ () -> deploy(null, "1.1.1"));
+ assertThat(e.getResponse().code(), is(BAD_REQUEST.code()));
+ }
+
+ @Test
+ public void testDeployFailedWithoutContent() {
+ String id = "unitId";
+ String version = "1.1.1";
+ HttpClientResponseException e = assertThrows(
+ HttpClientResponseException.class,
+ () -> deploy(id, version, null));
+ assertThat(e.getResponse().code(), is(BAD_REQUEST.code()));
+ }
+
+ @Test
+ public void testDeploySuccessfulWithoutVersion() {
+ String id = "testId";
+ HttpResponse<Object> response = deploy(id);
+
+ assertThat(response.code(), is(OK.code()));
+
+ MutableHttpRequest<Object> get = HttpRequest.GET("units");
+ UnitStatusDto status = client.toBlocking().retrieve(get,
UnitStatusDto.class);
+
+ String version = Version.LATEST.render();
+ assertThat(status.id(), is(id));
+ assertThat(status.versionToConsistentIds().keySet(),
equalTo(Set.of(version)));
+ assertThat(status.versionToConsistentIds().get(version),
hasItem(CLUSTER_NODE_NAMES.get(0)));
+ }
+
+ @Test
+ public void testDeployExisted() {
+ String id = "testId";
+ String version = "1.1.1";
+ HttpResponse<Object> response = deploy(id, version);
+
+ assertThat(response.code(), is(OK.code()));
+
+ HttpClientResponseException e = assertThrows(
+ HttpClientResponseException.class,
+ () -> deploy(id, version));
+ assertThat(e.getResponse().code(), is(CONFLICT.code()));
+ }
+
+ @Test
+ public void testDeployUndeploy() {
+ String id = "testId";
+ String version = "1.1.1";
+
+ HttpResponse<Object> response = deploy(id, version);
+
+ assertThat(response.code(), is(OK.code()));
+
+ response = undeploy(id, version);
+ assertThat(response.code(), is(OK.code()));
+ }
+
+ @Test
+ public void testUndeployFailed() {
+ HttpClientResponseException e = assertThrows(
+ HttpClientResponseException.class,
+ () -> undeploy("testId", "1.1.1"));
+ assertThat(e.getResponse().code(), is(NOT_FOUND.code()));
+ }
+
+ @Test
+ public void testVersionEmpty() {
+ String id = "nonExisted";
+ assertThat(versions(id), equalTo(Collections.emptyList()));
+ }
+
+ @Test
+ public void testDeployUndeployLatest() {
+ String id = "testId";
+ HttpResponse<Object> response = deploy(id);
+
+ assertThat(response.code(), is(OK.code()));
+ MutableHttpRequest<Object> delete = HttpRequest
+ .DELETE("units/" + id)
+ .contentType(MediaType.APPLICATION_JSON);
+ response = client.toBlocking().exchange(delete);
+ assertThat(response.code(), is(OK.code()));
+ }
+
+ @Test
+ public void testVersionOrder() {
+ String id = "unitId";
+ deploy(id);
+ deploy(id, "1.1.1");
+ deploy(id, "1.1.2");
+ deploy(id, "1.2.1");
+ deploy(id, "2.0");
+ deploy(id, "1.0.0");
+ deploy(id, "1.0.1");
+
+ List<String> versions = versions(id);
+
+ assertThat(versions, contains("1.0.0", "1.0.1", "1.1.1", "1.1.2",
"1.2.1", "2.0.0", "latest"));
+ }
+
+ private HttpResponse<Object> deploy(String id) {
+ return deploy(id, null);
+ }
+
+ private HttpResponse<Object> deploy(String id, String version) {
+ return deploy(id, version, dummyFile.toFile());
+ }
+
+ private HttpResponse<Object> deploy(String id, String version, File file) {
+ Builder builder = MultipartBody.builder()
+ .addPart("unitVersion", version);
+
+ if (id != null) {
+ builder.addPart("unitId", id);
+ }
+ if (file != null) {
+ builder.addPart("unitContent", file);
+ }
+
+ MutableHttpRequest<MultipartBody> post = HttpRequest.POST("units",
builder.build())
+ .contentType(MediaType.MULTIPART_FORM_DATA);
+ return client.toBlocking().exchange(post);
+ }
+
+ private HttpResponse<Object> undeploy(String id) {
+ MutableHttpRequest<Object> delete = HttpRequest
+ .DELETE("units/" + id)
+ .contentType(MediaType.APPLICATION_JSON);
+
+ return client.toBlocking().exchange(delete);
+ }
+
+ private HttpResponse<Object> undeploy(String id, String version) {
+ MutableHttpRequest<Object> delete = HttpRequest
+ .DELETE("units/" + id + "/" + version)
+ .contentType(MediaType.APPLICATION_JSON);
+
+ return client.toBlocking().exchange(delete);
+ }
+
+ private List<String> versions(String id) {
+ MutableHttpRequest<Object> versions = HttpRequest
+ .GET("units/" + id + "/versions")
+ .contentType(MediaType.APPLICATION_JSON);
+
+ return client.toBlocking().retrieve(versions, List.class);
+
+ }
+
+ private UnitStatusDto status(String id) {
+ MutableHttpRequest<Object> get = HttpRequest.GET("units/" + id +
"/status");
+ return client.toBlocking().retrieve(get, UnitStatusDto.class);
+ }
+}
diff --git
a/modules/api/src/main/java/org/apache/ignite/deployment/version/VersionParseException.java
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/CodeDeploymentRestFactory.java
similarity index 54%
copy from
modules/api/src/main/java/org/apache/ignite/deployment/version/VersionParseException.java
copy to
modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/CodeDeploymentRestFactory.java
index 717035a9c0..a75c167f2c 100644
---
a/modules/api/src/main/java/org/apache/ignite/deployment/version/VersionParseException.java
+++
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/CodeDeploymentRestFactory.java
@@ -15,34 +15,28 @@
* limitations under the License.
*/
-package org.apache.ignite.deployment.version;
+package org.apache.ignite.internal.rest.deployment;
+
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.context.annotation.Factory;
+import jakarta.inject.Singleton;
+import org.apache.ignite.deployment.IgniteDeployment;
+import org.apache.ignite.internal.rest.RestFactory;
/**
- * Throws when {@link Version} of deployment unit not parsable.
+ * Factory of {@link DeploymentManagementController}.
*/
-public class VersionParseException extends RuntimeException {
- /**
- * Constructor.
- */
- public VersionParseException() {
-
- }
+@Factory
+public class CodeDeploymentRestFactory implements RestFactory {
+ private final IgniteDeployment igniteDeployment;
- /**
- * Constructor.
- *
- * @param cause Cause error.
- */
- public VersionParseException(Throwable cause) {
- super(cause);
+ public CodeDeploymentRestFactory(IgniteDeployment igniteDeployment) {
+ this.igniteDeployment = igniteDeployment;
}
- /**
- * Constructor.
- *
- * @param message Error message.
- */
- public VersionParseException(String message) {
- super(message);
+ @Bean
+ @Singleton
+ public IgniteDeployment deployment() {
+ return igniteDeployment;
}
}
diff --git
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/DeploymentManagementController.java
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/DeploymentManagementController.java
new file mode 100644
index 0000000000..cf8fa908ff
--- /dev/null
+++
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/DeploymentManagementController.java
@@ -0,0 +1,99 @@
+/*
+ * 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.deployment;
+
+import io.micronaut.http.annotation.Controller;
+import io.micronaut.http.multipart.CompletedFileUpload;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import org.apache.ignite.deployment.DeploymentUnit;
+import org.apache.ignite.deployment.IgniteDeployment;
+import org.apache.ignite.deployment.version.Version;
+import org.apache.ignite.internal.rest.api.deployment.DeploymentCodeApi;
+import org.apache.ignite.internal.rest.api.deployment.UnitStatusDto;
+
+/**
+ * Implementation of {@link DeploymentCodeApi}.
+ */
+@Controller("/management/v1/deployment")
+public class DeploymentManagementController implements DeploymentCodeApi {
+ private final IgniteDeployment deployment;
+
+ public DeploymentManagementController(IgniteDeployment deployment) {
+ this.deployment = deployment;
+ }
+
+ @Override
+ public CompletableFuture<Boolean> deploy(String unitId, String
unitVersion, CompletedFileUpload unitContent) {
+ try {
+ DeploymentUnit deploymentUnit = toDeploymentUnit(unitContent);
+ if (unitVersion == null || unitVersion.isBlank()) {
+ return deployment.deployAsync(unitId, deploymentUnit);
+ }
+ return deployment.deployAsync(unitId,
Version.parseVersion(unitVersion), deploymentUnit);
+ } catch (IOException e) {
+ return CompletableFuture.failedFuture(e);
+ }
+ }
+
+ @Override
+ public CompletableFuture<Void> undeploy(String unitId, String unitVersion)
{
+ return deployment.undeployAsync(unitId,
Version.parseVersion(unitVersion));
+ }
+
+ @Override
+ public CompletableFuture<Void> undeploy(String unitId) {
+ return deployment.undeployAsync(unitId);
+ }
+
+ @Override
+ public CompletableFuture<Collection<UnitStatusDto>> units() {
+ return deployment.unitsAsync().thenApply(statuses ->
statuses.stream().map(UnitStatusDto::fromUnitStatus)
+ .collect(Collectors.toList()));
+ }
+
+ @Override
+ public CompletableFuture<Collection<String>> versions(String unitId) {
+ return deployment.versionsAsync(unitId)
+ .thenApply(versions ->
versions.stream().map(Version::render).collect(Collectors.toList()));
+ }
+
+ @Override
+ public CompletableFuture<UnitStatusDto> status(String unitId) {
+ return
deployment.statusAsync(unitId).thenApply(UnitStatusDto::fromUnitStatus);
+ }
+
+ private static DeploymentUnit toDeploymentUnit(CompletedFileUpload
unitContent) throws IOException {
+ String fileName = unitContent.getFilename();
+ InputStream is = unitContent.getInputStream();
+ return new DeploymentUnit() {
+ @Override
+ public String name() {
+ return fileName;
+ }
+
+ @Override
+ public InputStream content() {
+ return is;
+ }
+ };
+ }
+}
diff --git
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/exception/handler/DeploymentUnitAlreadyExistExceptionHandler.java
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/exception/handler/DeploymentUnitAlreadyExistExceptionHandler.java
new file mode 100644
index 0000000000..89ce644a83
--- /dev/null
+++
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/exception/handler/DeploymentUnitAlreadyExistExceptionHandler.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.deployment.exception.handler;
+
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.server.exceptions.ExceptionHandler;
+import jakarta.inject.Singleton;
+import
org.apache.ignite.internal.deployunit.exception.DeploymentUnitAlreadyExistsException;
+import org.apache.ignite.internal.rest.api.Problem;
+import org.apache.ignite.internal.rest.constants.HttpCode;
+import org.apache.ignite.internal.rest.problem.HttpProblemResponse;
+
+/**
+ * REST exception handler for {@link DeploymentUnitAlreadyExistsException}.
+ */
+@Singleton
+@Requires(classes = {DeploymentUnitAlreadyExistsException.class,
ExceptionHandler.class})
+public class DeploymentUnitAlreadyExistExceptionHandler
+ implements ExceptionHandler<DeploymentUnitAlreadyExistsException,
HttpResponse<? extends Problem>> {
+ @Override
+ public HttpResponse<? extends Problem> handle(HttpRequest request,
DeploymentUnitAlreadyExistsException exception) {
+ return HttpProblemResponse.from(
+ Problem.fromHttpCode(HttpCode.CONFLICT)
+ .detail(exception.getMessage()).build()
+ );
+ }
+}
diff --git
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/exception/handler/DeploymentUnitNotFoundExceptionHandler.java
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/exception/handler/DeploymentUnitNotFoundExceptionHandler.java
new file mode 100644
index 0000000000..505dbadd20
--- /dev/null
+++
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/exception/handler/DeploymentUnitNotFoundExceptionHandler.java
@@ -0,0 +1,45 @@
+/*
+ * 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.deployment.exception.handler;
+
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.server.exceptions.ExceptionHandler;
+import jakarta.inject.Singleton;
+import
org.apache.ignite.internal.deployunit.exception.DeploymentUnitNotFoundException;
+import org.apache.ignite.internal.rest.api.Problem;
+import org.apache.ignite.internal.rest.constants.HttpCode;
+import org.apache.ignite.internal.rest.problem.HttpProblemResponse;
+
+/**
+ * REST exception handler for {@link DeploymentUnitNotFoundException}.
+ */
+@Singleton
+@Requires(classes = {DeploymentUnitNotFoundException.class,
ExceptionHandler.class})
+public class DeploymentUnitNotFoundExceptionHandler implements
+ ExceptionHandler<DeploymentUnitNotFoundException, HttpResponse<?
extends Problem>> {
+
+ @Override
+ public HttpResponse<? extends Problem> handle(HttpRequest request,
DeploymentUnitNotFoundException exception) {
+ return HttpProblemResponse.from(
+ Problem.fromHttpCode(HttpCode.NOT_FOUND)
+ .detail(exception.getMessage()).build()
+ );
+ }
+}
diff --git
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/exception/handler/VersionParseExceptionHandler.java
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/exception/handler/VersionParseExceptionHandler.java
new file mode 100644
index 0000000000..752cfe78a8
--- /dev/null
+++
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/exception/handler/VersionParseExceptionHandler.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.deployment.exception.handler;
+
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.server.exceptions.ExceptionHandler;
+import jakarta.inject.Singleton;
+import org.apache.ignite.deployment.version.VersionParseException;
+import org.apache.ignite.internal.rest.api.Problem;
+import org.apache.ignite.internal.rest.constants.HttpCode;
+import org.apache.ignite.internal.rest.problem.HttpProblemResponse;
+
+/**
+ * REST exception handler for {@link VersionParseException}.
+ */
+@Singleton
+@Requires(classes = {VersionParseException.class, ExceptionHandler.class})
+public class VersionParseExceptionHandler implements
ExceptionHandler<VersionParseException, HttpResponse<? extends Problem>> {
+ @Override
+ public HttpResponse<? extends Problem> handle(HttpRequest request,
VersionParseException exception) {
+ return HttpProblemResponse.from(
+ Problem.fromHttpCode(HttpCode.BAD_REQUEST)
+ .detail("Invalid version format of provided version: "
+ exception.getRawVersion())
+ .build()
+ );
+ }
+}
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 d06b454a16..ff05278575 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
@@ -103,6 +103,7 @@ import
org.apache.ignite.internal.rest.authentication.AuthProviderFactory;
import org.apache.ignite.internal.rest.cluster.ClusterManagementRestFactory;
import org.apache.ignite.internal.rest.configuration.PresentationsFactory;
import org.apache.ignite.internal.rest.configuration.RestConfiguration;
+import org.apache.ignite.internal.rest.deployment.CodeDeploymentRestFactory;
import org.apache.ignite.internal.rest.metrics.MetricRestFactory;
import org.apache.ignite.internal.rest.node.NodeManagementRestFactory;
import org.apache.ignite.internal.schema.SchemaManager;
@@ -414,8 +415,6 @@ public class IgniteImpl implements Ignite {
DistributionZonesConfiguration zonesConfiguration =
clusterConfigRegistry
.getConfiguration(DistributionZonesConfiguration.KEY);
- restComponent = createRestComponent(name);
-
restAddressReporter = new RestAddressReporter(workDir);
baselineMgr = new BaselineManager(
@@ -518,6 +517,8 @@ public class IgniteImpl implements Ignite {
workDir,
nodeConfigRegistry.getConfiguration(DeploymentConfiguration.KEY),
cmgMgr);
+
+ restComponent = createRestComponent(name);
}
private RestComponent createRestComponent(String name) {
@@ -529,12 +530,14 @@ public class IgniteImpl implements Ignite {
RestFactory nodeManagementRestFactory = new
NodeManagementRestFactory(lifecycleManager, () -> name);
RestFactory nodeMetricRestFactory = new
MetricRestFactory(metricManager);
AuthProviderFactory authProviderFactory = new
AuthProviderFactory(authConfiguration);
+ RestFactory deploymentCodeRestFactory = new
CodeDeploymentRestFactory(deploymentManager);
RestConfiguration restConfiguration =
nodeCfgMgr.configurationRegistry().getConfiguration(RestConfiguration.KEY);
return new RestComponent(
List.of(presentationsFactory,
clusterManagementRestFactory,
nodeManagementRestFactory,
nodeMetricRestFactory,
+ deploymentCodeRestFactory,
authProviderFactory),
restConfiguration
);