diff --git a/dbrepo-data-service/pom.xml b/dbrepo-data-service/pom.xml index a64785635ee2171947000336bc099aae5a15d29c..114544101a3a50d8ccf0aa9c932271bf2988820b 100644 --- a/dbrepo-data-service/pom.xml +++ b/dbrepo-data-service/pom.xml @@ -40,8 +40,6 @@ <opencsv.version>5.7.1</opencsv.version> <super-csv.version>2.4.0</super-csv.version> <jsql.version>4.6</jsql.version> - <c3p0.version>0.9.5.5</c3p0.version> - <c3p0-hibernate.version>6.2.2.Final</c3p0-hibernate.version> <springdoc-openapi.version>2.1.0</springdoc-openapi.version> <hsqldb.version>2.7.2</hsqldb.version> <testcontainers.version>1.18.3</testcontainers.version> @@ -157,6 +155,22 @@ <artifactId>amqp-client</artifactId> <version>${rabbit-amqp-client.version}</version> </dependency> + <!-- Monitoring --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-aop</artifactId> + </dependency> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-registry-prometheus</artifactId> + <version>${micrometer.version}</version> + </dependency> + <dependency> + <groupId>io.micrometer</groupId> + <artifactId>micrometer-observation-test</artifactId> + <version>${micrometer.version}</version> + <scope>test</scope> + </dependency> <!-- Testing --> <dependency> <groupId>org.springframework.boot</groupId> diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/listener/DefaultListenerIntegrationTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/listener/DefaultListenerIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b7991ce5a393039fa622b8686063bc2e95151638 --- /dev/null +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/listener/DefaultListenerIntegrationTest.java @@ -0,0 +1,74 @@ +package at.tuwien.listener; + +import at.tuwien.BaseUnitTest; +import at.tuwien.annotations.MockOpensearch; +import at.tuwien.config.MariaDbConfig; +import at.tuwien.config.MariaDbContainerConfig; +import at.tuwien.exception.DatabaseNotFoundException; +import at.tuwien.service.DatabaseService; +import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.amqp.core.Message; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.containers.RabbitMQContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; + +import static at.tuwien.utils.RabbitMqUtils.buildMessage; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +@Log4j2 +@SpringBootTest +@ExtendWith({SpringExtension.class, OutputCaptureExtension.class}) +@Testcontainers +@MockOpensearch +public class DefaultListenerIntegrationTest extends BaseUnitTest { + + @MockBean + private DatabaseService databaseService; + + @Autowired + private DefaultListener defaultListener; + + @Container + private static RabbitMQContainer rabbitContainer = new RabbitMQContainer("rabbitmq:3.10"); + + @Container + private static MariaDBContainer<?> mariaDBContainer = MariaDbContainerConfig.getContainer(); + + @BeforeEach + public void beforeEach() throws SQLException { + /* metadata database */ + TABLE_1.setColumns(TABLE_1_COLUMNS); + DATABASE_1.setTables(List.of(TABLE_1, TABLE_2, TABLE_3)); + MariaDbConfig.dropAllDatabases(CONTAINER_1); + MariaDbConfig.createInitDatabase(CONTAINER_1, DATABASE_1); + } + + @Test + public void onMessage_succeeds(CapturedOutput output) throws DatabaseNotFoundException { + final Message request = buildMessage("dbrepo." + DATABASE_1_INTERNALNAME + "." + TABLE_1_INTERNALNAME, "{\"id\":4,\"date\":\"2023-10-03\",\"mintemp\":15.0,\"rainfall\":0.2}", new HashMap<>()); + + /* mock */ + when(databaseService.findByInternalName(DATABASE_1_INTERNALNAME)) + .thenReturn(DATABASE_1); + + /* test */ + defaultListener.onMessage(request); + assertTrue(output.getAll().contains("successfully inserted tuple")); + } + +} diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/listener/DefaultListenerUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/listener/DefaultListenerUnitTest.java index 04aff250f5580a9ac1c68e68630acbc4eeca1f56..a366513a6840cc6e1713ab1deb2f2e23b98467b1 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/listener/DefaultListenerUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/listener/DefaultListenerUnitTest.java @@ -1,14 +1,14 @@ package at.tuwien.listener; import at.tuwien.BaseUnitTest; -import at.tuwien.annotations.MockAmqp; import at.tuwien.annotations.MockOpensearch; +import at.tuwien.config.MariaDbConfig; import at.tuwien.config.MariaDbContainerConfig; import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.amqp.core.Message; -import org.springframework.amqp.core.MessageProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.system.CapturedOutput; @@ -19,10 +19,10 @@ import org.testcontainers.containers.RabbitMQContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import java.nio.charset.StandardCharsets; +import java.sql.SQLException; import java.util.HashMap; -import java.util.Map; +import static at.tuwien.utils.RabbitMqUtils.buildMessage; import static org.junit.jupiter.api.Assertions.assertTrue; @Log4j2 @@ -38,6 +38,16 @@ public class DefaultListenerUnitTest extends BaseUnitTest { @Container private static RabbitMQContainer rabbitContainer = new RabbitMQContainer("rabbitmq:3.10"); + @Container + private static MariaDBContainer<?> mariaDBContainer = MariaDbContainerConfig.getContainer(); + + @BeforeEach + public void beforeEach() throws SQLException { + /* metadata database */ + MariaDbConfig.dropAllDatabases(CONTAINER_1); + MariaDbConfig.createInitDatabase(CONTAINER_1, DATABASE_1); + } + @Test public void onMessage_routingKeyDatabaseAndTableMissing_fails(CapturedOutput output) { final Message request = buildMessage("dbrepo", "{}", new HashMap<>()); @@ -65,11 +75,13 @@ public class DefaultListenerUnitTest extends BaseUnitTest { assertTrue(output.getAll().contains("Failed to read object")); } - protected Message buildMessage(String routingKey, String payload, Map<String, Object> headers) { - final MessageProperties properties = new MessageProperties(); - properties.setReceivedRoutingKey(routingKey); - properties.setHeaders(headers); - return new Message(payload.getBytes(StandardCharsets.UTF_8), properties); + @Test + public void onMessage_databaseNotFound_fails(CapturedOutput output) { + final Message request = buildMessage("dbrepo.database.table", "{\"id\":1}", new HashMap<>()); + + /* test */ + defaultListener.onMessage(request); + assertTrue(output.getAll().contains("Failed to find database")); } } diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/ActuatorEndpointMvcTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/ActuatorEndpointMvcTest.java new file mode 100644 index 0000000000000000000000000000000000000000..11d52c79efd91d66aec071b20d9b7f409d507dd0 --- /dev/null +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/ActuatorEndpointMvcTest.java @@ -0,0 +1,50 @@ +package at.tuwien.mvc; + +import at.tuwien.BaseUnitTest; +import at.tuwien.annotations.MockAmqp; +import at.tuwien.annotations.MockOpensearch; +import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.actuate.observability.AutoConfigureObservability; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Log4j2 +@ExtendWith(SpringExtension.class) +@AutoConfigureMockMvc +@SpringBootTest +@AutoConfigureObservability +@MockAmqp +@MockOpensearch +public class ActuatorEndpointMvcTest extends BaseUnitTest { + + @Autowired + private MockMvc mockMvc; + + @Test + public void actuatorInfo_succeeds() throws Exception { + + /* test */ + this.mockMvc.perform(get("/actuator/info")) + .andDo(print()) + .andExpect(status().isOk()); + } + + @Test + public void actuatorPrometheus_succeeds() throws Exception { + + /* test */ + this.mockMvc.perform(get("/actuator/prometheus")) + .andDo(print()) + .andExpect(status().isOk()); + } + +} diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0781569eb8aff0e5aa91b1df6381dd10945f35c4 --- /dev/null +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java @@ -0,0 +1,78 @@ +package at.tuwien.mvc; + +import at.tuwien.BaseUnitTest; +import at.tuwien.annotations.MockAmqp; +import at.tuwien.annotations.MockOpensearch; +import at.tuwien.config.MetricsConfig; +import at.tuwien.listener.DefaultListener; +import io.micrometer.observation.tck.TestObservationRegistry; +import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.amqp.core.Message; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.actuate.observability.AutoConfigureObservability; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.HashMap; + +import static at.tuwien.utils.RabbitMqUtils.buildMessage; +import static io.micrometer.observation.tck.TestObservationRegistryAssert.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Log4j2 +@ExtendWith(SpringExtension.class) +@AutoConfigureMockMvc +@SpringBootTest +@Import(MetricsConfig.class) +@AutoConfigureObservability +@MockOpensearch +public class PrometheusEndpointMvcTest extends BaseUnitTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private TestObservationRegistry registry; + + @Autowired + private DefaultListener defaultListener; + + @TestConfiguration + static class ObservationTestConfiguration { + + @Bean + public TestObservationRegistry observationRegistry() { + return TestObservationRegistry.create(); + } + } + + @Test + public void prometheus_succeeds() throws Exception { + + /* test */ + this.mockMvc.perform(get("/actuator/prometheus")) + .andDo(print()) + .andExpect(status().isOk()); + } + + @Test + public void prometheusMessageReceiveExists_succeeds() { + + /* mock */ + defaultListener.onMessage(buildMessage("dbrepo.database", "{}", new HashMap<>())); + + /* test */ + assertThat(registry) + .hasObservationWithNameEqualTo("dbr_message_receive"); + } + +} diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/SwaggerEndpointMvcTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/SwaggerEndpointMvcTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c07446eee592ebdb8d2b7d7e631fdc9f10eca927 --- /dev/null +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/SwaggerEndpointMvcTest.java @@ -0,0 +1,37 @@ +package at.tuwien.mvc; + +import at.tuwien.BaseUnitTest; +import at.tuwien.annotations.MockAmqp; +import at.tuwien.annotations.MockOpensearch; +import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Log4j2 +@ExtendWith(SpringExtension.class) +@AutoConfigureMockMvc +@SpringBootTest +@MockAmqp +@MockOpensearch +public class SwaggerEndpointMvcTest extends BaseUnitTest { + + @Autowired + private MockMvc mockMvc; + + @Test + public void swaggerUi_succeeds() throws Exception { + this.mockMvc.perform(get("/swagger-ui/index.html")) + .andDo(print()) + .andExpect(status().isOk()); + } + +} diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/DatabaseServiceIntegrationTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/DatabaseServiceIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..55feb01c14091d0d1f0dec7d1a492a7bf95adcca --- /dev/null +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/DatabaseServiceIntegrationTest.java @@ -0,0 +1,103 @@ +package at.tuwien.service; + +import at.tuwien.BaseUnitTest; +import at.tuwien.annotations.MockAmqp; +import at.tuwien.annotations.MockOpensearch; +import at.tuwien.entities.database.Database; +import at.tuwien.entities.user.User; +import at.tuwien.exception.DatabaseNotFoundException; +import at.tuwien.exception.UserNotFoundException; +import at.tuwien.repository.mdb.ContainerRepository; +import at.tuwien.repository.mdb.DatabaseRepository; +import at.tuwien.repository.mdb.ImageRepository; +import at.tuwien.repository.mdb.UserRepository; +import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@Log4j2 +@SpringBootTest +@ExtendWith(SpringExtension.class) +@Testcontainers +@MockAmqp +@MockOpensearch +public class DatabaseServiceIntegrationTest extends BaseUnitTest { + + @Autowired + private UserRepository userRepository; + + @Autowired + private ImageRepository imageRepository; + + @Autowired + private ContainerRepository containerRepository; + + @Autowired + private DatabaseRepository databaseRepository; + + @Autowired + private DatabaseService databaseService; + + @BeforeEach + public void beforeEach() { + userRepository.save(USER_1); + imageRepository.save(IMAGE_1_SIMPLE); + containerRepository.save(CONTAINER_1_SIMPLE); + databaseRepository.save(DATABASE_1_SIMPLE); + } + + @Test + public void find_succeeds() throws DatabaseNotFoundException { + + /* test */ + final Database response = databaseService.find(DATABASE_1_ID); + assertEquals(DATABASE_1_ID, response.getId()); + } + + @Test + public void find_fails() { + + /* test */ + assertThrows(DatabaseNotFoundException.class, () -> { + databaseService.find(DATABASE_2_ID); + }); + } + + @Test + public void findByInternalName_succeeds() throws DatabaseNotFoundException { + + /* test */ + final Database response = databaseService.findByInternalName(DATABASE_1_INTERNALNAME); + assertEquals(DATABASE_1_ID, response.getId()); + } + + @Test + public void findByInternalName_fails() { + + /* test */ + assertThrows(DatabaseNotFoundException.class, () -> { + databaseService.findByInternalName(DATABASE_2_INTERNALNAME); + }); + } + + @Test + public void findAll_succeeds() { + + /* test */ + final List<Database> response = databaseService.findAll(); + assertEquals(1, response.size()); + final Database database0 = response.get(0); + assertEquals(DATABASE_1_ID, database0.getId()); + } + +} diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/QueueServiceIntegrationTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/QueueServiceIntegrationTest.java index 7fb17a5d3be2d49720feaabd804ad6a0cad2c229..669ae322bb7a1256fa4dfa9eeed4a073abe568d8 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/QueueServiceIntegrationTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/QueueServiceIntegrationTest.java @@ -8,6 +8,7 @@ import at.tuwien.exception.DatabaseNotFoundException; import at.tuwien.exception.QueryMalformedException; import at.tuwien.exception.TableNotFoundException; import at.tuwien.repository.mdb.*; +import at.tuwien.service.impl.QueueServiceImpl; import lombok.extern.log4j.Log4j2; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -16,7 +17,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.testcontainers.containers.MariaDBContainer; -import org.testcontainers.containers.RabbitMQContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -26,6 +26,8 @@ import java.util.List; import java.util.Map; import java.util.stream.Stream; +import static org.junit.jupiter.api.Assertions.assertThrows; + @Log4j2 @SpringBootTest @ExtendWith(SpringExtension.class) @@ -52,7 +54,7 @@ public class QueueServiceIntegrationTest extends BaseUnitTest { private ImageRepository imageRepository; @Autowired - private QueueService queueService; + private QueueServiceImpl queueService; @Container private static MariaDBContainer<?> mariaDBContainer = MariaDbContainerConfig.getContainer(); @@ -63,16 +65,16 @@ public class QueueServiceIntegrationTest extends BaseUnitTest { MariaDbConfig.createInitDatabase(CONTAINER_1, DATABASE_1); /* metadata database */ imageRepository.save(IMAGE_1); - userRepository.saveAll(List.of(USER_1, USER_2, USER_3, USER_4, USER_5)); - containerRepository.saveAll(List.of(CONTAINER_1, CONTAINER_2)); - databaseRepository.saveAll(List.of(DATABASE_1, DATABASE_2)); - tableRepository.saveAll(List.of(TABLE_1, TABLE_2, TABLE_3, TABLE_4, TABLE_5, TABLE_6, TABLE_7)); - tableColumnRepository.saveAll(Stream.of(TABLE_1_COLUMNS, TABLE_2_COLUMNS, TABLE_3_COLUMNS, TABLE_4_COLUMNS, TABLE_5_COLUMNS, TABLE_6_COLUMNS, TABLE_7_COLUMNS).flatMap(List::stream).toList()); + userRepository.save(USER_1); + containerRepository.save(CONTAINER_1_SIMPLE); + databaseRepository.save(DATABASE_1_SIMPLE); + tableRepository.save(TABLE_1_SIMPLE); + tableColumnRepository.saveAll(TABLE_1_COLUMNS); } @Test - public void insert_succeeds() throws TableNotFoundException, QueryMalformedException, DatabaseNotFoundException, - InterruptedException { + public void insert_succeeds() throws TableNotFoundException, DatabaseNotFoundException, InterruptedException, + SQLException { final Map<String, Object> request = new HashMap<>() {{ put("id", 4L); put("date", "2023-10-03"); @@ -89,8 +91,8 @@ public class QueueServiceIntegrationTest extends BaseUnitTest { } @Test - public void insert_onlyMandatoryFields_succeeds() throws TableNotFoundException, QueryMalformedException, - DatabaseNotFoundException, InterruptedException { + public void insert_onlyMandatoryFields_succeeds() throws TableNotFoundException, DatabaseNotFoundException, + InterruptedException, SQLException { final Map<String, Object> request = new HashMap<>() {{ put("id", 5L); put("date", "2023-10-04"); @@ -103,4 +105,28 @@ public class QueueServiceIntegrationTest extends BaseUnitTest { queueService.insert(DATABASE_1_INTERNALNAME, TABLE_1_INTERNALNAME, request); } + @Test + public void insert_databaseNotExists_fails() throws InterruptedException { + + /* pre-condition */ + Thread.sleep(1000) /* wait for test container some more */; + + /* test */ + assertThrows(DatabaseNotFoundException.class, () -> { + queueService.insert("not_exists", TABLE_1_INTERNALNAME, new HashMap<>()); + }); + } + + @Test + public void insert_tableNotExists_fails() throws InterruptedException { + + /* pre-condition */ + Thread.sleep(1000) /* wait for test container some more */; + + /* test */ + assertThrows(TableNotFoundException.class, () -> { + queueService.insert(DATABASE_1_INTERNALNAME, "not_exists", new HashMap<>()); + }); + } + } diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/UserServiceIntegrationTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/UserServiceIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3e3cff725d951850bfaa128f7c52a340f2afc4be --- /dev/null +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/UserServiceIntegrationTest.java @@ -0,0 +1,70 @@ +package at.tuwien.service; + +import at.tuwien.BaseUnitTest; +import at.tuwien.annotations.MockAmqp; +import at.tuwien.annotations.MockOpensearch; +import at.tuwien.config.MariaDbConfig; +import at.tuwien.config.MariaDbContainerConfig; +import at.tuwien.entities.user.User; +import at.tuwien.exception.DatabaseNotFoundException; +import at.tuwien.exception.QueryMalformedException; +import at.tuwien.exception.TableNotFoundException; +import at.tuwien.exception.UserNotFoundException; +import at.tuwien.repository.mdb.*; +import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@Log4j2 +@SpringBootTest +@ExtendWith(SpringExtension.class) +@Testcontainers +@MockAmqp +@MockOpensearch +public class UserServiceIntegrationTest extends BaseUnitTest { + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserService userService; + + @BeforeEach + public void beforeEach() { + userRepository.save(USER_1); + } + + @Test + public void findByUsername_succeeds() throws UserNotFoundException { + + /* test */ + final User response = userService.findByUsername(USER_1_USERNAME); + assertEquals(USER_1_ID, response.getId()); + } + + @Test + public void findByUsername_fails() { + + /* test */ + assertThrows(UserNotFoundException.class, () -> { + userService.findByUsername(USER_2_USERNAME); + }); + } + +} diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/utils/RabbitMqUtils.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/utils/RabbitMqUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..636ae4db745260e176840e17741f57b46fe40680 --- /dev/null +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/utils/RabbitMqUtils.java @@ -0,0 +1,17 @@ +package at.tuwien.utils; + +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; + +import java.nio.charset.StandardCharsets; +import java.util.Map; + +public class RabbitMqUtils { + + public static Message buildMessage(String routingKey, String payload, Map<String, Object> headers) { + final MessageProperties properties = new MessageProperties(); + properties.setReceivedRoutingKey(routingKey); + properties.setHeaders(headers); + return new Message(payload.getBytes(StandardCharsets.UTF_8), properties); + } +} diff --git a/dbrepo-data-service/rest-service/src/test/resources/application.properties b/dbrepo-data-service/rest-service/src/test/resources/application.properties index 11033e17a88c840c3e78c6ec70ed61d3c6449e75..54ad577192b2c4b01404e786edd7b89de1234d7e 100644 --- a/dbrepo-data-service/rest-service/src/test/resources/application.properties +++ b/dbrepo-data-service/rest-service/src/test/resources/application.properties @@ -19,7 +19,9 @@ spring.sql.init.schema-locations=classpath*:init/schema.sql spring.jpa.hibernate.ddl-auto=create # log -logging.level.org.hibernate.SQL=trace +logging.level.at.tuwien.=debug +logging.level.at.tuwien.service.impl.QueueServiceImpl=trace +logging.level.at.tuwien.listener.DefaultListener=trace # rabbitmq spring.rabbitmq.host=localhost diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/api/CachedConnection.java b/dbrepo-data-service/services/src/main/java/at/tuwien/api/CachedConnection.java deleted file mode 100644 index d2da9237118ca5f7be0b9e0f9f677d243c3d0701..0000000000000000000000000000000000000000 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/api/CachedConnection.java +++ /dev/null @@ -1,37 +0,0 @@ -package at.tuwien.api; - -import at.tuwien.entities.database.Database; -import at.tuwien.entities.database.table.Table; -import at.tuwien.exception.TableNotFoundException; -import com.mchange.v2.c3p0.ComboPooledDataSource; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; - -import java.time.Instant; -import java.util.Optional; - -@Getter -@Setter -@Builder -public class CachedConnection { - - private ComboPooledDataSource dataSource; - - private Database database; - - private Instant lastUsed; - - public Table getTable(String internalName) throws TableNotFoundException { - final Optional<Table> optional = database.getTables() - .stream() - .filter(t -> t.getInternalName().equals(internalName)) - .findFirst(); - if (optional.isEmpty()) { - /* can never happen */ - throw new TableNotFoundException("Failed to find table with internal name " + internalName); - } - return optional.get(); - } - -} diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/config/MetricsConfig.java b/dbrepo-data-service/services/src/main/java/at/tuwien/config/MetricsConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..450be2f7df8b52fe493dd498dc0422350bb3ff39 --- /dev/null +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/config/MetricsConfig.java @@ -0,0 +1,15 @@ +package at.tuwien.config; + +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.observation.aop.ObservedAspect; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MetricsConfig { + + @Bean + public ObservedAspect observedAspect(ObservationRegistry observationRegistry) { + return new ObservedAspect(observationRegistry); + } +} diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/listener/DefaultListener.java b/dbrepo-data-service/services/src/main/java/at/tuwien/listener/DefaultListener.java index 01b3722ada9449a06cf230832d3768596251c26c..272a636e6087d99d9ac16393e907ab5115fc4b2a 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/listener/DefaultListener.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/listener/DefaultListener.java @@ -6,6 +6,7 @@ import at.tuwien.exception.TableNotFoundException; import at.tuwien.service.QueueService; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import io.micrometer.observation.annotation.Observed; import lombok.extern.log4j.Log4j2; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageListener; @@ -15,6 +16,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.IOException; +import java.sql.SQLException; import java.util.HashMap; import java.util.Map; @@ -33,6 +35,7 @@ public class DefaultListener implements MessageListener { } @Override + @Observed(name = "dbr_message_receive") public void onMessage(Message message) { final MessageProperties properties = message.getMessageProperties(); final TypeReference<HashMap<String, Object>> typeRef = new TypeReference<>() { @@ -46,6 +49,7 @@ public class DefaultListener implements MessageListener { log.error("Failed to map database and table names from routing key: is not 3-part"); return; } + log.trace("received message with id {} and content length: {} bytes", message.getMessageProperties().getMessageId(), message.getMessageProperties().getContentLength()); final String database = parts[1]; final String table = parts[2]; final Map<String, Object> body; @@ -54,7 +58,7 @@ public class DefaultListener implements MessageListener { queueService.insert(database, table, body); } catch (IOException e) { log.error("Failed to read object: {}", e.getMessage()); - } catch (TableNotFoundException | QueryMalformedException | DatabaseNotFoundException e) { + } catch (TableNotFoundException | QueryMalformedException | DatabaseNotFoundException | SQLException e) { log.error("Failed to insert tuple: {}", e.getMessage()); } } diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/QueueService.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/QueueService.java index 6649047500d591617ea76f1492787f799f78fee2..29a2f47599838de3989b24d5bc7140b75b6234aa 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/QueueService.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/QueueService.java @@ -1,10 +1,10 @@ package at.tuwien.service; -import at.tuwien.exception.ContainerNotFoundException; import at.tuwien.exception.DatabaseNotFoundException; import at.tuwien.exception.QueryMalformedException; import at.tuwien.exception.TableNotFoundException; +import java.sql.SQLException; import java.util.Map; public interface QueueService { @@ -15,5 +15,5 @@ public interface QueueService { * @param table The table name. * @param data The data. */ - void insert(String database, String table, Map<String, Object> data) throws DatabaseNotFoundException, QueryMalformedException, TableNotFoundException; + void insert(String database, String table, Map<String, Object> data) throws DatabaseNotFoundException, QueryMalformedException, TableNotFoundException, SQLException; } diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/QueueServiceImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/QueueServiceImpl.java index 6146c08828d7a232b826c833640808fbca4cdd3d..68f665f12bbf776b278f8bedd61010e5bc0718ce 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/QueueServiceImpl.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/QueueServiceImpl.java @@ -1,10 +1,8 @@ package at.tuwien.service.impl; -import at.tuwien.api.CachedConnection; -import at.tuwien.config.RabbitConfig; import at.tuwien.entities.database.Database; +import at.tuwien.entities.database.table.Table; import at.tuwien.exception.DatabaseNotFoundException; -import at.tuwien.exception.QueryMalformedException; import at.tuwien.exception.TableNotFoundException; import at.tuwien.mapper.DataMapper; import at.tuwien.service.DatabaseService; @@ -12,83 +10,53 @@ import at.tuwien.service.QueueService; import com.mchange.v2.c3p0.ComboPooledDataSource; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; import java.util.Map; +import java.util.Optional; @Log4j2 @Service public class QueueServiceImpl extends HibernateConnector implements QueueService { private final DataMapper dataMapper; - private final RabbitConfig rabbitMqConfig; private final DatabaseService databaseService; - private final Map<String, CachedConnection> cachedConnections; @Autowired - public QueueServiceImpl(DataMapper dataMapper, RabbitConfig rabbitMqConfig, DatabaseService databaseService) { + public QueueServiceImpl(DataMapper dataMapper, DatabaseService databaseService) { this.dataMapper = dataMapper; - this.rabbitMqConfig = rabbitMqConfig; this.databaseService = databaseService; - this.cachedConnections = new HashMap<>(); - } - - @Scheduled(fixedRate = 5000) - @Transactional(readOnly = true) - public void updateCachedConnections() { - final Instant threshold = Instant.now().minus(rabbitMqConfig.getConnectionTimeout(), ChronoUnit.MILLIS); - cachedConnections.entrySet() - .stream() - .filter(e -> e.getValue().getLastUsed().isAfter(threshold)) - .forEach(connection -> { - connection.getValue().getDataSource().close(); - cachedConnections.remove(connection.getKey()); - log.debug("connection for database {} expired", connection.getKey()); - }); } @Override @Transactional(readOnly = true) - public void insert(String database, String table, Map<String, Object> data) throws DatabaseNotFoundException, - QueryMalformedException, TableNotFoundException { - /* check if connection can be reused */ - final CachedConnection cachedConnection = getCachedConnection(database); - cachedConnection.setLastUsed(Instant.now()); + public void insert(String databaseInternalName, String tableInternalName, Map<String, Object> data) + throws DatabaseNotFoundException, TableNotFoundException, SQLException { + final Database database = databaseService.findByInternalName(databaseInternalName); + log.debug("found database with id {} for name {}", database.getId(), databaseInternalName); + final Optional<Table> optional = database.getTables() + .stream() + .filter(t -> t.getInternalName().equals(tableInternalName)) + .findFirst(); + if (optional.isEmpty()) { + log.error("Failed to insert tuple into table {}: the table does not exist in database with name {}", tableInternalName, databaseInternalName); + throw new TableNotFoundException("Failed to insert tuple into table " + tableInternalName + ": the table does not exist in database with name " + databaseInternalName); + } + final ComboPooledDataSource dataSource = getPrivilegedDataSource(database.getContainer().getImage(), + database.getContainer(), database); /* run query */ try { - final Connection connection = cachedConnection.getDataSource().getConnection(); - final PreparedStatement preparedStatement = dataMapper.rabbitMqTupleToInsertOrUpdateQuery(connection, cachedConnection.getTable(table), data); + final Connection connection = dataSource.getConnection(); + final PreparedStatement preparedStatement = dataMapper.rabbitMqTupleToInsertOrUpdateQuery(connection, optional.get(), data); preparedStatement.executeUpdate(); - } catch (SQLException e) { - log.error("Failed to insert/update tuple in database {}: {}", database, e.getMessage()); - throw new QueryMalformedException("Failed to insert/update tuple in database " + database, e); + log.trace("successfully inserted tuple"); + } finally { + dataSource.close(); } } - @Transactional(readOnly = true) - public CachedConnection getCachedConnection(String databaseInternalName) throws DatabaseNotFoundException { - if (this.cachedConnections.containsKey(databaseInternalName)) { - return this.cachedConnections.get(databaseInternalName); - } - /* create */ - final Database database = databaseService.findByInternalName(databaseInternalName); - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database.getContainer().getImage(), - database.getContainer(), database); - final CachedConnection cachedConnection = CachedConnection.builder() - .dataSource(dataSource) - .database(database) - .lastUsed(Instant.now()) - .build(); - this.cachedConnections.put(databaseInternalName, cachedConnection); - log.info("Established connection and added database {} to cache pool", databaseInternalName); - return cachedConnection; - } } diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ContainerServiceIntegrationTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ContainerServiceIntegrationTest.java index 9efb25050e5538a366d027d44742700a03fdd287..9d4e8f39eaa3bb02e22c4fdaf2ae518dad96285f 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ContainerServiceIntegrationTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ContainerServiceIntegrationTest.java @@ -109,7 +109,7 @@ public class ContainerServiceIntegrationTest extends BaseUnitTest { public void create_notFound_fails() { final ContainerCreateRequestDto request = ContainerCreateRequestDto.builder() .name(CONTAINER_3_NAME) - .imageId(IMAGE_2_ID) + .imageId(9999L) .build(); /* test */ diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ImageServiceIntegrationTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ImageServiceIntegrationTest.java index d80f6787e49e6a97ea5e95f1a7e7e8cc058e3c88..e5d864af97f894d1d789f9e61c83c00317172ce5 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ImageServiceIntegrationTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ImageServiceIntegrationTest.java @@ -48,12 +48,12 @@ public class ImageServiceIntegrationTest extends BaseUnitTest { @Test public void create_succeeds() throws ImageAlreadyExistsException { final ImageCreateDto request = ImageCreateDto.builder() - .name(IMAGE_2_NAME) - .version(IMAGE_2_VERSION) - .jdbcMethod(IMAGE_2_JDBC) - .dialect(IMAGE_2_DIALECT) - .driverClass(IMAGE_2_DRIVER) - .defaultPort(IMAGE_2_PORT) + .name(IMAGE_1_NAME) + .version(IMAGE_1_VERSION) + .jdbcMethod(IMAGE_1_JDBC) + .dialect(IMAGE_1_DIALECT) + .driverClass(IMAGE_1_DRIVER) + .defaultPort(IMAGE_1_PORT) .build(); final Principal principal = new BasicUserPrincipal(USER_1_USERNAME); diff --git a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java index 6fa29160b3969cb28934c3bd33e3ac1522dfd200..fe9a744280e4a0ffb7e3e198952cd729e45076d4 100644 --- a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java +++ b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java @@ -814,16 +814,6 @@ public abstract class BaseTest { .version(IMAGE_1_VERSION) .build(); - public final static Long IMAGE_2_ID = 2L; - public final static String IMAGE_2_NAME = "mariadb"; - public final static String IMAGE_2_VERSION = "8.0"; - public final static Integer IMAGE_2_PORT = 3306; - public final static String IMAGE_2_DIALECT = "org.hibernate.dialect.MySQLDialect"; - public final static String IMAGE_2_DRIVER = "com.mysql.jdbc.Driver"; - public final static String IMAGE_2_JDBC = "mysql"; - public final static Long IMAGE_2_SIZE = 12000L; - public final static Instant IMAGE_2_BUILT = Instant.now().minus(38, HOURS); - public final static Long CONTAINER_1_ID = 1L; public final static ContainerImage CONTAINER_1_IMAGE = IMAGE_1; public final static ImageBriefDto CONTAINER_1_IMAGE_BRIEF_DTO = IMAGE_1_BRIEF_DTO;