This is an automated email from the ASF dual-hosted git repository. yufei 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 dd3c71c64 Core: Add Endpoints and resource paths for Generic Table (#1286) dd3c71c64 is described below commit dd3c71c64f0f1eadfddb125f173601468a36996d Author: gh-yzou <167037035+gh-y...@users.noreply.github.com> AuthorDate: Wed Apr 2 23:15:28 2025 -0700 Core: Add Endpoints and resource paths for Generic Table (#1286) --- .../apache/polaris/core/rest/PolarisEndpoints.java | 60 +++++++++++++++ .../polaris/core/rest/PolarisResourcePaths.java | 62 +++++++++++++++ .../core/rest/PolarisResourcePathsTest.java | 60 +++++++++++++++ .../service/quarkus/catalog/GetConfigTest.java | 87 ++++++++++++++++++++++ .../catalog/iceberg/IcebergCatalogAdapter.java | 2 + .../org/apache/polaris/service/TestServices.java | 7 +- 6 files changed, 276 insertions(+), 2 deletions(-) diff --git a/polaris-core/src/main/java/org/apache/polaris/core/rest/PolarisEndpoints.java b/polaris-core/src/main/java/org/apache/polaris/core/rest/PolarisEndpoints.java new file mode 100644 index 000000000..edbfb1414 --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/rest/PolarisEndpoints.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.core.rest; + +import com.google.common.collect.ImmutableSet; +import java.util.Set; +import org.apache.iceberg.rest.Endpoint; +import org.apache.polaris.core.config.FeatureConfiguration; +import org.apache.polaris.core.context.CallContext; + +public class PolarisEndpoints { + public static final Endpoint V1_LIST_GENERIC_TABLES = + Endpoint.create("GET", PolarisResourcePaths.V1_GENERIC_TABLES); + public static final Endpoint V1_LOAD_GENERIC_TABLE = + Endpoint.create("GET", PolarisResourcePaths.V1_GENERIC_TABLE); + public static final Endpoint V1_CREATE_GENERIC_TABLE = + Endpoint.create("POST", PolarisResourcePaths.V1_GENERIC_TABLES); + public static final Endpoint V1_DELETE_GENERIC_TABLE = + Endpoint.create("DELETE", PolarisResourcePaths.V1_GENERIC_TABLE); + + public static final Set<Endpoint> GENERIC_TABLE_ENDPOINTS = + ImmutableSet.<Endpoint>builder() + .add(V1_LIST_GENERIC_TABLES) + .add(V1_CREATE_GENERIC_TABLE) + .add(V1_DELETE_GENERIC_TABLE) + .add(V1_LOAD_GENERIC_TABLE) + .build(); + + /** + * Get the generic table endpoints. Returns GENERIC_TABLE_ENDPOINTS if ENABLE_GENERIC_TABLES is + * set to true, otherwise, returns an empty set. + */ + public static Set<Endpoint> getSupportedGenericTableEndpoints(CallContext callContext) { + // add the generic table endpoints as supported endpoints if generic table feature is enabled. + boolean genericTableEnabled = + callContext + .getPolarisCallContext() + .getConfigurationStore() + .getConfiguration( + callContext.getPolarisCallContext(), FeatureConfiguration.ENABLE_GENERIC_TABLES); + + return genericTableEnabled ? GENERIC_TABLE_ENDPOINTS : ImmutableSet.of(); + } +} diff --git a/polaris-core/src/main/java/org/apache/polaris/core/rest/PolarisResourcePaths.java b/polaris-core/src/main/java/org/apache/polaris/core/rest/PolarisResourcePaths.java new file mode 100644 index 000000000..159a1a014 --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/rest/PolarisResourcePaths.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.core.rest; + +import com.google.common.base.Joiner; +import java.util.Map; +import org.apache.iceberg.catalog.Namespace; +import org.apache.iceberg.catalog.TableIdentifier; +import org.apache.iceberg.rest.RESTUtil; + +public class PolarisResourcePaths { + private static final Joiner SLASH = Joiner.on("/").skipNulls(); + public static final String PREFIX = "prefix"; + + // Generic Table endpoints + public static final String V1_GENERIC_TABLES = + "polaris/v1/{prefix}/namespaces/{namespace}/generic-tables"; + public static final String V1_GENERIC_TABLE = + "polaris/v1/{prefix}/namespaces/{namespace}/generic-tables/{generic-table}"; + + private final String prefix; + + public PolarisResourcePaths(String prefix) { + this.prefix = prefix; + } + + public static PolarisResourcePaths forCatalogProperties(Map<String, String> properties) { + return new PolarisResourcePaths(properties.get(PREFIX)); + } + + public String genericTables(Namespace ns) { + return SLASH.join( + "polaris", "v1", prefix, "namespaces", RESTUtil.encodeNamespace(ns), "generic-tables"); + } + + public String genericTable(TableIdentifier ident) { + return SLASH.join( + "polaris", + "v1", + prefix, + "namespaces", + RESTUtil.encodeNamespace(ident.namespace()), + "generic-tables", + RESTUtil.encodeString(ident.name())); + } +} diff --git a/polaris-core/src/test/java/org/apache/polaris/core/rest/PolarisResourcePathsTest.java b/polaris-core/src/test/java/org/apache/polaris/core/rest/PolarisResourcePathsTest.java new file mode 100644 index 000000000..4572bb515 --- /dev/null +++ b/polaris-core/src/test/java/org/apache/polaris/core/rest/PolarisResourcePathsTest.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.core.rest; + +import java.util.HashMap; +import java.util.Map; +import org.apache.iceberg.catalog.Namespace; +import org.apache.iceberg.catalog.TableIdentifier; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class PolarisResourcePathsTest { + private static final String testPrefix = "polaris-test"; + + private PolarisResourcePaths paths; + + @BeforeEach + public void setUp() { + Map<String, String> properties = new HashMap<>(); + properties.put(PolarisResourcePaths.PREFIX, testPrefix); + paths = PolarisResourcePaths.forCatalogProperties(properties); + } + + @Test + public void testGenericTablesPath() { + Namespace ns = Namespace.of("ns1", "ns2"); + String genericTablesPath = paths.genericTables(ns); + String expectedPath = + String.format("polaris/v1/%s/namespaces/%s/generic-tables", testPrefix, "ns1%1Fns2"); + Assertions.assertThat(genericTablesPath).isEqualTo(expectedPath); + } + + @Test + public void testGenericTablePath() { + Namespace ns = Namespace.of("ns1"); + TableIdentifier ident = TableIdentifier.of(ns, "test-table"); + String genericTablePath = paths.genericTable(ident); + String expectedPath = + String.format( + "polaris/v1/%s/namespaces/%s/generic-tables/%s", testPrefix, "ns1", "test-table"); + Assertions.assertThat(genericTablePath).isEqualTo(expectedPath); + } +} diff --git a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/GetConfigTest.java b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/GetConfigTest.java new file mode 100644 index 000000000..5e77d6aea --- /dev/null +++ b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/catalog/GetConfigTest.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.polaris.service.quarkus.catalog; + +import static jakarta.ws.rs.core.Response.Status.CREATED; +import static org.assertj.core.api.Assertions.assertThat; + +import io.quarkus.test.junit.QuarkusTest; +import jakarta.ws.rs.core.Response; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.apache.iceberg.rest.responses.ConfigResponse; +import org.apache.polaris.core.admin.model.*; +import org.apache.polaris.core.rest.PolarisEndpoints; +import org.apache.polaris.service.TestServices; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +@QuarkusTest +public class GetConfigTest { + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testGetConfig(boolean enableGenericTable) { + TestServices services = + TestServices.builder().config(Map.of("ENABLE_GENERIC_TABLES", enableGenericTable)).build(); + + FileStorageConfigInfo fileStorage = + FileStorageConfigInfo.builder(StorageConfigInfo.StorageTypeEnum.FILE) + .setAllowedLocations(List.of("file://")) + .build(); + String catalogName = "test-catalog-" + UUID.randomUUID(); + Catalog catalog = + PolarisCatalog.builder() + .setType(Catalog.TypeEnum.INTERNAL) + .setName(catalogName) + .setProperties(new CatalogProperties("file:///tmp/path/to/data")) + .setStorageConfigInfo(fileStorage) + .build(); + + Response response = + services + .catalogsApi() + .createCatalog( + new CreateCatalogRequest(catalog), + services.realmContext(), + services.securityContext()); + assertThat(response.getStatus()).isEqualTo(CREATED.getStatusCode()); + + response = + services + .restConfigurationApi() + .getConfig(catalogName, services.realmContext(), services.securityContext()); + ConfigResponse configResponse = response.readEntity(ConfigResponse.class); + assertThat(configResponse.overrides()).contains(Map.entry("prefix", catalogName)); + if (enableGenericTable) { + assertThat(configResponse.endpoints()).contains(PolarisEndpoints.V1_CREATE_GENERIC_TABLE); + assertThat(configResponse.endpoints()).contains(PolarisEndpoints.V1_DELETE_GENERIC_TABLE); + assertThat(configResponse.endpoints()).contains(PolarisEndpoints.V1_LIST_GENERIC_TABLES); + assertThat(configResponse.endpoints()).contains(PolarisEndpoints.V1_LOAD_GENERIC_TABLE); + } else { + assertThat(configResponse.endpoints()) + .doesNotContain(PolarisEndpoints.V1_CREATE_GENERIC_TABLE); + assertThat(configResponse.endpoints()) + .doesNotContain(PolarisEndpoints.V1_DELETE_GENERIC_TABLE); + assertThat(configResponse.endpoints()) + .doesNotContain(PolarisEndpoints.V1_LIST_GENERIC_TABLES); + assertThat(configResponse.endpoints()).doesNotContain(PolarisEndpoints.V1_LOAD_GENERIC_TABLE); + } + } +} diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java b/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java index ccb6b44f5..9bab20586 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/iceberg/IcebergCatalogAdapter.java @@ -67,6 +67,7 @@ import org.apache.polaris.core.persistence.PolarisMetaStoreManager; import org.apache.polaris.core.persistence.ResolvedPolarisEntity; import org.apache.polaris.core.persistence.resolver.Resolver; import org.apache.polaris.core.persistence.resolver.ResolverStatus; +import org.apache.polaris.core.rest.PolarisEndpoints; import org.apache.polaris.service.catalog.AccessDelegationMode; import org.apache.polaris.service.catalog.CatalogPrefixParser; import org.apache.polaris.service.catalog.api.IcebergRestCatalogApiService; @@ -723,6 +724,7 @@ public class IcebergCatalogAdapter .addAll(DEFAULT_ENDPOINTS) .addAll(VIEW_ENDPOINTS) .addAll(COMMIT_ENDPOINT) + .addAll(PolarisEndpoints.getSupportedGenericTableEndpoints(callContext)) .build()) .build()) .build(); diff --git a/service/common/src/testFixtures/java/org/apache/polaris/service/TestServices.java b/service/common/src/testFixtures/java/org/apache/polaris/service/TestServices.java index c763af661..d55021792 100644 --- a/service/common/src/testFixtures/java/org/apache/polaris/service/TestServices.java +++ b/service/common/src/testFixtures/java/org/apache/polaris/service/TestServices.java @@ -46,7 +46,7 @@ import org.apache.polaris.service.admin.PolarisServiceImpl; import org.apache.polaris.service.admin.api.PolarisCatalogsApi; import org.apache.polaris.service.catalog.DefaultCatalogPrefixParser; import org.apache.polaris.service.catalog.api.IcebergRestCatalogApi; -import org.apache.polaris.service.catalog.api.IcebergRestCatalogApiService; +import org.apache.polaris.service.catalog.api.IcebergRestConfigurationApi; import org.apache.polaris.service.catalog.iceberg.IcebergCatalogAdapter; import org.apache.polaris.service.catalog.io.FileIOFactory; import org.apache.polaris.service.catalog.io.MeasuredFileIOFactory; @@ -64,6 +64,7 @@ import software.amazon.awssdk.services.sts.StsClient; public record TestServices( PolarisCatalogsApi catalogsApi, IcebergRestCatalogApi restApi, + IcebergRestConfigurationApi restConfigurationApi, PolarisConfigurationStore configurationStore, PolarisDiagnostics polarisDiagnostics, RealmEntityManagerFactory entityManagerFactory, @@ -170,7 +171,7 @@ public record TestServices( new PolarisCallContextCatalogFactory( realmEntityManagerFactory, metaStoreManagerFactory, taskExecutor, fileIOFactory); - IcebergRestCatalogApiService service = + IcebergCatalogAdapter service = new IcebergCatalogAdapter( realmContext, callContext, @@ -181,6 +182,7 @@ public record TestServices( new DefaultCatalogPrefixParser()); IcebergRestCatalogApi restApi = new IcebergRestCatalogApi(service); + IcebergRestConfigurationApi restConfigurationApi = new IcebergRestConfigurationApi(service); CreatePrincipalResult createdPrincipal = metaStoreManager.createPrincipal( @@ -225,6 +227,7 @@ public record TestServices( return new TestServices( catalogsApi, restApi, + restConfigurationApi, configurationStore, polarisDiagnostics, realmEntityManagerFactory,