diff --git a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisGrantManager.java b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisGrantManager.java new file mode 100644 index 0000000000..0bc2870fbf --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisGrantManager.java @@ -0,0 +1,276 @@ +/* + * 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.auth; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.apache.polaris.core.PolarisCallContext; +import org.apache.polaris.core.entity.PolarisBaseEntity; +import org.apache.polaris.core.entity.PolarisEntityCore; +import org.apache.polaris.core.entity.PolarisGrantRecord; +import org.apache.polaris.core.entity.PolarisPrivilege; +import org.apache.polaris.core.persistence.BaseResult; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** Manage grants for Polaris entities. */ +public interface PolarisGrantManager { + /** + * Grant usage on a role to a grantee, for example granting usage on a catalog role to a principal + * role or granting a principal role to a principal. + * + * @param callCtx call context + * @param catalog if the role is a catalog role, the caller needs to pass-in the catalog entity + * which was used to resolve that granted. Else null. + * @param role resolved catalog or principal role + * @param grantee principal role or principal as resolved by the caller + * @return the grant record we created for this grant. Will return ENTITY_NOT_FOUND if the + * specified role couldn't be found. Should be retried in that case + */ + @NotNull + PrivilegeResult grantUsageOnRoleToGrantee( + @NotNull PolarisCallContext callCtx, + @Nullable PolarisEntityCore catalog, + @NotNull PolarisEntityCore role, + @NotNull PolarisEntityCore grantee); + + /** + * Revoke usage on a role (a catalog or a principal role) from a grantee (e.g. a principal role or + * a principal). + * + * @param callCtx call context + * @param catalog if the granted is a catalog role, the caller needs to pass-in the catalog entity + * which was used to resolve that role. Else null should be passed-in. + * @param role a catalog/principal role as resolved by the caller + * @param grantee resolved principal role or principal + * @return the result. Will return ENTITY_NOT_FOUND if the * specified role couldn't be found. + * Should be retried in that case. Will return GRANT_NOT_FOUND if the grant to revoke cannot + * be found + */ + @NotNull + PrivilegeResult revokeUsageOnRoleFromGrantee( + @NotNull PolarisCallContext callCtx, + @Nullable PolarisEntityCore catalog, + @NotNull PolarisEntityCore role, + @NotNull PolarisEntityCore grantee); + + /** + * Grant a privilege on a catalog securable to a grantee. + * + * @param callCtx call context + * @param grantee resolved role, the grantee + * @param catalogPath path to that entity, cannot be null or empty unless securable is top-level + * @param securable securable entity, must have been resolved by the client. Can be the catalog + * itself + * @param privilege privilege to grant + * @return the grant record we created for this grant. Will return ENTITY_NOT_FOUND if the + * specified role couldn't be found. Should be retried in that case + */ + @NotNull + PrivilegeResult grantPrivilegeOnSecurableToRole( + @NotNull PolarisCallContext callCtx, + @NotNull PolarisEntityCore grantee, + @Nullable List catalogPath, + @NotNull PolarisEntityCore securable, + @NotNull PolarisPrivilege privilege); + + /** + * Revoke a privilege on a catalog securable from a grantee. + * + * @param callCtx call context + * @param grantee resolved role, the grantee + * @param catalogPath path to that entity, cannot be null or empty unless securable is top-level + * @param securable securable entity, must have been resolved by the client. Can be the catalog + * itself. + * @param privilege privilege to revoke + * @return the result. Will return ENTITY_NOT_FOUND if the * specified role couldn't be found. + * Should be retried in that case. Will return GRANT_NOT_FOUND if the grant to revoke cannot + * be found + */ + @NotNull + PrivilegeResult revokePrivilegeOnSecurableFromRole( + @NotNull PolarisCallContext callCtx, + @NotNull PolarisEntityCore grantee, + @Nullable List catalogPath, + @NotNull PolarisEntityCore securable, + @NotNull PolarisPrivilege privilege); + + /** + * This method should be used by the Polaris app to cache all grant records on a securable. + * + * @param callCtx call context + * @param securableCatalogId id of the catalog this securable belongs to + * @param securableId id of the securable + * @return the list of grants and the version of the grant records. We will return + * ENTITY_NOT_FOUND if the securable cannot be found + */ + @NotNull + LoadGrantsResult loadGrantsOnSecurable( + @NotNull PolarisCallContext callCtx, long securableCatalogId, long securableId); + + /** + * This method should be used by the Polaris app to load all grants made to a grantee, either a + * role or a principal. + * + * @param callCtx call context + * @param granteeCatalogId id of the catalog this grantee belongs to + * @param granteeId id of the grantee + * @return the list of grants and the version of the grant records. We will return NULL if the + * grantee does not exist + */ + @NotNull + LoadGrantsResult loadGrantsToGrantee( + PolarisCallContext callCtx, long granteeCatalogId, long granteeId); + + /** Result of a grant/revoke privilege call */ + class PrivilegeResult extends BaseResult { + + // null if not success. + private final PolarisGrantRecord grantRecord; + + /** + * Constructor for an error + * + * @param errorCode error code, cannot be SUCCESS + * @param extraInformation extra information + */ + public PrivilegeResult( + @NotNull BaseResult.ReturnStatus errorCode, @Nullable String extraInformation) { + super(errorCode, extraInformation); + this.grantRecord = null; + } + + /** + * Constructor for success + * + * @param grantRecord grant record being granted or revoked + */ + public PrivilegeResult(@NotNull PolarisGrantRecord grantRecord) { + super(BaseResult.ReturnStatus.SUCCESS); + this.grantRecord = grantRecord; + } + + @JsonCreator + private PrivilegeResult( + @JsonProperty("returnStatus") @NotNull BaseResult.ReturnStatus returnStatus, + @JsonProperty("extraInformation") String extraInformation, + @JsonProperty("grantRecord") PolarisGrantRecord grantRecord) { + super(returnStatus, extraInformation); + this.grantRecord = grantRecord; + } + + public PolarisGrantRecord getGrantRecord() { + return grantRecord; + } + } + + /** Result of a load grants call */ + class LoadGrantsResult extends BaseResult { + // true if success. If false, the caller should retry because of some concurrent change + private final int grantsVersion; + + // null if not success. Else set of grants records on a securable or to a grantee + private final List grantRecords; + + // null if not success. Else, for each grant record, list of securable or grantee entities + private final List entities; + + /** + * Constructor for an error + * + * @param errorCode error code, cannot be SUCCESS + * @param extraInformation extra information + */ + public LoadGrantsResult( + @NotNull BaseResult.ReturnStatus errorCode, @Nullable String extraInformation) { + super(errorCode, extraInformation); + this.grantsVersion = 0; + this.grantRecords = null; + this.entities = null; + } + + /** + * Constructor for success + * + * @param grantsVersion version of the grants + * @param grantRecords set of grant records + */ + public LoadGrantsResult( + int grantsVersion, + @NotNull List grantRecords, + List entities) { + super(BaseResult.ReturnStatus.SUCCESS); + this.grantsVersion = grantsVersion; + this.grantRecords = grantRecords; + this.entities = entities; + } + + @JsonCreator + private LoadGrantsResult( + @JsonProperty("returnStatus") @NotNull BaseResult.ReturnStatus returnStatus, + @JsonProperty("extraInformation") String extraInformation, + @JsonProperty("grantsVersion") int grantsVersion, + @JsonProperty("grantRecords") List grantRecords, + @JsonProperty("entities") List entities) { + super(returnStatus, extraInformation); + this.grantsVersion = grantsVersion; + this.grantRecords = grantRecords; + // old GS code might not serialize this argument + this.entities = entities; + } + + public int getGrantsVersion() { + return grantsVersion; + } + + public List getGrantRecords() { + return grantRecords; + } + + public List getEntities() { + return entities; + } + + @JsonIgnore + public Map getEntitiesAsMap() { + return (this.getEntities() == null) + ? null + : this.getEntities().stream() + .collect(Collectors.toMap(PolarisBaseEntity::getId, entity -> entity)); + } + + @Override + public String toString() { + return "LoadGrantsResult{" + + "grantsVersion=" + + grantsVersion + + ", grantRecords=" + + grantRecords + + ", entities=" + + entities + + ", returnStatus=" + + getReturnStatus() + + '}'; + } + } +} diff --git a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisSecretsManager.java b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisSecretsManager.java new file mode 100644 index 0000000000..7378820872 --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisSecretsManager.java @@ -0,0 +1,103 @@ +/* + * 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.auth; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.polaris.core.PolarisCallContext; +import org.apache.polaris.core.entity.PolarisPrincipalSecrets; +import org.apache.polaris.core.persistence.BaseResult; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** Manages secrets for Polaris principals. */ +public interface PolarisSecretsManager { + /** + * Load the principal secrets given the client_id. + * + * @param callCtx call context + * @param clientId principal client id + * @return the secrets associated to that principal, including the entity id of the principal + */ + @NotNull + PrincipalSecretsResult loadPrincipalSecrets( + @NotNull PolarisCallContext callCtx, @NotNull String clientId); + + /** + * Rotate secrets + * + * @param callCtx call context + * @param clientId principal client id + * @param principalId id of the principal + * @param reset true if the principal's secrets should be disabled and replaced with a one-time + * password. if the principal's secret is already a one-time password, this flag is + * automatically true + * @param oldSecretHash main secret hash for the principal + * @return the secrets associated to that principal amd the id of the principal + */ + @NotNull + PrincipalSecretsResult rotatePrincipalSecrets( + @NotNull PolarisCallContext callCtx, + @NotNull String clientId, + long principalId, + boolean reset, + @NotNull String oldSecretHash); + + /** the result of load/rotate principal secrets */ + class PrincipalSecretsResult extends BaseResult { + + // principal client identifier and associated secrets. Null if error + private final PolarisPrincipalSecrets principalSecrets; + + /** + * Constructor for an error + * + * @param errorCode error code, cannot be SUCCESS + * @param extraInformation extra information + */ + public PrincipalSecretsResult( + @NotNull BaseResult.ReturnStatus errorCode, @Nullable String extraInformation) { + super(errorCode, extraInformation); + this.principalSecrets = null; + } + + /** + * Constructor for success + * + * @param principalSecrets and associated secret information + */ + public PrincipalSecretsResult(@NotNull PolarisPrincipalSecrets principalSecrets) { + super(BaseResult.ReturnStatus.SUCCESS); + this.principalSecrets = principalSecrets; + } + + @JsonCreator + private PrincipalSecretsResult( + @JsonProperty("returnStatus") @NotNull BaseResult.ReturnStatus returnStatus, + @JsonProperty("extraInformation") @Nullable String extraInformation, + @JsonProperty("principalSecrets") @NotNull PolarisPrincipalSecrets principalSecrets) { + super(returnStatus, extraInformation); + this.principalSecrets = principalSecrets; + } + + public PolarisPrincipalSecrets getPrincipalSecrets() { + return principalSecrets; + } + } +} diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/BaseResult.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/BaseResult.java new file mode 100644 index 0000000000..c2e2d0bd7b --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/BaseResult.java @@ -0,0 +1,153 @@ +/* + * 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.persistence; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** Base result class for any call to the persistence layer */ +public class BaseResult { + // return code, indicates success or failure + private final int returnStatusCode; + + // additional information for some error return code + private final String extraInformation; + + public BaseResult() { + this.returnStatusCode = ReturnStatus.SUCCESS.getCode(); + this.extraInformation = null; + } + + public BaseResult(@NotNull BaseResult.ReturnStatus returnStatus) { + this.returnStatusCode = returnStatus.getCode(); + this.extraInformation = null; + } + + @JsonCreator + public BaseResult( + @JsonProperty("returnStatus") @NotNull BaseResult.ReturnStatus returnStatus, + @JsonProperty("extraInformation") @Nullable String extraInformation) { + this.returnStatusCode = returnStatus.getCode(); + this.extraInformation = extraInformation; + } + + public ReturnStatus getReturnStatus() { + return ReturnStatus.getStatus(this.returnStatusCode); + } + + public String getExtraInformation() { + return extraInformation; + } + + public boolean isSuccess() { + return this.returnStatusCode == ReturnStatus.SUCCESS.getCode(); + } + + public boolean alreadyExists() { + return this.returnStatusCode == ReturnStatus.ENTITY_ALREADY_EXISTS.getCode(); + } + + /** Possible return code for the various API calls. */ + public enum ReturnStatus { + // all good + SUCCESS(1), + + // an unexpected error was thrown, should result in a 500 error to the client + UNEXPECTED_ERROR_SIGNALED(2), + + // the specified catalog path cannot be resolved. There is a possibility that by the time a call + // is made by the client to the persistent storage, something has changed due to concurrent + // modification(s). The client should retry in that case. + CATALOG_PATH_CANNOT_BE_RESOLVED(3), + + // the specified entity (and its path) cannot be resolved. There is a possibility that by the + // time a call is made by the client to the persistent storage, something has changed due to + // concurrent modification(s). The client should retry in that case. + ENTITY_CANNOT_BE_RESOLVED(4), + + // entity not found + ENTITY_NOT_FOUND(5), + + // grant not found + GRANT_NOT_FOUND(6), + + // entity already exists + ENTITY_ALREADY_EXISTS(7), + + // entity cannot be dropped, it is one of the bootstrap object like a catalog admin role or the + // service admin principal role + ENTITY_UNDROPPABLE(8), + + // Namespace is not empty and cannot be dropped + NAMESPACE_NOT_EMPTY(9), + + // Catalog is not empty and cannot be dropped. All catalog roles (except the admin catalog + // role) and all namespaces in the catalog must be dropped before the namespace can be dropped + CATALOG_NOT_EMPTY(10), + + // The target entity was concurrently modified + TARGET_ENTITY_CONCURRENTLY_MODIFIED(11), + + // entity cannot be renamed + ENTITY_CANNOT_BE_RENAMED(12), + + // error caught while sub-scoping credentials. Error message will be returned + SUBSCOPE_CREDS_ERROR(13), + ; + + // code for the enum + private final int code; + + /** constructor */ + ReturnStatus(int code) { + this.code = code; + } + + int getCode() { + return this.code; + } + + // to efficiently map a code to its corresponding return status + private static final ReturnStatus[] REVERSE_MAPPING_ARRAY; + + static { + // find max array size + int maxCode = 0; + for (ReturnStatus returnStatus : ReturnStatus.values()) { + if (maxCode < returnStatus.code) { + maxCode = returnStatus.code; + } + } + + // allocate mapping array + REVERSE_MAPPING_ARRAY = new ReturnStatus[maxCode + 1]; + + // populate mapping array + for (ReturnStatus returnStatus : ReturnStatus.values()) { + REVERSE_MAPPING_ARRAY[returnStatus.code] = returnStatus; + } + } + + static ReturnStatus getStatus(int code) { + return code >= REVERSE_MAPPING_ARRAY.length ? null : REVERSE_MAPPING_ARRAY[code]; + } + } +} diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/LocalPolarisMetaStoreManagerFactory.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/LocalPolarisMetaStoreManagerFactory.java index 095853cff1..a53c65dac1 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/LocalPolarisMetaStoreManagerFactory.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/LocalPolarisMetaStoreManagerFactory.java @@ -25,6 +25,7 @@ import org.apache.polaris.core.PolarisCallContext; import org.apache.polaris.core.PolarisDefaultDiagServiceImpl; import org.apache.polaris.core.PolarisDiagnostics; +import org.apache.polaris.core.auth.PolarisSecretsManager.PrincipalSecretsResult; import org.apache.polaris.core.context.CallContext; import org.apache.polaris.core.context.RealmContext; import org.apache.polaris.core.entity.PolarisEntity; @@ -75,15 +76,14 @@ private void initializeForRealm(RealmContext realmContext) { } @Override - public synchronized Map bootstrapRealms( - List realms) { - Map results = new HashMap<>(); + public synchronized Map bootstrapRealms(List realms) { + Map results = new HashMap<>(); for (String realm : realms) { RealmContext realmContext = () -> realm; if (!metaStoreManagerMap.containsKey(realmContext.getRealmIdentifier())) { initializeForRealm(realmContext); - PolarisMetaStoreManager.PrincipalSecretsResult secretsResult = + PrincipalSecretsResult secretsResult = bootstrapServiceAndCreatePolarisPrincipalForRealm( realmContext, metaStoreManagerMap.get(realmContext.getRealmIdentifier())); results.put(realmContext.getRealmIdentifier(), secretsResult); @@ -157,9 +157,8 @@ public void setStorageIntegrationProvider(PolarisStorageIntegrationProvider stor * metastore and creates a root service principal. After that we rotate the root principal * credentials and print them to stdout */ - private PolarisMetaStoreManager.PrincipalSecretsResult - bootstrapServiceAndCreatePolarisPrincipalForRealm( - RealmContext realmContext, PolarisMetaStoreManager metaStoreManager) { + private PrincipalSecretsResult bootstrapServiceAndCreatePolarisPrincipalForRealm( + RealmContext realmContext, PolarisMetaStoreManager metaStoreManager) { // While bootstrapping we need to act as a fake privileged context since the real // CallContext hasn't even been resolved yet. PolarisCallContext polarisContext = @@ -199,7 +198,7 @@ public void setStorageIntegrationProvider(PolarisStorageIntegrationProvider stor .getInternalPropertiesAsMap() .get(PolarisEntityConstants.getClientIdPropertyName())) .getPrincipalSecrets(); - PolarisMetaStoreManager.PrincipalSecretsResult rotatedSecrets = + PrincipalSecretsResult rotatedSecrets = metaStoreManager.rotatePrincipalSecrets( polarisContext, secrets.getPrincipalClientId(), diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/MetaStoreManagerFactory.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/MetaStoreManagerFactory.java index 643294dc83..821d139a87 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/MetaStoreManagerFactory.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/MetaStoreManagerFactory.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.function.Supplier; +import org.apache.polaris.core.auth.PolarisSecretsManager.PrincipalSecretsResult; import org.apache.polaris.core.context.RealmContext; import org.apache.polaris.core.monitor.PolarisMetricRegistry; import org.apache.polaris.core.storage.PolarisStorageIntegrationProvider; @@ -45,7 +46,7 @@ public interface MetaStoreManagerFactory extends Discoverable { void setMetricRegistry(PolarisMetricRegistry metricRegistry); - Map bootstrapRealms(List realms); + Map bootstrapRealms(List realms); /** Purge all metadata for the realms provided */ void purgeRealms(List realms); diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisMetaStoreManager.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisMetaStoreManager.java index 697cb69e5e..4c646e3828 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisMetaStoreManager.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisMetaStoreManager.java @@ -21,25 +21,20 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.EnumMap; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; import org.apache.polaris.core.PolarisCallContext; +import org.apache.polaris.core.auth.PolarisGrantManager; +import org.apache.polaris.core.auth.PolarisSecretsManager; import org.apache.polaris.core.entity.PolarisBaseEntity; -import org.apache.polaris.core.entity.PolarisChangeTrackingVersions; import org.apache.polaris.core.entity.PolarisEntity; import org.apache.polaris.core.entity.PolarisEntityActiveRecord; import org.apache.polaris.core.entity.PolarisEntityCore; -import org.apache.polaris.core.entity.PolarisEntityId; import org.apache.polaris.core.entity.PolarisEntitySubType; import org.apache.polaris.core.entity.PolarisEntityType; -import org.apache.polaris.core.entity.PolarisGrantRecord; import org.apache.polaris.core.entity.PolarisPrincipalSecrets; -import org.apache.polaris.core.entity.PolarisPrivilege; -import org.apache.polaris.core.storage.PolarisCredentialProperty; -import org.apache.polaris.core.storage.PolarisStorageActions; +import org.apache.polaris.core.persistence.cache.PolarisRemoteCache; +import org.apache.polaris.core.storage.PolarisCredentialVendor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -47,136 +42,11 @@ * Polaris Metastore Manager manages all Polaris entities and associated grant records metadata for * authorization. It uses the underlying persistent metastore to store and retrieve Polaris metadata */ -public interface PolarisMetaStoreManager { - - /** Possible return code for the various API calls. */ - enum ReturnStatus { - // all good - SUCCESS(1), - - // an unexpected error was thrown, should result in a 500 error to the client - UNEXPECTED_ERROR_SIGNALED(2), - - // the specified catalog path cannot be resolved. There is a possibility that by the time a call - // is made by the client to the persistent storage, something has changed due to concurrent - // modification(s). The client should retry in that case. - CATALOG_PATH_CANNOT_BE_RESOLVED(3), - - // the specified entity (and its path) cannot be resolved. There is a possibility that by the - // time a call is made by the client to the persistent storage, something has changed due to - // concurrent modification(s). The client should retry in that case. - ENTITY_CANNOT_BE_RESOLVED(4), - - // entity not found - ENTITY_NOT_FOUND(5), - - // grant not found - GRANT_NOT_FOUND(6), - - // entity already exists - ENTITY_ALREADY_EXISTS(7), - - // entity cannot be dropped, it is one of the bootstrap object like a catalog admin role or the - // service admin principal role - ENTITY_UNDROPPABLE(8), - - // Namespace is not empty and cannot be dropped - NAMESPACE_NOT_EMPTY(9), - - // Catalog is not empty and cannot be dropped. All catalog roles (except the admin catalog - // role) and all namespaces in the catalog must be dropped before the namespace can be dropped - CATALOG_NOT_EMPTY(10), - - // The target entity was concurrently modified - TARGET_ENTITY_CONCURRENTLY_MODIFIED(11), - - // entity cannot be renamed - ENTITY_CANNOT_BE_RENAMED(12), - - // error caught while sub-scoping credentials. Error message will be returned - SUBSCOPE_CREDS_ERROR(13), - ; - - // code for the enum - private final int code; - - /** constructor */ - ReturnStatus(int code) { - this.code = code; - } - - int getCode() { - return this.code; - } - - // to efficiently map a code to its corresponding return status - private static final ReturnStatus[] REVERSE_MAPPING_ARRAY; - - static { - // find max array size - int maxCode = 0; - for (ReturnStatus returnStatus : ReturnStatus.values()) { - if (maxCode < returnStatus.code) { - maxCode = returnStatus.code; - } - } - - // allocate mapping array - REVERSE_MAPPING_ARRAY = new ReturnStatus[maxCode + 1]; - - // populate mapping array - for (ReturnStatus returnStatus : ReturnStatus.values()) { - REVERSE_MAPPING_ARRAY[returnStatus.code] = returnStatus; - } - } - - static ReturnStatus getStatus(int code) { - return code >= REVERSE_MAPPING_ARRAY.length ? null : REVERSE_MAPPING_ARRAY[code]; - } - } - - /** Base result class for any call to the persistence layer */ - class BaseResult { - // return code, indicates success or failure - private final int returnStatusCode; - - // additional information for some error return code - private final String extraInformation; - - public BaseResult() { - this.returnStatusCode = ReturnStatus.SUCCESS.getCode(); - this.extraInformation = null; - } - - public BaseResult(@NotNull PolarisMetaStoreManager.ReturnStatus returnStatus) { - this.returnStatusCode = returnStatus.getCode(); - this.extraInformation = null; - } - - @JsonCreator - public BaseResult( - @JsonProperty("returnStatus") @NotNull ReturnStatus returnStatus, - @JsonProperty("extraInformation") @Nullable String extraInformation) { - this.returnStatusCode = returnStatus.getCode(); - this.extraInformation = extraInformation; - } - - public ReturnStatus getReturnStatus() { - return ReturnStatus.getStatus(this.returnStatusCode); - } - - public String getExtraInformation() { - return extraInformation; - } - - public boolean isSuccess() { - return this.returnStatusCode == ReturnStatus.SUCCESS.getCode(); - } - - public boolean alreadyExists() { - return this.returnStatusCode == ReturnStatus.ENTITY_ALREADY_EXISTS.getCode(); - } - } +public interface PolarisMetaStoreManager + extends PolarisSecretsManager, + PolarisGrantManager, + PolarisRemoteCache, + PolarisCredentialVendor { /** * Bootstrap the Polaris service, creating the root catalog, root principal, and associated @@ -215,8 +85,7 @@ class EntityResult extends BaseResult { * @param extraInformation extra information if error. Implementation specific */ public EntityResult( - @NotNull PolarisMetaStoreManager.ReturnStatus errorCode, - @Nullable String extraInformation) { + @NotNull BaseResult.ReturnStatus errorCode, @Nullable String extraInformation) { super(errorCode, extraInformation); this.entity = null; } @@ -238,8 +107,7 @@ public EntityResult(@NotNull PolarisBaseEntity entity) { * @param errorStatus error status, cannot be SUCCESS * @param subTypeCode existing entity subtype code */ - public EntityResult( - @NotNull PolarisMetaStoreManager.ReturnStatus errorStatus, int subTypeCode) { + public EntityResult(@NotNull BaseResult.ReturnStatus errorStatus, int subTypeCode) { super(errorStatus, Integer.toString(subTypeCode)); this.entity = null; } @@ -317,8 +185,7 @@ class ListEntitiesResult extends BaseResult { * @param extraInformation extra information */ public ListEntitiesResult( - @NotNull PolarisMetaStoreManager.ReturnStatus errorCode, - @Nullable String extraInformation) { + @NotNull BaseResult.ReturnStatus errorCode, @Nullable String extraInformation) { super(errorCode, extraInformation); this.entities = null; } @@ -379,8 +246,7 @@ class GenerateEntityIdResult extends BaseResult { * @param extraInformation extra information */ public GenerateEntityIdResult( - @NotNull PolarisMetaStoreManager.ReturnStatus errorCode, - @Nullable String extraInformation) { + @NotNull BaseResult.ReturnStatus errorCode, @Nullable String extraInformation) { super(errorCode, extraInformation); this.id = null; } @@ -434,8 +300,7 @@ class CreatePrincipalResult extends BaseResult { * @param extraInformation extra information */ public CreatePrincipalResult( - @NotNull PolarisMetaStoreManager.ReturnStatus errorCode, - @Nullable String extraInformation) { + @NotNull BaseResult.ReturnStatus errorCode, @Nullable String extraInformation) { super(errorCode, extraInformation); this.principal = null; this.principalSecrets = null; @@ -487,80 +352,6 @@ public PolarisPrincipalSecrets getPrincipalSecrets() { CreatePrincipalResult createPrincipal( @NotNull PolarisCallContext callCtx, @NotNull PolarisBaseEntity principal); - /** the result of load/rotate principal secrets */ - class PrincipalSecretsResult extends BaseResult { - - // principal client identifier and associated secrets. Null if error - private final PolarisPrincipalSecrets principalSecrets; - - /** - * Constructor for an error - * - * @param errorCode error code, cannot be SUCCESS - * @param extraInformation extra information - */ - public PrincipalSecretsResult( - @NotNull PolarisMetaStoreManager.ReturnStatus errorCode, - @Nullable String extraInformation) { - super(errorCode, extraInformation); - this.principalSecrets = null; - } - - /** - * Constructor for success - * - * @param principalSecrets and associated secret information - */ - public PrincipalSecretsResult(@NotNull PolarisPrincipalSecrets principalSecrets) { - super(ReturnStatus.SUCCESS); - this.principalSecrets = principalSecrets; - } - - @JsonCreator - private PrincipalSecretsResult( - @JsonProperty("returnStatus") @NotNull ReturnStatus returnStatus, - @JsonProperty("extraInformation") @Nullable String extraInformation, - @JsonProperty("principalSecrets") @NotNull PolarisPrincipalSecrets principalSecrets) { - super(returnStatus, extraInformation); - this.principalSecrets = principalSecrets; - } - - public PolarisPrincipalSecrets getPrincipalSecrets() { - return principalSecrets; - } - } - - /** - * Load the principal secrets given the client_id. - * - * @param callCtx call context - * @param clientId principal client id - * @return the secrets associated to that principal, including the entity id of the principal - */ - @NotNull - PrincipalSecretsResult loadPrincipalSecrets( - @NotNull PolarisCallContext callCtx, @NotNull String clientId); - - /** - * Rotate secrets - * - * @param callCtx call context - * @param clientId principal client id - * @param principalId id of the principal - * @param reset true if the principal's secrets should be disabled and replaced with a one-time - * password. if the principal's secret is already a one-time password, this flag is - * automatically true - * @param oldSecretHash main secret hash for the principal - * @return the secrets associated to that principal amd the id of the principal - */ - @NotNull - PrincipalSecretsResult rotatePrincipalSecrets( - @NotNull PolarisCallContext callCtx, - @NotNull String clientId, - long principalId, - boolean reset, - @NotNull String oldSecretHash); - /** the return the result of a create-catalog method */ class CreateCatalogResult extends BaseResult { @@ -577,8 +368,7 @@ class CreateCatalogResult extends BaseResult { * @param extraInformation extra information */ public CreateCatalogResult( - @NotNull PolarisMetaStoreManager.ReturnStatus errorCode, - @Nullable String extraInformation) { + @NotNull BaseResult.ReturnStatus errorCode, @Nullable String extraInformation) { super(errorCode, extraInformation); this.catalog = null; this.catalogAdminRole = null; @@ -671,8 +461,7 @@ class EntitiesResult extends BaseResult { * @param extraInformation extra information */ public EntitiesResult( - @NotNull PolarisMetaStoreManager.ReturnStatus errorStatus, - @Nullable String extraInformation) { + @NotNull BaseResult.ReturnStatus errorStatus, @Nullable String extraInformation) { super(errorStatus, extraInformation); this.entities = null; } @@ -813,8 +602,7 @@ class DropEntityResult extends BaseResult { * @param extraInformation extra information */ public DropEntityResult( - @NotNull PolarisMetaStoreManager.ReturnStatus errorStatus, - @Nullable String extraInformation) { + @NotNull BaseResult.ReturnStatus errorStatus, @Nullable String extraInformation) { super(errorStatus, extraInformation); this.cleanupTaskId = null; } @@ -879,306 +667,6 @@ DropEntityResult dropEntityIfExists( @Nullable Map cleanupProperties, boolean cleanup); - /** Result of a grant/revoke privilege call */ - class PrivilegeResult extends BaseResult { - - // null if not success. - private final PolarisGrantRecord grantRecord; - - /** - * Constructor for an error - * - * @param errorCode error code, cannot be SUCCESS - * @param extraInformation extra information - */ - public PrivilegeResult( - @NotNull PolarisMetaStoreManager.ReturnStatus errorCode, - @Nullable String extraInformation) { - super(errorCode, extraInformation); - this.grantRecord = null; - } - - /** - * Constructor for success - * - * @param grantRecord grant record being granted or revoked - */ - public PrivilegeResult(@NotNull PolarisGrantRecord grantRecord) { - super(ReturnStatus.SUCCESS); - this.grantRecord = grantRecord; - } - - @JsonCreator - private PrivilegeResult( - @JsonProperty("returnStatus") @NotNull ReturnStatus returnStatus, - @JsonProperty("extraInformation") String extraInformation, - @JsonProperty("grantRecord") PolarisGrantRecord grantRecord) { - super(returnStatus, extraInformation); - this.grantRecord = grantRecord; - } - - public PolarisGrantRecord getGrantRecord() { - return grantRecord; - } - } - - /** - * Grant usage on a role to a grantee, for example granting usage on a catalog role to a principal - * role or granting a principal role to a principal. - * - * @param callCtx call context - * @param catalog if the role is a catalog role, the caller needs to pass-in the catalog entity - * which was used to resolve that granted. Else null. - * @param role resolved catalog or principal role - * @param grantee principal role or principal as resolved by the caller - * @return the grant record we created for this grant. Will return ENTITY_NOT_FOUND if the - * specified role couldn't be found. Should be retried in that case - */ - @NotNull - PrivilegeResult grantUsageOnRoleToGrantee( - @NotNull PolarisCallContext callCtx, - @Nullable PolarisEntityCore catalog, - @NotNull PolarisEntityCore role, - @NotNull PolarisEntityCore grantee); - - /** - * Revoke usage on a role (a catalog or a principal role) from a grantee (e.g. a principal role or - * a principal). - * - * @param callCtx call context - * @param catalog if the granted is a catalog role, the caller needs to pass-in the catalog entity - * which was used to resolve that role. Else null should be passed-in. - * @param role a catalog/principal role as resolved by the caller - * @param grantee resolved principal role or principal - * @return the result. Will return ENTITY_NOT_FOUND if the * specified role couldn't be found. - * Should be retried in that case. Will return GRANT_NOT_FOUND if the grant to revoke cannot - * be found - */ - @NotNull - PrivilegeResult revokeUsageOnRoleFromGrantee( - @NotNull PolarisCallContext callCtx, - @Nullable PolarisEntityCore catalog, - @NotNull PolarisEntityCore role, - @NotNull PolarisEntityCore grantee); - - /** - * Grant a privilege on a catalog securable to a grantee. - * - * @param callCtx call context - * @param grantee resolved role, the grantee - * @param catalogPath path to that entity, cannot be null or empty unless securable is top-level - * @param securable securable entity, must have been resolved by the client. Can be the catalog - * itself - * @param privilege privilege to grant - * @return the grant record we created for this grant. Will return ENTITY_NOT_FOUND if the - * specified role couldn't be found. Should be retried in that case - */ - @NotNull - PrivilegeResult grantPrivilegeOnSecurableToRole( - @NotNull PolarisCallContext callCtx, - @NotNull PolarisEntityCore grantee, - @Nullable List catalogPath, - @NotNull PolarisEntityCore securable, - @NotNull PolarisPrivilege privilege); - - /** - * Revoke a privilege on a catalog securable from a grantee. - * - * @param callCtx call context - * @param grantee resolved role, the grantee - * @param catalogPath path to that entity, cannot be null or empty unless securable is top-level - * @param securable securable entity, must have been resolved by the client. Can be the catalog - * itself. - * @param privilege privilege to revoke - * @return the result. Will return ENTITY_NOT_FOUND if the * specified role couldn't be found. - * Should be retried in that case. Will return GRANT_NOT_FOUND if the grant to revoke cannot - * be found - */ - @NotNull - PrivilegeResult revokePrivilegeOnSecurableFromRole( - @NotNull PolarisCallContext callCtx, - @NotNull PolarisEntityCore grantee, - @Nullable List catalogPath, - @NotNull PolarisEntityCore securable, - @NotNull PolarisPrivilege privilege); - - /** Result of a load grants call */ - class LoadGrantsResult extends BaseResult { - // true if success. If false, the caller should retry because of some concurrent change - private final int grantsVersion; - - // null if not success. Else set of grants records on a securable or to a grantee - private final List grantRecords; - - // null if not success. Else, for each grant record, list of securable or grantee entities - private final List entities; - - /** - * Constructor for an error - * - * @param errorCode error code, cannot be SUCCESS - * @param extraInformation extra information - */ - public LoadGrantsResult( - @NotNull PolarisMetaStoreManager.ReturnStatus errorCode, - @Nullable String extraInformation) { - super(errorCode, extraInformation); - this.grantsVersion = 0; - this.grantRecords = null; - this.entities = null; - } - - /** - * Constructor for success - * - * @param grantsVersion version of the grants - * @param grantRecords set of grant records - */ - public LoadGrantsResult( - int grantsVersion, - @NotNull List grantRecords, - List entities) { - super(ReturnStatus.SUCCESS); - this.grantsVersion = grantsVersion; - this.grantRecords = grantRecords; - this.entities = entities; - } - - @JsonCreator - private LoadGrantsResult( - @JsonProperty("returnStatus") @NotNull ReturnStatus returnStatus, - @JsonProperty("extraInformation") String extraInformation, - @JsonProperty("grantsVersion") int grantsVersion, - @JsonProperty("grantRecords") List grantRecords, - @JsonProperty("entities") List entities) { - super(returnStatus, extraInformation); - this.grantsVersion = grantsVersion; - this.grantRecords = grantRecords; - // old GS code might not serialize this argument - this.entities = entities; - } - - public int getGrantsVersion() { - return grantsVersion; - } - - public List getGrantRecords() { - return grantRecords; - } - - public List getEntities() { - return entities; - } - - @JsonIgnore - public Map getEntitiesAsMap() { - return (this.getEntities() == null) - ? null - : this.getEntities().stream() - .collect(Collectors.toMap(PolarisBaseEntity::getId, entity -> entity)); - } - - @Override - public String toString() { - return "LoadGrantsResult{" - + "grantsVersion=" - + grantsVersion - + ", grantRecords=" - + grantRecords - + ", entities=" - + entities - + ", returnStatus=" - + getReturnStatus() - + '}'; - } - } - - /** - * This method should be used by the Polaris app to cache all grant records on a securable. - * - * @param callCtx call context - * @param securableCatalogId id of the catalog this securable belongs to - * @param securableId id of the securable - * @return the list of grants and the version of the grant records. We will return - * ENTITY_NOT_FOUND if the securable cannot be found - */ - @NotNull - LoadGrantsResult loadGrantsOnSecurable( - @NotNull PolarisCallContext callCtx, long securableCatalogId, long securableId); - - /** - * This method should be used by the Polaris app to load all grants made to a grantee, either a - * role or a principal. - * - * @param callCtx call context - * @param granteeCatalogId id of the catalog this grantee belongs to - * @param granteeId id of the grantee - * @return the list of grants and the version of the grant records. We will return NULL if the - * grantee does not exist - */ - @NotNull - LoadGrantsResult loadGrantsToGrantee( - PolarisCallContext callCtx, long granteeCatalogId, long granteeId); - - /** Result of a loadEntitiesChangeTracking call */ - class ChangeTrackingResult extends BaseResult { - - // null if not success. Else, will be null if the grant to revoke was not found - private final List changeTrackingVersions; - - /** - * Constructor for an error - * - * @param errorCode error code, cannot be SUCCESS - * @param extraInformation extra information - */ - public ChangeTrackingResult( - @NotNull PolarisMetaStoreManager.ReturnStatus errorCode, - @Nullable String extraInformation) { - super(errorCode, extraInformation); - this.changeTrackingVersions = null; - } - - /** - * Constructor for success - * - * @param changeTrackingVersions change tracking versions - */ - public ChangeTrackingResult( - @NotNull List changeTrackingVersions) { - super(ReturnStatus.SUCCESS); - this.changeTrackingVersions = changeTrackingVersions; - } - - @JsonCreator - private ChangeTrackingResult( - @JsonProperty("returnStatus") @NotNull ReturnStatus returnStatus, - @JsonProperty("extraInformation") String extraInformation, - @JsonProperty("changeTrackingVersions") - List changeTrackingVersions) { - super(returnStatus, extraInformation); - this.changeTrackingVersions = changeTrackingVersions; - } - - public List getChangeTrackingVersions() { - return changeTrackingVersions; - } - } - - /** - * Load change tracking information for a set of entities in one single shot and return for each - * the version for the entity itself and the version associated to its grant records. - * - * @param callCtx call context - * @param entityIds list of catalog/entity pair ids for which we need to efficiently load the - * version information, both entity version and grant records version. - * @return a list of version tracking information. Order in that returned list is the same as the - * input list. Some elements might be NULL if the entity has been purged. Not expected to fail - */ - @NotNull - ChangeTrackingResult loadEntitiesChangeTracking( - @NotNull PolarisCallContext callCtx, @NotNull List entityIds); - /** * Load the entity from backend store. Will return NULL if the entity does not exist, i.e. has * been purged. The entity being loaded might have been dropped @@ -1200,296 +688,4 @@ ChangeTrackingResult loadEntitiesChangeTracking( */ @NotNull EntitiesResult loadTasks(@NotNull PolarisCallContext callCtx, String executorId, int limit); - - /** Result of a getSubscopedCredsForEntity() call */ - class ScopedCredentialsResult extends BaseResult { - - // null if not success. Else, set of name/value pairs for the credentials - private final EnumMap credentials; - - /** - * Constructor for an error - * - * @param errorCode error code, cannot be SUCCESS - * @param extraInformation extra information - */ - public ScopedCredentialsResult( - @NotNull PolarisMetaStoreManager.ReturnStatus errorCode, - @Nullable String extraInformation) { - super(errorCode, extraInformation); - this.credentials = null; - } - - /** - * Constructor for success - * - * @param credentials credentials - */ - public ScopedCredentialsResult( - @NotNull EnumMap credentials) { - super(ReturnStatus.SUCCESS); - this.credentials = credentials; - } - - @JsonCreator - private ScopedCredentialsResult( - @JsonProperty("returnStatus") @NotNull ReturnStatus returnStatus, - @JsonProperty("extraInformation") String extraInformation, - @JsonProperty("credentials") Map credentials) { - super(returnStatus, extraInformation); - this.credentials = new EnumMap<>(PolarisCredentialProperty.class); - if (credentials != null) { - credentials.forEach( - (k, v) -> this.credentials.put(PolarisCredentialProperty.valueOf(k), v)); - } - } - - public EnumMap getCredentials() { - return credentials; - } - } - - /** - * Get a sub-scoped credentials for an entity against the provided allowed read and write - * locations. - * - * @param callCtx the polaris call context - * @param catalogId the catalog id - * @param entityId the entity id - * @param allowListOperation whether to allow LIST operation on the allowedReadLocations and - * allowedWriteLocations - * @param allowedReadLocations a set of allowed to read locations - * @param allowedWriteLocations a set of allowed to write locations - * @return an enum map containing the scoped credentials - */ - @NotNull - ScopedCredentialsResult getSubscopedCredsForEntity( - @NotNull PolarisCallContext callCtx, - long catalogId, - long entityId, - boolean allowListOperation, - @NotNull Set allowedReadLocations, - @NotNull Set allowedWriteLocations); - - /** Result of a validateAccessToLocations() call */ - class ValidateAccessResult extends BaseResult { - - // null if not success. Else, set of location/validationResult pairs for each location in the - // set - private final Map validateResult; - - /** - * Constructor for an error - * - * @param errorCode error code, cannot be SUCCESS - * @param extraInformation extra information - */ - public ValidateAccessResult( - @NotNull PolarisMetaStoreManager.ReturnStatus errorCode, - @Nullable String extraInformation) { - super(errorCode, extraInformation); - this.validateResult = null; - } - - /** - * Constructor for success - * - * @param validateResult validate result - */ - public ValidateAccessResult(@NotNull Map validateResult) { - super(ReturnStatus.SUCCESS); - this.validateResult = validateResult; - } - - @JsonCreator - private ValidateAccessResult( - @JsonProperty("returnStatus") @NotNull ReturnStatus returnStatus, - @JsonProperty("extraInformation") String extraInformation, - @JsonProperty("validateResult") Map validateResult) { - super(returnStatus, extraInformation); - this.validateResult = validateResult; - } - - public Map getValidateResult() { - return this.validateResult; - } - } - - /** - * Validate whether the entity has access to the locations with the provided target operations - * - * @param callCtx the polaris call context - * @param catalogId the catalog id - * @param entityId the entity id - * @param actions a set of operation actions: READ/WRITE/LIST/DELETE/ALL - * @param locations a set of locations to verify - * @return a Map of {@code }, a validate result value looks like this - *
-   * {
-   *   "status" : "failure",
-   *   "actions" : {
-   *     "READ" : {
-   *       "message" : "The specified file was not found",
-   *       "status" : "failure"
-   *     },
-   *     "DELETE" : {
-   *       "message" : "One or more objects could not be deleted (Status Code: 200; Error Code: null)",
-   *       "status" : "failure"
-   *     },
-   *     "LIST" : {
-   *       "status" : "success"
-   *     },
-   *     "WRITE" : {
-   *       "message" : "Access Denied (Status Code: 403; Error Code: AccessDenied)",
-   *       "status" : "failure"
-   *     }
-   *   },
-   *   "message" : "Some of the integration checks failed. Check the Polaris documentation for more information."
-   * }
-   * 
- */ - @NotNull - ValidateAccessResult validateAccessToLocations( - @NotNull PolarisCallContext callCtx, - long catalogId, - long entityId, - @NotNull Set actions, - @NotNull Set locations); - - /** - * Represents an entry in the cache. If we refresh a cached entry, we will only refresh the - * information which have changed, based on the version of the entity - */ - class CachedEntryResult extends BaseResult { - - // the entity itself if it was loaded - private final @Nullable PolarisBaseEntity entity; - - // version for the grant records, in case the entity was not loaded - private final int grantRecordsVersion; - - private final @Nullable List entityGrantRecords; - - /** - * Constructor for an error - * - * @param errorCode error code, cannot be SUCCESS - * @param extraInformation extra information - */ - public CachedEntryResult( - @NotNull PolarisMetaStoreManager.ReturnStatus errorCode, - @Nullable String extraInformation) { - super(errorCode, extraInformation); - this.entity = null; - this.entityGrantRecords = null; - this.grantRecordsVersion = 0; - } - - /** - * Constructor with success - * - * @param entity the entity for that cached entry - * @param grantRecordsVersion the version of the grant records - * @param entityGrantRecords the list of grant records - */ - public CachedEntryResult( - @Nullable PolarisBaseEntity entity, - int grantRecordsVersion, - @Nullable List entityGrantRecords) { - super(ReturnStatus.SUCCESS); - this.entity = entity; - this.entityGrantRecords = entityGrantRecords; - this.grantRecordsVersion = grantRecordsVersion; - } - - @JsonCreator - public CachedEntryResult( - @JsonProperty("returnStatus") @NotNull ReturnStatus returnStatus, - @JsonProperty("extraInformation") String extraInformation, - @Nullable @JsonProperty("entity") PolarisBaseEntity entity, - @JsonProperty("grantRecordsVersion") int grantRecordsVersion, - @Nullable @JsonProperty("entityGrantRecords") List entityGrantRecords) { - super(returnStatus, extraInformation); - this.entity = entity; - this.entityGrantRecords = entityGrantRecords; - this.grantRecordsVersion = grantRecordsVersion; - } - - public @Nullable PolarisBaseEntity getEntity() { - return entity; - } - - public int getGrantRecordsVersion() { - return grantRecordsVersion; - } - - public @Nullable List getEntityGrantRecords() { - return entityGrantRecords; - } - } - - /** - * Load a cached entry, i.e. an entity definition and associated grant records, from the backend - * store. The entity is identified by its id (entity catalog id and id). - * - *

For entities that can be grantees, the associated grant records will include both the grant - * records for this entity as a grantee and for this entity as a securable. - * - * @param callCtx call context - * @param entityCatalogId id of the catalog for that entity - * @param entityId id of the entity - * @return cached entry for this entity. Status will be ENTITY_NOT_FOUND if the entity was not - * found - */ - @NotNull - PolarisMetaStoreManager.CachedEntryResult loadCachedEntryById( - @NotNull PolarisCallContext callCtx, long entityCatalogId, long entityId); - - /** - * Load a cached entry, i.e. an entity definition and associated grant records, from the backend - * store. The entity is identified by its name. Will return NULL if the entity does not exist, - * i.e. has been purged or dropped. - * - *

For entities that can be grantees, the associated grant records will include both the grant - * records for this entity as a grantee and for this entity as a securable. - * - * @param callCtx call context - * @param entityCatalogId id of the catalog for that entity - * @param parentId the id of the parent of that entity - * @param entityType the type of this entity - * @param entityName the name of this entity - * @return cached entry for this entity. Status will be ENTITY_NOT_FOUND if the entity was not - * found - */ - @NotNull - PolarisMetaStoreManager.CachedEntryResult loadCachedEntryByName( - @NotNull PolarisCallContext callCtx, - long entityCatalogId, - long parentId, - @NotNull PolarisEntityType entityType, - @NotNull String entityName); - - /** - * Refresh a cached entity from the backend store. Will return NULL if the entity does not exist, - * i.e. has been purged or dropped. Else, will determine what has changed based on the version - * information sent by the caller and will return only what has changed. - * - *

For entities that can be grantees, the associated grant records will include both the grant - * records for this entity as a grantee and for this entity as a securable. - * - * @param callCtx call context - * @param entityType type of the entity whose cached entry we are refreshing - * @param entityCatalogId id of the catalog for that entity - * @param entityId the id of the entity to load - * @return cached entry for this entity. Status will be ENTITY_NOT_FOUND if the entity was not * - * found - */ - @NotNull - PolarisMetaStoreManager.CachedEntryResult refreshCachedEntity( - @NotNull PolarisCallContext callCtx, - int entityVersion, - int entityGrantRecordsVersion, - @NotNull PolarisEntityType entityType, - long entityCatalogId, - long entityId); } diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisMetaStoreManagerImpl.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisMetaStoreManagerImpl.java index c3fc3a81ee..edbe6ccfc4 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisMetaStoreManagerImpl.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisMetaStoreManagerImpl.java @@ -49,6 +49,7 @@ import org.apache.polaris.core.entity.PolarisPrincipalSecrets; import org.apache.polaris.core.entity.PolarisPrivilege; import org.apache.polaris.core.entity.PolarisTaskConstants; +import org.apache.polaris.core.persistence.cache.PolarisRemoteCache.CachedEntryResult; import org.apache.polaris.core.storage.PolarisCredentialProperty; import org.apache.polaris.core.storage.PolarisStorageActions; import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo; @@ -565,7 +566,7 @@ private void revokeGrantRecord( // if it exists, this is an error, the client should retry if (otherCatalogRecord != null) { - return new CreateCatalogResult(ReturnStatus.ENTITY_ALREADY_EXISTS, null); + return new CreateCatalogResult(BaseResult.ReturnStatus.ENTITY_ALREADY_EXISTS, null); } ms.persistStorageIntegrationIfNeeded(callCtx, catalog, integration); @@ -706,7 +707,7 @@ private void bootstrapPolarisService( ms.runActionInTransaction(callCtx, () -> this.bootstrapPolarisService(callCtx, ms)); // all good - return new BaseResult(ReturnStatus.SUCCESS); + return new BaseResult(BaseResult.ReturnStatus.SUCCESS); } @Override @@ -720,7 +721,7 @@ private void bootstrapPolarisService( LOGGER.warn("Finished deleting all metadata in the metastore"); // all good - return new BaseResult(ReturnStatus.SUCCESS); + return new BaseResult(BaseResult.ReturnStatus.SUCCESS); } /** @@ -739,7 +740,7 @@ private void bootstrapPolarisService( // return if we failed to resolve if (resolver.isFailure()) { - return new EntityResult(ReturnStatus.CATALOG_PATH_CANNOT_BE_RESOLVED, null); + return new EntityResult(BaseResult.ReturnStatus.CATALOG_PATH_CANNOT_BE_RESOLVED, null); } // now looking the entity by name @@ -757,7 +758,7 @@ private void bootstrapPolarisService( // success, return what we found return (entity == null) - ? new EntityResult(ReturnStatus.ENTITY_NOT_FOUND, null) + ? new EntityResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null) : new EntityResult(entity); } @@ -791,7 +792,7 @@ private void bootstrapPolarisService( // return if we failed to resolve if (resolver.isFailure()) { - return new ListEntitiesResult(ReturnStatus.CATALOG_PATH_CANNOT_BE_RESOLVED, null); + return new ListEntitiesResult(BaseResult.ReturnStatus.CATALOG_PATH_CANNOT_BE_RESOLVED, null); } // return list of active entities @@ -951,7 +952,7 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str // if it exists, this is an error, the client should retry if (otherPrincipalRecord != null) { - return new CreatePrincipalResult(ReturnStatus.ENTITY_ALREADY_EXISTS, null); + return new CreatePrincipalResult(BaseResult.ReturnStatus.ENTITY_ALREADY_EXISTS, null); } // generate new secrets for this principal @@ -1002,7 +1003,7 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str ms.runInTransaction(callCtx, () -> this.loadPrincipalSecrets(callCtx, ms, clientId)); return (secrets == null) - ? new PrincipalSecretsResult(ReturnStatus.ENTITY_NOT_FOUND, null) + ? new PrincipalSecretsResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null) : new PrincipalSecretsResult(secrets); } @@ -1017,7 +1018,7 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str // if not found, the principal must have been dropped EntityResult loadEntityResult = loadEntity(callCtx, ms, PolarisEntityConstants.getNullId(), principalId); - if (loadEntityResult.getReturnStatus() != ReturnStatus.SUCCESS) { + if (loadEntityResult.getReturnStatus() != BaseResult.ReturnStatus.SUCCESS) { return null; } @@ -1075,7 +1076,7 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str callCtx, ms, clientId, principalId, reset, oldSecretHash)); return (secrets == null) - ? new PrincipalSecretsResult(ReturnStatus.ENTITY_NOT_FOUND, null) + ? new PrincipalSecretsResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null) : new PrincipalSecretsResult(secrets); } @@ -1137,7 +1138,7 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str // return if we failed to resolve if (resolver.isFailure()) { - return new EntityResult(ReturnStatus.CATALOG_PATH_CANNOT_BE_RESOLVED, null); + return new EntityResult(BaseResult.ReturnStatus.CATALOG_PATH_CANNOT_BE_RESOLVED, null); } // check if an entity does not already exist with the same name. If true, this is an error @@ -1150,7 +1151,7 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str PolarisEntityActiveRecord entityActiveRecord = ms.lookupEntityActive(callCtx, entityActiveKey); if (entityActiveRecord != null) { return new EntityResult( - ReturnStatus.ENTITY_ALREADY_EXISTS, entityActiveRecord.getSubTypeCode()); + BaseResult.ReturnStatus.ENTITY_ALREADY_EXISTS, entityActiveRecord.getSubTypeCode()); } // persist that new entity @@ -1191,7 +1192,7 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str EntityResult entityCreateResult = createEntityIfNotExists(callCtx, ms, catalogPath, entity); // abort everything if error - if (entityCreateResult.getReturnStatus() != ReturnStatus.SUCCESS) { + if (entityCreateResult.getReturnStatus() != BaseResult.ReturnStatus.SUCCESS) { ms.rollback(); return new EntitiesResult( entityCreateResult.getReturnStatus(), entityCreateResult.getExtraInformation()); @@ -1218,7 +1219,7 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str // if resolution failed, return false if (resolver.isFailure()) { - return new EntityResult(ReturnStatus.CATALOG_PATH_CANNOT_BE_RESOLVED, null); + return new EntityResult(BaseResult.ReturnStatus.CATALOG_PATH_CANNOT_BE_RESOLVED, null); } // lookup the entity, cannot be null @@ -1230,7 +1231,7 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str // check that the version of the entity has not changed at all to avoid concurrent updates if (entityRefreshed.getEntityVersion() != entity.getEntityVersion()) { - return new EntityResult(ReturnStatus.TARGET_ENTITY_CONCURRENTLY_MODIFIED, null); + return new EntityResult(BaseResult.ReturnStatus.TARGET_ENTITY_CONCURRENTLY_MODIFIED, null); } // update the two properties @@ -1276,7 +1277,7 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str callCtx, ms, entityWithPath.getCatalogPath(), entityWithPath.getEntity()); // if failed, rollback and return the last error - if (updatedEntityResult.getReturnStatus() != ReturnStatus.SUCCESS) { + if (updatedEntityResult.getReturnStatus() != BaseResult.ReturnStatus.SUCCESS) { ms.rollback(); return new EntitiesResult( updatedEntityResult.getReturnStatus(), updatedEntityResult.getExtraInformation()); @@ -1337,7 +1338,7 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str // if resolution failed, return false if (resolver.isFailure()) { - return new EntityResult(ReturnStatus.ENTITY_CANNOT_BE_RESOLVED, null); + return new EntityResult(BaseResult.ReturnStatus.ENTITY_CANNOT_BE_RESOLVED, null); } // find the entity to rename @@ -1347,17 +1348,17 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str // if this entity was not found, return failure. Not expected here because it was // resolved successfully (see above) if (refreshEntityToRename == null) { - return new EntityResult(ReturnStatus.ENTITY_NOT_FOUND, null); + return new EntityResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null); } // check that the source entity has not changed since it was updated by the caller if (refreshEntityToRename.getEntityVersion() != renamedEntity.getEntityVersion()) { - return new EntityResult(ReturnStatus.TARGET_ENTITY_CONCURRENTLY_MODIFIED, null); + return new EntityResult(BaseResult.ReturnStatus.TARGET_ENTITY_CONCURRENTLY_MODIFIED, null); } // ensure it can be renamed if (refreshEntityToRename.cannotBeDroppedOrRenamed()) { - return new EntityResult(ReturnStatus.ENTITY_CANNOT_BE_RENAMED, null); + return new EntityResult(BaseResult.ReturnStatus.ENTITY_CANNOT_BE_RENAMED, null); } // re-resolve the new catalog path if this entity is going to be moved @@ -1366,7 +1367,7 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str // if resolution failed, return false if (resolver.isFailure()) { - return new EntityResult(ReturnStatus.CATALOG_PATH_CANNOT_BE_RESOLVED, null); + return new EntityResult(BaseResult.ReturnStatus.CATALOG_PATH_CANNOT_BE_RESOLVED, null); } } @@ -1381,7 +1382,7 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str PolarisEntityActiveRecord entityActiveRecord = ms.lookupEntityActive(callCtx, entityActiveKey); if (entityActiveRecord != null) { return new EntityResult( - ReturnStatus.ENTITY_ALREADY_EXISTS, entityActiveRecord.getSubTypeCode()); + BaseResult.ReturnStatus.ENTITY_ALREADY_EXISTS, entityActiveRecord.getSubTypeCode()); } // all good, delete the existing entity from the active slice @@ -1447,7 +1448,7 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str // if resolution failed, return false if (resolver.isFailure()) { - return new DropEntityResult(ReturnStatus.CATALOG_PATH_CANNOT_BE_RESOLVED, null); + return new DropEntityResult(BaseResult.ReturnStatus.CATALOG_PATH_CANNOT_BE_RESOLVED, null); } // first find the entity to drop @@ -1456,12 +1457,12 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str // if this entity was not found, return failure if (refreshEntityToDrop == null) { - return new DropEntityResult(ReturnStatus.ENTITY_NOT_FOUND, null); + return new DropEntityResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null); } // ensure that this entity is droppable if (refreshEntityToDrop.cannotBeDroppedOrRenamed()) { - return new DropEntityResult(ReturnStatus.ENTITY_UNDROPPABLE, null); + return new DropEntityResult(BaseResult.ReturnStatus.ENTITY_UNDROPPABLE, null); } // check that the entity has children, in which case it is an error. This only applies to @@ -1472,7 +1473,7 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str // if not all namespaces are dropped, we cannot drop this catalog if (ms.hasChildren(callCtx, PolarisEntityType.NAMESPACE, catalogId, catalogId)) { - return new DropEntityResult(ReturnStatus.NAMESPACE_NOT_EMPTY, null); + return new DropEntityResult(BaseResult.ReturnStatus.NAMESPACE_NOT_EMPTY, null); } // get the list of catalog roles, at most 2 @@ -1488,7 +1489,7 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str // if we have 2, we cannot drop the catalog. If only one left, better be the admin role if (catalogRoles.size() > 1) { - return new DropEntityResult(ReturnStatus.CATALOG_NOT_EMPTY, null); + return new DropEntityResult(BaseResult.ReturnStatus.CATALOG_NOT_EMPTY, null); } // if 1, drop the last catalog role. Should be the catalog admin role but don't validate this @@ -1499,7 +1500,7 @@ public Map deserializeProperties(PolarisCallContext callCtx, Str } else if (refreshEntityToDrop.getType() == PolarisEntityType.NAMESPACE) { if (ms.hasChildren( callCtx, null, refreshEntityToDrop.getCatalogId(), refreshEntityToDrop.getId())) { - return new DropEntityResult(ReturnStatus.NAMESPACE_NOT_EMPTY, null); + return new DropEntityResult(BaseResult.ReturnStatus.NAMESPACE_NOT_EMPTY, null); } } @@ -1660,7 +1661,7 @@ private PolarisEntityResolver resolveSecurableToRoleGrant( // if failure to resolve, let the caller know if (resolver.isFailure()) { - return new PrivilegeResult(ReturnStatus.ENTITY_CANNOT_BE_RESOLVED, null); + return new PrivilegeResult(BaseResult.ReturnStatus.ENTITY_CANNOT_BE_RESOLVED, null); } // the usage privilege to grant @@ -1710,7 +1711,7 @@ private PolarisEntityResolver resolveSecurableToRoleGrant( // if failure to resolve, let the caller know if (resolver.isFailure()) { - return new PrivilegeResult(ReturnStatus.ENTITY_CANNOT_BE_RESOLVED, null); + return new PrivilegeResult(BaseResult.ReturnStatus.ENTITY_CANNOT_BE_RESOLVED, null); } // the usage privilege to revoke @@ -1731,7 +1732,7 @@ private PolarisEntityResolver resolveSecurableToRoleGrant( // this is not a really bad error, no-op really if (grantRecord == null) { - return new PrivilegeResult(ReturnStatus.GRANT_NOT_FOUND, null); + return new PrivilegeResult(BaseResult.ReturnStatus.GRANT_NOT_FOUND, null); } // revoke usage on the role from the grantee @@ -1773,7 +1774,7 @@ private PolarisEntityResolver resolveSecurableToRoleGrant( // if failure to resolve, let the caller know if (resolver.isFailure()) { - return new PrivilegeResult(ReturnStatus.ENTITY_CANNOT_BE_RESOLVED, null); + return new PrivilegeResult(BaseResult.ReturnStatus.ENTITY_CANNOT_BE_RESOLVED, null); } // grant specified privilege on this securable to this role and return the grant @@ -1819,7 +1820,7 @@ private PolarisEntityResolver resolveSecurableToRoleGrant( // if failure to resolve, let the caller know if (resolver.isFailure()) { - return new PrivilegeResult(ReturnStatus.ENTITY_CANNOT_BE_RESOLVED, null); + return new PrivilegeResult(BaseResult.ReturnStatus.ENTITY_CANNOT_BE_RESOLVED, null); } // lookup the grants records to find this grant @@ -1834,7 +1835,7 @@ private PolarisEntityResolver resolveSecurableToRoleGrant( // the grant does not exist, nothing to do really if (grantRecord == null) { - return new PrivilegeResult(ReturnStatus.GRANT_NOT_FOUND, null); + return new PrivilegeResult(BaseResult.ReturnStatus.GRANT_NOT_FOUND, null); } // revoke the specified privilege on this securable from this role @@ -1876,7 +1877,7 @@ private PolarisEntityResolver resolveSecurableToRoleGrant( // return null if securable does not exists if (grantsVersion == 0) { - return new LoadGrantsResult(ReturnStatus.ENTITY_NOT_FOUND, null); + return new LoadGrantsResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null); } // now fetch all grants for this securable @@ -1925,7 +1926,7 @@ private PolarisEntityResolver resolveSecurableToRoleGrant( // return null if grantee does not exists if (grantsVersion == 0) { - return new LoadGrantsResult(ReturnStatus.ENTITY_NOT_FOUND, null); + return new LoadGrantsResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null); } // now fetch all grants for this grantee @@ -1994,7 +1995,7 @@ private PolarisEntityResolver resolveSecurableToRoleGrant( PolarisBaseEntity entity = ms.lookupEntity(callCtx, entityCatalogId, entityId); return (entity != null) ? new EntityResult(entity) - : new EntityResult(ReturnStatus.ENTITY_NOT_FOUND, null); + : new EntityResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null); } /** {@inheritDoc} */ @@ -2087,7 +2088,7 @@ private PolarisEntityResolver resolveSecurableToRoleGrant( // reload the entity, error out if not found EntityResult reloadedEntity = loadEntity(callCtx, catalogId, entityId); - if (reloadedEntity.getReturnStatus() != ReturnStatus.SUCCESS) { + if (reloadedEntity.getReturnStatus() != BaseResult.ReturnStatus.SUCCESS) { return new ScopedCredentialsResult( reloadedEntity.getReturnStatus(), reloadedEntity.getExtraInformation()); } @@ -2118,7 +2119,8 @@ private PolarisEntityResolver resolveSecurableToRoleGrant( allowedWriteLocations); return new ScopedCredentialsResult(creds); } catch (Exception ex) { - return new ScopedCredentialsResult(ReturnStatus.SUBSCOPE_CREDS_ERROR, ex.getMessage()); + return new ScopedCredentialsResult( + BaseResult.ReturnStatus.SUBSCOPE_CREDS_ERROR, ex.getMessage()); } } @@ -2139,7 +2141,7 @@ private PolarisEntityResolver resolveSecurableToRoleGrant( "locations_and_operations_privileges_are_required"); // reload the entity, error out if not found EntityResult reloadedEntity = loadEntity(callCtx, catalogId, entityId); - if (reloadedEntity.getReturnStatus() != ReturnStatus.SUCCESS) { + if (reloadedEntity.getReturnStatus() != BaseResult.ReturnStatus.SUCCESS) { return new ValidateAccessResult( reloadedEntity.getReturnStatus(), reloadedEntity.getExtraInformation()); } @@ -2222,7 +2224,7 @@ public Map getInternalPropertyMap( // if entity not found, return null if (entity == null) { - return new CachedEntryResult(ReturnStatus.ENTITY_NOT_FOUND, null); + return new CachedEntryResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null); } // load the grant records @@ -2252,7 +2254,7 @@ public Map getInternalPropertyMap( } /** {@link #loadCachedEntryById(PolarisCallContext, long, long)} */ - private @NotNull PolarisMetaStoreManager.CachedEntryResult loadCachedEntryByName( + private @NotNull CachedEntryResult loadCachedEntryByName( @NotNull PolarisCallContext callCtx, @NotNull PolarisMetaStoreSession ms, long entityCatalogId, @@ -2267,7 +2269,7 @@ public Map getInternalPropertyMap( // null if entity not found if (entity == null) { - return new CachedEntryResult(ReturnStatus.ENTITY_NOT_FOUND, null); + return new CachedEntryResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null); } // load the grant records @@ -2369,7 +2371,7 @@ public Map getInternalPropertyMap( // if null, the entity has been purged if (entityVersions == null) { - return new CachedEntryResult(ReturnStatus.ENTITY_NOT_FOUND, null); + return new CachedEntryResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null); } // load the entity if something changed @@ -2379,7 +2381,7 @@ public Map getInternalPropertyMap( // if not found, return null if (entity == null) { - return new CachedEntryResult(ReturnStatus.ENTITY_NOT_FOUND, null); + return new CachedEntryResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null); } } else { // entity has not changed, no need to reload it @@ -2406,7 +2408,7 @@ public Map getInternalPropertyMap( /** {@inheritDoc} */ @Override - public @NotNull PolarisMetaStoreManager.CachedEntryResult refreshCachedEntity( + public @NotNull CachedEntryResult refreshCachedEntity( @NotNull PolarisCallContext callCtx, int entityVersion, int entityGrantRecordsVersion, diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/TransactionWorkspaceMetaStoreManager.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/TransactionWorkspaceMetaStoreManager.java index e2ae841468..92466b2f67 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/TransactionWorkspaceMetaStoreManager.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/TransactionWorkspaceMetaStoreManager.java @@ -31,6 +31,7 @@ import org.apache.polaris.core.entity.PolarisEntitySubType; import org.apache.polaris.core.entity.PolarisEntityType; import org.apache.polaris.core.entity.PolarisPrivilege; +import org.apache.polaris.core.persistence.cache.PolarisRemoteCache.CachedEntryResult; import org.apache.polaris.core.storage.PolarisStorageActions; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -335,7 +336,7 @@ public ValidateAccessResult validateAccessToLocations( } @Override - public PolarisMetaStoreManager.CachedEntryResult loadCachedEntryById( + public CachedEntryResult loadCachedEntryById( @NotNull PolarisCallContext callCtx, long entityCatalogId, long entityId) { callCtx .getDiagServices() @@ -344,7 +345,7 @@ public PolarisMetaStoreManager.CachedEntryResult loadCachedEntryById( } @Override - public PolarisMetaStoreManager.CachedEntryResult loadCachedEntryByName( + public CachedEntryResult loadCachedEntryByName( @NotNull PolarisCallContext callCtx, long entityCatalogId, long parentId, @@ -357,7 +358,7 @@ public PolarisMetaStoreManager.CachedEntryResult loadCachedEntryByName( } @Override - public PolarisMetaStoreManager.CachedEntryResult refreshCachedEntity( + public CachedEntryResult refreshCachedEntity( @NotNull PolarisCallContext callCtx, int entityVersion, int entityGrantRecordsVersion, diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/EntityCache.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/EntityCache.java index 3c34b3a992..3966b1e364 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/EntityCache.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/EntityCache.java @@ -29,7 +29,7 @@ import org.apache.polaris.core.entity.PolarisBaseEntity; import org.apache.polaris.core.entity.PolarisEntityType; import org.apache.polaris.core.entity.PolarisGrantRecord; -import org.apache.polaris.core.persistence.PolarisMetaStoreManager; +import org.apache.polaris.core.persistence.cache.PolarisRemoteCache.CachedEntryResult; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -40,7 +40,7 @@ public class EntityCache { private EntityCacheMode cacheMode; // the meta store manager - private final PolarisMetaStoreManager metaStoreManager; + private final PolarisRemoteCache polarisRemoteCache; // Caffeine cache to keep entries by id private final Cache byId; @@ -51,9 +51,9 @@ public class EntityCache { /** * Constructor. Cache can be private or shared * - * @param metaStoreManager the meta store manager implementation + * @param polarisRemoteCache the meta store manager implementation */ - public EntityCache(@NotNull PolarisMetaStoreManager metaStoreManager) { + public EntityCache(@NotNull PolarisRemoteCache polarisRemoteCache) { // by name cache this.byName = new ConcurrentHashMap<>(); @@ -80,7 +80,7 @@ public EntityCache(@NotNull PolarisMetaStoreManager metaStoreManager) { .build(); // remember the meta store manager - this.metaStoreManager = metaStoreManager; + this.polarisRemoteCache = polarisRemoteCache; // enabled by default this.cacheMode = EntityCacheMode.ENABLE; @@ -281,7 +281,7 @@ && isNewer(existingCacheEntry, existingCacheEntryByName)) { || existingCacheEntry.getEntity().getGrantRecordsVersion() < entityGrantRecordsMinVersion) { // the refreshed entity - final PolarisMetaStoreManager.CachedEntryResult refreshedCacheEntry; + final CachedEntryResult refreshedCacheEntry; // was not found in the cache? final PolarisBaseEntity entity; @@ -290,7 +290,7 @@ && isNewer(existingCacheEntry, existingCacheEntryByName)) { if (existingCacheEntry == null) { // try to load it refreshedCacheEntry = - this.metaStoreManager.loadCachedEntryById(callContext, entityCatalogId, entityId); + this.polarisRemoteCache.loadCachedEntryById(callContext, entityCatalogId, entityId); if (refreshedCacheEntry.isSuccess()) { entity = refreshedCacheEntry.getEntity(); grantRecords = refreshedCacheEntry.getEntityGrantRecords(); @@ -301,7 +301,7 @@ && isNewer(existingCacheEntry, existingCacheEntryByName)) { } else { // refresh it refreshedCacheEntry = - this.metaStoreManager.refreshCachedEntity( + this.polarisRemoteCache.refreshCachedEntity( callContext, existingCacheEntry.getEntity().getEntityVersion(), existingCacheEntry.getEntity().getGrantRecordsVersion(), @@ -378,8 +378,8 @@ && isNewer(existingCacheEntry, existingCacheEntryByName)) { cacheHit = false; // load it - PolarisMetaStoreManager.CachedEntryResult result = - metaStoreManager.loadCachedEntryById(callContext, entityCatalogId, entityId); + CachedEntryResult result = + polarisRemoteCache.loadCachedEntryById(callContext, entityCatalogId, entityId); // not found, exit if (!result.isSuccess()) { @@ -430,8 +430,8 @@ && isNewer(existingCacheEntry, existingCacheEntryByName)) { cacheHit = false; // load it - PolarisMetaStoreManager.CachedEntryResult result = - metaStoreManager.loadCachedEntryByName( + CachedEntryResult result = + polarisRemoteCache.loadCachedEntryByName( callContext, entityNameKey.getCatalogId(), entityNameKey.getParentId(), diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/PolarisRemoteCache.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/PolarisRemoteCache.java new file mode 100644 index 0000000000..ca5054ae80 --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/PolarisRemoteCache.java @@ -0,0 +1,232 @@ +/* + * 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.persistence.cache; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import org.apache.polaris.core.PolarisCallContext; +import org.apache.polaris.core.entity.PolarisBaseEntity; +import org.apache.polaris.core.entity.PolarisChangeTrackingVersions; +import org.apache.polaris.core.entity.PolarisEntityId; +import org.apache.polaris.core.entity.PolarisEntityType; +import org.apache.polaris.core.entity.PolarisGrantRecord; +import org.apache.polaris.core.persistence.BaseResult; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Interface to the remote entity cache. This allows the local cache to detect remote entity changes + * and refresh the local copies where necessary. + */ +public interface PolarisRemoteCache { + /** + * Load change tracking information for a set of entities in one single shot and return for each + * the version for the entity itself and the version associated to its grant records. + * + * @param callCtx call context + * @param entityIds list of catalog/entity pair ids for which we need to efficiently load the + * version information, both entity version and grant records version. + * @return a list of version tracking information. Order in that returned list is the same as the + * input list. Some elements might be NULL if the entity has been purged. Not expected to fail + */ + @NotNull + ChangeTrackingResult loadEntitiesChangeTracking( + @NotNull PolarisCallContext callCtx, @NotNull List entityIds); + + /** + * Load a cached entry, i.e. an entity definition and associated grant records, from the backend + * store. The entity is identified by its id (entity catalog id and id). + * + *

For entities that can be grantees, the associated grant records will include both the grant + * records for this entity as a grantee and for this entity as a securable. + * + * @param callCtx call context + * @param entityCatalogId id of the catalog for that entity + * @param entityId id of the entity + * @return cached entry for this entity. Status will be ENTITY_NOT_FOUND if the entity was not + * found + */ + @NotNull + CachedEntryResult loadCachedEntryById( + @NotNull PolarisCallContext callCtx, long entityCatalogId, long entityId); + + /** + * Load a cached entry, i.e. an entity definition and associated grant records, from the backend + * store. The entity is identified by its name. Will return NULL if the entity does not exist, + * i.e. has been purged or dropped. + * + *

For entities that can be grantees, the associated grant records will include both the grant + * records for this entity as a grantee and for this entity as a securable. + * + * @param callCtx call context + * @param entityCatalogId id of the catalog for that entity + * @param parentId the id of the parent of that entity + * @param entityType the type of this entity + * @param entityName the name of this entity + * @return cached entry for this entity. Status will be ENTITY_NOT_FOUND if the entity was not + * found + */ + @NotNull + CachedEntryResult loadCachedEntryByName( + @NotNull PolarisCallContext callCtx, + long entityCatalogId, + long parentId, + @NotNull PolarisEntityType entityType, + @NotNull String entityName); + + /** + * Refresh a cached entity from the backend store. Will return NULL if the entity does not exist, + * i.e. has been purged or dropped. Else, will determine what has changed based on the version + * information sent by the caller and will return only what has changed. + * + *

For entities that can be grantees, the associated grant records will include both the grant + * records for this entity as a grantee and for this entity as a securable. + * + * @param callCtx call context + * @param entityType type of the entity whose cached entry we are refreshing + * @param entityCatalogId id of the catalog for that entity + * @param entityId the id of the entity to load + * @return cached entry for this entity. Status will be ENTITY_NOT_FOUND if the entity was not * + * found + */ + @NotNull + CachedEntryResult refreshCachedEntity( + @NotNull PolarisCallContext callCtx, + int entityVersion, + int entityGrantRecordsVersion, + @NotNull PolarisEntityType entityType, + long entityCatalogId, + long entityId); + + /** Result of a loadEntitiesChangeTracking call */ + class ChangeTrackingResult extends BaseResult { + + // null if not success. Else, will be null if the grant to revoke was not found + private final List changeTrackingVersions; + + /** + * Constructor for an error + * + * @param errorCode error code, cannot be SUCCESS + * @param extraInformation extra information + */ + public ChangeTrackingResult( + @NotNull BaseResult.ReturnStatus errorCode, @Nullable String extraInformation) { + super(errorCode, extraInformation); + this.changeTrackingVersions = null; + } + + /** + * Constructor for success + * + * @param changeTrackingVersions change tracking versions + */ + public ChangeTrackingResult( + @NotNull List changeTrackingVersions) { + super(BaseResult.ReturnStatus.SUCCESS); + this.changeTrackingVersions = changeTrackingVersions; + } + + @JsonCreator + private ChangeTrackingResult( + @JsonProperty("returnStatus") @NotNull BaseResult.ReturnStatus returnStatus, + @JsonProperty("extraInformation") String extraInformation, + @JsonProperty("changeTrackingVersions") + List changeTrackingVersions) { + super(returnStatus, extraInformation); + this.changeTrackingVersions = changeTrackingVersions; + } + + public List getChangeTrackingVersions() { + return changeTrackingVersions; + } + } + + /** + * Represents an entry in the cache. If we refresh a cached entry, we will only refresh the + * information which have changed, based on the version of the entity + */ + class CachedEntryResult extends BaseResult { + + // the entity itself if it was loaded + private final @Nullable PolarisBaseEntity entity; + + // version for the grant records, in case the entity was not loaded + private final int grantRecordsVersion; + + private final @Nullable List entityGrantRecords; + + /** + * Constructor for an error + * + * @param errorCode error code, cannot be SUCCESS + * @param extraInformation extra information + */ + public CachedEntryResult( + @NotNull BaseResult.ReturnStatus errorCode, @Nullable String extraInformation) { + super(errorCode, extraInformation); + this.entity = null; + this.entityGrantRecords = null; + this.grantRecordsVersion = 0; + } + + /** + * Constructor with success + * + * @param entity the entity for that cached entry + * @param grantRecordsVersion the version of the grant records + * @param entityGrantRecords the list of grant records + */ + public CachedEntryResult( + @Nullable PolarisBaseEntity entity, + int grantRecordsVersion, + @Nullable List entityGrantRecords) { + super(BaseResult.ReturnStatus.SUCCESS); + this.entity = entity; + this.entityGrantRecords = entityGrantRecords; + this.grantRecordsVersion = grantRecordsVersion; + } + + @JsonCreator + public CachedEntryResult( + @JsonProperty("returnStatus") @NotNull BaseResult.ReturnStatus returnStatus, + @JsonProperty("extraInformation") String extraInformation, + @Nullable @JsonProperty("entity") PolarisBaseEntity entity, + @JsonProperty("grantRecordsVersion") int grantRecordsVersion, + @Nullable @JsonProperty("entityGrantRecords") List entityGrantRecords) { + super(returnStatus, extraInformation); + this.entity = entity; + this.entityGrantRecords = entityGrantRecords; + this.grantRecordsVersion = grantRecordsVersion; + } + + public @Nullable PolarisBaseEntity getEntity() { + return entity; + } + + public int getGrantRecordsVersion() { + return grantRecordsVersion; + } + + public @Nullable List getEntityGrantRecords() { + return entityGrantRecords; + } + } +} diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/Resolver.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/Resolver.java index affdf14bfa..be6311b255 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/Resolver.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/Resolver.java @@ -36,11 +36,12 @@ import org.apache.polaris.core.entity.PolarisEntityType; import org.apache.polaris.core.entity.PolarisGrantRecord; import org.apache.polaris.core.entity.PolarisPrivilege; -import org.apache.polaris.core.persistence.PolarisMetaStoreManager; import org.apache.polaris.core.persistence.cache.EntityCache; import org.apache.polaris.core.persistence.cache.EntityCacheByNameKey; import org.apache.polaris.core.persistence.cache.EntityCacheEntry; import org.apache.polaris.core.persistence.cache.EntityCacheLookupResult; +import org.apache.polaris.core.persistence.cache.PolarisRemoteCache; +import org.apache.polaris.core.persistence.cache.PolarisRemoteCache.ChangeTrackingResult; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -57,7 +58,7 @@ public class Resolver { private final @NotNull PolarisDiagnostics diagnostics; // the polaris metastore manager - private final @NotNull PolarisMetaStoreManager metaStoreManager; + private final @NotNull PolarisRemoteCache polarisRemoteCache; // the cache of entities private final @NotNull EntityCache cache; @@ -110,7 +111,7 @@ public class Resolver { * Constructor, effectively starts an entity resolver session * * @param polarisCallContext the polaris call context - * @param metaStoreManager meta store manager + * @param polarisRemoteCache meta store manager * @param callerPrincipalId if not 0, the id of the principal calling the service * @param callerPrincipalName if callerPrincipalId is 0, the name of the principal calling the * service @@ -126,7 +127,7 @@ public class Resolver { */ public Resolver( @NotNull PolarisCallContext polarisCallContext, - @NotNull PolarisMetaStoreManager metaStoreManager, + @NotNull PolarisRemoteCache polarisRemoteCache, long callerPrincipalId, @Nullable String callerPrincipalName, @Nullable Set callerPrincipalRoleNamesScope, @@ -134,7 +135,7 @@ public Resolver( @Nullable String referenceCatalogName) { this.polarisCallContext = polarisCallContext; this.diagnostics = polarisCallContext.getDiagServices(); - this.metaStoreManager = metaStoreManager; + this.polarisRemoteCache = polarisRemoteCache; this.cache = cache; this.callerPrincipalName = callerPrincipalName; this.callerPrincipalId = callerPrincipalId; @@ -144,7 +145,7 @@ public Resolver( this.callerPrincipalRoleNamesScope = callerPrincipalRoleNamesScope; // validate inputs - this.diagnostics.checkNotNull(metaStoreManager, "unexpected_null_metaStoreManager"); + this.diagnostics.checkNotNull(polarisRemoteCache, "unexpected_null_polarisRemoteCache"); this.diagnostics.checkNotNull(cache, "unexpected_null_cache"); this.diagnostics.check( callerPrincipalId != 0 || callerPrincipalName != null, "principal_must_be_specified"); @@ -534,8 +535,8 @@ private boolean bulkValidate(List toValidate) { .collect(Collectors.toList()); // now get the current backend versions of all these entities - PolarisMetaStoreManager.ChangeTrackingResult changeTrackingResult = - this.metaStoreManager.loadEntitiesChangeTracking(this.polarisCallContext, entityIds); + ChangeTrackingResult changeTrackingResult = + this.polarisRemoteCache.loadEntitiesChangeTracking(this.polarisCallContext, entityIds); // refresh any entity which is not fresh. If an entity is missing, reload it Iterator entityIterator = toValidate.iterator(); diff --git a/polaris-core/src/main/java/org/apache/polaris/core/storage/PolarisCredentialVendor.java b/polaris-core/src/main/java/org/apache/polaris/core/storage/PolarisCredentialVendor.java new file mode 100644 index 0000000000..c4d6989ed7 --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/storage/PolarisCredentialVendor.java @@ -0,0 +1,185 @@ +/* + * 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.storage; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.EnumMap; +import java.util.Map; +import java.util.Set; +import org.apache.polaris.core.PolarisCallContext; +import org.apache.polaris.core.persistence.BaseResult; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** Manage credentials for storage locations. */ +public interface PolarisCredentialVendor { + /** + * Get a sub-scoped credentials for an entity against the provided allowed read and write + * locations. + * + * @param callCtx the polaris call context + * @param catalogId the catalog id + * @param entityId the entity id + * @param allowListOperation whether to allow LIST operation on the allowedReadLocations and + * allowedWriteLocations + * @param allowedReadLocations a set of allowed to read locations + * @param allowedWriteLocations a set of allowed to write locations + * @return an enum map containing the scoped credentials + */ + @NotNull + ScopedCredentialsResult getSubscopedCredsForEntity( + @NotNull PolarisCallContext callCtx, + long catalogId, + long entityId, + boolean allowListOperation, + @NotNull Set allowedReadLocations, + @NotNull Set allowedWriteLocations); + + /** + * Validate whether the entity has access to the locations with the provided target operations + * + * @param callCtx the polaris call context + * @param catalogId the catalog id + * @param entityId the entity id + * @param actions a set of operation actions: READ/WRITE/LIST/DELETE/ALL + * @param locations a set of locations to verify + * @return a Map of {@code }, a validate result value looks like this + *

+   * {
+   *   "status" : "failure",
+   *   "actions" : {
+   *     "READ" : {
+   *       "message" : "The specified file was not found",
+   *       "status" : "failure"
+   *     },
+   *     "DELETE" : {
+   *       "message" : "One or more objects could not be deleted (Status Code: 200; Error Code: null)",
+   *       "status" : "failure"
+   *     },
+   *     "LIST" : {
+   *       "status" : "success"
+   *     },
+   *     "WRITE" : {
+   *       "message" : "Access Denied (Status Code: 403; Error Code: AccessDenied)",
+   *       "status" : "failure"
+   *     }
+   *   },
+   *   "message" : "Some of the integration checks failed. Check the Polaris documentation for more information."
+   * }
+   * 
+ */ + @NotNull + ValidateAccessResult validateAccessToLocations( + @NotNull PolarisCallContext callCtx, + long catalogId, + long entityId, + @NotNull Set actions, + @NotNull Set locations); + + /** Result of a getSubscopedCredsForEntity() call */ + class ScopedCredentialsResult extends BaseResult { + + // null if not success. Else, set of name/value pairs for the credentials + private final EnumMap credentials; + + /** + * Constructor for an error + * + * @param errorCode error code, cannot be SUCCESS + * @param extraInformation extra information + */ + public ScopedCredentialsResult( + @NotNull BaseResult.ReturnStatus errorCode, @Nullable String extraInformation) { + super(errorCode, extraInformation); + this.credentials = null; + } + + /** + * Constructor for success + * + * @param credentials credentials + */ + public ScopedCredentialsResult( + @NotNull EnumMap credentials) { + super(BaseResult.ReturnStatus.SUCCESS); + this.credentials = credentials; + } + + @JsonCreator + private ScopedCredentialsResult( + @JsonProperty("returnStatus") @NotNull BaseResult.ReturnStatus returnStatus, + @JsonProperty("extraInformation") String extraInformation, + @JsonProperty("credentials") Map credentials) { + super(returnStatus, extraInformation); + this.credentials = new EnumMap<>(PolarisCredentialProperty.class); + if (credentials != null) { + credentials.forEach( + (k, v) -> this.credentials.put(PolarisCredentialProperty.valueOf(k), v)); + } + } + + public EnumMap getCredentials() { + return credentials; + } + } + + /** Result of a validateAccessToLocations() call */ + class ValidateAccessResult extends BaseResult { + + // null if not success. Else, set of location/validationResult pairs for each location in the + // set + private final Map validateResult; + + /** + * Constructor for an error + * + * @param errorCode error code, cannot be SUCCESS + * @param extraInformation extra information + */ + public ValidateAccessResult( + @NotNull BaseResult.ReturnStatus errorCode, @Nullable String extraInformation) { + super(errorCode, extraInformation); + this.validateResult = null; + } + + /** + * Constructor for success + * + * @param validateResult validate result + */ + public ValidateAccessResult(@NotNull Map validateResult) { + super(BaseResult.ReturnStatus.SUCCESS); + this.validateResult = validateResult; + } + + @JsonCreator + private ValidateAccessResult( + @JsonProperty("returnStatus") @NotNull BaseResult.ReturnStatus returnStatus, + @JsonProperty("extraInformation") String extraInformation, + @JsonProperty("validateResult") Map validateResult) { + super(returnStatus, extraInformation); + this.validateResult = validateResult; + } + + public Map getValidateResult() { + return this.validateResult; + } + } +} diff --git a/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCache.java b/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCache.java index 65e287fb34..7a058442c9 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCache.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCache.java @@ -30,7 +30,7 @@ import org.apache.polaris.core.PolarisCallContext; import org.apache.polaris.core.entity.PolarisEntity; import org.apache.polaris.core.entity.PolarisEntityType; -import org.apache.polaris.core.persistence.PolarisMetaStoreManager; +import org.apache.polaris.core.storage.PolarisCredentialVendor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.VisibleForTesting; import org.slf4j.Logger; @@ -94,7 +94,7 @@ public long expireAfterRead( /** * Either get from the cache or generate a new entry for a scoped creds * - * @param metaStoreManager the meta storage manager used to generate a new scoped creds if needed + * @param credentialVendor the credential vendor used to generate a new scoped creds if needed * @param callCtx the call context * @param polarisEntity the polaris entity that is going to scoped creds * @param allowListOperation whether allow list action on the provided read and write locations @@ -103,7 +103,7 @@ public long expireAfterRead( * @return the a map of string containing the scoped creds information */ public Map getOrGenerateSubScopeCreds( - @NotNull PolarisMetaStoreManager metaStoreManager, + @NotNull PolarisCredentialVendor credentialVendor, @NotNull PolarisCallContext callCtx, @NotNull PolarisEntity polarisEntity, boolean allowListOperation, @@ -125,8 +125,8 @@ public Map getOrGenerateSubScopeCreds( Function loader = k -> { LOGGER.atDebug().log("StorageCredentialCache::load"); - PolarisMetaStoreManager.ScopedCredentialsResult scopedCredentialsResult = - metaStoreManager.getSubscopedCredsForEntity( + PolarisCredentialVendor.ScopedCredentialsResult scopedCredentialsResult = + credentialVendor.getSubscopedCredsForEntity( k.getCallContext(), k.getCatalogId(), k.getEntityId(), diff --git a/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheEntry.java b/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheEntry.java index 4db7b81ab4..6417cde0f7 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheEntry.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheEntry.java @@ -21,18 +21,18 @@ import java.util.EnumMap; import java.util.HashMap; import java.util.Map; -import org.apache.polaris.core.persistence.PolarisMetaStoreManager; import org.apache.polaris.core.storage.PolarisCredentialProperty; +import org.apache.polaris.core.storage.PolarisCredentialVendor; /** A storage credential cached entry. */ public class StorageCredentialCacheEntry { /** The scoped creds map that is fetched from a creds vending service */ public final EnumMap credsMap; - private final PolarisMetaStoreManager.ScopedCredentialsResult scopedCredentialsResult; + private final PolarisCredentialVendor.ScopedCredentialsResult scopedCredentialsResult; public StorageCredentialCacheEntry( - PolarisMetaStoreManager.ScopedCredentialsResult scopedCredentialsResult) { + PolarisCredentialVendor.ScopedCredentialsResult scopedCredentialsResult) { this.scopedCredentialsResult = scopedCredentialsResult; this.credsMap = scopedCredentialsResult.getCredentials(); } diff --git a/polaris-core/src/test/java/org/apache/polaris/core/persistence/ResolverTest.java b/polaris-core/src/test/java/org/apache/polaris/core/persistence/ResolverTest.java index 31369c0692..2c0678fa94 100644 --- a/polaris-core/src/test/java/org/apache/polaris/core/persistence/ResolverTest.java +++ b/polaris-core/src/test/java/org/apache/polaris/core/persistence/ResolverTest.java @@ -35,6 +35,7 @@ import org.apache.polaris.core.entity.PolarisPrivilege; import org.apache.polaris.core.persistence.cache.EntityCache; import org.apache.polaris.core.persistence.cache.EntityCacheEntry; +import org.apache.polaris.core.persistence.cache.PolarisRemoteCache.CachedEntryResult; import org.apache.polaris.core.persistence.resolver.Resolver; import org.apache.polaris.core.persistence.resolver.ResolverPath; import org.apache.polaris.core.persistence.resolver.ResolverStatus; @@ -904,7 +905,7 @@ private void ensureResolved( Assertions.assertThat(refEntity).isNotNull(); // reload the cached entry from the backend - PolarisMetaStoreManager.CachedEntryResult refCachedEntry = + CachedEntryResult refCachedEntry = this.metaStoreManager.loadCachedEntryById( this.callCtx, refEntity.getCatalogId(), refEntity.getId()); diff --git a/polaris-core/src/test/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheTest.java b/polaris-core/src/test/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheTest.java index 206e0d08de..804c282bed 100644 --- a/polaris-core/src/test/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheTest.java +++ b/polaris-core/src/test/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheTest.java @@ -34,6 +34,7 @@ import org.apache.polaris.core.entity.PolarisEntityConstants; import org.apache.polaris.core.entity.PolarisEntitySubType; import org.apache.polaris.core.entity.PolarisEntityType; +import org.apache.polaris.core.persistence.BaseResult; import org.apache.polaris.core.persistence.PolarisMetaStoreManager; import org.apache.polaris.core.persistence.PolarisMetaStoreManagerImpl; import org.apache.polaris.core.persistence.PolarisMetaStoreSession; @@ -41,6 +42,7 @@ import org.apache.polaris.core.persistence.PolarisTreeMapMetaStoreSessionImpl; import org.apache.polaris.core.persistence.PolarisTreeMapStore; import org.apache.polaris.core.storage.PolarisCredentialProperty; +import org.apache.polaris.core.storage.PolarisCredentialVendor.ScopedCredentialsResult; import org.assertj.core.api.Assertions; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.RepeatedTest; @@ -73,9 +75,9 @@ public StorageCredentialCacheTest() { @Test public void testBadResult() { storageCredentialCache = new StorageCredentialCache(); - PolarisMetaStoreManager.ScopedCredentialsResult badResult = - new PolarisMetaStoreManager.ScopedCredentialsResult( - PolarisMetaStoreManager.ReturnStatus.SUBSCOPE_CREDS_ERROR, "extra_error_info"); + ScopedCredentialsResult badResult = + new ScopedCredentialsResult( + BaseResult.ReturnStatus.SUBSCOPE_CREDS_ERROR, "extra_error_info"); Mockito.when( metaStoreManager.getSubscopedCredsForEntity( Mockito.any(), @@ -105,7 +107,7 @@ public void testBadResult() { @Test public void testCacheHit() { storageCredentialCache = new StorageCredentialCache(); - List mockedScopedCreds = + List mockedScopedCreds = getFakeScopedCreds(3, /* expireSoon= */ false); Mockito.when( metaStoreManager.getSubscopedCredsForEntity( @@ -147,8 +149,7 @@ public void testCacheHit() { @RepeatedTest(10) public void testCacheEvict() throws InterruptedException { storageCredentialCache = new StorageCredentialCache(); - List mockedScopedCreds = - getFakeScopedCreds(3, /* expireSoon= */ true); + List mockedScopedCreds = getFakeScopedCreds(3, /* expireSoon= */ true); Mockito.when( metaStoreManager.getSubscopedCredsForEntity( @@ -205,7 +206,7 @@ public void testCacheEvict() throws InterruptedException { @Test public void testCacheGenerateNewEntries() { storageCredentialCache = new StorageCredentialCache(); - List mockedScopedCreds = + List mockedScopedCreds = getFakeScopedCreds(3, /* expireSoon= */ false); Mockito.when( metaStoreManager.getSubscopedCredsForEntity( @@ -291,7 +292,7 @@ public void testCacheGenerateNewEntries() { @Test public void testCacheNotAffectedBy() { storageCredentialCache = new StorageCredentialCache(); - List mockedScopedCreds = + List mockedScopedCreds = getFakeScopedCreds(3, /* expireSoon= */ false); Mockito.when( @@ -369,9 +370,8 @@ public void testCacheNotAffectedBy() { } } - private static List getFakeScopedCreds( - int number, boolean expireSoon) { - List res = new ArrayList<>(); + private static List getFakeScopedCreds(int number, boolean expireSoon) { + List res = new ArrayList<>(); for (int i = 1; i <= number; i = i + 3) { int finalI = i; // NOTE: The default behavior of the Caffeine cache seems to have a bug; if our @@ -386,7 +386,7 @@ private static List getFakeScop ? String.valueOf(System.currentTimeMillis() - 100) : String.valueOf(Long.MAX_VALUE); res.add( - new PolarisMetaStoreManager.ScopedCredentialsResult( + new ScopedCredentialsResult( new EnumMap<>( ImmutableMap.builder() .put(PolarisCredentialProperty.AWS_KEY_ID, "key_id_" + finalI) @@ -395,7 +395,7 @@ private static List getFakeScop .buildOrThrow()))); if (res.size() == number) return res; res.add( - new PolarisMetaStoreManager.ScopedCredentialsResult( + new ScopedCredentialsResult( new EnumMap<>( ImmutableMap.builder() .put(PolarisCredentialProperty.AZURE_SAS_TOKEN, "sas_token_" + finalI) @@ -404,7 +404,7 @@ private static List getFakeScop .buildOrThrow()))); if (res.size() == number) return res; res.add( - new PolarisMetaStoreManager.ScopedCredentialsResult( + new ScopedCredentialsResult( new EnumMap<>( ImmutableMap.builder() .put(PolarisCredentialProperty.GCS_ACCESS_TOKEN, "gcs_token_" + finalI) diff --git a/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/PolarisTestMetaStoreManager.java b/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/PolarisTestMetaStoreManager.java index fe44f38e81..53b0e8408f 100644 --- a/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/PolarisTestMetaStoreManager.java +++ b/polaris-core/src/testFixtures/java/org/apache/polaris/core/persistence/PolarisTestMetaStoreManager.java @@ -28,6 +28,7 @@ import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.apache.polaris.core.PolarisCallContext; +import org.apache.polaris.core.auth.PolarisGrantManager.LoadGrantsResult; import org.apache.polaris.core.entity.PolarisBaseEntity; import org.apache.polaris.core.entity.PolarisChangeTrackingVersions; import org.apache.polaris.core.entity.PolarisEntity; @@ -41,6 +42,7 @@ import org.apache.polaris.core.entity.PolarisPrincipalSecrets; import org.apache.polaris.core.entity.PolarisPrivilege; import org.apache.polaris.core.entity.PolarisTaskConstants; +import org.apache.polaris.core.persistence.cache.PolarisRemoteCache.CachedEntryResult; import org.assertj.core.api.Assertions; import org.jetbrains.annotations.NotNull; @@ -255,7 +257,7 @@ void ensureGrantRecordExists( Assertions.assertThat(grantee.getType().isGrantee()).isTrue(); // load all grant records on that securable, should not fail - PolarisMetaStoreManager.LoadGrantsResult loadGrantsOnSecurable = + LoadGrantsResult loadGrantsOnSecurable = polarisMetaStoreManager.loadGrantsOnSecurable( this.polarisCallContext, securable.getCatalogId(), securable.getId()); // ensure entities for these grant records have been properly loaded @@ -265,7 +267,7 @@ void ensureGrantRecordExists( this.checkGrantRecordExists(loadGrantsOnSecurable.getGrantRecords(), securable, grantee, priv); // load all grant records on that grantee, should not fail - PolarisMetaStoreManager.LoadGrantsResult loadGrantsOnGrantee = + LoadGrantsResult loadGrantsOnGrantee = polarisMetaStoreManager.loadGrantsToGrantee( this.polarisCallContext, grantee.getCatalogId(), grantee.getId()); // ensure entities for these grant records have been properly loaded @@ -282,8 +284,7 @@ void ensureGrantRecordExists( * @param isGrantee if true, loadGrantsToGrantee() was called, else loadGrantsOnSecurable() was * called */ - private void validateLoadedGrants( - PolarisMetaStoreManager.LoadGrantsResult loadGrantRecords, boolean isGrantee) { + private void validateLoadedGrants(LoadGrantsResult loadGrantRecords, boolean isGrantee) { // ensure not null Assertions.assertThat(loadGrantRecords).isNotNull(); @@ -333,7 +334,7 @@ void ensureGrantRecordRemoved( Assertions.assertThat(grantee.getType().isGrantee()).isTrue(); // load all grant records on that securable, should not fail - PolarisMetaStoreManager.LoadGrantsResult loadGrantsOnSecurable = + LoadGrantsResult loadGrantsOnSecurable = polarisMetaStoreManager.loadGrantsOnSecurable( this.polarisCallContext, securable.getCatalogId(), securable.getId()); // ensure entities for these grant records have been properly loaded @@ -343,7 +344,7 @@ void ensureGrantRecordRemoved( this.checkGrantRecordRemoved(loadGrantsOnSecurable.getGrantRecords(), securable, grantee, priv); // load all grant records on that grantee, should not fail - PolarisMetaStoreManager.LoadGrantsResult loadGrantsOnGrantee = + LoadGrantsResult loadGrantsOnGrantee = polarisMetaStoreManager.loadGrantsToGrantee( this.polarisCallContext, grantee.getCatalogId(), grantee.getId()); this.validateLoadedGrants(loadGrantsOnGrantee, true); @@ -787,12 +788,12 @@ void dropEntity(List catalogPath, PolarisEntityCore entityToD // should not be found Assertions.assertThat(entityFound.getReturnStatus()) - .isEqualTo(PolarisMetaStoreManager.ReturnStatus.ENTITY_NOT_FOUND); + .isEqualTo(BaseResult.ReturnStatus.ENTITY_NOT_FOUND); // make sure that the entity which was dropped is no longer referenced by a grant with any // of the entity it was connected with before being dropped for (PolarisBaseEntity connectedEntity : granteeEntities) { - PolarisMetaStoreManager.LoadGrantsResult grantResult = + LoadGrantsResult grantResult = polarisMetaStoreManager.loadGrantsToGrantee( this.polarisCallContext, connectedEntity.getCatalogId(), connectedEntity.getId()); if (grantResult.isSuccess()) { @@ -804,8 +805,7 @@ void dropEntity(List catalogPath, PolarisEntityCore entityToD } else { // special case when a catalog is dropped, the catalog_admin role is also dropped with it Assertions.assertThat( - grantResult.getReturnStatus() - == PolarisMetaStoreManager.ReturnStatus.ENTITY_NOT_FOUND + grantResult.getReturnStatus() == BaseResult.ReturnStatus.ENTITY_NOT_FOUND && entityToDrop.getType() == PolarisEntityType.CATALOG && connectedEntity.getType() == PolarisEntityType.CATALOG_ROLE && connectedEntity @@ -815,7 +815,7 @@ void dropEntity(List catalogPath, PolarisEntityCore entityToD } } for (PolarisBaseEntity connectedEntity : securableEntities) { - PolarisMetaStoreManager.LoadGrantsResult grantResult = + LoadGrantsResult grantResult = polarisMetaStoreManager.loadGrantsOnSecurable( this.polarisCallContext, connectedEntity.getCatalogId(), connectedEntity.getId()); long cnt = @@ -1226,7 +1226,7 @@ private void validateListReturn( * * @param cacheEntry the cached entity to validate */ - private void validateCacheEntryLoad(PolarisMetaStoreManager.CachedEntryResult cacheEntry) { + private void validateCacheEntryLoad(CachedEntryResult cacheEntry) { // cannot be null Assertions.assertThat(cacheEntry).isNotNull(); @@ -1254,7 +1254,7 @@ private void validateCacheEntryLoad(PolarisMetaStoreManager.CachedEntryResult ca // reload the grants List refGrantRecords = new ArrayList<>(); if (refEntity.getType().isGrantee()) { - PolarisMetaStoreManager.LoadGrantsResult loadGrantResult = + LoadGrantsResult loadGrantResult = this.polarisMetaStoreManager.loadGrantsToGrantee( this.polarisCallContext, refEntity.getCatalogId(), refEntity.getId()); this.validateLoadedGrants(loadGrantResult, true); @@ -1266,7 +1266,7 @@ private void validateCacheEntryLoad(PolarisMetaStoreManager.CachedEntryResult ca refGrantRecords.addAll(loadGrantResult.getGrantRecords()); } - PolarisMetaStoreManager.LoadGrantsResult loadGrantResult = + LoadGrantsResult loadGrantResult = this.polarisMetaStoreManager.loadGrantsOnSecurable( this.polarisCallContext, refEntity.getCatalogId(), refEntity.getId()); this.validateLoadedGrants(loadGrantResult, false); @@ -1287,7 +1287,7 @@ private void validateCacheEntryLoad(PolarisMetaStoreManager.CachedEntryResult ca * @param cacheEntry the cached entity to validate */ private void validateCacheEntryRefresh( - PolarisMetaStoreManager.CachedEntryResult cacheEntry, + CachedEntryResult cacheEntry, long catalogId, long entityId, int entityVersion, @@ -1305,7 +1305,7 @@ private void validateCacheEntryRefresh( Assertions.assertThat(refEntity).isNotNull(); // reload the grants - PolarisMetaStoreManager.LoadGrantsResult loadGrantResult = + LoadGrantsResult loadGrantResult = refEntity.getType().isGrantee() ? this.polarisMetaStoreManager.loadGrantsToGrantee( this.polarisCallContext, catalogId, entityId) @@ -1362,7 +1362,7 @@ private PolarisBaseEntity loadCacheEntryByName( @NotNull String entityName, boolean expectExists) { // load cached entry - PolarisMetaStoreManager.CachedEntryResult cacheEntry = + CachedEntryResult cacheEntry = this.polarisMetaStoreManager.loadCachedEntryByName( this.polarisCallContext, entityCatalogId, parentId, entityType, entityName); @@ -1408,7 +1408,7 @@ private PolarisBaseEntity loadCacheEntryByName( private PolarisBaseEntity loadCacheEntryById( long entityCatalogId, long entityId, boolean expectExists) { // load cached entry - PolarisMetaStoreManager.CachedEntryResult cacheEntry = + CachedEntryResult cacheEntry = this.polarisMetaStoreManager.loadCachedEntryById( this.polarisCallContext, entityCatalogId, entityId); @@ -1455,7 +1455,7 @@ private void refreshCacheEntry( long entityId, boolean expectExists) { // load cached entry - PolarisMetaStoreManager.CachedEntryResult cacheEntry = + CachedEntryResult cacheEntry = this.polarisMetaStoreManager.refreshCachedEntity( this.polarisCallContext, entityVersion, @@ -1972,7 +1972,7 @@ void testDropEntities() { "test"); // success and not found Assertions.assertThat(catalogFound.getReturnStatus()) - .isEqualTo(PolarisMetaStoreManager.ReturnStatus.ENTITY_NOT_FOUND); + .isEqualTo(BaseResult.ReturnStatus.ENTITY_NOT_FOUND); // drop the principal role PR1 PolarisBaseEntity PR1 = this.ensureExistsByName(null, PolarisEntityType.PRINCIPAL_ROLE, "PR1"); @@ -2052,7 +2052,7 @@ public void testPrivileges() { // assign catalog role to PR9000 grantToGrantee(catalog, R1, PR9000, PolarisPrivilege.CATALOG_ROLE_USAGE); - PolarisMetaStoreManager.LoadGrantsResult loadGrantsResult = + LoadGrantsResult loadGrantsResult = polarisMetaStoreManager.loadGrantsToGrantee(this.polarisCallContext, 0L, PR9000.getId()); this.validateLoadedGrants(loadGrantsResult, true); Assertions.assertThat(loadGrantsResult.getGrantRecords()).hasSize(1); @@ -2110,7 +2110,7 @@ void renameEntity( .getEntity(); // ensure success - if (newNameLookup.getReturnStatus() == PolarisMetaStoreManager.ReturnStatus.ENTITY_NOT_FOUND) { + if (newNameLookup.getReturnStatus() == BaseResult.ReturnStatus.ENTITY_NOT_FOUND) { Assertions.assertThat(renamedEntity).isNotNull(); // ensure it exists @@ -2136,7 +2136,7 @@ void renameEntity( // not found Assertions.assertThat(res.getReturnStatus()) - .isEqualTo(PolarisMetaStoreManager.ReturnStatus.ENTITY_NOT_FOUND); + .isEqualTo(BaseResult.ReturnStatus.ENTITY_NOT_FOUND); } else { // cannot rename since the entity exists Assertions.assertThat(renamedEntity).isNull(); diff --git a/polaris-service/src/main/java/org/apache/polaris/service/BootstrapRealmsCommand.java b/polaris-service/src/main/java/org/apache/polaris/service/BootstrapRealmsCommand.java index e0f5290f98..3f282b042e 100644 --- a/polaris-service/src/main/java/org/apache/polaris/service/BootstrapRealmsCommand.java +++ b/polaris-service/src/main/java/org/apache/polaris/service/BootstrapRealmsCommand.java @@ -23,8 +23,8 @@ import java.util.Map; import net.sourceforge.argparse4j.inf.Namespace; import org.apache.polaris.core.PolarisConfigurationStore; +import org.apache.polaris.core.auth.PolarisSecretsManager.PrincipalSecretsResult; import org.apache.polaris.core.persistence.MetaStoreManagerFactory; -import org.apache.polaris.core.persistence.PolarisMetaStoreManager; import org.apache.polaris.service.config.ConfigurationStoreAware; import org.apache.polaris.service.config.PolarisApplicationConfig; import org.apache.polaris.service.context.CallContextResolver; @@ -61,13 +61,12 @@ protected void run( } // Execute the bootstrap - Map results = + Map results = metaStoreManagerFactory.bootstrapRealms(configuration.getDefaultRealms()); // Log any errors: boolean success = true; - for (Map.Entry result : - results.entrySet()) { + for (Map.Entry result : results.entrySet()) { if (!result.getValue().isSuccess()) { LOGGER.error( "Bootstrapping `{}` failed: {}", diff --git a/polaris-service/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java b/polaris-service/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java index 236279d4f8..ba5113e977 100644 --- a/polaris-service/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java +++ b/polaris-service/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java @@ -57,6 +57,7 @@ import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal; import org.apache.polaris.core.auth.PolarisAuthorizableOperation; import org.apache.polaris.core.auth.PolarisAuthorizer; +import org.apache.polaris.core.auth.PolarisGrantManager.LoadGrantsResult; import org.apache.polaris.core.catalog.PolarisCatalogHelpers; import org.apache.polaris.core.context.CallContext; import org.apache.polaris.core.entity.CatalogEntity; @@ -1189,7 +1190,7 @@ public List listPrincipalRolesAssigned(String principalName) { PolarisEntity principalEntity = findPrincipalByName(principalName) .orElseThrow(() -> new NotFoundException("Principal %s not found", principalName)); - PolarisMetaStoreManager.LoadGrantsResult grantList = + LoadGrantsResult grantList = metaStoreManager.loadGrantsToGrantee( getCurrentPolarisContext(), principalEntity.getCatalogId(), principalEntity.getId()); return buildEntitiesFromGrantResults(grantList, false, null); @@ -1253,7 +1254,7 @@ public List listAssigneePrincipalsForPrincipalRole(String princip findPrincipalRoleByName(principalRoleName) .orElseThrow( () -> new NotFoundException("PrincipalRole %s not found", principalRoleName)); - PolarisMetaStoreManager.LoadGrantsResult grantList = + LoadGrantsResult grantList = metaStoreManager.loadGrantsOnSecurable( getCurrentPolarisContext(), principalRoleEntity.getCatalogId(), @@ -1272,7 +1273,7 @@ public List listAssigneePrincipalsForPrincipalRole(String princip * @return list of grantees or securables matching the filter */ private List buildEntitiesFromGrantResults( - @NotNull PolarisMetaStoreManager.LoadGrantsResult grantList, + @NotNull LoadGrantsResult grantList, boolean grantees, @Nullable Function grantFilter) { Map granteeMap = grantList.getEntitiesAsMap(); @@ -1306,7 +1307,7 @@ public List listCatalogRolesForPrincipalRole( findPrincipalRoleByName(principalRoleName) .orElseThrow( () -> new NotFoundException("PrincipalRole %s not found", principalRoleName)); - PolarisMetaStoreManager.LoadGrantsResult grantList = + LoadGrantsResult grantList = metaStoreManager.loadGrantsToGrantee( getCurrentPolarisContext(), principalRoleEntity.getCatalogId(), @@ -1532,7 +1533,7 @@ public List listAssigneePrincipalRolesForCatalogRole( PolarisEntity catalogRoleEntity = findCatalogRoleByName(catalogName, catalogRoleName) .orElseThrow(() -> new NotFoundException("CatalogRole %s not found", catalogRoleName)); - PolarisMetaStoreManager.LoadGrantsResult grantList = + LoadGrantsResult grantList = metaStoreManager.loadGrantsOnSecurable( getCurrentPolarisContext(), catalogRoleEntity.getCatalogId(), @@ -1551,7 +1552,7 @@ public List listGrantsForCatalogRole(String catalogName, String c PolarisEntity catalogRoleEntity = findCatalogRoleByName(catalogName, catalogRoleName) .orElseThrow(() -> new NotFoundException("CatalogRole %s not found", catalogRoleName)); - PolarisMetaStoreManager.LoadGrantsResult grantList = + LoadGrantsResult grantList = metaStoreManager.loadGrantsToGrantee( getCurrentPolarisContext(), catalogRoleEntity.getCatalogId(), diff --git a/polaris-service/src/main/java/org/apache/polaris/service/auth/TestOAuth2ApiService.java b/polaris-service/src/main/java/org/apache/polaris/service/auth/TestOAuth2ApiService.java index 26a8e797f4..a000489f18 100644 --- a/polaris-service/src/main/java/org/apache/polaris/service/auth/TestOAuth2ApiService.java +++ b/polaris-service/src/main/java/org/apache/polaris/service/auth/TestOAuth2ApiService.java @@ -26,6 +26,7 @@ import java.util.Objects; import org.apache.iceberg.exceptions.NotAuthorizedException; import org.apache.polaris.core.PolarisCallContext; +import org.apache.polaris.core.auth.PolarisSecretsManager.PrincipalSecretsResult; import org.apache.polaris.core.context.CallContext; import org.apache.polaris.core.entity.PolarisEntitySubType; import org.apache.polaris.core.entity.PolarisEntityType; @@ -79,7 +80,7 @@ private String getPrincipalName(String clientId) { metaStoreManagerFactory.getOrCreateMetaStoreManager( CallContext.getCurrentContext().getRealmContext()); PolarisCallContext polarisCallContext = CallContext.getCurrentContext().getPolarisCallContext(); - PolarisMetaStoreManager.PrincipalSecretsResult secretsResult = + PrincipalSecretsResult secretsResult = metaStoreManager.loadPrincipalSecrets(polarisCallContext, clientId); if (secretsResult.isSuccess()) { LOGGER.debug("Found principal secrets for client id {}", clientId); diff --git a/polaris-service/src/main/java/org/apache/polaris/service/auth/TokenBroker.java b/polaris-service/src/main/java/org/apache/polaris/service/auth/TokenBroker.java index 4927187015..16e27ec5ef 100644 --- a/polaris-service/src/main/java/org/apache/polaris/service/auth/TokenBroker.java +++ b/polaris-service/src/main/java/org/apache/polaris/service/auth/TokenBroker.java @@ -20,6 +20,7 @@ import java.util.Optional; import org.apache.polaris.core.PolarisCallContext; +import org.apache.polaris.core.auth.PolarisSecretsManager.PrincipalSecretsResult; import org.apache.polaris.core.context.CallContext; import org.apache.polaris.core.entity.PolarisEntityType; import org.apache.polaris.core.entity.PrincipalEntity; @@ -46,7 +47,7 @@ TokenResponse generateFromToken( PolarisMetaStoreManager metaStoreManager, String clientId, String clientSecret) { // Validate the principal is present and secrets match PolarisCallContext polarisCallContext = CallContext.getCurrentContext().getPolarisCallContext(); - PolarisMetaStoreManager.PrincipalSecretsResult principalSecrets = + PrincipalSecretsResult principalSecrets = metaStoreManager.loadPrincipalSecrets(polarisCallContext, clientId); if (!principalSecrets.isSuccess()) { return Optional.empty(); diff --git a/polaris-service/src/main/java/org/apache/polaris/service/catalog/BasePolarisCatalog.java b/polaris-service/src/main/java/org/apache/polaris/service/catalog/BasePolarisCatalog.java index 20a44263f0..4911b5cfde 100644 --- a/polaris-service/src/main/java/org/apache/polaris/service/catalog/BasePolarisCatalog.java +++ b/polaris-service/src/main/java/org/apache/polaris/service/catalog/BasePolarisCatalog.java @@ -86,6 +86,7 @@ import org.apache.polaris.core.entity.PolarisEntityType; import org.apache.polaris.core.entity.PolarisTaskConstants; import org.apache.polaris.core.entity.TableLikeEntity; +import org.apache.polaris.core.persistence.BaseResult; import org.apache.polaris.core.persistence.PolarisEntityManager; import org.apache.polaris.core.persistence.PolarisMetaStoreManager; import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper; @@ -94,6 +95,7 @@ import org.apache.polaris.core.persistence.resolver.ResolverPath; import org.apache.polaris.core.persistence.resolver.ResolverStatus; import org.apache.polaris.core.storage.InMemoryStorageIntegration; +import org.apache.polaris.core.storage.PolarisCredentialVendor; import org.apache.polaris.core.storage.PolarisStorageActions; import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo; import org.apache.polaris.core.storage.PolarisStorageIntegration; @@ -892,7 +894,7 @@ private Map refreshCredentials( entityManager .getCredentialCache() .getOrGenerateSubScopeCreds( - getMetaStoreManager(), + getCredentialVendor(), callContext.getPolarisCallContext(), entity, allowList, @@ -1623,6 +1625,10 @@ private PolarisMetaStoreManager getMetaStoreManager() { return metaStoreManager; } + private PolarisCredentialVendor getCredentialVendor() { + return metaStoreManager; + } + @VisibleForTesting void setFileIOFactory(FileIOFactory newFactory) { this.fileIOFactory = newFactory; @@ -1694,7 +1700,7 @@ private void renameTableLike( from, to); switch (returnedEntityResult.getReturnStatus()) { - case PolarisMetaStoreManager.ReturnStatus.ENTITY_ALREADY_EXISTS: + case BaseResult.ReturnStatus.ENTITY_ALREADY_EXISTS: { PolarisEntitySubType existingEntitySubType = returnedEntityResult.getAlreadyExistsEntitySubType(); @@ -1713,16 +1719,16 @@ private void renameTableLike( String.format("Unexpected entity type '%s'", existingEntitySubType)); } - case PolarisMetaStoreManager.ReturnStatus.ENTITY_NOT_FOUND: + case BaseResult.ReturnStatus.ENTITY_NOT_FOUND: throw new NotFoundException("Cannot rename %s to %s. %s does not exist", from, to, from); // this is temporary. Should throw a special error that will be caught and retried - case PolarisMetaStoreManager.ReturnStatus.TARGET_ENTITY_CONCURRENTLY_MODIFIED: - case PolarisMetaStoreManager.ReturnStatus.ENTITY_CANNOT_BE_RESOLVED: + case BaseResult.ReturnStatus.TARGET_ENTITY_CONCURRENTLY_MODIFIED: + case BaseResult.ReturnStatus.ENTITY_CANNOT_BE_RESOLVED: throw new RuntimeException("concurrent update detected, please retry"); // some entities cannot be renamed - case PolarisMetaStoreManager.ReturnStatus.ENTITY_CANNOT_BE_RENAMED: + case BaseResult.ReturnStatus.ENTITY_CANNOT_BE_RENAMED: throw new BadRequestException("Cannot rename built-in object %s", leafEntity.getName()); // some entities cannot be renamed @@ -1834,7 +1840,7 @@ private void updateTableLike(TableIdentifier identifier, PolarisEntity entity) { if (resolvedEntities == null) { // TODO: Error? return new PolarisMetaStoreManager.DropEntityResult( - PolarisMetaStoreManager.ReturnStatus.ENTITY_NOT_FOUND, null); + BaseResult.ReturnStatus.ENTITY_NOT_FOUND, null); } List catalogPath = resolvedEntities.getRawParentPath(); diff --git a/polaris-service/src/main/java/org/apache/polaris/service/persistence/InMemoryPolarisMetaStoreManagerFactory.java b/polaris-service/src/main/java/org/apache/polaris/service/persistence/InMemoryPolarisMetaStoreManagerFactory.java index 1d58a1eb58..a268fd6a6f 100644 --- a/polaris-service/src/main/java/org/apache/polaris/service/persistence/InMemoryPolarisMetaStoreManagerFactory.java +++ b/polaris-service/src/main/java/org/apache/polaris/service/persistence/InMemoryPolarisMetaStoreManagerFactory.java @@ -25,6 +25,7 @@ import java.util.Set; import java.util.function.Supplier; import org.apache.polaris.core.PolarisDiagnostics; +import org.apache.polaris.core.auth.PolarisSecretsManager.PrincipalSecretsResult; import org.apache.polaris.core.context.RealmContext; import org.apache.polaris.core.persistence.LocalPolarisMetaStoreManagerFactory; import org.apache.polaris.core.persistence.PolarisMetaStoreManager; @@ -70,11 +71,11 @@ public synchronized Supplier getOrCreateSessionSupplier } private void bootstrapRealmAndPrintCredentials(String realmId) { - Map results = + Map results = this.bootstrapRealms(Collections.singletonList(realmId)); bootstrappedRealms.add(realmId); - PolarisMetaStoreManager.PrincipalSecretsResult principalSecrets = results.get(realmId); + PrincipalSecretsResult principalSecrets = results.get(realmId); String msg = String.format( diff --git a/polaris-service/src/test/java/org/apache/polaris/service/auth/JWTRSAKeyPairTest.java b/polaris-service/src/test/java/org/apache/polaris/service/auth/JWTRSAKeyPairTest.java index 38ed6d44ad..3cd8b925d6 100644 --- a/polaris-service/src/test/java/org/apache/polaris/service/auth/JWTRSAKeyPairTest.java +++ b/polaris-service/src/test/java/org/apache/polaris/service/auth/JWTRSAKeyPairTest.java @@ -36,6 +36,7 @@ import java.util.HashMap; import java.util.Map; import org.apache.polaris.core.PolarisCallContext; +import org.apache.polaris.core.auth.PolarisSecretsManager.PrincipalSecretsResult; import org.apache.polaris.core.context.CallContext; import org.apache.polaris.core.context.RealmContext; import org.apache.polaris.core.entity.PolarisBaseEntity; @@ -118,7 +119,7 @@ public void testSuccessfulTokenGeneration() throws Exception { PolarisPrincipalSecrets principalSecrets = new PolarisPrincipalSecrets(1L, clientId, mainSecret, "otherSecret"); Mockito.when(metastoreManager.loadPrincipalSecrets(polarisCallContext, clientId)) - .thenReturn(new PolarisMetaStoreManager.PrincipalSecretsResult(principalSecrets)); + .thenReturn(new PrincipalSecretsResult(principalSecrets)); PolarisBaseEntity principal = new PolarisBaseEntity( 0L, diff --git a/polaris-service/src/test/java/org/apache/polaris/service/auth/JWTSymmetricKeyGeneratorTest.java b/polaris-service/src/test/java/org/apache/polaris/service/auth/JWTSymmetricKeyGeneratorTest.java index 63061da4e3..db7a00fff9 100644 --- a/polaris-service/src/test/java/org/apache/polaris/service/auth/JWTSymmetricKeyGeneratorTest.java +++ b/polaris-service/src/test/java/org/apache/polaris/service/auth/JWTSymmetricKeyGeneratorTest.java @@ -26,6 +26,7 @@ import com.auth0.jwt.interfaces.DecodedJWT; import java.util.Map; import org.apache.polaris.core.PolarisCallContext; +import org.apache.polaris.core.auth.PolarisSecretsManager.PrincipalSecretsResult; import org.apache.polaris.core.context.CallContext; import org.apache.polaris.core.context.RealmContext; import org.apache.polaris.core.entity.PolarisBaseEntity; @@ -65,7 +66,7 @@ public Map contextVariables() { PolarisPrincipalSecrets principalSecrets = new PolarisPrincipalSecrets(1L, clientId, mainSecret, "otherSecret"); Mockito.when(metastoreManager.loadPrincipalSecrets(polarisCallContext, clientId)) - .thenReturn(new PolarisMetaStoreManager.PrincipalSecretsResult(principalSecrets)); + .thenReturn(new PrincipalSecretsResult(principalSecrets)); PolarisBaseEntity principal = new PolarisBaseEntity( 0L, diff --git a/polaris-service/src/test/java/org/apache/polaris/service/catalog/BasePolarisCatalogTest.java b/polaris-service/src/test/java/org/apache/polaris/service/catalog/BasePolarisCatalogTest.java index bb9bc55806..7b0270c401 100644 --- a/polaris-service/src/test/java/org/apache/polaris/service/catalog/BasePolarisCatalogTest.java +++ b/polaris-service/src/test/java/org/apache/polaris/service/catalog/BasePolarisCatalogTest.java @@ -20,7 +20,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.iceberg.types.Types.NestedField.required; -import static org.apache.polaris.service.exception.IcebergExceptionMapper.*; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.when; @@ -66,6 +65,7 @@ import org.apache.polaris.core.admin.model.StorageConfigInfo; import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal; import org.apache.polaris.core.auth.PolarisAuthorizerImpl; +import org.apache.polaris.core.auth.PolarisSecretsManager.PrincipalSecretsResult; import org.apache.polaris.core.context.CallContext; import org.apache.polaris.core.context.RealmContext; import org.apache.polaris.core.entity.CatalogEntity; @@ -291,8 +291,7 @@ public StorageCredentialCache getOrCreateStorageCredentialCache(RealmContext rea public void setMetricRegistry(PolarisMetricRegistry metricRegistry) {} @Override - public Map bootstrapRealms( - List realms) { + public Map bootstrapRealms(List realms) { throw new NotImplementedException("Bootstrapping realms is not supported"); }