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

gaborgsomogyi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git


The following commit(s) were added to refs/heads/master by this push:
     new 42e8aed66f0 [FLINK-40855][filesystems] Shut down STS clients after S3 
delegation-token acquisition
42e8aed66f0 is described below

commit 42e8aed66f067564f733f36adef43d2e6482746c
Author: Aleksandr Savonin <[email protected]>
AuthorDate: Fri Oct 2 11:53:53 2026 +0200

    [FLINK-40855][filesystems] Shut down STS clients after S3 delegation-token 
acquisition
---
 .../token/AbstractS3DelegationTokenProvider.java   |  47 ++++++---
 .../AbstractS3DelegationTokenProviderTest.java     | 117 +++++++++++++++++++++
 2 files changed, 148 insertions(+), 16 deletions(-)

diff --git 
a/flink-filesystems/flink-s3-fs-base/src/main/java/org/apache/flink/fs/s3/common/token/AbstractS3DelegationTokenProvider.java
 
b/flink-filesystems/flink-s3-fs-base/src/main/java/org/apache/flink/fs/s3/common/token/AbstractS3DelegationTokenProvider.java
index 82e99dcde89..a311a6aad45 100644
--- 
a/flink-filesystems/flink-s3-fs-base/src/main/java/org/apache/flink/fs/s3/common/token/AbstractS3DelegationTokenProvider.java
+++ 
b/flink-filesystems/flink-s3-fs-base/src/main/java/org/apache/flink/fs/s3/common/token/AbstractS3DelegationTokenProvider.java
@@ -19,6 +19,7 @@
 package org.apache.flink.fs.s3.common.token;
 
 import org.apache.flink.annotation.Internal;
+import org.apache.flink.annotation.VisibleForTesting;
 import org.apache.flink.configuration.Configuration;
 import org.apache.flink.configuration.GlobalConfiguration;
 import org.apache.flink.core.security.token.DelegationTokenProvider;
@@ -87,22 +88,36 @@ public abstract class AbstractS3DelegationTokenProvider 
implements DelegationTok
     public ObtainedDelegationTokens obtainDelegationTokens() throws Exception {
         LOG.info("Obtaining session credentials token with access key: {}", 
accessKey);
 
-        AWSSecurityTokenService stsClient =
-                AWSSecurityTokenServiceClientBuilder.standard()
-                        .withRegion(region)
-                        .withCredentials(
-                                new AWSStaticCredentialsProvider(
-                                        new BasicAWSCredentials(accessKey, 
secretKey)))
-                        .build();
-        GetSessionTokenResult sessionTokenResult = stsClient.getSessionToken();
-        Credentials credentials = sessionTokenResult.getCredentials();
-        LOG.info(
-                "Session credentials obtained successfully with access key: {} 
expiration: {}",
-                credentials.getAccessKeyId(),
-                credentials.getExpiration());
+        final AWSSecurityTokenService stsClient = createStsClient();
+        try {
+            final GetSessionTokenResult sessionTokenResult = 
stsClient.getSessionToken();
+            final Credentials credentials = 
sessionTokenResult.getCredentials();
+            LOG.info(
+                    "Session credentials obtained successfully with access 
key: {} expiration: {}",
+                    credentials.getAccessKeyId(),
+                    credentials.getExpiration());
 
-        return new ObtainedDelegationTokens(
-                InstantiationUtil.serializeObject(credentials),
-                Optional.of(credentials.getExpiration().getTime()));
+            return new ObtainedDelegationTokens(
+                    InstantiationUtil.serializeObject(credentials),
+                    Optional.of(credentials.getExpiration().getTime()));
+        } finally {
+            // Shutdown is best effort, so a failure must not discard the 
obtained tokens or
+            // mask an acquisition failure.
+            try {
+                stsClient.shutdown();
+            } catch (RuntimeException e) {
+                LOG.warn("Failed to shut down STS client for {}", 
serviceName(), e);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    AWSSecurityTokenService createStsClient() {
+        return AWSSecurityTokenServiceClientBuilder.standard()
+                .withRegion(region)
+                .withCredentials(
+                        new AWSStaticCredentialsProvider(
+                                new BasicAWSCredentials(accessKey, secretKey)))
+                .build();
     }
 }
diff --git 
a/flink-filesystems/flink-s3-fs-base/src/test/java/org/apache/flink/fs/s3/common/token/AbstractS3DelegationTokenProviderTest.java
 
b/flink-filesystems/flink-s3-fs-base/src/test/java/org/apache/flink/fs/s3/common/token/AbstractS3DelegationTokenProviderTest.java
index 43f3eb528f5..d4d2f697ef2 100644
--- 
a/flink-filesystems/flink-s3-fs-base/src/test/java/org/apache/flink/fs/s3/common/token/AbstractS3DelegationTokenProviderTest.java
+++ 
b/flink-filesystems/flink-s3-fs-base/src/test/java/org/apache/flink/fs/s3/common/token/AbstractS3DelegationTokenProviderTest.java
@@ -19,12 +19,21 @@
 package org.apache.flink.fs.s3.common.token;
 
 import org.apache.flink.configuration.Configuration;
+import 
org.apache.flink.core.security.token.DelegationTokenProvider.ObtainedDelegationTokens;
+import org.apache.flink.util.InstantiationUtil;
 
+import com.amazonaws.services.securitytoken.AWSSecurityTokenService;
+import com.amazonaws.services.securitytoken.AbstractAWSSecurityTokenService;
+import com.amazonaws.services.securitytoken.model.Credentials;
+import com.amazonaws.services.securitytoken.model.GetSessionTokenResult;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
+import java.util.Date;
+
 import static 
org.apache.flink.core.security.token.DelegationTokenProvider.CONFIG_PREFIX;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 /** Tests for {@link AbstractS3DelegationTokenProvider}. */
 class AbstractS3DelegationTokenProviderTest {
@@ -33,6 +42,8 @@ class AbstractS3DelegationTokenProviderTest {
     private static final String ACCESS_KEY_ID = "testAccessKeyId";
     private static final String SECRET_ACCESS_KEY = "testSecretAccessKey";
 
+    private final TestingStsClient stsClient = new TestingStsClient();
+
     private AbstractS3DelegationTokenProvider provider;
 
     @BeforeEach
@@ -43,6 +54,11 @@ class AbstractS3DelegationTokenProviderTest {
                     public String serviceName() {
                         return "s3";
                     }
+
+                    @Override
+                    AWSSecurityTokenService createStsClient() {
+                        return stsClient;
+                    }
                 };
     }
 
@@ -62,4 +78,105 @@ class AbstractS3DelegationTokenProviderTest {
 
         assertThat(provider.delegationTokensRequired()).isTrue();
     }
+
+    @Test
+    void obtainDelegationTokensShouldShutdownClientAfterSuccess() throws 
Exception {
+        final Credentials credentials = createCredentials();
+        stsClient.credentials = credentials;
+
+        final ObtainedDelegationTokens tokens = 
provider.obtainDelegationTokens();
+
+        final Credentials deserializedCredentials =
+                InstantiationUtil.deserializeObject(
+                        tokens.getTokens(), getClass().getClassLoader());
+        assertThat(deserializedCredentials).isEqualTo(credentials);
+        
assertThat(tokens.getValidUntil()).contains(credentials.getExpiration().getTime());
+        assertThat(stsClient.requestCount).isEqualTo(1);
+        assertThat(stsClient.shutdownCount).isEqualTo(1);
+    }
+
+    @Test
+    void obtainDelegationTokensShouldShutdownClientAfterRequestFailure() {
+        final RuntimeException requestFailure = new RuntimeException("STS 
request failed");
+        stsClient.requestFailure = requestFailure;
+
+        
assertThatThrownBy(provider::obtainDelegationTokens).isSameAs(requestFailure);
+
+        assertThat(stsClient.shutdownCount).isEqualTo(1);
+    }
+
+    @Test
+    void 
obtainDelegationTokensShouldShutdownClientAfterPostprocessingFailure() {
+        stsClient.credentials = createCredentials().withExpiration(null);
+
+        assertThatThrownBy(provider::obtainDelegationTokens)
+                .isInstanceOf(NullPointerException.class);
+
+        assertThat(stsClient.shutdownCount).isEqualTo(1);
+    }
+
+    @Test
+    void obtainDelegationTokensShouldPreserveRequestFailureWhenShutdownFails() 
{
+        final RuntimeException requestFailure = new RuntimeException("STS 
request failed");
+        final RuntimeException shutdownFailure = new RuntimeException("STS 
shutdown failed");
+        stsClient.requestFailure = requestFailure;
+        stsClient.shutdownFailure = shutdownFailure;
+
+        
assertThatThrownBy(provider::obtainDelegationTokens).isSameAs(requestFailure);
+        assertThat(stsClient.shutdownCount).isEqualTo(1);
+    }
+
+    @Test
+    void obtainDelegationTokensShouldReturnTokensWhenShutdownFails() throws 
Exception {
+        final Credentials credentials = createCredentials();
+        stsClient.credentials = credentials;
+        final RuntimeException shutdownFailure = new RuntimeException("STS 
shutdown failed");
+        stsClient.shutdownFailure = shutdownFailure;
+
+        final ObtainedDelegationTokens tokens = 
provider.obtainDelegationTokens();
+
+        final Credentials deserializedCredentials =
+                InstantiationUtil.deserializeObject(
+                        tokens.getTokens(), getClass().getClassLoader());
+        assertThat(deserializedCredentials).isEqualTo(credentials);
+        
assertThat(tokens.getValidUntil()).contains(credentials.getExpiration().getTime());
+        assertThat(stsClient.requestCount).isEqualTo(1);
+        assertThat(stsClient.shutdownCount).isEqualTo(1);
+    }
+
+    private static Credentials createCredentials() {
+        return new Credentials()
+                .withAccessKeyId(ACCESS_KEY_ID)
+                .withSecretAccessKey(SECRET_ACCESS_KEY)
+                .withSessionToken("testSessionToken")
+                .withExpiration(new Date(123456789L));
+    }
+
+    private static class TestingStsClient extends 
AbstractAWSSecurityTokenService {
+        private Credentials credentials = createCredentials();
+        private RuntimeException requestFailure;
+        private RuntimeException shutdownFailure;
+        private int requestCount;
+        private int shutdownCount;
+
+        @Override
+        public GetSessionTokenResult getSessionToken() {
+            if (shutdownCount > 0) {
+                throw new IllegalStateException("STS client already shut 
down");
+            }
+            requestCount++;
+            if (requestFailure != null) {
+                throw requestFailure;
+            }
+            return new GetSessionTokenResult().withCredentials(credentials);
+        }
+
+        @Override
+        public void shutdown() {
+            shutdownCount++;
+            if (shutdownFailure != null) {
+                throw shutdownFailure;
+            }
+        }
+    }
 }

Reply via email to