From 5888ac5a0d7104467d55cc46f5cef91d027d84a8 Mon Sep 17 00:00:00 2001 From: Anatolii Karlov Date: Mon, 27 Nov 2023 17:21:22 +0200 Subject: [PATCH] add @OpensearchTestcontainer (#28) --- README.md | 18 ++-- pom.xml | 4 +- .../opensearch/OpensearchTestcontainer.java | 23 +++++ .../OpensearchTestcontainerExtension.java | 92 +++++++++++++++++++ .../OpensearchTestcontainerFactory.java | 65 +++++++++++++ .../OpensearchTestcontainerSingleton.java | 25 +++++ .../PostgresqlTestcontainerFactory.java | 10 +- src/main/resources/META-INF/spring.factories | 3 +- .../resources/testcontainers-annotations.yml | 2 + 9 files changed, 227 insertions(+), 15 deletions(-) create mode 100644 src/main/java/dev/vality/testcontainers/annotations/opensearch/OpensearchTestcontainer.java create mode 100644 src/main/java/dev/vality/testcontainers/annotations/opensearch/OpensearchTestcontainerExtension.java create mode 100644 src/main/java/dev/vality/testcontainers/annotations/opensearch/OpensearchTestcontainerFactory.java create mode 100644 src/main/java/dev/vality/testcontainers/annotations/opensearch/OpensearchTestcontainerSingleton.java diff --git a/README.md b/README.md index 359109e..d645aee 100644 --- a/README.md +++ b/README.md @@ -17,17 +17,19 @@ confluentinc/cp-kafka yandex/clickhouse-server ceph/daemon minio/minio +opensearchproject/opensearch ``` Базовые аннотации для использования: -```java -@PostgresqlTestcontainer /@PostgresqlTestcontainerSingleton -@KafkaTestcontainer /@KafkaTestcontainerSingleton -@ClickhouseTestcontainer /@ClickhouseTestcontainerSingleton -@CephTestcontainer /@CephTestcontainerSingleton -@MinioTestcontainer /@MinioTestcontainerSingleton -``` +| basic | singleton | +|--------------------------|-----------------------------------| +| @PostgresqlTestcontainer | @PostgresqlTestcontainerSingleton | +| @KafkaTestcontainer | @KafkaTestcontainerSingleton | +| @ClickhouseTestcontainer | @ClickhouseTestcontainerSingleton | +| @CephTestcontainer | @CephTestcontainerSingleton | +| @MinioTestcontainer | @MinioTestcontainerSingleton | +| @OpensearchTestcontainer | @OpensearchTestcontainerSingleton | Для изменения `docker image tag`, который используется тестконтейнерами нужно переопределить параметры в `application.yml`: @@ -47,6 +49,8 @@ testcontainers: tag: 'RELEASE.2021-10-13T00-23-17Z' user: 'minio' password: 'minio123' + opensearch: + tag: '2.0.0' ``` Eсли параметр не указан библиотека будет использовать параметры по умолчанию, указанные в репозитории в diff --git a/pom.xml b/pom.xml index bba69df..0af4784 100644 --- a/pom.xml +++ b/pom.xml @@ -6,13 +6,13 @@ dev.vality library-parent-pom - 2.0.0 + 2.0.1 jar testcontainers-annotations - 2.0.2 + 2.0.3 testcontainers-annotations testcontainers-annotations diff --git a/src/main/java/dev/vality/testcontainers/annotations/opensearch/OpensearchTestcontainer.java b/src/main/java/dev/vality/testcontainers/annotations/opensearch/OpensearchTestcontainer.java new file mode 100644 index 0000000..e15d812 --- /dev/null +++ b/src/main/java/dev/vality/testcontainers/annotations/opensearch/OpensearchTestcontainer.java @@ -0,0 +1,23 @@ +package dev.vality.testcontainers.annotations.opensearch; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(OpensearchTestcontainerExtension.class) +public @interface OpensearchTestcontainer { + + /** + * Аналогичный параметр как у аннотации {@link SpringBootTest#properties()} + *

+ * пример — properties = {"opensearch.make.happy=true",...} + */ + String[] properties() default {}; + +} diff --git a/src/main/java/dev/vality/testcontainers/annotations/opensearch/OpensearchTestcontainerExtension.java b/src/main/java/dev/vality/testcontainers/annotations/opensearch/OpensearchTestcontainerExtension.java new file mode 100644 index 0000000..3b6c7b3 --- /dev/null +++ b/src/main/java/dev/vality/testcontainers/annotations/opensearch/OpensearchTestcontainerExtension.java @@ -0,0 +1,92 @@ +package dev.vality.testcontainers.annotations.opensearch; + +import dev.vality.testcontainers.annotations.util.GenericContainerUtil; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.support.AnnotationSupport; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.ContextCustomizerFactory; +import org.testcontainers.containers.GenericContainer; + +import java.util.List; +import java.util.Optional; + +@Slf4j +public class OpensearchTestcontainerExtension implements BeforeAllCallback, AfterAllCallback { + + private static final ThreadLocal> THREAD_CONTAINER = new ThreadLocal<>(); + + @Override + public void beforeAll(ExtensionContext context) { + if (findPrototypeAnnotation(context).isPresent()) { + var container = OpensearchTestcontainerFactory.container(); + GenericContainerUtil.startContainer(container); + THREAD_CONTAINER.set(container); + } else if (findSingletonAnnotation(context).isPresent()) { + var container = OpensearchTestcontainerFactory.singletonContainer(); + if (!container.isRunning()) { + GenericContainerUtil.startContainer(container); + } + THREAD_CONTAINER.set(container); + } + } + + @Override + public void afterAll(ExtensionContext context) { + if (findPrototypeAnnotation(context).isPresent()) { + var container = THREAD_CONTAINER.get(); + if (container != null && container.isRunning()) { + container.stop(); + } + THREAD_CONTAINER.remove(); + } else if (findSingletonAnnotation(context).isPresent()) { + THREAD_CONTAINER.remove(); + } + } + + private static Optional findPrototypeAnnotation(ExtensionContext context) { + return AnnotationSupport.findAnnotation(context.getElement(), OpensearchTestcontainer.class); + } + + private static Optional findPrototypeAnnotation(Class testClass) { + return AnnotationSupport.findAnnotation(testClass, OpensearchTestcontainer.class); + } + + private static Optional findSingletonAnnotation(ExtensionContext context) { + return AnnotationSupport.findAnnotation(context.getElement(), OpensearchTestcontainerSingleton.class); + } + + private static Optional findSingletonAnnotation(Class testClass) { + return AnnotationSupport.findAnnotation(testClass, OpensearchTestcontainerSingleton.class); + } + + public static class OpensearchTestcontainerContextCustomizerFactory implements ContextCustomizerFactory { + + @Override + public ContextCustomizer createContextCustomizer( + Class testClass, + List configAttributes) { + return (context, mergedConfig) -> { + if (findPrototypeAnnotation(testClass).isPresent()) { + init(context, findPrototypeAnnotation(testClass).get().properties()); //NOSONAR + } else if (findSingletonAnnotation(testClass).isPresent()) { + init(context, findSingletonAnnotation(testClass).get().properties()); //NOSONAR + } + }; + } + + private void init(ConfigurableApplicationContext context, String[] properties) { + var container = THREAD_CONTAINER.get(); + TestPropertyValues.of( + "opensearch.hostname=" + container.getHost(), + "opensearch.port=" + container.getFirstMappedPort()) + .and(properties) + .applyTo(context); + } + } +} diff --git a/src/main/java/dev/vality/testcontainers/annotations/opensearch/OpensearchTestcontainerFactory.java b/src/main/java/dev/vality/testcontainers/annotations/opensearch/OpensearchTestcontainerFactory.java new file mode 100644 index 0000000..dc9142b --- /dev/null +++ b/src/main/java/dev/vality/testcontainers/annotations/opensearch/OpensearchTestcontainerFactory.java @@ -0,0 +1,65 @@ +package dev.vality.testcontainers.annotations.opensearch; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.Synchronized; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; +import org.testcontainers.utility.DockerImageName; + +import java.util.UUID; + +import static dev.vality.testcontainers.annotations.util.SpringApplicationPropertiesLoader.loadDefaultLibraryProperty; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class OpensearchTestcontainerFactory { + + private static final String OPENSEARCH_IMAGE_NAME = "opensearchproject/opensearch"; + private static final String TAG_PROPERTY = "testcontainers.opensearch.tag"; + + private GenericContainer opensearchContainer; + + public static GenericContainer container() { + return instance().create(); + } + + public static GenericContainer singletonContainer() { + return instance().getOrCreateSingletonContainer(); + } + + private static OpensearchTestcontainerFactory instance() { + return SingletonHolder.INSTANCE; + } + + @Synchronized + private GenericContainer getOrCreateSingletonContainer() { + if (opensearchContainer != null) { + return opensearchContainer; + } + opensearchContainer = create(); + return opensearchContainer; + } + + private GenericContainer create() { + try (GenericContainer container = new GenericContainer<>( + DockerImageName + .parse(OPENSEARCH_IMAGE_NAME) + .withTag(loadDefaultLibraryProperty(TAG_PROPERTY)))) { + container.withNetworkAliases("opensearch-" + UUID.randomUUID()); + container.withExposedPorts(9200, 9600); + container.setWaitStrategy((new HttpWaitStrategy()) + .forPort(9200) + .forStatusCodeMatching(response -> response == 200 || response == 401)); + container.withEnv("discovery.type", "single-node"); + container.withEnv("DISABLE_INSTALL_DEMO_CONFIG", "true"); + container.withEnv("DISABLE_SECURITY_PLUGIN", "true"); + return container; + } + } + + private static class SingletonHolder { + + private static final OpensearchTestcontainerFactory INSTANCE = new OpensearchTestcontainerFactory(); + + } +} diff --git a/src/main/java/dev/vality/testcontainers/annotations/opensearch/OpensearchTestcontainerSingleton.java b/src/main/java/dev/vality/testcontainers/annotations/opensearch/OpensearchTestcontainerSingleton.java new file mode 100644 index 0000000..195378e --- /dev/null +++ b/src/main/java/dev/vality/testcontainers/annotations/opensearch/OpensearchTestcontainerSingleton.java @@ -0,0 +1,25 @@ +package dev.vality.testcontainers.annotations.opensearch; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(OpensearchTestcontainerExtension.class) +@Transactional +public @interface OpensearchTestcontainerSingleton { + + /** + * Аналогичный параметр как у аннотации {@link SpringBootTest#properties()} + *

+ * пример — properties = {"opensearch.make.happy=true",...} + */ + String[] properties() default {}; + +} diff --git a/src/main/java/dev/vality/testcontainers/annotations/postgresql/PostgresqlTestcontainerFactory.java b/src/main/java/dev/vality/testcontainers/annotations/postgresql/PostgresqlTestcontainerFactory.java index b803058..9dd4fd8 100644 --- a/src/main/java/dev/vality/testcontainers/annotations/postgresql/PostgresqlTestcontainerFactory.java +++ b/src/main/java/dev/vality/testcontainers/annotations/postgresql/PostgresqlTestcontainerFactory.java @@ -23,7 +23,7 @@ public class PostgresqlTestcontainerFactory { private static final String POSTGRESQL_IMAGE_NAME = "postgres"; private static final String TAG_PROPERTY = "testcontainers.postgresql.tag"; - private PostgreSQLContainer postgreSqlContainer; + private PostgreSQLContainer postgresqlContainer; public static PostgreSQLContainer container() { return instance().create(); @@ -39,11 +39,11 @@ public class PostgresqlTestcontainerFactory { @Synchronized private PostgreSQLContainer getOrCreateSingletonContainer() { - if (postgreSqlContainer != null) { - return postgreSqlContainer; + if (postgresqlContainer != null) { + return postgresqlContainer; } - postgreSqlContainer = create(); - return postgreSqlContainer; + postgresqlContainer = create(); + return postgresqlContainer; } private PostgreSQLContainer create() { diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories index 46a544b..618124e 100644 --- a/src/main/resources/META-INF/spring.factories +++ b/src/main/resources/META-INF/spring.factories @@ -4,4 +4,5 @@ dev.vality.testcontainers.annotations.postgresql.PostgresqlTestcontainerExtensio dev.vality.testcontainers.annotations.kafka.KafkaTestcontainerExtension.KafkaTestcontainerContextCustomizerFactory,\ dev.vality.testcontainers.annotations.clickhouse.ClickhouseTestcontainerExtension.ClickhouseTestcontainerContextCustomizerFactory,\ dev.vality.testcontainers.annotations.ceph.CephTestcontainerExtension.CephTestcontainerContextCustomizerFactory,\ -dev.vality.testcontainers.annotations.minio.MinioTestcontainerExtension.MinioTestcontainerContextCustomizerFactory +dev.vality.testcontainers.annotations.minio.MinioTestcontainerExtension.MinioTestcontainerContextCustomizerFactory,\ +dev.vality.testcontainers.annotations.opensearch.OpensearchTestcontainerExtension.OpensearchTestcontainerContextCustomizerFactory diff --git a/src/main/resources/testcontainers-annotations.yml b/src/main/resources/testcontainers-annotations.yml index ad32f60..38001b8 100644 --- a/src/main/resources/testcontainers-annotations.yml +++ b/src/main/resources/testcontainers-annotations.yml @@ -13,3 +13,5 @@ testcontainers: tag: 'RELEASE.2021-10-13T00-23-17Z' user: 'minio' password: 'minio123' + opensearch: + tag: '2.0.0'