diff --git a/CHANGELOG.md b/CHANGELOG.md index f0ee74b..225ecdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ All notable changes to this project will be documented in this file. +## [[8.6.0]](https://github.com/iExecBlockchainComputing/tee-worker-post-compute/releases/tag/v8.6.0) 2024-12-20 + +### New Features + +- Replace RestTemplate with Feign client for Result Proxy upload. (#120) + +### Deprecated + +- Stop building Gramine TEE image in Jenkins Pipeline. (#118) + +### Dependency Upgrades + +- Upgrade to `eclipse-temurin:11.0.24_8-jre-focal`. (#116) +- Upgrade to Gradle 8.10.2. (#117) +- Upgrade to `testcontainers` 1.20.4. (#119) +- Upgrade to `iexec-commons-poco` 4.2.0. (#121) +- Upgrade to `iexec-common` 8.6.0. (#121) + ## [[8.5.0]](https://github.com/iExecBlockchainComputing/tee-worker-post-compute/releases/tag/v8.5.0) 2024-06-18 ### Bug Fixes diff --git a/Dockerfile b/Dockerfile index dbd3b09..8799c8b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM eclipse-temurin:11.0.22_7-jre-focal +FROM eclipse-temurin:11.0.24_8-jre-focal ARG jar diff --git a/Jenkinsfile b/Jenkinsfile index f6bd0e4..1fe2e67 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -25,11 +25,6 @@ if (addParameters && !params.BUILD_TEE) { return } -buildGramine( - buildInfo: buildInfo, - dockerfileDir: 'gramine' -) - sconeBuildUnlocked( nativeImage: "docker-regis.iex.ec/$repositoryName:$buildInfo.imageTag", imageName: repositoryName, diff --git a/build.gradle b/build.gradle index 02178c1..b06e73a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,11 @@ plugins { // Apply the application plugin to add support for building a CLI application in Java. id 'application' - id 'io.freefair.lombok' version '8.6' + id 'io.freefair.lombok' version '8.10.2' id 'jacoco' - id 'org.sonarqube' version '5.0.0.4638' + id 'org.sonarqube' version '5.1.0.4882' id 'maven-publish' - id 'com.github.johnrengelman.shadow' version '8.1.1' + id 'com.gradleup.shadow' version '8.3.3' } ext { @@ -13,7 +13,7 @@ ext { ociImageName = ociImageBase + ':dev' ociTeeImageName = ociImageBase + '-unlocked:dev' // versions - testContainersVersion = '1.19.0' + testContainersVersion = '1.20.4' mockitoVersion = '4.4.0' systemStubsVersion = '2.0.1' } @@ -53,9 +53,6 @@ dependencies { // Apache Commons-lang3 implementation 'org.apache.commons:commons-lang3:3.12.0' - //rest template - implementation 'org.springframework:spring-web:5.2.4.RELEASE' - implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.4' } java { diff --git a/gradle.properties b/gradle.properties index 4b949d9..2c43529 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ -version=8.5.0 -iexecCommonVersion=8.5.0 -iexecCommonsPocoVersion=4.1.0 +version=8.6.0 +iexecCommonVersion=8.6.0 +iexecCommonsPocoVersion=4.2.0 nexusUser nexusPassword diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e644113..a4b76b9 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b82aa23..df97d72 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a4..f5feea6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 7101f8e..9b42019 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/src/main/java/com/iexec/worker/api/ResultProxyApiClient.java b/src/main/java/com/iexec/worker/api/ResultProxyApiClient.java new file mode 100644 index 0000000..5534619 --- /dev/null +++ b/src/main/java/com/iexec/worker/api/ResultProxyApiClient.java @@ -0,0 +1,31 @@ +/* + * Copyright 2024 IEXEC BLOCKCHAIN TECH + * + * Licensed 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 com.iexec.worker.api; + +import com.iexec.common.result.ResultModel; +import feign.Headers; +import feign.Param; +import feign.RequestLine; + + +public interface ResultProxyApiClient { + @RequestLine("POST /v1/results") + @Headers("Authorization: {authorization}") + String uploadToIpfs( + @Param("authorization") String authorization, + ResultModel resultModel); +} diff --git a/src/main/java/com/iexec/worker/compute/post/web2/UploaderService.java b/src/main/java/com/iexec/worker/compute/post/web2/UploaderService.java index f4c8c22..d1498e3 100644 --- a/src/main/java/com/iexec/worker/compute/post/web2/UploaderService.java +++ b/src/main/java/com/iexec/worker/compute/post/web2/UploaderService.java @@ -21,14 +21,12 @@ import com.dropbox.core.v2.DbxClientV2; import com.iexec.common.result.ComputedFile; import com.iexec.common.result.ResultModel; +import com.iexec.common.utils.FeignBuilder; +import com.iexec.worker.api.ResultProxyApiClient; import com.iexec.worker.compute.post.PostComputeException; +import feign.FeignException; +import feign.Logger; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.client.RestClientException; -import org.springframework.web.client.RestTemplate; import java.io.File; import java.io.IOException; @@ -46,12 +44,12 @@ public UploaderService() { this.dropBoxService = new DropBoxService(); } - public UploaderService(DropBoxService dropBoxService) { + public UploaderService(final DropBoxService dropBoxService) { this.dropBoxService = dropBoxService; } //region Dropbox - public String uploadToDropBox(String localFilePath, String dropboxAccessToken, String remoteFilename) throws PostComputeException { + public String uploadToDropBox(final String localFilePath, final String dropboxAccessToken, final String remoteFilename) throws PostComputeException { if (localFilePath == null || !new File(localFilePath).exists()) { throw new PostComputeException(POST_COMPUTE_RESULT_FILE_NOT_FOUND, "Can't uploadToDropBox (localFile issue) (exiting)"); } @@ -71,13 +69,13 @@ public String uploadToDropBox(String localFilePath, String dropboxAccessToken, S return dropBoxService.uploadFile(client, new File(localFilePath), "/results/" + remoteFilename); } - DbxClientV2 createDropboxClient(String dropboxAccessToken, DbxRequestConfig config) { + DbxClientV2 createDropboxClient(final String dropboxAccessToken, final DbxRequestConfig config) { return new DbxClientV2(config, dropboxAccessToken); } //endregion //region IPFS - public String uploadToIpfsWithIexecProxy(ComputedFile computedFile, String baseUrl, String token, String fileToUploadPath) throws PostComputeException { + public String uploadToIpfsWithIexecProxy(final ComputedFile computedFile, final String baseUrl, final String token, final String fileToUploadPath) throws PostComputeException { final String taskId = computedFile.getTaskId(); byte[] fileToUpload; @@ -89,38 +87,25 @@ public String uploadToIpfsWithIexecProxy(ComputedFile computedFile, String baseU String.format("Can't uploadToIpfsWithIexecProxy (missing filePath to upload) [taskId:%s, fileToUploadPath:%s]", taskId, fileToUploadPath)); } - ResultModel resultModel = ResultModel.builder() + final ResultModel resultModel = ResultModel.builder() .chainTaskId(taskId) .deterministHash(computedFile.getResultDigest()) .enclaveSignature(computedFile.getEnclaveSignature()) .zip(fileToUpload) .build(); - HttpHeaders headers = new HttpHeaders(); - headers.set("Authorization", token); + final ResultProxyApiClient resultProxyApiClient = FeignBuilder.createBuilder(Logger.Level.HEADERS) + .target(ResultProxyApiClient.class, baseUrl); - HttpEntity request = new HttpEntity<>(resultModel, headers); - - HttpStatus statusCode = null; try { - ResponseEntity response = createRestTemplate().postForEntity(baseUrl, request, String.class); - statusCode = response.getStatusCode(); - - if (statusCode.is2xxSuccessful()) { - return response.getBody(); - } - } catch (RestClientException e) { + return resultProxyApiClient.uploadToIpfs(token, resultModel); + } catch (FeignException e) { log.error("Can't uploadToIpfsWithIexecProxy (result proxy issue)[taskId:{}]", taskId, e); + throw new PostComputeException( + POST_COMPUTE_IPFS_UPLOAD_FAILED, + String.format("Can't uploadToIpfsWithIexecProxy (result proxy issue)[taskId:%s, status:%s]", taskId, e.status()) + ); } - - throw new PostComputeException( - POST_COMPUTE_IPFS_UPLOAD_FAILED, - String.format("Can't uploadToIpfsWithIexecProxy (result proxy issue)[taskId:%s, status:%s]", taskId, statusCode) - ); - } - - RestTemplate createRestTemplate() { - return new RestTemplate(); } //endregion diff --git a/src/test/java/com/iexec/worker/compute/post/web2/UploaderServiceTests.java b/src/test/java/com/iexec/worker/compute/post/web2/UploaderServiceTests.java index 07d9ec6..94949db 100644 --- a/src/test/java/com/iexec/worker/compute/post/web2/UploaderServiceTests.java +++ b/src/test/java/com/iexec/worker/compute/post/web2/UploaderServiceTests.java @@ -22,25 +22,30 @@ import com.dropbox.core.v2.users.DbxUserUsersRequests; import com.dropbox.core.v2.users.FullAccount; import com.iexec.common.result.ComputedFile; +import com.iexec.common.result.ResultModel; +import com.iexec.common.utils.FeignBuilder; +import com.iexec.worker.api.ResultProxyApiClient; import com.iexec.worker.compute.post.PostComputeException; -import org.junit.jupiter.api.BeforeEach; +import feign.Feign; +import feign.FeignException; +import feign.Logger; +import feign.Request; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.MockedStatic; import org.mockito.Spy; -import org.springframework.http.ResponseEntity; -import org.springframework.web.client.RestTemplate; +import org.mockito.junit.jupiter.MockitoExtension; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Optional; import static com.iexec.common.replicate.ReplicateStatusCause.*; import static org.junit.jupiter.api.Assertions.*; @@ -48,6 +53,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; +@ExtendWith(MockitoExtension.class) class UploaderServiceTests { private static final String TASK_ID = "0x0"; @@ -67,11 +73,6 @@ class UploaderServiceTests { @InjectMocks UploaderService uploaderService; - @BeforeEach - void init() { - MockitoAnnotations.openMocks(this); - } - //region createDropboxClient @Test void shouldCreateDropboxClient() { @@ -83,7 +84,7 @@ void shouldCreateDropboxClient() { @ParameterizedTest @NullSource @ValueSource(strings = {"", "accountId"}) - void shouldUploadToDropbox(String accountId) throws DbxException, IOException { + void shouldUploadToDropbox(final String accountId) throws DbxException, IOException { final String fileToUpload = Files.createFile(Path.of(tmpFolder.getAbsolutePath(), "fileToUpload.zip")).toString(); final DbxClientV2 client = mock(DbxClientV2.class); @@ -104,7 +105,7 @@ void shouldUploadToDropbox(String accountId) throws DbxException, IOException { @ParameterizedTest @NullSource @ValueSource(strings = {"/test/nothing/here/"}) - void shouldNotUploadToDropboxSinceFileDoesNotExist(String wrongPath) { + void shouldNotUploadToDropboxSinceFileDoesNotExist(final String wrongPath) { final PostComputeException exception = assertThrows(PostComputeException.class, () -> uploaderService.uploadToDropBox(wrongPath, DROPBOX_TOKEN, REMOTE_FILENAME)); assertEquals(POST_COMPUTE_RESULT_FILE_NOT_FOUND, exception.getStatusCause()); assertEquals("Can't uploadToDropBox (localFile issue) (exiting)", exception.getMessage()); @@ -128,32 +129,31 @@ void shouldNotUploadToDropboxSinceWrongAccount() throws DbxException, IOExceptio } //endregion - //region createRestTemplate - @Test - void shouldCreateRestTemplate() { - assertNotNull(uploaderService.createRestTemplate()); - } - //endregion - //region uploadToIpfsWithIexecProxy @Test void shouldUploadToIpfsWithIexecProxy() throws IOException { - final String baseUrl = "baseUrl"; + final String baseUrl = "http://localhost"; final String fileToUpload = Files.createFile(Path.of(tmpFolder.getAbsolutePath(), "fileToUpload.zip")).toString(); final String responseBody = "responseBody"; + final ResultProxyApiClient resultProxyApiClient = mock(ResultProxyApiClient.class); - final RestTemplate restTemplate = mock(RestTemplate.class); - when(restTemplate.postForEntity(eq(baseUrl), any(), eq(String.class))).thenReturn(ResponseEntity.of(Optional.of(responseBody))); + try (MockedStatic mockedFeignBuilder = mockStatic(FeignBuilder.class)) { + final Feign.Builder feignBuilder = mock(Feign.Builder.class); + mockedFeignBuilder.when(() -> FeignBuilder.createBuilder(Logger.Level.HEADERS)) + .thenReturn(feignBuilder); + when(feignBuilder.target(ResultProxyApiClient.class, baseUrl)).thenReturn(resultProxyApiClient); - when(uploaderService.createRestTemplate()).thenReturn(restTemplate); + when(resultProxyApiClient.uploadToIpfs(any(), any(ResultModel.class))).thenReturn(responseBody); - final String actualResponseBody = assertDoesNotThrow(() -> uploaderService.uploadToIpfsWithIexecProxy(COMPUTED_FILE, baseUrl, IPFS_TOKEN, fileToUpload)); - assertEquals(responseBody, actualResponseBody); + final String actualResponseBody = assertDoesNotThrow(() -> uploaderService.uploadToIpfsWithIexecProxy(COMPUTED_FILE, baseUrl, IPFS_TOKEN, fileToUpload)); + assertEquals(responseBody, actualResponseBody); + verify(resultProxyApiClient).uploadToIpfs(eq(IPFS_TOKEN), any(ResultModel.class)); + } } @Test void shouldNotUploadToIpfsWithIexecProxySinceCantReadFile() { - final String baseUrl = "baseUrl"; + final String baseUrl = "http://localhost"; final String fileToUpload = "/this/file/does/not/exist"; final PostComputeException exception = assertThrows(PostComputeException.class, () -> uploaderService.uploadToIpfsWithIexecProxy(COMPUTED_FILE, baseUrl, IPFS_TOKEN, fileToUpload)); @@ -163,18 +163,31 @@ void shouldNotUploadToIpfsWithIexecProxySinceCantReadFile() { @Test void shouldNotUploadToIpfsWithIexecProxySincePostFailed() throws IOException { - final String baseUrl = "baseUrl"; + final String baseUrl = "http://localhost"; final String fileToUpload = Files.createFile(Path.of(tmpFolder.getAbsolutePath(), "fileToUpload.zip")).toString(); - - final RestTemplate restTemplate = mock(RestTemplate.class); - final ResponseEntity response = ResponseEntity.notFound().build(); - when(restTemplate.postForEntity(eq(baseUrl), any(), eq(String.class))).thenReturn(response); - - when(uploaderService.createRestTemplate()).thenReturn(restTemplate); - - final PostComputeException exception = assertThrows(PostComputeException.class, () -> uploaderService.uploadToIpfsWithIexecProxy(COMPUTED_FILE, baseUrl, IPFS_TOKEN, fileToUpload)); - assertEquals(POST_COMPUTE_IPFS_UPLOAD_FAILED, exception.getStatusCause()); - assertEquals(String.format("Can't uploadToIpfsWithIexecProxy (result proxy issue)[taskId:%s, status:%s]", TASK_ID, response.getStatusCode()), exception.getMessage()); + final ResultProxyApiClient resultProxyApiClient = mock(ResultProxyApiClient.class); + + try (MockedStatic mockedFeignBuilder = mockStatic(FeignBuilder.class)) { + final Feign.Builder feignBuilder = mock(Feign.Builder.class); + mockedFeignBuilder.when(() -> FeignBuilder.createBuilder(Logger.Level.HEADERS)) + .thenReturn(feignBuilder); + when(feignBuilder.target(ResultProxyApiClient.class, baseUrl)).thenReturn(resultProxyApiClient); + + FeignException feignException = FeignException.errorStatus( + "uploadToIpfs", + feign.Response.builder() + .status(500) + .reason("Internal Server Error") + .request(Request.create(Request.HttpMethod.POST, "", java.util.Collections.emptyMap(), null, null, null)) + .build() + ); + when(resultProxyApiClient.uploadToIpfs(any(), any(ResultModel.class))).thenThrow(feignException); + + final PostComputeException exception = assertThrows(PostComputeException.class, () -> uploaderService.uploadToIpfsWithIexecProxy(COMPUTED_FILE, baseUrl, IPFS_TOKEN, fileToUpload)); + assertEquals(POST_COMPUTE_IPFS_UPLOAD_FAILED, exception.getStatusCause()); + assertEquals(String.format("Can't uploadToIpfsWithIexecProxy (result proxy issue)[taskId:%s, status:%s]", TASK_ID, feignException.status()), exception.getMessage()); + } } //endregion -} \ No newline at end of file +} +