haydn-j-evans commented on code in PR #17389:
URL: https://github.com/apache/iceberg/pull/17389#discussion_r4116559330


##########
core/src/main/java/org/apache/iceberg/rest/auth/OAuth2Util.java:
##########
@@ -651,6 +655,121 @@ public static AuthSession fromAccessToken(
       return session;
     }
 
+    /**
+     * Creates a session whose token is sourced from a file, such as a 
Kubernetes-mounted projected
+     * service account token. The file is periodically re-read ahead of the 
current token's
+     * expiration (see {@code refreshBufferMillis}) so that a token rotated in 
place is picked up
+     * without restarting the process. No {@link RESTClient} is required since 
refreshing never
+     * calls out over the network; it only re-reads the file.
+     */
+    public static AuthSession fromTokenFile(
+        ScheduledExecutorService executor,
+        String tokenPath,
+        long refreshBufferMillis,
+        AuthSession parent) {
+      String token;
+      try {
+        token = readTokenFile(tokenPath);
+      } catch (IOException e) {
+        throw new UncheckedIOException("Failed to read token file: " + 
tokenPath, e);
+      }
+
+      Long expiresAtMillis = OAuth2Util.expiresAtMillis(token);
+      if (null == expiresAtMillis) {
+        expiresAtMillis = System.currentTimeMillis() + 
OAuth2Properties.TOKEN_EXPIRES_IN_MS_DEFAULT;
+      }
+
+      AuthSession session =
+          new AuthSession(
+              RESTUtil.merge(parent.headers(), authHeaders(token)),
+              AuthConfig.builder()
+                  .from(parent.config())
+                  .token(token)
+                  .tokenPath(tokenPath)
+                  .tokenType(OAuth2Properties.ACCESS_TOKEN_TYPE)
+                  .expiresAtMillis(expiresAtMillis)
+                  .tokenPathRefreshBufferMillis(refreshBufferMillis)
+                  .build());
+
+      if (null != executor) {
+        scheduleFileTokenRefresh(executor, session, expiresAtMillis, 
refreshBufferMillis);
+      }
+
+      return session;
+    }
+
+    private static String readTokenFile(String tokenPath) throws IOException {
+      return Files.readString(Path.of(tokenPath)).trim();
+    }
+
+    /**
+     * Re-reads the token from {@link AuthConfig#tokenPath()} and updates this 
session's headers and
+     * config accordingly.
+     *
+     * @return the new token's expiration time in epoch millis, or null if the 
file could not be
+     *     read
+     */
+    Long refreshFromFile() {
+      String tokenPath = config.tokenPath();
+      String token;
+      try {
+        token = readTokenFile(tokenPath);
+      } catch (IOException e) {
+        LOG.warn("Failed to re-read token file {}, will retry", tokenPath, e);
+        return null;
+      }
+
+      Long expiresAtMillis = OAuth2Util.expiresAtMillis(token);
+      if (null == expiresAtMillis) {
+        expiresAtMillis = System.currentTimeMillis() + 
OAuth2Properties.TOKEN_EXPIRES_IN_MS_DEFAULT;
+      }
+
+      this.config =
+          AuthConfig.builder()
+              .from(config())
+              .token(token)
+              .tokenType(OAuth2Properties.ACCESS_TOKEN_TYPE)
+              .expiresAtMillis(expiresAtMillis)
+              .build();
+      this.headers = RESTUtil.merge(this.headers, authHeaders(token));
+
+      return expiresAtMillis;
+    }
+
+    /**
+     * Schedule the next file-based token refresh, {@code refreshBufferMillis} 
ahead of {@code
+     * expiresAtMillis}. Unlike {@link #scheduleTokenRefresh}, this never 
calls out over the
+     * network: on a transient read failure, it retries after a short fixed 
delay instead of giving
+     * up.
+     */
+    @SuppressWarnings("FutureReturnValueIgnored")
+    private static void scheduleFileTokenRefresh(

Review Comment:
    scheduleFileTokenRefresh now takes a delay directly instead of an expiry, 
so a failed read just reschedules after FILE_REFRESH_RETRY_WAIT_MILLIS.
    
    also added a case where the read suceeds but for some reason the kubelet 
hasnt rotated the file yet (i.e. node cpu saturation,) - So every reschedule 
now goes through fileRefreshDelayMillis, which floors the delay at 
FILE_REFRESH_RETRY_WAIT_MILLIS. Worst case is now one cheap local file read 
every 5 s until the kubelet rotates the token.
    
    



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to