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

cloud-fan pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.x by this push:
     new c77095080627 [SPARK-38954][CORE][FOLLOWUP] Isolate credential provider 
requirement failures
c77095080627 is described below

commit c770950806277c2bf893aee5d4536e05cf7772bd
Author: Wenchen Fan <[email protected]>
AuthorDate: Mon Aug 10 13:22:17 2026 +0800

    [SPARK-38954][CORE][FOLLOWUP] Isolate credential provider requirement 
failures
    
    ### What changes were proposed in this pull request?
    
    Followup to https://github.com/apache/spark/pull/57285.
    
    Catch exceptions from `delegationTokensRequired` so one faulty provider 
does not prevent other
    direct credential providers from renewing and distributing credentials. Add 
regression coverage
    for renewal eligibility and token acquisition.
    
    If every active direct provider fails its eligibility check during initial 
setup, renewal remains
    disabled and no retry is scheduled; the failures are logged. Once renewal 
has started, provider
    failures during a renewal cycle contribute to the existing failure count 
and retry behavior.
    
    ### Why are the changes needed?
    
    A provider throwing from `delegationTokensRequired` currently aborts 
processing for every
    provider. Failures should be isolated so healthy providers can continue 
supplying credentials.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    Ran `NonKerberosCredentialsSuite`.
    
    Without the production fix, 11 of 14 tests failed because the simulated 
provider exception
    escaped. With the fix, all 14 tests passed.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Codex GPT-5
    
    Closes #57828 from cloud-fan/SPARK-38954-followup.
    
    Authored-by: Wenchen Fan <[email protected]>
    Signed-off-by: Wenchen Fan <[email protected]>
    (cherry picked from commit 2a62c37e7e0f1e6f3c1c264ef9e6c52ce73e4134)
    Signed-off-by: Wenchen Fan <[email protected]>
---
 .../security/HadoopDelegationTokenManager.scala    | 40 +++++++++++---------
 .../spark/scheduler/SupportsDelegationToken.scala  |  5 ++-
 ...he.spark.security.HadoopDelegationTokenProvider |  1 +
 .../security/NonKerberosCredentialsSuite.scala     | 44 ++++++++++++++++++++++
 4 files changed, 71 insertions(+), 19 deletions(-)

diff --git 
a/core/src/main/scala/org/apache/spark/deploy/security/HadoopDelegationTokenManager.scala
 
b/core/src/main/scala/org/apache/spark/deploy/security/HadoopDelegationTokenManager.scala
index ca202bd9bbfb..b98a71b13cba 100644
--- 
a/core/src/main/scala/org/apache/spark/deploy/security/HadoopDelegationTokenManager.scala
+++ 
b/core/src/main/scala/org/apache/spark/deploy/security/HadoopDelegationTokenManager.scala
@@ -24,6 +24,7 @@ import java.util.ServiceLoader
 import java.util.concurrent.{ScheduledExecutorService, TimeUnit}
 
 import scala.collection.mutable
+import scala.util.control.NonFatal
 
 import org.apache.hadoop.conf.Configuration
 import org.apache.hadoop.fs.FileSystem
@@ -111,7 +112,16 @@ private[spark] class HadoopDelegationTokenManager(
   def renewalEnabled: Boolean = {
     hasKerberosCredentials ||
       (sparkConf.get(DIRECT_CREDENTIAL_PROVIDERS_ENABLED) &&
-        
delegationTokenProviders.values.exists(_.delegationTokensRequired(sparkConf, 
hadoopConf)))
+        delegationTokenProviders.values.exists { provider =>
+          try {
+            provider.delegationTokensRequired(sparkConf, hadoopConf)
+          } catch {
+            case NonFatal(e) =>
+              logWarning(log"Failed to determine whether credentials are 
required from " +
+                log"${MDC(LogKeys.SERVICE_NAME, provider.serviceName)}.", e)
+              false
+          }
+        })
   }
 
   /**
@@ -198,24 +208,20 @@ private[spark] class HadoopDelegationTokenManager(
     val creds = new Credentials()
     var failureCount = 0
     val nextRenewal = delegationTokenProviders.values.flatMap { provider =>
-      if (provider.delegationTokensRequired(sparkConf, hadoopConf)) {
-        if (isolateFailures) {
-          try {
-            provider.obtainDelegationTokens(hadoopConf, sparkConf, creds)
-          } catch {
-            case e: Exception =>
-              logWarning(log"Failed to obtain credentials from " +
-                log"${MDC(LogKeys.SERVICE_NAME, provider.serviceName)}.", e)
-              failureCount += 1
-              None
-          }
-        } else {
+      try {
+        if (provider.delegationTokensRequired(sparkConf, hadoopConf)) {
           provider.obtainDelegationTokens(hadoopConf, sparkConf, creds)
+        } else {
+          logDebug(s"Service ${provider.serviceName} does not require a 
token." +
+            s" Check your configuration to see if security is disabled or 
not.")
+          None
         }
-      } else {
-        logDebug(s"Service ${provider.serviceName} does not require a token." +
-          s" Check your configuration to see if security is disabled or not.")
-        None
+      } catch {
+        case NonFatal(e) if isolateFailures =>
+          logWarning(log"Failed to obtain credentials from " +
+            log"${MDC(LogKeys.SERVICE_NAME, provider.serviceName)}.", e)
+          failureCount += 1
+          None
       }
     }.foldLeft(Long.MaxValue)(math.min)
     (creds, nextRenewal, failureCount)
diff --git 
a/core/src/main/scala/org/apache/spark/scheduler/SupportsDelegationToken.scala 
b/core/src/main/scala/org/apache/spark/scheduler/SupportsDelegationToken.scala
index 3fdbe4c25017..6edcc7806e01 100644
--- 
a/core/src/main/scala/org/apache/spark/scheduler/SupportsDelegationToken.scala
+++ 
b/core/src/main/scala/org/apache/spark/scheduler/SupportsDelegationToken.scala
@@ -43,8 +43,9 @@ private[spark] trait SupportsDelegationToken {
   protected def updateDelegationTokens(tokens: Array[Byte]): Unit
 
   /**
-   * Whether the token manager should be started. Returns true if Hadoop 
security is enabled
-   * or if direct credential providers are configured.
+   * Whether the token manager should be started. The default implementation 
returns true when
+   * Hadoop security is enabled. Backends that support direct credential 
providers override this
+   * method to also check whether those providers are configured.
    */
   protected def tokenManagerRequired(): Boolean = 
UserGroupInformation.isSecurityEnabled
 
diff --git 
a/core/src/test/resources/META-INF/services/org.apache.spark.security.HadoopDelegationTokenProvider
 
b/core/src/test/resources/META-INF/services/org.apache.spark.security.HadoopDelegationTokenProvider
index 8621d66256e0..add25b0c26fe 100644
--- 
a/core/src/test/resources/META-INF/services/org.apache.spark.security.HadoopDelegationTokenProvider
+++ 
b/core/src/test/resources/META-INF/services/org.apache.spark.security.HadoopDelegationTokenProvider
@@ -19,4 +19,5 @@ 
org.apache.spark.deploy.security.ExceptionThrowingDelegationTokenProvider
 org.apache.spark.deploy.security.TestNonKerberosTokenProvider
 org.apache.spark.deploy.security.TestDisabledProvider
 org.apache.spark.deploy.security.TestFailingProvider
+org.apache.spark.deploy.security.TestRequirementFailingProvider
 org.apache.spark.deploy.security.TestNoExpiryProvider
diff --git 
a/core/src/test/scala/org/apache/spark/deploy/security/NonKerberosCredentialsSuite.scala
 
b/core/src/test/scala/org/apache/spark/deploy/security/NonKerberosCredentialsSuite.scala
index d6ecc954cfe0..f9f77b4d2798 100644
--- 
a/core/src/test/scala/org/apache/spark/deploy/security/NonKerberosCredentialsSuite.scala
+++ 
b/core/src/test/scala/org/apache/spark/deploy/security/NonKerberosCredentialsSuite.scala
@@ -79,6 +79,24 @@ private class TestFailingProvider extends 
HadoopDelegationTokenProvider {
   }
 }
 
+private class TestRequirementFailingProvider extends 
HadoopDelegationTokenProvider {
+  override def serviceName: String = "test-requirement-failing"
+
+  override def delegationTokensRequired(
+      sparkConf: SparkConf, hadoopConf: Configuration): Boolean = {
+    if (sparkConf.getBoolean(
+        "spark.test.requirement-failing.throw", false)) {
+      throw new RuntimeException("Simulated provider requirement failure")
+    }
+    false
+  }
+
+  override def obtainDelegationTokens(
+      hadoopConf: Configuration,
+      sparkConf: SparkConf,
+      creds: Credentials): Option[Long] = None
+}
+
 // Adds a credential but reports no expiry (returns None), mimicking providers 
such as
 // HBaseDelegationTokenProvider. This exercises the nextRenewal == 
Long.MaxValue case where
 // credentials were nevertheless obtained.
@@ -149,6 +167,32 @@ class NonKerberosCredentialsSuite extends SparkFunSuite {
     assert(new String(creds.getSecretKey(new Text("test.direct.credential"))) 
=== "test-token")
   }
 
+  test("provider requirement failure does not prevent other providers from 
running") {
+    val sparkConf = baseConf
+      .set("spark.security.credentials.test-requirement-failing.enabled", 
"true")
+      .set("spark.test.requirement-failing.throw", "true")
+    val manager = new HadoopDelegationTokenManager(sparkConf, hadoopConf, null)
+
+    assert(manager.renewalEnabled)
+
+    val creds = new Credentials()
+    manager.obtainDelegationTokens(creds)
+
+    assert(creds.getSecretKey(new Text("test.direct.credential")) != null)
+  }
+
+  test("provider requirement failure does not abort renewalEnabled") {
+    val sparkConf = baseConf
+      .set("spark.security.credentials.test-direct.enabled", "false")
+      .set("spark.security.credentials.test-failing.enabled", "false")
+      .set("spark.security.credentials.test-noexpiry.enabled", "false")
+      .set("spark.security.credentials.test-requirement-failing.enabled", 
"true")
+      .set("spark.test.requirement-failing.throw", "true")
+    val manager = new HadoopDelegationTokenManager(sparkConf, hadoopConf, null)
+
+    assert(!manager.renewalEnabled)
+  }
+
   test("individual provider can be disabled via per-service config") {
     val sparkConf = baseConf
       .set("spark.security.credentials.test-direct.enabled", "false")


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

Reply via email to