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 8b28bd1dcf IGNITE-19021 Support the directory deployment (#1931)
8b28bd1dcf is described below
commit 8b28bd1dcfe85e80abcd57ee6fb834594f366cde
Author: Vadim Pakhnushev <[email protected]>
AuthorDate: Wed Apr 26 10:49:47 2023 +0300
IGNITE-19021 Support the directory deployment (#1931)
---
.../cli/commands/unit/ItDeploymentUnitTest.java | 37 ++++--
.../internal/rest/ItGeneratedRestClientTest.java | 22 ++--
.../internal/cli/call/unit/DeployUnitCall.java | 68 ++++++-----
.../internal/cli/call/unit/DeployUnitClient.java | 100 ++++++++++++++++
.../cli/commands/unit/UnitDeployCommand.java | 2 +-
.../deployunit/DeployMessagingService.java | 8 +-
.../internal/deployunit/DeploymentManagerImpl.java | 45 +++++---
.../ignite/internal/deployunit/DeploymentUnit.java | 15 +--
.../internal/deployunit/FileDeployerService.java | 25 ++--
.../ignite/internal/deployunit/UnitMeta.java | 20 ++--
.../deployunit/key/UnitMetaSerializer.java | 64 +++++++----
.../deployunit/message/DeployUnitRequest.java | 15 +--
.../ignite/deployment/UnitMetaSerializerTest.java | 71 +++++-------
modules/rest-api/openapi/openapi.yaml | 6 +-
.../rest/api/deployment/DeploymentCodeApi.java | 4 +-
.../deployment/CompletedFileUploadSubscriber.java | 81 +++++++++++++
.../deployment/DeploymentManagementController.java | 40 ++-----
.../internal/deployment/ItDeploymentUnitTest.java | 128 ++++++++++++++-------
18 files changed, 504 insertions(+), 247 deletions(-)
diff --git
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/unit/ItDeploymentUnitTest.java
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/unit/ItDeploymentUnitTest.java
index 46549068fa..70868903de 100644
---
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/unit/ItDeploymentUnitTest.java
+++
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/unit/ItDeploymentUnitTest.java
@@ -21,8 +21,9 @@ import static org.junit.jupiter.api.Assertions.assertAll;
import java.io.IOException;
import java.nio.file.Files;
+import java.nio.file.Path;
import
org.apache.ignite.internal.cli.commands.CliCommandTestInitializedIntegrationBase;
-import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -31,18 +32,22 @@ import org.junit.jupiter.api.Test;
@Disabled("https://issues.apache.org/jira/browse/IGNITE-19139")
public class ItDeploymentUnitTest extends
CliCommandTestInitializedIntegrationBase {
- private String path;
+ private String testFile;
- @BeforeEach
- void setUp() throws IOException {
- path = Files.createTempFile(WORK_DIR, "test",
"txt").toAbsolutePath().toString();
+ private Path testDirectory;
+
+ @BeforeAll
+ void beforeAll() throws IOException {
+ testDirectory = Files.createDirectory(WORK_DIR.resolve("test"));
+ testFile =
Files.createFile(testDirectory.resolve("test.txt")).toString();
+ Files.createFile(testDirectory.resolve("test2.txt"));
}
@Test
@DisplayName("Should deploy a unit with version")
void deploy() {
// When deploy with version
- execute("unit", "deploy", "test.unit.id.1", "--version", "1.0.0",
"--path", path);
+ execute("unit", "deploy", "test.unit.id.1", "--version", "1.0.0",
"--path", testFile);
// Then
assertAll(
@@ -56,7 +61,7 @@ public class ItDeploymentUnitTest extends
CliCommandTestInitializedIntegrationBa
@DisplayName("Should deploy a unit without version")
void deployWithoutVersion() {
// When deploy without version
- execute("unit", "deploy", "test.unit.id.2", "--path", path);
+ execute("unit", "deploy", "test.unit.id.2", "--path", testFile);
// Then
assertAll(
@@ -70,7 +75,7 @@ public class ItDeploymentUnitTest extends
CliCommandTestInitializedIntegrationBa
@DisplayName("Should undeploy a unit with version")
void undeploy() {
// When deploy
- execute("unit", "deploy", "test.unit.id.3", "--version", "1.0.0",
"--path", path);
+ execute("unit", "deploy", "test.unit.id.3", "--version", "1.0.0",
"--path", testFile);
// And undeploy
execute("unit", "undeploy", "test.unit.id.3", "--version", "1.0.0");
@@ -100,7 +105,7 @@ public class ItDeploymentUnitTest extends
CliCommandTestInitializedIntegrationBa
@DisplayName("Should display correct status after deploy")
void deployAndStatusCheck() {
// When undeploy non-existing unit
- execute("unit", "deploy", "test.unit.id.5", "--version", "1.0.0",
"--path", path);
+ execute("unit", "deploy", "test.unit.id.5", "--version", "1.0.0",
"--path", testFile);
// Then
assertAll(
@@ -118,4 +123,18 @@ public class ItDeploymentUnitTest extends
CliCommandTestInitializedIntegrationBa
() -> assertOutputContains("DEPLOYED")
);
}
+
+ @Test
+ @DisplayName("Should deploy a unit from directory")
+ void deployDirectory() {
+ // When deploy with version
+ execute("unit", "deploy", "test.unit.id.5", "--path",
testDirectory.toString());
+
+ // Then
+ assertAll(
+ this::assertExitCodeIsZero,
+ this::assertErrOutputIsEmpty,
+ () -> assertOutputContains("Done")
+ );
+ }
}
diff --git
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/rest/ItGeneratedRestClientTest.java
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/rest/ItGeneratedRestClientTest.java
index ec9a4bb4a9..f641c0af03 100644
---
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/rest/ItGeneratedRestClientTest.java
+++
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/rest/ItGeneratedRestClientTest.java
@@ -51,6 +51,7 @@ 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.cli.call.unit.DeployUnitClient;
import org.apache.ignite.internal.cli.core.rest.ApiClientFactory;
import org.apache.ignite.internal.testframework.TestIgnitionManager;
import org.apache.ignite.internal.testframework.WorkDirectory;
@@ -100,6 +101,8 @@ public class ItGeneratedRestClientTest {
private final List<Ignite> clusterNodes = new ArrayList<>();
+ private ApiClient apiClient;
+
private ClusterConfigurationApi clusterConfigurationApi;
private NodeConfigurationApi nodeConfigurationApi;
@@ -157,15 +160,15 @@ public class ItGeneratedRestClientTest {
firstNodeName = clusterNodes.get(0).name();
- ApiClient client = clientFactory.getClient("http://localhost:" +
BASE_REST_PORT);
+ apiClient = clientFactory.getClient("http://localhost:" +
BASE_REST_PORT);
- clusterConfigurationApi = new ClusterConfigurationApi(client);
- nodeConfigurationApi = new NodeConfigurationApi(client);
- clusterManagementApi = new ClusterManagementApi(client);
- nodeManagementApi = new NodeManagementApi(client);
- topologyApi = new TopologyApi(client);
- nodeMetricApi = new NodeMetricApi(client);
- deploymentApi = new DeploymentApi(client);
+ clusterConfigurationApi = new ClusterConfigurationApi(apiClient);
+ nodeConfigurationApi = new NodeConfigurationApi(apiClient);
+ clusterManagementApi = new ClusterManagementApi(apiClient);
+ nodeManagementApi = new NodeManagementApi(apiClient);
+ topologyApi = new TopologyApi(apiClient);
+ nodeMetricApi = new NodeMetricApi(apiClient);
+ deploymentApi = new DeploymentApi(apiClient);
objectMapper = new ObjectMapper();
}
@@ -387,7 +390,8 @@ public class ItGeneratedRestClientTest {
void deployUndeployUnitSync() throws ApiException {
assertThat(deploymentApi.units(), empty());
- deploymentApi.deployUnit("test.unit.id", emptyFile(), "1.0.0");
+ // TODO https://issues.apache.org/jira/browse/IGNITE-19295
+ new DeployUnitClient(apiClient).deployUnit("test.unit.id",
List.of(emptyFile()), "1.0.0");
List<UnitStatus> units = deploymentApi.units();
assertThat(units, hasSize(1));
assertThat(units.get(0).getId(), equalTo("test.unit.id"));
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/unit/DeployUnitCall.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/unit/DeployUnitCall.java
index 8d8a94547a..41d38ca5e8 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/unit/DeployUnitCall.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/unit/DeployUnitCall.java
@@ -21,7 +21,13 @@ import static
java.util.concurrent.CompletableFuture.completedFuture;
import java.io.File;
import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import okhttp3.Call;
import org.apache.ignite.internal.cli.core.call.AsyncCall;
import org.apache.ignite.internal.cli.core.call.CallOutput;
@@ -33,7 +39,7 @@ import
org.apache.ignite.internal.cli.core.repl.registry.UnitsRegistry;
import org.apache.ignite.internal.cli.core.rest.ApiClientFactory;
import org.apache.ignite.internal.cli.core.style.component.MessageUiComponent;
import org.apache.ignite.internal.cli.core.style.element.UiElements;
-import org.apache.ignite.rest.client.api.DeploymentApi;
+import org.apache.ignite.rest.client.invoker.ApiClient;
import org.apache.ignite.rest.client.invoker.ApiException;
/** Call to deploy a unit. */
@@ -53,36 +59,42 @@ public class DeployUnitCall implements
AsyncCall<DeployUnitCallInput, String> {
@Override
public CompletableFuture<CallOutput<String>> execute(DeployUnitCallInput
input) {
- try {
- DeploymentApi api = new
DeploymentApi(clientFactory.getClient(input.clusterUrl()));
+ ApiClient apiClient = clientFactory.getClient(input.clusterUrl());
+ DeployUnitClient api = new DeployUnitClient(apiClient);
- File file = input.path().toFile();
- if (!file.exists()) {
- return completedFuture(DefaultCallOutput.failure(new
FileNotFoundException(input.path().toString())));
- }
+ Path path = input.path();
+ if (Files.notExists(path)) {
+ return completedFuture(DefaultCallOutput.failure(new
FileNotFoundException(path.toString())));
+ }
+ List<File> files;
+ try (Stream<Path> stream = Files.walk(path, 1)) {
+ files = stream
+ .filter(Files::isRegularFile)
+ .map(Path::toFile)
+ .collect(Collectors.toList());
+ } catch (IOException e) {
+ return completedFuture(DefaultCallOutput.failure(e));
+ }
- TrackingCallback<Boolean> callback = new
TrackingCallback<>(tracker);
- String ver = input.version() == null ? "" : input.version();
- Call call = api.deployUnitAsync(input.id(), file, ver, callback);
+ TrackingCallback<Boolean> callback = new TrackingCallback<>(tracker);
+ String ver = input.version() == null ? "" : input.version();
+ Call call = api.deployUnitAsync(input.id(), files, ver, callback);
- return CompletableFuture.supplyAsync(() -> {
- try {
- callback.awaitDone();
- } catch (InterruptedException e) {
- return DefaultCallOutput.failure(e);
- }
- if (call.isCanceled()) {
- return DefaultCallOutput.failure(new
RuntimeException("Unit deployment process was canceled"));
- } else if (callback.exception() != null) {
- return handleException(callback.exception(), input);
- } else {
- unitsRegistry.refresh();
- return
DefaultCallOutput.success(MessageUiComponent.from(UiElements.done()).render());
- }
- });
- } catch (ApiException e) {
- return completedFuture(DefaultCallOutput.failure(new
IgniteCliApiException(e, input.clusterUrl())));
- }
+ return CompletableFuture.supplyAsync(() -> {
+ try {
+ callback.awaitDone();
+ } catch (InterruptedException e) {
+ return DefaultCallOutput.failure(e);
+ }
+ if (call.isCanceled()) {
+ return DefaultCallOutput.failure(new RuntimeException("Unit
deployment process was canceled"));
+ } else if (callback.exception() != null) {
+ return handleException(callback.exception(), input);
+ } else {
+ unitsRegistry.refresh();
+ return
DefaultCallOutput.success(MessageUiComponent.from(UiElements.done()).render());
+ }
+ });
}
private static CallOutput<String> handleException(Exception exception,
DeployUnitCallInput input) {
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/unit/DeployUnitClient.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/unit/DeployUnitClient.java
new file mode 100644
index 0000000000..ecf2886e13
--- /dev/null
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/unit/DeployUnitClient.java
@@ -0,0 +1,100 @@
+/*
+ * 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.cli.call.unit;
+
+import java.io.File;
+import java.util.List;
+import okhttp3.Call;
+import okhttp3.MediaType;
+import okhttp3.MultipartBody;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import org.apache.ignite.rest.client.invoker.ApiCallback;
+import org.apache.ignite.rest.client.invoker.ApiClient;
+import org.apache.ignite.rest.client.invoker.ApiException;
+import org.apache.ignite.rest.client.invoker.ApiResponse;
+import org.apache.ignite.rest.client.invoker.ProgressRequestBody;
+
+/**
+ * Temporary class for calling REST with list of files until underlying issue
in the openapi-generator is fixed.
+ * TODO https://issues.apache.org/jira/browse/IGNITE-19295
+ */
+public class DeployUnitClient {
+ private final ApiClient apiClient;
+
+ public DeployUnitClient(ApiClient apiClient) {
+ this.apiClient = apiClient;
+ }
+
+ /**
+ * Deploy unit.
+ *
+ * @param unitId The ID of the deployment unit.
+ * @param unitContent The code to deploy.
+ * @param unitVersion The version of the deployment unit.
+ * @return {@code true} if the call succeeded.
+ * @throws ApiException if fail to call.
+ */
+ public Boolean deployUnit(String unitId, List<File> unitContent, String
unitVersion) throws ApiException {
+ Call call = deployUnitCall(unitId, unitContent, unitVersion, null);
+ ApiResponse<Boolean> response = apiClient.execute(call, Boolean.class);
+ return response.getData();
+ }
+
+ /**
+ * Deploy unit asynchronously.
+ *
+ * @param unitId The ID of the deployment unit.
+ * @param unitContent The code to deploy.
+ * @param unitVersion The version of the deployment unit.
+ * @return Request call.
+ */
+ public Call deployUnitAsync(String unitId, List<File> unitContent, String
unitVersion, ApiCallback<Boolean> callback) {
+ Call call = deployUnitCall(unitId, unitContent, unitVersion, callback);
+ apiClient.executeAsync(call, Boolean.class, callback);
+ return call;
+ }
+
+ private Call deployUnitCall(String unitId, List<File> unitContent, String
unitVersion, ApiCallback<Boolean> callback) {
+ String url = apiClient.getBasePath() +
"/management/v1/deployment/units";
+
+ MultipartBody.Builder mpBuilder = new
MultipartBody.Builder().setType(MultipartBody.FORM);
+ mpBuilder.addFormDataPart("unitId", unitId);
+ mpBuilder.addFormDataPart("unitVersion", unitVersion);
+ for (File file : unitContent) {
+ RequestBody requestBody = RequestBody.create(file,
MediaType.parse("application/octet-stream"));
+ mpBuilder.addFormDataPart("unitContent", file.getName(),
requestBody);
+ }
+ MultipartBody body = mpBuilder.build();
+
+ Request.Builder reqBuilder = new Request.Builder()
+ .url(url)
+ .header("Accept", "application/json")
+ .header("Content-Type", "multipart/form-data");
+
+ if (callback != null) {
+ ProgressRequestBody progressRequestBody = new
ProgressRequestBody(body, callback);
+ reqBuilder.tag(callback)
+ .post(progressRequestBody);
+ } else {
+ reqBuilder.post(body);
+ }
+
+ return apiClient.getHttpClient().newCall(reqBuilder.build());
+ }
+}
diff --git
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/unit/UnitDeployCommand.java
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/unit/UnitDeployCommand.java
index 2ebf35d163..282be0873a 100644
---
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/unit/UnitDeployCommand.java
+++
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/unit/UnitDeployCommand.java
@@ -42,7 +42,7 @@ import picocli.CommandLine.ParameterException;
import picocli.CommandLine.Parameters;
/** Command to deploy a unit. */
-@Command(name = "deploy", description = "Deploys a unit")
+@Command(name = "deploy", description = "Deploys a unit from file or a
directory (non-recursively)")
public class UnitDeployCommand extends BaseCommand implements
Callable<Integer> {
@Mixin
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 7461d2772c..0f173c4377 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
@@ -112,13 +112,11 @@ public class DeployMessagingService {
*
* @param id Deployment unit identifier.
* @param version Deployment unit version.
- * @param unitName Deployment unit file name.
- * @param unitContent Deployment unit file content.
+ * @param unitContent Deployment unit file names and content.
* @return Future with deployment result.
*/
- public CompletableFuture<List<String>> startDeployAsyncToCmg(String id,
Version version, String unitName, byte[] unitContent) {
+ public CompletableFuture<List<String>> startDeployAsyncToCmg(String id,
Version version, Map<String, byte[]> unitContent) {
DeployUnitRequest request = DeployUnitRequestImpl.builder()
- .unitName(unitName)
.id(id)
.version(version.render())
.unitContent(unitContent)
@@ -214,7 +212,7 @@ public class DeployMessagingService {
String id = executeRequest.id();
String version = executeRequest.version();
tracker.track(id, Version.parseVersion(version),
- deployerService.deploy(id, version, executeRequest.unitName(),
executeRequest.unitContent())
+ deployerService.deploy(id, version,
executeRequest.unitContent())
.thenCompose(success ->
clusterService.messagingService().respond(
senderConsistentId,
DEPLOYMENT_CHANNEL,
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 c1cd919bbe..91c4289dc4 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
@@ -18,17 +18,23 @@
package org.apache.ignite.internal.deployunit;
import static java.util.concurrent.CompletableFuture.allOf;
+import static java.util.concurrent.CompletableFuture.completedFuture;
+import static java.util.concurrent.CompletableFuture.failedFuture;
import static
org.apache.ignite.internal.rest.api.deployment.DeploymentStatus.DEPLOYED;
import static
org.apache.ignite.internal.rest.api.deployment.DeploymentStatus.OBSOLETE;
import static
org.apache.ignite.internal.rest.api.deployment.DeploymentStatus.REMOVING;
import static
org.apache.ignite.internal.rest.api.deployment.DeploymentStatus.UPLOADING;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
import
org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
import
org.apache.ignite.internal.deployunit.configuration.DeploymentConfiguration;
import
org.apache.ignite.internal.deployunit.exception.DeploymentUnitAlreadyExistsException;
@@ -122,31 +128,31 @@ public class DeploymentManagerImpl implements
IgniteDeployment {
Objects.requireNonNull(version);
Objects.requireNonNull(deploymentUnit);
- UnitMeta meta = new UnitMeta(id, version, deploymentUnit.name(),
UPLOADING, Collections.emptyList());
-
- byte[] unitContent;
- try {
- unitContent = deploymentUnit.content().readAllBytes();
- } catch (IOException e) {
- LOG.error("Error to read deployment unit content", e);
- return CompletableFuture.failedFuture(new
DeploymentUnitReadException(e));
- }
+ List<String> fileNames =
List.copyOf(deploymentUnit.content().keySet());
+ UnitMeta meta = new UnitMeta(id, version, fileNames, UPLOADING,
Collections.emptyList());
return metastore.putIfNotExist(id, version, meta)
.thenCompose(success -> {
if (success) {
- return tracker.track(id, version, deployer.deploy(id,
version.render(), deploymentUnit.name(), unitContent)
+ Map<String, byte[]> unitContent;
+ try {
+ unitContent =
deploymentUnit.content().entrySet().stream()
+ .collect(Collectors.toMap(Entry::getKey,
entry -> readContent(entry.getValue())));
+ } catch (DeploymentUnitReadException e) {
+ return failedFuture(e);
+ }
+ return tracker.track(id, version, deployer.deploy(id,
version.render(), unitContent)
.thenCompose(deployed -> {
if (deployed) {
return metastore.updateMeta(id,
version,
unitMeta -> unitMeta
.addConsistentId(clusterService.topologyService().localMember().name()));
}
- return
CompletableFuture.completedFuture(false);
+ return completedFuture(false);
})
.thenApply(completed -> {
if (completed) {
- messaging.startDeployAsyncToCmg(id,
version, deploymentUnit.name(), unitContent)
+ messaging.startDeployAsyncToCmg(id,
version, unitContent)
.thenAccept(ids ->
metastore.updateMeta(id, version, unitMeta -> {
for (String consistentId :
ids) {
unitMeta.addConsistentId(consistentId);
@@ -163,7 +169,7 @@ public class DeploymentManagerImpl implements
IgniteDeployment {
}
LOG.warn("Failed to deploy meta of unit " + id + ":" +
version + " to metastore. "
+ "Already exists.");
- return CompletableFuture.failedFuture(
+ return failedFuture(
new DeploymentUnitAlreadyExistsException(id,
"Unit " + id + ":" + version + "
already exists"));
}
@@ -183,13 +189,13 @@ public class DeploymentManagerImpl implements
IgniteDeployment {
//TODO: Check unit usages here. If unit used in
compute task we cannot just remove it.
return metastore.updateMeta(id, version, true, meta ->
meta.updateStatus(REMOVING));
}
- return CompletableFuture.completedFuture(false);
+ return completedFuture(false);
})
.thenCompose(success -> {
if (success) {
return cmgManager.logicalTopology();
}
- return CompletableFuture.failedFuture(new
DeploymentUnitNotFoundException(
+ return failedFuture(new DeploymentUnitNotFoundException(
"Unit " + id + " with version " + version + "
doesn't exist"));
}).thenCompose(logicalTopologySnapshot -> allOf(
logicalTopologySnapshot.nodes().stream()
@@ -262,4 +268,13 @@ public class DeploymentManagerImpl implements
IgniteDeployment {
throw new IllegalArgumentException("Id is blank");
}
}
+
+ private static byte[] readContent(InputStream inputStream) {
+ try (inputStream) {
+ return inputStream.readAllBytes();
+ } catch (IOException e) {
+ LOG.error("Error reading deployment unit content", e);
+ throw new DeploymentUnitReadException(e);
+ }
+ }
}
diff --git
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/DeploymentUnit.java
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/DeploymentUnit.java
index 4e206eb0ae..3c4bddd4bf 100644
---
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/DeploymentUnit.java
+++
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/DeploymentUnit.java
@@ -18,22 +18,17 @@
package org.apache.ignite.internal.deployunit;
import java.io.InputStream;
+import java.util.Map;
/**
* Deployment unit interface.
*/
+@FunctionalInterface
public interface DeploymentUnit {
/**
- * Unit name.
+ * Deployment unit content - a map from file name to input stream.
*
- * @return Name of deployment unit.
+ * @return Deployment unit content.
*/
- String name();
-
- /**
- * Input stream with deployment unit content.
- *
- * @return input stream with deployment unit content.
- */
- InputStream content();
+ Map<String, InputStream> content();
}
diff --git
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/FileDeployerService.java
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/FileDeployerService.java
index fe4c7dfab3..be8adb7743 100644
---
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/FileDeployerService.java
+++
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/FileDeployerService.java
@@ -26,6 +26,8 @@ import static
java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -62,24 +64,25 @@ public class FileDeployerService {
*
* @param id Deploy unit identifier.
* @param version Deploy unit version.
- * @param unitName Deploy unit file name.
- * @param unitContent Deploy unit content.
+ * @param unitFileContent Map of deploy unit file names to file content.
* @return Future with deploy result.
*/
- public CompletableFuture<Boolean> deploy(String id, String version, String
unitName, byte[] unitContent) {
+ public CompletableFuture<Boolean> deploy(String id, String version,
Map<String, byte[]> unitFileContent) {
return CompletableFuture.supplyAsync(() -> {
try {
- Path unitPath = unitsFolder
+ Path unitFolder = unitsFolder
.resolve(id)
- .resolve(version)
- .resolve(unitName);
-
- Path unitPathTmp =
unitPath.resolveSibling(unitPath.getFileName() + TMP_SUFFIX);
+ .resolve(version);
- Files.createDirectories(unitPathTmp.getParent());
+ Files.createDirectories(unitFolder);
- Files.write(unitPathTmp, unitContent, CREATE, SYNC,
TRUNCATE_EXISTING);
- Files.move(unitPathTmp, unitPath, ATOMIC_MOVE,
REPLACE_EXISTING);
+ for (Entry<String, byte[]> entry : unitFileContent.entrySet())
{
+ String fileName = entry.getKey();
+ Path unitPath = unitFolder.resolve(fileName);
+ Path unitPathTmp = unitFolder.resolve(fileName +
TMP_SUFFIX);
+ Files.write(unitPathTmp, entry.getValue(), CREATE, SYNC,
TRUNCATE_EXISTING);
+ Files.move(unitPathTmp, unitPath, ATOMIC_MOVE,
REPLACE_EXISTING);
+ }
return true;
} catch (IOException e) {
LOG.error("Failed to deploy unit " + id + ":" + version, e);
diff --git
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/UnitMeta.java
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/UnitMeta.java
index c34ec09928..391deb36b8 100644
---
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/UnitMeta.java
+++
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/UnitMeta.java
@@ -37,9 +37,9 @@ public class UnitMeta {
private final Version version;
/**
- * Unit name.
+ * Unit file names.
*/
- private final String name;
+ private final List<String> fileNames;
/**
* Deployment status.
@@ -56,13 +56,13 @@ public class UnitMeta {
*
* @param id Unit identifier.
* @param version Unit version.
- * @param name Unit name.
+ * @param fileNames Unit file names.
* @param consistentIdLocation Consistent ids of nodes where unit deployed.
*/
- public UnitMeta(String id, Version version, String name, DeploymentStatus
status, List<String> consistentIdLocation) {
+ public UnitMeta(String id, Version version, List<String> fileNames,
DeploymentStatus status, List<String> consistentIdLocation) {
this.id = id;
this.version = version;
- this.name = name;
+ this.fileNames = fileNames;
this.status = status;
this.consistentIdLocation.addAll(consistentIdLocation);
}
@@ -90,8 +90,8 @@ public class UnitMeta {
*
* @return name of deployment unit.
*/
- public String name() {
- return name;
+ public List<String> fileNames() {
+ return fileNames;
}
/**
@@ -137,7 +137,7 @@ public class UnitMeta {
if (version != null ? !version.equals(meta.version) : meta.version !=
null) {
return false;
}
- if (name != null ? !name.equals(meta.name) : meta.name != null) {
+ if (fileNames != null ? !fileNames.equals(meta.fileNames) :
meta.fileNames != null) {
return false;
}
if (status != meta.status) {
@@ -150,7 +150,7 @@ public class UnitMeta {
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (version != null ? version.hashCode() : 0);
- result = 31 * result + (name != null ? name.hashCode() : 0);
+ result = 31 * result + (fileNames != null ? fileNames.hashCode() : 0);
result = 31 * result + (status != null ? status.hashCode() : 0);
result = 31 * result + consistentIdLocation.hashCode();
return result;
@@ -161,7 +161,7 @@ public class UnitMeta {
return "UnitMeta{"
+ "id='" + id + '\''
+ ", version=" + version
- + ", name='" + name + '\''
+ + ", fileNames=" + String.join(", ", fileNames)
+ ", status=" + status
+ ", consistentIdLocation=" + String.join(", ",
consistentIdLocation)
+ '}';
diff --git
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/key/UnitMetaSerializer.java
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/key/UnitMetaSerializer.java
index e1d927e960..669b061073 100644
---
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/key/UnitMetaSerializer.java
+++
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/key/UnitMetaSerializer.java
@@ -19,11 +19,11 @@ package org.apache.ignite.internal.deployunit.key;
import static java.nio.charset.StandardCharsets.UTF_8;
-import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Base64;
-import java.util.Base64.Decoder;
-import java.util.Base64.Encoder;
+import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
import org.apache.ignite.internal.deployunit.UnitMeta;
import org.apache.ignite.internal.deployunit.version.Version;
import org.apache.ignite.internal.rest.api.deployment.DeploymentStatus;
@@ -34,6 +34,8 @@ import
org.apache.ignite.internal.rest.api.deployment.DeploymentStatus;
public final class UnitMetaSerializer {
private static final String SEPARATOR = ";";
+ private static final String LIST_SEPARATOR = ":";
+
/**
* Constructor.
*/
@@ -52,21 +54,13 @@ public final class UnitMetaSerializer {
appendWithEncoding(sb, meta.id());
appendWithEncoding(sb, meta.version().render());
- appendWithEncoding(sb, meta.name());
+ appendWithEncoding(sb, meta.fileNames());
appendWithEncoding(sb, meta.status().name());
-
- for (String id : meta.consistentIdLocation()) {
- appendWithEncoding(sb, id);
- }
+ appendWithEncoding(sb, meta.consistentIdLocation());
return sb.toString().getBytes(UTF_8);
}
- private static void appendWithEncoding(StringBuilder sb, String content) {
- Encoder encoder = Base64.getEncoder();
- sb.append(new String(encoder.encode(content.getBytes(UTF_8)),
UTF_8)).append(SEPARATOR);
- }
-
/**
* Deserialize byte array to unit meta.
*
@@ -75,21 +69,45 @@ public final class UnitMetaSerializer {
*/
public static UnitMeta deserialize(byte[] bytes) {
String s = new String(bytes, UTF_8);
- String[] split = s.split(SEPARATOR);
+ String[] split = s.split(SEPARATOR, -1);
+
+ String id = decode(split[0]);
+ String version = decode(split[1]);
+ List<String> fileNames = deserializeList(split[2]);
- Decoder decoder = Base64.getDecoder();
+ DeploymentStatus status = DeploymentStatus.valueOf(decode(split[3]));
- String id = new String(decoder.decode(split[0]), UTF_8);
- String version = new String(decoder.decode(split[1]), UTF_8);
- String unitName = new String(decoder.decode(split[2]), UTF_8);
+ List<String> ids = deserializeList(split[4]);
- DeploymentStatus status = DeploymentStatus.valueOf(new
String(decoder.decode(split[3]), UTF_8));
+ return new UnitMeta(id, Version.parseVersion(version), fileNames,
status, ids);
+ }
- List<String> ids = new ArrayList<>();
- for (int i = 4; i < split.length; i++) {
- ids.add(new String(decoder.decode(split[i]), UTF_8));
+ private static void appendWithEncoding(StringBuilder sb, String content) {
+ sb.append(encode(content)).append(SEPARATOR);
+ }
+
+ private static void appendWithEncoding(StringBuilder sb, List<String>
content) {
+ String list = content.stream()
+ .map(UnitMetaSerializer::encode)
+ .collect(Collectors.joining(LIST_SEPARATOR));
+ sb.append(list).append(SEPARATOR);
+ }
+
+ private static List<String> deserializeList(String data) {
+ if (data.isEmpty()) {
+ return Collections.emptyList();
}
- return new UnitMeta(id, Version.parseVersion(version), unitName,
status, ids);
+ return Arrays.stream(data.split(LIST_SEPARATOR))
+ .map(UnitMetaSerializer::decode)
+ .collect(Collectors.toList());
+ }
+
+ private static String encode(String s) {
+ return new String(Base64.getEncoder().encode(s.getBytes(UTF_8)),
UTF_8);
+ }
+
+ private static String decode(String s) {
+ return new String(Base64.getDecoder().decode(s), UTF_8);
}
}
diff --git
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/message/DeployUnitRequest.java
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/message/DeployUnitRequest.java
index ddf16bf6d7..7e88719300 100644
---
a/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/message/DeployUnitRequest.java
+++
b/modules/code-deployment/src/main/java/org/apache/ignite/internal/deployunit/message/DeployUnitRequest.java
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.deployunit.message;
+import java.util.Map;
import org.apache.ignite.network.NetworkMessage;
import org.apache.ignite.network.annotations.Transferable;
@@ -40,18 +41,10 @@ public interface DeployUnitRequest extends NetworkMessage {
String version();
/**
- * Returns name of deployment unit.
+ * Returns map from file names of deployment unit to their content.
*
- * @return name of deployment unit.
+ * @return map from file names of deployment unit to their content.
*/
- String unitName();
-
- /**
- * Returns content of deployment unit.
- *
- * @return content of deployment unit.
- */
-
- byte[] unitContent();
+ Map<String, byte[]> unitContent();
}
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 866dc30cc6..8d522044cf 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
@@ -20,64 +20,51 @@ package org.apache.ignite.deployment;
import static
org.apache.ignite.internal.deployunit.key.UnitMetaSerializer.deserialize;
import static
org.apache.ignite.internal.deployunit.key.UnitMetaSerializer.serialize;
import static
org.apache.ignite.internal.rest.api.deployment.DeploymentStatus.UPLOADING;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
+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.internal.deployunit.UnitMeta;
import org.apache.ignite.internal.deployunit.key.UnitMetaSerializer;
import org.apache.ignite.internal.deployunit.version.Version;
-import org.hamcrest.MatcherAssert;
-import org.junit.jupiter.api.Test;
+import org.apache.ignite.internal.rest.api.deployment.DeploymentStatus;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
/**
* Test for {@link UnitMetaSerializer}.
*/
public class UnitMetaSerializerTest {
- @Test
- public void testSerializeDeserializeLatest() {
- UnitMeta meta = new UnitMeta("id", Version.LATEST, "unitName",
UPLOADING, Arrays.asList("id1", "id2"));
-
- byte[] serialize = serialize(meta);
-
- MatcherAssert.assertThat(deserialize(serialize), is(meta));
- }
-
- @Test
- public void testSerializeDeserializeUnit() {
- UnitMeta meta = new UnitMeta("id", Version.parseVersion("3.0.0"),
"unitName", UPLOADING, Arrays.asList("id1", "id2"));
-
- byte[] serialize = serialize(meta);
-
- MatcherAssert.assertThat(deserialize(serialize), is(meta));
- }
-
- @Test
- public void testSerializeDeserializeUnitIncompleteVersion() {
- UnitMeta meta = new UnitMeta("id", Version.parseVersion("3.0"),
"unitName", UPLOADING, Arrays.asList("id1", "id2"));
-
- byte[] serialize = serialize(meta);
-
- MatcherAssert.assertThat(deserialize(serialize), is(meta));
- }
-
- @Test
- public void testSerializeDeserializeUnitEmptyConsistentId() {
- UnitMeta meta = new UnitMeta("id", Version.parseVersion("3.0.0"),
"unitName", UPLOADING, Collections.emptyList());
-
- byte[] serialize = serialize(meta);
-
- UnitMeta deserialize = deserialize(serialize);
- MatcherAssert.assertThat(deserialize, is(meta));
+ private static List<Arguments> metaProvider() {
+ return List.of(
+ arguments("id", Version.LATEST, List.of("fileName"),
UPLOADING, Arrays.asList("id1", "id2")),
+ arguments("id", Version.LATEST, List.of("fileName1",
"fileName2"), UPLOADING, Arrays.asList("id1", "id2")),
+ arguments("id", Version.parseVersion("3.0.0"),
List.of("fileName"), UPLOADING, Arrays.asList("id1", "id2")),
+ arguments("id", Version.parseVersion("3.0"),
List.of("fileName"), UPLOADING, Arrays.asList("id1", "id2")),
+ arguments("id", Version.parseVersion("3.0.0"),
List.of("fileName"), UPLOADING, Collections.emptyList()),
+ arguments("id", Version.parseVersion("3.0.0"),
List.of("fileName1", "fileName2"), UPLOADING, Collections.emptyList()),
+ arguments("id;", Version.parseVersion("3.0.0"),
List.of("fileName;"), UPLOADING, Collections.emptyList()),
+ arguments("id;", Version.parseVersion("3.0.0"),
List.of("fileName1:;", "fileName2"), UPLOADING, Collections.emptyList())
+ );
}
- @Test
- public void testSerializeDeserializeWithSeparatorCharInIdName() {
- UnitMeta meta = new UnitMeta("id;", Version.parseVersion("3.0.0"),
"unitName;", UPLOADING, Collections.emptyList());
+ @ParameterizedTest
+ @MethodSource("metaProvider")
+ public void testSerializeDeserialize(
+ String id,
+ Version version,
+ List<String> fileNames,
+ DeploymentStatus status,
+ List<String> consistentIdLocation
+ ) {
+ UnitMeta meta = new UnitMeta(id, version, fileNames, status,
consistentIdLocation);
byte[] serialize = serialize(meta);
- UnitMeta deserialize = deserialize(serialize);
- MatcherAssert.assertThat(deserialize, is(meta));
+ assertThat(deserialize(serialize), is(meta));
}
}
diff --git a/modules/rest-api/openapi/openapi.yaml
b/modules/rest-api/openapi/openapi.yaml
index 86fbe21ebf..f7dd0156c9 100644
--- a/modules/rest-api/openapi/openapi.yaml
+++ b/modules/rest-api/openapi/openapi.yaml
@@ -365,9 +365,11 @@ paths:
type: string
description: The version of the deployment unit.
unitContent:
- type: string
+ type: array
description: The code to deploy.
- format: binary
+ items:
+ type: string
+ format: binary
required: true
responses:
"200":
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
index 5abfafb69b..387d139da0 100644
---
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
@@ -39,6 +39,7 @@ 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.reactivestreams.Publisher;
/**
* REST endpoint allows to deployment code service.
@@ -70,7 +71,8 @@ public interface DeploymentCodeApi {
description = "The version of the deployment unit.")
String unitVersion,
@Schema(name = "unitContent",
requiredMode = RequiredMode.REQUIRED,
- description = "The code to deploy.") CompletedFileUpload
unitContent);
+ description = "The code to deploy.")
Publisher<CompletedFileUpload> unitContent
+ );
/**
* Undeploy unit REST method.
diff --git
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/CompletedFileUploadSubscriber.java
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/CompletedFileUploadSubscriber.java
new file mode 100644
index 0000000000..4eb5fba651
--- /dev/null
+++
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/deployment/CompletedFileUploadSubscriber.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.rest.deployment;
+
+import io.micronaut.http.multipart.CompletedFileUpload;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.internal.deployunit.DeploymentUnit;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+
+/**
+ * Implementation of {@link Subscriber} based on {@link CompletedFileUpload}
which will collect uploaded files to the
+ * {@link DeploymentUnit}.
+ */
+class CompletedFileUploadSubscriber implements Subscriber<CompletedFileUpload>
{
+ private final CompletableFuture<DeploymentUnit> result;
+
+ private final Map<String, InputStream> content = new HashMap<>();
+
+ private IOException ex;
+
+ /**
+ * Constructor.
+ *
+ * @param result Result future.
+ */
+ CompletedFileUploadSubscriber(CompletableFuture<DeploymentUnit> result) {
+ this.result = result;
+ }
+
+ @Override
+ public void onSubscribe(Subscription subscription) {
+ subscription.request(Long.MAX_VALUE);
+ }
+
+ @Override
+ public void onNext(CompletedFileUpload item) {
+ try {
+ content.put(item.getFilename(), item.getInputStream());
+ } catch (IOException e) {
+ if (ex != null) {
+ ex.addSuppressed(e);
+ } else {
+ ex = e;
+ }
+ }
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ result.completeExceptionally(throwable);
+ }
+
+ @Override
+ public void onComplete() {
+ if (ex != null) {
+ result.completeExceptionally(ex);
+ } else {
+ result.complete(() -> content);
+ }
+ }
+}
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 8034966567..7ebe26611d 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
@@ -19,8 +19,6 @@ 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.HashMap;
import java.util.Map;
@@ -33,6 +31,7 @@ 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.DeploymentInfo;
import org.apache.ignite.internal.rest.api.deployment.UnitStatus;
+import org.reactivestreams.Publisher;
/**
* Implementation of {@link DeploymentCodeApi}.
@@ -46,16 +45,10 @@ public class DeploymentManagementController implements
DeploymentCodeApi {
}
@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, Version.LATEST,
deploymentUnit);
- }
- return deployment.deployAsync(unitId,
Version.parseVersion(unitVersion), deploymentUnit);
- } catch (IOException e) {
- return CompletableFuture.failedFuture(e);
- }
+ 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, parseVersion(unitVersion), deploymentUnit));
}
@Override
@@ -92,22 +85,6 @@ public class DeploymentManagementController implements
DeploymentCodeApi {
.collect(Collectors.toList()));
}
- 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;
- }
- };
- }
-
/**
* Mapper method.
*
@@ -123,4 +100,11 @@ public class DeploymentManagementController implements
DeploymentCodeApi {
}
return new UnitStatus(status.id(), versionToDeploymentStatus);
}
+
+ private static Version parseVersion(String unitVersion) {
+ if (unitVersion == null || unitVersion.isBlank()) {
+ return Version.LATEST;
+ }
+ return Version.parseVersion(unitVersion);
+ }
}
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 59634a7ea1..7f53b5e4d0 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
@@ -30,7 +30,6 @@ import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
@@ -38,9 +37,12 @@ import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
import org.apache.ignite.internal.ClusterPerTestIntegrationTest;
import org.apache.ignite.internal.app.IgniteImpl;
import org.apache.ignite.internal.deployunit.DeploymentInfo;
@@ -72,16 +74,20 @@ public class ItDeploymentUnitTest extends
ClusterPerTestIntegrationTest {
private DeployFile bigFile;
+ private List<DeployFile> allFiles;
+
@BeforeEach
public void generateDummy() throws IOException {
smallFile = create("small.txt", SMALL_IN_BYTES, BASE_REPLICA_TIMEOUT);
mediumFile = create("medium.txt", MEDIUM_IN_BYTES,
BASE_REPLICA_TIMEOUT * 2);
- bigFile = create("big.txt", BIG_IN_BYTES, BASE_REPLICA_TIMEOUT * 3);
+ // TODO https://issues.apache.org/jira/browse/IGNITE-19009
+ // bigFile = create("big.txt", BIG_IN_BYTES, BASE_REPLICA_TIMEOUT * 3);
+ allFiles = List.of(smallFile, mediumFile);
}
private DeployFile create(String name, long size, int replicaTimeout)
throws IOException {
DeployFile deployFile = new DeployFile(workDir.resolve(name), size,
replicaTimeout);
- deployFile.ensureExist();
+ deployFile.ensureExists();
return deployFile;
}
@@ -100,6 +106,21 @@ public class ItDeploymentUnitTest extends
ClusterPerTestIntegrationTest {
.until(() -> node(2).deployment().unitsAsync(),
willBe(Collections.singletonList(status)));
}
+ @Test
+ public void deployDirectory() {
+ String id = "test";
+ Unit unit = deployAndVerify(id, Version.parseVersion("1.1.0"), false,
allFiles, 1);
+
+ IgniteImpl cmg = cluster.node(0);
+ waitUnitReplica(cmg, unit);
+
+ UnitStatus status = buildStatus(id, unit);
+
+ await().timeout(2, SECONDS)
+ .pollDelay(500, MILLISECONDS)
+ .until(() -> node(2).deployment().unitsAsync(),
willBe(Collections.singletonList(status)));
+ }
+
@Test
public void testDeployUndeploy() {
Unit unit = deployAndVerifySmall("test",
Version.parseVersion("1.1.0"), 1);
@@ -261,22 +282,35 @@ public class ItDeploymentUnitTest extends
ClusterPerTestIntegrationTest {
}
private Unit deployAndVerify(String id, Version version, boolean force,
DeployFile file, int nodeIndex) {
+ return deployAndVerify(id, version, force, List.of(file), nodeIndex);
+ }
+
+ private Unit deployAndVerify(String id, Version version, boolean force,
List<DeployFile> files, int nodeIndex) {
IgniteImpl entryNode = node(nodeIndex);
+ List<Path> paths = files.stream()
+ .map(deployFile -> deployFile.file)
+ .collect(Collectors.toList());
+
CompletableFuture<Boolean> deploy = entryNode.deployment()
- .deployAsync(id, version, force, fromPath(file.file));
+ .deployAsync(id, version, force, fromPaths(paths));
assertThat(deploy, willBe(true));
- Unit unit = new Unit(entryNode, id, version, file);
- Path nodeUnitFile = getNodeUnitFile(unit);
- assertTrue(Files.exists(nodeUnitFile));
+ Unit unit = new Unit(entryNode, id, version, files);
+
+ Path nodeUnitDirectory = getNodeUnitDirectory(entryNode, id, version);
+
+ for (DeployFile file : files) {
+ Path filePath = nodeUnitDirectory.resolve(file.file.getFileName());
+ assertTrue(Files.exists(filePath));
+ }
return unit;
}
private Unit deployAndVerifySmall(String id, Version version, int
nodeIndex) {
- return deployAndVerifyMedium(id, version, nodeIndex);
+ return deployAndVerify(id, version, smallFile, nodeIndex);
}
private Unit deployAndVerifyMedium(String id, Version version, int
nodeIndex) {
@@ -287,35 +321,52 @@ public class ItDeploymentUnitTest extends
ClusterPerTestIntegrationTest {
return deployAndVerify(id, version, bigFile, nodeIndex);
}
- private Path getNodeUnitFile(Unit unit) {
- return getNodeUnitFile(unit.deployedNode, unit.id, unit.version,
unit.file);
- }
-
- private Path getNodeUnitFile(IgniteImpl node, String unitId, Version
unitVersion, DeployFile file) {
+ private Path getNodeUnitDirectory(IgniteImpl node, String unitId, Version
unitVersion) {
String deploymentFolder = node.nodeConfiguration()
.getConfiguration(DeploymentConfiguration.KEY)
.deploymentLocation().value();
Path resolve = workDir.resolve(node.name()).resolve(deploymentFolder);
return resolve.resolve(unitId)
- .resolve(unitVersion.render())
- .resolve(file.file.getFileName());
+ .resolve(unitVersion.render());
}
private void waitUnitReplica(IgniteImpl ignite, Unit unit) {
- Path unitPath = getNodeUnitFile(ignite, unit.id, unit.version,
unit.file);
+ Path unitDirectory = getNodeUnitDirectory(ignite, unit.id,
unit.version);
+
+ int combinedTimeout = unit.files.stream().map(file ->
file.replicaTimeout).reduce(Integer::sum).get();
- await().timeout(unit.file.replicaTimeout, SECONDS)
+ await().timeout(combinedTimeout, SECONDS)
.pollDelay(1, SECONDS)
.ignoreException(IOException.class)
- .until(() -> Files.exists(unitPath) && Files.size(unitPath) ==
unit.file.expectedSize);
+ .until(() -> {
+ for (DeployFile file : unit.files) {
+ Path filePath =
unitDirectory.resolve(file.file.getFileName());
+ if (Files.notExists(filePath) || Files.size(filePath)
!= file.expectedSize) {
+ return false;
+ }
+ }
+
+ return true;
+ });
}
private void waitUnitClean(IgniteImpl ignite, Unit unit) {
- Path unitPath = getNodeUnitFile(ignite, unit.id, unit.version,
unit.file);
+ Path unitDirectory = getNodeUnitDirectory(ignite, unit.id,
unit.version);
- await().timeout(unit.file.replicaTimeout, SECONDS)
+ int combinedTimeout = unit.files.stream().map(file ->
file.replicaTimeout).reduce(Integer::sum).get();
+
+ await().timeout(combinedTimeout, SECONDS)
.pollDelay(2, SECONDS)
- .until(() -> !Files.exists(unitPath));
+ .until(() -> {
+ for (DeployFile file : unit.files) {
+ Path filePath =
unitDirectory.resolve(file.file.getFileName());
+ if (Files.exists(filePath)) {
+ return false;
+ }
+ }
+
+ return true;
+ });
}
class Unit {
@@ -325,13 +376,13 @@ public class ItDeploymentUnitTest extends
ClusterPerTestIntegrationTest {
private final Version version;
- private final DeployFile file;
+ private final List<DeployFile> files;
- Unit(IgniteImpl deployedNode, String id, Version version, DeployFile
file) {
+ Unit(IgniteImpl deployedNode, String id, Version version,
List<DeployFile> files) {
this.deployedNode = deployedNode;
this.id = id;
this.version = version;
- this.file = file;
+ this.files = files;
}
CompletableFuture<Void> undeployAsync() {
@@ -357,7 +408,7 @@ public class ItDeploymentUnitTest extends
ClusterPerTestIntegrationTest {
this.replicaTimeout = replicaTimeout;
}
- public void ensureExist() throws IOException {
+ public void ensureExists() throws IOException {
ensureFile(file, expectedSize);
}
@@ -374,23 +425,16 @@ public class ItDeploymentUnitTest extends
ClusterPerTestIntegrationTest {
}
}
- private static DeploymentUnit fromPath(Path path) {
- Objects.requireNonNull(path);
- return new DeploymentUnit() {
-
- @Override
- public String name() {
- return path.getFileName().toString();
+ private static DeploymentUnit fromPaths(List<Path> paths) {
+ Objects.requireNonNull(paths);
+ Map<String, InputStream> map = new HashMap<>();
+ try {
+ for (Path path : paths) {
+ map.put(path.getFileName().toString(),
Files.newInputStream(path));
}
-
- @Override
- public InputStream content() {
- try {
- return new FileInputStream(path.toFile());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
- };
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return () -> map;
}
}