This is an automated email from the ASF dual-hosted git repository.
emaynard 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 453e9fb19 Disable custom namespace locations (#2422)
453e9fb19 is described below
commit 453e9fb19aaad48f8c46ef4ffe3d516df62e4706
Author: Eric Maynard <[email protected]>
AuthorDate: Thu Sep 4 11:08:43 2025 -0700
Disable custom namespace locations (#2422)
When we create a namespace or alter its location, we must confirm that this
location is within the parent location. This PR introduces introduces a check
similar to the one we have for tables, where custom locations are prohibited by
default. This functionality is gated behind a new behavior change flag
`ALLOW_NAMESPACE_CUSTOM_LOCATION`. In addition to allowing us to revert to the
old behavior, this flag allows some tests relying on arbitrarily-located
namespaces to pass (such as thos [...]
Fixes: #2417
---
.../it/test/PolarisApplicationIntegrationTest.java | 80 +++++++++++++++++++++-
.../it/test/PolarisRestCatalogIntegrationBase.java | 3 +-
.../spark/quarkus/it/SparkIntegrationBase.java | 1 +
.../core/config/BehaviorChangeConfiguration.java | 20 ++++--
.../polaris/core/config/PolarisConfiguration.java | 4 --
.../polaris/core/storage/StorageLocation.java | 2 +-
.../polaris/core/storage/aws/S3Location.java | 15 ++++
.../polaris/core/storage/azure/AzureLocation.java | 15 ++++
.../polaris/core/storage/aws/S3LocationTest.java | 3 +-
regtests/t_cli/src/test_cli.py | 6 +-
.../service/catalog/iceberg/IcebergCatalog.java | 80 ++++++++++++++++++++++
.../service/admin/PolarisAuthzTestBase.java | 1 +
.../iceberg/AbstractIcebergCatalogTest.java | 1 +
.../service/it/RestCatalogFileIntegrationTest.java | 2 +
14 files changed, 216 insertions(+), 17 deletions(-)
diff --git
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisApplicationIntegrationTest.java
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisApplicationIntegrationTest.java
index 2e0acc5a3..a7e21427e 100644
---
a/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisApplicationIntegrationTest.java
+++
b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisApplicationIntegrationTest.java
@@ -20,9 +20,11 @@ package org.apache.polaris.service.it.test;
import static org.apache.polaris.service.it.env.PolarisClient.polarisClient;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.awaitility.Awaitility.await;
+import com.google.common.collect.ImmutableMap;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation;
@@ -67,6 +69,7 @@ import
org.apache.polaris.core.admin.model.FileStorageConfigInfo;
import org.apache.polaris.core.admin.model.PolarisCatalog;
import org.apache.polaris.core.admin.model.PrincipalRole;
import org.apache.polaris.core.admin.model.StorageConfigInfo;
+import org.apache.polaris.core.config.BehaviorChangeConfiguration;
import org.apache.polaris.core.entity.CatalogEntity;
import org.apache.polaris.core.entity.PolarisEntityConstants;
import org.apache.polaris.service.it.env.ClientPrincipal;
@@ -76,6 +79,7 @@ import org.apache.polaris.service.it.env.PolarisClient;
import org.apache.polaris.service.it.env.RestApi;
import org.apache.polaris.service.it.ext.PolarisIntegrationTestExtension;
import org.assertj.core.api.InstanceOfAssertFactories;
+import org.assertj.core.api.ThrowableAssert;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
@@ -84,6 +88,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
/**
* @implSpec This test expects the server to be configured with the following
features configured:
@@ -186,11 +192,30 @@ public class PolarisApplicationIntegrationTest {
String principalRoleName,
StorageConfigInfo storageConfig,
String defaultBaseLocation) {
- CatalogProperties props =
+ createCatalog(
+ catalogName,
+ catalogType,
+ principalRoleName,
+ storageConfig,
+ defaultBaseLocation,
+ ImmutableMap.of());
+ }
+
+ private static void createCatalog(
+ String catalogName,
+ Catalog.TypeEnum catalogType,
+ String principalRoleName,
+ StorageConfigInfo storageConfig,
+ String defaultBaseLocation,
+ Map<String, String> additionalProperties) {
+ CatalogProperties.Builder propsBuilder =
CatalogProperties.builder(defaultBaseLocation)
.addProperty(
-
CatalogEntity.REPLACE_NEW_LOCATION_PREFIX_WITH_CATALOG_DEFAULT_KEY, "file:/")
- .build();
+
CatalogEntity.REPLACE_NEW_LOCATION_PREFIX_WITH_CATALOG_DEFAULT_KEY, "file:/");
+ for (var entry : additionalProperties.entrySet()) {
+ propsBuilder.addProperty(entry.getKey(), entry.getValue());
+ }
+ CatalogProperties props = propsBuilder.build();
Catalog catalog =
catalogType.equals(Catalog.TypeEnum.INTERNAL)
? PolarisCatalog.builder()
@@ -641,4 +666,53 @@ public class PolarisApplicationIntegrationTest {
});
}
}
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ public void testNamespaceOutsideCatalog(boolean
allowNamespaceLocationEscape) throws IOException {
+ String catalogName =
client.newEntityName("testNamespaceOutsideCatalog_specificLocation");
+ String catalogLocation = baseLocation.resolve(catalogName +
"/catalog").toString();
+ String badLocation = baseLocation.resolve(catalogName + "/ns").toString();
+ createCatalog(
+ catalogName,
+ Catalog.TypeEnum.INTERNAL,
+ principalRoleName,
+ FileStorageConfigInfo.builder(StorageConfigInfo.StorageTypeEnum.FILE)
+ .setAllowedLocations(List.of(catalogLocation))
+ .build(),
+ catalogLocation,
+ ImmutableMap.of(
+
BehaviorChangeConfiguration.ALLOW_NAMESPACE_CUSTOM_LOCATION.catalogConfig(),
+ String.valueOf(allowNamespaceLocationEscape)));
+ try (RESTSessionCatalog sessionCatalog = newSessionCatalog(catalogName)) {
+ SessionCatalog.SessionContext sessionContext =
SessionCatalog.SessionContext.createEmpty();
+ sessionCatalog.createNamespace(sessionContext,
Namespace.of("good_namespace"));
+ ThrowableAssert.ThrowingCallable createBadNamespace =
+ () ->
+ sessionCatalog.createNamespace(
+ sessionContext,
+ Namespace.of("bad_namespace"),
+ ImmutableMap.of("location", badLocation));
+ if (!allowNamespaceLocationEscape) {
+ assertThatThrownBy(createBadNamespace)
+ .isInstanceOf(BadRequestException.class)
+ .hasMessageContaining("custom location");
+ } else {
+ assertThatCode(createBadNamespace).doesNotThrowAnyException();
+ }
+ ThrowableAssert.ThrowingCallable createBadChildGoodParent =
+ () ->
+ sessionCatalog.createNamespace(
+ sessionContext,
+ Namespace.of("good_namespace", "bad_child"),
+ ImmutableMap.of("location", badLocation));
+ if (!allowNamespaceLocationEscape) {
+ assertThatThrownBy(createBadChildGoodParent)
+ .isInstanceOf(BadRequestException.class)
+ .hasMessageContaining("custom location");
+ } else {
+ assertThatCode(createBadChildGoodParent).doesNotThrowAnyException();
+ }
+ }
+ }
}
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 7a1889b93..ad72ad5eb 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
@@ -173,7 +173,8 @@ public abstract class PolarisRestCatalogIntegrationBase
extends CatalogTests<RES
Map.of(
"polaris.config.allow.unstructured.table.location", "true",
"polaris.config.allow.external.table.location", "true",
- "polaris.config.list-pagination-enabled", "true");
+ "polaris.config.list-pagination-enabled", "true",
+ "polaris.config.namespace-custom-location.enabled", "true");
/**
* Get the storage configuration information for the catalog.
diff --git
a/plugins/spark/v3.5/integration/src/intTest/java/org/apache/polaris/spark/quarkus/it/SparkIntegrationBase.java
b/plugins/spark/v3.5/integration/src/intTest/java/org/apache/polaris/spark/quarkus/it/SparkIntegrationBase.java
index d2fa1b4a6..1383e8c48 100644
---
a/plugins/spark/v3.5/integration/src/intTest/java/org/apache/polaris/spark/quarkus/it/SparkIntegrationBase.java
+++
b/plugins/spark/v3.5/integration/src/intTest/java/org/apache/polaris/spark/quarkus/it/SparkIntegrationBase.java
@@ -100,6 +100,7 @@ public abstract class SparkIntegrationBase {
CatalogProperties props = new
CatalogProperties("s3://my-bucket/path/to/data");
props.putAll(s3Container.getS3ConfigProperties());
props.put("polaris.config.drop-with-purge.enabled", "true");
+ props.put("polaris.config.namespace-custom-location.enabled", "true");
Catalog catalog =
PolarisCatalog.builder()
.setType(Catalog.TypeEnum.INTERNAL)
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/config/BehaviorChangeConfiguration.java
b/polaris-core/src/main/java/org/apache/polaris/core/config/BehaviorChangeConfiguration.java
index db5176e7c..3caded0dc 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/config/BehaviorChangeConfiguration.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/config/BehaviorChangeConfiguration.java
@@ -22,11 +22,11 @@ import java.util.Optional;
/**
* Internal configuration flags for non-feature behavior changes in Polaris.
These flags control
- * subtle behavior adjustments and bug fixes, not user-facing catalog
settings. They are intended
- * for internal use only, are inherently unstable, and may be removed at any
time. When introducing
- * a new flag, consider the trade-off between maintenance burden and the risk
of an unguarded
- * behavior change. Flags here are generally short-lived and should either be
removed or promoted to
- * stable feature flags before the next release.
+ * subtle behavior adjustments and bug fixes, not user-facing settings. They
are intended for
+ * internal use only, are inherently unstable, and may be removed at any time.
When introducing a
+ * new flag, consider the trade-off between maintenance burden and the risk of
an unguarded behavior
+ * change. Flags here are generally short-lived and should either be removed
or promoted to stable
+ * feature flags before the next release.
*
* @param <T> The type of the configuration
*/
@@ -74,4 +74,14 @@ public class BehaviorChangeConfiguration<T> extends
PolarisConfiguration<T> {
+ " the committed metadata again.")
.defaultValue(true)
.buildBehaviorChangeConfiguration();
+
+ public static final BehaviorChangeConfiguration<Boolean>
ALLOW_NAMESPACE_CUSTOM_LOCATION =
+ PolarisConfiguration.<Boolean>builder()
+ .key("ALLOW_NAMESPACE_CUSTOM_LOCATION")
+ .catalogConfig("polaris.config.namespace-custom-location.enabled")
+ .description(
+ "If set to true, allow namespaces with completely arbitrary
locations. This should not affect"
+ + " credential vending.")
+ .defaultValue(false)
+ .buildBehaviorChangeConfiguration();
}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/config/PolarisConfiguration.java
b/polaris-core/src/main/java/org/apache/polaris/core/config/PolarisConfiguration.java
index f9cf8192f..0750f7026 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/config/PolarisConfiguration.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/config/PolarisConfiguration.java
@@ -209,10 +209,6 @@ public abstract class PolarisConfiguration<T> {
public BehaviorChangeConfiguration<T> buildBehaviorChangeConfiguration() {
validateOrThrow();
- if (catalogConfig.isPresent() || catalogConfigUnsafe.isPresent()) {
- throw new IllegalArgumentException(
- "catalog configs are not valid for behavior change configs");
- }
BehaviorChangeConfiguration<T> config =
new BehaviorChangeConfiguration<>(
key, description, defaultValue, catalogConfig,
catalogConfigUnsafe);
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/storage/StorageLocation.java
b/polaris-core/src/main/java/org/apache/polaris/core/storage/StorageLocation.java
index a1774b4ad..fab1892ab 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/storage/StorageLocation.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/storage/StorageLocation.java
@@ -65,7 +65,7 @@ public class StorageLocation {
}
/** If a path doesn't end in `/`, this will add one */
- protected static String ensureTrailingSlash(String location) {
+ public static String ensureTrailingSlash(String location) {
if (location == null || location.endsWith("/")) {
return location;
} else {
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/S3Location.java
b/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/S3Location.java
index 2146a5aee..a051d0600 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/S3Location.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/S3Location.java
@@ -67,4 +67,19 @@ public class S3Location extends StorageLocation {
public String withoutScheme() {
return locationWithoutScheme;
}
+
+ @Override
+ public int hashCode() {
+ return withoutScheme().hashCode();
+ }
+
+ /** Checks if two S3Location instances represent the same physical location.
*/
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof S3Location) {
+ return withoutScheme().equals(((StorageLocation) obj).withoutScheme());
+ } else {
+ return false;
+ }
+ }
}
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/storage/azure/AzureLocation.java
b/polaris-core/src/main/java/org/apache/polaris/core/storage/azure/AzureLocation.java
index 0214ebca0..475a8da42 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/storage/azure/AzureLocation.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/storage/azure/AzureLocation.java
@@ -126,4 +126,19 @@ public class AzureLocation extends StorageLocation {
Matcher matcher = URI_PATTERN.matcher(location);
return matcher.matches();
}
+
+ @Override
+ public int hashCode() {
+ return withoutScheme().hashCode();
+ }
+
+ /** Checks if two AzureLocation instances represent the same physical
location. */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof AzureLocation) {
+ return withoutScheme().equals(((StorageLocation) obj).withoutScheme());
+ } else {
+ return false;
+ }
+ }
}
diff --git
a/polaris-core/src/test/java/org/apache/polaris/core/storage/aws/S3LocationTest.java
b/polaris-core/src/test/java/org/apache/polaris/core/storage/aws/S3LocationTest.java
index d89720b0d..cbc4425ac 100644
---
a/polaris-core/src/test/java/org/apache/polaris/core/storage/aws/S3LocationTest.java
+++
b/polaris-core/src/test/java/org/apache/polaris/core/storage/aws/S3LocationTest.java
@@ -47,6 +47,7 @@ class S3LocationTest {
Assertions.assertThat(loc1.isChildOf(loc2)).isTrue();
StorageLocation loc3 = StorageLocation.of(childScheme +
"://bucket/schema1");
- Assertions.assertThat(loc2.equals(loc3)).isFalse();
+ Assertions.assertThat(loc2.toString().equals(loc3.toString())).isFalse();
+ Assertions.assertThat(loc2.equals(loc3)).isTrue();
}
}
diff --git a/regtests/t_cli/src/test_cli.py b/regtests/t_cli/src/test_cli.py
index ee6bea714..07de695cf 100644
--- a/regtests/t_cli/src/test_cli.py
+++ b/regtests/t_cli/src/test_cli.py
@@ -110,6 +110,8 @@ def test_quickstart_flow():
ROLE_ARN,
'--default-base-location',
f's3://fake-location-{SALT}',
+ '--property',
+ 'polaris.config.namespace-custom-location.enabled=true',
f'test_cli_catalog_{SALT}'), checker=lambda s: s == '')
check_output(root_cli('catalogs', 'list'),
checker=lambda s: f'test_cli_catalog_{SALT}' in s)
@@ -170,7 +172,7 @@ def test_quickstart_flow():
'--property',
'foo=bar',
'--location',
- 's3://custom-namespace-location'
+ f's3://fake-location-{SALT}/custom-namespace-location/'
), checker=lambda s: s == '')
check_output(cli(user_token)('namespaces', 'list', '--catalog',
f'test_cli_catalog_{SALT}'),
checker=lambda s: f'test_cli_namespace_{SALT}' in s)
@@ -180,7 +182,7 @@ def test_quickstart_flow():
'--catalog',
f'test_cli_catalog_{SALT}',
f'test_cli_namespace_{SALT}'
- ), checker=lambda s: 's3://custom-namespace-location' in s and '"foo":
"bar"' in s)
+ ), checker=lambda s:
f's3://fake-location-{SALT}/custom-namespace-location/' in s and '"foo": "bar"'
in s)
check_output(cli(user_token)(
'namespaces',
'delete',
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalog.java
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalog.java
index 7402de80d..01ad489d8 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalog.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalog.java
@@ -510,6 +510,10 @@ public class IcebergCatalog extends
BaseMetastoreViewCatalog
} else {
LOGGER.debug("Skipping location overlap validation for namespace '{}'",
namespace);
}
+ if (!realmConfig.getConfig(
+ BehaviorChangeConfiguration.ALLOW_NAMESPACE_CUSTOM_LOCATION,
catalogEntity)) {
+ validateNamespaceLocation(entity, resolvedParent);
+ }
PolarisEntity returnedEntity =
PolarisEntity.of(
getMetaStoreManager()
@@ -667,6 +671,12 @@ public class IcebergCatalog extends
BaseMetastoreViewCatalog
} else {
LOGGER.debug("Skipping location overlap validation for namespace '{}'",
namespace);
}
+ if (!realmConfig.getConfig(
+ BehaviorChangeConfiguration.ALLOW_NAMESPACE_CUSTOM_LOCATION,
catalogEntity)) {
+ if (properties.containsKey(PolarisEntityConstants.ENTITY_BASE_LOCATION))
{
+ validateNamespaceLocation(NamespaceEntity.of(entity),
resolvedEntities);
+ }
+ }
List<PolarisEntity> parentPath = resolvedEntities.getRawFullPath();
PolarisEntity returnedEntity =
@@ -1058,6 +1068,76 @@ public class IcebergCatalog extends
BaseMetastoreViewCatalog
}
}
+ /** Checks whether the location of a namespace is valid given its parent */
+ private void validateNamespaceLocation(
+ NamespaceEntity namespace, PolarisResolvedPathWrapper resolvedParent) {
+ StorageLocation namespaceLocation =
+ StorageLocation.of(
+ StorageLocation.ensureTrailingSlash(
+ resolveNamespaceLocation(namespace.asNamespace(),
namespace.getPropertiesAsMap())));
+ PolarisEntity parent = resolvedParent.getResolvedLeafEntity().getEntity();
+ Preconditions.checkArgument(
+ parent.getType().equals(PolarisEntityType.CATALOG)
+ || parent.getType().equals(PolarisEntityType.NAMESPACE),
+ "Invalid parent type");
+ if (parent.getType().equals(PolarisEntityType.CATALOG)) {
+ CatalogEntity parentEntity = CatalogEntity.of(parent);
+ LOGGER.debug(
+ "Validating namespace {} given parent catalog {}",
+ namespace.getName(),
+ parentEntity.getName());
+ var storageConfigInfo = parentEntity.getStorageConfigurationInfo();
+ if (storageConfigInfo == null) {
+ throw new IllegalArgumentException(
+ "Cannot create namespace without a parent storage configuration");
+ }
+ List<StorageLocation> defaultLocations =
+
parentEntity.getStorageConfigurationInfo().getAllowedLocations().stream()
+ .filter(java.util.Objects::nonNull)
+ .map(
+ l ->
+ StorageLocation.ensureTrailingSlash(
+ StorageLocation.ensureTrailingSlash(l) +
namespace.getName()))
+ .map(StorageLocation::of)
+ .toList();
+ if (!defaultLocations.contains(namespaceLocation)) {
+ throw new IllegalArgumentException(
+ "Namespace "
+ + namespace.getName()
+ + " has a custom location, "
+ + "which is not enabled. Expected a location in: ["
+ + String.join(
+ ", ",
defaultLocations.stream().map(StorageLocation::toString).toList())
+ + "]. Got location: "
+ + namespaceLocation
+ + "]");
+ }
+ } else if (parent.getType().equals(PolarisEntityType.NAMESPACE)) {
+ NamespaceEntity parentEntity = NamespaceEntity.of(parent);
+ LOGGER.debug(
+ "Validating namespace {} given parent namespace {}",
+ namespace.getName(),
+ parentEntity.getName());
+ String parentLocation =
+ resolveNamespaceLocation(parentEntity.asNamespace(),
parentEntity.getPropertiesAsMap());
+ StorageLocation defaultLocation =
+ StorageLocation.of(
+ StorageLocation.ensureTrailingSlash(
+ StorageLocation.ensureTrailingSlash(parentLocation) +
namespace.getName()));
+ if (!defaultLocation.equals(namespaceLocation)) {
+ throw new IllegalArgumentException(
+ "Namespace "
+ + namespace.getName()
+ + " has a custom location, "
+ + "which is not enabled. Expected location: ["
+ + defaultLocation
+ + "]. Got location: ["
+ + namespaceLocation
+ + "]");
+ }
+ }
+ }
+
/**
* Validate no location overlap exists between the entity path and its
sibling entities. This
* resolves all siblings at the same level as the target entity (namespaces
if the target entity
diff --git
a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAuthzTestBase.java
b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAuthzTestBase.java
index 52c105113..fee88c25a 100644
---
a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAuthzTestBase.java
+++
b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAuthzTestBase.java
@@ -120,6 +120,7 @@ public abstract class PolarisAuthzTestBase {
"polaris.features.\"ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING\"",
"true")
.put("polaris.features.\"DROP_WITH_PURGE_ENABLED\"", "true")
+ .put("polaris.behavior-changes.\"ALLOW_NAMESPACE_CUSTOM_LOCATION\"",
"true")
.build();
}
}
diff --git
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogTest.java
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogTest.java
index 9b81249f0..a1146ceae 100644
---
a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogTest.java
+++
b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogTest.java
@@ -199,6 +199,7 @@ public abstract class AbstractIcebergCatalogTest extends
CatalogTests<IcebergCat
.putAll(super.getConfigOverrides())
.put("polaris.features.\"ALLOW_TABLE_LOCATION_OVERLAP\"", "true")
.put("polaris.features.\"LIST_PAGINATION_ENABLED\"", "true")
+ .put("polaris.features.\"ALLOW_NAMESPACE_CUSTOM_LOCATION\"", "true")
.build();
}
}
diff --git
a/runtime/service/src/test/java/org/apache/polaris/service/it/RestCatalogFileIntegrationTest.java
b/runtime/service/src/test/java/org/apache/polaris/service/it/RestCatalogFileIntegrationTest.java
index e50947ca0..5aaf858de 100644
---
a/runtime/service/src/test/java/org/apache/polaris/service/it/RestCatalogFileIntegrationTest.java
+++
b/runtime/service/src/test/java/org/apache/polaris/service/it/RestCatalogFileIntegrationTest.java
@@ -42,6 +42,8 @@ public class RestCatalogFileIntegrationTest extends
PolarisRestCatalogFileIntegr
"polaris.features.\"SUPPORTED_CATALOG_STORAGE_TYPES\"",
"[\"FILE\",\"S3\"]",
"polaris.readiness.ignore-severe-issues",
+ "true",
+ "polaris.features.\"ALLOW_NAMESPACE_CUSTOM_LOCATION\"",
"true");
}
}