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

FrankChen021 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new a01e318f96c minor: use rustfs instead of minio for s3 embedded-tests 
(#20420)
a01e318f96c is described below

commit a01e318f96c97912c227cfba2f946bc5cab37a03
Author: Clint Wylie <[email protected]>
AuthorDate: Thu Sep 24 18:59:19 2026 -0700

    minor: use rustfs instead of minio for s3 embedded-tests (#20420)
---
 .github/workflows/worker.yml                       |  2 +-
 embedded-tests/pom.xml                             |  6 --
 .../docker/BaseConsulDiscoveryDockerTest.java      | 10 +--
 .../embedded/hdfs/HdfsToS3ParallelIndexTest.java   | 14 ++--
 .../embedded/hdfs/S3ToHdfsParallelIndexTest.java   | 10 +--
 .../AbstractS3InputSourceParallelIndexTest.java    | 16 ++---
 .../embedded/indexing/IngestionSmokeTest.java      |  4 +-
 .../msq/EmbeddedDurableShuffleStorageTest.java     |  8 +--
 ...Resource.java => S3DurableStorageResource.java} | 12 ++--
 .../testing/embedded/msq/S3ExternQueryTest.java    |  8 +--
 .../query/PartialProjectionLoadRuleQueryTest.java  |  6 +-
 .../embedded/query/QueryVirtualStorageTest.java    |  4 +-
 .../{minio => s3}/ITS3SQLBasedIngestionTest.java   |  2 +-
 .../{minio => s3}/ITS3ToS3ParallelIndexTest.java   |  6 +-
 .../testing/embedded/{minio => s3}/S3PerfTest.java | 20 +++---
 .../S3StorageResource.java}                        | 44 +++++++------
 .../S3StorageResourceTest.java}                    | 16 ++---
 .../S3StorageTest.java}                            |  8 +--
 .../testing/embedded/{minio => s3}/S3TestUtil.java |  2 +-
 extensions-core/s3-extensions/pom.xml              |  6 --
 .../apache/druid/storage/s3/RustFSContainer.java   | 77 ++++++++++++++++++++++
 ...rSideEncryptingAmazonS3MultipartUploadTest.java | 13 ++--
 .../storage/s3/output/S3StorageConnectorTest.java  | 55 ++++------------
 23 files changed, 191 insertions(+), 158 deletions(-)

diff --git a/.github/workflows/worker.yml b/.github/workflows/worker.yml
index 6c7de6aae22..1b08a3ceb75 100644
--- a/.github/workflows/worker.yml
+++ b/.github/workflows/worker.yml
@@ -93,7 +93,7 @@ jobs:
           images=(
             apache/kafka:4.2.0
             postgres:16-alpine
-            quay.io/minio/minio:latest
+            rustfs/rustfs:1.0.0
             hashicorp/consul:1.18
             tabulario/iceberg-rest:1.6.0
             localstack/localstack:4.13.1
diff --git a/embedded-tests/pom.xml b/embedded-tests/pom.xml
index 140c7959724..d9052fea9c1 100644
--- a/embedded-tests/pom.xml
+++ b/embedded-tests/pom.xml
@@ -587,12 +587,6 @@
       <version>${mariadb.version}</version>
       <scope>test</scope>
     </dependency>
-    <dependency>
-      <groupId>org.testcontainers</groupId>
-      <artifactId>testcontainers-minio</artifactId>
-      <version>${testcontainers.version}</version>
-      <scope>test</scope>
-    </dependency>
     <dependency>
       <groupId>org.testcontainers</groupId>
       <artifactId>testcontainers-redpanda</artifactId>
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/docker/BaseConsulDiscoveryDockerTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/docker/BaseConsulDiscoveryDockerTest.java
index 216630a1140..a3002b331ec 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/docker/BaseConsulDiscoveryDockerTest.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/docker/BaseConsulDiscoveryDockerTest.java
@@ -33,8 +33,8 @@ import 
org.apache.druid.testing.embedded.consul.ConsulClusterResource;
 import org.apache.druid.testing.embedded.consul.ConsulSecurityMode;
 import org.apache.druid.testing.embedded.emitter.LatchableEmitterModule;
 import org.apache.druid.testing.embedded.indexing.IngestionSmokeTest;
-import org.apache.druid.testing.embedded.minio.MinIOStorageResource;
 import org.apache.druid.testing.embedded.psql.PostgreSQLMetadataResource;
+import org.apache.druid.testing.embedded.s3.S3StorageResource;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Tag;
@@ -66,7 +66,7 @@ abstract class BaseConsulDiscoveryDockerTest extends 
IngestionSmokeTest
   public EmbeddedDruidCluster createCluster()
   {
     final PostgreSQLMetadataResource postgreSQLMetadataResource = new 
PostgreSQLMetadataResource();
-    final MinIOStorageResource minIOStorageResource = new 
MinIOStorageResource();
+    final S3StorageResource s3StorageResource = new S3StorageResource();
 
     consulResource = new ConsulClusterResource(getConsulSecurityMode());
 
@@ -81,7 +81,7 @@ abstract class BaseConsulDiscoveryDockerTest extends 
IngestionSmokeTest
         );
 
     cluster.addResource(postgreSQLMetadataResource)
-           .addResource(minIOStorageResource)
+           .addResource(s3StorageResource)
            .addResource(consulResource)
            .addResource(kafkaServer);
 
@@ -125,12 +125,12 @@ abstract class BaseConsulDiscoveryDockerTest extends 
IngestionSmokeTest
 
         String consulHost = "localhost";
         String consulPort = Integer.toString(consulResource.getMappedPort());
-        String minioUrl = minIOStorageResource.getEndpointUrl();
+        String s3Url = s3StorageResource.getEndpointUrl();
 
         
cluster.addCommonProperty("druid.metadata.storage.connector.connectURI", 
psqlConnectURI);
         cluster.addCommonProperty("druid.discovery.consul.connection.host", 
consulHost);
         cluster.addCommonProperty("druid.discovery.consul.connection.port", 
consulPort);
-        cluster.addCommonProperty("druid.s3.endpoint.url", minioUrl);
+        cluster.addCommonProperty("druid.s3.endpoint.url", s3Url);
 
         ConsulSecurityMode mode = getConsulSecurityMode();
         if (mode == ConsulSecurityMode.TLS || mode == ConsulSecurityMode.MTLS) 
{
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/hdfs/HdfsToS3ParallelIndexTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/hdfs/HdfsToS3ParallelIndexTest.java
index 2c57f70dc46..62ce85450a0 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/hdfs/HdfsToS3ParallelIndexTest.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/hdfs/HdfsToS3ParallelIndexTest.java
@@ -22,15 +22,15 @@ package org.apache.druid.testing.embedded.hdfs;
 import org.apache.druid.java.util.common.Pair;
 import org.apache.druid.java.util.common.logger.Logger;
 import org.apache.druid.testing.embedded.EmbeddedDruidCluster;
-import org.apache.druid.testing.embedded.minio.MinIOStorageResource;
+import org.apache.druid.testing.embedded.s3.S3StorageResource;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 
 /**
  * Embedded parallel index test that reads input data from HDFS (in-process 
MiniDFSCluster)
- * and stores segments in S3 (MinIO testcontainer).
+ * and stores segments in S3 (an S3-compatible testcontainer).
  *
- * <p>The MinIO resource is registered after the HDFS resource so that the S3 
deep-storage
+ * <p>The S3 resource is registered after the HDFS resource so that the S3 
deep-storage
  * configuration ({@code druid.storage.type=s3}) is applied last. The HDFS 
input-source
  * connection properties ({@code hadoop.fs.defaultFS}) remain active and are 
used by the
  * indexer to read data from HDFS.
@@ -39,9 +39,9 @@ public class HdfsToS3ParallelIndexTest extends 
AbstractHdfsInputSourceParallelIn
 {
   private static final Logger LOG = new 
Logger(HdfsToS3ParallelIndexTest.class);
 
-  /** HDFS is the input source only — deep storage is S3/MinIO. */
+  /** HDFS is the input source only — deep storage is S3. */
   private final HdfsStorageResource hdfsResource = new 
HdfsStorageResource(false);
-  private final MinIOStorageResource minIOResource = new 
MinIOStorageResource();
+  private final S3StorageResource s3Resource = new S3StorageResource();
 
   @Override
   protected HdfsStorageResource getHdfsResource()
@@ -56,9 +56,9 @@ public class HdfsToS3ParallelIndexTest extends 
AbstractHdfsInputSourceParallelIn
     // can read from HDFS. configureAsDeepStorage=false means it does NOT set 
druid.storage.type.
     cluster.addResource(hdfsResource);
 
-    // MinIO resource: configures S3/MinIO as deep storage 
(druid.storage.type=s3, etc.).
+    // S3 resource: configures S3 as deep storage (druid.storage.type=s3, 
etc.).
     // Adding it after the HDFS resource ensures the S3 deep-storage settings 
win.
-    cluster.addResource(minIOResource);
+    cluster.addResource(s3Resource);
   }
 
   @ParameterizedTest
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/hdfs/S3ToHdfsParallelIndexTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/hdfs/S3ToHdfsParallelIndexTest.java
index 23063af355d..e44d0bc6cd6 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/hdfs/S3ToHdfsParallelIndexTest.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/hdfs/S3ToHdfsParallelIndexTest.java
@@ -28,13 +28,13 @@ import org.junit.jupiter.params.provider.MethodSource;
 import java.util.List;
 
 /**
- * Embedded parallel index test that reads input data from S3 (MinIO 
testcontainer) and stores
+ * Embedded parallel index test that reads input data from S3 (an 
S3-compatible testcontainer) and stores
  * segments in HDFS (in-process MiniDFSCluster).
  *
- * <p>The HDFS resource is registered after the MinIO resource so that the 
HDFS deep-storage
- * configuration ({@code druid.storage.type=hdfs}) overrides the S3 
configuration set by MinIO.
+ * <p>The HDFS resource is registered after the S3 resource so that the HDFS 
deep-storage
+ * configuration ({@code druid.storage.type=hdfs}) overrides the S3 
configuration set by the S3 resource.
  * The S3 input-source connection properties remain active and are used by the 
indexer to read
- * data from MinIO.
+ * data from S3.
  */
 public class S3ToHdfsParallelIndexTest extends 
AbstractS3InputSourceParallelIndexTest
 {
@@ -43,7 +43,7 @@ public class S3ToHdfsParallelIndexTest extends 
AbstractS3InputSourceParallelInde
   @Override
   protected void addResources(EmbeddedDruidCluster cluster)
   {
-    // S3/MinIO resource first: uploads data to MinIO and sets S3 connection 
properties.
+    // S3 resource first: uploads data to S3 and sets S3 connection properties.
     super.addResources(cluster);
     // HDFS resource second: overrides druid.storage.type to "hdfs" for deep 
storage.
     cluster.addResource(hdfsResource);
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/indexer/AbstractS3InputSourceParallelIndexTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/indexer/AbstractS3InputSourceParallelIndexTest.java
index 997a884a821..6029495dbfb 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/indexer/AbstractS3InputSourceParallelIndexTest.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/indexer/AbstractS3InputSourceParallelIndexTest.java
@@ -26,8 +26,8 @@ import org.apache.druid.java.util.common.Pair;
 import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.java.util.common.logger.Logger;
 import org.apache.druid.testing.embedded.EmbeddedDruidCluster;
-import org.apache.druid.testing.embedded.minio.MinIOStorageResource;
-import org.apache.druid.testing.embedded.minio.S3TestUtil;
+import org.apache.druid.testing.embedded.s3.S3StorageResource;
+import org.apache.druid.testing.embedded.s3.S3TestUtil;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
@@ -50,14 +50,14 @@ public abstract class 
AbstractS3InputSourceParallelIndexTest extends AbstractClo
   private static final Logger LOG = new 
Logger(AbstractS3InputSourceParallelIndexTest.class);
   private static final String INDEX_TASK = 
"/indexer/wikipedia_cloud_index_task.json";
   private static final String INDEX_QUERIES_RESOURCE = 
"/indexer/wikipedia_index_queries.json";
-  protected final MinIOStorageResource minIOStorageResource = new 
MinIOStorageResource();
+  protected final S3StorageResource s3StorageResource = new 
S3StorageResource();
   private S3TestUtil s3;
 
   @Override
   protected void addResources(EmbeddedDruidCluster cluster)
   {
     cluster.addExtension(S3InputSourceDruidModule.class)
-           .addResource(minIOStorageResource);
+           .addResource(s3StorageResource);
   }
 
   @BeforeAll
@@ -70,7 +70,7 @@ public abstract class AbstractS3InputSourceParallelIndexTest 
extends AbstractClo
     }
     try {
       s3 = new S3TestUtil(
-          minIOStorageResource.getS3Client(),
+          s3StorageResource.getS3Client(),
           getCloudBucket("s3"),
           getCloudPath("s3")
       );
@@ -101,7 +101,7 @@ public abstract class 
AbstractS3InputSourceParallelIndexTest extends AbstractClo
   /**
    * Variant of {@link #doTest} that injects an {@code endpointConfig} into 
the S3 input source spec.
    * Required when using per-input-source credentials: S3InputSource builds a 
new S3 client in that
-   * case and must be told the MinIO endpoint explicitly, since it cannot 
inherit it from Druid's
+   * case and must be told the S3 endpoint explicitly, since it cannot inherit 
it from Druid's
    * global S3 config.
    */
   protected void doTestWithEndpointConfig(
@@ -116,7 +116,7 @@ public abstract class 
AbstractS3InputSourceParallelIndexTest extends AbstractClo
       final String endpointConfigJson = jsonMapper.writeValueAsString(
           Map.of("url", endpointUrl, "signingRegion", "us-east-1")
       );
-      // Path-style access is required for MinIO running at a local IP address
+      // Path-style access is required for the S3 container running at a local 
IP address
       final String clientConfigJson = jsonMapper.writeValueAsString(
           Map.of("enablePathStyleAccess", true)
       );
@@ -140,7 +140,7 @@ public abstract class 
AbstractS3InputSourceParallelIndexTest extends AbstractClo
           );
           spec = StringUtils.replace(spec, "%%INPUT_SOURCE_PROPERTY_KEY%%", 
inputSource.lhs);
           spec = StringUtils.replace(spec, "%%INPUT_SOURCE_PROPERTY_VALUE%%", 
inputSourceValue);
-          // Inject endpointConfig and clientConfig so the custom S3 client 
points at MinIO with path-style access
+          // Inject endpointConfig and clientConfig so the custom S3 client 
points at the S3 container with path-style access
           spec = StringUtils.replace(
               spec,
               "\"type\": \"s3\",",
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/indexing/IngestionSmokeTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/indexing/IngestionSmokeTest.java
index d703325ce6e..2d22e80960b 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/indexing/IngestionSmokeTest.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/indexing/IngestionSmokeTest.java
@@ -51,9 +51,9 @@ import org.apache.druid.testing.embedded.EmbeddedOverlord;
 import org.apache.druid.testing.embedded.EmbeddedRouter;
 import org.apache.druid.testing.embedded.emitter.LatchableEmitterModule;
 import org.apache.druid.testing.embedded.junit5.EmbeddedClusterTestBase;
-import org.apache.druid.testing.embedded.minio.MinIOStorageResource;
 import org.apache.druid.testing.embedded.msq.EmbeddedMSQApis;
 import org.apache.druid.testing.embedded.psql.PostgreSQLMetadataResource;
+import org.apache.druid.testing.embedded.s3.S3StorageResource;
 import org.apache.druid.testing.embedded.server.EmbeddedEventCollector;
 import org.apache.druid.timeline.DataSegment;
 import org.apache.kafka.clients.producer.ProducerRecord;
@@ -116,7 +116,7 @@ public class IngestionSmokeTest extends 
EmbeddedClusterTestBase
             )
             .useDefaultTimeoutForLatchableEmitter(20)
             .addResource(new PostgreSQLMetadataResource())
-            .addResource(new MinIOStorageResource())
+            .addResource(new S3StorageResource())
             .addResource(kafkaServer)
             .addCommonProperty("druid.manager.segments.useIncrementalCache", 
"always")
             .addCommonProperty("druid.emitter", "http")
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/EmbeddedDurableShuffleStorageTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/EmbeddedDurableShuffleStorageTest.java
index 05fd2d02094..9c2d3c277c8 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/EmbeddedDurableShuffleStorageTest.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/EmbeddedDurableShuffleStorageTest.java
@@ -40,7 +40,7 @@ import org.apache.druid.testing.embedded.EmbeddedIndexer;
 import org.apache.druid.testing.embedded.EmbeddedOverlord;
 import org.apache.druid.testing.embedded.EmbeddedRouter;
 import org.apache.druid.testing.embedded.junit5.EmbeddedClusterTestBase;
-import org.apache.druid.testing.embedded.minio.MinIOStorageResource;
+import org.apache.druid.testing.embedded.s3.S3StorageResource;
 import org.hamcrest.MatcherAssert;
 import org.hamcrest.Matchers;
 import org.junit.jupiter.api.Assertions;
@@ -60,7 +60,7 @@ import java.util.stream.Collectors;
 
 /**
  * Embedded test to batch-ingest wikipedia data from {@link 
#loadWikipediaTable()}, then query that data
- * with MSQ tasks using MinIO-based durable storage.
+ * with MSQ tasks using S3-based durable storage.
  */
 public class EmbeddedDurableShuffleStorageTest extends EmbeddedClusterTestBase
 {
@@ -70,8 +70,8 @@ public class EmbeddedDurableShuffleStorageTest extends 
EmbeddedClusterTestBase
   private final EmbeddedHistorical historical = new EmbeddedHistorical();
   private final EmbeddedCoordinator coordinator = new EmbeddedCoordinator();
   private final EmbeddedRouter router = new EmbeddedRouter();
-  private final MinIOStorageResource storageResource = new 
MinIOStorageResource();
-  private final MinIODurableStorageResource msqStorageResource = new 
MinIODurableStorageResource(storageResource);
+  private final S3StorageResource storageResource = new S3StorageResource();
+  private final S3DurableStorageResource msqStorageResource = new 
S3DurableStorageResource(storageResource);
 
   private EmbeddedMSQApis msqApis;
 
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/MinIODurableStorageResource.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/S3DurableStorageResource.java
similarity index 85%
rename from 
embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/MinIODurableStorageResource.java
rename to 
embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/S3DurableStorageResource.java
index 8913e3e3ea4..509f8dffa84 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/MinIODurableStorageResource.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/S3DurableStorageResource.java
@@ -22,18 +22,18 @@ package org.apache.druid.testing.embedded.msq;
 import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.testing.embedded.EmbeddedDruidCluster;
 import org.apache.druid.testing.embedded.EmbeddedResource;
-import org.apache.druid.testing.embedded.minio.MinIOStorageResource;
+import org.apache.druid.testing.embedded.s3.S3StorageResource;
 
 import java.io.File;
 
 /**
- * Resource that configures MSQ to use a bucket from {@link 
MinIOStorageResource} for durable intermediate data storage.
+ * Resource that configures MSQ to use a bucket from {@link S3StorageResource} 
for durable intermediate data storage.
  */
-public class MinIODurableStorageResource implements EmbeddedResource
+public class S3DurableStorageResource implements EmbeddedResource
 {
-  private final MinIOStorageResource storageResource;
+  private final S3StorageResource storageResource;
 
-  public MinIODurableStorageResource(MinIOStorageResource storageResource)
+  public S3DurableStorageResource(S3StorageResource storageResource)
   {
     this.storageResource = storageResource;
   }
@@ -63,7 +63,7 @@ public class MinIODurableStorageResource implements 
EmbeddedResource
     // Set tmpStorageBytesPerTask to 3 GB. This controls when stage-internal 
channels (such as ones used internally
     // by SuperSorter) "spill over" from local disk to durable storage. Cannot 
set this much lower currently, due to
     // validations in WorkerStorageParameters. Ideally, we'd like to set this 
low enough such that embedded tests are
-    // actually using minio/S3 for stage-internal storage. At this level, they 
won't be using it, but at least stages
+    // actually using S3 for stage-internal storage. At this level, they won't 
be using it, but at least stages
     // will go through the motions of setting up composing channels 
internally. There is still value in exercising
     // that code path.
     //
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/S3ExternQueryTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/S3ExternQueryTest.java
index 8eb06e76a39..56f43e0ed1f 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/S3ExternQueryTest.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/msq/S3ExternQueryTest.java
@@ -29,7 +29,7 @@ import org.apache.druid.testing.embedded.EmbeddedDruidCluster;
 import org.apache.druid.testing.embedded.EmbeddedIndexer;
 import org.apache.druid.testing.embedded.EmbeddedOverlord;
 import org.apache.druid.testing.embedded.junit5.EmbeddedClusterTestBase;
-import org.apache.druid.testing.embedded.minio.MinIOStorageResource;
+import org.apache.druid.testing.embedded.s3.S3StorageResource;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -44,7 +44,7 @@ import java.util.List;
 import java.util.zip.GZIPOutputStream;
 
 /**
- * Tests MSQ task-based SELECT queries that read external data via {@code 
EXTERN} from S3 (backed by MinIO).
+ * Tests MSQ task-based SELECT queries that read external data via {@code 
EXTERN} from S3 (backed by an S3-compatible testcontainer).
  */
 public class S3ExternQueryTest extends EmbeddedClusterTestBase
 {
@@ -72,7 +72,7 @@ public class S3ExternQueryTest extends EmbeddedClusterTestBase
    */
   private long totalUploadedBytes;
 
-  private final MinIOStorageResource storageResource = new 
MinIOStorageResource();
+  private final S3StorageResource storageResource = new S3StorageResource();
   private final EmbeddedOverlord overlord = new EmbeddedOverlord();
   private final EmbeddedCoordinator coordinator = new EmbeddedCoordinator();
   private final EmbeddedIndexer indexer = new EmbeddedIndexer()
@@ -177,7 +177,7 @@ public class S3ExternQueryTest extends 
EmbeddedClusterTestBase
   }
 
   /**
-   * Uploads {@link #PLAIN_FILE} (plain JSON) and {@link #GZ_FILE} (gzipped 
JSON) to the MinIO bucket.
+   * Uploads {@link #PLAIN_FILE} (plain JSON) and {@link #GZ_FILE} (gzipped 
JSON) to the S3 bucket.
    */
   private void uploadExternalFiles() throws IOException
   {
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/query/PartialProjectionLoadRuleQueryTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/query/PartialProjectionLoadRuleQueryTest.java
index 925fb78722a..a4dd81e1144 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/query/PartialProjectionLoadRuleQueryTest.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/query/PartialProjectionLoadRuleQueryTest.java
@@ -57,8 +57,8 @@ import org.apache.druid.testing.embedded.EmbeddedOverlord;
 import org.apache.druid.testing.embedded.EmbeddedRouter;
 import org.apache.druid.testing.embedded.catalog.TestCatalogClient;
 import org.apache.druid.testing.embedded.junit5.EmbeddedClusterTestBase;
-import org.apache.druid.testing.embedded.minio.MinIOStorageResource;
 import org.apache.druid.testing.embedded.msq.EmbeddedMSQApis;
+import org.apache.druid.testing.embedded.s3.S3StorageResource;
 import org.hamcrest.MatcherAssert;
 import org.hamcrest.Matchers;
 import org.junit.jupiter.api.Assertions;
@@ -112,7 +112,7 @@ class PartialProjectionLoadRuleQueryTest extends 
EmbeddedClusterTestBase
   private final EmbeddedHistorical historical = new EmbeddedHistorical();
   private final EmbeddedCoordinator coordinator = new EmbeddedCoordinator();
   private final EmbeddedRouter router = new EmbeddedRouter();
-  private final MinIOStorageResource storageResource = new 
MinIOStorageResource();
+  private final S3StorageResource storageResource = new S3StorageResource();
 
   private EmbeddedMSQApis msqApis;
 
@@ -176,7 +176,7 @@ class PartialProjectionLoadRuleQueryTest extends 
EmbeddedClusterTestBase
         .addCommonProperty("druid.msq.dart.enabled", "true")
         // Clustered base-table + projection segments require the V10 segment 
format.
         .addCommonProperty("druid.indexer.task.buildV10", "true")
-        // Range reads over MinIO — required for partial loading to be 
functional.
+        // Range reads over S3 — required for partial loading to be functional.
         .addCommonProperty("druid.storage.zip", "false")
         .addCommonProperty("druid.monitoring.emissionPeriod", "PT1s")
         .addServer(coordinator)
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/query/QueryVirtualStorageTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/query/QueryVirtualStorageTest.java
index 466f2cb9eb0..bf054bc3063 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/query/QueryVirtualStorageTest.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/query/QueryVirtualStorageTest.java
@@ -43,9 +43,9 @@ import org.apache.druid.testing.embedded.EmbeddedIndexer;
 import org.apache.druid.testing.embedded.EmbeddedOverlord;
 import org.apache.druid.testing.embedded.EmbeddedRouter;
 import org.apache.druid.testing.embedded.junit5.EmbeddedClusterTestBase;
-import org.apache.druid.testing.embedded.minio.MinIOStorageResource;
 import org.apache.druid.testing.embedded.msq.EmbeddedDurableShuffleStorageTest;
 import org.apache.druid.testing.embedded.msq.EmbeddedMSQApis;
+import org.apache.druid.testing.embedded.s3.S3StorageResource;
 import org.hamcrest.MatcherAssert;
 import org.hamcrest.Matchers;
 import org.junit.jupiter.api.Assertions;
@@ -83,7 +83,7 @@ class QueryVirtualStorageTest extends EmbeddedClusterTestBase
   private final EmbeddedHistorical historical = new EmbeddedHistorical();
   private final EmbeddedCoordinator coordinator = new EmbeddedCoordinator();
   private final EmbeddedRouter router = new EmbeddedRouter();
-  private final MinIOStorageResource storageResource = new 
MinIOStorageResource();
+  private final S3StorageResource storageResource = new S3StorageResource();
 
   private EmbeddedMSQApis msqApis;
 
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/ITS3SQLBasedIngestionTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/ITS3SQLBasedIngestionTest.java
similarity index 96%
rename from 
embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/ITS3SQLBasedIngestionTest.java
rename to 
embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/ITS3SQLBasedIngestionTest.java
index 57b3183226e..0a513fc33f7 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/ITS3SQLBasedIngestionTest.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/ITS3SQLBasedIngestionTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.druid.testing.embedded.minio;
+package org.apache.druid.testing.embedded.s3;
 
 import org.apache.druid.java.util.common.Pair;
 import 
org.apache.druid.testing.embedded.indexer.AbstractS3InputSourceParallelIndexTest;
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/ITS3ToS3ParallelIndexTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/ITS3ToS3ParallelIndexTest.java
similarity index 90%
rename from 
embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/ITS3ToS3ParallelIndexTest.java
rename to 
embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/ITS3ToS3ParallelIndexTest.java
index cfd4a64960a..2e9e014585c 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/ITS3ToS3ParallelIndexTest.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/ITS3ToS3ParallelIndexTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.druid.testing.embedded.minio;
+package org.apache.druid.testing.embedded.s3;
 
 import org.apache.druid.java.util.common.Pair;
 import 
org.apache.druid.testing.embedded.indexer.AbstractS3InputSourceParallelIndexTest;
@@ -42,8 +42,8 @@ public class ITS3ToS3ParallelIndexTest extends 
AbstractS3InputSourceParallelInde
     doTestWithEndpointConfig(
         s3InputSource,
         new Pair<>(false, false),
-        minIOStorageResource.createTempCredentialsForInputSource(),
-        minIOStorageResource.getEndpointUrl()
+        s3StorageResource.createTempCredentialsForInputSource(),
+        s3StorageResource.getEndpointUrl()
     );
   }
 }
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/S3PerfTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/S3PerfTest.java
similarity index 94%
rename from 
embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/S3PerfTest.java
rename to 
embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/S3PerfTest.java
index 3446998f3f2..c63aa5e0606 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/S3PerfTest.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/S3PerfTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.druid.testing.embedded.minio;
+package org.apache.druid.testing.embedded.s3;
 
 import org.apache.druid.common.aws.AWSModule;
 import org.apache.druid.indexing.common.task.IndexTask;
@@ -62,7 +62,7 @@ import java.util.concurrent.TimeUnit;
  * <p>Each {@link Nested} inner class runs a full embedded cluster with a 
different
  * storage backend / HTTP client combination:
  * <ul>
- *   <li>MinIO container — CRT (default), Netty NIO, synchronous PutObject</li>
+ *   <li>Local S3-compatible container — CRT (default), Netty NIO, synchronous 
PutObject</li>
  *   <li>Real AWS S3 (opt-in via {@value BUCKET_PROPERTY}) — same three 
variants</li>
  * </ul>
  *
@@ -165,22 +165,22 @@ public class S3PerfTest
     }
   }
 
-  abstract static class MinIOPerfBase extends PerfTestBase
+  abstract static class LocalS3PerfBase extends PerfTestBase
   {
     abstract void configureCluster(EmbeddedDruidCluster cluster);
 
     @Override
     public EmbeddedDruidCluster createCluster()
     {
-      final EmbeddedDruidCluster cluster = 
addServers(newBaseCluster().addResource(new MinIOStorageResource()));
+      final EmbeddedDruidCluster cluster = 
addServers(newBaseCluster().addResource(new S3StorageResource()));
       configureCluster(cluster);
       return cluster;
     }
   }
 
-  /** MinIO + Amazon CRT async HTTP client (default). */
+  /** Local S3 container + Amazon CRT async HTTP client (default). */
   @Nested
-  static class MinIO_Crt extends MinIOPerfBase
+  static class LocalS3_Crt extends LocalS3PerfBase
   {
     @Override
     void configureCluster(EmbeddedDruidCluster cluster)
@@ -188,9 +188,9 @@ public class S3PerfTest
     }
   }
 
-  /** MinIO + Netty NIO async HTTP client. */
+  /** Local S3 container + Netty NIO async HTTP client. */
   @Nested
-  static class MinIO_Netty extends MinIOPerfBase
+  static class LocalS3_Netty extends LocalS3PerfBase
   {
     @Override
     void configureCluster(EmbeddedDruidCluster cluster)
@@ -199,9 +199,9 @@ public class S3PerfTest
     }
   }
 
-  /** MinIO + synchronous PutObject (transfer manager disabled). */
+  /** Local S3 container + synchronous PutObject (transfer manager disabled). 
*/
   @Nested
-  static class MinIO_Sync extends MinIOPerfBase
+  static class LocalS3_Sync extends LocalS3PerfBase
   {
     @Override
     void configureCluster(EmbeddedDruidCluster cluster)
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/MinIOStorageResource.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/S3StorageResource.java
similarity index 79%
rename from 
embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/MinIOStorageResource.java
rename to 
embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/S3StorageResource.java
index b298d6e2627..525d862abce 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/MinIOStorageResource.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/S3StorageResource.java
@@ -17,16 +17,18 @@
  * under the License.
  */
 
-package org.apache.druid.testing.embedded.minio;
+package org.apache.druid.testing.embedded.s3;
 
 import org.apache.druid.common.aws.AWSModule;
 import org.apache.druid.data.input.s3.S3InputSourceConfig;
 import org.apache.druid.java.util.common.ISE;
+import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.metadata.DefaultPasswordProvider;
 import org.apache.druid.storage.s3.S3StorageDruidModule;
 import org.apache.druid.testing.embedded.EmbeddedDruidCluster;
 import org.apache.druid.testing.embedded.TestcontainerResource;
-import org.testcontainers.containers.MinIOContainer;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
 import org.testcontainers.utility.DockerImageName;
 import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
 import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
@@ -41,39 +43,41 @@ import 
software.amazon.awssdk.services.sts.model.Credentials;
 import java.net.URI;
 
 /**
- * A MinIO container resource for use in embedded tests as deep storage.
- * Sets up MinIO as S3-compatible storage and configures Druid's S3 connector.
+ * An S3-compatible storage container resource for use in embedded tests as 
deep storage.
+ * Runs <a href="https://github.com/rustfs/rustfs";>RustFS</a> and configures 
Druid's S3 connector to use it.
  */
-public class MinIOStorageResource extends TestcontainerResource<MinIOContainer>
+public class S3StorageResource extends 
TestcontainerResource<GenericContainer<?>>
 {
-  private static final DockerImageName MINIO_IMAGE =
-      
DockerImageName.parse("quay.io/minio/minio:latest").asCompatibleSubstituteFor("minio/minio");
+  private static final DockerImageName IMAGE = 
DockerImageName.parse("rustfs/rustfs:1.0.0");
+  private static final int S3_PORT = 9000;
   private static final String DEFAULT_BUCKET = "druid-deep-storage";
   private static final String DEFAULT_BASE_KEY = "druid/segments";
-  private static final String ACCESS_KEY = "minioadmin";
-  private static final String SECRET_KEY = "minioadmin";
+  private static final String ACCESS_KEY = "rustfsadmin";
+  private static final String SECRET_KEY = "rustfsadmin";
 
   private final String bucket;
   private final String baseKey;
   private S3Client s3Client;
 
-  public MinIOStorageResource()
+  public S3StorageResource()
   {
     this(DEFAULT_BUCKET, DEFAULT_BASE_KEY);
   }
 
-  public MinIOStorageResource(String bucket, String baseKey)
+  public S3StorageResource(String bucket, String baseKey)
   {
     this.bucket = bucket;
     this.baseKey = baseKey;
   }
 
   @Override
-  protected MinIOContainer createContainer()
+  protected GenericContainer<?> createContainer()
   {
-    return new MinIOContainer(MINIO_IMAGE)
-        .withUserName(getAccessKey())
-        .withPassword(getSecretKey());
+    return new GenericContainer<>(IMAGE)
+        .withExposedPorts(S3_PORT)
+        .withEnv("RUSTFS_ACCESS_KEY", getAccessKey())
+        .withEnv("RUSTFS_SECRET_KEY", getSecretKey())
+        .waitingFor(Wait.forHttp("/health/ready").forPort(S3_PORT));
   }
 
   @Override
@@ -97,7 +101,7 @@ public class MinIOStorageResource extends 
TestcontainerResource<MinIOContainer>
 
     // Configure S3 connection properties
     cluster.addCommonProperty("druid.s3.endpoint.url", 
cluster.getEmbeddedHostname().useInUri(getEndpointUrl()));
-    // AWS SDK v2 requires a region; use a fixed value since MinIO doesn't 
validate it
+    // AWS SDK v2 requires a region; use a fixed value since the storage 
container doesn't validate it
     cluster.addCommonProperty("druid.s3.endpoint.signingRegion", "us-east-1");
     cluster.addCommonProperty("druid.s3.accessKey", getAccessKey());
     cluster.addCommonProperty("druid.s3.secretKey", getSecretKey());
@@ -129,7 +133,7 @@ public class MinIOStorageResource extends 
TestcontainerResource<MinIOContainer>
   public String getEndpointUrl()
   {
     ensureRunning();
-    return getContainer().getS3URL();
+    return StringUtils.format("http://%s:%d";, getContainer().getHost(), 
getContainer().getMappedPort(S3_PORT));
   }
 
   public S3Client getS3Client()
@@ -144,7 +148,7 @@ public class MinIOStorageResource extends 
TestcontainerResource<MinIOContainer>
    *
    * @return S3InputSourceConfig with temporary credentials. The
    * {@code assumeRoleArn} and {@code assumeRoleExternalId} fields are set to 
null
-   * since MinIO does not support them.
+   * since the storage container does not support them.
    */
   public S3InputSourceConfig createTempCredentialsForInputSource()
   {
@@ -152,11 +156,11 @@ public class MinIOStorageResource extends 
TestcontainerResource<MinIOContainer>
 
     final StsClient stsClient = createStsClient();
 
-    // SDK v2 requires a non-null roleArn. MinIO does not validate the ARN,
+    // SDK v2 requires a non-null roleArn. The storage container does not 
validate the ARN,
     // but without an inline policy the resulting session may have no 
permissions.
     // An explicit S3 full-access policy ensures the temp credentials work.
     final String s3FullAccessPolicy =
-        
"{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"s3:*\"],\"Resource\":[\"*\"]}]}";
+        
"{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"s3:*\"],\"Resource\":[\"arn:aws:s3:::*\"]}]}";
     final AssumeRoleRequest assumeRoleRequest = AssumeRoleRequest.builder()
         .roleArn("arn:aws:iam::000000000000:role/test-role")
         .roleSessionName("test-session")
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/MinIOStorageResourceTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/S3StorageResourceTest.java
similarity index 84%
rename from 
embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/MinIOStorageResourceTest.java
rename to 
embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/S3StorageResourceTest.java
index 0cdcb4b2b9a..6e865f0b0ba 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/MinIOStorageResourceTest.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/S3StorageResourceTest.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.druid.testing.embedded.minio;
+package org.apache.druid.testing.embedded.s3;
 
 import org.apache.druid.testing.embedded.EmbeddedDruidCluster;
 import org.apache.druid.testing.embedded.EmbeddedHostname;
@@ -31,13 +31,13 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.verify;
 
-public class MinIOStorageResourceTest
+public class S3StorageResourceTest
 {
   @Test
   @Timeout(120)
-  public void testMinIOContainerLifecycle()
+  public void testContainerLifecycle()
   {
-    final MinIOStorageResource resource = new 
MinIOStorageResource("test-bucket", "test/base");
+    final S3StorageResource resource = new S3StorageResource("test-bucket", 
"test/base");
     final EmbeddedDruidCluster cluster = 
Mockito.mock(EmbeddedDruidCluster.class);
     
Mockito.when(cluster.getEmbeddedHostname()).thenReturn(EmbeddedHostname.localhost());
     resource.beforeStart(cluster);
@@ -50,16 +50,16 @@ public class MinIOStorageResourceTest
     // Verify container properties
     assertEquals("test-bucket", resource.getBucket());
     assertEquals("test/base", resource.getBaseKey());
-    assertEquals("minioadmin", resource.getAccessKey());
-    assertEquals("minioadmin", resource.getSecretKey());
+    assertEquals("rustfsadmin", resource.getAccessKey());
+    assertEquals("rustfsadmin", resource.getSecretKey());
 
     // Verify all the required properties are set
     verify(cluster).addCommonProperty("druid.storage.type", "s3");
     verify(cluster).addCommonProperty("druid.indexer.logs.type", "s3");
     verify(cluster).addCommonProperty("druid.s3.enablePathStyleAccess", 
"true");
     verify(cluster).addCommonProperty("druid.s3.protocol", "http");
-    verify(cluster).addCommonProperty("druid.s3.accessKey", "minioadmin");
-    verify(cluster).addCommonProperty("druid.s3.secretKey", "minioadmin");
+    verify(cluster).addCommonProperty("druid.s3.accessKey", "rustfsadmin");
+    verify(cluster).addCommonProperty("druid.s3.secretKey", "rustfsadmin");
     verify(cluster).addCommonProperty("druid.storage.bucket", "test-bucket");
     verify(cluster).addCommonProperty("druid.storage.baseKey", "test/base");
     verify(cluster).addCommonProperty("druid.indexer.logs.s3Bucket", 
"test-bucket");
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/MinIOStorageTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/S3StorageTest.java
similarity index 84%
rename from 
embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/MinIOStorageTest.java
rename to 
embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/S3StorageTest.java
index 199ddaec9c7..3d80091c8d9 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/MinIOStorageTest.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/S3StorageTest.java
@@ -17,23 +17,23 @@
  * under the License.
  */
 
-package org.apache.druid.testing.embedded.minio;
+package org.apache.druid.testing.embedded.s3;
 
 import org.apache.druid.testing.embedded.EmbeddedDruidCluster;
 import org.apache.druid.testing.embedded.EmbeddedRouter;
 import org.apache.druid.testing.embedded.indexing.IndexTaskTest;
 
 /**
- * Same as {@link IndexTaskTest}, but using a MinIO metadata store through the 
S3 extension.
+ * Same as {@link IndexTaskTest}, but using S3 deep storage (an S3-compatible 
testcontainer) through the S3 extension.
  */
-public class MinIOStorageTest extends IndexTaskTest
+public class S3StorageTest extends IndexTaskTest
 {
   @Override
   public EmbeddedDruidCluster createCluster()
   {
     return EmbeddedDruidCluster.withEmbeddedDerbyAndZookeeper()
                                .useLatchableEmitter()
-                               .addResource(new MinIOStorageResource())
+                               .addResource(new S3StorageResource())
                                .addServer(overlord)
                                .addServer(coordinator)
                                .addServer(indexer)
diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/S3TestUtil.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/S3TestUtil.java
similarity index 99%
rename from 
embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/S3TestUtil.java
rename to 
embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/S3TestUtil.java
index 99672275d4f..e2996c8d2ff 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/minio/S3TestUtil.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/s3/S3TestUtil.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.apache.druid.testing.embedded.minio;
+package org.apache.druid.testing.embedded.s3;
 
 import org.apache.druid.java.util.common.logger.Logger;
 import org.apache.druid.testing.embedded.indexing.Resources;
diff --git a/extensions-core/s3-extensions/pom.xml 
b/extensions-core/s3-extensions/pom.xml
index 9b6581e8434..c872293a47c 100644
--- a/extensions-core/s3-extensions/pom.xml
+++ b/extensions-core/s3-extensions/pom.xml
@@ -231,12 +231,6 @@
       <artifactId>mockito-junit-jupiter</artifactId>
       <scope>test</scope>
     </dependency>
-    <dependency>
-      <groupId>org.testcontainers</groupId>
-      <artifactId>testcontainers-minio</artifactId>
-      <version>${testcontainers.version}</version>
-      <scope>test</scope>
-    </dependency>
   </dependencies>
 
   <profiles>
diff --git 
a/extensions-core/s3-extensions/src/test/java/org/apache/druid/storage/s3/RustFSContainer.java
 
b/extensions-core/s3-extensions/src/test/java/org/apache/druid/storage/s3/RustFSContainer.java
new file mode 100644
index 00000000000..9a658e62abf
--- /dev/null
+++ 
b/extensions-core/s3-extensions/src/test/java/org/apache/druid/storage/s3/RustFSContainer.java
@@ -0,0 +1,77 @@
+/*
+ * 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.druid.storage.s3;
+
+import org.apache.druid.java.util.common.StringUtils;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.utility.DockerImageName;
+
+/**
+ * Testcontainer for <a href="https://github.com/rustfs/rustfs";>RustFS</a>, an 
S3-compatible object store, for tests
+ * that need a real S3 endpoint.
+ */
+public class RustFSContainer extends GenericContainer<RustFSContainer>
+{
+  public static final DockerImageName IMAGE = 
DockerImageName.parse("rustfs/rustfs:1.0.0");
+  public static final String DEFAULT_ACCESS_KEY = "rustfsadmin";
+  public static final String DEFAULT_SECRET_KEY = "rustfsadmin";
+
+  private static final int S3_PORT = 9000;
+
+  private String accessKey = DEFAULT_ACCESS_KEY;
+  private String secretKey = DEFAULT_SECRET_KEY;
+
+  public RustFSContainer()
+  {
+    super(IMAGE);
+    withExposedPorts(S3_PORT);
+    waitingFor(Wait.forHttp("/health/ready").forPort(S3_PORT));
+  }
+
+  public RustFSContainer withCredentials(String accessKey, String secretKey)
+  {
+    this.accessKey = accessKey;
+    this.secretKey = secretKey;
+    return this;
+  }
+
+  @Override
+  protected void configure()
+  {
+    withEnv("RUSTFS_ACCESS_KEY", accessKey);
+    withEnv("RUSTFS_SECRET_KEY", secretKey);
+  }
+
+  public String getS3URL()
+  {
+    return StringUtils.format("http://%s:%d";, getHost(), 
getMappedPort(S3_PORT));
+  }
+
+  public String getAccessKey()
+  {
+    return accessKey;
+  }
+
+  public String getSecretKey()
+  {
+    return secretKey;
+  }
+}
diff --git 
a/extensions-core/s3-extensions/src/test/java/org/apache/druid/storage/s3/ServerSideEncryptingAmazonS3MultipartUploadTest.java
 
b/extensions-core/s3-extensions/src/test/java/org/apache/druid/storage/s3/ServerSideEncryptingAmazonS3MultipartUploadTest.java
index f1ec809a8c3..ce3979db5a8 100644
--- 
a/extensions-core/s3-extensions/src/test/java/org/apache/druid/storage/s3/ServerSideEncryptingAmazonS3MultipartUploadTest.java
+++ 
b/extensions-core/s3-extensions/src/test/java/org/apache/druid/storage/s3/ServerSideEncryptingAmazonS3MultipartUploadTest.java
@@ -27,10 +27,8 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
-import org.testcontainers.containers.MinIOContainer;
 import org.testcontainers.junit.jupiter.Container;
 import org.testcontainers.junit.jupiter.Testcontainers;
-import org.testcontainers.utility.DockerImageName;
 import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
 import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
 import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
@@ -66,10 +64,7 @@ public class ServerSideEncryptingAmazonS3MultipartUploadTest
   private static final long CONFIGURED_PART_SIZE = 16 * MIB;
 
   @Container
-  private static final MinIOContainer MINIO =
-      new MinIOContainer(
-          
DockerImageName.parse("quay.io/minio/minio:latest").asCompatibleSubstituteFor("minio/minio")
-      ).withEnv("MINIO_DOMAIN", "localhost");
+  private static final RustFSContainer S3 = new RustFSContainer();
 
   @TempDir
   public File temporaryFolder;
@@ -89,7 +84,7 @@ public class ServerSideEncryptingAmazonS3MultipartUploadTest
       @Override
       public String getUrl()
       {
-        return MINIO.getS3URL();
+        return S3.getS3URL();
       }
 
       @Override
@@ -99,7 +94,7 @@ public class ServerSideEncryptingAmazonS3MultipartUploadTest
       }
     };
 
-    // MinIO is reached by host:port, so bucket-as-subdomain addressing will 
not resolve.
+    // The container is reached by host:port, so bucket-as-subdomain 
addressing will not resolve.
     final AWSClientConfig clientConfig = new AWSClientConfig()
     {
       @Override
@@ -110,7 +105,7 @@ public class ServerSideEncryptingAmazonS3MultipartUploadTest
     };
 
     s3 = ServerSideEncryptingAmazonS3.builder(
-        
StaticCredentialsProvider.create(AwsBasicCredentials.create(MINIO.getUserName(),
 MINIO.getPassword())),
+        
StaticCredentialsProvider.create(AwsBasicCredentials.create(S3.getAccessKey(), 
S3.getSecretKey())),
         new S3StorageConfig(new NoopServerSideEncryption(), transferConfig),
         null,
         endpointConfig,
diff --git 
a/extensions-core/s3-extensions/src/test/java/org/apache/druid/storage/s3/output/S3StorageConnectorTest.java
 
b/extensions-core/s3-extensions/src/test/java/org/apache/druid/storage/s3/output/S3StorageConnectorTest.java
index 61c1bbdb7f9..cd418bbb56c 100644
--- 
a/extensions-core/s3-extensions/src/test/java/org/apache/druid/storage/s3/output/S3StorageConnectorTest.java
+++ 
b/extensions-core/s3-extensions/src/test/java/org/apache/druid/storage/s3/output/S3StorageConnectorTest.java
@@ -27,18 +27,16 @@ import 
org.apache.druid.java.util.metrics.StubServiceEmitter;
 import org.apache.druid.query.DruidProcessingConfigTest;
 import org.apache.druid.storage.StorageConnector;
 import org.apache.druid.storage.remote.ChunkingStorageConnectorParameters;
+import org.apache.druid.storage.s3.RustFSContainer;
 import org.apache.druid.storage.s3.ServerSideEncryptingAmazonS3;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
-import org.testcontainers.containers.MinIOContainer;
 import org.testcontainers.junit.jupiter.Container;
 import org.testcontainers.junit.jupiter.Testcontainers;
-import org.testcontainers.utility.DockerImageName;
 import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
-import software.amazon.awssdk.auth.credentials.AwsCredentials;
 import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
 import software.amazon.awssdk.regions.Region;
 import software.amazon.awssdk.services.s3.S3Client;
@@ -69,7 +67,7 @@ public class S3StorageConnectorTest
   private static final String PREFIX = "P/R/E/F/I/X";
   public static final String TEST_FILE = "test.csv";
   @Container
-  private static final MinIOContainer MINIO = MinioUtil.createContainer();
+  private static final RustFSContainer S3 = new RustFSContainer();
   @TempDir
   public static File temporaryFolder;
   private ServerSideEncryptingAmazonS3 s3Client;
@@ -79,7 +77,7 @@ public class S3StorageConnectorTest
   @BeforeEach
   public void setup() throws IOException
   {
-    s3Client = MinioUtil.createS3Client(MINIO);
+    s3Client = S3ClientUtil.createS3Client(S3);
     try {
       s3Client.getS3Client().headBucket(builder -> builder.bucket(BUCKET));
     }
@@ -141,7 +139,7 @@ public class S3StorageConnectorTest
     );
     StorageConnector unauthorizedStorageConnector = new S3StorageConnector(
         s3OutputConfig,
-        MinioUtil.createUnauthorizedS3Client(MINIO),
+        S3ClientUtil.createUnauthorizedS3Client(S3),
         new S3UploadManager(
             s3OutputConfig,
             new S3ExportConfig("tempDir", new HumanReadableBytes("5MiB"), 1, 
null),
@@ -339,58 +337,29 @@ public class S3StorageConnectorTest
     Assertions.assertEquals(ImmutableList.of("listFirst"), listDirResult);
   }
 
-  // adapted from apache iceberg tests
-  private static class MinioUtil
+  private static class S3ClientUtil
   {
-    private MinioUtil()
+    private S3ClientUtil()
     {
     }
 
-    public static MinIOContainer createContainer()
+    public static ServerSideEncryptingAmazonS3 createS3Client(RustFSContainer 
container)
     {
-      return createContainer(null);
+      return createS3Client(container, container.getSecretKey());
     }
 
-    public static MinIOContainer createContainer(AwsCredentials credentials)
+    public static ServerSideEncryptingAmazonS3 
createUnauthorizedS3Client(RustFSContainer container)
     {
-      MinIOContainer container = new MinIOContainer(
-          
DockerImageName.parse("quay.io/minio/minio:latest").asCompatibleSubstituteFor("minio/minio")
-      );
-
-      // this enables virtual-host-style requests. see
-      // https://github.com/minio/minio/tree/master/docs/config#domain
-      container.withEnv("MINIO_DOMAIN", "localhost");
-
-      if (credentials != null) {
-        container.withUserName(credentials.accessKeyId());
-        container.withPassword(credentials.secretAccessKey());
-      }
-
-      return container;
-    }
-
-    public static ServerSideEncryptingAmazonS3 createS3Client(MinIOContainer 
container)
-    {
-      final S3Client s3Client = S3Client.builder()
-          .endpointOverride(URI.create(container.getS3URL()))
-          .region(Region.US_EAST_1)
-          .credentialsProvider(StaticCredentialsProvider.create(
-              AwsBasicCredentials.create(container.getUserName(), 
container.getPassword())))
-          .forcePathStyle(true) // OSX won't resolve subdomains
-          .build();
-
-      return ServerSideEncryptingAmazonS3.builder()
-                                         .setS3ClientSupplier(() -> s3Client)
-                                         .build();
+      return createS3Client(container, "wrong");
     }
 
-    public static ServerSideEncryptingAmazonS3 
createUnauthorizedS3Client(MinIOContainer container)
+    private static ServerSideEncryptingAmazonS3 createS3Client(RustFSContainer 
container, String secretKey)
     {
       final S3Client s3Client = S3Client.builder()
           .endpointOverride(URI.create(container.getS3URL()))
           .region(Region.US_EAST_1)
           .credentialsProvider(StaticCredentialsProvider.create(
-              AwsBasicCredentials.create(container.getUserName(), "wrong")))
+              AwsBasicCredentials.create(container.getAccessKey(), secretKey)))
           .forcePathStyle(true) // OSX won't resolve subdomains
           .build();
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to