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

yihua pushed a commit to branch release-1.2.0
in repository https://gitbox.apache.org/repos/asf/hudi.git

commit 3dddb15ac610d013ff694381d8d65556923f860b
Author: Xinli Shang <[email protected]>
AuthorDate: Tue May 19 12:05:07 2026 -0700

    test(azure): skip ITAzureStorageLockClientAzurite when MCR image pull fails 
(#18772)
    
    Co-authored-by: Xinli Shang <[email protected]>
---
 .../lock/ITAzureStorageLockClientAzurite.java      | 72 ++++++++++++++++++----
 1 file changed, 61 insertions(+), 11 deletions(-)

diff --git 
a/hudi-azure/src/test/java/org/apache/hudi/azure/transaction/lock/ITAzureStorageLockClientAzurite.java
 
b/hudi-azure/src/test/java/org/apache/hudi/azure/transaction/lock/ITAzureStorageLockClientAzurite.java
index a7897a6ac3d7..8f5b57aab0aa 100644
--- 
a/hudi-azure/src/test/java/org/apache/hudi/azure/transaction/lock/ITAzureStorageLockClientAzurite.java
+++ 
b/hudi-azure/src/test/java/org/apache/hudi/azure/transaction/lock/ITAzureStorageLockClientAzurite.java
@@ -30,12 +30,15 @@ import org.apache.hudi.config.AzureStorageLockConfig;
 import com.azure.storage.blob.BlobContainerClient;
 import com.azure.storage.blob.BlobServiceClient;
 import com.azure.storage.blob.BlobServiceClientBuilder;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;
+import org.testcontainers.containers.ContainerFetchException;
+import org.testcontainers.containers.ContainerLaunchException;
 import org.testcontainers.containers.GenericContainer;
 import org.testcontainers.containers.wait.strategy.Wait;
-import org.testcontainers.junit.jupiter.Container;
-import org.testcontainers.junit.jupiter.Testcontainers;
 import org.testcontainers.utility.DockerImageName;
 
 import java.time.Duration;
@@ -43,18 +46,28 @@ import java.util.Properties;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.abort;
 
 /**
  * Integration tests for {@link AzureStorageLockClient} using Azurite (Azure 
Storage emulator).
  *
  * <p>Run with: {@code mvn -Pazure-integration-tests -pl hudi-azure verify}
+ *
+ * <p>If the Azurite Docker image cannot be pulled (Microsoft Container 
Registry blocked,
+ * rate-limited, or network unavailable), the test class is skipped via {@link
+ * org.junit.jupiter.api.Assumptions#abort(String)} rather than failing the CI 
run. Integration
+ * tests against external container images should not gate the overall build 
on registry-side
+ * outages that are outside the project's control.
  */
-@Testcontainers(disabledWithoutDocker = true)
+@Slf4j
 @DisabledIfEnvironmentVariable(named = "SKIP_AZURITE_IT", matches = "true")
 public class ITAzureStorageLockClientAzurite {
 
+  // Pin the Azurite version. The previous unpinned reference (implicit 
":latest") was more
+  // susceptible to MCR rate-limiting / WAF blocks because :latest is queried 
more aggressively
+  // and is not cached as effectively as a tagged manifest. Bump deliberately 
when needed.
   private static final DockerImageName AZURITE_IMAGE =
-      DockerImageName.parse("mcr.microsoft.com/azure-storage/azurite");
+      DockerImageName.parse("mcr.microsoft.com/azure-storage/azurite:3.34.0");
 
   // Standard Azurite defaults (documented by Microsoft)
   private static final String ACCOUNT_NAME = "devstoreaccount1";
@@ -63,16 +76,53 @@ public class ITAzureStorageLockClientAzurite {
   private static final String ACCOUNT_KEY =
       
"Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";
 
-  @Container
-  public static final GenericContainer<?> AZURITE =
-      new GenericContainer<>(AZURITE_IMAGE)
-          .withExposedPorts(10000)
-          .withCommand("azurite-blob", "--blobHost", "0.0.0.0", "--blobPort", 
"10000", "--loose")
-          
.waitingFor(Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(60)));
+  // Manual container lifecycle (instead of @Testcontainers + @Container) so 
the test class is
+  // skipped — not failed — when the image cannot be pulled. The 
@Testcontainers extension
+  // surfaces image-pull errors as test errors, which is the wrong outcome for 
an external
+  // infrastructure failure (e.g. MCR returning HTTP 403 "request blocked" via 
Azure WAF).
+  private static GenericContainer<?> azurite;
+
+  @BeforeAll
+  static void startAzurite() {
+    GenericContainer<?> container = new GenericContainer<>(AZURITE_IMAGE)
+        .withExposedPorts(10000)
+        .withCommand("azurite-blob", "--blobHost", "0.0.0.0", "--blobPort", 
"10000", "--loose")
+        
.waitingFor(Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(60)));
+    try {
+      container.start();
+      azurite = container;
+    } catch (ContainerFetchException | ContainerLaunchException e) {
+      // Image pull / container start failure. Most commonly caused by MCR 
being unreachable
+      // or rate-limited from CI runners. Skip the suite rather than failing 
the build.
+      log.warn("Azurite container unavailable; skipping suite.", e);
+      abort("Azurite container could not be started (image pull or launch 
failed): " + e.getMessage());
+    } catch (RuntimeException e) {
+      // Testcontainers wraps a variety of Docker / network failures in plain 
RuntimeExceptions
+      // during start(). Treat any "could not start container" failure as a 
skip rather than
+      // an error for the same reason — these are infrastructure flakes, not 
test failures.
+      // Re-throw if Docker itself is missing so the developer gets a clear 
diagnosis locally.
+      // Known messages we want to surface: "Could not find a valid Docker 
environment",
+      // "Docker not found", "docker: command not found".
+      String msg = e.getMessage() == null ? "" : e.getMessage().toLowerCase();
+      if (msg.contains("docker") && (msg.contains("not found") || 
msg.contains("no such")
+          || msg.contains("could not find") || msg.contains("not running"))) {
+        throw e;
+      }
+      log.warn("Azurite container unavailable; skipping suite.", e);
+      abort("Azurite container could not be started: " + e.getMessage());
+    }
+  }
+
+  @AfterAll
+  static void stopAzurite() {
+    if (azurite != null && azurite.isRunning()) {
+      azurite.stop();
+    }
+  }
 
   private static String blobEndpoint() {
     // Azurite expects /<accountName> in the endpoint URL path
-    return "http://"; + AZURITE.getHost() + ":" + AZURITE.getMappedPort(10000) 
+ "/" + ACCOUNT_NAME;
+    return "http://"; + azurite.getHost() + ":" + azurite.getMappedPort(10000) 
+ "/" + ACCOUNT_NAME;
   }
 
   private static String connectionString() {

Reply via email to