From 951ac7a0e93894ffc8dfa238ab77fd47a138fee4 Mon Sep 17 00:00:00 2001 From: Honah J Date: Mon, 21 Apr 2025 13:11:09 -0700 Subject: [PATCH 01/12] GetApplicablePolicies auth --- .../catalog/policy/PolicyCatalogHandler.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java index f4dea27b43..21b12cba91 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java @@ -320,4 +320,47 @@ private void throwNotFoundExceptionIfFailToResolve( } } } + + private void authorizeGetApplicablePoliciesOperationOrThrow( + @Nullable Namespace namespace, @Nullable String targetName) { + if (namespace == null || namespace.isEmpty()) { + // catalog + PolarisAuthorizableOperation op = + PolarisAuthorizableOperation.GET_APPLICABLE_POLICIES_ON_CATALOG; + authorizeBasicCatalogOperationOrThrow(op); + } else if (Strings.isNullOrEmpty(targetName)) { + // namespace + PolarisAuthorizableOperation op = + PolarisAuthorizableOperation.GET_APPLICABLE_POLICIES_ON_NAMESPACE; + authorizeBasicNamespaceOperationOrThrow(op, namespace); + } else { + // table + TableIdentifier tableIdentifier = TableIdentifier.of(namespace, targetName); + PolarisAuthorizableOperation op = + PolarisAuthorizableOperation.GET_APPLICABLE_POLICIES_ON_TABLE; + // only Iceberg tables are supported + authorizeBasicTableLikeOperationOrThrow( + op, PolarisEntitySubType.ICEBERG_TABLE, tableIdentifier); + } + } + + private void authorizeBasicCatalogOperationOrThrow(PolarisAuthorizableOperation op) { + resolutionManifest = + entityManager.prepareResolutionManifest(callContext, securityContext, catalogName); + resolutionManifest.resolveAll(); + + PolarisResolvedPathWrapper targetCatalog = + resolutionManifest.getResolvedReferenceCatalogEntity(); + if (targetCatalog == null) { + throw new NotFoundException("Catalog not found"); + } + authorizer.authorizeOrThrow( + authenticatedPrincipal, + resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(), + op, + targetCatalog, + null /* secondary */); + + initializeCatalog(); + } } From 1761c24e72e591a0cf4a767cfe5a152a3d7d25c6 Mon Sep 17 00:00:00 2001 From: Honah J Date: Mon, 21 Apr 2025 17:22:57 -0700 Subject: [PATCH 02/12] Adapter + Integration Test Framework --- .../polaris/service/it/env/PolarisClient.java | 9 + .../polaris/service/it/env/PolicyApi.java | 196 ++++++++++++ .../PolarisPolicyServiceIntegrationTest.java | 291 ++++++++++++++++++ .../quarkus/it/QuarkusPolicyServiceIT.java | 10 +- .../QuarkusPolicyServiceIntegrationTest.java | 39 +++ .../catalog/policy/PolicyCatalogAdapter.java | 239 ++++++++++++++ .../catalog/policy/PolicyCatalogHandler.java | 10 +- 7 files changed, 788 insertions(+), 6 deletions(-) create mode 100644 integration-tests/src/main/java/org/apache/polaris/service/it/env/PolicyApi.java create mode 100644 integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisPolicyServiceIntegrationTest.java rename service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyServiceImpl.java => quarkus/service/src/intTest/java/org/apache/polaris/service/quarkus/it/QuarkusPolicyServiceIT.java (73%) create mode 100644 quarkus/service/src/test/java/org/apache/polaris/service/quarkus/it/QuarkusPolicyServiceIntegrationTest.java create mode 100644 service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java diff --git a/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisClient.java b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisClient.java index e18f8736b5..baec590f91 100644 --- a/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisClient.java +++ b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolarisClient.java @@ -116,6 +116,15 @@ public GenericTableApi genericTableApi(ClientCredentials credentials) { client, endpoints, obtainToken(credentials), endpoints.catalogApiEndpoint()); } + public PolicyApi policyApi(PrincipalWithCredentials principal) { + return new PolicyApi(client, endpoints, obtainToken(principal), endpoints.catalogApiEndpoint()); + } + + public PolicyApi policyApi(ClientCredentials credentials) { + return new PolicyApi( + client, endpoints, obtainToken(credentials), endpoints.catalogApiEndpoint()); + } + /** * Requests an access token from the Polaris server for the client ID/secret pair that is part of * the given principal data object. diff --git a/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolicyApi.java b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolicyApi.java new file mode 100644 index 0000000000..51291cfe7a --- /dev/null +++ b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolicyApi.java @@ -0,0 +1,196 @@ +/* + * 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.it.env; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Response; +import java.net.URI; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.iceberg.catalog.Namespace; +import org.apache.iceberg.rest.RESTUtil; +import org.apache.polaris.core.policy.PolicyType; +import org.apache.polaris.service.types.ApplicablePolicy; +import org.apache.polaris.service.types.AttachPolicyRequest; +import org.apache.polaris.service.types.CreatePolicyRequest; +import org.apache.polaris.service.types.DetachPolicyRequest; +import org.apache.polaris.service.types.GetApplicablePoliciesResponse; +import org.apache.polaris.service.types.ListPoliciesResponse; +import org.apache.polaris.service.types.LoadPolicyResponse; +import org.apache.polaris.service.types.Policy; +import org.apache.polaris.service.types.PolicyAttachmentTarget; +import org.apache.polaris.service.types.PolicyIdentifier; +import org.apache.polaris.service.types.UpdatePolicyRequest; +import org.assertj.core.api.Assertions; + +public class PolicyApi extends RestApi { + PolicyApi(Client client, PolarisApiEndpoints endpoints, String authToken, URI uri) { + super(client, endpoints, authToken, uri); + } + + public void purge(String catalog, Namespace ns) { + listPolicies(catalog, ns).forEach(t -> dropPolicy(catalog, t)); + } + + public List listPolicies(String catalog, Namespace namespace) { + return listPolicies(catalog, namespace, null); + } + + public List listPolicies(String catalog, Namespace namespace, PolicyType type) { + String ns = RESTUtil.encodeNamespace(namespace); + Map queryParams = new HashMap<>(); + if (type != null) { + queryParams.put("policyType", type.getName()); + } + try (Response res = + request( + "polaris/v1/{cat}/namespaces/{ns}/policies", + Map.of("cat", catalog, "ns", ns), + queryParams) + .get()) { + Assertions.assertThat(res.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + return res.readEntity(ListPoliciesResponse.class).getIdentifiers().stream().toList(); + } + } + + public void dropPolicy(String catalog, PolicyIdentifier policyIdentifier) { + String ns = RESTUtil.encodeNamespace(policyIdentifier.getNamespace()); + try (Response res = + request( + "polaris/v1/{cat}/namespaces/{ns}/policies/{policy}", + Map.of("cat", catalog, "ns", ns, "policy", policyIdentifier.getName())) + .delete()) { + Assertions.assertThat(res.getStatus()).isEqualTo(Response.Status.NO_CONTENT.getStatusCode()); + } + } + + public Policy loadPolicy(String catalog, PolicyIdentifier policyIdentifier) { + String ns = RESTUtil.encodeNamespace(policyIdentifier.getNamespace()); + try (Response res = + request( + "polaris/v1/{cat}/namespaces/{ns}/policies/{policy}", + Map.of("cat", catalog, "ns", ns, "policy", policyIdentifier.getName())) + .get()) { + Assertions.assertThat(res.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + return res.readEntity(LoadPolicyResponse.class).getPolicy(); + } + } + + public Policy createPolicy( + String catalog, + PolicyIdentifier policyIdentifier, + PolicyType policyType, + String content, + String description) { + String ns = RESTUtil.encodeNamespace(policyIdentifier.getNamespace()); + CreatePolicyRequest request = + CreatePolicyRequest.builder() + .setType(policyType.getName()) + .setName(policyIdentifier.getName()) + .setDescription(description) + .setContent(content) + .build(); + try (Response res = + request("polaris/v1/{cat}/namespaces/{ns}/policies", Map.of("cat", catalog, "ns", ns)) + .post(Entity.json(request))) { + Assertions.assertThat(res.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + return res.readEntity(LoadPolicyResponse.class).getPolicy(); + } + } + + public Policy updatePolicy( + String catalog, + PolicyIdentifier policyIdentifier, + String newContent, + String newDescription, + int currentPolicyVersion) { + String ns = RESTUtil.encodeNamespace(policyIdentifier.getNamespace()); + UpdatePolicyRequest request = + UpdatePolicyRequest.builder() + .setContent(newContent) + .setDescription(newDescription) + .setCurrentPolicyVersion(currentPolicyVersion) + .build(); + try (Response res = + request( + "polaris/v1/{cat}/namespaces/{ns}/policies/{policy}", + Map.of("cat", catalog, "ns", ns, "policy", policyIdentifier.getName())) + .put(Entity.json(request))) { + Assertions.assertThat(res.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + return res.readEntity(LoadPolicyResponse.class).getPolicy(); + } + } + + public void attachPolicy( + String catalog, + PolicyIdentifier policyIdentifier, + PolicyAttachmentTarget target, + Map parameters) { + String ns = RESTUtil.encodeNamespace(policyIdentifier.getNamespace()); + AttachPolicyRequest request = + AttachPolicyRequest.builder().setTarget(target).setParameters(parameters).build(); + try (Response res = + request( + "polaris/v1/{cat}/namespaces/{ns}/policies/{policy}/mappings", + Map.of("cat", catalog, "ns", ns, "policy", policyIdentifier.getName())) + .put(Entity.json(request))) { + Assertions.assertThat(res.getStatus()).isEqualTo(Response.Status.NO_CONTENT.getStatusCode()); + } + } + + public void detachPolicy( + String catalog, + PolicyIdentifier policyIdentifier, + PolicyAttachmentTarget target, + Map parameters) { + String ns = RESTUtil.encodeNamespace(policyIdentifier.getNamespace()); + DetachPolicyRequest request = DetachPolicyRequest.builder().setTarget(target).build(); + try (Response res = + request( + "polaris/v1/{cat}/namespaces/{ns}/policies/{policy}/mappings", + Map.of("cat", catalog, "ns", ns, "policy", policyIdentifier.getName())) + .post(Entity.json(request))) { + Assertions.assertThat(res.getStatus()).isEqualTo(Response.Status.NO_CONTENT.getStatusCode()); + } + } + + public List getApplicablePolicies( + String catalog, Namespace namespace, String targetName, PolicyType policyType) { + String ns = namespace != null ? RESTUtil.encodeNamespace(namespace) : null; + Map queryParams = new HashMap<>(); + if (ns != null) { + queryParams.put("namespace", ns); + } + if (targetName != null) { + queryParams.put("target-name", targetName); + } + if (policyType != null) { + queryParams.put("policyType", policyType.getName()); + } + + try (Response res = + request("polaris/v1/policies/applicable-policies", Map.of(), queryParams).get()) { + Assertions.assertThat(res.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); + return res.readEntity(GetApplicablePoliciesResponse.class).getApplicablePolicies().stream() + .toList(); + } + } +} 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 new file mode 100644 index 0000000000..896efafceb --- /dev/null +++ b/integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisPolicyServiceIntegrationTest.java @@ -0,0 +1,291 @@ +/* + * 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.it.test; + +import static org.apache.polaris.service.it.env.PolarisClient.polarisClient; + +import com.google.common.collect.ImmutableMap; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; +import java.net.URI; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.apache.iceberg.catalog.Namespace; +import org.apache.iceberg.rest.RESTCatalog; +import org.apache.polaris.core.admin.model.AwsStorageConfigInfo; +import org.apache.polaris.core.admin.model.Catalog; +import org.apache.polaris.core.admin.model.CatalogGrant; +import org.apache.polaris.core.admin.model.CatalogPrivilege; +import org.apache.polaris.core.admin.model.CatalogProperties; +import org.apache.polaris.core.admin.model.CatalogRole; +import org.apache.polaris.core.admin.model.FileStorageConfigInfo; +import org.apache.polaris.core.admin.model.GrantResource; +import org.apache.polaris.core.admin.model.PolarisCatalog; +import org.apache.polaris.core.admin.model.PrincipalWithCredentials; +import org.apache.polaris.core.admin.model.StorageConfigInfo; +import org.apache.polaris.core.entity.CatalogEntity; +import org.apache.polaris.core.policy.PredefinedPolicyTypes; +import org.apache.polaris.service.it.env.CatalogApi; +import org.apache.polaris.service.it.env.ClientCredentials; +import org.apache.polaris.service.it.env.IcebergHelper; +import org.apache.polaris.service.it.env.IntegrationTestsHelper; +import org.apache.polaris.service.it.env.ManagementApi; +import org.apache.polaris.service.it.env.PolarisApiEndpoints; +import org.apache.polaris.service.it.env.PolarisClient; +import org.apache.polaris.service.it.env.PolicyApi; +import org.apache.polaris.service.it.ext.PolarisIntegrationTestExtension; +import org.apache.polaris.service.types.Policy; +import org.apache.polaris.service.types.PolicyIdentifier; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +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; + +@ExtendWith(PolarisIntegrationTestExtension.class) +public class PolarisPolicyServiceIntegrationTest { + + private static final String TEST_ROLE_ARN = + Optional.ofNullable(System.getenv("INTEGRATION_TEST_ROLE_ARN")) + .orElse("arn:aws:iam::123456789012:role/my-role"); + + private static URI s3BucketBase; + private static URI externalCatalogBase; + + protected static final String VIEW_QUERY = "select * from ns1.layer1_table"; + private static String principalRoleName; + private static ClientCredentials adminCredentials; + private static PrincipalWithCredentials principalCredentials; + private static PolarisApiEndpoints endpoints; + private static PolarisClient client; + private static ManagementApi managementApi; + private static CatalogApi catalogApi; + private static PolicyApi policyApi; + + private RESTCatalog restCatalog; + private String currentCatalogName; + + private final String catalogBaseLocation = + s3BucketBase + "/" + System.getenv("USER") + "/path/to/data"; + + private static final String[] DEFAULT_CATALOG_PROPERTIES = { + "allow.unstructured.table.location", "true", + "allow.external.table.location", "true" + }; + + @Retention(RetentionPolicy.RUNTIME) + private @interface CatalogConfig { + Catalog.TypeEnum value() default Catalog.TypeEnum.INTERNAL; + + String[] properties() default { + "allow.unstructured.table.location", "true", + "allow.external.table.location", "true" + }; + } + + @Retention(RetentionPolicy.RUNTIME) + private @interface RestCatalogConfig { + String[] value() default {}; + } + + @BeforeAll + public static void setup( + PolarisApiEndpoints apiEndpoints, ClientCredentials credentials, @TempDir Path tempDir) { + adminCredentials = credentials; + endpoints = apiEndpoints; + client = polarisClient(endpoints); + managementApi = client.managementApi(credentials); + String principalName = client.newEntityName("snowman-rest"); + principalRoleName = client.newEntityName("rest-admin"); + principalCredentials = managementApi.createPrincipalWithRole(principalName, principalRoleName); + catalogApi = client.catalogApi(principalCredentials); + URI testRootUri = IntegrationTestsHelper.getTemporaryDirectory(tempDir); + s3BucketBase = testRootUri.resolve("my-bucket"); + externalCatalogBase = testRootUri.resolve("external-catalog"); + + policyApi = client.policyApi(principalCredentials); + } + + @AfterAll + public static void close() throws Exception { + client.close(); + } + + @BeforeEach + public void before(TestInfo testInfo) { + String principalName = "snowman-rest-" + UUID.randomUUID(); + principalRoleName = "rest-admin-" + UUID.randomUUID(); + PrincipalWithCredentials principalCredentials = + managementApi.createPrincipalWithRole(principalName, principalRoleName); + + catalogApi = client.catalogApi(principalCredentials); + + Method method = testInfo.getTestMethod().orElseThrow(); + currentCatalogName = client.newEntityName(method.getName()); + AwsStorageConfigInfo awsConfigModel = + AwsStorageConfigInfo.builder() + .setRoleArn(TEST_ROLE_ARN) + .setExternalId("externalId") + .setUserArn("a:user:arn") + .setStorageType(StorageConfigInfo.StorageTypeEnum.S3) + .setAllowedLocations(List.of("s3://my-old-bucket/path/to/data")) + .build(); + Optional catalogConfig = + Optional.ofNullable( + method.getAnnotation(PolarisPolicyServiceIntegrationTest.CatalogConfig.class)); + + CatalogProperties.Builder catalogPropsBuilder = CatalogProperties.builder(catalogBaseLocation); + String[] properties = + catalogConfig + .map(PolarisPolicyServiceIntegrationTest.CatalogConfig::properties) + .orElse(DEFAULT_CATALOG_PROPERTIES); + for (int i = 0; i < properties.length; i += 2) { + catalogPropsBuilder.addProperty(properties[i], properties[i + 1]); + } + if (!s3BucketBase.getScheme().equals("file")) { + catalogPropsBuilder.addProperty( + CatalogEntity.REPLACE_NEW_LOCATION_PREFIX_WITH_CATALOG_DEFAULT_KEY, "file:"); + } + Catalog catalog = + PolarisCatalog.builder() + .setType( + catalogConfig + .map(PolarisPolicyServiceIntegrationTest.CatalogConfig::value) + .orElse(Catalog.TypeEnum.INTERNAL)) + .setName(currentCatalogName) + .setProperties(catalogPropsBuilder.build()) + .setStorageConfigInfo( + s3BucketBase.getScheme().equals("file") + ? new FileStorageConfigInfo( + StorageConfigInfo.StorageTypeEnum.FILE, List.of("file://")) + : awsConfigModel) + .build(); + + managementApi.createCatalog(principalRoleName, catalog); + + Optional restCatalogConfig = + testInfo + .getTestMethod() + .flatMap( + m -> + Optional.ofNullable( + m.getAnnotation( + PolarisPolicyServiceIntegrationTest.RestCatalogConfig.class))); + ImmutableMap.Builder extraPropertiesBuilder = ImmutableMap.builder(); + restCatalogConfig.ifPresent( + config -> { + for (int i = 0; i < config.value().length; i += 2) { + extraPropertiesBuilder.put(config.value()[i], config.value()[i + 1]); + } + }); + + restCatalog = + IcebergHelper.restCatalog( + client, + endpoints, + principalCredentials, + currentCatalogName, + extraPropertiesBuilder.build()); + CatalogGrant catalogGrant = + new CatalogGrant(CatalogPrivilege.CATALOG_MANAGE_CONTENT, GrantResource.TypeEnum.CATALOG); + managementApi.createCatalogRole(currentCatalogName, "catalogrole1"); + managementApi.addGrant(currentCatalogName, "catalogrole1", catalogGrant); + CatalogRole catalogRole = managementApi.getCatalogRole(currentCatalogName, "catalogrole1"); + managementApi.grantCatalogRoleToPrincipalRole( + principalRoleName, currentCatalogName, catalogRole); + + policyApi = client.policyApi(principalCredentials); + } + + @AfterEach + public void cleanUp() { + client.cleanUp(adminCredentials); + } + + @Test + public void testCreatePolicy() { + Namespace NS1 = Namespace.of("ns1"); + restCatalog.createNamespace(NS1); + PolicyIdentifier identifier = new PolicyIdentifier(NS1, "testPolicy"); + String example_content = + "{\"version\":\"2025-02-03\",\"enable\":true,\"config\":{\"target_file_size_bytes\":134217728,\"compaction_strategy\":\"bin-pack\",\"max-concurrent-file-group-rewrites\":5,\"key1\":\"value1\"}}"; + Policy policy = + policyApi.createPolicy( + currentCatalogName, + identifier, + PredefinedPolicyTypes.DATA_COMPACTION, + example_content, + "test policy"); + + Assertions.assertThat(policy).isNotNull(); + Assertions.assertThat(policy.getName()).isEqualTo("testPolicy"); + Assertions.assertThat(policy.getDescription()).isEqualTo("test policy"); + Assertions.assertThat(policy.getPolicyType()) + .isEqualTo(PredefinedPolicyTypes.DATA_COMPACTION.getName()); + Assertions.assertThat(policy.getContent()).isEqualTo(example_content); + Assertions.assertThat(policy.getInheritable()) + .isEqualTo(PredefinedPolicyTypes.DATA_COMPACTION.isInheritable()); + Assertions.assertThat(policy.getVersion()).isEqualTo(0); + + Policy loadedPolicy = policyApi.loadPolicy(currentCatalogName, identifier); + Assertions.assertThat(loadedPolicy).isEqualTo(policy); + + policyApi.dropPolicy(currentCatalogName, identifier); + } + + @Test + public void testUpdatePolicy() { + Namespace NS1 = Namespace.of("ns1"); + restCatalog.createNamespace(NS1); + String example_content = + "{\"version\":\"2025-02-03\",\"enable\":true,\"config\":{\"target_file_size_bytes\":134217728,\"compaction_strategy\":\"bin-pack\",\"max-concurrent-file-group-rewrites\":5,\"key1\":\"value1\"}}"; + PolicyIdentifier identifier = new PolicyIdentifier(NS1, "testPolicy"); + policyApi.createPolicy( + currentCatalogName, + identifier, + PredefinedPolicyTypes.DATA_COMPACTION, + example_content, + "test policy"); + + String updatedContent = "{\"enable\":false}"; + String updatedDescription = "updated test policy"; + Policy updatedPolicy = + policyApi.updatePolicy( + currentCatalogName, identifier, updatedContent, updatedDescription, 0); + + Assertions.assertThat(updatedPolicy).isNotNull(); + Assertions.assertThat(updatedPolicy.getName()).isEqualTo("testPolicy"); + Assertions.assertThat(updatedPolicy.getDescription()).isEqualTo(updatedDescription); + Assertions.assertThat(updatedPolicy.getPolicyType()) + .isEqualTo(PredefinedPolicyTypes.DATA_COMPACTION.getName()); + Assertions.assertThat(updatedPolicy.getContent()).isEqualTo(updatedContent); + Assertions.assertThat(updatedPolicy.getInheritable()) + .isEqualTo(PredefinedPolicyTypes.DATA_COMPACTION.isInheritable()); + Assertions.assertThat(updatedPolicy.getVersion()).isEqualTo(1); + + policyApi.dropPolicy(currentCatalogName, identifier); + } +} diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyServiceImpl.java b/quarkus/service/src/intTest/java/org/apache/polaris/service/quarkus/it/QuarkusPolicyServiceIT.java similarity index 73% rename from service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyServiceImpl.java rename to quarkus/service/src/intTest/java/org/apache/polaris/service/quarkus/it/QuarkusPolicyServiceIT.java index 55bb180006..68280a6728 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyServiceImpl.java +++ b/quarkus/service/src/intTest/java/org/apache/polaris/service/quarkus/it/QuarkusPolicyServiceIT.java @@ -16,10 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.catalog.policy; +package org.apache.polaris.service.quarkus.it; -import jakarta.enterprise.context.RequestScoped; -import org.apache.polaris.service.catalog.api.PolarisCatalogPolicyApiService; +import io.quarkus.test.junit.QuarkusIntegrationTest; +import org.apache.polaris.service.it.test.PolarisPolicyServiceIntegrationTest; -@RequestScoped -public class PolicyServiceImpl implements PolarisCatalogPolicyApiService {} +@QuarkusIntegrationTest +public class QuarkusPolicyServiceIT extends PolarisPolicyServiceIntegrationTest {} diff --git a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/it/QuarkusPolicyServiceIntegrationTest.java b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/it/QuarkusPolicyServiceIntegrationTest.java new file mode 100644 index 0000000000..8c920e08bb --- /dev/null +++ b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/it/QuarkusPolicyServiceIntegrationTest.java @@ -0,0 +1,39 @@ +/* + * 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.it; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import java.util.Map; +import org.apache.polaris.service.it.test.PolarisPolicyServiceIntegrationTest; + +@QuarkusTest +@TestProfile(QuarkusPolicyServiceIntegrationTest.Profile.class) +public class QuarkusPolicyServiceIntegrationTest extends PolarisPolicyServiceIntegrationTest { + + public static class Profile implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + return Map.of( + "polaris.features.defaults.\"ALLOW_EXTERNAL_CATALOG_CREDENTIAL_VENDING\"", "false"); + } + } +} diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java new file mode 100644 index 0000000000..bd74931f67 --- /dev/null +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java @@ -0,0 +1,239 @@ +/* + * 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.catalog.policy; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.SecurityContext; +import java.util.function.Function; +import org.apache.iceberg.catalog.Namespace; +import org.apache.iceberg.exceptions.NotAuthorizedException; +import org.apache.iceberg.rest.RESTUtil; +import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal; +import org.apache.polaris.core.auth.PolarisAuthorizer; +import org.apache.polaris.core.context.CallContext; +import org.apache.polaris.core.context.RealmContext; +import org.apache.polaris.core.persistence.PolarisEntityManager; +import org.apache.polaris.core.persistence.PolarisMetaStoreManager; +import org.apache.polaris.core.policy.PolicyType; +import org.apache.polaris.service.catalog.CatalogPrefixParser; +import org.apache.polaris.service.catalog.api.PolarisCatalogPolicyApiService; +import org.apache.polaris.service.catalog.common.CatalogAdapter; +import org.apache.polaris.service.types.AttachPolicyRequest; +import org.apache.polaris.service.types.CreatePolicyRequest; +import org.apache.polaris.service.types.DetachPolicyRequest; +import org.apache.polaris.service.types.PolicyIdentifier; +import org.apache.polaris.service.types.UpdatePolicyRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@RequestScoped +public class PolicyCatalogAdapter implements PolarisCatalogPolicyApiService, CatalogAdapter { + private static final Logger LOGGER = LoggerFactory.getLogger(PolicyCatalogAdapter.class); + + private final RealmContext realmContext; + private final CallContext callContext; + private final PolarisEntityManager entityManager; + private final PolarisMetaStoreManager metaStoreManager; + private final PolarisAuthorizer polarisAuthorizer; + private final CatalogPrefixParser prefixParser; + + @Inject + public PolicyCatalogAdapter( + RealmContext realmContext, + CallContext callContext, + PolarisEntityManager entityManager, + PolarisMetaStoreManager metaStoreManager, + PolarisAuthorizer polarisAuthorizer, + CatalogPrefixParser prefixParser) { + this.realmContext = realmContext; + this.callContext = callContext; + this.entityManager = entityManager; + this.metaStoreManager = metaStoreManager; + this.polarisAuthorizer = polarisAuthorizer; + this.prefixParser = prefixParser; + } + + private Response withCatalog( + SecurityContext securityContext, + String prefix, + Function action) { + String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix); + try (PolicyCatalogHandler wrapper = newHandlerWrapper(securityContext, catalogName)) { + return action.apply(wrapper); + } catch (RuntimeException e) { + LOGGER.debug("RuntimeException while operating on policy catalog. Propagating to caller.", e); + throw e; + } catch (Exception e) { + LOGGER.error("Error while operating on policy catalog", e); + throw new RuntimeException(e); + } + } + + private PolicyCatalogHandler newHandlerWrapper( + SecurityContext securityContext, String catalogName) { + var authenticatedPrincipal = (AuthenticatedPolarisPrincipal) securityContext.getUserPrincipal(); + if (authenticatedPrincipal == null) { + throw new NotAuthorizedException("Failed to find authenticatedPrincipal in SecurityContext"); + } + + return new PolicyCatalogHandler( + callContext, + entityManager, + metaStoreManager, + securityContext, + catalogName, + polarisAuthorizer); + } + + @Override + public Response createPolicy( + String prefix, + String namespace, + CreatePolicyRequest createPolicyRequest, + RealmContext realmContext, + SecurityContext securityContext) { + Namespace ns = decodeNamespace(namespace); + return withCatalog( + securityContext, + prefix, + catalog -> Response.ok(catalog.createPolicy(ns, createPolicyRequest)).build()); + } + + @Override + public Response listPolicies( + String prefix, + String namespace, + String pageToken, + Integer pageSize, + String policyType, + RealmContext realmContext, + SecurityContext securityContext) { + Namespace ns = decodeNamespace(namespace); + PolicyType type = PolicyType.fromName(RESTUtil.decodeString(policyType)); + return withCatalog( + securityContext, prefix, catalog -> Response.ok(catalog.listPolicies(ns, type)).build()); + } + + @Override + public Response loadPolicy( + String prefix, + String namespace, + String policyName, + RealmContext realmContext, + SecurityContext securityContext) { + Namespace ns = decodeNamespace(namespace); + PolicyIdentifier identifier = new PolicyIdentifier(ns, RESTUtil.decodeString(policyName)); + return withCatalog( + securityContext, prefix, catalog -> Response.ok(catalog.loadPolicy(identifier)).build()); + } + + @Override + public Response updatePolicy( + String prefix, + String namespace, + String policyName, + UpdatePolicyRequest updatePolicyRequest, + RealmContext realmContext, + SecurityContext securityContext) { + Namespace ns = decodeNamespace(namespace); + PolicyIdentifier identifier = new PolicyIdentifier(ns, RESTUtil.decodeString(policyName)); + return withCatalog( + securityContext, + prefix, + catalog -> Response.ok(catalog.updatePolicy(identifier, updatePolicyRequest)).build()); + } + + @Override + public Response dropPolicy( + String prefix, + String namespace, + String policyName, + Boolean detachAll, + RealmContext realmContext, + SecurityContext securityContext) { + Namespace ns = decodeNamespace(namespace); + PolicyIdentifier identifier = new PolicyIdentifier(ns, RESTUtil.decodeString(policyName)); + return withCatalog( + securityContext, + prefix, + catalog -> { + catalog.dropPolicy(identifier, detachAll != null && detachAll); + return Response.status(Response.Status.NO_CONTENT).build(); + }); + } + + @Override + public Response attachPolicy( + String prefix, + String namespace, + String policyName, + AttachPolicyRequest attachPolicyRequest, + RealmContext realmContext, + SecurityContext securityContext) { + Namespace ns = decodeNamespace(namespace); + PolicyIdentifier identifier = new PolicyIdentifier(ns, RESTUtil.decodeString(policyName)); + return withCatalog( + securityContext, + prefix, + catalog -> { + catalog.attachPolicy(identifier, attachPolicyRequest); + return Response.status(Response.Status.NO_CONTENT).build(); + }); + } + + @Override + public Response detachPolicy( + String prefix, + String namespace, + String policyName, + DetachPolicyRequest detachPolicyRequest, + RealmContext realmContext, + SecurityContext securityContext) { + Namespace ns = decodeNamespace(namespace); + PolicyIdentifier identifier = new PolicyIdentifier(ns, RESTUtil.decodeString(policyName)); + return withCatalog( + securityContext, + prefix, + catalog -> { + catalog.detachPolicy(identifier, detachPolicyRequest); + return Response.status(Response.Status.NO_CONTENT).build(); + }); + } + + @Override + public Response getApplicablePolicies( + String prefix, + String pageToken, + Integer pageSize, + String namespace, + String targetName, + String policyType, + RealmContext realmContext, + SecurityContext securityContext) { + Namespace ns = decodeNamespace(namespace); + String target = RESTUtil.decodeString(targetName); + PolicyType type = PolicyType.fromName(RESTUtil.decodeString(policyType)); + return withCatalog( + securityContext, + prefix, + catalog -> Response.ok(catalog.getApplicablePolicies(ns, target, type)).build()); + } +} diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java index 21b12cba91..06d070b96e 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java @@ -21,6 +21,7 @@ import com.google.common.base.Strings; import jakarta.annotation.Nullable; import jakarta.ws.rs.core.SecurityContext; +import java.io.Closeable; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -53,7 +54,7 @@ import org.apache.polaris.service.types.PolicyIdentifier; import org.apache.polaris.service.types.UpdatePolicyRequest; -public class PolicyCatalogHandler extends CatalogHandler { +public class PolicyCatalogHandler extends CatalogHandler implements AutoCloseable { private PolarisMetaStoreManager metaStoreManager; @@ -363,4 +364,11 @@ private void authorizeBasicCatalogOperationOrThrow(PolarisAuthorizableOperation initializeCatalog(); } + + @Override + public void close() throws Exception { + if (policyCatalog instanceof Closeable closeable) { + closeable.close(); + } + } } From 1de4276528aa124f52e0d191579976e658ab617d Mon Sep 17 00:00:00 2001 From: Honah J Date: Wed, 23 Apr 2025 19:27:17 -0700 Subject: [PATCH 03/12] add policy integration tests --- .../polaris/service/it/env/PolicyApi.java | 8 +- .../PolarisPolicyServiceIntegrationTest.java | 195 +++++++++++++++--- .../catalog/policy/PolicyCatalogAdapter.java | 10 +- 3 files changed, 175 insertions(+), 38 deletions(-) diff --git a/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolicyApi.java b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolicyApi.java index 51291cfe7a..7597aaef3c 100644 --- a/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolicyApi.java +++ b/integration-tests/src/main/java/org/apache/polaris/service/it/env/PolicyApi.java @@ -157,10 +157,7 @@ public void attachPolicy( } public void detachPolicy( - String catalog, - PolicyIdentifier policyIdentifier, - PolicyAttachmentTarget target, - Map parameters) { + String catalog, PolicyIdentifier policyIdentifier, PolicyAttachmentTarget target) { String ns = RESTUtil.encodeNamespace(policyIdentifier.getNamespace()); DetachPolicyRequest request = DetachPolicyRequest.builder().setTarget(target).build(); try (Response res = @@ -187,7 +184,8 @@ public List getApplicablePolicies( } try (Response res = - request("polaris/v1/policies/applicable-policies", Map.of(), queryParams).get()) { + request("polaris/v1/{cat}/applicable-policies", Map.of("cat", catalog), queryParams) + .get()) { Assertions.assertThat(res.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); return res.readEntity(GetApplicablePoliciesResponse.class).getApplicablePolicies().stream() .toList(); 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 896efafceb..055fa411c9 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 @@ -26,11 +26,16 @@ import java.lang.reflect.Method; import java.net.URI; import java.nio.file.Path; +import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; +import org.apache.iceberg.Schema; import org.apache.iceberg.catalog.Namespace; +import org.apache.iceberg.catalog.TableIdentifier; import org.apache.iceberg.rest.RESTCatalog; +import org.apache.iceberg.types.Types; import org.apache.polaris.core.admin.model.AwsStorageConfigInfo; import org.apache.polaris.core.admin.model.Catalog; import org.apache.polaris.core.admin.model.CatalogGrant; @@ -42,9 +47,9 @@ import org.apache.polaris.core.admin.model.PolarisCatalog; import org.apache.polaris.core.admin.model.PrincipalWithCredentials; import org.apache.polaris.core.admin.model.StorageConfigInfo; +import org.apache.polaris.core.catalog.PolarisCatalogHelpers; import org.apache.polaris.core.entity.CatalogEntity; import org.apache.polaris.core.policy.PredefinedPolicyTypes; -import org.apache.polaris.service.it.env.CatalogApi; import org.apache.polaris.service.it.env.ClientCredentials; import org.apache.polaris.service.it.env.IcebergHelper; import org.apache.polaris.service.it.env.IntegrationTestsHelper; @@ -53,7 +58,9 @@ import org.apache.polaris.service.it.env.PolarisClient; import org.apache.polaris.service.it.env.PolicyApi; import org.apache.polaris.service.it.ext.PolarisIntegrationTestExtension; +import org.apache.polaris.service.types.ApplicablePolicy; import org.apache.polaris.service.types.Policy; +import org.apache.polaris.service.types.PolicyAttachmentTarget; import org.apache.polaris.service.types.PolicyIdentifier; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.AfterAll; @@ -72,17 +79,21 @@ public class PolarisPolicyServiceIntegrationTest { Optional.ofNullable(System.getenv("INTEGRATION_TEST_ROLE_ARN")) .orElse("arn:aws:iam::123456789012:role/my-role"); - private static URI s3BucketBase; - private static URI externalCatalogBase; + private static final String EXAMPLE_TABLE_MAINTENANCE_POLICY_CONTENT = "{\"enable\":true}"; + private static final Namespace NS1 = Namespace.of("NS1"); + private static final Namespace NS2 = Namespace.of("NS2"); + private static final PolicyIdentifier NS1_P1 = new PolicyIdentifier(NS1, "P1"); + private static final PolicyIdentifier NS1_P2 = new PolicyIdentifier(NS1, "P2"); + private static final PolicyIdentifier NS1_P3 = new PolicyIdentifier(NS1, "P3"); + private static final TableIdentifier NS2_T1 = TableIdentifier.of(NS2, "T1"); - protected static final String VIEW_QUERY = "select * from ns1.layer1_table"; + private static URI s3BucketBase; private static String principalRoleName; private static ClientCredentials adminCredentials; private static PrincipalWithCredentials principalCredentials; private static PolarisApiEndpoints endpoints; private static PolarisClient client; private static ManagementApi managementApi; - private static CatalogApi catalogApi; private static PolicyApi policyApi; private RESTCatalog restCatalog; @@ -121,10 +132,8 @@ public static void setup( String principalName = client.newEntityName("snowman-rest"); principalRoleName = client.newEntityName("rest-admin"); principalCredentials = managementApi.createPrincipalWithRole(principalName, principalRoleName); - catalogApi = client.catalogApi(principalCredentials); URI testRootUri = IntegrationTestsHelper.getTemporaryDirectory(tempDir); s3BucketBase = testRootUri.resolve("my-bucket"); - externalCatalogBase = testRootUri.resolve("external-catalog"); policyApi = client.policyApi(principalCredentials); } @@ -141,8 +150,6 @@ public void before(TestInfo testInfo) { PrincipalWithCredentials principalCredentials = managementApi.createPrincipalWithRole(principalName, principalRoleName); - catalogApi = client.catalogApi(principalCredentials); - Method method = testInfo.getTestMethod().orElseThrow(); currentCatalogName = client.newEntityName(method.getName()); AwsStorageConfigInfo awsConfigModel = @@ -227,57 +234,61 @@ public void cleanUp() { @Test public void testCreatePolicy() { - Namespace NS1 = Namespace.of("ns1"); restCatalog.createNamespace(NS1); - PolicyIdentifier identifier = new PolicyIdentifier(NS1, "testPolicy"); - String example_content = - "{\"version\":\"2025-02-03\",\"enable\":true,\"config\":{\"target_file_size_bytes\":134217728,\"compaction_strategy\":\"bin-pack\",\"max-concurrent-file-group-rewrites\":5,\"key1\":\"value1\"}}"; Policy policy = policyApi.createPolicy( currentCatalogName, - identifier, + NS1_P1, PredefinedPolicyTypes.DATA_COMPACTION, - example_content, + EXAMPLE_TABLE_MAINTENANCE_POLICY_CONTENT, "test policy"); Assertions.assertThat(policy).isNotNull(); - Assertions.assertThat(policy.getName()).isEqualTo("testPolicy"); + Assertions.assertThat(policy.getName()).isEqualTo("P1"); Assertions.assertThat(policy.getDescription()).isEqualTo("test policy"); Assertions.assertThat(policy.getPolicyType()) .isEqualTo(PredefinedPolicyTypes.DATA_COMPACTION.getName()); - Assertions.assertThat(policy.getContent()).isEqualTo(example_content); + Assertions.assertThat(policy.getContent()).isEqualTo(EXAMPLE_TABLE_MAINTENANCE_POLICY_CONTENT); Assertions.assertThat(policy.getInheritable()) .isEqualTo(PredefinedPolicyTypes.DATA_COMPACTION.isInheritable()); Assertions.assertThat(policy.getVersion()).isEqualTo(0); - Policy loadedPolicy = policyApi.loadPolicy(currentCatalogName, identifier); + Policy loadedPolicy = policyApi.loadPolicy(currentCatalogName, NS1_P1); Assertions.assertThat(loadedPolicy).isEqualTo(policy); - policyApi.dropPolicy(currentCatalogName, identifier); + policyApi.dropPolicy(currentCatalogName, NS1_P1); + } + + @Test + public void testDropPolicy() { + restCatalog.createNamespace(NS1); + policyApi.createPolicy( + currentCatalogName, + NS1_P1, + PredefinedPolicyTypes.DATA_COMPACTION, + EXAMPLE_TABLE_MAINTENANCE_POLICY_CONTENT, + "test policy"); + policyApi.dropPolicy(currentCatalogName, NS1_P1); + Assertions.assertThat(policyApi.listPolicies(currentCatalogName, NS1)).hasSize(0); } @Test public void testUpdatePolicy() { - Namespace NS1 = Namespace.of("ns1"); restCatalog.createNamespace(NS1); - String example_content = - "{\"version\":\"2025-02-03\",\"enable\":true,\"config\":{\"target_file_size_bytes\":134217728,\"compaction_strategy\":\"bin-pack\",\"max-concurrent-file-group-rewrites\":5,\"key1\":\"value1\"}}"; - PolicyIdentifier identifier = new PolicyIdentifier(NS1, "testPolicy"); policyApi.createPolicy( currentCatalogName, - identifier, + NS1_P1, PredefinedPolicyTypes.DATA_COMPACTION, - example_content, + EXAMPLE_TABLE_MAINTENANCE_POLICY_CONTENT, "test policy"); String updatedContent = "{\"enable\":false}"; String updatedDescription = "updated test policy"; Policy updatedPolicy = - policyApi.updatePolicy( - currentCatalogName, identifier, updatedContent, updatedDescription, 0); + policyApi.updatePolicy(currentCatalogName, NS1_P1, updatedContent, updatedDescription, 0); Assertions.assertThat(updatedPolicy).isNotNull(); - Assertions.assertThat(updatedPolicy.getName()).isEqualTo("testPolicy"); + Assertions.assertThat(updatedPolicy.getName()).isEqualTo("P1"); Assertions.assertThat(updatedPolicy.getDescription()).isEqualTo(updatedDescription); Assertions.assertThat(updatedPolicy.getPolicyType()) .isEqualTo(PredefinedPolicyTypes.DATA_COMPACTION.getName()); @@ -286,6 +297,132 @@ public void testUpdatePolicy() { .isEqualTo(PredefinedPolicyTypes.DATA_COMPACTION.isInheritable()); Assertions.assertThat(updatedPolicy.getVersion()).isEqualTo(1); - policyApi.dropPolicy(currentCatalogName, identifier); + policyApi.dropPolicy(currentCatalogName, NS1_P1); + } + + @Test + public void testListPolicies() { + restCatalog.createNamespace(NS1); + policyApi.createPolicy( + currentCatalogName, + NS1_P1, + PredefinedPolicyTypes.DATA_COMPACTION, + EXAMPLE_TABLE_MAINTENANCE_POLICY_CONTENT, + "test policy"); + policyApi.createPolicy( + currentCatalogName, + NS1_P2, + PredefinedPolicyTypes.METADATA_COMPACTION, + EXAMPLE_TABLE_MAINTENANCE_POLICY_CONTENT, + "test policy"); + + Assertions.assertThat(policyApi.listPolicies(currentCatalogName, NS1)) + .containsExactlyInAnyOrder(NS1_P1, NS1_P2); + Assertions.assertThat( + policyApi.listPolicies(currentCatalogName, NS1, PredefinedPolicyTypes.DATA_COMPACTION)) + .containsExactly(NS1_P1); + Assertions.assertThat( + policyApi.listPolicies( + currentCatalogName, NS1, PredefinedPolicyTypes.METADATA_COMPACTION)) + .containsExactly(NS1_P2); + + policyApi.dropPolicy(currentCatalogName, NS1_P1); + policyApi.dropPolicy(currentCatalogName, NS1_P2); + } + + @Test + public void testPolicyMapping() { + restCatalog.createNamespace(NS1); + restCatalog.createNamespace(NS2); + Policy p1 = + policyApi.createPolicy( + currentCatalogName, + NS1_P1, + PredefinedPolicyTypes.DATA_COMPACTION, + EXAMPLE_TABLE_MAINTENANCE_POLICY_CONTENT, + "test policy"); + Policy p2 = + policyApi.createPolicy( + currentCatalogName, + NS1_P2, + PredefinedPolicyTypes.METADATA_COMPACTION, + EXAMPLE_TABLE_MAINTENANCE_POLICY_CONTENT, + "test policy"); + Policy p3 = + policyApi.createPolicy( + currentCatalogName, + NS1_P3, + PredefinedPolicyTypes.ORPHAN_FILE_REMOVAL, + EXAMPLE_TABLE_MAINTENANCE_POLICY_CONTENT, + "test policy"); + + restCatalog + .buildTable( + NS2_T1, new Schema(Types.NestedField.of(1, true, "string", Types.StringType.get()))) + .create(); + + PolicyAttachmentTarget catalogTarget = + PolicyAttachmentTarget.builder().setType(PolicyAttachmentTarget.TypeEnum.CATALOG).build(); + PolicyAttachmentTarget namespaceTarget = + PolicyAttachmentTarget.builder() + .setType(PolicyAttachmentTarget.TypeEnum.NAMESPACE) + .setPath(Arrays.asList(NS2.levels())) + .build(); + PolicyAttachmentTarget tableTarget = + PolicyAttachmentTarget.builder() + .setType(PolicyAttachmentTarget.TypeEnum.TABLE_LIKE) + .setPath(PolarisCatalogHelpers.tableIdentifierToList(NS2_T1)) + .build(); + + policyApi.attachPolicy(currentCatalogName, NS1_P1, catalogTarget, Map.of()); + policyApi.attachPolicy(currentCatalogName, NS1_P2, namespaceTarget, Map.of()); + policyApi.attachPolicy(currentCatalogName, NS1_P3, tableTarget, Map.of()); + + List applicablePoliciesOnCatalog = + policyApi.getApplicablePolicies(currentCatalogName, null, null, null); + Assertions.assertThat(applicablePoliciesOnCatalog) + .containsExactly(policyToApplicablePolicy(p1, false, NS1)); + + List applicablePoliciesOnNamespace = + policyApi.getApplicablePolicies(currentCatalogName, NS2, null, null); + Assertions.assertThat(applicablePoliciesOnNamespace) + .containsExactlyInAnyOrder( + policyToApplicablePolicy(p1, true, NS1), policyToApplicablePolicy(p2, false, NS1)); + + List applicablePoliciesOnTable = + policyApi.getApplicablePolicies(currentCatalogName, NS2, NS2_T1.name(), null); + Assertions.assertThat(applicablePoliciesOnTable) + .containsExactlyInAnyOrder( + policyToApplicablePolicy(p1, true, NS1), + policyToApplicablePolicy(p2, true, NS1), + policyToApplicablePolicy(p3, false, NS1)); + + Assertions.assertThat( + policyApi.getApplicablePolicies( + currentCatalogName, NS2, NS2_T1.name(), PredefinedPolicyTypes.METADATA_COMPACTION)) + .containsExactlyInAnyOrder(policyToApplicablePolicy(p2, true, NS1)); + + policyApi.detachPolicy(currentCatalogName, NS1_P1, catalogTarget); + policyApi.detachPolicy(currentCatalogName, NS1_P2, namespaceTarget); + policyApi.detachPolicy(currentCatalogName, NS1_P3, tableTarget); + + policyApi.dropPolicy(currentCatalogName, NS1_P1); + policyApi.dropPolicy(currentCatalogName, NS1_P2); + policyApi.dropPolicy(currentCatalogName, NS1_P3); + + restCatalog.dropTable(NS2_T1); + } + + private static ApplicablePolicy policyToApplicablePolicy( + Policy policy, boolean inherited, Namespace parent) { + return new ApplicablePolicy( + policy.getPolicyType(), + policy.getInheritable(), + policy.getName(), + policy.getDescription(), + policy.getContent(), + policy.getVersion(), + inherited, + Arrays.asList(parent.levels())); } } diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java index bd74931f67..56f5d08e5e 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java @@ -127,7 +127,8 @@ public Response listPolicies( RealmContext realmContext, SecurityContext securityContext) { Namespace ns = decodeNamespace(namespace); - PolicyType type = PolicyType.fromName(RESTUtil.decodeString(policyType)); + PolicyType type = + policyType != null ? PolicyType.fromName(RESTUtil.decodeString(policyType)) : null; return withCatalog( securityContext, prefix, catalog -> Response.ok(catalog.listPolicies(ns, type)).build()); } @@ -228,9 +229,10 @@ public Response getApplicablePolicies( String policyType, RealmContext realmContext, SecurityContext securityContext) { - Namespace ns = decodeNamespace(namespace); - String target = RESTUtil.decodeString(targetName); - PolicyType type = PolicyType.fromName(RESTUtil.decodeString(policyType)); + Namespace ns = namespace != null ? decodeNamespace(namespace) : null; + String target = targetName != null ? RESTUtil.decodeString(targetName) : null; + PolicyType type = + policyType != null ? PolicyType.fromName(RESTUtil.decodeString(policyType)) : null; return withCatalog( securityContext, prefix, From 8dc8f23618bef4cd978d8e3a178182b604ea848c Mon Sep 17 00:00:00 2001 From: Honah J Date: Wed, 23 Apr 2025 19:31:35 -0700 Subject: [PATCH 04/12] add feature configuration for policy --- .../polaris/core/config/FeatureConfiguration.java | 7 +++++++ .../catalog/policy/PolicyCatalogHandler.java | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java b/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java index f857d03acd..ee663dbb19 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java @@ -227,4 +227,11 @@ public static void enforceFeatureEnabledOrThrow( + " to perform federation to remote catalogs.") .defaultValue(false) .buildFeatureConfiguration(); + + public static final FeatureConfiguration ENABLE_POLICY_STORE = + PolarisConfiguration.builder() + .key("ENABLE_POLICY_STORE") + .description("If true, the policy-store endpoints are enabled") + .defaultValue(true) + .buildFeatureConfiguration(); } diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java index 06d070b96e..cf91a4d6e0 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java @@ -33,6 +33,7 @@ import org.apache.polaris.core.auth.PolarisAuthorizableOperation; import org.apache.polaris.core.auth.PolarisAuthorizer; import org.apache.polaris.core.catalog.PolarisCatalogHelpers; +import org.apache.polaris.core.config.FeatureConfiguration; import org.apache.polaris.core.context.CallContext; import org.apache.polaris.core.entity.PolarisEntitySubType; import org.apache.polaris.core.entity.PolarisEntityType; @@ -73,6 +74,7 @@ public PolicyCatalogHandler( @Override protected void initializeCatalog() { + enforcePolicyStoreEnabledOrThrow(); this.policyCatalog = new PolicyCatalog(metaStoreManager, callContext, this.resolutionManifest); } @@ -365,6 +367,18 @@ private void authorizeBasicCatalogOperationOrThrow(PolarisAuthorizableOperation initializeCatalog(); } + public void enforcePolicyStoreEnabledOrThrow() { + boolean enabled = + callContext + .getPolarisCallContext() + .getConfigurationStore() + .getConfiguration( + callContext.getPolarisCallContext(), FeatureConfiguration.ENABLE_POLICY_STORE); + if (!enabled) { + throw new UnsupportedOperationException("Policy store support is not enabled"); + } + } + @Override public void close() throws Exception { if (policyCatalog instanceof Closeable closeable) { From de19daac1ad55813f0cda79e9f5860d76dd90af1 Mon Sep 17 00:00:00 2001 From: Honah J Date: Wed, 23 Apr 2025 19:44:37 -0700 Subject: [PATCH 05/12] Add policy endpoints to Iceberg catalog config list --- .../polaris/core/rest/PolarisEndpoints.java | 43 +++++++++++++++++++ .../core/rest/PolarisResourcePaths.java | 8 ++++ .../iceberg/IcebergCatalogAdapter.java | 1 + 3 files changed, 52 insertions(+) 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 index edbfb14149..add1dde5cd 100644 --- 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 @@ -42,6 +42,35 @@ public class PolarisEndpoints { .add(V1_LOAD_GENERIC_TABLE) .build(); + public static final Endpoint V1_LIST_POLICIES = + Endpoint.create("GET", PolarisResourcePaths.V1_POLICIES); + public static final Endpoint V1_CREATE_POLICY = + Endpoint.create("POST", PolarisResourcePaths.V1_POLICIES); + public static final Endpoint V1_LOAD_POLICY = + Endpoint.create("GET", PolarisResourcePaths.V1_POLICY); + public static final Endpoint V1_UPDATE_POLICY = + Endpoint.create("PUT", PolarisResourcePaths.V1_POLICY); + public static final Endpoint V1_DROP_POLICY = + Endpoint.create("DELETE", PolarisResourcePaths.V1_POLICY); + public static final Endpoint V1_ATTACH_POLICY = + Endpoint.create("PUT", PolarisResourcePaths.V1_POLICY_MAPPINGS); + public static final Endpoint V1_DETACH_POLICY = + Endpoint.create("POST", PolarisResourcePaths.V1_POLICY_MAPPINGS); + public static final Endpoint V1_GET_APPLICABLE_POLICIES = + Endpoint.create("GET", PolarisResourcePaths.V1_APPLICABLE_POLICIES); + + public static final Set POLICY_STORE_ENDPOINTS = + ImmutableSet.builder() + .add(V1_LIST_POLICIES) + .add(V1_CREATE_POLICY) + .add(V1_LOAD_POLICY) + .add(V1_UPDATE_POLICY) + .add(V1_DROP_POLICY) + .add(V1_ATTACH_POLICY) + .add(V1_DETACH_POLICY) + .add(V1_GET_APPLICABLE_POLICIES) + .build(); + /** * Get the generic table endpoints. Returns GENERIC_TABLE_ENDPOINTS if ENABLE_GENERIC_TABLES is * set to true, otherwise, returns an empty set. @@ -57,4 +86,18 @@ public static Set getSupportedGenericTableEndpoints(CallContext callCo return genericTableEnabled ? GENERIC_TABLE_ENDPOINTS : ImmutableSet.of(); } + + /** + * Get the policy store endpoints. Returns POLICY_ENDPOINTS if ENABLE_POLICY_STORE is set to true, + * otherwise, returns an empty set + */ + public static Set getSupportedPolicyEndpoints(CallContext callContext) { + boolean policyStoreEnabled = + callContext + .getPolarisCallContext() + .getConfigurationStore() + .getConfiguration( + callContext.getPolarisCallContext(), FeatureConfiguration.ENABLE_POLICY_STORE); + return policyStoreEnabled ? POLICY_STORE_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 index 159a1a0148..8a30d79624 100644 --- 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 @@ -34,6 +34,14 @@ public class PolarisResourcePaths { public static final String V1_GENERIC_TABLE = "polaris/v1/{prefix}/namespaces/{namespace}/generic-tables/{generic-table}"; + // Policy Store endpoints + public static final String V1_POLICIES = "/polaris/v1/{prefix}/namespaces/{namespace}/policies"; + public static final String V1_POLICY = + "/polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}"; + public static final String V1_POLICY_MAPPINGS = + "/polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}/mappings"; + public static final String V1_APPLICABLE_POLICIES = "/polaris/v1/{prefix}/applicable-policies"; + private final String prefix; public PolarisResourcePaths(String prefix) { 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 6895351395..e2dcefc0bc 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 @@ -724,6 +724,7 @@ public Response getConfig( .addAll(VIEW_ENDPOINTS) .addAll(COMMIT_ENDPOINT) .addAll(PolarisEndpoints.getSupportedGenericTableEndpoints(callContext)) + .addAll(PolarisEndpoints.getSupportedPolicyEndpoints(callContext)) .build()) .build()) .build(); From 72285c8bd10bb83d9b841557116e3b57e4ffa9d0 Mon Sep 17 00:00:00 2001 From: Honah J Date: Wed, 23 Apr 2025 19:49:39 -0700 Subject: [PATCH 06/12] fix merge conflict --- .../catalog/policy/PolicyCatalogHandler.java | 43 ------------------- 1 file changed, 43 deletions(-) diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java index cf91a4d6e0..c0b713dcca 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java @@ -324,49 +324,6 @@ private void throwNotFoundExceptionIfFailToResolve( } } - private void authorizeGetApplicablePoliciesOperationOrThrow( - @Nullable Namespace namespace, @Nullable String targetName) { - if (namespace == null || namespace.isEmpty()) { - // catalog - PolarisAuthorizableOperation op = - PolarisAuthorizableOperation.GET_APPLICABLE_POLICIES_ON_CATALOG; - authorizeBasicCatalogOperationOrThrow(op); - } else if (Strings.isNullOrEmpty(targetName)) { - // namespace - PolarisAuthorizableOperation op = - PolarisAuthorizableOperation.GET_APPLICABLE_POLICIES_ON_NAMESPACE; - authorizeBasicNamespaceOperationOrThrow(op, namespace); - } else { - // table - TableIdentifier tableIdentifier = TableIdentifier.of(namespace, targetName); - PolarisAuthorizableOperation op = - PolarisAuthorizableOperation.GET_APPLICABLE_POLICIES_ON_TABLE; - // only Iceberg tables are supported - authorizeBasicTableLikeOperationOrThrow( - op, PolarisEntitySubType.ICEBERG_TABLE, tableIdentifier); - } - } - - private void authorizeBasicCatalogOperationOrThrow(PolarisAuthorizableOperation op) { - resolutionManifest = - entityManager.prepareResolutionManifest(callContext, securityContext, catalogName); - resolutionManifest.resolveAll(); - - PolarisResolvedPathWrapper targetCatalog = - resolutionManifest.getResolvedReferenceCatalogEntity(); - if (targetCatalog == null) { - throw new NotFoundException("Catalog not found"); - } - authorizer.authorizeOrThrow( - authenticatedPrincipal, - resolutionManifest.getAllActivatedCatalogRoleAndPrincipalRoles(), - op, - targetCatalog, - null /* secondary */); - - initializeCatalog(); - } - public void enforcePolicyStoreEnabledOrThrow() { boolean enabled = callContext From cd8e1c785ef9273013e6adf590015a558b031d7d Mon Sep 17 00:00:00 2001 From: Honah J Date: Thu, 24 Apr 2025 11:57:48 -0500 Subject: [PATCH 07/12] fix reg test failure --- regtests/t_spark_sql/ref/spark_sql_basic.sh.ref | 2 +- regtests/t_spark_sql/ref/spark_sql_views.sh.ref | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/regtests/t_spark_sql/ref/spark_sql_basic.sh.ref b/regtests/t_spark_sql/ref/spark_sql_basic.sh.ref index eaf0e18a88..79b317fcf4 100755 --- a/regtests/t_spark_sql/ref/spark_sql_basic.sh.ref +++ b/regtests/t_spark_sql/ref/spark_sql_basic.sh.ref @@ -1,4 +1,4 @@ -{"defaults":{"default-base-location":"file:///tmp/spark_sql_s3_catalog"},"overrides":{"prefix":"spark_sql_basic_catalog"},"endpoints":["GET /v1/{prefix}/namespaces","GET /v1/{prefix}/namespaces/{namespace}","HEAD /v1/{prefix}/namespaces/{namespace}","POST /v1/{prefix}/namespaces","POST /v1/{prefix}/namespaces/{namespace}/properties","DELETE /v1/{prefix}/namespaces/{namespace}","GET /v1/{prefix}/namespaces/{namespace}/tables","GET /v1/{prefix}/namespaces/{namespace}/tables/{table}","HEAD /v1/{prefix}/namespaces/{namespace}/tables/{table}","POST /v1/{prefix}/namespaces/{namespace}/tables","POST /v1/{prefix}/namespaces/{namespace}/tables/{table}","DELETE /v1/{prefix}/namespaces/{namespace}/tables/{table}","POST /v1/{prefix}/tables/rename","POST /v1/{prefix}/namespaces/{namespace}/register","POST /v1/{prefix}/namespaces/{namespace}/tables/{table}/metrics","POST /v1/{prefix}/transactions/commit","GET /v1/{prefix}/namespaces/{namespace}/views","GET /v1/{prefix}/namespaces/{namespace}/views/{view}","HEAD /v1/{prefix}/namespaces/{namespace}/views/{view}","POST /v1/{prefix}/namespaces/{namespace}/views","POST /v1/{prefix}/namespaces/{namespace}/views/{view}","DELETE /v1/{prefix}/namespaces/{namespace}/views/{view}","POST /v1/{prefix}/views/rename","POST /v1/{prefix}/transactions/commit","GET polaris/v1/{prefix}/namespaces/{namespace}/generic-tables","POST polaris/v1/{prefix}/namespaces/{namespace}/generic-tables","DELETE polaris/v1/{prefix}/namespaces/{namespace}/generic-tables/{generic-table}","GET polaris/v1/{prefix}/namespaces/{namespace}/generic-tables/{generic-table}"]} +{"defaults":{"default-base-location":"file:///tmp/spark_sql_s3_catalog"},"overrides":{"prefix":"spark_sql_basic_catalog"},"endpoints":["GET /v1/{prefix}/namespaces","GET /v1/{prefix}/namespaces/{namespace}","HEAD /v1/{prefix}/namespaces/{namespace}","POST /v1/{prefix}/namespaces","POST /v1/{prefix}/namespaces/{namespace}/properties","DELETE /v1/{prefix}/namespaces/{namespace}","GET /v1/{prefix}/namespaces/{namespace}/tables","GET /v1/{prefix}/namespaces/{namespace}/tables/{table}","HEAD /v1/{prefix}/namespaces/{namespace}/tables/{table}","POST /v1/{prefix}/namespaces/{namespace}/tables","POST /v1/{prefix}/namespaces/{namespace}/tables/{table}","DELETE /v1/{prefix}/namespaces/{namespace}/tables/{table}","POST /v1/{prefix}/tables/rename","POST /v1/{prefix}/namespaces/{namespace}/register","POST /v1/{prefix}/namespaces/{namespace}/tables/{table}/metrics","POST /v1/{prefix}/transactions/commit","GET /v1/{prefix}/namespaces/{namespace}/views","GET /v1/{prefix}/namespaces/{namespace}/views/{view}","HEAD /v1/{prefix}/namespaces/{namespace}/views/{view}","POST /v1/{prefix}/namespaces/{namespace}/views","POST /v1/{prefix}/namespaces/{namespace}/views/{view}","DELETE /v1/{prefix}/namespaces/{namespace}/views/{view}","POST /v1/{prefix}/views/rename","POST /v1/{prefix}/transactions/commit","GET polaris/v1/{prefix}/namespaces/{namespace}/generic-tables","POST polaris/v1/{prefix}/namespaces/{namespace}/generic-tables","DELETE polaris/v1/{prefix}/namespaces/{namespace}/generic-tables/{generic-table}","GET polaris/v1/{prefix}/namespaces/{namespace}/generic-tables/{generic-table}","GET /polaris/v1/{prefix}/namespaces/{namespace}/policies","POST /polaris/v1/{prefix}/namespaces/{namespace}/policies","GET /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}","PUT /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}","DELETE /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}","PUT /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}/mappings","POST /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}/mappings","GET /polaris/v1/{prefix}/applicable-policies"]} Catalog created spark-sql (default)> use polaris; spark-sql ()> show namespaces; diff --git a/regtests/t_spark_sql/ref/spark_sql_views.sh.ref b/regtests/t_spark_sql/ref/spark_sql_views.sh.ref index ffae79311b..9bb78d644e 100755 --- a/regtests/t_spark_sql/ref/spark_sql_views.sh.ref +++ b/regtests/t_spark_sql/ref/spark_sql_views.sh.ref @@ -1,4 +1,4 @@ -{"defaults":{"default-base-location":"file:///tmp/spark_sql_s3_catalog"},"overrides":{"prefix":"spark_sql_views_catalog"},"endpoints":["GET /v1/{prefix}/namespaces","GET /v1/{prefix}/namespaces/{namespace}","HEAD /v1/{prefix}/namespaces/{namespace}","POST /v1/{prefix}/namespaces","POST /v1/{prefix}/namespaces/{namespace}/properties","DELETE /v1/{prefix}/namespaces/{namespace}","GET /v1/{prefix}/namespaces/{namespace}/tables","GET /v1/{prefix}/namespaces/{namespace}/tables/{table}","HEAD /v1/{prefix}/namespaces/{namespace}/tables/{table}","POST /v1/{prefix}/namespaces/{namespace}/tables","POST /v1/{prefix}/namespaces/{namespace}/tables/{table}","DELETE /v1/{prefix}/namespaces/{namespace}/tables/{table}","POST /v1/{prefix}/tables/rename","POST /v1/{prefix}/namespaces/{namespace}/register","POST /v1/{prefix}/namespaces/{namespace}/tables/{table}/metrics","POST /v1/{prefix}/transactions/commit","GET /v1/{prefix}/namespaces/{namespace}/views","GET /v1/{prefix}/namespaces/{namespace}/views/{view}","HEAD /v1/{prefix}/namespaces/{namespace}/views/{view}","POST /v1/{prefix}/namespaces/{namespace}/views","POST /v1/{prefix}/namespaces/{namespace}/views/{view}","DELETE /v1/{prefix}/namespaces/{namespace}/views/{view}","POST /v1/{prefix}/views/rename","POST /v1/{prefix}/transactions/commit","GET polaris/v1/{prefix}/namespaces/{namespace}/generic-tables","POST polaris/v1/{prefix}/namespaces/{namespace}/generic-tables","DELETE polaris/v1/{prefix}/namespaces/{namespace}/generic-tables/{generic-table}","GET polaris/v1/{prefix}/namespaces/{namespace}/generic-tables/{generic-table}"]} +{"defaults":{"default-base-location":"file:///tmp/spark_sql_s3_catalog"},"overrides":{"prefix":"spark_sql_views_catalog"},"endpoints":["GET /v1/{prefix}/namespaces","GET /v1/{prefix}/namespaces/{namespace}","HEAD /v1/{prefix}/namespaces/{namespace}","POST /v1/{prefix}/namespaces","POST /v1/{prefix}/namespaces/{namespace}/properties","DELETE /v1/{prefix}/namespaces/{namespace}","GET /v1/{prefix}/namespaces/{namespace}/tables","GET /v1/{prefix}/namespaces/{namespace}/tables/{table}","HEAD /v1/{prefix}/namespaces/{namespace}/tables/{table}","POST /v1/{prefix}/namespaces/{namespace}/tables","POST /v1/{prefix}/namespaces/{namespace}/tables/{table}","DELETE /v1/{prefix}/namespaces/{namespace}/tables/{table}","POST /v1/{prefix}/tables/rename","POST /v1/{prefix}/namespaces/{namespace}/register","POST /v1/{prefix}/namespaces/{namespace}/tables/{table}/metrics","POST /v1/{prefix}/transactions/commit","GET /v1/{prefix}/namespaces/{namespace}/views","GET /v1/{prefix}/namespaces/{namespace}/views/{view}","HEAD /v1/{prefix}/namespaces/{namespace}/views/{view}","POST /v1/{prefix}/namespaces/{namespace}/views","POST /v1/{prefix}/namespaces/{namespace}/views/{view}","DELETE /v1/{prefix}/namespaces/{namespace}/views/{view}","POST /v1/{prefix}/views/rename","POST /v1/{prefix}/transactions/commit","GET polaris/v1/{prefix}/namespaces/{namespace}/generic-tables","POST polaris/v1/{prefix}/namespaces/{namespace}/generic-tables","DELETE polaris/v1/{prefix}/namespaces/{namespace}/generic-tables/{generic-table}","GET polaris/v1/{prefix}/namespaces/{namespace}/generic-tables/{generic-table}","GET /polaris/v1/{prefix}/namespaces/{namespace}/policies","POST /polaris/v1/{prefix}/namespaces/{namespace}/policies","GET /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}","PUT /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}","DELETE /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}","PUT /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}/mappings","POST /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}/mappings","GET /polaris/v1/{prefix}/applicable-policies"]} Catalog created spark-sql (default)> use polaris; spark-sql ()> show namespaces; From e8ab7d8860aca8527e23d0c8b7ab31d186dbae7b Mon Sep 17 00:00:00 2001 From: Honah J Date: Thu, 24 Apr 2025 12:13:21 -0500 Subject: [PATCH 08/12] fix spark client reg test --- plugins/spark/v3.5/regtests/spark_sql.ref | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/spark/v3.5/regtests/spark_sql.ref b/plugins/spark/v3.5/regtests/spark_sql.ref index 5825d09318..525a705aec 100755 --- a/plugins/spark/v3.5/regtests/spark_sql.ref +++ b/plugins/spark/v3.5/regtests/spark_sql.ref @@ -1,4 +1,4 @@ -{"defaults":{"default-base-location":"file:///tmp/spark_catalog"},"overrides":{"prefix":"spark_sql_catalog"},"endpoints":["GET /v1/{prefix}/namespaces","GET /v1/{prefix}/namespaces/{namespace}","HEAD /v1/{prefix}/namespaces/{namespace}","POST /v1/{prefix}/namespaces","POST /v1/{prefix}/namespaces/{namespace}/properties","DELETE /v1/{prefix}/namespaces/{namespace}","GET /v1/{prefix}/namespaces/{namespace}/tables","GET /v1/{prefix}/namespaces/{namespace}/tables/{table}","HEAD /v1/{prefix}/namespaces/{namespace}/tables/{table}","POST /v1/{prefix}/namespaces/{namespace}/tables","POST /v1/{prefix}/namespaces/{namespace}/tables/{table}","DELETE /v1/{prefix}/namespaces/{namespace}/tables/{table}","POST /v1/{prefix}/tables/rename","POST /v1/{prefix}/namespaces/{namespace}/register","POST /v1/{prefix}/namespaces/{namespace}/tables/{table}/metrics","POST /v1/{prefix}/transactions/commit","GET /v1/{prefix}/namespaces/{namespace}/views","GET /v1/{prefix}/namespaces/{namespace}/views/{view}","HEAD /v1/{prefix}/namespaces/{namespace}/views/{view}","POST /v1/{prefix}/namespaces/{namespace}/views","POST /v1/{prefix}/namespaces/{namespace}/views/{view}","DELETE /v1/{prefix}/namespaces/{namespace}/views/{view}","POST /v1/{prefix}/views/rename","POST /v1/{prefix}/transactions/commit","GET polaris/v1/{prefix}/namespaces/{namespace}/generic-tables","POST polaris/v1/{prefix}/namespaces/{namespace}/generic-tables","DELETE polaris/v1/{prefix}/namespaces/{namespace}/generic-tables/{generic-table}","GET polaris/v1/{prefix}/namespaces/{namespace}/generic-tables/{generic-table}"]} +{"defaults":{"default-base-location":"file:///tmp/spark_catalog"},"overrides":{"prefix":"spark_sql_catalog"},"endpoints":["GET /v1/{prefix}/namespaces","GET /v1/{prefix}/namespaces/{namespace}","HEAD /v1/{prefix}/namespaces/{namespace}","POST /v1/{prefix}/namespaces","POST /v1/{prefix}/namespaces/{namespace}/properties","DELETE /v1/{prefix}/namespaces/{namespace}","GET /v1/{prefix}/namespaces/{namespace}/tables","GET /v1/{prefix}/namespaces/{namespace}/tables/{table}","HEAD /v1/{prefix}/namespaces/{namespace}/tables/{table}","POST /v1/{prefix}/namespaces/{namespace}/tables","POST /v1/{prefix}/namespaces/{namespace}/tables/{table}","DELETE /v1/{prefix}/namespaces/{namespace}/tables/{table}","POST /v1/{prefix}/tables/rename","POST /v1/{prefix}/namespaces/{namespace}/register","POST /v1/{prefix}/namespaces/{namespace}/tables/{table}/metrics","POST /v1/{prefix}/transactions/commit","GET /v1/{prefix}/namespaces/{namespace}/views","GET /v1/{prefix}/namespaces/{namespace}/views/{view}","HEAD /v1/{prefix}/namespaces/{namespace}/views/{view}","POST /v1/{prefix}/namespaces/{namespace}/views","POST /v1/{prefix}/namespaces/{namespace}/views/{view}","DELETE /v1/{prefix}/namespaces/{namespace}/views/{view}","POST /v1/{prefix}/views/rename","POST /v1/{prefix}/transactions/commit","GET polaris/v1/{prefix}/namespaces/{namespace}/generic-tables","POST polaris/v1/{prefix}/namespaces/{namespace}/generic-tables","DELETE polaris/v1/{prefix}/namespaces/{namespace}/generic-tables/{generic-table}","GET polaris/v1/{prefix}/namespaces/{namespace}/generic-tables/{generic-table}","GET /polaris/v1/{prefix}/namespaces/{namespace}/policies","POST /polaris/v1/{prefix}/namespaces/{namespace}/policies","GET /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}","PUT /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}","DELETE /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}","PUT /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}/mappings","POST /polaris/v1/{prefix}/namespaces/{namespace}/policies/{policy-name}/mappings","GET /polaris/v1/{prefix}/applicable-policies"]} Catalog created spark-sql (default)> use polaris; spark-sql ()> create namespace db1; From b400e8b06e0a51275a8fd4c3a77e145bfc1b6cc6 Mon Sep 17 00:00:00 2001 From: Honah J Date: Thu, 24 Apr 2025 12:28:30 -0500 Subject: [PATCH 09/12] remove unnecessary config --- .../it/QuarkusPolicyServiceIntegrationTest.java | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/it/QuarkusPolicyServiceIntegrationTest.java b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/it/QuarkusPolicyServiceIntegrationTest.java index 8c920e08bb..a668e92e5c 100644 --- a/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/it/QuarkusPolicyServiceIntegrationTest.java +++ b/quarkus/service/src/test/java/org/apache/polaris/service/quarkus/it/QuarkusPolicyServiceIntegrationTest.java @@ -19,21 +19,7 @@ package org.apache.polaris.service.quarkus.it; import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.QuarkusTestProfile; -import io.quarkus.test.junit.TestProfile; -import java.util.Map; import org.apache.polaris.service.it.test.PolarisPolicyServiceIntegrationTest; @QuarkusTest -@TestProfile(QuarkusPolicyServiceIntegrationTest.Profile.class) -public class QuarkusPolicyServiceIntegrationTest extends PolarisPolicyServiceIntegrationTest { - - public static class Profile implements QuarkusTestProfile { - - @Override - public Map getConfigOverrides() { - return Map.of( - "polaris.features.defaults.\"ALLOW_EXTERNAL_CATALOG_CREDENTIAL_VENDING\"", "false"); - } - } -} +public class QuarkusPolicyServiceIntegrationTest extends PolarisPolicyServiceIntegrationTest {} From 34165d505e6f5ad434c6452544e77bd8da8ad28f Mon Sep 17 00:00:00 2001 From: Honah J Date: Thu, 24 Apr 2025 15:18:21 -0500 Subject: [PATCH 10/12] use newHandlerWrapper directly --- .../catalog/policy/PolicyCatalogAdapter.java | 86 +++++++------------ .../catalog/policy/PolicyCatalogHandler.java | 10 +-- 2 files changed, 30 insertions(+), 66 deletions(-) diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java index 56f5d08e5e..f313c339f6 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java @@ -22,7 +22,6 @@ import jakarta.inject.Inject; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.SecurityContext; -import java.util.function.Function; import org.apache.iceberg.catalog.Namespace; import org.apache.iceberg.exceptions.NotAuthorizedException; import org.apache.iceberg.rest.RESTUtil; @@ -39,6 +38,9 @@ import org.apache.polaris.service.types.AttachPolicyRequest; import org.apache.polaris.service.types.CreatePolicyRequest; import org.apache.polaris.service.types.DetachPolicyRequest; +import org.apache.polaris.service.types.GetApplicablePoliciesResponse; +import org.apache.polaris.service.types.ListPoliciesResponse; +import org.apache.polaris.service.types.LoadPolicyResponse; import org.apache.polaris.service.types.PolicyIdentifier; import org.apache.polaris.service.types.UpdatePolicyRequest; import org.slf4j.Logger; @@ -71,24 +73,7 @@ public PolicyCatalogAdapter( this.prefixParser = prefixParser; } - private Response withCatalog( - SecurityContext securityContext, - String prefix, - Function action) { - String catalogName = prefixParser.prefixToCatalogName(realmContext, prefix); - try (PolicyCatalogHandler wrapper = newHandlerWrapper(securityContext, catalogName)) { - return action.apply(wrapper); - } catch (RuntimeException e) { - LOGGER.debug("RuntimeException while operating on policy catalog. Propagating to caller.", e); - throw e; - } catch (Exception e) { - LOGGER.error("Error while operating on policy catalog", e); - throw new RuntimeException(e); - } - } - - private PolicyCatalogHandler newHandlerWrapper( - SecurityContext securityContext, String catalogName) { + private PolicyCatalogHandler newHandlerWrapper(SecurityContext securityContext, String prefix) { var authenticatedPrincipal = (AuthenticatedPolarisPrincipal) securityContext.getUserPrincipal(); if (authenticatedPrincipal == null) { throw new NotAuthorizedException("Failed to find authenticatedPrincipal in SecurityContext"); @@ -99,7 +84,7 @@ private PolicyCatalogHandler newHandlerWrapper( entityManager, metaStoreManager, securityContext, - catalogName, + prefixParser.prefixToCatalogName(realmContext, prefix), polarisAuthorizer); } @@ -111,10 +96,9 @@ public Response createPolicy( RealmContext realmContext, SecurityContext securityContext) { Namespace ns = decodeNamespace(namespace); - return withCatalog( - securityContext, - prefix, - catalog -> Response.ok(catalog.createPolicy(ns, createPolicyRequest)).build()); + PolicyCatalogHandler handler = newHandlerWrapper(securityContext, prefix); + LoadPolicyResponse response = handler.createPolicy(ns, createPolicyRequest); + return Response.ok(response).build(); } @Override @@ -129,8 +113,9 @@ public Response listPolicies( Namespace ns = decodeNamespace(namespace); PolicyType type = policyType != null ? PolicyType.fromName(RESTUtil.decodeString(policyType)) : null; - return withCatalog( - securityContext, prefix, catalog -> Response.ok(catalog.listPolicies(ns, type)).build()); + PolicyCatalogHandler handler = newHandlerWrapper(securityContext, prefix); + ListPoliciesResponse response = handler.listPolicies(ns, type); + return Response.ok(response).build(); } @Override @@ -142,8 +127,9 @@ public Response loadPolicy( SecurityContext securityContext) { Namespace ns = decodeNamespace(namespace); PolicyIdentifier identifier = new PolicyIdentifier(ns, RESTUtil.decodeString(policyName)); - return withCatalog( - securityContext, prefix, catalog -> Response.ok(catalog.loadPolicy(identifier)).build()); + PolicyCatalogHandler handler = newHandlerWrapper(securityContext, prefix); + LoadPolicyResponse response = handler.loadPolicy(identifier); + return Response.ok(response).build(); } @Override @@ -156,10 +142,9 @@ public Response updatePolicy( SecurityContext securityContext) { Namespace ns = decodeNamespace(namespace); PolicyIdentifier identifier = new PolicyIdentifier(ns, RESTUtil.decodeString(policyName)); - return withCatalog( - securityContext, - prefix, - catalog -> Response.ok(catalog.updatePolicy(identifier, updatePolicyRequest)).build()); + PolicyCatalogHandler handler = newHandlerWrapper(securityContext, prefix); + LoadPolicyResponse response = handler.updatePolicy(identifier, updatePolicyRequest); + return Response.ok(response).build(); } @Override @@ -172,13 +157,9 @@ public Response dropPolicy( SecurityContext securityContext) { Namespace ns = decodeNamespace(namespace); PolicyIdentifier identifier = new PolicyIdentifier(ns, RESTUtil.decodeString(policyName)); - return withCatalog( - securityContext, - prefix, - catalog -> { - catalog.dropPolicy(identifier, detachAll != null && detachAll); - return Response.status(Response.Status.NO_CONTENT).build(); - }); + PolicyCatalogHandler handler = newHandlerWrapper(securityContext, prefix); + handler.dropPolicy(identifier, detachAll != null && detachAll); + return Response.status(Response.Status.NO_CONTENT).build(); } @Override @@ -191,13 +172,9 @@ public Response attachPolicy( SecurityContext securityContext) { Namespace ns = decodeNamespace(namespace); PolicyIdentifier identifier = new PolicyIdentifier(ns, RESTUtil.decodeString(policyName)); - return withCatalog( - securityContext, - prefix, - catalog -> { - catalog.attachPolicy(identifier, attachPolicyRequest); - return Response.status(Response.Status.NO_CONTENT).build(); - }); + PolicyCatalogHandler handler = newHandlerWrapper(securityContext, prefix); + handler.attachPolicy(identifier, attachPolicyRequest); + return Response.status(Response.Status.NO_CONTENT).build(); } @Override @@ -210,13 +187,9 @@ public Response detachPolicy( SecurityContext securityContext) { Namespace ns = decodeNamespace(namespace); PolicyIdentifier identifier = new PolicyIdentifier(ns, RESTUtil.decodeString(policyName)); - return withCatalog( - securityContext, - prefix, - catalog -> { - catalog.detachPolicy(identifier, detachPolicyRequest); - return Response.status(Response.Status.NO_CONTENT).build(); - }); + PolicyCatalogHandler handler = newHandlerWrapper(securityContext, prefix); + handler.detachPolicy(identifier, detachPolicyRequest); + return Response.status(Response.Status.NO_CONTENT).build(); } @Override @@ -233,9 +206,8 @@ public Response getApplicablePolicies( String target = targetName != null ? RESTUtil.decodeString(targetName) : null; PolicyType type = policyType != null ? PolicyType.fromName(RESTUtil.decodeString(policyType)) : null; - return withCatalog( - securityContext, - prefix, - catalog -> Response.ok(catalog.getApplicablePolicies(ns, target, type)).build()); + PolicyCatalogHandler handler = newHandlerWrapper(securityContext, prefix); + GetApplicablePoliciesResponse response = handler.getApplicablePolicies(ns, target, type); + return Response.ok(response).build(); } } diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java index c0b713dcca..3452936fe2 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java @@ -21,7 +21,6 @@ import com.google.common.base.Strings; import jakarta.annotation.Nullable; import jakarta.ws.rs.core.SecurityContext; -import java.io.Closeable; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -55,7 +54,7 @@ import org.apache.polaris.service.types.PolicyIdentifier; import org.apache.polaris.service.types.UpdatePolicyRequest; -public class PolicyCatalogHandler extends CatalogHandler implements AutoCloseable { +public class PolicyCatalogHandler extends CatalogHandler { private PolarisMetaStoreManager metaStoreManager; @@ -335,11 +334,4 @@ public void enforcePolicyStoreEnabledOrThrow() { throw new UnsupportedOperationException("Policy store support is not enabled"); } } - - @Override - public void close() throws Exception { - if (policyCatalog instanceof Closeable closeable) { - closeable.close(); - } - } } From 77eedbbfa19948109dece9bc45992ed82ee59c18 Mon Sep 17 00:00:00 2001 From: Honah J Date: Thu, 24 Apr 2025 21:21:31 -0500 Subject: [PATCH 11/12] Fix some nit --- .../java/org/apache/polaris/core/rest/PolarisEndpoints.java | 2 ++ .../service/catalog/policy/PolicyCatalogAdapter.java | 6 +++--- 2 files changed, 5 insertions(+), 3 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 index add1dde5cd..1fd91395b0 100644 --- 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 @@ -25,6 +25,7 @@ import org.apache.polaris.core.context.CallContext; public class PolarisEndpoints { + // Generic table endpoints public static final Endpoint V1_LIST_GENERIC_TABLES = Endpoint.create("GET", PolarisResourcePaths.V1_GENERIC_TABLES); public static final Endpoint V1_LOAD_GENERIC_TABLE = @@ -42,6 +43,7 @@ public class PolarisEndpoints { .add(V1_LOAD_GENERIC_TABLE) .build(); + // Policy store endpoints public static final Endpoint V1_LIST_POLICIES = Endpoint.create("GET", PolarisResourcePaths.V1_POLICIES); public static final Endpoint V1_CREATE_POLICY = diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java index f313c339f6..fe70d00a80 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogAdapter.java @@ -159,7 +159,7 @@ public Response dropPolicy( PolicyIdentifier identifier = new PolicyIdentifier(ns, RESTUtil.decodeString(policyName)); PolicyCatalogHandler handler = newHandlerWrapper(securityContext, prefix); handler.dropPolicy(identifier, detachAll != null && detachAll); - return Response.status(Response.Status.NO_CONTENT).build(); + return Response.noContent().build(); } @Override @@ -174,7 +174,7 @@ public Response attachPolicy( PolicyIdentifier identifier = new PolicyIdentifier(ns, RESTUtil.decodeString(policyName)); PolicyCatalogHandler handler = newHandlerWrapper(securityContext, prefix); handler.attachPolicy(identifier, attachPolicyRequest); - return Response.status(Response.Status.NO_CONTENT).build(); + return Response.noContent().build(); } @Override @@ -189,7 +189,7 @@ public Response detachPolicy( PolicyIdentifier identifier = new PolicyIdentifier(ns, RESTUtil.decodeString(policyName)); PolicyCatalogHandler handler = newHandlerWrapper(securityContext, prefix); handler.detachPolicy(identifier, detachPolicyRequest); - return Response.status(Response.Status.NO_CONTENT).build(); + return Response.noContent().build(); } @Override From 10ab5b567dd78680dea01ffde71cec04f695c768 Mon Sep 17 00:00:00 2001 From: Honah J Date: Thu, 24 Apr 2025 22:15:16 -0500 Subject: [PATCH 12/12] remove unnecessary method --- .../catalog/policy/PolicyCatalogHandler.java | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java index 3452936fe2..8273256ba9 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/policy/PolicyCatalogHandler.java @@ -73,7 +73,8 @@ public PolicyCatalogHandler( @Override protected void initializeCatalog() { - enforcePolicyStoreEnabledOrThrow(); + FeatureConfiguration.enforceFeatureEnabledOrThrow( + callContext, FeatureConfiguration.ENABLE_POLICY_STORE); this.policyCatalog = new PolicyCatalog(metaStoreManager, callContext, this.resolutionManifest); } @@ -322,16 +323,4 @@ private void throwNotFoundExceptionIfFailToResolve( } } } - - public void enforcePolicyStoreEnabledOrThrow() { - boolean enabled = - callContext - .getPolarisCallContext() - .getConfigurationStore() - .getConfiguration( - callContext.getPolarisCallContext(), FeatureConfiguration.ENABLE_POLICY_STORE); - if (!enabled) { - throw new UnsupportedOperationException("Policy store support is not enabled"); - } - } }