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 ed1534ce7c IGNITE-19475 Implement JobClassLoader (#2099)
ed1534ce7c is described below

commit ed1534ce7cf643ee576699cd181c2afcc933f5ce
Author: Ivan Gagarkin <[email protected]>
AuthorDate: Tue May 30 18:21:31 2023 +0400

    IGNITE-19475 Implement JobClassLoader (#2099)
---
 modules/api/build.gradle                           |   1 +
 .../org/apache/ignite/compute/DeploymentUnit.java  |  69 ++++++
 .../ignite/compute}/version/UnitVersion.java       |  11 +-
 .../apache/ignite/compute}/version/Version.java    |   4 +-
 .../compute}/version/VersionParseException.java    |   2 +-
 .../ignite/compute/version/VersionTest.java}       |   7 +-
 .../deployunit/DeployMessagingService.java         |   2 +-
 .../ignite/internal/deployunit/DeployTracker.java  |   2 +-
 .../internal/deployunit/DeploymentManagerImpl.java |   2 +-
 .../internal/deployunit/IgniteDeployment.java      |   2 +-
 .../ignite/internal/deployunit/UnitStatus.java     |   2 +-
 .../ignite/internal/deployunit/UnitStatuses.java   |   2 +-
 .../deployunit/metastore/DeploymentUnitStore.java  |   2 +-
 .../metastore/DeploymentUnitStoreImpl.java         |   2 +-
 .../internal/deployunit/metastore/key/UnitKey.java |   2 +-
 .../metastore/key/UnitMetaSerializer.java          |   2 +-
 .../ignite/deployment/UnitMetaSerializerTest.java  |   2 +-
 .../metastore/DeploymentUnitStoreImplTest.java     |   2 +-
 .../ignite/internal/compute/JobClassLoader.java    | 121 ++++++++++
 .../internal/compute/JobClassLoaderFactory.java    | 137 +++++++++++
 modules/compute/src/test/README.md                 |  15 ++
 .../compute/JobClassLoaderFactoryTest.java         | 262 +++++++++++++++++++++
 .../internal/compute/JobClassLoaderTest.java       |  88 +++++++
 .../units/test-units-1.0-SNAPSHOT-src.zip          | Bin 0 -> 69386 bytes
 .../units/unit1/1.0.0/unit1-1.0-SNAPSHOT.jar       | Bin 0 -> 1693 bytes
 .../units/unit1/2.0.0/unit2-1.0-SNAPSHOT.jar       | Bin 0 -> 1681 bytes
 .../units/unit1/3.0.1/unit1-1.0-SNAPSHOT.jar       | Bin 0 -> 1693 bytes
 .../units/unit1/3.0.1/unit2-1.0-SNAPSHOT.jar       | Bin 0 -> 1681 bytes
 .../unit1/3.0.2/subdir/unit2-1.0-SNAPSHOT.jar      | Bin 0 -> 1681 bytes
 .../units/unit1/3.0.2/unit1-1.0-SNAPSHOT.jar       | Bin 0 -> 1693 bytes
 .../units/unit1/4.0.0/unit1-1.0-corrupted.jar      | Bin 0 -> 1693 bytes
 .../resources/units/unit1/5.0.0/subdir/test.txt    |   1 +
 .../src/test/resources/units/unit1/5.0.0/test.txt  |   1 +
 .../units/unit2/1.0.0/unit1-1.0-SNAPSHOT.jar       | Bin 0 -> 1693 bytes
 .../units/unit2/2.0.0/unit2-1.0-SNAPSHOT.jar       | Bin 0 -> 1681 bytes
 .../java/org/apache/ignite/lang/ErrorGroups.java   |  14 ++
 .../deployment/DeploymentManagementController.java |   7 +-
 .../handler/VersionParseExceptionHandler.java      |   2 +-
 .../internal/deployment/ItDeploymentUnitTest.java  |   2 +-
 39 files changed, 737 insertions(+), 31 deletions(-)

diff --git a/modules/api/build.gradle b/modules/api/build.gradle
index 9069b586a9..0155eb0ba7 100644
--- a/modules/api/build.gradle
+++ b/modules/api/build.gradle
@@ -29,6 +29,7 @@ dependencies {
     testImplementation libs.hamcrest.optional
     testImplementation libs.archunit.core
     testImplementation libs.archunit.junit5
+    testImplementation testFixtures(project(":ignite-core"))
 
     testFixturesAnnotationProcessor libs.micronaut.inject.annotation.processor
     testFixturesImplementation project(":ignite-core")
diff --git 
a/modules/api/src/main/java/org/apache/ignite/compute/DeploymentUnit.java 
b/modules/api/src/main/java/org/apache/ignite/compute/DeploymentUnit.java
new file mode 100644
index 0000000000..2f152eb599
--- /dev/null
+++ b/modules/api/src/main/java/org/apache/ignite/compute/DeploymentUnit.java
@@ -0,0 +1,69 @@
+/*
+ * 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.compute;
+
+import org.apache.ignite.compute.version.Version;
+
+/**
+ * Deployment unit.
+ */
+public class DeploymentUnit {
+
+    /** Name of the deployment unit. */
+    private final String name;
+
+    /** Version of the deployment unit. */
+    private final Version version;
+
+    /**
+     * Constructor.
+     *
+     * @param name Name of the deployment unit.
+     * @param version Version of the deployment unit.
+     */
+    public DeploymentUnit(String name, Version version) {
+        this.name = name;
+        this.version = version;
+    }
+
+    /**
+     * Returns name of the deployment unit.
+     *
+     * @return Name of the deployment unit.
+     */
+    public String name() {
+        return name;
+    }
+
+    /**
+     * Returns version of the deployment unit.
+     *
+     * @return Version of the deployment unit.
+     */
+    public Version version() {
+        return version;
+    }
+
+    @Override
+    public String toString() {
+        return "DeploymentUnit{"
+                + "name='" + name + '\''
+                + ", version=" + version
+                + '}';
+    }
+}
diff --git 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/version/UnitVersion.java
 b/modules/api/src/main/java/org/apache/ignite/compute/version/UnitVersion.java
similarity index 94%
rename from 
modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/version/UnitVersion.java
rename to 
modules/api/src/main/java/org/apache/ignite/compute/version/UnitVersion.java
index c408595e64..c0a50177c7 100644
--- 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/version/UnitVersion.java
+++ 
b/modules/api/src/main/java/org/apache/ignite/compute/version/UnitVersion.java
@@ -15,17 +15,18 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.deployunit.version;
+package org.apache.ignite.compute.version;
 
 import java.util.Objects;
 
 /**
- * Implementation of {@link Version} interface based on the three numbers 
format,
- * like x.x.x. where x is short number.
+ * Implementation of {@link Version} interface based on the three numbers 
format, like x.x.x. where x is short number.
  */
-public class UnitVersion implements Version {
+class UnitVersion implements Version {
     private final short major;
+
     private final short minor;
+
     private final short patch;
 
     /**
@@ -35,7 +36,7 @@ public class UnitVersion implements Version {
      * @param minor Minor part of version.
      * @param patch Patch part of version.
      */
-    public UnitVersion(short major, short minor, short patch) {
+    UnitVersion(short major, short minor, short patch) {
         this.major = major;
         this.minor = minor;
         this.patch = patch;
diff --git 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/version/Version.java
 b/modules/api/src/main/java/org/apache/ignite/compute/version/Version.java
similarity index 95%
rename from 
modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/version/Version.java
rename to 
modules/api/src/main/java/org/apache/ignite/compute/version/Version.java
index f15393c3b4..a4d2a4366d 100644
--- 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/version/Version.java
+++ b/modules/api/src/main/java/org/apache/ignite/compute/version/Version.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.deployunit.version;
+package org.apache.ignite.compute.version;
 
 
 /**
@@ -61,7 +61,7 @@ public interface Version extends Comparable<Version> {
      * @return Version instance of
      */
     static Version parseVersion(String s) {
-        if ("latest".equals(s)) {
+        if ("latest".equalsIgnoreCase(s)) {
             return LATEST;
         }
 
diff --git 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/version/VersionParseException.java
 
b/modules/api/src/main/java/org/apache/ignite/compute/version/VersionParseException.java
similarity index 96%
rename from 
modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/version/VersionParseException.java
rename to 
modules/api/src/main/java/org/apache/ignite/compute/version/VersionParseException.java
index 57f1ed9e10..ff5fa15300 100644
--- 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/version/VersionParseException.java
+++ 
b/modules/api/src/main/java/org/apache/ignite/compute/version/VersionParseException.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.deployunit.version;
+package org.apache.ignite.compute.version;
 
 /**
  * Throws when {@link Version} of deployment unit not parsable.
diff --git 
a/modules/code-deployment/src/test/java/org/apache/ignite/deployment/version/VersionUnitTest.java
 b/modules/api/src/test/java/org/apache/ignite/compute/version/VersionTest.java
similarity index 88%
rename from 
modules/code-deployment/src/test/java/org/apache/ignite/deployment/version/VersionUnitTest.java
rename to 
modules/api/src/test/java/org/apache/ignite/compute/version/VersionTest.java
index f2f436fef3..e209f7d582 100644
--- 
a/modules/code-deployment/src/test/java/org/apache/ignite/deployment/version/VersionUnitTest.java
+++ 
b/modules/api/src/test/java/org/apache/ignite/compute/version/VersionTest.java
@@ -15,21 +15,18 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.deployment.version;
+package org.apache.ignite.compute.version;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 
-import org.apache.ignite.internal.deployunit.version.UnitVersion;
-import org.apache.ignite.internal.deployunit.version.Version;
-import org.apache.ignite.internal.deployunit.version.VersionParseException;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 /**
  * Unit tests for {@link Version}.
  */
-public class VersionUnitTest {
+public class VersionTest {
     @Test
     public void testParseCorrect() {
         assertThat(Version.parseVersion("1.1.0"), is(new UnitVersion((short) 
1, (short) 1, (short) 0)));
diff --git 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/DeployMessagingService.java
 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/DeployMessagingService.java
index 59c920684a..1b7cfde0f3 100644
--- 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/DeployMessagingService.java
+++ 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/DeployMessagingService.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.deployunit;
 
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.compute.version.Version;
 import 
org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
 import org.apache.ignite.internal.deployunit.message.DeployUnitMessageTypes;
 import org.apache.ignite.internal.deployunit.message.DeployUnitRequest;
@@ -31,7 +32,6 @@ import 
org.apache.ignite.internal.deployunit.message.StopDeployResponseImpl;
 import org.apache.ignite.internal.deployunit.message.UndeployUnitRequest;
 import org.apache.ignite.internal.deployunit.message.UndeployUnitRequestImpl;
 import org.apache.ignite.internal.deployunit.message.UndeployUnitResponseImpl;
-import org.apache.ignite.internal.deployunit.version.Version;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.network.ChannelType;
diff --git 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/DeployTracker.java
 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/DeployTracker.java
index ad733c4618..644ab998a2 100644
--- 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/DeployTracker.java
+++ 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/DeployTracker.java
@@ -22,7 +22,7 @@ import static 
org.apache.ignite.internal.deployunit.metastore.key.UnitKey.cluste
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
-import org.apache.ignite.internal.deployunit.version.Version;
+import org.apache.ignite.compute.version.Version;
 import org.apache.ignite.internal.future.InFlightFutures;
 import org.apache.ignite.lang.ByteArray;
 
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 c083aea712..16f515b0b8 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
@@ -33,6 +33,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
+import org.apache.ignite.compute.version.Version;
 import 
org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
 import 
org.apache.ignite.internal.deployunit.configuration.DeploymentConfiguration;
 import 
org.apache.ignite.internal.deployunit.exception.DeploymentUnitAlreadyExistsException;
@@ -40,7 +41,6 @@ import 
org.apache.ignite.internal.deployunit.exception.DeploymentUnitNotFoundExc
 import 
org.apache.ignite.internal.deployunit.exception.DeploymentUnitReadException;
 import org.apache.ignite.internal.deployunit.metastore.DeploymentUnitStore;
 import org.apache.ignite.internal.deployunit.metastore.DeploymentUnitStoreImpl;
-import org.apache.ignite.internal.deployunit.version.Version;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.metastorage.MetaStorageManager;
diff --git 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/IgniteDeployment.java
 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/IgniteDeployment.java
index 525904a4a0..ee85618777 100644
--- 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/IgniteDeployment.java
+++ 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/IgniteDeployment.java
@@ -19,7 +19,7 @@ package org.apache.ignite.internal.deployunit;
 
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
-import org.apache.ignite.internal.deployunit.version.Version;
+import org.apache.ignite.compute.version.Version;
 import org.apache.ignite.internal.manager.IgniteComponent;
 
 /**
diff --git 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/UnitStatus.java
 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/UnitStatus.java
index 136edd7c46..1e106f5b3b 100644
--- 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/UnitStatus.java
+++ 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/UnitStatus.java
@@ -17,7 +17,7 @@
 
 package org.apache.ignite.internal.deployunit;
 
-import org.apache.ignite.internal.deployunit.version.Version;
+import org.apache.ignite.compute.version.Version;
 import org.apache.ignite.internal.rest.api.deployment.DeploymentStatus;
 
 /**
diff --git 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/UnitStatuses.java
 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/UnitStatuses.java
index b8c9c1670d..346d8bb6be 100644
--- 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/UnitStatuses.java
+++ 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/UnitStatuses.java
@@ -22,7 +22,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import org.apache.ignite.internal.deployunit.version.Version;
+import org.apache.ignite.compute.version.Version;
 import org.apache.ignite.internal.rest.api.deployment.DeploymentStatus;
 
 /**
diff --git 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/metastore/DeploymentUnitStore.java
 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/metastore/DeploymentUnitStore.java
index 39aeeec6d9..ae20ab30f8 100644
--- 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/metastore/DeploymentUnitStore.java
+++ 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/metastore/DeploymentUnitStore.java
@@ -21,9 +21,9 @@ import static 
org.apache.ignite.internal.rest.api.deployment.DeploymentStatus.UP
 
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.compute.version.Version;
 import org.apache.ignite.internal.deployunit.UnitStatus;
 import org.apache.ignite.internal.deployunit.UnitStatuses;
-import org.apache.ignite.internal.deployunit.version.Version;
 import org.apache.ignite.internal.rest.api.deployment.DeploymentStatus;
 
 /**
diff --git 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/metastore/DeploymentUnitStoreImpl.java
 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/metastore/DeploymentUnitStoreImpl.java
index a472cacd01..2d81bf22d9 100644
--- 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/metastore/DeploymentUnitStoreImpl.java
+++ 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/metastore/DeploymentUnitStoreImpl.java
@@ -38,9 +38,9 @@ import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import org.apache.ignite.compute.version.Version;
 import org.apache.ignite.internal.deployunit.UnitStatus;
 import org.apache.ignite.internal.deployunit.UnitStatuses;
-import org.apache.ignite.internal.deployunit.version.Version;
 import org.apache.ignite.internal.metastorage.MetaStorageManager;
 import org.apache.ignite.internal.metastorage.dsl.Condition;
 import org.apache.ignite.internal.metastorage.dsl.Conditions;
diff --git 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/metastore/key/UnitKey.java
 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/metastore/key/UnitKey.java
index 355d5b57a3..c66a273b97 100644
--- 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/metastore/key/UnitKey.java
+++ 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/metastore/key/UnitKey.java
@@ -23,7 +23,7 @@ import java.util.Base64;
 import java.util.Base64.Encoder;
 import java.util.Objects;
 import java.util.stream.Collectors;
-import org.apache.ignite.internal.deployunit.version.Version;
+import org.apache.ignite.compute.version.Version;
 import org.apache.ignite.lang.ByteArray;
 
 /**
diff --git 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/metastore/key/UnitMetaSerializer.java
 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/metastore/key/UnitMetaSerializer.java
index a5b514b138..aff0679bb3 100644
--- 
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/metastore/key/UnitMetaSerializer.java
+++ 
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/metastore/key/UnitMetaSerializer.java
@@ -22,8 +22,8 @@ import static java.nio.charset.StandardCharsets.UTF_8;
 import java.util.Base64;
 import java.util.List;
 import java.util.stream.Collectors;
+import org.apache.ignite.compute.version.Version;
 import org.apache.ignite.internal.deployunit.UnitStatus;
-import org.apache.ignite.internal.deployunit.version.Version;
 import org.apache.ignite.internal.rest.api.deployment.DeploymentStatus;
 
 /**
diff --git 
a/modules/code-deployment/src/test/java/org/apache/ignite/deployment/UnitMetaSerializerTest.java
 
b/modules/code-deployment/src/test/java/org/apache/ignite/deployment/UnitMetaSerializerTest.java
index 23996ebdb7..625cccb2be 100644
--- 
a/modules/code-deployment/src/test/java/org/apache/ignite/deployment/UnitMetaSerializerTest.java
+++ 
b/modules/code-deployment/src/test/java/org/apache/ignite/deployment/UnitMetaSerializerTest.java
@@ -27,9 +27,9 @@ import static 
org.junit.jupiter.params.provider.Arguments.arguments;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import org.apache.ignite.compute.version.Version;
 import org.apache.ignite.internal.deployunit.UnitStatus;
 import org.apache.ignite.internal.deployunit.metastore.key.UnitMetaSerializer;
-import org.apache.ignite.internal.deployunit.version.Version;
 import org.apache.ignite.internal.rest.api.deployment.DeploymentStatus;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
diff --git 
a/modules/code-deployment/src/test/java/org/apache/ignite/deployment/metastore/DeploymentUnitStoreImplTest.java
 
b/modules/code-deployment/src/test/java/org/apache/ignite/deployment/metastore/DeploymentUnitStoreImplTest.java
index 2a0cba4003..1d0eb350e9 100644
--- 
a/modules/code-deployment/src/test/java/org/apache/ignite/deployment/metastore/DeploymentUnitStoreImplTest.java
+++ 
b/modules/code-deployment/src/test/java/org/apache/ignite/deployment/metastore/DeploymentUnitStoreImplTest.java
@@ -27,10 +27,10 @@ import static org.hamcrest.Matchers.equalTo;
 import java.nio.file.Path;
 import java.util.Collections;
 import java.util.List;
+import org.apache.ignite.compute.version.Version;
 import org.apache.ignite.internal.deployunit.UnitStatus;
 import org.apache.ignite.internal.deployunit.UnitStatuses;
 import org.apache.ignite.internal.deployunit.metastore.DeploymentUnitStoreImpl;
-import org.apache.ignite.internal.deployunit.version.Version;
 import org.apache.ignite.internal.metastorage.MetaStorageManager;
 import 
org.apache.ignite.internal.metastorage.impl.StandaloneMetaStorageManager;
 import org.apache.ignite.internal.metastorage.server.KeyValueStorage;
diff --git 
a/modules/compute/src/main/java/org/apache/ignite/internal/compute/JobClassLoader.java
 
b/modules/compute/src/main/java/org/apache/ignite/internal/compute/JobClassLoader.java
new file mode 100644
index 0000000000..22483b3287
--- /dev/null
+++ 
b/modules/compute/src/main/java/org/apache/ignite/internal/compute/JobClassLoader.java
@@ -0,0 +1,121 @@
+/*
+ * 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.compute;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.regex.Pattern;
+import org.apache.ignite.lang.ErrorGroups.Compute;
+import org.apache.ignite.lang.IgniteException;
+
+/**
+ * Implementation of {@link ClassLoader} that loads classes from specified 
component directories.
+ */
+public class JobClassLoader extends URLClassLoader {
+
+    /**
+     * Pattern to match system packages.
+     */
+    private static final Pattern SYSTEM_PACKAGES_PATTERN = 
Pattern.compile("^(java|x|javax|org\\.apache\\.ignite)\\..*$");
+
+    /**
+     * Parent class loader.
+     */
+    private final ClassLoader parent;
+
+    /**
+     * Creates new instance of {@link JobClassLoader}.
+     *
+     * @param urls URLs to load classes from.
+     * @param parent Parent class loader.
+     */
+    JobClassLoader(URL[] urls, ClassLoader parent) {
+        super(urls, parent);
+        this.parent = parent;
+    }
+
+    /**
+     * Loads the class with the specified <a href="#binary-name">binary 
name</a>. The implementation of this method searches for classes in
+     * the following order:
+     *
+     * <ol>
+     *
+     *   <li><p> If the name starts with one of {@link 
JobClassLoader#SYSTEM_PACKAGES_PATTERN},
+     *   the loader delegates search to the parent.  </p></li>
+     *
+     *   <li><p> If the name doesn't start with one of {@link 
JobClassLoader#SYSTEM_PACKAGES_PATTERN}:
+     *
+     *   <ol>
+     *      <li><p> Invoke {@link #findLoadedClass(String)} to check if the 
class has already been loaded.  </p></li>
+     *      <li><p> Invoke the {@link #findClass(String)} method to find the
+     *      class.  </p></li>
+     *      <li><p> Invoke the {@link #loadClass(String) loadClass} method
+     *      on the parent class loader.  </p></li>
+     *   </ol></p></li>
+     *
+     * </ol>
+     *
+     * @param name The <a href="#binary-name">binary name</a> of the class.
+     * @return The resulting {@code Class} object.
+     * @throws ClassNotFoundException If the class was not found.
+     */
+    @Override
+    protected Class<?> loadClass(String name, boolean resolve) throws 
ClassNotFoundException {
+        boolean isSystem = SYSTEM_PACKAGES_PATTERN.matcher(name).find();
+        if (isSystem) {
+            try {
+                return parent.loadClass(name);
+            } catch (ClassNotFoundException exception) {
+                return loadClassFromClasspath(name, resolve);
+            }
+        } else {
+            try {
+                return loadClassFromClasspath(name, resolve);
+            } catch (ClassNotFoundException exception) {
+                return parent.loadClass(name);
+            }
+        }
+    }
+
+    private Class<?> loadClassFromClasspath(String name, boolean resolve) 
throws ClassNotFoundException {
+        Class<?> loadedClass = findLoadedClass(name);
+        if (loadedClass == null) {
+            Class<?> clazz = findClass(name);
+            if (resolve) {
+                resolveClass(clazz);
+            }
+            return clazz;
+        } else {
+            return loadedClass;
+        }
+    }
+
+    @Override
+    public void close() {
+        try {
+            super.close();
+        } catch (IOException e) {
+            throw new IgniteException(
+                    Compute.CLASS_LOADER_ERR,
+                    "Failed to close class loader: " + e.getMessage(),
+                    e
+            );
+        }
+    }
+}
diff --git 
a/modules/compute/src/main/java/org/apache/ignite/internal/compute/JobClassLoaderFactory.java
 
b/modules/compute/src/main/java/org/apache/ignite/internal/compute/JobClassLoaderFactory.java
new file mode 100644
index 0000000000..223cfb0b23
--- /dev/null
+++ 
b/modules/compute/src/main/java/org/apache/ignite/internal/compute/JobClassLoaderFactory.java
@@ -0,0 +1,137 @@
+/*
+ * 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.compute;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import org.apache.ignite.compute.DeploymentUnit;
+import org.apache.ignite.compute.version.Version;
+import org.apache.ignite.internal.logger.IgniteLogger;
+import org.apache.ignite.internal.logger.Loggers;
+import org.apache.ignite.lang.ErrorGroups.Compute;
+import org.apache.ignite.lang.IgniteException;
+
+/**
+ * Creates a class loader for a job.
+ */
+public class JobClassLoaderFactory {
+    private static final IgniteLogger LOG = 
Loggers.forClass(JobClassLoaderFactory.class);
+
+    /**
+     * Directory for units.
+     */
+    private final Path unitsDir;
+
+    /**
+     * The function to detect the last version of the unit.
+     */
+    private final Function<String, Version> detectLastUnitVersion;
+
+    /**
+     * Constructor.
+     *
+     * @param unitsDir The directory for units.
+     * @param detectLastUnitVersion The function to detect the last version of 
the unit.
+     */
+    public JobClassLoaderFactory(Path unitsDir, Function<String, Version> 
detectLastUnitVersion) {
+        this.unitsDir = unitsDir;
+        this.detectLastUnitVersion = detectLastUnitVersion;
+    }
+
+    /**
+     * Create a class loader for the specified units.
+     *
+     * @param units The units of the job.
+     * @return The class loader.
+     */
+    public JobClassLoader createClassLoader(List<DeploymentUnit> units) {
+        if (units.isEmpty()) {
+            throw new IllegalArgumentException("At least one unit must be 
specified");
+        }
+
+        URL[] classPath = units.stream()
+                .map(this::constructPath)
+                .flatMap(JobClassLoaderFactory::collectClasspath)
+                .toArray(URL[]::new);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Classpath for job: " + Arrays.toString(classPath));
+        }
+
+        return new JobClassLoader(classPath, getClass().getClassLoader());
+    }
+
+    private Path constructPath(DeploymentUnit unit) {
+        Version version = unit.version() == Version.LATEST ? 
detectLastUnitVersion.apply(unit.name()) : unit.version();
+        return unitsDir.resolve(unit.name()).resolve(version.toString());
+    }
+
+    private static Stream<URL> collectClasspath(Path unitDir) {
+        if (Files.notExists(unitDir)) {
+            throw new IllegalArgumentException("Unit does not exist: " + 
unitDir);
+        }
+
+        if (!Files.isDirectory(unitDir)) {
+            throw new IllegalArgumentException("Unit is not a directory: " + 
unitDir);
+        }
+
+        // Construct the "class path" for this unit
+        try {
+            ClasspathCollector classpathCollector = new 
ClasspathCollector(unitDir);
+            Files.walkFileTree(unitDir, classpathCollector);
+            return classpathCollector.classpathAsStream();
+        } catch (IOException e) {
+            throw new IgniteException(
+                    Compute.CLASS_PATH_ERR,
+                    "Failed to construct classpath for job: " + unitDir,
+                    e
+            );
+        }
+    }
+
+    private static class ClasspathCollector extends SimpleFileVisitor<Path> {
+        private final List<URL> classpath = new ArrayList<>();
+
+        private ClasspathCollector(Path base) throws MalformedURLException {
+            classpath.add(base.toAbsolutePath().toUri().toURL());
+        }
+
+        @Override
+        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 
throws IOException {
+            if (Files.isDirectory(file) || file.toString().endsWith(".jar")) {
+                classpath.add(file.toAbsolutePath().toUri().toURL());
+            }
+            return FileVisitResult.CONTINUE;
+        }
+
+        Stream<URL> classpathAsStream() {
+            return classpath.stream();
+        }
+    }
+}
diff --git a/modules/compute/src/test/README.md 
b/modules/compute/src/test/README.md
new file mode 100644
index 0000000000..18966ac8ef
--- /dev/null
+++ b/modules/compute/src/test/README.md
@@ -0,0 +1,15 @@
+# ignite-compute
+
+## Unit tests
+`modules/compute/src/test/resources/units/test-units-1.0-SNAPSHOT-src.zip` 
contains a zip archive with a 
+test project which was used to create jars for 
`org.apache.ignite.internal.compute.JobClassLoaderFactoryTest` tests.
+
+[unit1-1.0-SNAPSHOT.jar](resources%2Funits%2Funit2%2F1.0.0%2Funit1-1.0-SNAPSHOT.jar)
 contains two classes 
+which are used in tests:
+* `org.apache.ignite.internal.compute.unit1.Unit1` - extends Runnable and 
returns 1 as Integer.
+* `org.my.job.compute.unit.Job1Utility`
+
+[unit2-1.0-SNAPSHOT.jar](resources%2Funits%2Funit2%2F2.0.0%2Funit2-1.0-SNAPSHOT.jar)
 contains two classes
+which are used in tests:
+* `org.apache.ignite.internal.compute.unit1.Unit2` - extends Runnable and 
returns "Hello World!" as String.
+* `org.my.job.compute.unit.Job2Utility`
diff --git 
a/modules/compute/src/test/java/org/apache/ignite/internal/compute/JobClassLoaderFactoryTest.java
 
b/modules/compute/src/test/java/org/apache/ignite/internal/compute/JobClassLoaderFactoryTest.java
new file mode 100644
index 0000000000..19cd57f2ef
--- /dev/null
+++ 
b/modules/compute/src/test/java/org/apache/ignite/internal/compute/JobClassLoaderFactoryTest.java
@@ -0,0 +1,262 @@
+/*
+ * 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.compute;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.doReturn;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.function.Function;
+import org.apache.ignite.compute.DeploymentUnit;
+import org.apache.ignite.compute.version.Version;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class JobClassLoaderFactoryTest {
+    private final Path units = 
Path.of(JobClassLoaderFactory.class.getClassLoader().getResource("units").getPath());
+
+    @Mock
+    private Function<String, Version> detectLastUnitVersion;
+
+    private JobClassLoaderFactory jobClassLoaderFactory;
+
+    @BeforeEach
+    void setUp() {
+        jobClassLoaderFactory = new JobClassLoaderFactory(units, 
detectLastUnitVersion);
+    }
+
+    @Test
+    @DisplayName("Load class with the same name from two different class 
loaders")
+    public void unit1() throws Exception {
+
+        // when unit1-1.0.0 is loaded  and unit2-1.0.0 is loaded
+        List<DeploymentUnit> units1 = List.of(new DeploymentUnit("unit1", 
Version.parseVersion("1.0.0")));
+        List<DeploymentUnit> units2 = List.of(new DeploymentUnit("unit2", 
Version.parseVersion("2.0.0")));
+
+        try (JobClassLoader classLoader1 = 
jobClassLoaderFactory.createClassLoader(units1);
+                JobClassLoader classLoader2 = 
jobClassLoaderFactory.createClassLoader(units2)) {
+            // then classes from the first unit are loaded from the first 
class loader
+            Class<?> clazz1 = 
classLoader1.loadClass("org.my.job.compute.unit.UnitJob");
+            Callable<Object> job1 = (Callable<Object>) 
clazz1.getDeclaredConstructor().newInstance();
+            Object result1 = job1.call();
+            assertSame(Integer.class, result1.getClass());
+            assertEquals(1, result1);
+
+            // and classes from the second unit are loaded from the second 
class loader
+            Class<?> clazz2 = 
classLoader2.loadClass("org.my.job.compute.unit.UnitJob");
+            Callable<Object> job2 = (Callable<Object>) 
clazz2.getDeclaredConstructor().newInstance();
+            Object result2 = job2.call();
+            assertSame(String.class, result2.getClass());
+            assertEquals("Hello world!", result2);
+        }
+    }
+
+    @Test
+    @DisplayName("Load unit with the LATEST version")
+    public void unit2LatestVersion() throws Exception {
+        
doReturn(Version.parseVersion("2.0.0")).when(detectLastUnitVersion).apply("unit2");
+
+        // when the version is LATEST
+        List<DeploymentUnit> units = List.of(new DeploymentUnit("unit2", 
Version.LATEST));
+
+        try (JobClassLoader classLoader = 
jobClassLoaderFactory.createClassLoader(units)) {
+            // the the unit with the highest version is loaded
+            Class<?> clazz = 
classLoader.loadClass("org.my.job.compute.unit.UnitJob");
+            Callable<Object> job = (Callable<Object>) 
clazz.getDeclaredConstructor().newInstance();
+            Object result = job.call();
+            assertSame(String.class, result.getClass());
+            assertEquals("Hello world!", result);
+
+            // and the unit with the lower version is not loaded
+            assertThrows(ClassNotFoundException.class, () -> 
classLoader.loadClass("org.my.job.compute.unit.Job1Utility"));
+        }
+    }
+
+    @Test
+    @DisplayName("Load both versions of the unit")
+    public void unit1BothVersions() throws Exception {
+        List<DeploymentUnit> units = List.of(
+                new DeploymentUnit("unit1", Version.parseVersion("1.0.0")),
+                new DeploymentUnit("unit1", Version.parseVersion("2.0.0"))
+        );
+
+        try (JobClassLoader classLoader = 
jobClassLoaderFactory.createClassLoader(units)) {
+            Class<?> unitJobClass = 
classLoader.loadClass("org.my.job.compute.unit.UnitJob");
+            assertNotNull(unitJobClass);
+
+            // and classes are loaded in the aplhabetical order
+            Callable<Object> job1 = (Callable<Object>) 
unitJobClass.getDeclaredConstructor().newInstance();
+            Object result1 = job1.call();
+            assertSame(Integer.class, result1.getClass());
+            assertEquals(1, result1);
+
+            Class<?> job1UtilityClass = 
classLoader.loadClass("org.my.job.compute.unit.Job1Utility");
+            assertNotNull(job1UtilityClass);
+
+            Class<?> job2UtilityClass = 
classLoader.loadClass("org.my.job.compute.unit.Job2Utility");
+            assertNotNull(job2UtilityClass);
+
+            // classes from the different units are loaded from the same class 
loader
+            assertSame(job1UtilityClass.getClassLoader(), 
job2UtilityClass.getClassLoader());
+        }
+    }
+
+    @Test
+    @DisplayName("Unit with multiple jars")
+    public void unit1_3_0_1() throws Exception {
+
+        // when unit with multiple jars is loaded
+        List<DeploymentUnit> units = List.of(
+                new DeploymentUnit("unit1", Version.parseVersion("3.0.1"))
+        );
+
+        // then class from all jars are loaded
+        try (JobClassLoader classLoader = 
jobClassLoaderFactory.createClassLoader(units)) {
+            Class<?> unitJobClass = 
classLoader.loadClass("org.my.job.compute.unit.UnitJob");
+            assertNotNull(unitJobClass);
+
+            Class<?> job1UtilityClass = 
classLoader.loadClass("org.my.job.compute.unit.Job1Utility");
+            assertNotNull(job1UtilityClass);
+
+            Class<?> job2UtilityClass = 
classLoader.loadClass("org.my.job.compute.unit.Job2Utility");
+            assertNotNull(job2UtilityClass);
+        }
+    }
+
+    @Test
+    @DisplayName("Unit with multiple jars")
+    public void unit1_3_0_2() throws Exception {
+
+        // when unit with multiple jars is loaded
+        List<DeploymentUnit> units = List.of(
+                new DeploymentUnit("unit1", Version.parseVersion("3.0.2"))
+        );
+
+        // then class from all jars are loaded
+        try (JobClassLoader classLoader = 
jobClassLoaderFactory.createClassLoader(units)) {
+            Class<?> unitJobClass = 
classLoader.loadClass("org.my.job.compute.unit.UnitJob");
+            assertNotNull(unitJobClass);
+
+            Class<?> job1UtilityClass = 
classLoader.loadClass("org.my.job.compute.unit.Job1Utility");
+            assertNotNull(job1UtilityClass);
+
+            Class<?> job2UtilityClass = 
classLoader.loadClass("org.my.job.compute.unit.Job2Utility");
+            assertNotNull(job2UtilityClass);
+        }
+    }
+
+    @Test
+    @DisplayName("Corrupted unit")
+    public void unit1_4_0_0() throws IOException {
+
+        // when unit with corrupted jar is loaded
+        List<DeploymentUnit> units = List.of(
+                new DeploymentUnit("unit1", Version.parseVersion("4.0.0"))
+        );
+
+        // then class loader throws an exception
+        try (JobClassLoader classLoader = 
jobClassLoaderFactory.createClassLoader(units)) {
+            assertThrows(ClassNotFoundException.class, () -> 
classLoader.loadClass("org.my.job.compute.unit.UnitJob"));
+        }
+    }
+
+    @Test
+    @DisplayName("Load resource from unit directory")
+    public void unit1_5_0_0() throws IOException {
+
+        String resourcePath = JobClassLoaderFactoryTest.class.getClassLoader()
+                .getResource("units/unit1/5.0.0/test.txt")
+                .getPath();
+        String expectedContent = Files.readString(Path.of(resourcePath));
+
+        // when unit with files is loaded
+        List<DeploymentUnit> units = List.of(
+                new DeploymentUnit("unit1", Version.parseVersion("5.0.0"))
+        );
+
+        // then the files are accessible
+        try (JobClassLoader classLoader = 
jobClassLoaderFactory.createClassLoader(units)) {
+            String resource = 
Files.readString(Path.of(classLoader.getResource("test.txt").getPath()));
+            String subDirResource = 
Files.readString(Path.of(classLoader.getResource("subdir/test.txt").getPath()));
+            assertEquals(expectedContent, resource);
+            assertEquals(expectedContent, subDirResource);
+
+            try (InputStream resourceAsStream = 
classLoader.getResourceAsStream("test.txt");
+                    InputStream subDirResourceAsStream = 
classLoader.getResourceAsStream("subdir/test.txt")) {
+                String resourceStreamString = new 
String(resourceAsStream.readAllBytes());
+                String subDirResourceStreamString = new 
String(subDirResourceAsStream.readAllBytes());
+
+                assertEquals(expectedContent, resourceStreamString);
+                assertEquals(expectedContent, subDirResourceStreamString);
+            }
+        }
+    }
+
+    @Test
+    @DisplayName("Create class loader with empty units")
+    public void emptyUnits() {
+        IllegalArgumentException exception = assertThrows(
+                IllegalArgumentException.class,
+                () -> jobClassLoaderFactory.createClassLoader(List.of())
+        );
+
+        assertThat(exception.getMessage(), containsString("At least one unit 
must be specified"));
+    }
+
+    @Test
+    @DisplayName("Create class loader with non-existing unit")
+    public void nonExistingUnit() {
+        List<DeploymentUnit> units = List.of(
+                new DeploymentUnit("non-existing", 
Version.parseVersion("1.0.0"))
+        );
+        IllegalArgumentException exception = assertThrows(
+                IllegalArgumentException.class,
+                () -> jobClassLoaderFactory.createClassLoader(units)
+        );
+        assertThat(exception.getMessage(), containsString("Unit does not 
exist:"));
+    }
+
+    @Test
+    @DisplayName("Create class loader with non-existing version")
+    public void nonExistingVersion() {
+        List<DeploymentUnit> units = List.of(
+                new DeploymentUnit("unit1", Version.parseVersion("-1.0.0"))
+        );
+        IllegalArgumentException exception = assertThrows(
+                IllegalArgumentException.class,
+                () -> jobClassLoaderFactory.createClassLoader(units)
+        );
+
+        assertThat(exception.getMessage(), containsString("Unit does not 
exist:"));
+    }
+}
diff --git 
a/modules/compute/src/test/java/org/apache/ignite/internal/compute/JobClassLoaderTest.java
 
b/modules/compute/src/test/java/org/apache/ignite/internal/compute/JobClassLoaderTest.java
new file mode 100644
index 0000000000..a1bed9da36
--- /dev/null
+++ 
b/modules/compute/src/test/java/org/apache/ignite/internal/compute/JobClassLoaderTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.compute;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.net.URL;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class JobClassLoaderTest {
+
+    @Mock
+    private ClassLoader parentClassLoader;
+
+    private static Stream<Arguments> testArguments() {
+        return Stream.of(
+                Arguments.of("java.lang.String", String.class),
+                Arguments.of("javax.lang.String", String.class),
+                
Arguments.of("org.apache.ignite.internal.compute.JobExecutionContextImpl", 
JobExecutionContextImpl.class)
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("testArguments")
+    public void loadsSystemClassesFirst(String className, Class<?> 
desiredClass) throws Exception {
+
+        doReturn(desiredClass).when(parentClassLoader).loadClass(className);
+
+        try (TestJobClassLoader jobClassLoader = spy(new 
TestJobClassLoader(new URL[0], parentClassLoader))) {
+            assertSame(desiredClass, jobClassLoader.loadClass(className));
+            verify(jobClassLoader, never()).findClass(className);
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = {"org.apache.ignite.compute.unit1.UnitJob", 
"java.lang.String", "javax.lang.String"})
+    public void loadsOwnClassIfSystemAbsent(String className) throws Exception 
{
+        
doThrow(ClassNotFoundException.class).when(parentClassLoader).loadClass(className);
+
+        try (TestJobClassLoader jobClassLoader = spy(new 
TestJobClassLoader(new URL[0], parentClassLoader))) {
+            Class<TestJobClassLoader> toBeReturned = TestJobClassLoader.class;
+            doReturn(toBeReturned).when(jobClassLoader).findClass(className);
+
+            assertSame(toBeReturned, jobClassLoader.loadClass(className));
+            verify(parentClassLoader, times(1)).loadClass(className);
+        }
+    }
+
+    private static class TestJobClassLoader extends JobClassLoader {
+        TestJobClassLoader(URL[] urls, ClassLoader parent) {
+            super(urls, parent);
+        }
+
+        @Override
+        public Class<?> findClass(String name) throws ClassNotFoundException {
+            return super.findClass(name);
+        }
+    }
+}
diff --git 
a/modules/compute/src/test/resources/units/test-units-1.0-SNAPSHOT-src.zip 
b/modules/compute/src/test/resources/units/test-units-1.0-SNAPSHOT-src.zip
new file mode 100644
index 0000000000..53502ab4e5
Binary files /dev/null and 
b/modules/compute/src/test/resources/units/test-units-1.0-SNAPSHOT-src.zip 
differ
diff --git 
a/modules/compute/src/test/resources/units/unit1/1.0.0/unit1-1.0-SNAPSHOT.jar 
b/modules/compute/src/test/resources/units/unit1/1.0.0/unit1-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000000..88dd25aa3b
Binary files /dev/null and 
b/modules/compute/src/test/resources/units/unit1/1.0.0/unit1-1.0-SNAPSHOT.jar 
differ
diff --git 
a/modules/compute/src/test/resources/units/unit1/2.0.0/unit2-1.0-SNAPSHOT.jar 
b/modules/compute/src/test/resources/units/unit1/2.0.0/unit2-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000000..391c5b6964
Binary files /dev/null and 
b/modules/compute/src/test/resources/units/unit1/2.0.0/unit2-1.0-SNAPSHOT.jar 
differ
diff --git 
a/modules/compute/src/test/resources/units/unit1/3.0.1/unit1-1.0-SNAPSHOT.jar 
b/modules/compute/src/test/resources/units/unit1/3.0.1/unit1-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000000..88dd25aa3b
Binary files /dev/null and 
b/modules/compute/src/test/resources/units/unit1/3.0.1/unit1-1.0-SNAPSHOT.jar 
differ
diff --git 
a/modules/compute/src/test/resources/units/unit1/3.0.1/unit2-1.0-SNAPSHOT.jar 
b/modules/compute/src/test/resources/units/unit1/3.0.1/unit2-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000000..391c5b6964
Binary files /dev/null and 
b/modules/compute/src/test/resources/units/unit1/3.0.1/unit2-1.0-SNAPSHOT.jar 
differ
diff --git 
a/modules/compute/src/test/resources/units/unit1/3.0.2/subdir/unit2-1.0-SNAPSHOT.jar
 
b/modules/compute/src/test/resources/units/unit1/3.0.2/subdir/unit2-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000000..391c5b6964
Binary files /dev/null and 
b/modules/compute/src/test/resources/units/unit1/3.0.2/subdir/unit2-1.0-SNAPSHOT.jar
 differ
diff --git 
a/modules/compute/src/test/resources/units/unit1/3.0.2/unit1-1.0-SNAPSHOT.jar 
b/modules/compute/src/test/resources/units/unit1/3.0.2/unit1-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000000..88dd25aa3b
Binary files /dev/null and 
b/modules/compute/src/test/resources/units/unit1/3.0.2/unit1-1.0-SNAPSHOT.jar 
differ
diff --git 
a/modules/compute/src/test/resources/units/unit1/4.0.0/unit1-1.0-corrupted.jar 
b/modules/compute/src/test/resources/units/unit1/4.0.0/unit1-1.0-corrupted.jar
new file mode 100644
index 0000000000..5cb9c2c3e5
Binary files /dev/null and 
b/modules/compute/src/test/resources/units/unit1/4.0.0/unit1-1.0-corrupted.jar 
differ
diff --git 
a/modules/compute/src/test/resources/units/unit1/5.0.0/subdir/test.txt 
b/modules/compute/src/test/resources/units/unit1/5.0.0/subdir/test.txt
new file mode 100644
index 0000000000..f1fe480ba4
--- /dev/null
+++ b/modules/compute/src/test/resources/units/unit1/5.0.0/subdir/test.txt
@@ -0,0 +1 @@
+test resource
diff --git a/modules/compute/src/test/resources/units/unit1/5.0.0/test.txt 
b/modules/compute/src/test/resources/units/unit1/5.0.0/test.txt
new file mode 100644
index 0000000000..f1fe480ba4
--- /dev/null
+++ b/modules/compute/src/test/resources/units/unit1/5.0.0/test.txt
@@ -0,0 +1 @@
+test resource
diff --git 
a/modules/compute/src/test/resources/units/unit2/1.0.0/unit1-1.0-SNAPSHOT.jar 
b/modules/compute/src/test/resources/units/unit2/1.0.0/unit1-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000000..88dd25aa3b
Binary files /dev/null and 
b/modules/compute/src/test/resources/units/unit2/1.0.0/unit1-1.0-SNAPSHOT.jar 
differ
diff --git 
a/modules/compute/src/test/resources/units/unit2/2.0.0/unit2-1.0-SNAPSHOT.jar 
b/modules/compute/src/test/resources/units/unit2/2.0.0/unit2-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000000..391c5b6964
Binary files /dev/null and 
b/modules/compute/src/test/resources/units/unit2/2.0.0/unit2-1.0-SNAPSHOT.jar 
differ
diff --git a/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroups.java 
b/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroups.java
index 32fb3285e9..f6ccf08c6d 100755
--- a/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroups.java
+++ b/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroups.java
@@ -404,4 +404,18 @@ public class ErrorGroups {
         /** General authentication error. */
         public static final int COMMON_AUTHENTICATION_ERR = 
AUTHENTICATION_ERR_GROUP.registerErrorCode(1);
     }
+
+    /**
+     * Compute error group.
+     */
+    public static class Compute {
+        /** Compute error group. */
+        public static final ErrorGroup COMPUTE_ERR_GROUP = 
ErrorGroup.newGroup("COMPUTE", 16);
+
+        /** Classpath error. */
+        public static final int CLASS_PATH_ERR = 
COMPUTE_ERR_GROUP.registerErrorCode(1);
+
+        /** Class loader error. */
+        public static final int CLASS_LOADER_ERR = 
COMPUTE_ERR_GROUP.registerErrorCode(2);
+    }
 }
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
index 4c291e589c..550aaada6a 100644
--- 
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
@@ -25,11 +25,10 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
+import org.apache.ignite.compute.version.Version;
 import org.apache.ignite.internal.deployunit.DeploymentUnit;
 import org.apache.ignite.internal.deployunit.IgniteDeployment;
 import org.apache.ignite.internal.deployunit.UnitStatuses;
-import org.apache.ignite.internal.deployunit.version.UnitVersion;
-import org.apache.ignite.internal.deployunit.version.Version;
 import org.apache.ignite.internal.rest.api.deployment.DeploymentCodeApi;
 import org.apache.ignite.internal.rest.api.deployment.DeploymentStatus;
 import org.apache.ignite.internal.rest.api.deployment.UnitStatus;
@@ -50,12 +49,12 @@ public class DeploymentManagementController implements 
DeploymentCodeApi {
     public CompletableFuture<Boolean> deploy(String unitId, String 
unitVersion, Publisher<CompletedFileUpload> unitContent) {
         CompletableFuture<DeploymentUnit> result = new CompletableFuture<>();
         unitContent.subscribe(new CompletedFileUploadSubscriber(result));
-        return result.thenCompose(deploymentUnit -> 
deployment.deployAsync(unitId, UnitVersion.parse(unitVersion), deploymentUnit));
+        return result.thenCompose(deploymentUnit -> 
deployment.deployAsync(unitId, Version.parseVersion(unitVersion), 
deploymentUnit));
     }
 
     @Override
     public CompletableFuture<Boolean> undeploy(String unitId, String 
unitVersion) {
-        return deployment.undeployAsync(unitId, 
UnitVersion.parse(unitVersion));
+        return deployment.undeployAsync(unitId, 
Version.parseVersion(unitVersion));
     }
 
     @Override
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
index 04afdacd4b..195ca6617c 100644
--- 
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
@@ -22,7 +22,7 @@ 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.version.VersionParseException;
+import org.apache.ignite.compute.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;
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/deployment/ItDeploymentUnitTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/deployment/ItDeploymentUnitTest.java
index ce630ec4d4..26392e8b52 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/deployment/ItDeploymentUnitTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/deployment/ItDeploymentUnitTest.java
@@ -43,6 +43,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
+import org.apache.ignite.compute.version.Version;
 import org.apache.ignite.internal.ClusterPerTestIntegrationTest;
 import org.apache.ignite.internal.app.IgniteImpl;
 import org.apache.ignite.internal.deployunit.DeploymentUnit;
@@ -51,7 +52,6 @@ import org.apache.ignite.internal.deployunit.UnitStatuses;
 import org.apache.ignite.internal.deployunit.UnitStatuses.UnitStatusesBuilder;
 import 
org.apache.ignite.internal.deployunit.configuration.DeploymentConfiguration;
 import 
org.apache.ignite.internal.deployunit.exception.DeploymentUnitNotFoundException;
-import org.apache.ignite.internal.deployunit.version.Version;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 

Reply via email to