diff --git a/dbrepo-metadata-service/pom.xml b/dbrepo-metadata-service/pom.xml index 0dd04aee226b89eb6ea5f2467a1f3102901e8252..8cd08e853640a0b6c810c36b65a5c46d4cc425c0 100644 --- a/dbrepo-metadata-service/pom.xml +++ b/dbrepo-metadata-service/pom.xml @@ -298,6 +298,11 @@ <version>${testcontainers.version}</version> <scope>test</scope> </dependency> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>minio</artifactId> + <version>${testcontainers.version}</version> + </dependency> <dependency> <groupId>com.github.dasniko</groupId> <artifactId>testcontainers-keycloak</artifactId> diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java index 6c7498c8596ba50fed17b4a414baa1a6b783a7e8..6fa835e4d739ea08dc3d6b01cfa3226af648c17f 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/handlers/ApiExceptionHandler.java @@ -903,4 +903,16 @@ public class ApiExceptionHandler extends ResponseEntityExceptionHandler { return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); } + @Hidden + @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) + @ExceptionHandler({DataDbSidecarException.class}) + public ResponseEntity<ApiErrorDto> handle(DataDbSidecarException e, WebRequest request) { + final ApiErrorDto response = ApiErrorDto.builder() + .status(HttpStatus.UNPROCESSABLE_ENTITY) + .message(e.getLocalizedMessage()) + .code("error.datadb.sidecar") + .build(); + return new ResponseEntity<>(response, new HttpHeaders(), response.getStatus()); + } + } diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/config/MinioConfig.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/config/MinioConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..50740579d2cdff4b6f5dd317a299b8b927c40bee --- /dev/null +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/config/MinioConfig.java @@ -0,0 +1,62 @@ +package at.tuwien.config; + +import io.minio.MakeBucketArgs; +import io.minio.MinioClient; +import io.minio.UploadObjectArgs; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.io.IOException; + +@Slf4j +@Configuration +public class MinioConfig { + + @Value("${fda.minio.endpoint}") + private String minioEndpoint; + + @Value("${fda.minio.accessKeyId}") + private String minioAccessKeyId; + + @Value("${fda.minio.secretAccessKey}") + private String minioSecretAccessKey; + + @Bean + public MinioClient minioClient() { + return MinioClient.builder() + .endpoint(minioEndpoint) + .credentials(minioAccessKeyId, minioSecretAccessKey) + .build(); + } + + public void makeBuckets(String... buckets) throws IOException { + for (String bucket : buckets) { + try { + minioClient().makeBucket(MakeBucketArgs.builder() + .bucket(bucket) + .build()); + log.debug("created bucket {}", bucket); + } catch (Exception e) { + log.error("Failed to make bucket {}", bucket); + throw new IOException("Failed to make bucket: " + e.getMessage()); + } + } + } + + public void uploadFile(String bucket, String filename, String key) throws IOException { + try { + minioClient().uploadObject(UploadObjectArgs.builder() + .bucket(bucket) + .filename(filename) + .object(key) + .build()); + log.debug("uploaded file into bucket {} with key {}", bucket, key); + } catch (Exception e) { + log.error("Failed to upload file into bucket {}", bucket); + throw new IOException("Failed to upload file into bucket: " + e.getMessage()); + } + } + +} diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/QueryServiceIntegrationTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/QueryServiceIntegrationTest.java index 09ca1a1d4dcd85d5833df68d7e8cd9a8fcd8646f..9dcc1d92b3b8698b0d3d5878174bdbf7875928ff 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/QueryServiceIntegrationTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/QueryServiceIntegrationTest.java @@ -9,11 +9,17 @@ import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.api.database.table.TableCsvDto; import at.tuwien.config.MariaDbConfig; import at.tuwien.config.MariaDbContainerConfig; +import at.tuwien.config.MinioConfig; import at.tuwien.exception.*; +import at.tuwien.gateway.DataDbSidecarGateway; import at.tuwien.querystore.Query; import at.tuwien.repository.mdb.*; +import at.tuwien.service.impl.QueryServiceImpl; +import io.minio.MinioClient; import lombok.SneakyThrows; import lombok.extern.log4j.Log4j2; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.RandomStringUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -27,9 +33,13 @@ import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.containers.MinIOContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; import java.math.BigInteger; import java.sql.SQLException; import java.time.Instant; @@ -41,6 +51,9 @@ import java.time.temporal.ChronoUnit; import java.util.*; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; @Log4j2 @@ -75,11 +88,27 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { private TableColumnRepository tableColumnRepository; @Autowired - private QueryService queryService; + private QueryServiceImpl queryService; + + @Autowired + private MinioConfig minioConfig; + + @MockBean + private DataDbSidecarGateway dataDbSidecarGateway; @Container private static MariaDBContainer<?> mariaDBContainer = MariaDbContainerConfig.getContainer(); + @Container + private static MinIOContainer minIOContainer = new MinIOContainer("minio/minio") + .withUserName("minioadmin") + .withPassword("minioadmin"); + + @DynamicPropertySource + static void openSearchProperties(DynamicPropertyRegistry registry) { + registry.add("fda.minio.endpoint", () -> minIOContainer.getS3URL()); + } + @BeforeEach public void beforeEach() throws SQLException { MariaDbConfig.dropAllDatabases(CONTAINER_1); @@ -469,9 +498,8 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { } @Test - public void viewFindAll_succeeds() throws DatabaseConnectionException, TableMalformedException, - DatabaseNotFoundException, ImageNotSupportedException, QueryMalformedException, UserNotFoundException, - InterruptedException, ViewMalformedException, PaginationException, ViewNotFoundException { + public void viewFindAll_succeeds() throws TableMalformedException, DatabaseNotFoundException, + QueryMalformedException, InterruptedException { /* pre-condition */ Thread.sleep(1000) /* wait for test container some more */; @@ -499,9 +527,10 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { } @Test - public void count_emptySet_succeeds() throws DatabaseConnectionException, TableMalformedException, - DatabaseNotFoundException, ImageNotSupportedException, QueryMalformedException, UserNotFoundException, - QueryStoreException, QueryNotFoundException, FileStorageException, SQLException, DataDbSidecarException { + public void findOne_emptySet_succeeds() throws DatabaseConnectionException, DatabaseNotFoundException, + ImageNotSupportedException, QueryMalformedException, UserNotFoundException, QueryStoreException, + QueryNotFoundException, FileStorageException, SQLException, IOException { + final String filename = RandomStringUtils.randomAlphabetic(40) + ".csv"; final Query query = Query.builder() .id(QUERY_1_ID) .query("SELECT `location`, `lat`, `lng` FROM `weather_location` WHERE `location` = \"Vienna\"") @@ -514,12 +543,16 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { .isPersisted(true) .build(); - /* mock */ MariaDbConfig.insertQueryStore(DATABASE_1, query, USER_1_USERNAME); + doNothing() + .when(dataDbSidecarGateway) + .exportFile(anyString(), anyInt(), anyString()); + minioConfig.makeBuckets("dbrepo-upload", "dbrepo-download"); + minioConfig.uploadFile("dbrepo-download", "./src/test/resources/csv/testdata.csv", filename); /* test */ - final ExportResource response = queryService.findOne(DATABASE_1_ID, QUERY_1_ID, USER_1_PRINCIPAL); + final ExportResource response = queryService.findOne(DATABASE_1_ID, QUERY_1_ID, USER_1_PRINCIPAL, filename); assertNotNull(response.getFilename()); assertNotNull(response.getResource()); } diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java index 957161e468eacd0c630428a7c00b6226ba462011..e2cf9491d49448784eaa11fe51a1abf2e5aae173 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java @@ -288,7 +288,13 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService public ExportResource findOne(Long databaseId, Long queryId, Principal principal) throws DatabaseNotFoundException, ImageNotSupportedException, FileStorageException, QueryStoreException, QueryNotFoundException, QueryMalformedException, DatabaseConnectionException, UserNotFoundException, DataDbSidecarException { - final String filename = RandomStringUtils.randomAlphabetic(40) + ".csv"; + return findOne(databaseId, queryId, principal, RandomStringUtils.randomAlphabetic(40) + ".csv"); + } + + @Transactional(readOnly = true) + public ExportResource findOne(Long databaseId, Long queryId, Principal principal, String filename) + throws DatabaseNotFoundException, ImageNotSupportedException, FileStorageException, QueryStoreException, + QueryNotFoundException, QueryMalformedException, DatabaseConnectionException, UserNotFoundException, DataDbSidecarException { /* find */ final Database database = databaseService.find(databaseId); final Query query = storeService.findOne(databaseId, queryId, principal);