You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polaris.apache.org by yu...@apache.org on 2026/08/31 21:54:47 UTC

(polaris) branch main updated: Service: Fix namespace creation rejecting its own default location (#5357)

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

flyrain 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 e0a4306933 Service: Fix namespace creation rejecting its own default location (#5357)
e0a4306933 is described below

commit e0a4306933b563584892ba98cb6c454b5ac1b77b
Author: Ayush Saxena <ay...@apache.org>
AuthorDate: Tue Sep 1 03:24:41 2026 +0530

    Service: Fix namespace creation rejecting its own default location (#5357)
---
 CHANGELOG.md                                       |  7 +++
 .../catalog/iceberg/LocalIcebergCatalog.java       | 31 ++++++-------
 .../iceberg/IcebergAllowedLocationTest.java        | 52 ++++++++++++++++++++++
 3 files changed, 72 insertions(+), 18 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 32fd290184..69998f343f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -138,6 +138,13 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti
   loading the grants held by a principal, principal role or catalog role scans every grant record
   in the realm, because the `grant_records` primary key continues with the securable columns after
   `realm_id`. Existing CockroachDB deployments need a manual index creation — see Upgrade notes.
+- Creating a namespace without an explicit location no longer fails with HTTP 400 when the
+  catalog's `default-base-location` sits inside an allowed location instead of being one of
+  them. For example, with allowed location `s3://b1` and `default-base-location` `s3://b1/d1`,
+  `CREATE NAMESPACE ns` places the namespace at `s3://b1/d1/ns`, but the check expected it
+  directly under an allowed location, at `s3://b1/ns`, and rejected it as a custom location even
+  though the request asked for none. The namespace location is now compared against the
+  catalog's `default-base-location`, which is what it is derived from.
 
 ### Commits
 
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/LocalIcebergCatalog.java b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/LocalIcebergCatalog.java
index 58044461c0..f400eabf7f 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/LocalIcebergCatalog.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/catalog/iceberg/LocalIcebergCatalog.java
@@ -702,7 +702,7 @@ public class LocalIcebergCatalog extends BaseMetastoreViewCatalog
     }
     if (!realmConfig.getConfig(
         BehaviorChangeConfiguration.ALLOW_NAMESPACE_CUSTOM_LOCATION, catalogEntity)) {
-      validateNamespaceLocation(entity, resolvedParent);
+      validateNamespaceUsesDefaultLocation(entity, resolvedParent);
     }
     EntityResult result =
         getMetaStoreManager()
@@ -899,7 +899,7 @@ public class LocalIcebergCatalog extends BaseMetastoreViewCatalog
     if (!realmConfig.getConfig(
         BehaviorChangeConfiguration.ALLOW_NAMESPACE_CUSTOM_LOCATION, catalogEntity)) {
       if (properties.containsKey(PolarisEntityConstants.ENTITY_BASE_LOCATION)) {
-        validateNamespaceLocation(NamespaceEntity.of(entity), resolvedEntities);
+        validateNamespaceUsesDefaultLocation(NamespaceEntity.of(entity), resolvedEntities);
       }
     }
 
@@ -1439,8 +1439,8 @@ public class LocalIcebergCatalog extends BaseMetastoreViewCatalog
     }
   }
 
-  /** Checks whether the location of a namespace is valid given its parent */
-  private void validateNamespaceLocation(
+  /** Checks that a namespace sits at the default location derived from its parent. */
+  private void validateNamespaceUsesDefaultLocation(
       NamespaceEntity namespace, PolarisResolvedPathWrapper resolvedParent) {
     StorageLocation namespaceLocation =
         StorageLocation.of(
@@ -1462,24 +1462,19 @@ public class LocalIcebergCatalog extends BaseMetastoreViewCatalog
         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)) {
+      String parentLocation = resolveLocationForPath(diagnostics, List.of(parent));
+      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 a location in: ["
-                + String.join(
-                    ", ", defaultLocations.stream().map(StorageLocation::toString).toList())
-                + "]. Got location: "
+                + "which is not enabled. Expected location: ["
+                + defaultLocation
+                + "]. Got location: ["
                 + namespaceLocation
                 + "]");
       }
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergAllowedLocationTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergAllowedLocationTest.java
index aef8b9eb75..dacd3055ca 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergAllowedLocationTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergAllowedLocationTest.java
@@ -678,6 +678,58 @@ public class IcebergAllowedLocationTest {
         .hasMessageContaining("Invalid locations");
   }
 
+  @Test
+  void testCreateNamespaceWhenBaseLocationIsNestedInsideAllowedLocation(@TempDir Path tmpDir) {
+    TestServices services =
+        TestServices.builder()
+            .config(
+                Map.of(
+                    "ALLOW_INSECURE_STORAGE_TYPES",
+                    "true",
+                    "SUPPORTED_CATALOG_STORAGE_TYPES",
+                    List.of("FILE")))
+            .build();
+
+    String allowedLocation = tmpDir.toAbsolutePath().toUri().toString();
+    String catalogLocation = tmpDir.resolve("warehouse").toAbsolutePath().toUri().toString();
+    createCatalog(services, Map.of(), catalogLocation, List.of(allowedLocation));
+
+    CreateNamespaceRequest derivedLocationRequest =
+        CreateNamespaceRequest.builder().withNamespace(Namespace.of(namespace)).build();
+    try (Response response =
+        services
+            .restApi()
+            .createNamespace(
+                catalog,
+                derivedLocationRequest,
+                IDEMPOTENCY_KEY,
+                services.realmContext(),
+                services.securityContext())) {
+      assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode());
+    }
+
+    // A caller-specified location must still be rejected.
+    Map<String, String> customLocation = new HashMap<>();
+    customLocation.put("location", allowedLocation + "elsewhere");
+    CreateNamespaceRequest customLocationRequest =
+        CreateNamespaceRequest.builder()
+            .withNamespace(Namespace.of("ns2"))
+            .setProperties(customLocation)
+            .build();
+    assertThatThrownBy(
+            () ->
+                services
+                    .restApi()
+                    .createNamespace(
+                        catalog,
+                        customLocationRequest,
+                        IDEMPOTENCY_KEY,
+                        services.realmContext(),
+                        services.securityContext()))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("has a custom location");
+  }
+
   private void createCatalog(
       TestServices services,
       Map<String, String> catalogConfig,