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

jerryshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new 197e1adba9 [#13369] fix(catalog-glue): Report rejected AWS credentials 
clearly (#13370)
197e1adba9 is described below

commit 197e1adba9b073c7df890f8526e2102f232ac005
Author: Yuhui <[email protected]>
AuthorDate: Mon Sep 21 17:42:09 2026 +0800

    [#13369] fix(catalog-glue): Report rejected AWS credentials clearly (#13370)
    
    ### What changes were proposed in this pull request?
    
    - Recognize AWS Glue authentication error codes separately from
    authorization and general connection failures.
    - Convert credential-provider resolution failures and AWS-rejected
    credentials into actionable connection errors that identify the relevant
    Gravitino catalog properties.
    - Apply the same actionable authentication diagnostics to Glue schema
    and table operations.
    - Keep catalog creation offline-capable; static credentials are not
    authenticated during catalog creation.
    
    ### Why are the changes needed?
    
    Static AWS credentials can resolve locally even when AWS will reject
    them. Glue connection checks and metadata operations currently expose
    raw or generic AWS SDK errors, which do not tell users which catalog
    properties need correction.
    
    Fix: #13369
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. Glue connection tests and metadata operations now report clearer
    authentication failures and identify `aws-access-key-id` and
    `aws-secret-access-key`. Catalog creation behavior is unchanged.
    
    ### How was this patch tested?
    
    Added unit tests for:
    
    - Missing credentials in the default provider chain.
    - AWS-rejected static credentials during connection tests.
    - AWS-rejected credentials during schema operations.
    - Authentication error-code recognition and conversion.
    - Credential-provider resolution failures.
    
    Ran:
    
    `./gradlew :catalogs:catalog-glue:spotlessApply
    :catalogs:catalog-glue:test -PskipITs`
---
 .../catalog/glue/GlueCatalogOperations.java        |  3 +-
 .../gravitino/catalog/glue/GlueClientProvider.java | 30 +++-----
 .../catalog/glue/GlueExceptionConverter.java       | 87 ++++++++++++++++++++++
 .../glue/TestGlueCatalogSchemaOperations.java      | 60 +++++++++++++++
 .../catalog/glue/TestGlueClientProvider.java       | 13 +++-
 .../catalog/glue/TestGlueExceptionConverter.java   | 41 ++++++++++
 6 files changed, 208 insertions(+), 26 deletions(-)

diff --git 
a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueCatalogOperations.java
 
b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueCatalogOperations.java
index 8b36af4129..898e6964a2 100644
--- 
a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueCatalogOperations.java
+++ 
b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueCatalogOperations.java
@@ -46,7 +46,6 @@ import org.apache.gravitino.connector.CatalogInfo;
 import org.apache.gravitino.connector.CatalogOperations;
 import org.apache.gravitino.connector.HasPropertyMetadata;
 import org.apache.gravitino.connector.SupportsSchemas;
-import org.apache.gravitino.exceptions.ConnectionFailedException;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
@@ -166,7 +165,7 @@ public class GlueCatalogOperations implements 
CatalogOperations, SupportsSchemas
       applyCatalogId(catalogId, req::catalogId);
       glueClient.getDatabases(req.build());
     } catch (SdkException e) {
-      throw new ConnectionFailedException(e, "Failed to connect to AWS Glue: 
%s", e.getMessage());
+      throw GlueExceptionConverter.toConnectionException(e);
     }
   }
 
diff --git 
a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueClientProvider.java
 
b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueClientProvider.java
index 0cc5bb9e90..be0dbe1925 100644
--- 
a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueClientProvider.java
+++ 
b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueClientProvider.java
@@ -23,6 +23,7 @@ import com.google.common.base.Preconditions;
 import java.net.URI;
 import java.util.Map;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.exceptions.ConnectionFailedException;
 import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
 import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
 import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
@@ -55,8 +56,9 @@ public final class GlueClientProvider {
    * @param config Catalog configuration properties.
    * @return A configured and ready-to-use {@link GlueClient}.
    * @throws IllegalArgumentException if {@code aws-region} is missing or 
blank, if only one of the
-   *     credential keys is provided, if {@code aws-glue-endpoint} is not a 
valid URI, or if no
-   *     usable AWS credential source can be resolved.
+   *     credential keys is provided, or if {@code aws-glue-endpoint} is not a 
valid URI
+   * @throws ConnectionFailedException if the configured credential provider 
cannot resolve
+   *     credentials
    */
   public static GlueClient buildClient(Map<String, String> config) {
     String region = config.get(GlueConstants.AWS_REGION);
@@ -97,31 +99,19 @@ public final class GlueClientProvider {
   }
 
   /**
-   * Eagerly resolves {@code credentialsProvider} to confirm a usable 
credential source exists,
-   * instead of leaving resolution to the first real Glue API call. Without 
this check, a catalog
-   * created with no static credentials and no usable default-chain source 
(env vars, instance
-   * profile, etc.) is stored successfully and then fails on every operation 
with a raw AWS SDK
-   * error that never mentions this connector's own credential properties.
+   * Eagerly resolves {@code credentialsProvider} when Glue operations are 
initialized, instead of
+   * leaving resolution to the first real Glue API call. This makes an 
explicit connection test or
+   * the first operation fail with an actionable connection error when no 
credential source is
+   * available. It does not authenticate static credentials; only an AWS API 
request can do that.
    *
-   * @throws IllegalArgumentException if no credentials can be resolved
+   * @throws ConnectionFailedException if no credentials can be resolved
    */
   @VisibleForTesting
   static void validateCredentials(AwsCredentialsProvider credentialsProvider) {
     try {
       credentialsProvider.resolveCredentials();
     } catch (SdkClientException e) {
-      if (!GlueExceptionConverter.isCredentialFailure(e)) {
-        throw new IllegalArgumentException(
-            "Failed to resolve AWS credentials for the Glue catalog: " + 
e.getMessage(), e);
-      }
-      throw new IllegalArgumentException(
-          String.format(
-              "No usable AWS credentials found for the Glue catalog. Set both 
'%s' and '%s' "
-                  + "catalog properties for static authentication, or ensure 
the default AWS "
-                  + "credential chain (environment variables, instance 
profile, web identity "
-                  + "token, etc.) can resolve credentials.",
-              GlueConstants.AWS_ACCESS_KEY_ID, 
GlueConstants.AWS_SECRET_ACCESS_KEY),
-          e);
+      throw GlueExceptionConverter.toConnectionException(e);
     }
   }
 
diff --git 
a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueExceptionConverter.java
 
b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueExceptionConverter.java
index 4ab3ed9c9b..5725c75fb1 100644
--- 
a/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueExceptionConverter.java
+++ 
b/catalogs/catalog-glue/src/main/java/org/apache/gravitino/catalog/glue/GlueExceptionConverter.java
@@ -18,7 +18,9 @@
  */
 package org.apache.gravitino.catalog.glue;
 
+import java.util.Set;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.exceptions.ConnectionFailedException;
 import org.apache.gravitino.exceptions.ForbiddenException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
@@ -27,6 +29,7 @@ import 
org.apache.gravitino.exceptions.TableAlreadyExistsException;
 import org.apache.gravitino.utils.ExceptionMessages;
 import software.amazon.awssdk.awscore.exception.AwsErrorDetails;
 import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.core.exception.SdkException;
 import software.amazon.awssdk.services.glue.model.AccessDeniedException;
 import software.amazon.awssdk.services.glue.model.AlreadyExistsException;
 import software.amazon.awssdk.services.glue.model.EntityNotFoundException;
@@ -39,6 +42,20 @@ final class GlueExceptionConverter {
   private static final String NO_CREDENTIALS_MARKER =
       "Unable to load credentials from any of the providers";
 
+  private static final Set<String> AUTHENTICATION_ERROR_CODES =
+      Set.of(
+          "AuthFailure",
+          "ExpiredToken",
+          "ExpiredTokenException",
+          "IncompleteSignature",
+          "InvalidAccessKeyId",
+          "InvalidClientTokenId",
+          "InvalidSignatureException",
+          "RequestExpired",
+          "SignatureDoesNotMatch",
+          "TokenRefreshRequired",
+          "UnrecognizedClientException");
+
   private GlueExceptionConverter() {}
 
   /**
@@ -72,6 +89,57 @@ final class GlueExceptionConverter {
         e);
   }
 
+  /**
+   * Whether AWS Glue rejected credentials that were successfully resolved by 
the configured
+   * provider. Static credential providers can return any nonblank access-key 
pair locally, so only
+   * an AWS service response can establish whether that pair is authentic.
+   *
+   * @param e the service exception raised by AWS Glue
+   * @return true if AWS classified the failure as an authentication error
+   */
+  static boolean isAuthenticationFailure(GlueException e) {
+    AwsErrorDetails details = e.awsErrorDetails();
+    return details != null
+        && StringUtils.isNotBlank(details.errorCode())
+        && AUTHENTICATION_ERROR_CODES.contains(details.errorCode());
+  }
+
+  /**
+   * Converts an AWS Glue SDK failure raised by a connection probe into a 
connection error. Known
+   * credential failures name the connector properties that an operator can 
correct; authorization
+   * and transport failures retain the AWS or SDK detail without claiming the 
credentials are
+   * invalid.
+   *
+   * @param e the SDK failure raised by the connection probe
+   * @return an actionable connection failure
+   */
+  static ConnectionFailedException toConnectionException(SdkException e) {
+    if (e instanceof SdkClientException && 
isCredentialFailure((SdkClientException) e)) {
+      return new ConnectionFailedException(
+          e,
+          "Failed to authenticate with AWS Glue. No usable AWS credentials 
were found. Set both "
+              + "'%s' and '%s' catalog properties, or ensure the default AWS 
credential chain can "
+              + "resolve credentials.",
+          GlueConstants.AWS_ACCESS_KEY_ID,
+          GlueConstants.AWS_SECRET_ACCESS_KEY);
+    }
+    if (e instanceof GlueException && isAuthenticationFailure((GlueException) 
e)) {
+      return new ConnectionFailedException(
+          e,
+          "AWS Glue rejected the configured credentials. Verify the '%s' and 
'%s' catalog "
+              + "properties, or the configured default AWS credential source. 
AWS error: %s",
+          GlueConstants.AWS_ACCESS_KEY_ID,
+          GlueConstants.AWS_SECRET_ACCESS_KEY,
+          awsErrorDetail((GlueException) e));
+    }
+
+    String detail =
+        e instanceof GlueException
+            ? awsErrorDetail((GlueException) e)
+            : StringUtils.defaultIfBlank(e.getMessage(), 
e.getClass().getSimpleName());
+    return new ConnectionFailedException(e, "Failed to connect to AWS Glue: 
%s", detail);
+  }
+
   /**
    * Converts a {@link GlueException} to the appropriate Gravitino schema 
exception.
    *
@@ -80,6 +148,9 @@ final class GlueExceptionConverter {
    * @return a Gravitino or standard Java runtime exception
    */
   static RuntimeException toSchemaException(GlueException e, String context) {
+    if (isAuthenticationFailure(e)) {
+      return toAuthenticationException(e, context);
+    }
     if (e instanceof EntityNotFoundException) {
       return new NoSuchSchemaException(e, "%s does not exist", context);
     }
@@ -103,6 +174,9 @@ final class GlueExceptionConverter {
    * @return a Gravitino or standard Java runtime exception
    */
   static RuntimeException toTableException(GlueException e, String context) {
+    if (isAuthenticationFailure(e)) {
+      return toAuthenticationException(e, context);
+    }
     if (e instanceof EntityNotFoundException) {
       return new NoSuchTableException(e, "%s does not exist", context);
     }
@@ -118,6 +192,19 @@ final class GlueExceptionConverter {
     return new RuntimeException("Glue error: " + context + ": " + 
awsErrorDetail(e), e);
   }
 
+  private static RuntimeException toAuthenticationException(GlueException e, 
String context) {
+    return new RuntimeException(
+        String.format(
+            "Failed to authenticate with AWS Glue for %s. AWS rejected the 
configured "
+                + "credentials. Verify the '%s' and '%s' catalog properties, 
or the configured "
+                + "default AWS credential source. AWS error: %s",
+            context,
+            GlueConstants.AWS_ACCESS_KEY_ID,
+            GlueConstants.AWS_SECRET_ACCESS_KEY,
+            awsErrorDetail(e)),
+        e);
+  }
+
   /**
    * Renders the AWS-side detail of a Glue exception. AWS names the failing 
action and the resource
    * there, which is what the caller needs to act on; the error code is 
prefixed so the failure can
diff --git 
a/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueCatalogSchemaOperations.java
 
b/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueCatalogSchemaOperations.java
index 7c03aaeddb..6bebc1baac 100644
--- 
a/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueCatalogSchemaOperations.java
+++ 
b/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueCatalogSchemaOperations.java
@@ -42,6 +42,7 @@ import 
org.apache.gravitino.exceptions.SchemaAlreadyExistsException;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
+import software.amazon.awssdk.awscore.exception.AwsErrorDetails;
 import software.amazon.awssdk.core.exception.SdkClientException;
 import software.amazon.awssdk.services.glue.GlueClient;
 import software.amazon.awssdk.services.glue.model.AlreadyExistsException;
@@ -55,6 +56,7 @@ import 
software.amazon.awssdk.services.glue.model.GetDatabasesRequest;
 import software.amazon.awssdk.services.glue.model.GetDatabasesResponse;
 import software.amazon.awssdk.services.glue.model.GetTablesRequest;
 import software.amazon.awssdk.services.glue.model.GetTablesResponse;
+import software.amazon.awssdk.services.glue.model.GlueException;
 import software.amazon.awssdk.services.glue.model.Table;
 import software.amazon.awssdk.services.glue.model.UpdateDatabaseRequest;
 import software.amazon.awssdk.services.glue.model.UpdateDatabaseResponse;
@@ -86,6 +88,38 @@ class TestGlueCatalogSchemaOperations {
     assertTrue(exception.getMessage().contains("connection refused"));
   }
 
+  @Test
+  void testConnectionMapsMissingCredentialsToActionableMessage() {
+    SdkClientException cause =
+        SdkClientException.create("Unable to load credentials from any of the 
providers");
+    
when(mockClient.getDatabases(any(GetDatabasesRequest.class))).thenThrow(cause);
+
+    ConnectionFailedException exception =
+        assertThrows(
+            ConnectionFailedException.class,
+            () -> ops.testConnection(NameIdentifier.of("metalake", 
"catalog")));
+
+    assertEquals(cause, exception.getCause());
+    
assertTrue(exception.getMessage().contains(GlueConstants.AWS_ACCESS_KEY_ID));
+    
assertTrue(exception.getMessage().contains(GlueConstants.AWS_SECRET_ACCESS_KEY));
+  }
+
+  @Test
+  void testConnectionMapsRejectedCredentialsToActionableMessage() {
+    GlueException cause = invalidCredentialsException();
+    
when(mockClient.getDatabases(any(GetDatabasesRequest.class))).thenThrow(cause);
+
+    ConnectionFailedException exception =
+        assertThrows(
+            ConnectionFailedException.class,
+            () -> ops.testConnection(NameIdentifier.of("metalake", 
"catalog")));
+
+    assertEquals(cause, exception.getCause());
+    
assertTrue(exception.getMessage().contains(GlueConstants.AWS_ACCESS_KEY_ID));
+    
assertTrue(exception.getMessage().contains(GlueConstants.AWS_SECRET_ACCESS_KEY));
+    assertTrue(exception.getMessage().contains("UnrecognizedClientException"));
+  }
+
   // -------------------------------------------------------------------------
   // listSchemas
   // -------------------------------------------------------------------------
@@ -139,6 +173,20 @@ class TestGlueCatalogSchemaOperations {
     assertTrue(ex.getMessage().contains("aws-secret-access-key"));
   }
 
+  @Test
+  void testListSchemasMapsRejectedCredentialsToActionableMessage() {
+    Namespace ns = Namespace.of("metalake", "catalog");
+    GlueException cause = invalidCredentialsException();
+    
when(mockClient.getDatabases(any(GetDatabasesRequest.class))).thenThrow(cause);
+
+    RuntimeException ex = assertThrows(RuntimeException.class, () -> 
ops.listSchemas(ns));
+
+    assertEquals(cause, ex.getCause());
+    assertTrue(ex.getMessage().contains(GlueConstants.AWS_ACCESS_KEY_ID));
+    assertTrue(ex.getMessage().contains(GlueConstants.AWS_SECRET_ACCESS_KEY));
+    assertTrue(ex.getMessage().contains("UnrecognizedClientException"));
+  }
+
   @Test
   void testListSchemasRethrowsNonCredentialSdkClientException() {
     Namespace ns = Namespace.of("metalake", "catalog");
@@ -352,4 +400,16 @@ class TestGlueCatalogSchemaOperations {
     verify(mockClient).deleteDatabase(captor.capture());
     assertEquals("123456789012", captor.getValue().catalogId());
   }
+
+  private static GlueException invalidCredentialsException() {
+    return (GlueException)
+        GlueException.builder()
+            .message("The security token included in the request is invalid")
+            .awsErrorDetails(
+                AwsErrorDetails.builder()
+                    .errorCode("UnrecognizedClientException")
+                    .errorMessage("The security token included in the request 
is invalid")
+                    .build())
+            .build();
+  }
 }
diff --git 
a/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueClientProvider.java
 
b/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueClientProvider.java
index e3b1a2cb1c..665e76d72b 100644
--- 
a/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueClientProvider.java
+++ 
b/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueClientProvider.java
@@ -32,6 +32,7 @@ import static org.mockito.Mockito.mock;
 
 import java.util.HashMap;
 import java.util.Map;
+import org.apache.gravitino.exceptions.ConnectionFailedException;
 import org.junit.jupiter.api.Test;
 import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
 import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
@@ -74,9 +75,10 @@ class TestGlueClientProvider {
         .when(provider)
         .resolveCredentials();
 
-    IllegalArgumentException ex =
+    ConnectionFailedException ex =
         assertThrows(
-            IllegalArgumentException.class, () -> 
GlueClientProvider.validateCredentials(provider));
+            ConnectionFailedException.class,
+            () -> GlueClientProvider.validateCredentials(provider));
     assertTrue(ex.getMessage().contains(AWS_ACCESS_KEY_ID));
     assertTrue(ex.getMessage().contains(AWS_SECRET_ACCESS_KEY));
   }
@@ -89,9 +91,10 @@ class TestGlueClientProvider {
     SdkClientException cause = SdkClientException.create("connection refused");
     doThrow(cause).when(provider).resolveCredentials();
 
-    IllegalArgumentException ex =
+    ConnectionFailedException ex =
         assertThrows(
-            IllegalArgumentException.class, () -> 
GlueClientProvider.validateCredentials(provider));
+            ConnectionFailedException.class,
+            () -> GlueClientProvider.validateCredentials(provider));
     assertEquals(cause, ex.getCause());
     assertTrue(ex.getMessage().contains("connection refused"));
     assertFalse(ex.getMessage().contains("No usable AWS credentials"));
@@ -155,6 +158,8 @@ class TestGlueClientProvider {
   void testBuildClientInvalidEndpointThrows() {
     Map<String, String> config = new HashMap<>();
     config.put(AWS_REGION, "us-east-1");
+    config.put(AWS_ACCESS_KEY_ID, "test");
+    config.put(AWS_SECRET_ACCESS_KEY, "test");
     config.put(AWS_GLUE_ENDPOINT, "not a valid uri ://");
 
     assertThrows(IllegalArgumentException.class, () -> 
GlueClientProvider.buildClient(config));
diff --git 
a/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueExceptionConverter.java
 
b/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueExceptionConverter.java
index 23a32114a7..bb6ff91378 100644
--- 
a/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueExceptionConverter.java
+++ 
b/catalogs/catalog-glue/src/test/java/org/apache/gravitino/catalog/glue/TestGlueExceptionConverter.java
@@ -71,6 +71,23 @@ public class TestGlueExceptionConverter {
     assertFalse(GlueExceptionConverter.isCredentialFailure(e));
   }
 
+  @Test
+  public void testIsAuthenticationFailureMatchesAwsErrorCode() {
+    GlueException e = invalidCredentialsException();
+
+    assertTrue(GlueExceptionConverter.isAuthenticationFailure(e));
+  }
+
+  @Test
+  public void testIsAuthenticationFailureRejectsAuthorizationError() {
+    AccessDeniedException e =
+        AccessDeniedException.builder()
+            
.awsErrorDetails(AwsErrorDetails.builder().errorCode("AccessDeniedException").build())
+            .build();
+
+    assertFalse(GlueExceptionConverter.isAuthenticationFailure(e));
+  }
+
   @Test
   public void testToCredentialExceptionIncludesContextAndPropertyNames() {
     SdkClientException cause =
@@ -84,6 +101,18 @@ public class TestGlueExceptionConverter {
     assertTrue(ex.getMessage().contains(GlueConstants.AWS_SECRET_ACCESS_KEY));
   }
 
+  @Test
+  public void testRejectedCredentialsIncludePropertyNamesAndAwsError() {
+    GlueException cause = invalidCredentialsException();
+
+    RuntimeException converted = 
GlueExceptionConverter.toSchemaException(cause, "listing schemas");
+
+    assertSame(cause, converted.getCause());
+    
assertTrue(converted.getMessage().contains(GlueConstants.AWS_ACCESS_KEY_ID));
+    
assertTrue(converted.getMessage().contains(GlueConstants.AWS_SECRET_ACCESS_KEY));
+    assertTrue(converted.getMessage().contains("UnrecognizedClientException"));
+  }
+
   @Test
   public void testSchemaAccessDeniedKeepsAwsMessage() {
     AccessDeniedException e =
@@ -218,4 +247,16 @@ public class TestGlueExceptionConverter {
         IllegalArgumentException.class,
         GlueExceptionConverter.toTableException(invalid, "table ctas_test"));
   }
+
+  private static GlueException invalidCredentialsException() {
+    return (GlueException)
+        GlueException.builder()
+            .message("The security token included in the request is invalid")
+            .awsErrorDetails(
+                AwsErrorDetails.builder()
+                    .errorCode("UnrecognizedClientException")
+                    .errorMessage("The security token included in the request 
is invalid")
+                    .build())
+            .build();
+  }
 }

Reply via email to