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

kfaraz 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 b8e11706e61 test: Fix flaky `BasicAuthMSQTest` (#19593)
b8e11706e61 is described below

commit b8e11706e61054889b5f2061ac7f67e0dbf2a236
Author: Andreas Maechler <[email protected]>
AuthorDate: Tue Jun 23 00:52:00 2026 -0600

    test: Fix flaky `BasicAuthMSQTest` (#19593)
    
    Retry the task submission while it fails with these transient auth
    errors so the assertions only run once the Broker's auth cache reflects
    the test setup. Other failures are not retried, so real errors still
    fail fast.
---
 .../testing/embedded/auth/BasicAuthMSQTest.java    | 122 ++++++++++++++-------
 1 file changed, 81 insertions(+), 41 deletions(-)

diff --git 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/auth/BasicAuthMSQTest.java
 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/auth/BasicAuthMSQTest.java
index 87012ce2c7b..46210caec44 100644
--- 
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/auth/BasicAuthMSQTest.java
+++ 
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/auth/BasicAuthMSQTest.java
@@ -21,6 +21,7 @@ package org.apache.druid.testing.embedded.auth;
 
 import com.google.common.collect.ImmutableList;
 import org.apache.druid.error.ExceptionMatcher;
+import org.apache.druid.java.util.common.RetryUtils;
 import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.metadata.DefaultPasswordProvider;
 import org.apache.druid.query.http.ClientSqlQuery;
@@ -58,6 +59,12 @@ public class BasicAuthMSQTest extends EmbeddedClusterTestBase
   public static final String ROLE_1 = "role1";
   public static final String USER_1_PASSWORD = "password1";
 
+  /**
+   * Attempts allowed for a request while basic-auth changes reach the broker, 
which the
+   * coordinator then propagates asynchronously.
+   */
+  private static final int AUTH_PROPAGATION_ATTEMPTS = 5;
+
   private SecurityClient securityClient;
   private EmbeddedServiceClient userClient;
 
@@ -134,7 +141,7 @@ public class BasicAuthMSQTest extends 
EmbeddedClusterTestBase
   }
 
   @Test
-  public void testIngestionWithPermissions()
+  public void testIngestionWithPermissions() throws Exception
   {
     List<ResourceAction> permissions = ImmutableList.of(
         new ResourceAction(new Resource(".*", "DATASOURCE"), Action.READ),
@@ -150,11 +157,7 @@ public class BasicAuthMSQTest extends 
EmbeddedClusterTestBase
         Resources.DataFile.tinyWiki1Json()
     );
 
-    final SqlTaskStatus taskStatus = userClient.onAnyBroker(
-        b -> b.submitSqlTask(
-            new ClientSqlQuery(queryLocal, null, false, false, false, 
Map.of(), List.of())
-        )
-    );
+    final SqlTaskStatus taskStatus = submitSqlTaskWhenAuthorized(queryLocal);
     cluster.callApi().waitForTaskToSucceed(taskStatus.getTaskId(), overlord);
   }
 
@@ -173,16 +176,18 @@ public class BasicAuthMSQTest extends 
EmbeddedClusterTestBase
 
     String exportQuery =
         StringUtils.format(
-            "INSERT INTO extern(%s(exportPath => '%s'))\n"
-            + "AS CSV\n"
-            + "SELECT page, added, delta\n"
-            + "FROM TABLE(\n"
-            + "  EXTERN(\n"
-            + "    '{\"type\":\"local\",\"files\":[\"%s\"]}',\n"
-            + "    '{\"type\":\"json\"}',\n"
-            + "    
'[{\"type\":\"string\",\"name\":\"timestamp\"},{\"type\":\"string\",\"name\":\"isRobot\"},{\"type\":\"string\",\"name\":\"diffUrl\"},{\"type\":\"long\",\"name\":\"added\"},{\"type\":\"string\",\"name\":\"countryIsoCode\"},{\"type\":\"string\",\"name\":\"regionName\"},{\"type\":\"string\",\"name\":\"channel\"},{\"type\":\"string\",\"name\":\"flags\"},{\"type\":\"long\",\"name\":\"delta\"},{\"type\":\"string\",\"name\":\"isUnpatrolled\"},{\"type\":\"string\",\"name\":\"i
 [...]
-            + "  )\n"
-            + ")\n",
+            """
+                INSERT INTO extern(%s(exportPath => '%s'))
+                AS CSV
+                SELECT page, added, delta
+                FROM TABLE(
+                  EXTERN(
+                    '{"type":"local","files":["%s"]}',
+                    '{"type":"json"}',
+                    
'[{"type":"string","name":"timestamp"},{"type":"string","name":"isRobot"},{"type":"string","name":"diffUrl"},{"type":"long","name":"added"},{"type":"string","name":"countryIsoCode"},{"type":"string","name":"regionName"},{"type":"string","name":"channel"},{"type":"string","name":"flags"},{"type":"long","name":"delta"},{"type":"string","name":"isUnpatrolled"},{"type":"string","name":"isNew"},{"type":"double","name":"deltaBucket"},{"type":"string","name":"isMinor"},{"typ
 [...]
+                  )
+                )
+                """,
             LocalFileExportStorageProvider.TYPE_NAME,
             
cluster.getTestFolder().getOrCreateFolder("msq-export").getAbsolutePath(),
             Resources.DataFile.tinyWiki1Json().getAbsolutePath()
@@ -192,7 +197,7 @@ public class BasicAuthMSQTest extends 
EmbeddedClusterTestBase
   }
 
   @Test
-  public void testExportWithPermissions()
+  public void testExportWithPermissions() throws Exception
   {
     // No external write permissions for s3
     List<ResourceAction> permissions = ImmutableList.of(
@@ -206,41 +211,76 @@ public class BasicAuthMSQTest extends 
EmbeddedClusterTestBase
 
     String exportQuery =
         StringUtils.format(
-            "INSERT INTO extern(%s(exportPath => '%s'))\n"
-            + "AS CSV\n"
-            + "SELECT page, added, delta\n"
-            + "FROM TABLE(\n"
-            + "  EXTERN(\n"
-            + "    '{\"type\":\"local\",\"files\":[\"%s\"]}',\n"
-            + "    '{\"type\":\"json\"}',\n"
-            + "    
'[{\"type\":\"string\",\"name\":\"timestamp\"},{\"type\":\"string\",\"name\":\"isRobot\"},{\"type\":\"string\",\"name\":\"diffUrl\"},{\"type\":\"long\",\"name\":\"added\"},{\"type\":\"string\",\"name\":\"countryIsoCode\"},{\"type\":\"string\",\"name\":\"regionName\"},{\"type\":\"string\",\"name\":\"channel\"},{\"type\":\"string\",\"name\":\"flags\"},{\"type\":\"long\",\"name\":\"delta\"},{\"type\":\"string\",\"name\":\"isUnpatrolled\"},{\"type\":\"string\",\"name\":\"i
 [...]
-            + "  )\n"
-            + ")\n",
+            """
+                INSERT INTO extern(%s(exportPath => '%s'))
+                AS CSV
+                SELECT page, added, delta
+                FROM TABLE(
+                  EXTERN(
+                    '{"type":"local","files":["%s"]}',
+                    '{"type":"json"}',
+                    
'[{"type":"string","name":"timestamp"},{"type":"string","name":"isRobot"},{"type":"string","name":"diffUrl"},{"type":"long","name":"added"},{"type":"string","name":"countryIsoCode"},{"type":"string","name":"regionName"},{"type":"string","name":"channel"},{"type":"string","name":"flags"},{"type":"long","name":"delta"},{"type":"string","name":"isUnpatrolled"},{"type":"string","name":"isNew"},{"type":"double","name":"deltaBucket"},{"type":"string","name":"isMinor"},{"typ
 [...]
+                  )
+                )
+                """,
             LocalFileExportStorageProvider.TYPE_NAME,
             new File(exportDirectory.get(), dataSource).getAbsolutePath(),
             Resources.DataFile.tinyWiki1Json()
         );
 
-    final SqlTaskStatus taskStatus = userClient.onAnyBroker(
+    final SqlTaskStatus taskStatus = submitSqlTaskWhenAuthorized(exportQuery);
+    cluster.callApi().waitForTaskToSucceed(taskStatus.getTaskId(), overlord);
+  }
+
+  /**
+   * Submits an MSQ task to an arbitrary Broker as {@link #USER_1}.
+   */
+  private SqlTaskStatus submitSqlTaskAsUser(String sql)
+  {
+    return userClient.onAnyBroker(
         b -> b.submitSqlTask(
-            new ClientSqlQuery(exportQuery, null, false, false, false, 
Map.of(), List.of())
+            new ClientSqlQuery(sql, null, false, false, false, Map.of(), 
List.of())
         )
     );
-    cluster.callApi().waitForTaskToSucceed(taskStatus.getTaskId(), overlord);
   }
 
-  private void verifySqlSubmitFailsWith403Forbidden(String sql)
+  /**
+   * Submits an MSQ task as {@link #USER_1}.
+   */
+  private SqlTaskStatus submitSqlTaskWhenAuthorized(String sql) throws 
Exception
   {
-    MatcherAssert.assertThat(
-        Assertions.assertThrows(
-            Exception.class,
-            () -> userClient.onAnyBroker(
-                b -> b.submitSqlTask(
-                    new ClientSqlQuery(sql, null, false, false, false, 
Map.of(), List.of())
-                )
-            )
-        ),
-        ExceptionMatcher.of(Exception.class).expectMessageContains("403 
Forbidden")
+    return RetryUtils.retry(
+        () -> submitSqlTaskAsUser(sql),
+        e -> unauthorizedExceptionMatcher().matches(e) || 
forbiddenExceptionMatcher().matches(e),
+        AUTH_PROPAGATION_ATTEMPTS
     );
   }
+
+  /**
+   * Asserts that submitting SQL as an unauthorized user fails with 403 
Forbidden.
+   */
+  private void verifySqlSubmitFailsWith403Forbidden(String sql)
+  {
+    try {
+      RetryUtils.retry(
+          () -> submitSqlTaskAsUser(sql),
+          e -> unauthorizedExceptionMatcher().matches(e),
+          AUTH_PROPAGATION_ATTEMPTS
+      );
+      Assertions.fail("Expected submit to fail with 403 Forbidden");
+    }
+    catch (Exception e) {
+      MatcherAssert.assertThat(e, forbiddenExceptionMatcher());
+    }
+  }
+
+  private static ExceptionMatcher unauthorizedExceptionMatcher()
+  {
+    return ExceptionMatcher.of(Exception.class).expectMessageContains("401 
Unauthorized");
+  }
+
+  private static ExceptionMatcher forbiddenExceptionMatcher()
+  {
+    return ExceptionMatcher.of(Exception.class).expectMessageContains("403 
Forbidden");
+  }
 }


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

Reply via email to