Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -197,21 +197,23 @@ public synchronized Supplier<BasePersistence> getOrCreateSessionSupplier(

@Override
public synchronized StorageCredentialCache getOrCreateStorageCredentialCache(
RealmContext realmContext) {
RealmContext realmContext, PolarisCallContext polarisCallContext) {
if (!storageCredentialCacheMap.containsKey(realmContext.getRealmIdentifier())) {
storageCredentialCacheMap.put(
realmContext.getRealmIdentifier(), new StorageCredentialCache());
realmContext.getRealmIdentifier(), new StorageCredentialCache(polarisCallContext));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This look strange to me. We cache StorageCredentialCache only by realm ID, but the instance references a potentially old PolarisCallContext. Can we be sure that PolarisCallContext inside the StorageCredentialCache is still relevant when it is obtained from the map later?

Currently we only use polarisCallContext for resolving config parameters, still, it is very non-intuitive that the config values resolved in one polarisCallContext are applicable to another polarisCallContext even within the same realm 🤔

}

return storageCredentialCacheMap.get(realmContext.getRealmIdentifier());
}

@Override
public synchronized EntityCache getOrCreateEntityCache(RealmContext realmContext) {
public synchronized EntityCache getOrCreateEntityCache(
RealmContext realmContext, PolarisCallContext polarisCallContext) {
if (!entityCacheMap.containsKey(realmContext.getRealmIdentifier())) {
PolarisMetaStoreManager metaStoreManager = getOrCreateMetaStoreManager(realmContext);
entityCacheMap.put(
realmContext.getRealmIdentifier(), new InMemoryEntityCache(metaStoreManager));
realmContext.getRealmIdentifier(),
new InMemoryEntityCache(metaStoreManager, polarisCallContext));
}

return entityCacheMap.get(realmContext.getRealmIdentifier());
Expand Down
1 change: 1 addition & 0 deletions polaris-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ dependencies {
implementation("com.fasterxml.jackson.core:jackson-annotations")
implementation("com.fasterxml.jackson.core:jackson-core")
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation(libs.jakarta.enterprise.cdi.api)
implementation(libs.caffeine)
implementation(libs.commons.lang3)
implementation(libs.commons.codec1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.apache.polaris.core.context.CallContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -116,25 +115,6 @@ public BehaviorChangeConfiguration<T> buildBehaviorChangeConfiguration() {
}
}

/**
* Returns the value of a `PolarisConfiguration`, or the default if it cannot be loaded. This
* method does not need to be used when a `CallContext` is already available
*/
public static <T> T loadConfig(PolarisConfiguration<T> configuration) {
var callContext = CallContext.getCurrentContext();
if (callContext == null) {
LOGGER.warn(
String.format(
"Unable to load current call context; using %s = %s",
configuration.key, configuration.defaultValue));
return configuration.defaultValue;
}
return callContext
.getPolarisCallContext()
.getConfigurationStore()
.getConfiguration(callContext.getPolarisCallContext(), configuration);
}

public static <T> Builder<T> builder() {
return new Builder<>();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.Optional;
import java.util.Set;
import org.apache.iceberg.exceptions.BadRequestException;
import org.apache.polaris.core.PolarisCallContext;
import org.apache.polaris.core.PolarisDefaultDiagServiceImpl;
import org.apache.polaris.core.admin.model.AwsStorageConfigInfo;
import org.apache.polaris.core.admin.model.AzureStorageConfigInfo;
Expand Down Expand Up @@ -80,7 +81,7 @@ public static CatalogEntity of(PolarisBaseEntity sourceEntity) {
return null;
}

public static CatalogEntity fromCatalog(Catalog catalog) {
public static CatalogEntity fromCatalog(PolarisCallContext callContext, Catalog catalog) {
Builder builder =
new Builder()
.setName(catalog.getName())
Expand All @@ -90,7 +91,7 @@ public static CatalogEntity fromCatalog(Catalog catalog) {
internalProperties.put(CATALOG_TYPE_PROPERTY, catalog.getType().name());
builder.setInternalProperties(internalProperties);
builder.setStorageConfigurationInfo(
catalog.getStorageConfigInfo(), getDefaultBaseLocation(catalog));
catalog.getStorageConfigInfo(), getDefaultBaseLocation(catalog), callContext);
return builder.build();
}

Expand Down Expand Up @@ -247,7 +248,9 @@ public Builder setReplaceNewLocationPrefixWithCatalogDefault(String value) {
}

public Builder setStorageConfigurationInfo(
StorageConfigInfo storageConfigModel, String defaultBaseLocation) {
StorageConfigInfo storageConfigModel,
String defaultBaseLocation,
PolarisCallContext callContext) {
if (storageConfigModel != null) {
PolarisStorageConfigurationInfo config;
Set<String> allowedLocations = new HashSet<>(storageConfigModel.getAllowedLocations());
Expand All @@ -261,7 +264,7 @@ public Builder setStorageConfigurationInfo(
throw new BadRequestException("Must specify default base location");
}
allowedLocations.add(defaultBaseLocation);
validateMaxAllowedLocations(allowedLocations);
validateMaxAllowedLocations(allowedLocations, callContext);
switch (storageConfigModel.getStorageType()) {
case S3:
AwsStorageConfigInfo awsConfigModel = (AwsStorageConfigInfo) storageConfigModel;
Expand Down Expand Up @@ -305,10 +308,13 @@ public Builder setStorageConfigurationInfo(
}

/** Validate the number of allowed locations not exceeding the max value. */
private void validateMaxAllowedLocations(Collection<String> allowedLocations) {
private void validateMaxAllowedLocations(
Collection<String> allowedLocations, PolarisCallContext callContext) {
int maxAllowedLocations =
BehaviorChangeConfiguration.loadConfig(
BehaviorChangeConfiguration.STORAGE_CONFIGURATION_MAX_LOCATIONS);
callContext
.getConfigurationStore()
.getConfiguration(
callContext, BehaviorChangeConfiguration.STORAGE_CONFIGURATION_MAX_LOCATIONS);
if (maxAllowedLocations != -1 && allowedLocations.size() > maxAllowedLocations) {
throw new IllegalArgumentException(
String.format(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1607,7 +1607,7 @@ private void revokeGrantRecord(
try {
EnumMap<PolarisCredentialProperty, String> creds =
storageIntegration.getSubscopedCreds(
callCtx.getDiagServices(),
callCtx,
storageConfigurationInfo,
allowListOperation,
allowedReadLocations,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,21 +178,23 @@ public synchronized Supplier<TransactionalPersistence> getOrCreateSessionSupplie

@Override
public synchronized StorageCredentialCache getOrCreateStorageCredentialCache(
RealmContext realmContext) {
RealmContext realmContext, PolarisCallContext polarisCallContext) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can get from PolarisCallContext to RealmContext, so I don't see a point in passing in two arguments

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can get from PolarisCallContext to RealmContext

Is that true? I think you can get from CallContext to RealmContext, but not from PolarisCallContext. Personally I would like to unify these in the future but I want to keep this PR tightly scoped.

if (!storageCredentialCacheMap.containsKey(realmContext.getRealmIdentifier())) {
storageCredentialCacheMap.put(
realmContext.getRealmIdentifier(), new StorageCredentialCache());
realmContext.getRealmIdentifier(), new StorageCredentialCache(polarisCallContext));
}

return storageCredentialCacheMap.get(realmContext.getRealmIdentifier());
}

@Override
public synchronized EntityCache getOrCreateEntityCache(RealmContext realmContext) {
public synchronized EntityCache getOrCreateEntityCache(
RealmContext realmContext, PolarisCallContext polarisCallContext) {
if (!entityCacheMap.containsKey(realmContext.getRealmIdentifier())) {
PolarisMetaStoreManager metaStoreManager = getOrCreateMetaStoreManager(realmContext);
entityCacheMap.put(
realmContext.getRealmIdentifier(), new InMemoryEntityCache(metaStoreManager));
realmContext.getRealmIdentifier(),
new InMemoryEntityCache(metaStoreManager, polarisCallContext));
}

return entityCacheMap.get(realmContext.getRealmIdentifier());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import java.util.Map;
import java.util.function.Supplier;
import org.apache.polaris.core.PolarisCallContext;
import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.core.persistence.bootstrap.RootCredentialsSet;
import org.apache.polaris.core.persistence.cache.EntityCache;
Expand All @@ -34,9 +35,11 @@ public interface MetaStoreManagerFactory {

Supplier<? extends BasePersistence> getOrCreateSessionSupplier(RealmContext realmContext);

StorageCredentialCache getOrCreateStorageCredentialCache(RealmContext realmContext);
StorageCredentialCache getOrCreateStorageCredentialCache(
RealmContext realmContext, PolarisCallContext polarisCallContext);

EntityCache getOrCreateEntityCache(RealmContext realmContext);
EntityCache getOrCreateEntityCache(
RealmContext realmContext, PolarisCallContext polarisCallContext);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not support passing context parameter as method args in general and this PR adds another context parameter.

Could we leverage CDI for injecting context data?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good callout. In general I agree that we can leverage CDI to inject context parameters (e.g. PolarisCallContext) around. However in this particular case, the EntityCache is not a CDI-managed class. There's a discussion above about this too, but I think we need a way to have configs that are not tied to a PolarisCallContext('s configuration store).


Map<String, PrincipalSecretsResult> bootstrapRealms(
Iterable<String> realms, RootCredentialsSet rootCredentialsSet);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import org.apache.polaris.core.PolarisCallContext;
import org.apache.polaris.core.config.BehaviorChangeConfiguration;
import org.apache.polaris.core.config.FeatureConfiguration;
import org.apache.polaris.core.config.PolarisConfiguration;
import org.apache.polaris.core.entity.PolarisBaseEntity;
import org.apache.polaris.core.entity.PolarisEntityType;
import org.apache.polaris.core.entity.PolarisGrantRecord;
Expand Down Expand Up @@ -58,7 +57,9 @@ public class InMemoryEntityCache implements EntityCache {
*
* @param polarisMetaStoreManager the meta store manager implementation
*/
public InMemoryEntityCache(@Nonnull PolarisMetaStoreManager polarisMetaStoreManager) {
public InMemoryEntityCache(
@Nonnull PolarisMetaStoreManager polarisMetaStoreManager,
@Nonnull PolarisCallContext polarisCallContext) {

// by name cache
this.byName = new ConcurrentHashMap<>();
Expand All @@ -76,15 +77,22 @@ public InMemoryEntityCache(@Nonnull PolarisMetaStoreManager polarisMetaStoreMana
};

long weigherTarget =
PolarisConfiguration.loadConfig(FeatureConfiguration.ENTITY_CACHE_WEIGHER_TARGET);
polarisCallContext
.getConfigurationStore()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked through the code and as far as I can see all instances of PolarisConfigurationStore happen to be application-scoped. Could we avoid requiring a "call" (i.e. request) context for getting PolarisConfigurationStore?

If injection through CDI seems too intrusive for this PR, could we instead use PolarisConfigurationStore as the new parameter (instead of PolarisCallContext)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but TBH, I'd really like to use CDI (with custom producers) for injection rather than relying on factories in the main call path. In other words, I'd like the main body of code to receive dependent objects through injection and factories / producers to be removed from the main call paths and be engaged by CDI when request-handlers are constructed.

All-in-all, the intention behind this PR seems to be pointing in that direction, so why not do the CDI-based refactoring now and then perhaps the config loading issue is solved automatically?

.getConfiguration(polarisCallContext, FeatureConfiguration.ENTITY_CACHE_WEIGHER_TARGET);
Caffeine<Long, ResolvedPolarisEntity> byIdBuilder =
Caffeine.newBuilder()
.maximumWeight(weigherTarget)
.weigher(EntityWeigher.asWeigher())
.expireAfterAccess(1, TimeUnit.HOURS) // Expire entries after 1 hour of no access
.removalListener(removalListener); // Set the removal listener

if (PolarisConfiguration.loadConfig(BehaviorChangeConfiguration.ENTITY_CACHE_SOFT_VALUES)) {
boolean useSoftValues =
polarisCallContext
.getConfigurationStore()
.getConfiguration(
polarisCallContext, BehaviorChangeConfiguration.ENTITY_CACHE_SOFT_VALUES);
if (useSoftValues) {
byIdBuilder.softValues();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2037,7 +2037,7 @@ private PolarisEntityResolver resolveSecurableToRoleGrant(
try {
EnumMap<PolarisCredentialProperty, String> creds =
storageIntegration.getSubscopedCreds(
callCtx.getDiagServices(),
callCtx,
storageConfigurationInfo,
allowListOperation,
allowedReadLocations,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.apache.polaris.core.PolarisDiagnostics;
import org.apache.polaris.core.PolarisCallContext;

/**
* Abstract of Polaris Storage Integration. It holds the reference to an object that having the
Expand Down Expand Up @@ -55,7 +55,7 @@ public String getStorageIdentifierOrId() {
* @return An enum map including the scoped credentials
*/
public abstract EnumMap<PolarisCredentialProperty, String> getSubscopedCreds(
@Nonnull PolarisDiagnostics diagnostics,
@Nonnull PolarisCallContext callContext,
@Nonnull T storageConfig,
boolean allowListOperation,
@Nonnull Set<String> allowedReadLocations,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
package org.apache.polaris.core.storage.aws;

import static org.apache.polaris.core.config.FeatureConfiguration.STORAGE_CREDENTIAL_DURATION_SECONDS;
import static org.apache.polaris.core.config.PolarisConfiguration.loadConfig;

import jakarta.annotation.Nonnull;
import java.net.URI;
Expand All @@ -29,7 +28,7 @@
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import org.apache.polaris.core.PolarisDiagnostics;
import org.apache.polaris.core.PolarisCallContext;
import org.apache.polaris.core.storage.InMemoryStorageIntegration;
import org.apache.polaris.core.storage.PolarisCredentialProperty;
import org.apache.polaris.core.storage.StorageUtil;
Expand All @@ -55,11 +54,15 @@ public AwsCredentialsStorageIntegration(StsClient stsClient) {
/** {@inheritDoc} */
@Override
public EnumMap<PolarisCredentialProperty, String> getSubscopedCreds(
@Nonnull PolarisDiagnostics diagnostics,
@Nonnull PolarisCallContext callContext,
@Nonnull AwsStorageConfigurationInfo storageConfig,
boolean allowListOperation,
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations) {
int durationSeconds =
callContext
.getConfigurationStore()
.getConfiguration(callContext, STORAGE_CREDENTIAL_DURATION_SECONDS);
AssumeRoleResponse response =
stsClient.assumeRole(
AssumeRoleRequest.builder()
Expand All @@ -73,7 +76,7 @@ public EnumMap<PolarisCredentialProperty, String> getSubscopedCreds(
allowedReadLocations,
allowedWriteLocations)
.toJson())
.durationSeconds(loadConfig(STORAGE_CREDENTIAL_DURATION_SECONDS))
.durationSeconds(durationSeconds)
.build());
EnumMap<PolarisCredentialProperty, String> credentialMap =
new EnumMap<>(PolarisCredentialProperty.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
package org.apache.polaris.core.storage.azure;

import static org.apache.polaris.core.config.FeatureConfiguration.STORAGE_CREDENTIAL_DURATION_SECONDS;

import com.azure.core.credential.AccessToken;
import com.azure.core.credential.TokenRequestContext;
import com.azure.identity.DefaultAzureCredential;
Expand Down Expand Up @@ -45,8 +47,7 @@
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import org.apache.polaris.core.PolarisDiagnostics;
import org.apache.polaris.core.config.FeatureConfiguration;
import org.apache.polaris.core.PolarisCallContext;
import org.apache.polaris.core.storage.InMemoryStorageIntegration;
import org.apache.polaris.core.storage.PolarisCredentialProperty;
import org.slf4j.Logger;
Expand All @@ -71,7 +72,7 @@ public AzureCredentialsStorageIntegration() {

@Override
public EnumMap<PolarisCredentialProperty, String> getSubscopedCreds(
@Nonnull PolarisDiagnostics diagnostics,
@Nonnull PolarisCallContext callContext,
@Nonnull AzureStorageConfigurationInfo storageConfig,
boolean allowListOperation,
@Nonnull Set<String> allowedReadLocations,
Expand Down Expand Up @@ -126,7 +127,9 @@ public EnumMap<PolarisCredentialProperty, String> getSubscopedCreds(
// clock skew between the client and server,
OffsetDateTime startTime = start.truncatedTo(ChronoUnit.SECONDS).atOffset(ZoneOffset.UTC);
int intendedDurationSeconds =
FeatureConfiguration.loadConfig(FeatureConfiguration.STORAGE_CREDENTIAL_DURATION_SECONDS);
callContext
.getConfigurationStore()
.getConfiguration(callContext, STORAGE_CREDENTIAL_DURATION_SECONDS);
OffsetDateTime intendedEndTime =
start.plusSeconds(intendedDurationSeconds).atOffset(ZoneOffset.UTC);
OffsetDateTime maxAllowedEndTime =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import org.apache.iceberg.exceptions.UnprocessableEntityException;
import org.apache.polaris.core.PolarisCallContext;
import org.apache.polaris.core.config.FeatureConfiguration;
import org.apache.polaris.core.config.PolarisConfiguration;
import org.apache.polaris.core.entity.PolarisEntity;
import org.apache.polaris.core.entity.PolarisEntityType;
import org.apache.polaris.core.persistence.dao.entity.ScopedCredentialsResult;
Expand All @@ -47,8 +46,11 @@ public class StorageCredentialCache {
private static final long CACHE_MAX_NUMBER_OF_ENTRIES = 10_000L;
private final LoadingCache<StorageCredentialCacheKey, StorageCredentialCacheEntry> cache;

private final long maxCacheDurationMs;

/** Initialize the creds cache */
public StorageCredentialCache() {
public StorageCredentialCache(PolarisCallContext polarisCallContext) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comments re: the EntityCache

this.maxCacheDurationMs = maxCacheDurationMs(polarisCallContext);
cache =
Caffeine.newBuilder()
.maximumSize(CACHE_MAX_NUMBER_OF_ENTRIES)
Expand All @@ -60,7 +62,7 @@ public StorageCredentialCache() {
0,
Math.min(
(entry.getExpirationTime() - System.currentTimeMillis()) / 2,
maxCacheDurationMs()));
this.maxCacheDurationMs));
return Duration.ofMillis(expireAfterMillis);
}))
.build(
Expand All @@ -71,12 +73,17 @@ public StorageCredentialCache() {
}

/** How long credentials should remain in the cache. */
private static long maxCacheDurationMs() {
var cacheDurationSeconds =
PolarisConfiguration.loadConfig(
FeatureConfiguration.STORAGE_CREDENTIAL_CACHE_DURATION_SECONDS);
var credentialDurationSeconds =
PolarisConfiguration.loadConfig(FeatureConfiguration.STORAGE_CREDENTIAL_DURATION_SECONDS);
private static long maxCacheDurationMs(PolarisCallContext polarisCallContext) {
int cacheDurationSeconds =
polarisCallContext
.getConfigurationStore()
.getConfiguration(
polarisCallContext, FeatureConfiguration.STORAGE_CREDENTIAL_CACHE_DURATION_SECONDS);
int credentialDurationSeconds =
polarisCallContext
.getConfigurationStore()
.getConfiguration(
polarisCallContext, FeatureConfiguration.STORAGE_CREDENTIAL_DURATION_SECONDS);
if (cacheDurationSeconds >= credentialDurationSeconds) {
throw new IllegalArgumentException(
String.format(
Expand Down
Loading