This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 71f546438529 CAMEL-26618: camel-azure-eventhubs - run the integration 
tests on Floci AZ (#29245)
71f546438529 is described below

commit 71f546438529969384462a6343d82f9963372a09
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Nov 9 13:41:30 2026 +0100

    CAMEL-26618: camel-azure-eventhubs - run the integration tests on Floci AZ 
(#29245)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 .../catalog/docs/azure-eventhubs-component.adoc    |  12 +-
 .../apache/camel/catalog/test-infra/metadata.json  |  11 ++
 .../camel-azure/camel-azure-eventhubs/pom.xml      |   9 +
 .../src/main/docs/azure-eventhubs-component.adoc   |  12 +-
 .../camel/component/azure/eventhubs/TestUtils.java |  39 ++--
 .../eventhubs/integration/EventHubsConsumerIT.java |  43 +++--
 .../EventHubsProducerCustomClientIT.java           |  14 +-
 .../eventhubs/integration/EventHubsProducerIT.java |  17 +-
 .../eventhubs/integration/EventProcessorIT.java    |  30 +--
 .../operations/EventHubsProducerOperationsIT.java  |  19 +-
 test-infra/camel-test-infra-all/pom.xml            |  11 ++
 .../src/generated/resources/META-INF/metadata.json |  11 ++
 .../test/infra/azure/common/AzureProperties.java   |   6 +
 .../azure/common/services/FlociAzContainer.java    | 109 +++++++++++
 .../azure/common/services/container.properties     |   6 +
 .../camel-test-infra-azure-eventhubs/pom.xml       |  58 ++++++
 .../clients/AzureEventHubsClientUtils.java         |  44 +++++
 .../eventhubs/common/AzureEventHubsProperties.java |  39 ++++
 .../services/AzureEventHubsInfraService.java}      |  31 ++--
 .../AzureEventHubsLocalContainerInfraService.java  | 201 +++++++++++++++++++++
 .../services/AzureEventHubsRemoteInfraService.java |  62 +++++++
 .../eventhubs/services/AzureEventHubsService.java} |  21 +--
 .../services/AzureEventHubsServiceFactory.java     |  92 ++++++++++
 .../common/AzureServiceBusProperties.java          |   4 -
 .../AzureServiceBusLocalContainerInfraService.java |  99 ++--------
 .../azure/servicebus/services/container.properties |  22 ---
 test-infra/pom.xml                                 |   1 +
 27 files changed, 823 insertions(+), 200 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/azure-eventhubs-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/azure-eventhubs-component.adoc
index 0ac28e852335..716fda655a0d 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/azure-eventhubs-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/azure-eventhubs-component.adoc
@@ -223,9 +223,17 @@ from("direct:start")
 
 == Important Development Notes
 
-When developing on this component, you will need to obtain your Azure 
accessKey to run the integration tests. In addition to the mocked unit tests,
+When developing on this component, in addition to the mocked unit tests,
 you *will need to run the integration tests with every change you make or even 
client upgrade as the Azure client can break things even on minor versions 
upgrade.*
-To run the integration tests, on this component directory, run the following 
maven command:
+The integration tests run against a local 
https://github.com/floci-io/floci-az[Floci AZ] container, which needs Docker.
+To run them, on this component directory, run the following maven command:
+
+[source,bash]
+----
+mvn verify
+----
+
+To run the integration tests against an event hub in Azure, run the following 
maven command instead:
 
 [source,bash]
 ----
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json
index 963e52191569..cc8303394261 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/test-infra/metadata.json
@@ -251,6 +251,17 @@
   "version" : "4.24.0-SNAPSHOT",
   "serviceVersion" : "3.37.0",
   "uiSupported" : false
+}, {
+  "service" : 
"org.apache.camel.test.infra.azure.eventhubs.services.AzureEventHubsInfraService",
+  "description" : "Local Azure Event Hubs service with Floci AZ",
+  "implementation" : 
"org.apache.camel.test.infra.azure.eventhubs.services.AzureEventHubsLocalContainerInfraService",
+  "alias" : [ "azure" ],
+  "aliasImplementation" : [ "event-hubs" ],
+  "groupId" : "org.apache.camel",
+  "artifactId" : "camel-test-infra-azure-eventhubs",
+  "version" : "4.24.0-SNAPSHOT",
+  "serviceVersion" : "0.16.0",
+  "uiSupported" : false
 }, {
   "service" : 
"org.apache.camel.test.infra.infinispan.services.InfinispanInfraService",
   "description" : "Infinispan is a distributed in-memory key/value data store",
diff --git a/components/camel-azure/camel-azure-eventhubs/pom.xml 
b/components/camel-azure/camel-azure-eventhubs/pom.xml
index ce7e502be543..d8276b50aaf2 100644
--- a/components/camel-azure/camel-azure-eventhubs/pom.xml
+++ b/components/camel-azure/camel-azure-eventhubs/pom.xml
@@ -35,6 +35,9 @@
     <properties>
         <!-- the DST tests load the byte code agent of camel-test-dst when the 
JVM starts (see test-dst.adoc) -->
         
<camel.surefire.fork.additional-vmargs>"-javaagent:${net.bytebuddy:byte-buddy-agent:jar}"</camel.surefire.fork.additional-vmargs>
+        <!-- Floci AZ is not available on these platforms -->
+        <skipITs.ppc64le>true</skipITs.ppc64le>
+        <skipITs.s390x>true</skipITs.s390x>
     </properties>
 
     <dependencies>
@@ -89,6 +92,12 @@
             <artifactId>camel-test-dst</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-infra-azure-eventhubs</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git 
a/components/camel-azure/camel-azure-eventhubs/src/main/docs/azure-eventhubs-component.adoc
 
b/components/camel-azure/camel-azure-eventhubs/src/main/docs/azure-eventhubs-component.adoc
index 0ac28e852335..716fda655a0d 100644
--- 
a/components/camel-azure/camel-azure-eventhubs/src/main/docs/azure-eventhubs-component.adoc
+++ 
b/components/camel-azure/camel-azure-eventhubs/src/main/docs/azure-eventhubs-component.adoc
@@ -223,9 +223,17 @@ from("direct:start")
 
 == Important Development Notes
 
-When developing on this component, you will need to obtain your Azure 
accessKey to run the integration tests. In addition to the mocked unit tests,
+When developing on this component, in addition to the mocked unit tests,
 you *will need to run the integration tests with every change you make or even 
client upgrade as the Azure client can break things even on minor versions 
upgrade.*
-To run the integration tests, on this component directory, run the following 
maven command:
+The integration tests run against a local 
https://github.com/floci-io/floci-az[Floci AZ] container, which needs Docker.
+To run them, on this component directory, run the following maven command:
+
+[source,bash]
+----
+mvn verify
+----
+
+To run the integration tests against an event hub in Azure, run the following 
maven command instead:
 
 [source,bash]
 ----
diff --git 
a/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/TestUtils.java
 
b/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/TestUtils.java
index af7d9def72b7..07ef07975741 100644
--- 
a/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/TestUtils.java
+++ 
b/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/TestUtils.java
@@ -19,6 +19,9 @@ package org.apache.camel.component.azure.eventhubs;
 import java.io.IOException;
 import java.util.Properties;
 
+import 
org.apache.camel.test.infra.azure.eventhubs.common.AzureEventHubsProperties;
+import 
org.apache.camel.test.infra.azure.eventhubs.services.AzureEventHubsService;
+import 
org.apache.camel.test.infra.azure.eventhubs.services.AzureEventHubsServiceFactory;
 import org.apache.camel.test.junit6.TestSupport;
 
 public final class TestUtils {
@@ -34,20 +37,32 @@ public final class TestUtils {
         return TestSupport.loadExternalProperties(TestUtils.class, 
"azure_key.properties");
     }
 
-    public static Properties loadAzureAccessFromJvmEnv() throws Exception {
-        final Properties properties = new Properties();
-        if (System.getProperty(CONNECTION_STRING) == null
-                || System.getProperty(BLOB_ACCOUNT_NAME) == null
-                || System.getProperty(BLOB_ACCESS_KEY) == null) {
-            throw new Exception(
-                    "Make sure to supply azure eventHubs connectionString, 
e.g:  mvn verify -DconnectionString=string"
-                                + " -DblobAccountName=blob 
-DblobAccessKey=key");
+    /**
+     * The service of the integration tests: an event hub on a local Floci AZ 
container, unless the connectionString,
+     * blobAccountName and blobAccessKey system properties are set: then the 
event hub of that connection string, with
+     * the checkpoints in that storage account.
+     */
+    public static AzureEventHubsService createService() {
+        String connectionString = System.getProperty(CONNECTION_STRING);
+        if (connectionString != null) {
+            if (System.getProperty(BLOB_ACCOUNT_NAME) == null || 
System.getProperty(BLOB_ACCESS_KEY) == null) {
+                throw new IllegalArgumentException(
+                        "Make sure to supply azure eventHubs connectionString, 
e.g:  mvn verify -DconnectionString=string"
+                                                   + " -DblobAccountName=blob 
-DblobAccessKey=key");
+            }
+            System.setProperty(AzureEventHubsProperties.CONNECTION_STRING, 
connectionString);
+            System.setProperty(AzureEventHubsProperties.BLOB_ACCOUNT_NAME, 
System.getProperty(BLOB_ACCOUNT_NAME));
+            System.setProperty(AzureEventHubsProperties.BLOB_ACCESS_KEY, 
System.getProperty(BLOB_ACCESS_KEY));
+            System.setProperty("azure.instance.type", "remote");
         }
-        properties.setProperty(CONNECTION_STRING, 
System.getProperty(CONNECTION_STRING));
-        properties.setProperty(BLOB_ACCOUNT_NAME, 
System.getProperty(BLOB_ACCOUNT_NAME));
-        properties.setProperty(BLOB_ACCESS_KEY, 
System.getProperty(BLOB_ACCESS_KEY));
 
-        return properties;
+        return AzureEventHubsServiceFactory.createSingletonService();
     }
 
+    /**
+     * Whether the integration tests run against Azure rather than a local 
Floci AZ container
+     */
+    public static boolean isRemote() {
+        return "remote".equals(System.getProperty("azure.instance.type"));
+    }
 }
diff --git 
a/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/EventHubsConsumerIT.java
 
b/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/EventHubsConsumerIT.java
index 209ef537d637..bc68a8c2a4a3 100644
--- 
a/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/EventHubsConsumerIT.java
+++ 
b/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/EventHubsConsumerIT.java
@@ -21,10 +21,10 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Properties;
 
 import com.azure.messaging.eventhubs.EventData;
 import com.azure.messaging.eventhubs.EventHubProducerAsyncClient;
+import com.azure.messaging.eventhubs.checkpointstore.blob.BlobCheckpointStore;
 import com.azure.messaging.eventhubs.models.EventPosition;
 import com.azure.messaging.eventhubs.models.SendOptions;
 import com.azure.storage.blob.BlobContainerAsyncClient;
@@ -37,22 +37,25 @@ import 
org.apache.camel.component.azure.eventhubs.EventHubsConstants;
 import org.apache.camel.component.azure.eventhubs.TestUtils;
 import 
org.apache.camel.component.azure.eventhubs.client.EventHubsClientFactory;
 import org.apache.camel.component.mock.MockEndpoint;
+import 
org.apache.camel.test.infra.azure.eventhubs.clients.AzureEventHubsClientUtils;
+import 
org.apache.camel.test.infra.azure.eventhubs.services.AzureEventHubsService;
 import org.apache.camel.test.junit6.CamelTestSupport;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
-import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
+import org.junit.jupiter.api.extension.RegisterExtension;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
-@EnabledIfSystemProperty(named = "connectionString", matches = ".*",
-                         disabledReason = "Make sure to supply azure eventHubs 
connectionString, e.g:  mvn verify -DconnectionString=string 
-DblobAccountName=blob -DblobAccessKey=key")
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
 class EventHubsConsumerIT extends CamelTestSupport {
 
+    @RegisterExtension
+    static AzureEventHubsService service = TestUtils.createService();
+
     @EndpointInject("mock:result")
     private MockEndpoint result;
 
@@ -61,18 +64,13 @@ class EventHubsConsumerIT extends CamelTestSupport {
     private EventHubsConfiguration configuration;
 
     @BeforeAll
-    public void prepare() throws Exception {
+    public void prepare() {
         containerName = 
RandomStringUtils.secure().nextAlphabetic(5).toLowerCase();
 
-        final Properties properties = TestUtils.loadAzureAccessFromJvmEnv();
-
         configuration = new EventHubsConfiguration();
-        
configuration.setBlobAccessKey(properties.getProperty(TestUtils.BLOB_ACCESS_KEY));
-        
configuration.setBlobAccountName(properties.getProperty(TestUtils.BLOB_ACCOUNT_NAME));
-        configuration.setBlobContainerName(containerName);
-        
configuration.setConnectionString(properties.getProperty(TestUtils.CONNECTION_STRING));
+        configuration.setConnectionString(service.connectionString());
 
-        containerAsyncClient = 
EventHubsClientFactory.createBlobContainerClient(configuration);
+        containerAsyncClient = 
AzureEventHubsClientUtils.createBlobContainerClient(service, containerName);
 
         // create test container
         containerAsyncClient.create().block();
@@ -129,9 +127,8 @@ class EventHubsConsumerIT extends CamelTestSupport {
             @Override
             public void configure() {
                 from("azure-eventhubs:?"
-                     + "connectionString=RAW({{connectionString}})"
-                     + "&blobContainerName=" + containerName + 
"&eventPosition=#eventPosition"
-                     + 
"&blobAccountName={{blobAccountName}}&blobAccessKey=RAW({{blobAccessKey}})")
+                     + "connectionString=RAW(" + service.connectionString() + 
")"
+                     + "&eventPosition=#eventPosition" + checkpointOptions())
                         .to(result);
 
             }
@@ -146,7 +143,23 @@ class EventHubsConsumerIT extends CamelTestSupport {
 
         CamelContext context = super.createCamelContext();
         context.getRegistry().bind("eventPosition", positionMap);
+        if (!TestUtils.isRemote()) {
+            context.getRegistry().bind("checkpointStore", new 
BlobCheckpointStore(containerAsyncClient));
+        }
 
         return context;
     }
+
+    /*
+     * Against Azure the consumer builds its checkpoint store from the Blob 
options, as an application does. A local
+     * service has its Blob storage at an endpoint of its own, which the 
component builds no client for, so the consumer
+     * gets a checkpoint store on that endpoint.
+     */
+    private String checkpointOptions() {
+        if (TestUtils.isRemote()) {
+            return "&blobContainerName=" + containerName + "&blobAccountName=" 
+ service.blobAccountName()
+                   + "&blobAccessKey=RAW(" + service.blobAccessKey() + ")";
+        }
+        return "&checkpointStore=#checkpointStore";
+    }
 }
diff --git 
a/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/EventHubsProducerCustomClientIT.java
 
b/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/EventHubsProducerCustomClientIT.java
index 62471697c986..9d4f45e7b6ac 100644
--- 
a/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/EventHubsProducerCustomClientIT.java
+++ 
b/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/EventHubsProducerCustomClientIT.java
@@ -16,7 +16,6 @@
  */
 package org.apache.camel.component.azure.eventhubs.integration;
 
-import java.util.Properties;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -34,19 +33,21 @@ import 
org.apache.camel.component.azure.eventhubs.EventHubsConstants;
 import org.apache.camel.component.azure.eventhubs.TestUtils;
 import 
org.apache.camel.component.azure.eventhubs.client.EventHubsClientFactory;
 import org.apache.camel.component.mock.MockEndpoint;
+import 
org.apache.camel.test.infra.azure.eventhubs.services.AzureEventHubsService;
 import org.apache.camel.test.junit6.CamelTestSupport;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
-import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
+import org.junit.jupiter.api.extension.RegisterExtension;
 
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-@EnabledIfSystemProperty(named = "connectionString", matches = ".*",
-                         disabledReason = "Make sure to supply azure eventHubs 
connectionString, e.g:  mvn verify -DconnectionString=string 
-DblobAccountName=blob -DblobAccessKey=key")
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
 class EventHubsProducerCustomClientIT extends CamelTestSupport {
+    @RegisterExtension
+    static AzureEventHubsService service = TestUtils.createService();
+
     @EndpointInject
     private ProducerTemplate template;
 
@@ -59,10 +60,9 @@ class EventHubsProducerCustomClientIT extends 
CamelTestSupport {
     private EventHubConsumerAsyncClient consumerAsyncClient;
 
     @BeforeAll
-    public void prepare() throws Exception {
-        final Properties properties = TestUtils.loadAzureAccessFromJvmEnv();
+    public void prepare() {
         final EventHubsConfiguration configuration = new 
EventHubsConfiguration();
-        
configuration.setConnectionString(properties.getProperty(TestUtils.CONNECTION_STRING));
+        configuration.setConnectionString(service.connectionString());
         
configuration.setConsumerGroupName(EventHubClientBuilder.DEFAULT_CONSUMER_GROUP_NAME);
 
         consumerAsyncClient = 
EventHubsClientFactory.createEventHubConsumerAsyncClient(configuration);
diff --git 
a/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/EventHubsProducerIT.java
 
b/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/EventHubsProducerIT.java
index cdb2bf232e0d..4c8ce885c6ed 100644
--- 
a/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/EventHubsProducerIT.java
+++ 
b/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/EventHubsProducerIT.java
@@ -16,7 +16,6 @@
  */
 package org.apache.camel.component.azure.eventhubs.integration;
 
-import java.util.Properties;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
@@ -33,22 +32,24 @@ import 
org.apache.camel.component.azure.eventhubs.EventHubsConstants;
 import org.apache.camel.component.azure.eventhubs.TestUtils;
 import 
org.apache.camel.component.azure.eventhubs.client.EventHubsClientFactory;
 import org.apache.camel.component.mock.MockEndpoint;
+import 
org.apache.camel.test.infra.azure.eventhubs.services.AzureEventHubsService;
 import org.apache.camel.test.junit6.CamelTestSupport;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
-import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
+import org.junit.jupiter.api.extension.RegisterExtension;
 
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-@EnabledIfSystemProperty(named = "connectionString", matches = ".*",
-                         disabledReason = "Make sure to supply azure eventHubs 
connectionString, e.g:  mvn verify -DconnectionString=string 
-DblobAccountName=blob -DblobAccessKey=key")
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
 class EventHubsProducerIT extends CamelTestSupport {
 
+    @RegisterExtension
+    static AzureEventHubsService service = TestUtils.createService();
+
     @EndpointInject
     private ProducerTemplate template;
 
@@ -58,11 +59,9 @@ class EventHubsProducerIT extends CamelTestSupport {
     private EventHubConsumerAsyncClient consumerAsyncClient;
 
     @BeforeAll
-    public void prepare() throws Exception {
-        final Properties properties = TestUtils.loadAzureAccessFromJvmEnv();
-
+    public void prepare() {
         final EventHubsConfiguration configuration = new 
EventHubsConfiguration();
-        
configuration.setConnectionString(properties.getProperty(TestUtils.CONNECTION_STRING));
+        configuration.setConnectionString(service.connectionString());
         
configuration.setConsumerGroupName(EventHubClientBuilder.DEFAULT_CONSUMER_GROUP_NAME);
 
         consumerAsyncClient = 
EventHubsClientFactory.createEventHubConsumerAsyncClient(configuration);
@@ -137,7 +136,7 @@ class EventHubsProducerIT extends CamelTestSupport {
             @Override
             public void configure() {
                 from("direct:sendAsync")
-                        
.to("azure-eventhubs:?connectionString=RAW({{connectionString}})")
+                        .to("azure-eventhubs:?connectionString=RAW(" + 
service.connectionString() + ")")
                         .to(result);
             }
         };
diff --git 
a/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/EventProcessorIT.java
 
b/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/EventProcessorIT.java
index 3f253cc3e6e9..8cb3302c4006 100644
--- 
a/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/EventProcessorIT.java
+++ 
b/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/EventProcessorIT.java
@@ -19,7 +19,6 @@ package 
org.apache.camel.component.azure.eventhubs.integration;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Properties;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
@@ -28,6 +27,7 @@ import com.azure.messaging.eventhubs.EventData;
 import com.azure.messaging.eventhubs.EventHubClientBuilder;
 import com.azure.messaging.eventhubs.EventHubProducerAsyncClient;
 import com.azure.messaging.eventhubs.EventProcessorClient;
+import com.azure.messaging.eventhubs.checkpointstore.blob.BlobCheckpointStore;
 import com.azure.messaging.eventhubs.models.ErrorContext;
 import com.azure.messaging.eventhubs.models.EventContext;
 import com.azure.messaging.eventhubs.models.EventPosition;
@@ -36,42 +36,50 @@ import com.azure.storage.blob.BlobContainerAsyncClient;
 import org.apache.camel.component.azure.eventhubs.EventHubsConfiguration;
 import org.apache.camel.component.azure.eventhubs.TestUtils;
 import 
org.apache.camel.component.azure.eventhubs.client.EventHubsClientFactory;
+import 
org.apache.camel.test.infra.azure.eventhubs.clients.AzureEventHubsClientUtils;
+import 
org.apache.camel.test.infra.azure.eventhubs.services.AzureEventHubsService;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.awaitility.Awaitility;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
-import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
+import org.junit.jupiter.api.extension.RegisterExtension;
 
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-@EnabledIfSystemProperty(named = "connectionString", matches = ".*",
-                         disabledReason = "Make sure to supply azure eventHubs 
connectionString, e.g:  mvn verify -DconnectionString=string 
-DblobAccountName=blob -DblobAccessKey=key")
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
 public class EventProcessorIT {
 
+    @RegisterExtension
+    static AzureEventHubsService service = TestUtils.createService();
+
     private EventHubsConfiguration configuration;
     private BlobContainerAsyncClient containerAsyncClient;
 
     @BeforeAll
-    public void prepare() throws Exception {
-        final Properties properties = TestUtils.loadAzureAccessFromJvmEnv();
+    public void prepare() {
         final String containerName = 
RandomStringUtils.secure().nextAlphabetic(5).toLowerCase();
 
         configuration = new EventHubsConfiguration();
-        
configuration.setConnectionString(properties.getProperty("connectionString"));
+        configuration.setConnectionString(service.connectionString());
         
configuration.setConsumerGroupName(EventHubClientBuilder.DEFAULT_CONSUMER_GROUP_NAME);
-        
configuration.setBlobAccessKey(properties.getProperty("blobAccessKey"));
-        
configuration.setBlobAccountName(properties.getProperty("blobAccountName"));
 
         Map<String, EventPosition> positionMap = new HashMap<>();
         positionMap.put("0", EventPosition.earliest());
 
         configuration.setEventPosition(positionMap);
-        configuration.setBlobContainerName(containerName);
 
-        containerAsyncClient = 
EventHubsClientFactory.createBlobContainerClient(configuration);
+        containerAsyncClient = 
AzureEventHubsClientUtils.createBlobContainerClient(service, containerName);
+        if (TestUtils.isRemote()) {
+            // against Azure the event processor builds its checkpoint store 
from the Blob options, as an application does
+            configuration.setBlobAccountName(service.blobAccountName());
+            configuration.setBlobAccessKey(service.blobAccessKey());
+            configuration.setBlobContainerName(containerName);
+        } else {
+            // a local service has its Blob storage at an endpoint of its own, 
which the component builds no client for
+            configuration.setCheckpointStore(new 
BlobCheckpointStore(containerAsyncClient));
+        }
 
         // create test container
         containerAsyncClient.create().block();
diff --git 
a/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/operations/EventHubsProducerOperationsIT.java
 
b/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/operations/EventHubsProducerOperationsIT.java
index 80786f835149..1e6061227227 100644
--- 
a/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/operations/EventHubsProducerOperationsIT.java
+++ 
b/components/camel-azure/camel-azure-eventhubs/src/test/java/org/apache/camel/component/azure/eventhubs/integration/operations/EventHubsProducerOperationsIT.java
@@ -19,7 +19,6 @@ package 
org.apache.camel.component.azure.eventhubs.integration.operations;
 import java.time.Duration;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Properties;
 import java.util.concurrent.TimeUnit;
 
 import com.azure.messaging.eventhubs.EventHubClientBuilder;
@@ -33,29 +32,29 @@ import org.apache.camel.component.azure.eventhubs.TestUtils;
 import 
org.apache.camel.component.azure.eventhubs.client.EventHubsClientFactory;
 import 
org.apache.camel.component.azure.eventhubs.operations.EventHubsProducerOperations;
 import org.apache.camel.support.DefaultExchange;
+import 
org.apache.camel.test.infra.azure.eventhubs.services.AzureEventHubsService;
 import org.apache.camel.test.junit6.CamelTestSupport;
 import org.awaitility.Awaitility;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInstance;
-import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
+import org.junit.jupiter.api.extension.RegisterExtension;
 
-@EnabledIfSystemProperty(named = "connectionString", matches = ".*",
-                         disabledReason = "Make sure to supply azure eventHubs 
connectionString, e.g:  mvn verify -DconnectionString=string 
-DblobAccountName=blob -DblobAccessKey=key")
 @TestInstance(TestInstance.Lifecycle.PER_CLASS)
 class EventHubsProducerOperationsIT extends CamelTestSupport {
 
+    @RegisterExtension
+    static AzureEventHubsService service = TestUtils.createService();
+
     private EventHubsConfiguration configuration;
     private EventHubProducerAsyncClient producerAsyncClient;
     private EventHubConsumerAsyncClient consumerAsyncClient;
 
     @BeforeAll
-    public void prepare() throws Exception {
-        final Properties properties = TestUtils.loadAzureAccessFromJvmEnv();
-
+    public void prepare() {
         configuration = new EventHubsConfiguration();
-        
configuration.setConnectionString(properties.getProperty("connectionString"));
+        configuration.setConnectionString(service.connectionString());
         
configuration.setConsumerGroupName(EventHubClientBuilder.DEFAULT_CONSUMER_GROUP_NAME);
 
         producerAsyncClient = 
EventHubsClientFactory.createEventHubProducerAsyncClient(configuration);
@@ -112,6 +111,8 @@ class EventHubsProducerOperationsIT extends 
CamelTestSupport {
         exchanges.add(exchange2);
 
         final Exchange exchange = new DefaultExchange(context);
+        // the events are looked for in this partition only
+        exchange.getIn().setHeader(EventHubsConstants.PARTITION_ID, 
firstPartition);
         exchange.getIn().setBody(exchanges);
 
         operations.sendEvents(exchange, doneSync -> {
@@ -155,6 +156,8 @@ class EventHubsProducerOperationsIT extends 
CamelTestSupport {
         messages.add("Test String Message 2");
 
         final Exchange exchange = new DefaultExchange(context);
+        // the events are looked for in this partition only
+        exchange.getIn().setHeader(EventHubsConstants.PARTITION_ID, 
firstPartition);
         exchange.getIn().setBody(messages);
 
         operations.sendEvents(exchange, doneSync -> {
diff --git a/test-infra/camel-test-infra-all/pom.xml 
b/test-infra/camel-test-infra-all/pom.xml
index d1725e0352e9..b01eac332f60 100644
--- a/test-infra/camel-test-infra-all/pom.xml
+++ b/test-infra/camel-test-infra-all/pom.xml
@@ -76,6 +76,11 @@
             <artifactId>camel-test-infra-azure-eventgrid</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-infra-azure-eventhubs</artifactId>
+            <version>${project.version}</version>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-test-infra-azure-functions</artifactId>
@@ -444,6 +449,12 @@
                                         
<artifactId>camel-test-infra-azure-eventgrid</artifactId>
                                     </dependency>
                                 </fileSet>
+                                <fileSet>
+                                    <dependency>
+                                        <groupId>org.apache.camel</groupId>
+                                        
<artifactId>camel-test-infra-azure-eventhubs</artifactId>
+                                    </dependency>
+                                </fileSet>
                                 <fileSet>
                                     <dependency>
                                         <groupId>org.apache.camel</groupId>
diff --git 
a/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json
 
b/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json
index 963e52191569..cc8303394261 100644
--- 
a/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json
+++ 
b/test-infra/camel-test-infra-all/src/generated/resources/META-INF/metadata.json
@@ -251,6 +251,17 @@
   "version" : "4.24.0-SNAPSHOT",
   "serviceVersion" : "3.37.0",
   "uiSupported" : false
+}, {
+  "service" : 
"org.apache.camel.test.infra.azure.eventhubs.services.AzureEventHubsInfraService",
+  "description" : "Local Azure Event Hubs service with Floci AZ",
+  "implementation" : 
"org.apache.camel.test.infra.azure.eventhubs.services.AzureEventHubsLocalContainerInfraService",
+  "alias" : [ "azure" ],
+  "aliasImplementation" : [ "event-hubs" ],
+  "groupId" : "org.apache.camel",
+  "artifactId" : "camel-test-infra-azure-eventhubs",
+  "version" : "4.24.0-SNAPSHOT",
+  "serviceVersion" : "0.16.0",
+  "uiSupported" : false
 }, {
   "service" : 
"org.apache.camel.test.infra.infinispan.services.InfinispanInfraService",
   "description" : "Infinispan is a distributed in-memory key/value data store",
diff --git 
a/test-infra/camel-test-infra-azure-common/src/main/java/org/apache/camel/test/infra/azure/common/AzureProperties.java
 
b/test-infra/camel-test-infra-azure-common/src/main/java/org/apache/camel/test/infra/azure/common/AzureProperties.java
index 74f40e210c7b..7f409b69edb7 100644
--- 
a/test-infra/camel-test-infra-azure-common/src/main/java/org/apache/camel/test/infra/azure/common/AzureProperties.java
+++ 
b/test-infra/camel-test-infra-azure-common/src/main/java/org/apache/camel/test/infra/azure/common/AzureProperties.java
@@ -31,6 +31,12 @@ public final class AzureProperties {
      * The Azurite image of the local Queue service
      */
     public static final String AZURE_STORAGE_QUEUE_CONTAINER = 
"azure.storage.queue.container";
+    /**
+     * The Artemis image of the AMQP brokers that Floci AZ starts for Event 
Hubs and Service Bus. Floci AZ copies AMQP
+     * libraries patched for one Artemis version into it, so the version must 
be the one that the Floci AZ image of
+     * {@link #AZURE_CONTAINER} expects.
+     */
+    public static final String AZURE_BROKER_CONTAINER = 
"azure.broker.container";
 
     private AzureProperties() {
 
diff --git 
a/test-infra/camel-test-infra-azure-common/src/main/java/org/apache/camel/test/infra/azure/common/services/FlociAzContainer.java
 
b/test-infra/camel-test-infra-azure-common/src/main/java/org/apache/camel/test/infra/azure/common/services/FlociAzContainer.java
index 5ce38aca62d2..76839acdf508 100644
--- 
a/test-infra/camel-test-infra-azure-common/src/main/java/org/apache/camel/test/infra/azure/common/services/FlociAzContainer.java
+++ 
b/test-infra/camel-test-infra-azure-common/src/main/java/org/apache/camel/test/infra/azure/common/services/FlociAzContainer.java
@@ -18,13 +18,31 @@
 package org.apache.camel.test.infra.azure.common.services;
 
 import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
 
+import com.github.dockerjava.api.DockerClient;
+import com.github.dockerjava.api.command.CreateContainerCmd;
+import com.github.dockerjava.api.exception.NotFoundException;
+import com.github.dockerjava.api.model.Bind;
+import com.github.dockerjava.api.model.Container;
+import com.github.dockerjava.api.model.HostConfig;
+import com.github.dockerjava.api.model.Volume;
 import org.apache.camel.test.infra.azure.common.AzureCredentialsHolder;
 import org.apache.camel.test.infra.azure.common.AzureProperties;
 import org.apache.camel.test.infra.common.LocalPropertyResolver;
 import org.apache.camel.test.infra.common.services.ContainerEnvironmentUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.DockerClientFactory;
 import org.testcontainers.containers.GenericContainer;
 import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.images.RemoteDockerImage;
+import org.testcontainers.utility.DockerImageName;
+import org.testcontainers.utility.ResourceReaper;
 
 /**
  * A container running Floci AZ (https://github.com/floci-io/floci-az), a 
local Azure emulator. It serves every Azure
@@ -38,6 +56,14 @@ public class FlociAzContainer extends 
GenericContainer<FlociAzContainer> {
     public static final String DEFAULT_ACCOUNT_KEY
             = 
"Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";
 
+    private static final Logger LOG = 
LoggerFactory.getLogger(FlociAzContainer.class);
+    // Floci AZ puts this namespace in the name of the containers it starts, 
and in their floci_namespace label
+    private static final String RESOURCE_NAMESPACE_ENV = 
"FLOCI_AZ_DOCKER_RESOURCE_NAMESPACE";
+    private static final String RESOURCE_NAMESPACE_LABEL = "floci_namespace";
+    private static final String DOCKER_SOCKET = "/var/run/docker.sock";
+
+    private String resourceNamespace;
+
     public FlociAzContainer() {
         this(LocalPropertyResolver.getProperty(FlociAzContainer.class, 
AzureProperties.AZURE_CONTAINER), false);
     }
@@ -81,4 +107,87 @@ public class FlociAzContainer extends 
GenericContainer<FlociAzContainer> {
     public String accountEndpoint() {
         return serviceAddress() + "/" + DEFAULT_ACCOUNT_NAME;
     }
+
+    /**
+     * Lets Floci AZ start containers of its own, such as the Artemis brokers 
of the AMQP data plane of Event Hubs and
+     * Service Bus: mounts the Docker socket of the host, and gives the 
containers that Floci AZ starts a resource
+     * namespace of this container, by which {@link #stop()} removes them. 
Each container has a namespace of its own:
+     * when it starts, Floci AZ removes the containers of its namespace that 
it finds, as left over.
+     */
+    public FlociAzContainer withDockerSocket() {
+        resourceNamespace = "camel" + 
UUID.randomUUID().toString().replace("-", "").substring(0, 12);
+        withEnv(RESOURCE_NAMESPACE_ENV, resourceNamespace);
+        return 
withCreateContainerCmdModifier(FlociAzContainer::bindDockerSocket);
+    }
+
+    /**
+     * Has Floci AZ start an AMQP broker with the Artemis image of {@link 
AzureProperties#AZURE_BROKER_CONTAINER}, which
+     * this method pulls as Testcontainers pulls its own images, so that image 
substitution and registry authentication
+     * apply: Floci AZ would pull it itself otherwise. Needs {@link 
#withDockerSocket()}.
+     *
+     * @param imageVariable the variable of Floci AZ that names the broker 
image of a service, for example
+     *                      {@code FLOCI_AZ_SERVICES_EVENT_HUB_ARTEMIS_IMAGE}
+     */
+    public FlociAzContainer withBrokerImage(String imageVariable) {
+        String image = 
LocalPropertyResolver.getProperty(FlociAzContainer.class, 
AzureProperties.AZURE_BROKER_CONTAINER);
+        return withEnv(imageVariable, new 
RemoteDockerImage(DockerImageName.parse(image)).get());
+    }
+
+    @Override
+    public void start() {
+        if (resourceNamespace != null) {
+            // Testcontainers does not know the containers that Floci AZ 
starts: have them removed when the JVM exits,
+            // should this container not be stopped
+            
ResourceReaper.instance().registerLabelsFilterForCleanup(Map.of(RESOURCE_NAMESPACE_LABEL,
 resourceNamespace));
+        }
+        super.start();
+    }
+
+    @Override
+    public void stop() {
+        try {
+            super.stop();
+        } finally {
+            if (resourceNamespace != null) {
+                removeStartedContainers();
+            }
+        }
+    }
+
+    // the containers that Floci AZ started are not removed with it
+    private void removeStartedContainers() {
+        DockerClient client = DockerClientFactory.instance().client();
+        List<Container> containers = client.listContainersCmd()
+                .withShowAll(true)
+                .withLabelFilter(Map.of(RESOURCE_NAMESPACE_LABEL, 
resourceNamespace))
+                .exec();
+        for (Container container : containers) {
+            LOG.debug("Removing the container {} that Floci AZ started", 
Arrays.toString(container.getNames()));
+            try {
+                
client.removeContainerCmd(container.getId()).withForce(true).withRemoveVolumes(true).exec();
+            } catch (NotFoundException e) {
+                LOG.debug("The container {} was already removed", 
container.getId());
+            }
+        }
+    }
+
+    // where SELinux is enforcing, it denies a container the Docker socket of 
the host unless the labeling of the
+    // container is disabled
+    private static void bindDockerSocket(CreateContainerCmd cmd) {
+        HostConfig hostConfig = cmd.getHostConfig();
+
+        List<Bind> binds = new ArrayList<>();
+        if (hostConfig.getBinds() != null) {
+            binds.addAll(Arrays.asList(hostConfig.getBinds()));
+        }
+        binds.add(new 
Bind(DockerClientFactory.instance().getRemoteDockerUnixSocketPath(), new 
Volume(DOCKER_SOCKET)));
+
+        List<String> securityOpts = new ArrayList<>();
+        if (hostConfig.getSecurityOpts() != null) {
+            securityOpts.addAll(hostConfig.getSecurityOpts());
+        }
+        securityOpts.add("label=disable");
+
+        hostConfig.withBinds(binds).withSecurityOpts(securityOpts);
+    }
 }
diff --git 
a/test-infra/camel-test-infra-azure-common/src/main/resources/org/apache/camel/test/infra/azure/common/services/container.properties
 
b/test-infra/camel-test-infra-azure-common/src/main/resources/org/apache/camel/test/infra/azure/common/services/container.properties
index 3089b7a8a244..98b65b7da966 100644
--- 
a/test-infra/camel-test-infra-azure-common/src/main/resources/org/apache/camel/test/infra/azure/common/services/container.properties
+++ 
b/test-infra/camel-test-infra-azure-common/src/main/resources/org/apache/camel/test/infra/azure/common/services/container.properties
@@ -17,6 +17,12 @@
 # Floci AZ: the local Azure services
 azure.container=mirror.gcr.io/floci/floci-az:0.16.0
 azure.container.version.exclude=latest,nightly,compat
+# Artemis: the AMQP broker that Floci AZ starts for Event Hubs and Service 
Bus. Floci AZ copies AMQP jars patched for one
+# Artemis version into it, so the version must be the one that the Floci AZ 
image of azure.container expects: it is
+# pinned, and changes with azure.container. The key does not end with 
"artemis": the test-infra metadata would take
+# this version for the Artemis services.
+azure.broker.container=mirror.gcr.io/apache/activemq-artemis:2.44.0
+azure.broker.container.version.include=2.44.0
 # Azurite: the local Blob and Queue services, until Floci AZ covers what their 
tests use
 azure.storage.blob.container=mcr.microsoft.com/azure-storage/azurite:3.37.0
 
azure.storage.blob.container.version.exclude=table-alpha,amd64,arm64,preview,latest
diff --git a/test-infra/camel-test-infra-azure-eventhubs/pom.xml 
b/test-infra/camel-test-infra-azure-eventhubs/pom.xml
new file mode 100644
index 000000000000..d059f886c9a1
--- /dev/null
+++ b/test-infra/camel-test-infra-azure-eventhubs/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <parent>
+        <artifactId>camel-test-infra-parent</artifactId>
+        <groupId>org.apache.camel</groupId>
+        <relativePath>../camel-test-infra-parent/pom.xml</relativePath>
+        <version>4.24.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <name>Camel :: Test Infra :: Azure Event Hubs</name>
+    <artifactId>camel-test-infra-azure-eventhubs</artifactId>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>com.azure</groupId>
+                <artifactId>azure-sdk-bom</artifactId>
+                <version>${azure-sdk-bom-version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-infra-azure-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <!-- azure sdk: the Blob container of the checkpoint store -->
+        <dependency>
+            <groupId>com.azure</groupId>
+            <artifactId>azure-storage-blob</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git 
a/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/clients/AzureEventHubsClientUtils.java
 
b/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/clients/AzureEventHubsClientUtils.java
new file mode 100644
index 000000000000..b9c68efdeb79
--- /dev/null
+++ 
b/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/clients/AzureEventHubsClientUtils.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.test.infra.azure.eventhubs.clients;
+
+import com.azure.storage.blob.BlobContainerAsyncClient;
+import com.azure.storage.blob.BlobContainerClientBuilder;
+import com.azure.storage.common.StorageSharedKeyCredential;
+import 
org.apache.camel.test.infra.azure.eventhubs.services.AzureEventHubsInfraService;
+
+public final class AzureEventHubsClientUtils {
+
+    private AzureEventHubsClientUtils() {
+
+    }
+
+    /**
+     * A client of a Blob container in the storage account of the service, for 
example for the
+     * {@code BlobCheckpointStore} of an event processor. A 
camel-azure-eventhubs consumer builds the Blob client of its
+     * checkpoint store for the endpoint of an account in Azure only, so a 
local service needs a checkpoint store built
+     * on this client.
+     */
+    public static BlobContainerAsyncClient 
createBlobContainerClient(AzureEventHubsInfraService service, String 
containerName) {
+        return new BlobContainerClientBuilder()
+                .endpoint(service.blobEndpoint())
+                .containerName(containerName)
+                .credential(new 
StorageSharedKeyCredential(service.blobAccountName(), service.blobAccessKey()))
+                .buildAsyncClient();
+    }
+}
diff --git 
a/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/common/AzureEventHubsProperties.java
 
b/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/common/AzureEventHubsProperties.java
new file mode 100644
index 000000000000..9f3e6d6d3069
--- /dev/null
+++ 
b/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/common/AzureEventHubsProperties.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.test.infra.azure.eventhubs.common;
+
+/**
+ * The system properties of the Event Hubs service: the local service sets 
them, the remote service reads them.
+ */
+public final class AzureEventHubsProperties {
+    /**
+     * The connection string of the event hub, with the name of the event hub 
in its {@code EntityPath}
+     */
+    public static final String CONNECTION_STRING = 
"azure.eventhubs.connection.string";
+    /**
+     * The endpoint of the Blob service that holds the checkpoints. A remote 
service defaults to
+     * {@code https://<blob account name>.blob.core.windows.net}.
+     */
+    public static final String BLOB_ENDPOINT = "azure.eventhubs.blob.endpoint";
+    public static final String BLOB_ACCOUNT_NAME = 
"azure.eventhubs.blob.account.name";
+    public static final String BLOB_ACCESS_KEY = 
"azure.eventhubs.blob.access.key";
+
+    private AzureEventHubsProperties() {
+
+    }
+}
diff --git 
a/test-infra/camel-test-infra-azure-common/src/main/java/org/apache/camel/test/infra/azure/common/AzureProperties.java
 
b/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/services/AzureEventHubsInfraService.java
similarity index 50%
copy from 
test-infra/camel-test-infra-azure-common/src/main/java/org/apache/camel/test/infra/azure/common/AzureProperties.java
copy to 
test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/services/AzureEventHubsInfraService.java
index 74f40e210c7b..aee5f45c64af 100644
--- 
a/test-infra/camel-test-infra-azure-common/src/main/java/org/apache/camel/test/infra/azure/common/AzureProperties.java
+++ 
b/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/services/AzureEventHubsInfraService.java
@@ -15,24 +15,29 @@
  * limitations under the License.
  */
 
-package org.apache.camel.test.infra.azure.common;
+package org.apache.camel.test.infra.azure.eventhubs.services;
+
+import org.apache.camel.test.infra.common.services.InfrastructureService;
+
+/**
+ * Test infra service for Azure Event Hubs: an event hub, and the Blob storage 
account where an event processor keeps
+ * its checkpoints.
+ */
+public interface AzureEventHubsInfraService extends InfrastructureService {
 
-public final class AzureProperties {
-    /**
-     * The Floci AZ image of the local Azure services
-     */
-    public static final String AZURE_CONTAINER = "azure.container";
     /**
-     * The Azurite image of the local Blob service. Blob and Queue keep a key 
each, so the version of the test-infra
-     * metadata is matched by their implementation alias and not by the shared 
{@code azure} alias of Floci AZ.
+     * The connection string of the event hub, with the name of the event hub 
in its {@code EntityPath}, as the
+     * camel-azure-eventhubs connectionString option expects it.
      */
-    public static final String AZURE_STORAGE_BLOB_CONTAINER = 
"azure.storage.blob.container";
+    String connectionString();
+
     /**
-     * The Azurite image of the local Queue service
+     * The endpoint of the Blob service of the storage account that holds the 
checkpoints, for example
+     * {@code https://myaccount.blob.core.windows.net}.
      */
-    public static final String AZURE_STORAGE_QUEUE_CONTAINER = 
"azure.storage.queue.container";
+    String blobEndpoint();
 
-    private AzureProperties() {
+    String blobAccountName();
 
-    }
+    String blobAccessKey();
 }
diff --git 
a/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/services/AzureEventHubsLocalContainerInfraService.java
 
b/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/services/AzureEventHubsLocalContainerInfraService.java
new file mode 100644
index 000000000000..b5a71a6e1d68
--- /dev/null
+++ 
b/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/services/AzureEventHubsLocalContainerInfraService.java
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.test.infra.azure.eventhubs.services;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.camel.spi.annotations.InfraService;
+import org.apache.camel.test.infra.azure.common.services.FlociAzContainer;
+import org.apache.camel.test.infra.azure.common.services.FlociAzInfraService;
+import 
org.apache.camel.test.infra.azure.eventhubs.common.AzureEventHubsProperties;
+import org.apache.camel.test.infra.common.services.ContainerEnvironmentUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A local Event Hubs service with Floci AZ, which runs the AMQP broker of an 
Event Hubs namespace in an Apache ActiveMQ
+ * Artemis container that it starts itself, through the Docker socket mounted 
into the Floci AZ container. The namespace
+ * has one event hub, {@value #EVENT_HUB_NAME}, with {@value #PARTITION_COUNT} 
partitions, and the checkpoints go to the
+ * Blob service of Floci AZ.
+ * <p>
+ * Floci AZ reaches the broker on the Docker network, the clients on the port 
Docker publishes it on. Testcontainers
+ * does not know the broker container: stopping the Floci AZ container removes 
it (see
+ * {@link FlociAzContainer#withDockerSocket()}).
+ * <p>
+ * Floci AZ answers the {@code $management} requests for the properties of the 
event hub, but not for the properties of
+ * a partition. It spreads the events of a batch sent with no partition id or 
key over the partitions, one event after
+ * the other, where Azure stores the whole batch in one partition.
+ */
+@InfraService(service = AzureEventHubsInfraService.class,
+              description = "Local Azure Event Hubs service with Floci AZ",
+              serviceAlias = "azure", serviceImplementationAlias = 
"event-hubs")
+public class AzureEventHubsLocalContainerInfraService extends 
FlociAzInfraService implements AzureEventHubsInfraService {
+    /**
+     * The event hub, in lower case: an event processor of the Azure SDK 
lower-cases the name of its event hub
+     */
+    public static final String EVENT_HUB_NAME = "camel-test";
+    public static final int PARTITION_COUNT = 2;
+    /**
+     * The consumer groups of the event hub. Floci AZ takes their names as 
case-sensitive, while Azure does not, and an
+     * event processor of the Azure SDK lower-cases the name of its consumer 
group: {@code $default} is the default
+     * consumer group of the event processors, {@code $Default} the one of the 
other consumers.
+     */
+    public static final List<String> CONSUMER_GROUPS = List.of("$Default", 
"$default");
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(AzureEventHubsLocalContainerInfraService.class);
+
+    // the namespace that Floci AZ creates by default
+    private static final String NAMESPACE = "emulatorNs1";
+    private static final String ARTEMIS_IMAGE_VARIABLE = 
"FLOCI_AZ_SERVICES_EVENT_HUB_ARTEMIS_IMAGE";
+    private static final Pattern AMQP_HOST_PORT = 
Pattern.compile("\"amqpHostPort\"\\s*:\\s*(\\d+)");
+    // Floci AZ ignores the key: the broker accepts any client
+    private static final String SHARED_ACCESS_KEY_NAME = 
"RootManageSharedAccessKey";
+    private static final String SHARED_ACCESS_KEY = "devkey";
+
+    private int amqpPort;
+    private volatile boolean started;
+
+    @Override
+    protected FlociAzContainer initContainer(String imageName) {
+        FlociAzContainer container = super.initContainer(imageName)
+                .withDockerSocket()
+                .withEnv("FLOCI_AZ_SERVICES_EVENT_HUB_ENTITIES", 
EVENT_HUB_NAME + ":" + PARTITION_COUNT)
+                .withEnv("FLOCI_AZ_SERVICES_EVENT_HUB_CONSUMER_GROUPS", 
String.join(",", CONSUMER_GROUPS));
+        if (!ContainerEnvironmentUtil.isFixedPort(getClass())) {
+            // Docker publishes the broker on free host ports, so that builds 
running at the same time do not clash;
+            // camel infra run keeps the ports 5672 (AMQP) and 5671 (AMQPS) 
that Floci AZ documents
+            container.withEnv("FLOCI_AZ_SERVICES_EVENT_HUB_AMQP_PORT", "0")
+                    .withEnv("FLOCI_AZ_SERVICES_EVENT_HUB_AMQP_TLS_PORT", "0");
+        }
+        return container;
+    }
+
+    @Override
+    public void initialize() {
+        started = true;
+        try {
+            getContainer().withBrokerImage(ARTEMIS_IMAGE_VARIABLE);
+            getContainer().start();
+            amqpPort = startNamespace();
+        } catch (RuntimeException e) {
+            // also removes the broker, should Floci AZ have started it
+            try {
+                shutdown();
+            } catch (RuntimeException suppressed) {
+                e.addSuppressed(suppressed);
+            }
+            throw e;
+        }
+
+        LOG.info("Floci AZ running at address {}, with the Event Hubs broker 
at {}:{}", getContainer().serviceAddress(),
+                host(), amqpPort);
+
+        registerProperties();
+    }
+
+    /**
+     * Floci AZ starts the broker of a namespace when the namespace is 
created, and the request returns once the broker
+     * listens.
+     *
+     * @return the host port of the AMQP port of the broker
+     */
+    private int startNamespace() {
+        URI uri = URI.create(getContainer().serviceAddress() + "/" + 
FlociAzContainer.DEFAULT_ACCOUNT_NAME
+                             + "-eventhub/namespaces/" + NAMESPACE);
+        HttpRequest request = HttpRequest.newBuilder(uri)
+                .timeout(Duration.ofMinutes(2))
+                .PUT(HttpRequest.BodyPublishers.noBody())
+                .build();
+
+        HttpResponse<String> response;
+        try (HttpClient client = HttpClient.newHttpClient()) {
+            response = client.send(request, 
HttpResponse.BodyHandlers.ofString());
+        } catch (IOException e) {
+            throw new IllegalStateException("Floci AZ did not start the Event 
Hubs namespace " + NAMESPACE, e);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new IllegalStateException("Interrupted while Floci AZ was 
starting the Event Hubs namespace", e);
+        }
+
+        Matcher matcher = AMQP_HOST_PORT.matcher(response.body());
+        if (response.statusCode() / 100 != 2 || !matcher.find()) {
+            throw new IllegalStateException(
+                    "Floci AZ did not start the Event Hubs namespace " + 
NAMESPACE + ": HTTP " + response.statusCode() + " "
+                                            + response.body());
+        }
+        return Integer.parseInt(matcher.group(1));
+    }
+
+    /**
+     * Stops Floci AZ, which removes the broker too. A singleton service is 
shut down by a JVM shutdown hook for each
+     * test class that used it, and these hooks run at the same time.
+     */
+    @Override
+    public synchronized void shutdown() {
+        if (!started) {
+            return;
+        }
+        started = false;
+
+        super.shutdown();
+    }
+
+    @Override
+    public void registerProperties() {
+        super.registerProperties();
+        System.setProperty(AzureEventHubsProperties.CONNECTION_STRING, 
connectionString());
+        System.setProperty(AzureEventHubsProperties.BLOB_ENDPOINT, 
blobEndpoint());
+        System.setProperty(AzureEventHubsProperties.BLOB_ACCOUNT_NAME, 
blobAccountName());
+        System.setProperty(AzureEventHubsProperties.BLOB_ACCESS_KEY, 
blobAccessKey());
+    }
+
+    /**
+     * The connection string of the event hub. {@code 
UseDevelopmentEmulator=true} makes the client connect with plain
+     * AMQP.
+     */
+    @Override
+    public String connectionString() {
+        return "Endpoint=sb://" + host() + ":" + amqpPort
+               + ";SharedAccessKeyName=" + SHARED_ACCESS_KEY_NAME
+               + ";SharedAccessKey=" + SHARED_ACCESS_KEY
+               + ";UseDevelopmentEmulator=true;EntityPath=" + EVENT_HUB_NAME;
+    }
+
+    @Override
+    public String blobEndpoint() {
+        return getContainer().accountEndpoint();
+    }
+
+    @Override
+    public String blobAccountName() {
+        return accountName();
+    }
+
+    @Override
+    public String blobAccessKey() {
+        return accessKey();
+    }
+}
diff --git 
a/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/services/AzureEventHubsRemoteInfraService.java
 
b/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/services/AzureEventHubsRemoteInfraService.java
new file mode 100644
index 000000000000..c666990a38f6
--- /dev/null
+++ 
b/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/services/AzureEventHubsRemoteInfraService.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.test.infra.azure.eventhubs.services;
+
+import 
org.apache.camel.test.infra.azure.eventhubs.common.AzureEventHubsProperties;
+
+/**
+ * An event hub and a storage account in Azure, given by the {@link 
AzureEventHubsProperties} system properties.
+ */
+public class AzureEventHubsRemoteInfraService implements 
AzureEventHubsInfraService {
+
+    @Override
+    public void registerProperties() {
+        // NO-OP
+    }
+
+    @Override
+    public void initialize() {
+        registerProperties();
+    }
+
+    @Override
+    public void shutdown() {
+        // NO-OP
+    }
+
+    @Override
+    public String connectionString() {
+        return System.getProperty(AzureEventHubsProperties.CONNECTION_STRING);
+    }
+
+    @Override
+    public String blobEndpoint() {
+        return System.getProperty(AzureEventHubsProperties.BLOB_ENDPOINT,
+                String.format("https://%s.blob.core.windows.net";, 
blobAccountName()));
+    }
+
+    @Override
+    public String blobAccountName() {
+        return System.getProperty(AzureEventHubsProperties.BLOB_ACCOUNT_NAME);
+    }
+
+    @Override
+    public String blobAccessKey() {
+        return System.getProperty(AzureEventHubsProperties.BLOB_ACCESS_KEY);
+    }
+}
diff --git 
a/test-infra/camel-test-infra-azure-servicebus/src/main/java/org/apache/camel/test/infra/azure/servicebus/common/AzureServiceBusProperties.java
 
b/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/services/AzureEventHubsService.java
similarity index 57%
copy from 
test-infra/camel-test-infra-azure-servicebus/src/main/java/org/apache/camel/test/infra/azure/servicebus/common/AzureServiceBusProperties.java
copy to 
test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/services/AzureEventHubsService.java
index ad5c47cd83da..69d9cd194910 100644
--- 
a/test-infra/camel-test-infra-azure-servicebus/src/main/java/org/apache/camel/test/infra/azure/servicebus/common/AzureServiceBusProperties.java
+++ 
b/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/services/AzureEventHubsService.java
@@ -15,20 +15,13 @@
  * limitations under the License.
  */
 
-package org.apache.camel.test.infra.azure.servicebus.common;
+package org.apache.camel.test.infra.azure.eventhubs.services;
 
-public final class AzureServiceBusProperties {
-    /**
-     * The connection string of the Service Bus namespace: the remote service 
reads it, and the local service sets it to
-     * the connection string of Floci AZ
-     */
-    public static final String CONNECTION_STRING = 
"azure.servicebus.connection.string";
-    /**
-     * The Artemis image that Floci AZ starts as the Service Bus broker
-     */
-    public static final String BROKER_CONTAINER = 
"azure.servicebus.broker.container";
+import org.apache.camel.test.infra.common.services.ContainerTestService;
+import org.apache.camel.test.infra.common.services.TestService;
 
-    private AzureServiceBusProperties() {
-
-    }
+/**
+ * Test infra service for Azure Event Hubs
+ */
+public interface AzureEventHubsService extends AzureEventHubsInfraService, 
TestService, ContainerTestService {
 }
diff --git 
a/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/services/AzureEventHubsServiceFactory.java
 
b/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/services/AzureEventHubsServiceFactory.java
new file mode 100644
index 000000000000..2840f53fc81e
--- /dev/null
+++ 
b/test-infra/camel-test-infra-azure-eventhubs/src/main/java/org/apache/camel/test/infra/azure/eventhubs/services/AzureEventHubsServiceFactory.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.test.infra.azure.eventhubs.services;
+
+import org.apache.camel.test.infra.common.services.SimpleTestServiceBuilder;
+import org.apache.camel.test.infra.common.services.SingletonService;
+
+/**
+ * Creates the Event Hubs service: the local one, unless the {@code 
azure.instance.type} system property is
+ * {@code remote}.
+ */
+public final class AzureEventHubsServiceFactory {
+
+    private static class SingletonAzureEventHubsService extends 
SingletonService<AzureEventHubsService>
+            implements AzureEventHubsService {
+        public SingletonAzureEventHubsService(AzureEventHubsService service, 
String name) {
+            super(service, name);
+        }
+
+        @Override
+        public String connectionString() {
+            return getService().connectionString();
+        }
+
+        @Override
+        public String blobEndpoint() {
+            return getService().blobEndpoint();
+        }
+
+        @Override
+        public String blobAccountName() {
+            return getService().blobAccountName();
+        }
+
+        @Override
+        public String blobAccessKey() {
+            return getService().blobAccessKey();
+        }
+    }
+
+    private AzureEventHubsServiceFactory() {
+
+    }
+
+    public static SimpleTestServiceBuilder<AzureEventHubsService> builder() {
+        return new SimpleTestServiceBuilder<>("azure");
+    }
+
+    public static AzureEventHubsService createService() {
+        return builder()
+                .addLocalMapping(AzureEventHubsLocalContainerService::new)
+                .addRemoteMapping(AzureEventHubsRemoteService::new)
+                .build();
+    }
+
+    public static AzureEventHubsService createSingletonService() {
+        return SingletonServiceHolder.INSTANCE;
+    }
+
+    private static class SingletonServiceHolder {
+        static final AzureEventHubsService INSTANCE;
+        static {
+            SimpleTestServiceBuilder<AzureEventHubsService> instance = 
builder();
+            instance.addLocalMapping(
+                    () -> new SingletonAzureEventHubsService(new 
AzureEventHubsLocalContainerService(), "azure-eventhubs"))
+                    .addRemoteMapping(AzureEventHubsRemoteService::new);
+            INSTANCE = instance.build();
+        }
+    }
+
+    static class AzureEventHubsLocalContainerService extends 
AzureEventHubsLocalContainerInfraService
+            implements AzureEventHubsService {
+    }
+
+    static class AzureEventHubsRemoteService extends 
AzureEventHubsRemoteInfraService implements AzureEventHubsService {
+    }
+}
diff --git 
a/test-infra/camel-test-infra-azure-servicebus/src/main/java/org/apache/camel/test/infra/azure/servicebus/common/AzureServiceBusProperties.java
 
b/test-infra/camel-test-infra-azure-servicebus/src/main/java/org/apache/camel/test/infra/azure/servicebus/common/AzureServiceBusProperties.java
index ad5c47cd83da..213b5be68a1a 100644
--- 
a/test-infra/camel-test-infra-azure-servicebus/src/main/java/org/apache/camel/test/infra/azure/servicebus/common/AzureServiceBusProperties.java
+++ 
b/test-infra/camel-test-infra-azure-servicebus/src/main/java/org/apache/camel/test/infra/azure/servicebus/common/AzureServiceBusProperties.java
@@ -23,10 +23,6 @@ public final class AzureServiceBusProperties {
      * the connection string of Floci AZ
      */
     public static final String CONNECTION_STRING = 
"azure.servicebus.connection.string";
-    /**
-     * The Artemis image that Floci AZ starts as the Service Bus broker
-     */
-    public static final String BROKER_CONTAINER = 
"azure.servicebus.broker.container";
 
     private AzureServiceBusProperties() {
 
diff --git 
a/test-infra/camel-test-infra-azure-servicebus/src/main/java/org/apache/camel/test/infra/azure/servicebus/services/AzureServiceBusLocalContainerInfraService.java
 
b/test-infra/camel-test-infra-azure-servicebus/src/main/java/org/apache/camel/test/infra/azure/servicebus/services/AzureServiceBusLocalContainerInfraService.java
index fca528eba436..c14c3e82d190 100644
--- 
a/test-infra/camel-test-infra-azure-servicebus/src/main/java/org/apache/camel/test/infra/azure/servicebus/services/AzureServiceBusLocalContainerInfraService.java
+++ 
b/test-infra/camel-test-infra-azure-servicebus/src/main/java/org/apache/camel/test/infra/azure/servicebus/services/AzureServiceBusLocalContainerInfraService.java
@@ -25,34 +25,18 @@ import java.net.http.HttpClient;
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
 import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
 import java.util.Objects;
-import java.util.UUID;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import com.github.dockerjava.api.DockerClient;
-import com.github.dockerjava.api.exception.NotFoundException;
-import com.github.dockerjava.api.model.Bind;
-import com.github.dockerjava.api.model.Container;
-import com.github.dockerjava.api.model.HostConfig;
-import com.github.dockerjava.api.model.Volume;
 import org.apache.camel.spi.annotations.InfraService;
 import org.apache.camel.test.infra.azure.common.services.FlociAzContainer;
 import org.apache.camel.test.infra.azure.common.services.FlociAzInfraService;
 import 
org.apache.camel.test.infra.azure.servicebus.common.AzureServiceBusProperties;
-import org.apache.camel.test.infra.common.LocalPropertyResolver;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.testcontainers.DockerClientFactory;
 import org.testcontainers.containers.wait.strategy.Wait;
-import org.testcontainers.images.RemoteDockerImage;
 import org.testcontainers.images.builder.Transferable;
-import org.testcontainers.utility.DockerImageName;
-import org.testcontainers.utility.ResourceReaper;
 
 /**
  * A local Service Bus namespace. Floci AZ serves the AMQP 1.0 data plane of 
Service Bus with an Artemis broker, which
@@ -69,17 +53,13 @@ import org.testcontainers.utility.ResourceReaper;
 public class AzureServiceBusLocalContainerInfraService extends 
FlociAzInfraService implements AzureServiceBusInfraService {
     private static final Logger LOG = 
LoggerFactory.getLogger(AzureServiceBusLocalContainerInfraService.class);
 
-    private static final String DOCKER_SOCKET = "/var/run/docker.sock";
     // where the official Service Bus emulator reads its topology, which Floci 
AZ reads as well
     private static final String TOPOLOGY_FILE = 
"/ServiceBus_Emulator/ConfigFiles/Config.json";
     private static final String TOPOLOGY_RESOURCE = "Config.json";
     // reports the namespaces and the host ports that Docker published their 
broker on
     private static final String NAMESPACES_PATH = "/" + 
FlociAzContainer.DEFAULT_ACCOUNT_NAME + "-servicebus/namespaces";
     private static final Pattern AMQP_HOST_PORT = 
Pattern.compile("\"amqpHostPort\"\\s*:\\s*(\\d+)");
-    // Floci AZ puts this namespace in the name of the containers it creates, 
and in their floci_namespace label
-    private static final String RESOURCE_NAMESPACE_ENV = 
"FLOCI_AZ_DOCKER_RESOURCE_NAMESPACE";
-    private static final String RESOURCE_NAMESPACE_LABEL = "floci_namespace";
-    private static final String ARTEMIS_IMAGE_ENV = 
"FLOCI_AZ_SERVICES_SERVICE_BUS_ARTEMIS_IMAGE";
+    private static final String ARTEMIS_IMAGE_VARIABLE = 
"FLOCI_AZ_SERVICES_SERVICE_BUS_ARTEMIS_IMAGE";
 
     private int amqpPort;
 
@@ -87,28 +67,13 @@ public class AzureServiceBusLocalContainerInfraService 
extends FlociAzInfraServi
     protected FlociAzContainer initContainer(String imageName) {
         FlociAzContainer container = super.initContainer(imageName);
 
-        container.withEnv("FLOCI_AZ_SERVICES_SERVICE_BUS_MOCKED", "false")
+        container.withDockerSocket()
+                .withEnv("FLOCI_AZ_SERVICES_SERVICE_BUS_MOCKED", "false")
                 // Docker publishes the broker on free host ports, read back 
once it started, so that builds running at
                 // the same time on one Docker host do not clash
                 .withEnv("FLOCI_AZ_SERVICES_SERVICE_BUS_AMQP_PORT", "0")
                 .withEnv("FLOCI_AZ_SERVICES_SERVICE_BUS_AMQP_TLS_PORT", "0")
-                // when it starts, Floci AZ removes the containers of its 
namespace it finds, as left over: each
-                // instance has a namespace of its own, so that it does not 
remove the broker of another instance
-                .withEnv(RESOURCE_NAMESPACE_ENV, "camel" + 
UUID.randomUUID().toString().replace("-", "").substring(0, 12))
                 .withCopyToContainer(Transferable.of(readTopology()), 
TOPOLOGY_FILE)
-                .withCreateContainerCmdModifier(cmd -> {
-                    HostConfig hostConfig = cmd.getHostConfig();
-                    List<Bind> binds = new ArrayList<>();
-                    if (hostConfig.getBinds() != null) {
-                        binds.addAll(Arrays.asList(hostConfig.getBinds()));
-                    }
-                    binds.add(new Bind(
-                            
DockerClientFactory.instance().getRemoteDockerUnixSocketPath(), new 
Volume(DOCKER_SOCKET)));
-                    hostConfig.withBinds(binds)
-                            // where SELinux is enforcing, it denies a 
container the Docker socket of the host unless
-                            // the labeling of the container is disabled
-                            .withSecurityOpts(List.of("label=disable"));
-                })
                 // Floci AZ answers once the broker started and the topology 
is created
                 .waitingFor(Wait.forHttp(NAMESPACES_PATH)
                         .forPort(FlociAzContainer.SERVICE_PORT)
@@ -131,17 +96,19 @@ public class AzureServiceBusLocalContainerInfraService 
extends FlociAzInfraServi
     public void initialize() {
         FlociAzContainer container = getContainer();
 
-        // Floci AZ pulls the broker image before it answers: pull it here 
instead, as Testcontainers pulls its images
-        String brokerImage = 
LocalPropertyResolver.getProperty(AzureServiceBusLocalContainerInfraService.class,
-                AzureServiceBusProperties.BROKER_CONTAINER);
-        container.withEnv(ARTEMIS_IMAGE_ENV, new 
RemoteDockerImage(DockerImageName.parse(brokerImage)).get());
-
-        // Testcontainers does not know the broker container, which Floci AZ 
creates: have it removed when the JVM
-        // exits, should the service not be shut down
-        
ResourceReaper.instance().registerLabelsFilterForCleanup(Map.of(RESOURCE_NAMESPACE_LABEL,
 resourceNamespace()));
-
-        container.start();
-        amqpPort = readAmqpPort();
+        try {
+            container.withBrokerImage(ARTEMIS_IMAGE_VARIABLE);
+            container.start();
+            amqpPort = readAmqpPort();
+        } catch (RuntimeException e) {
+            // also removes the broker, should Floci AZ have started it
+            try {
+                shutdown();
+            } catch (RuntimeException suppressed) {
+                e.addSuppressed(suppressed);
+            }
+            throw e;
+        }
 
         LOG.info("Floci AZ running at address {}, with the Service Bus broker 
at {}:{}", container.serviceAddress(), host(),
                 amqpPort);
@@ -153,8 +120,8 @@ public class AzureServiceBusLocalContainerInfraService 
extends FlociAzInfraServi
         HttpRequest request = 
HttpRequest.newBuilder(URI.create(getContainer().serviceAddress() + 
NAMESPACES_PATH))
                 .timeout(Duration.ofSeconds(30))
                 .build();
-        try {
-            String namespaces = HttpClient.newHttpClient().send(request, 
HttpResponse.BodyHandlers.ofString()).body();
+        try (HttpClient client = HttpClient.newHttpClient()) {
+            String namespaces = client.send(request, 
HttpResponse.BodyHandlers.ofString()).body();
             Matcher matcher = AMQP_HOST_PORT.matcher(namespaces);
             if (!matcher.find()) {
                 throw new IllegalStateException("Floci AZ reports no Service 
Bus broker: " + namespaces);
@@ -173,36 +140,6 @@ public class AzureServiceBusLocalContainerInfraService 
extends FlociAzInfraServi
         System.setProperty(AzureServiceBusProperties.CONNECTION_STRING, 
connectionString());
     }
 
-    @Override
-    public void shutdown() {
-        try {
-            super.shutdown();
-        } finally {
-            removeBrokerContainers();
-        }
-    }
-
-    // the broker containers are not removed with Floci AZ, which 
Testcontainers kills
-    private void removeBrokerContainers() {
-        DockerClient client = DockerClientFactory.instance().client();
-        List<Container> containers = client.listContainersCmd()
-                .withShowAll(true)
-                .withLabelFilter(Map.of(RESOURCE_NAMESPACE_LABEL, 
resourceNamespace()))
-                .exec();
-        for (Container container : containers) {
-            LOG.debug("Removing the container {} that Floci AZ started", 
Arrays.toString(container.getNames()));
-            try {
-                
client.removeContainerCmd(container.getId()).withForce(true).withRemoveVolumes(true).exec();
-            } catch (NotFoundException e) {
-                LOG.debug("The container {} was already removed", 
container.getId());
-            }
-        }
-    }
-
-    private String resourceNamespace() {
-        return getContainer().getEnvMap().get(RESOURCE_NAMESPACE_ENV);
-    }
-
     @Override
     public String connectionString() {
         // Floci AZ ignores the key, and UseDevelopmentEmulator has the Azure 
SDK connect with AMQP rather than AMQPS
diff --git 
a/test-infra/camel-test-infra-azure-servicebus/src/main/resources/org/apache/camel/test/infra/azure/servicebus/services/container.properties
 
b/test-infra/camel-test-infra-azure-servicebus/src/main/resources/org/apache/camel/test/infra/azure/servicebus/services/container.properties
deleted file mode 100644
index 308e6b6203ec..000000000000
--- 
a/test-infra/camel-test-infra-azure-servicebus/src/main/resources/org/apache/camel/test/infra/azure/servicebus/services/container.properties
+++ /dev/null
@@ -1,22 +0,0 @@
-## ---------------------------------------------------------------------------
-## Licensed to the Apache Software Foundation (ASF) under one or more
-## contributor license agreements.  See the NOTICE file distributed with
-## this work for additional information regarding copyright ownership.
-## The ASF licenses this file to You under the Apache License, Version 2.0
-## (the "License"); you may not use this file except in compliance with
-## the License.  You may obtain a copy of the License at
-##
-##      http://www.apache.org/licenses/LICENSE-2.0
-##
-## Unless required by applicable law or agreed to in writing, software
-## distributed under the License is distributed on an "AS IS" BASIS,
-## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-## See the License for the specific language governing permissions and
-## limitations under the License.
-## ---------------------------------------------------------------------------
-# The Artemis broker that Floci AZ (azure.container) starts for the Service 
Bus data plane. Floci AZ copies AMQP jars
-# patched for one Artemis version into it, so the version must be the one that 
the Floci AZ image expects (its
-# FLOCI_AZ_SERVICES_SERVICE_BUS_ARTEMIS_IMAGE default): it is pinned, and 
changes with azure.container. The key does
-# not end with "artemis": the test-infra metadata would take this version for 
the Artemis services.
-azure.servicebus.broker.container=mirror.gcr.io/apache/activemq-artemis:2.44.0
-azure.servicebus.broker.container.version.include=2.44.0
diff --git a/test-infra/pom.xml b/test-infra/pom.xml
index d93566078221..95570efda9ef 100644
--- a/test-infra/pom.xml
+++ b/test-infra/pom.xml
@@ -52,6 +52,7 @@
         <module>camel-test-infra-rabbitmq</module>
         <module>camel-test-infra-azure-common</module>
         <module>camel-test-infra-azure-eventgrid</module>
+        <module>camel-test-infra-azure-eventhubs</module>
         <module>camel-test-infra-azure-functions</module>
         <module>camel-test-infra-azure-key-vault</module>
         <module>camel-test-infra-azure-servicebus</module>

Reply via email to