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

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


The following commit(s) were added to refs/heads/main by this push:
     new 26a394e0e Fix FileIO usage in PolarisRestCatalogIntegrationBase (#3658)
26a394e0e is described below

commit 26a394e0ea68c68373a75251c97ab0711e637d20
Author: Alexandre Dutra <[email protected]>
AuthorDate: Fri Feb 6 13:35:54 2026 +0100

    Fix FileIO usage in PolarisRestCatalogIntegrationBase (#3658)
    
    Currently, all tests inheriting from `PolarisRestCatalogIntegrationBase` 
are forcibly using `InMemoryFileIO` for all `RESTCatalog` instances.
    
    Furthermore, this class wasn't overriding 
`CatalogTests.baseTableLocation()`, so tests calling this method were actually 
operating on `file://` paths all the time.
    
    Because of that, some child test classes, and in particular, the 
MiniIO-based ones, weren't actually testing anything useful.
    
    This PR changes `PolarisRestCatalogIntegrationBase` to use a configurable 
`FileIO` impl, defaulting to `ResolvingFileIO`.
    
    This change uncovered a few latent issues with current tests, and notably:
    
    1) Tests using MinIO should request credential vending consistently, 
otherwise most tests would fail because the S3 client attempts to load 
credentials from the environment.
    
    2) Tests calling `registerTable` need special handling since this endpoint 
does NOT support access delegation at all. For these tests, the `RESTCatalog` 
clients need to be given direct credentials for S3 – since the server won't 
give them anything – even with vended credentials requested.
    
    Incidentally, this PR simplifies 
`IntegrationTestsHelper.mergeFromAnnotatedElements()` – but its functionality 
stays the same.
---
 .../service/it/env/IntegrationTestsHelper.java     |  6 +-
 .../test/PolarisPolicyServiceIntegrationTest.java  |  6 +-
 .../it/test/PolarisRestCatalogIntegrationBase.java | 84 ++++++++++++++++++----
 .../service/it/env/IntegrationTestsHelperTest.java | 42 ++++-------
 .../it/RestCatalogAdlsCredentialVendingIT.java     |  2 +
 .../service/it/PolarisRestCatalogMinIOIT.java      | 30 ++++++--
 6 files changed, 114 insertions(+), 56 deletions(-)

diff --git 
a/integration-tests/src/main/java/org/apache/polaris/service/it/env/IntegrationTestsHelper.java
 
b/integration-tests/src/main/java/org/apache/polaris/service/it/env/IntegrationTestsHelper.java
index a2becabe3..08746e456 100644
--- 
a/integration-tests/src/main/java/org/apache/polaris/service/it/env/IntegrationTestsHelper.java
+++ 
b/integration-tests/src/main/java/org/apache/polaris/service/it/env/IntegrationTestsHelper.java
@@ -90,10 +90,7 @@ public final class IntegrationTestsHelper {
    * annotation will be used.
    */
   public static <A extends Annotation> Map<String, String> 
mergeFromAnnotatedElements(
-      TestInfo testInfo,
-      Class<A> annotationClass,
-      Function<A, String[]> propertiesExtractor,
-      Map<String, String> defaults) {
+      TestInfo testInfo, Class<A> annotationClass, Function<A, String[]> 
propertiesExtractor) {
     String[] methodProperties =
         testInfo
             .getTestMethod()
@@ -115,7 +112,6 @@ public final class IntegrationTestsHelper {
               + Arrays.toString(properties));
     }
     ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
-    builder.putAll(defaults);
     for (int i = 0; i < properties.length; i += 2) {
       builder.put(properties[i], properties[i + 1]);
     }
diff --git 
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisPolicyServiceIntegrationTest.java
 
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisPolicyServiceIntegrationTest.java
index 2557466de..a8ea9fda6 100644
--- 
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisPolicyServiceIntegrationTest.java
+++ 
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisPolicyServiceIntegrationTest.java
@@ -176,9 +176,11 @@ public class PolarisPolicyServiceIntegrationTest {
 
     CatalogProperties.Builder catalogPropsBuilder = 
CatalogProperties.builder(catalogBaseLocation);
 
+    catalogPropsBuilder.putAll(DEFAULT_CATALOG_PROPERTIES);
+
     Map<String, String> catalogProperties =
         IntegrationTestsHelper.mergeFromAnnotatedElements(
-            testInfo, CatalogConfig.class, CatalogConfig::properties, 
DEFAULT_CATALOG_PROPERTIES);
+            testInfo, CatalogConfig.class, CatalogConfig::properties);
     catalogPropsBuilder.putAll(catalogProperties);
 
     if (!s3BucketBase.getScheme().equals("file")) {
@@ -206,7 +208,7 @@ public class PolarisPolicyServiceIntegrationTest {
 
     Map<String, String> restCatalogProperties =
         IntegrationTestsHelper.mergeFromAnnotatedElements(
-            testInfo, RestCatalogConfig.class, RestCatalogConfig::value, 
Map.of());
+            testInfo, RestCatalogConfig.class, RestCatalogConfig::value);
 
     String principalToken = client.obtainToken(principalCredentials);
     restCatalog =
diff --git 
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogIntegrationBase.java
 
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogIntegrationBase.java
index 0b38f864a..69a39c7b0 100644
--- 
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogIntegrationBase.java
+++ 
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogIntegrationBase.java
@@ -159,10 +159,12 @@ public abstract class PolarisRestCatalogIntegrationBase 
extends CatalogTests<RES
   private URI externalCatalogBaseLocation;
   private String catalogBaseLocation;
 
+  /**
+   * Default RESTCatalog properties. Most tests from the parent class expect 
these properties to be
+   * set.
+   */
   private static final Map<String, String> DEFAULT_REST_CATALOG_CONFIG =
       Map.of(
-          org.apache.iceberg.CatalogProperties.FILE_IO_IMPL,
-          "org.apache.iceberg.inmemory.InMemoryFileIO",
           org.apache.iceberg.CatalogProperties.TABLE_DEFAULT_PREFIX + 
"default-key1",
           "catalog-default-key1",
           org.apache.iceberg.CatalogProperties.TABLE_DEFAULT_PREFIX + 
"default-key2",
@@ -209,6 +211,19 @@ public abstract class PolarisRestCatalogIntegrationBase 
extends CatalogTests<RES
     return ImmutableMap.builder();
   }
 
+  /**
+   * The expected location for a table. Some tests rely on this location, but 
the default
+   * implementation uses file:// locations.
+   */
+  @Override
+  protected String baseTableLocation(TableIdentifier identifier) {
+    return RESTUtil.stripTrailingSlash(catalogBaseLocation)
+        + "/"
+        + identifier.namespace()
+        + "/"
+        + identifier.name();
+  }
+
   /** Get the base URI for the external catalog. */
   protected URI externalCatalogBaseLocation() {
     return externalCatalogBaseLocation;
@@ -255,10 +270,17 @@ public abstract class PolarisRestCatalogIntegrationBase 
extends CatalogTests<RES
 
     CatalogProperties.Builder catalogPropsBuilder = 
CatalogProperties.builder(catalogBaseLocation);
 
-    Map<String, String> catalogProperties =
+    // Sources of catalog properties:
+    // 1. DEFAULT_CATALOG_PROPERTIES
+    // 2. config from annotations
+    // 3. DROP_WITH_PURGE_ENABLED
+    // 4. FILE scheme adjustments
+
+    catalogPropsBuilder.putAll(DEFAULT_CATALOG_PROPERTIES);
+
+    catalogPropsBuilder.putAll(
         IntegrationTestsHelper.mergeFromAnnotatedElements(
-            testInfo, CatalogConfig.class, CatalogConfig::properties, 
DEFAULT_CATALOG_PROPERTIES);
-    catalogPropsBuilder.putAll(catalogProperties);
+            testInfo, CatalogConfig.class, CatalogConfig::properties));
 
     catalogPropsBuilder.addProperty(
         FeatureConfiguration.DROP_WITH_PURGE_ENABLED.catalogConfig(), "true");
@@ -283,14 +305,29 @@ public abstract class PolarisRestCatalogIntegrationBase 
extends CatalogTests<RES
     createPolarisCatalog(catalog);
     managementApi.makeAdmin(principalRoleName, catalog);
 
-    restCatalogConfig =
+    ImmutableMap.Builder<String, String> restCatalogConfigBuilder = 
ImmutableMap.builder();
+
+    // Sources of RESTCatalog properties:
+    // 1. DEFAULT_REST_CATALOG_CONFIG
+    // 3. FileIO implementation
+    // 2. config from annotations
+    // 4. config from extraCatalogProperties()
+    // 5. config passed directly to initCatalog() (not stored in 
restCatalogConfig)
+
+    restCatalogConfigBuilder.putAll(DEFAULT_REST_CATALOG_CONFIG);
+
+    restCatalogConfigBuilder.put(
+        org.apache.iceberg.CatalogProperties.FILE_IO_IMPL,
+        restCatalogFileIOImplementation().getName());
+
+    restCatalogConfigBuilder.putAll(
         IntegrationTestsHelper.mergeFromAnnotatedElements(
-            testInfo,
-            RestCatalogConfig.class,
-            RestCatalogConfig::value,
-            DEFAULT_REST_CATALOG_CONFIG);
+            testInfo, RestCatalogConfig.class, RestCatalogConfig::value));
+
+    restCatalogConfigBuilder.putAll(extraCatalogProperties(testInfo));
 
-    restCatalog = initCatalog(currentCatalogName, ImmutableMap.of());
+    restCatalogConfig = restCatalogConfigBuilder.buildKeepingLast();
+    restCatalog = initCatalog(currentCatalogName, Map.of());
   }
 
   /**
@@ -354,8 +391,11 @@ public abstract class PolarisRestCatalogIntegrationBase 
extends CatalogTests<RES
   /**
    * Initialize a RESTCatalog for testing.
    *
-   * @param catalogName this parameter is currently unused.
-   * @param additionalProperties additional properties to apply on top of the 
default test settings
+   * @param catalogName this parameter is currently unused. All RESTCatalog 
instances created by
+   *     tests have the name "polaris".
+   * @param additionalProperties additional properties to apply on top of the 
configured defaults.
+   *     Some tests in the parent class use this to inject test-specific 
properties e.g. {@link
+   *     #testCatalogWithCustomMetricsReporter()}.
    * @return a configured instance of RESTCatalog
    */
   @Override
@@ -367,6 +407,23 @@ public abstract class PolarisRestCatalogIntegrationBase 
extends CatalogTests<RES
         endpoints, currentCatalogName, 
extraPropertiesBuilder.buildKeepingLast(), principalToken);
   }
 
+  /**
+   * Additional properties to apply when creating the RESTCatalog, on top of 
the default test
+   * settings and any properties specified via {@link RestCatalogConfig} 
annotations.
+   *
+   * <p>This is useful when subclasses need to override some of the default 
settings with a value
+   * that cannot be expressed via annotations (e.g. because it needs to refer 
to some other
+   * test-local state).
+   */
+  protected Map<String, String> extraCatalogProperties(TestInfo testInfo) {
+    return Map.of();
+  }
+
+  /** The FileIO implementation class name to be used for the RESTCatalog 
instance. */
+  protected Class<? extends FileIO> restCatalogFileIOImplementation() {
+    return ResolvingFileIO.class;
+  }
+
   @Override
   protected boolean requiresNamespaceCreate() {
     return true;
@@ -690,6 +747,7 @@ public abstract class PolarisRestCatalogIntegrationBase 
extends CatalogTests<RES
    * should succeed.
    */
   @CatalogConfig(Catalog.TypeEnum.EXTERNAL)
+  @RestCatalogConfig({"header.X-Iceberg-Access-Delegation", ""}) // force 
empty header
   @Test
   public void 
testLoadTableWithoutAccessDelegationForExternalCatalogWithConfigDisabled() {
     Namespace ns1 = Namespace.of("ns1");
diff --git 
a/integration-tests/src/test/java/org/apache/polaris/service/it/env/IntegrationTestsHelperTest.java
 
b/integration-tests/src/test/java/org/apache/polaris/service/it/env/IntegrationTestsHelperTest.java
index 63f75fd91..ddd2f3674 100644
--- 
a/integration-tests/src/test/java/org/apache/polaris/service/it/env/IntegrationTestsHelperTest.java
+++ 
b/integration-tests/src/test/java/org/apache/polaris/service/it/env/IntegrationTestsHelperTest.java
@@ -136,17 +136,14 @@ class IntegrationTestsHelperTest {
   @ParameterizedTest
   @MethodSource
   void mergeFromAnnotatedElements(
-      Method testMethod,
-      Class<?> testClass,
-      Map<String, String> defaults,
-      Map<String, String> expected) {
+      Method testMethod, Class<?> testClass, Map<String, String> expected) {
     TestInfo testInfo = mock(TestInfo.class);
     when(testInfo.getTestMethod()).thenReturn(Optional.ofNullable(testMethod));
     when(testInfo.getTestClass()).thenReturn(Optional.ofNullable(testClass));
 
     Map<String, String> result =
         IntegrationTestsHelper.mergeFromAnnotatedElements(
-            testInfo, TestAnnotation.class, TestAnnotation::properties, 
defaults);
+            testInfo, TestAnnotation.class, TestAnnotation::properties);
 
     assertThat(result).isEqualTo(expected);
   }
@@ -160,42 +157,27 @@ class IntegrationTestsHelperTest {
         Arguments.of(
             annotatedMethod,
             AnnotatedClass.class,
-            Map.of(),
             Map.of(
                 "sharedKey", "methodValue", "classKey", "classValue", 
"methodKey", "methodValue")),
+        // Only method has properties
+        Arguments.of(
+            annotatedMethod,
+            UnannotatedClass.class,
+            Map.of("sharedKey", "methodValue", "methodKey", "methodValue")),
         // Only class has properties
         Arguments.of(
             unannotatedMethod,
             AnnotatedClass.class,
-            Map.of(),
             Map.of("sharedKey", "classValue", "classKey", "classValue")),
         // No method, class has properties
         Arguments.of(
             null,
             AnnotatedClass.class,
-            Map.of(),
-            Map.of("sharedKey", "classValue", "classKey", "classValue")),
-        // Neither has properties - return defaults
-        Arguments.of(
-            unannotatedMethod,
-            UnannotatedClass.class,
-            Map.of("defaultKey", "defaultValue"),
-            Map.of("defaultKey", "defaultValue")),
-        // No method, no class - return defaults
-        Arguments.of(null, null, Map.of("key", "value"), Map.of("key", 
"value")),
-        // Defaults are overridden by class properties
-        Arguments.of(
-            null,
-            AnnotatedClass.class,
-            Map.of("sharedKey", "overridden"),
             Map.of("sharedKey", "classValue", "classKey", "classValue")),
-        // Defaults are overridden by method properties
-        Arguments.of(
-            annotatedMethod,
-            AnnotatedClass.class,
-            Map.of("methodKey", "overridden"),
-            Map.of(
-                "sharedKey", "methodValue", "classKey", "classValue", 
"methodKey", "methodValue")));
+        // Neither has properties - return empty
+        Arguments.of(unannotatedMethod, UnannotatedClass.class, Map.of()),
+        // No method, no class - return empty
+        Arguments.of(null, null, Map.of()));
   }
 
   @TestAnnotation(properties = {"key1", "value1", "key2"})
@@ -213,7 +195,7 @@ class IntegrationTestsHelperTest {
     assertThatThrownBy(
             () ->
                 IntegrationTestsHelper.mergeFromAnnotatedElements(
-                    testInfo, TestAnnotation.class, 
TestAnnotation::properties, Map.of()))
+                    testInfo, TestAnnotation.class, 
TestAnnotation::properties))
         .isInstanceOf(IllegalArgumentException.class)
         .hasMessageContaining("Properties must be in key-value pairs");
   }
diff --git 
a/runtime/service/src/cloudTest/java/org/apache/polaris/service/it/RestCatalogAdlsCredentialVendingIT.java
 
b/runtime/service/src/cloudTest/java/org/apache/polaris/service/it/RestCatalogAdlsCredentialVendingIT.java
index 169337d99..77f335cab 100644
--- 
a/runtime/service/src/cloudTest/java/org/apache/polaris/service/it/RestCatalogAdlsCredentialVendingIT.java
+++ 
b/runtime/service/src/cloudTest/java/org/apache/polaris/service/it/RestCatalogAdlsCredentialVendingIT.java
@@ -20,10 +20,12 @@ package org.apache.polaris.service.it;
 
 import io.quarkus.test.junit.QuarkusIntegrationTest;
 import io.quarkus.test.junit.TestProfile;
+import org.apache.polaris.service.it.env.RestCatalogConfig;
 import 
org.apache.polaris.service.it.test.PolarisRestCatalogAdlsIntegrationTestBase;
 import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
 
 @QuarkusIntegrationTest
 @TestProfile(CredentialVendingProfile.class)
 @EnabledIfEnvironmentVariable(named = "INTEGRATION_TEST_AZURE_PATH", matches = 
".+")
+@RestCatalogConfig({"header.X-Iceberg-Access-Delegation", 
"vended-credentials"})
 public class RestCatalogAdlsCredentialVendingIT extends 
PolarisRestCatalogAdlsIntegrationTestBase {}
diff --git 
a/runtime/service/src/intTest/java/org/apache/polaris/service/it/PolarisRestCatalogMinIOIT.java
 
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/PolarisRestCatalogMinIOIT.java
index 48a19d8ae..76c95dac1 100644
--- 
a/runtime/service/src/intTest/java/org/apache/polaris/service/it/PolarisRestCatalogMinIOIT.java
+++ 
b/runtime/service/src/intTest/java/org/apache/polaris/service/it/PolarisRestCatalogMinIOIT.java
@@ -25,21 +25,24 @@ import io.quarkus.test.junit.TestProfile;
 import java.net.URI;
 import java.util.List;
 import java.util.Map;
+import org.apache.iceberg.aws.s3.S3FileIOProperties;
 import org.apache.polaris.core.admin.model.AwsStorageConfigInfo;
 import org.apache.polaris.core.admin.model.StorageConfigInfo;
-import org.apache.polaris.core.storage.StorageAccessProperty;
+import org.apache.polaris.service.it.env.RestCatalogConfig;
 import org.apache.polaris.service.it.ext.PolarisIntegrationTestExtension;
 import org.apache.polaris.service.it.test.PolarisRestCatalogIntegrationBase;
 import org.apache.polaris.test.minio.Minio;
 import org.apache.polaris.test.minio.MinioAccess;
 import org.apache.polaris.test.minio.MinioExtension;
 import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.TestInfo;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 @QuarkusIntegrationTest
 @TestProfile(PolarisRestCatalogMinIOIT.Profile.class)
 @ExtendWith(MinioExtension.class)
 @ExtendWith(PolarisIntegrationTestExtension.class)
+@RestCatalogConfig({"header.X-Iceberg-Access-Delegation", 
"vended-credentials"})
 public class PolarisRestCatalogMinIOIT extends 
PolarisRestCatalogIntegrationBase {
 
   protected static final String BUCKET_URI_PREFIX = "/minio-test-polaris";
@@ -61,20 +64,24 @@ public class PolarisRestCatalogMinIOIT extends 
PolarisRestCatalogIntegrationBase
   private static URI storageBase;
   private static String endpoint;
 
+  private static Map<String, String> s3Properties;
+
   @BeforeAll
   static void setup(
       @Minio(accessKey = MINIO_ACCESS_KEY, secretKey = MINIO_SECRET_KEY) 
MinioAccess minioAccess) {
     storageBase = minioAccess.s3BucketUri(BUCKET_URI_PREFIX);
     endpoint = minioAccess.s3endpoint();
+    s3Properties =
+        Map.of(
+            S3FileIOProperties.ENDPOINT, endpoint,
+            S3FileIOProperties.PATH_STYLE_ACCESS, "true",
+            S3FileIOProperties.ACCESS_KEY_ID, MINIO_ACCESS_KEY,
+            S3FileIOProperties.SECRET_ACCESS_KEY, MINIO_SECRET_KEY);
   }
 
   @Override
   protected ImmutableMap.Builder<String, String> clientFileIOProperties() {
-    return super.clientFileIOProperties()
-        .put(StorageAccessProperty.AWS_ENDPOINT.getPropertyName(), endpoint)
-        .put(StorageAccessProperty.AWS_PATH_STYLE_ACCESS.getPropertyName(), 
"true")
-        .put(StorageAccessProperty.AWS_KEY_ID.getPropertyName(), 
MINIO_ACCESS_KEY)
-        .put(StorageAccessProperty.AWS_SECRET_KEY.getPropertyName(), 
MINIO_SECRET_KEY);
+    return super.clientFileIOProperties().putAll(s3Properties);
   }
 
   @Override
@@ -88,4 +95,15 @@ public class PolarisRestCatalogMinIOIT extends 
PolarisRestCatalogIntegrationBase
 
     return storageConfig.build();
   }
+
+  @Override
+  protected Map<String, String> extraCatalogProperties(TestInfo testInfo) {
+    if 
(testInfo.getTestMethod().orElseThrow().getName().equals("testRegisterTable")) {
+      // This test registers a table – operation that doesn't support access 
delegation –
+      // then attempts to use the table's FileIO. This can only work if the 
client has its
+      // own S3 credentials.
+      return s3Properties;
+    }
+    return Map.of();
+  }
 }

Reply via email to