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() {