You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hudi.apache.org by yi...@apache.org on 2026/05/20 06:21:50 UTC

(hudi) 37/45: test(azure): skip ITAzureStorageLockClientAzurite when MCR image pull fails (#18772)

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 <sh...@uber.com>
AuthorDate: Tue May 19 12:05:07 2026 -0700

    test(azure): skip ITAzureStorageLockClientAzurite when MCR image pull fails (#18772)
    
    Co-authored-by: Xinli Shang <sh...@apache.org>
---
 .../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() {