Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Azure Service Bus Emulator container to Azure module #9795

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion docs/modules/azure.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ This module is INCUBATING. While it is ready for use and operational in the curr

Testcontainers module for the Microsoft Azure's [SDK](https://github.com/Azure/azure-sdk-for-java).

Currently, the module supports `Azurite` and `CosmosDB` emulators. In order to use them, you should use the following classes:
Currently, the module supports `Azurite`, `Azure Service Bus` and `CosmosDB` emulators. In order to use them, you should use the following classes:

Class | Container Image
-|-
AzuriteContainer | [mcr.microsoft.com/azure-storage/azurite](https://github.com/microsoft/containerregistry)
AzureServiceBusEmulatorContainer | [mcr.microsoft.com/azure-messaging/servicebus-emulator](https://github.com/microsoft/containerregistry)
CosmosDBEmulatorContainer | [mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator](https://github.com/microsoft/containerregistry)

## Usage example
Expand Down Expand Up @@ -72,6 +73,38 @@ Build Azure Table client:
!!! note
We can use custom credentials the same way as defined in the Blob section.

### Azure Service Bus Emulator

<!--codeinclude-->
[Configuring the Azure Service Bus Emulator container](../../modules/azure/src/test/resources/service-bus-config.json)
<!--/codeinclude-->

Start Azure Service Bus Emulator during a test:

<!--codeinclude-->
[Setting up a network](../../modules/azure/src/test/java/org/testcontainers/azure/AzureServiceBusEmulatorContainerTest.java) inside_block:network
<!--/codeinclude-->

<!--codeinclude-->
[Starting a SQL Server container as dependency](../../modules/azure/src/test/java/org/testcontainers/azure/AzureServiceBusEmulatorContainerTest.java) inside_block:sqlContainer
<!--/codeinclude-->

<!--codeinclude-->
[Starting a Service Bus Emulator container](../../modules/azure/src/test/java/org/testcontainers/azure/AzureServiceBusEmulatorContainerTest.java) inside_block:emulatorContainer
<!--/codeinclude-->

#### Using Azure Service Bus clients

Configure the sender and the processor clients:

<!--codeinclude-->
[Configuring the sender client](../../modules/azure/src/test/java/org/testcontainers/azure/AzureServiceBusEmulatorContainerTest.java) inside_block:senderClient
<!--/codeinclude-->

<!--codeinclude-->
[Configuring the processor client](../../modules/azure/src/test/java/org/testcontainers/azure/AzureServiceBusEmulatorContainerTest.java) inside_block:processorClient
<!--/codeinclude-->

### CosmosDB

Start Azure CosmosDB Emulator during a test:
Expand Down
3 changes: 3 additions & 0 deletions modules/azure/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ description = "Testcontainers :: Azure"

dependencies {
api project(':testcontainers')
api project(':mssqlserver')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

documentation must mention dependency on this module

Suggested change
api project(':mssqlserver')
testImplementation project(':mssqlserver')

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left as-is due to: #9795 (comment)

// TODO use JDK's HTTP client and/or Apache HttpClient5
shaded 'com.squareup.okhttp3:okhttp:4.12.0'

Expand All @@ -10,4 +11,6 @@ dependencies {
testImplementation 'com.azure:azure-storage-blob:12.29.0'
testImplementation 'com.azure:azure-storage-queue:12.24.0'
testImplementation 'com.azure:azure-data-tables:12.5.0'
testImplementation 'com.azure:azure-messaging-servicebus:7.17.8'
testImplementation 'com.microsoft.sqlserver:mssql-jdbc:12.8.1.jre8'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package org.testcontainers.azure;

import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.MSSQLServerContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.images.builder.Transferable;
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.LicenseAcceptance;

/**
* Testcontainers implementation for Azure Service Bus Emulator.
* <p>
* Supported image: {@code mcr.microsoft.com/azure-messaging/servicebus-emulator}
* <p>
* Exposed port: 5672
*/
public class AzureServiceBusEmulatorContainer extends GenericContainer<AzureServiceBusEmulatorContainer> {

private static final String CONNECTION_STRING_FORMAT =
"Endpoint=sb://%s:%d;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;";

private static final int DEFAULT_PORT = 5672;

private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(
"mcr.microsoft.com/azure-messaging/servicebus-emulator"
);

private final MSSQLServerContainer<?> mssqlServerContainer;

/**
* @param dockerImageName The specified docker image name to run
* @param mssqlServerContainer The MS SQL Server container used by Service Bus as a dependency
*/
public AzureServiceBusEmulatorContainer(
final DockerImageName dockerImageName,
final MSSQLServerContainer<?> mssqlServerContainer
) {
super(dockerImageName);
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
this.mssqlServerContainer = mssqlServerContainer;
dependsOn(mssqlServerContainer);
withExposedPorts(DEFAULT_PORT);
waitingFor(Wait.forLogMessage(".*Emulator Service is Successfully Up!.*", 1));
}

/**
* Provide the Service Bus configuration JSON.
*
* @param config The configuration
* @return this
*/
public AzureServiceBusEmulatorContainer withConfig(final Transferable config) {
withCopyToContainer(config, "/ServiceBus_Emulator/ConfigFiles/Config.json");
return this;
}

/**
* Accepts the EULA of the container.
*
* @return this
*/
public AzureServiceBusEmulatorContainer acceptLicense() {
return withEnv("ACCEPT_EULA", "Y");
}

@Override
protected void configure() {
withEnv("SQL_SERVER", mssqlServerContainer.getNetworkAliases().get(0));
withEnv("MSSQL_SA_PASSWORD", mssqlServerContainer.getPassword());
// If license was not accepted programmatically, check if it was accepted via resource file
if (!getEnvMap().containsKey("ACCEPT_EULA")) {
LicenseAcceptance.assertLicenseAccepted(this.getDockerImageName());
acceptLicense();
}
}

/**
* Returns the connection string.
*
* @return connection string
*/
public String getConnectionString() {
return String.format(CONNECTION_STRING_FORMAT, getHost(), getMappedPort(DEFAULT_PORT));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package org.testcontainers.azure;

import com.azure.messaging.servicebus.ServiceBusClientBuilder;
import com.azure.messaging.servicebus.ServiceBusErrorContext;
import com.azure.messaging.servicebus.ServiceBusMessage;
import com.azure.messaging.servicebus.ServiceBusProcessorClient;
import com.azure.messaging.servicebus.ServiceBusReceivedMessage;
import com.azure.messaging.servicebus.ServiceBusReceivedMessageContext;
import com.azure.messaging.servicebus.ServiceBusSenderClient;
import com.azure.messaging.servicebus.ServiceBusTransactionContext;
import com.github.dockerjava.api.model.Capability;
import org.assertj.core.api.Assertions;
import org.junit.Rule;
import org.junit.Test;
import org.testcontainers.containers.MSSQLServerContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.MountableFile;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

import static org.assertj.core.api.Assertions.assertThat;

public class AzureServiceBusEmulatorContainerTest {

@Rule
// network {
public Network network = Network.newNetwork();

nagyesta marked this conversation as resolved.
Show resolved Hide resolved
// }

@Rule
// sqlContainer {
public MSSQLServerContainer<?> mssqlServerContainer = new MSSQLServerContainer<>(
"mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04"
)
eddumelendez marked this conversation as resolved.
Show resolved Hide resolved
.acceptLicense()
.withPassword("yourStrong(!)Password")
.withCreateContainerCmdModifier(cmd -> {
cmd.getHostConfig().withCapAdd(Capability.SYS_PTRACE);
})
.withNetwork(network);

nagyesta marked this conversation as resolved.
Show resolved Hide resolved
// }

@Rule
// emulatorContainer {
public AzureServiceBusEmulatorContainer emulator = new AzureServiceBusEmulatorContainer(
DockerImageName.parse("mcr.microsoft.com/azure-messaging/servicebus-emulator:1.0.1"),
mssqlServerContainer
)
.acceptLicense()
.withConfig(MountableFile.forClasspathResource("/service-bus-config.json"))
.withNetwork(network);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep the constructor only accepting the image as a String or DockerImageName. Rather we can declare dependsOn via de API.

Suggested change
public AzureServiceBusEmulatorContainer emulator = new AzureServiceBusEmulatorContainer(
DockerImageName.parse("mcr.microsoft.com/azure-messaging/servicebus-emulator:1.0.1"),
mssqlServerContainer
)
.acceptLicense()
.withConfig(MountableFile.forClasspathResource("/service-bus-config.json"))
.withNetwork(network);
public AzureServiceBusEmulatorContainer emulator = new AzureServiceBusEmulatorContainer(
DockerImageName.parse("mcr.microsoft.com/azure-messaging/servicebus-emulator:1.0.1")
)
.acceptLicense()
.withConfig(MountableFile.forClasspathResource("/service-bus-config.json"))
.withNetwork(network)
.dependsOn(mssqlServerContainer);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have created a new withMsSqlServerContainer(MSSQLServerContainer) method instead because I needed to get the password and the network alias for the env setup in configure(). For this reason the dependency on the module remained as-is too. Is this acceptable?

// }

@Test
public void testWithClient() throws InterruptedException {
assertThat(emulator.getConnectionString()).startsWith("Endpoint=sb://");

// senderClient {
ServiceBusSenderClient senderClient = new ServiceBusClientBuilder()
.connectionString(emulator.getConnectionString())
.sender()
.queueName("queue.1")
.buildClient();
// }

TimeUnit.SECONDS.sleep(5);
nagyesta marked this conversation as resolved.
Show resolved Hide resolved
ServiceBusTransactionContext transaction = senderClient.createTransaction();
senderClient.sendMessage(new ServiceBusMessage("Hello, Testcontainers!"), transaction);
senderClient.commitTransaction(transaction);
senderClient.close();

TimeUnit.SECONDS.sleep(5);
final List<ServiceBusReceivedMessage> received = new ArrayList<>();
Consumer<ServiceBusReceivedMessageContext> messageConsumer = m -> {
received.add(m.getMessage());
m.complete();
};
Consumer<ServiceBusErrorContext> errorConsumer = e -> Assertions.fail("Unexpected error: " + e);
// processorClient {
ServiceBusProcessorClient processorClient = new ServiceBusClientBuilder()
.connectionString(emulator.getConnectionString())
.processor()
.queueName("queue.1")
.processMessage(messageConsumer)
.processError(errorConsumer)
.buildProcessorClient();
// }
processorClient.start();

TimeUnit.SECONDS.sleep(10);
processorClient.close();
assertThat(received).hasSize(1);
assertThat(received.get(0).getBody().toString()).isEqualTo("Hello, Testcontainers!");
}
}
29 changes: 29 additions & 0 deletions modules/azure/src/test/resources/service-bus-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"UserConfig": {
"Namespaces": [
{
"Name": "sbemulatorns",
"Queues": [
{
"Name": "queue.1",
"Properties": {
"DeadLetteringOnMessageExpiration": false,
"DefaultMessageTimeToLive": "PT1H",
"DuplicateDetectionHistoryTimeWindow": "PT20S",
"ForwardDeadLetteredMessagesTo": "",
"ForwardTo": "",
"LockDuration": "PT1M",
"MaxDeliveryCount": 3,
"RequiresDuplicateDetection": false,
"RequiresSession": false
}
}
],
"Topics": []
}
],
"Logging": {
"Type": "File"
}
}
}
Loading