diff --git a/docs/modules/rabbitmq.md b/docs/modules/rabbitmq.md index 8a9ae9d9b97..4055bb59f06 100644 --- a/docs/modules/rabbitmq.md +++ b/docs/modules/rabbitmq.md @@ -1,8 +1,5 @@ # RabbitMQ Module -!!! note - This module is INCUBATING. While it is ready for use and operational in the current version of Testcontainers, it is possible that it may receive breaking changes in the future. See [our contributing guidelines](/contributing/#incubating-modules) for more information on our incubating modules policy. - ## Adding this module to your project dependencies Add the following dependency to your `pom.xml`/`build.gradle` file: diff --git a/modules/rabbitmq/src/main/java/org/testcontainers/containers/RabbitMQContainer.java b/modules/rabbitmq/src/main/java/org/testcontainers/containers/RabbitMQContainer.java index 3bb45632c54..b7b2eb35051 100644 --- a/modules/rabbitmq/src/main/java/org/testcontainers/containers/RabbitMQContainer.java +++ b/modules/rabbitmq/src/main/java/org/testcontainers/containers/RabbitMQContainer.java @@ -27,7 +27,10 @@ *
+ * Supported image: {@code rabbitmq} + *
+ * Exposed ports: + *
admin account
+ */
+ public String getAdminPassword() {
+ return this.adminPassword;
+ }
+
+ /**
+ * @return The admin user for the admin account
+ */
+ public String getAdminUsername() {
+ return this.adminUsername;
+ }
+
+ public Integer getAmqpPort() {
+ return getMappedPort(DEFAULT_AMQP_PORT);
+ }
+
+ public Integer getAmqpsPort() {
+ return getMappedPort(DEFAULT_AMQPS_PORT);
+ }
+
+ public Integer getHttpsPort() {
+ return getMappedPort(DEFAULT_HTTPS_PORT);
+ }
+
+ public Integer getHttpPort() {
+ return getMappedPort(DEFAULT_HTTP_PORT);
+ }
+
+ /**
+ * @return AMQP URL for use with AMQP clients.
+ */
+ public String getAmqpUrl() {
+ return "amqp://" + getHost() + ":" + getAmqpPort();
+ }
+
+ /**
+ * @return AMQPS URL for use with AMQPS clients.
+ */
+ public String getAmqpsUrl() {
+ return "amqps://" + getHost() + ":" + getAmqpsPort();
+ }
+
+ /**
+ * @return URL of the HTTP management endpoint.
+ */
+ public String getHttpUrl() {
+ return "http://" + getHost() + ":" + getHttpPort();
+ }
+
+ /**
+ * @return URL of the HTTPS management endpoint.
+ */
+ public String getHttpsUrl() {
+ return "https://" + getHost() + ":" + getHttpsPort();
+ }
+
+ /**
+ * Sets the user for the admin (default is guest) + * + * @param adminUsername The admin user. + * @return This container. + */ + public RabbitMQContainer withAdminUser(final String adminUsername) { + this.adminUsername = adminUsername; + return this; + } + + /** + * Sets the password for the admin (default is
guest) + * + * @param adminPassword The admin password. + * @return This container. + */ + public RabbitMQContainer withAdminPassword(final String adminPassword) { + this.adminPassword = adminPassword; + return this; + } + + /** + * Overwrites the default RabbitMQ configuration file with the supplied one. + * + * @param rabbitMQConf The rabbitmq.conf file to use (in sysctl format, don't forget empty line in the end of file) + * @return This container. + */ + public RabbitMQContainer withRabbitMQConfig(MountableFile rabbitMQConf) { + return withRabbitMQConfigSysctl(rabbitMQConf); + } + + /** + * Overwrites the default RabbitMQ configuration file with the supplied one. + * + * This function doesn't work with RabbitMQ < 3.7. + * + * This function and the Sysctl format is recommended for RabbitMQ >= 3.7 + * + * @param rabbitMQConf The rabbitmq.config file to use (in sysctl format, don't forget empty line in the end of file) + * @return This container. + */ + public RabbitMQContainer withRabbitMQConfigSysctl(MountableFile rabbitMQConf) { + withEnv("RABBITMQ_CONFIG_FILE", "/etc/rabbitmq/rabbitmq-custom.conf"); + return withCopyFileToContainer(rabbitMQConf, "/etc/rabbitmq/rabbitmq-custom.conf"); + } + + /** + * Overwrites the default RabbitMQ configuration file with the supplied one. + * + * @param rabbitMQConf The rabbitmq.config file to use (in erlang format) + * @return This container. + */ + public RabbitMQContainer withRabbitMQConfigErlang(MountableFile rabbitMQConf) { + withEnv("RABBITMQ_CONFIG_FILE", "/etc/rabbitmq/rabbitmq-custom.config"); + return withCopyFileToContainer(rabbitMQConf, "/etc/rabbitmq/rabbitmq-custom.config"); + } +} diff --git a/modules/rabbitmq/src/test/java/org/testcontainers/containers/RabbitMQContainerTest.java b/modules/rabbitmq/src/test/java/org/testcontainers/containers/RabbitMQContainerTest.java deleted file mode 100644 index 2893e812f09..00000000000 --- a/modules/rabbitmq/src/test/java/org/testcontainers/containers/RabbitMQContainerTest.java +++ /dev/null @@ -1,335 +0,0 @@ -package org.testcontainers.containers; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import com.rabbitmq.client.DeliverCallback; -import org.junit.jupiter.api.Test; -import org.testcontainers.containers.RabbitMQContainer.SslVerification; -import org.testcontainers.utility.MountableFile; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; -import java.util.Collections; -import java.util.concurrent.TimeoutException; - -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; - -class RabbitMQContainerTest { - - public static final int DEFAULT_AMQPS_PORT = 5671; - - public static final int DEFAULT_AMQP_PORT = 5672; - - public static final int DEFAULT_HTTPS_PORT = 15671; - - public static final int DEFAULT_HTTP_PORT = 15672; - - @Test - void shouldCreateRabbitMQContainer() { - try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) { - container.start(); - - assertThat(container.getAdminPassword()).isEqualTo("guest"); - assertThat(container.getAdminUsername()).isEqualTo("guest"); - - assertThat(container.getAmqpsUrl()) - .isEqualTo(String.format("amqps://%s:%d", container.getHost(), container.getAmqpsPort())); - assertThat(container.getAmqpUrl()) - .isEqualTo(String.format("amqp://%s:%d", container.getHost(), container.getAmqpPort())); - assertThat(container.getHttpsUrl()) - .isEqualTo(String.format("https://%s:%d", container.getHost(), container.getHttpsPort())); - assertThat(container.getHttpUrl()) - .isEqualTo(String.format("http://%s:%d", container.getHost(), container.getHttpPort())); - - assertThat(container.getLivenessCheckPortNumbers()) - .containsExactlyInAnyOrder( - container.getMappedPort(DEFAULT_AMQP_PORT), - container.getMappedPort(DEFAULT_AMQPS_PORT), - container.getMappedPort(DEFAULT_HTTP_PORT), - container.getMappedPort(DEFAULT_HTTPS_PORT) - ); - - assertFunctionality(container); - } - } - - @Test - void shouldCreateRabbitMQContainerWithCustomCredentials() { - try ( - RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE) - .withAdminUser("admin") - .withAdminPassword("admin") - ) { - container.start(); - - assertThat(container.getAdminPassword()).isEqualTo("admin"); - assertThat(container.getAdminUsername()).isEqualTo("admin"); - - assertFunctionality(container); - } - } - - @Test - void shouldCreateRabbitMQContainerWithExchange() throws IOException, InterruptedException { - try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) { - container.withExchange("test-exchange", "direct"); - - container.start(); - - assertThat(container.execInContainer("rabbitmqctl", "list_exchanges").getStdout()) - .containsPattern("test-exchange\\s+direct"); - } - } - - @Test - void shouldCreateRabbitMQContainerWithExchangeInVhost() throws IOException, InterruptedException { - try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) { - container.withVhost("test-vhost"); - container.withExchange( - "test-vhost", - "test-exchange", - "direct", - false, - false, - false, - Collections.emptyMap() - ); - - container.start(); - - assertThat(container.execInContainer("rabbitmqctl", "list_exchanges", "-p", "test-vhost").getStdout()) - .containsPattern("test-exchange\\s+direct"); - } - } - - @Test - void shouldCreateRabbitMQContainerWithQueues() throws IOException, InterruptedException { - try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) { - container - .withQueue("queue-one") - .withQueue("queue-two", false, true, ImmutableMap.of("x-message-ttl", 1000)); - - container.start(); - - assertThat(container.execInContainer("rabbitmqctl", "list_queues", "name", "arguments").getStdout()) - .containsPattern("queue-one"); - assertThat(container.execInContainer("rabbitmqctl", "list_queues", "name", "arguments").getStdout()) - .containsPattern("queue-two\\s.*x-message-ttl"); - } - } - - @Test - void shouldMountConfigurationFile() { - try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) { - container.withRabbitMQConfig(MountableFile.forClasspathResource("/rabbitmq-custom.conf")); - container.start(); - - assertThat(container.getLogs()).contains("debug"); // config file changes log level to `debug` - } - } - - @Test - void shouldMountConfigurationFileErlang() { - try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) { - container.withRabbitMQConfigErlang(MountableFile.forClasspathResource("/rabbitmq-custom.config")); - container.start(); - - assertThat(container.getLogs()).contains("debug"); // config file changes log level to `debug` - } - } - - @Test - void shouldMountConfigurationFileSysctl() { - try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) { - container.withRabbitMQConfigSysctl(MountableFile.forClasspathResource("/rabbitmq-custom.conf")); - container.start(); - - assertThat(container.getLogs()).contains("debug"); // config file changes log level to `debug` - } - } - - @Test - void shouldStartTheWholeEnchilada() throws IOException, InterruptedException { - try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) { - container - .withVhost("vhost1") - .withVhostLimit("vhost1", "max-connections", 1) - .withVhost("vhost2", true) - .withExchange("direct-exchange", "direct") - .withExchange("topic-exchange", "topic") - .withExchange("vhost1", "topic-exchange-2", "topic", false, false, true, Collections.emptyMap()) - .withExchange("vhost2", "topic-exchange-3", "topic") - .withExchange("topic-exchange-4", "topic", false, false, true, Collections.emptyMap()) - .withQueue("queue1") - .withQueue("queue2", true, false, ImmutableMap.of("x-message-ttl", 1000)) - .withQueue("vhost1", "queue3", true, false, ImmutableMap.of("x-message-ttl", 1000)) - .withQueue("vhost2", "queue4") - .withBinding("direct-exchange", "queue1") - .withBinding("vhost1", "topic-exchange-2", "queue3") - .withBinding("vhost2", "topic-exchange-3", "queue4", Collections.emptyMap(), "ss7", "queue") - .withUser("user1", "password1") - .withUser("user2", "password2", ImmutableSet.of("administrator")) - .withPermission("vhost1", "user1", ".*", ".*", ".*") - .withPolicy("max length policy", "^dog", ImmutableMap.of("max-length", 1), 1, "queues") - .withPolicy( - "alternate exchange policy", - "^direct-exchange", - ImmutableMap.of("alternate-exchange", "amq.direct") - ) - .withPolicy("vhost2", "ha-all", ".*", ImmutableMap.of("ha-mode", "all", "ha-sync-mode", "automatic")) - .withOperatorPolicy("operator policy 1", "^queue1", ImmutableMap.of("message-ttl", 1000), 1, "queues") - .withPluginsEnabled("rabbitmq_shovel", "rabbitmq_random_exchange"); - - container.start(); - - assertThat(container.execInContainer("rabbitmqadmin", "list", "queues").getStdout()) - .contains("queue1", "queue2", "queue3", "queue4"); - - assertThat(container.execInContainer("rabbitmqadmin", "list", "exchanges").getStdout()) - .contains( - "direct-exchange", - "topic-exchange", - "topic-exchange-2", - "topic-exchange-3", - "topic-exchange-4" - ); - - assertThat(container.execInContainer("rabbitmqadmin", "list", "bindings").getStdout()) - .contains("direct-exchange", "topic-exchange-2", "topic-exchange-3"); - - assertThat(container.execInContainer("rabbitmqadmin", "list", "users").getStdout()) - .contains("user1", "user2"); - - assertThat(container.execInContainer("rabbitmqadmin", "list", "policies").getStdout()) - .contains("max length policy", "alternate exchange policy"); - - assertThat(container.execInContainer("rabbitmqadmin", "list", "policies", "--vhost=vhost2").getStdout()) - .contains("ha-all", "ha-sync-mode"); - - assertThat(container.execInContainer("rabbitmqadmin", "list", "operator_policies").getStdout()) - .contains("operator policy 1"); - - assertThat( - container.execInContainer("rabbitmq-plugins", "is_enabled", "rabbitmq_shovel", "--quiet").getStdout() - ) - .contains("rabbitmq_shovel is enabled"); - - assertThat( - container - .execInContainer("rabbitmq-plugins", "is_enabled", "rabbitmq_random_exchange", "--quiet") - .getStdout() - ) - .contains("rabbitmq_random_exchange is enabled"); - } - } - - @Test - void shouldThrowExceptionForDodgyJson() { - try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) { - assertThatCode(() -> container.withQueue("queue2", true, false, ImmutableMap.of("x-message-ttl", container)) - ) - .hasMessageStartingWith("Failed to convert arguments into json"); - } - } - - @Test - void shouldWorkWithSSL() { - try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) { - container.withSSL( - MountableFile.forClasspathResource("/certs/server_key.pem", 0644), - MountableFile.forClasspathResource("/certs/server_certificate.pem", 0644), - MountableFile.forClasspathResource("/certs/ca_certificate.pem", 0644), - SslVerification.VERIFY_PEER, - true - ); - - container.start(); - - assertThatCode(() -> { - ConnectionFactory connectionFactory = new ConnectionFactory(); - connectionFactory.useSslProtocol( - createSslContext("certs/client_key.p12", "password", "certs/truststore.jks", "password") - ); - connectionFactory.enableHostnameVerification(); - connectionFactory.setUri(container.getAmqpsUrl()); - connectionFactory.setPassword(container.getAdminPassword()); - Connection connection = connectionFactory.newConnection(); - Channel channel = connection - .openChannel() - .orElseThrow(() -> new RuntimeException("Failed to Open channel")); - channel.close(); - connection.close(); - }) - .doesNotThrowAnyException(); - } - } - - private SSLContext createSslContext( - String keystoreFile, - String keystorePassword, - String truststoreFile, - String truststorePassword - ) - throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, KeyManagementException { - ClassLoader classLoader = getClass().getClassLoader(); - - KeyStore ks = KeyStore.getInstance("PKCS12"); - ks.load( - Files.newInputStream(new File(classLoader.getResource(keystoreFile).getFile()).toPath()), - keystorePassword.toCharArray() - ); - KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); - kmf.init(ks, "password".toCharArray()); - - KeyStore trustStore = KeyStore.getInstance("PKCS12"); - trustStore.load( - Files.newInputStream(new File(classLoader.getResource(truststoreFile).getFile()).toPath()), - truststorePassword.toCharArray() - ); - TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); - tmf.init(trustStore); - - SSLContext c = SSLContext.getInstance("TLSv1.2"); - c.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - return c; - } - - private void assertFunctionality(RabbitMQContainer container) { - String queueName = "test-queue"; - String text = "Hello World!"; - - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost(container.getHost()); - factory.setPort(container.getAmqpPort()); - factory.setUsername(container.getAdminUsername()); - factory.setPassword(container.getAdminPassword()); - try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) { - channel.queueDeclare(queueName, false, false, false, null); - channel.basicPublish("", queueName, null, text.getBytes()); - - DeliverCallback deliverCallback = (consumerTag, delivery) -> { - String message = new String(delivery.getBody(), StandardCharsets.UTF_8); - assertThat(message).isEqualTo(text); - }; - channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {}); - } catch (IOException | TimeoutException e) { - throw new RuntimeException(e); - } - } -} diff --git a/modules/rabbitmq/src/test/java/org/testcontainers/rabbitmq/RabbitMQContainerTest.java b/modules/rabbitmq/src/test/java/org/testcontainers/rabbitmq/RabbitMQContainerTest.java new file mode 100644 index 00000000000..b3434b2d69d --- /dev/null +++ b/modules/rabbitmq/src/test/java/org/testcontainers/rabbitmq/RabbitMQContainerTest.java @@ -0,0 +1,123 @@ +package org.testcontainers.rabbitmq; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.DeliverCallback; +import org.junit.jupiter.api.Test; +import org.testcontainers.utility.MountableFile; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeoutException; + +import static org.assertj.core.api.Assertions.assertThat; + +class RabbitMQContainerTest { + + public static final int DEFAULT_AMQPS_PORT = 5671; + + public static final int DEFAULT_AMQP_PORT = 5672; + + public static final int DEFAULT_HTTPS_PORT = 15671; + + public static final int DEFAULT_HTTP_PORT = 15672; + + @Test + void shouldCreateRabbitMQContainer() { + try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) { + container.start(); + + assertThat(container.getAdminPassword()).isEqualTo("guest"); + assertThat(container.getAdminUsername()).isEqualTo("guest"); + + assertThat(container.getAmqpsUrl()) + .isEqualTo(String.format("amqps://%s:%d", container.getHost(), container.getAmqpsPort())); + assertThat(container.getAmqpUrl()) + .isEqualTo(String.format("amqp://%s:%d", container.getHost(), container.getAmqpPort())); + assertThat(container.getHttpsUrl()) + .isEqualTo(String.format("https://%s:%d", container.getHost(), container.getHttpsPort())); + assertThat(container.getHttpUrl()) + .isEqualTo(String.format("http://%s:%d", container.getHost(), container.getHttpPort())); + + assertThat(container.getLivenessCheckPortNumbers()) + .containsExactlyInAnyOrder( + container.getMappedPort(DEFAULT_AMQP_PORT), + container.getMappedPort(DEFAULT_AMQPS_PORT), + container.getMappedPort(DEFAULT_HTTP_PORT), + container.getMappedPort(DEFAULT_HTTPS_PORT) + ); + + assertFunctionality(container); + } + } + + @Test + void shouldCreateRabbitMQContainerWithCustomCredentials() { + try ( + RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE) + .withAdminUser("admin") + .withAdminPassword("admin") + ) { + container.start(); + + assertThat(container.getAdminPassword()).isEqualTo("admin"); + assertThat(container.getAdminUsername()).isEqualTo("admin"); + + assertFunctionality(container); + } + } + + @Test + void shouldMountConfigurationFile() { + try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) { + container.withRabbitMQConfig(MountableFile.forClasspathResource("/rabbitmq-custom.conf")); + container.start(); + + assertThat(container.getLogs()).contains("debug"); // config file changes log level to `debug` + } + } + + @Test + void shouldMountConfigurationFileErlang() { + try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) { + container.withRabbitMQConfigErlang(MountableFile.forClasspathResource("/rabbitmq-custom.config")); + container.start(); + + assertThat(container.getLogs()).contains("debug"); // config file changes log level to `debug` + } + } + + @Test + void shouldMountConfigurationFileSysctl() { + try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) { + container.withRabbitMQConfigSysctl(MountableFile.forClasspathResource("/rabbitmq-custom.conf")); + container.start(); + + assertThat(container.getLogs()).contains("debug"); // config file changes log level to `debug` + } + } + + private void assertFunctionality(RabbitMQContainer container) { + String queueName = "test-queue"; + String text = "Hello World!"; + + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost(container.getHost()); + factory.setPort(container.getAmqpPort()); + factory.setUsername(container.getAdminUsername()); + factory.setPassword(container.getAdminPassword()); + try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) { + channel.queueDeclare(queueName, false, false, false, null); + channel.basicPublish("", queueName, null, text.getBytes()); + + DeliverCallback deliverCallback = (consumerTag, delivery) -> { + String message = new String(delivery.getBody(), StandardCharsets.UTF_8); + assertThat(message).isEqualTo(text); + }; + channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {}); + } catch (IOException | TimeoutException e) { + throw new RuntimeException(e); + } + } +} diff --git a/modules/rabbitmq/src/test/java/org/testcontainers/containers/RabbitMQTestImages.java b/modules/rabbitmq/src/test/java/org/testcontainers/rabbitmq/RabbitMQTestImages.java similarity index 82% rename from modules/rabbitmq/src/test/java/org/testcontainers/containers/RabbitMQTestImages.java rename to modules/rabbitmq/src/test/java/org/testcontainers/rabbitmq/RabbitMQTestImages.java index 0898cbdb350..56294f1c196 100644 --- a/modules/rabbitmq/src/test/java/org/testcontainers/containers/RabbitMQTestImages.java +++ b/modules/rabbitmq/src/test/java/org/testcontainers/rabbitmq/RabbitMQTestImages.java @@ -1,4 +1,4 @@ -package org.testcontainers.containers; +package org.testcontainers.rabbitmq; import org.testcontainers.utility.DockerImageName;