This is an automated email from the ASF dual-hosted git repository.
fanng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new d49e7eb463 [#4398] feat(core): support credential cache for Gravitino
server (#5995)
d49e7eb463 is described below
commit d49e7eb463a666055d8dc55287b9b25c755d0f1a
Author: FANNG <[email protected]>
AuthorDate: Fri Dec 27 09:49:23 2024 +0800
[#4398] feat(core): support credential cache for Gravitino server (#5995)
### What changes were proposed in this pull request?
add credential cache for Gravitino server, not support for Iceberg rest
server yet.
### Why are the changes needed?
Fix: #4398
### Does this PR introduce _any_ user-facing change?
no
### How was this patch tested?
testing in local env, get credential from Gravitino server and see
whether it's fetched from remote or local cache
---
.../gravitino/credential/CredentialConstants.java | 2 +
.../org/apache/gravitino/config/ConfigBuilder.java | 25 +++++
.../apache/gravitino/connector/PropertyEntry.java | 24 +++++
.../credential/CatalogCredentialContext.java | 24 +++++
.../credential/CatalogCredentialManager.java | 21 ++++-
.../gravitino/credential/CredentialCache.java | 101 +++++++++++++++++++++
.../gravitino/credential/CredentialCacheKey.java | 64 +++++++++++++
.../credential/PathBasedCredentialContext.java | 33 +++++++
.../credential/config/CredentialConfig.java | 56 +++++++++++-
.../credential/TestCredentialCacheKey.java | 56 ++++++++++++
10 files changed, 399 insertions(+), 7 deletions(-)
diff --git
a/catalogs/catalog-common/src/main/java/org/apache/gravitino/credential/CredentialConstants.java
b/catalogs/catalog-common/src/main/java/org/apache/gravitino/credential/CredentialConstants.java
index d2753f24b5..c766a86c14 100644
---
a/catalogs/catalog-common/src/main/java/org/apache/gravitino/credential/CredentialConstants.java
+++
b/catalogs/catalog-common/src/main/java/org/apache/gravitino/credential/CredentialConstants.java
@@ -22,6 +22,8 @@ package org.apache.gravitino.credential;
public class CredentialConstants {
public static final String CREDENTIAL_PROVIDER_TYPE =
"credential-provider-type";
public static final String CREDENTIAL_PROVIDERS = "credential-providers";
+ public static final String CREDENTIAL_CACHE_EXPIRE_RATIO =
"credential-cache-expire-ratio";
+ public static final String CREDENTIAL_CACHE_MAX_SIZE =
"credential-cache-max-size";
public static final String S3_TOKEN_CREDENTIAL_PROVIDER = "s3-token";
public static final String S3_TOKEN_EXPIRE_IN_SECS =
"s3-token-expire-in-secs";
diff --git a/core/src/main/java/org/apache/gravitino/config/ConfigBuilder.java
b/core/src/main/java/org/apache/gravitino/config/ConfigBuilder.java
index 148cda4fa7..d42f445e10 100644
--- a/core/src/main/java/org/apache/gravitino/config/ConfigBuilder.java
+++ b/core/src/main/java/org/apache/gravitino/config/ConfigBuilder.java
@@ -170,6 +170,31 @@ public class ConfigBuilder {
return conf;
}
+ /**
+ * Creates a configuration entry for Double data type.
+ *
+ * @return The created ConfigEntry instance for Double data type.
+ */
+ public ConfigEntry<Double> doubleConf() {
+ ConfigEntry<Double> conf =
+ new ConfigEntry<>(key, version, doc, alternatives, isPublic,
isDeprecated);
+ Function<String, Double> func =
+ s -> {
+ if (s == null || s.isEmpty()) {
+ return null;
+ } else {
+ return Double.parseDouble(s);
+ }
+ };
+ conf.setValueConverter(func);
+
+ Function<Double, String> stringFunc =
+ t -> Optional.ofNullable(t).map(String::valueOf).orElse(null);
+ conf.setStringConverter(stringFunc);
+
+ return conf;
+ }
+
/**
* Creates a configuration entry for Boolean data type.
*
diff --git
a/core/src/main/java/org/apache/gravitino/connector/PropertyEntry.java
b/core/src/main/java/org/apache/gravitino/connector/PropertyEntry.java
index b4c788a60d..a32c2fff21 100644
--- a/core/src/main/java/org/apache/gravitino/connector/PropertyEntry.java
+++ b/core/src/main/java/org/apache/gravitino/connector/PropertyEntry.java
@@ -29,6 +29,7 @@ import org.apache.commons.lang3.StringUtils;
@Getter
public final class PropertyEntry<T> {
+
private final String name;
private final String description;
private final boolean required;
@@ -90,6 +91,7 @@ public final class PropertyEntry<T> {
}
public static class Builder<T> {
+
private String name;
private String description;
private boolean required;
@@ -214,6 +216,28 @@ public final class PropertyEntry<T> {
.build();
}
+ public static PropertyEntry<Double> doublePropertyEntry(
+ String name,
+ String description,
+ boolean required,
+ boolean immutable,
+ double defaultValue,
+ boolean hidden,
+ boolean reserved) {
+ return new Builder<Double>()
+ .withName(name)
+ .withDescription(description)
+ .withRequired(required)
+ .withImmutable(immutable)
+ .withJavaType(Double.class)
+ .withDefaultValue(defaultValue)
+ .withDecoder(Double::parseDouble)
+ .withEncoder(String::valueOf)
+ .withHidden(hidden)
+ .withReserved(reserved)
+ .build();
+ }
+
public static PropertyEntry<Integer> integerPropertyEntry(
String name,
String description,
diff --git
a/core/src/main/java/org/apache/gravitino/credential/CatalogCredentialContext.java
b/core/src/main/java/org/apache/gravitino/credential/CatalogCredentialContext.java
index a39dbba01b..6ac0c498c0 100644
---
a/core/src/main/java/org/apache/gravitino/credential/CatalogCredentialContext.java
+++
b/core/src/main/java/org/apache/gravitino/credential/CatalogCredentialContext.java
@@ -19,6 +19,7 @@
package org.apache.gravitino.credential;
+import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import javax.validation.constraints.NotNull;
@@ -35,4 +36,27 @@ public class CatalogCredentialContext implements
CredentialContext {
public String getUserName() {
return userName;
}
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(userName);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || !(o instanceof CatalogCredentialContext)) {
+ return false;
+ }
+ return Objects.equal(userName, ((CatalogCredentialContext) o).userName);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("User name: ").append(userName);
+ return stringBuilder.toString();
+ }
}
diff --git
a/core/src/main/java/org/apache/gravitino/credential/CatalogCredentialManager.java
b/core/src/main/java/org/apache/gravitino/credential/CatalogCredentialManager.java
index 2fe6fedccd..0e407a399b 100644
---
a/core/src/main/java/org/apache/gravitino/credential/CatalogCredentialManager.java
+++
b/core/src/main/java/org/apache/gravitino/credential/CatalogCredentialManager.java
@@ -34,20 +34,21 @@ public class CatalogCredentialManager implements Closeable {
private static final Logger LOG =
LoggerFactory.getLogger(CatalogCredentialManager.class);
+ private final CredentialCache<CredentialCacheKey> credentialCache;
+
private final String catalogName;
private final Map<String, CredentialProvider> credentialProviders;
public CatalogCredentialManager(String catalogName, Map<String, String>
catalogProperties) {
this.catalogName = catalogName;
this.credentialProviders =
CredentialUtils.loadCredentialProviders(catalogProperties);
+ this.credentialCache = new CredentialCache();
+ credentialCache.initialize(catalogProperties);
}
public Credential getCredential(String credentialType, CredentialContext
context) {
- // todo: add credential cache
- Preconditions.checkState(
- credentialProviders.containsKey(credentialType),
- String.format("Credential %s not found", credentialType));
- return credentialProviders.get(credentialType).getCredential(context);
+ CredentialCacheKey credentialCacheKey = new
CredentialCacheKey(credentialType, context);
+ return credentialCache.getCredential(credentialCacheKey, cacheKey ->
doGetCredential(cacheKey));
}
@Override
@@ -67,4 +68,14 @@ public class CatalogCredentialManager implements Closeable {
}
});
}
+
+ private Credential doGetCredential(CredentialCacheKey credentialCacheKey) {
+ String credentialType = credentialCacheKey.getCredentialType();
+ CredentialContext context = credentialCacheKey.getCredentialContext();
+ LOG.debug("Try get credential, credential type: {}, context: {}.",
credentialType, context);
+ Preconditions.checkState(
+ credentialProviders.containsKey(credentialType),
+ String.format("Credential %s not found", credentialType));
+ return credentialProviders.get(credentialType).getCredential(context);
+ }
}
diff --git
a/core/src/main/java/org/apache/gravitino/credential/CredentialCache.java
b/core/src/main/java/org/apache/gravitino/credential/CredentialCache.java
new file mode 100644
index 0000000000..afbb09d50e
--- /dev/null
+++ b/core/src/main/java/org/apache/gravitino/credential/CredentialCache.java
@@ -0,0 +1,101 @@
+/*
+ * 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.gravitino.credential;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.Expiry;
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import org.apache.gravitino.credential.config.CredentialConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CredentialCache<T> implements Closeable {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(CredentialCache.class);
+
+ // Calculates the credential expire time in the cache.
+ static class CredentialExpireTimeCalculator<T> implements Expiry<T,
Credential> {
+
+ private double credentialCacheExpireRatio;
+
+ public CredentialExpireTimeCalculator(double credentialCacheExpireRatio) {
+ this.credentialCacheExpireRatio = credentialCacheExpireRatio;
+ }
+
+ // Set expire time after add a credential in the cache.
+ @Override
+ public long expireAfterCreate(T key, Credential credential, long
currentTime) {
+ long credentialExpireTime = credential.expireTimeInMs();
+ long timeToExpire = credentialExpireTime - System.currentTimeMillis();
+ if (timeToExpire <= 0) {
+ return 0;
+ }
+
+ timeToExpire = (long) (timeToExpire * credentialCacheExpireRatio);
+ return TimeUnit.MILLISECONDS.toNanos(timeToExpire);
+ }
+
+ // Not change expire time after update credential, this should not happen.
+ @Override
+ public long expireAfterUpdate(T key, Credential value, long currentTime,
long currentDuration) {
+ return currentDuration;
+ }
+
+ // Not change expire time after read credential.
+ @Override
+ public long expireAfterRead(T key, Credential value, long currentTime,
long currentDuration) {
+ return currentDuration;
+ }
+ }
+
+ private Cache<T, Credential> credentialCache;
+
+ public void initialize(Map<String, String> catalogProperties) {
+ CredentialConfig credentialConfig = new
CredentialConfig(catalogProperties);
+ long cacheSize =
credentialConfig.get(CredentialConfig.CREDENTIAL_CACHE_MAX_SIZE);
+ double cacheExpireRatio =
credentialConfig.get(CredentialConfig.CREDENTIAL_CACHE_EXPIRE_RATIO);
+
+ this.credentialCache =
+ Caffeine.newBuilder()
+ .expireAfter(new CredentialExpireTimeCalculator(cacheExpireRatio))
+ .maximumSize(cacheSize)
+ .removalListener(
+ (cacheKey, credential, c) ->
+ LOG.debug("Credential expire, cache key: {}.", cacheKey))
+ .build();
+ }
+
+ public Credential getCredential(T cacheKey, Function<T, Credential>
credentialSupplier) {
+ return credentialCache.get(cacheKey, key ->
credentialSupplier.apply(cacheKey));
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (credentialCache != null) {
+ credentialCache.invalidateAll();
+ credentialCache = null;
+ }
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/credential/CredentialCacheKey.java
b/core/src/main/java/org/apache/gravitino/credential/CredentialCacheKey.java
new file mode 100644
index 0000000000..1d0d8f7b3b
--- /dev/null
+++ b/core/src/main/java/org/apache/gravitino/credential/CredentialCacheKey.java
@@ -0,0 +1,64 @@
+/*
+ * 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.gravitino.credential;
+
+import java.util.Objects;
+import lombok.Getter;
+
+@Getter
+public class CredentialCacheKey {
+
+ private final String credentialType;
+ private final CredentialContext credentialContext;
+
+ public CredentialCacheKey(String credentialType, CredentialContext
credentialContext) {
+ this.credentialType = credentialType;
+ this.credentialContext = credentialContext;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(credentialType, credentialContext);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || !(o instanceof CredentialCacheKey)) {
+ return false;
+ }
+ CredentialCacheKey that = (CredentialCacheKey) o;
+ return Objects.equals(credentialType, that.credentialType)
+ && Objects.equals(credentialContext, that.credentialContext);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder
+ .append("credentialType: ")
+ .append(credentialType)
+ .append("credentialContext: ")
+ .append(credentialContext);
+ return stringBuilder.toString();
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/credential/PathBasedCredentialContext.java
b/core/src/main/java/org/apache/gravitino/credential/PathBasedCredentialContext.java
index 03e7bbe0e3..06d17b134b 100644
---
a/core/src/main/java/org/apache/gravitino/credential/PathBasedCredentialContext.java
+++
b/core/src/main/java/org/apache/gravitino/credential/PathBasedCredentialContext.java
@@ -20,6 +20,7 @@
package org.apache.gravitino.credential;
import com.google.common.base.Preconditions;
+import java.util.Objects;
import java.util.Set;
import javax.validation.constraints.NotNull;
@@ -55,4 +56,36 @@ public class PathBasedCredentialContext implements
CredentialContext {
public Set<String> getReadPaths() {
return readPaths;
}
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(userName, writePaths, readPaths);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || !(o instanceof PathBasedCredentialContext)) {
+ return false;
+ }
+ PathBasedCredentialContext that = (PathBasedCredentialContext) o;
+ return Objects.equals(userName, that.userName)
+ && Objects.equals(writePaths, that.writePaths)
+ && Objects.equals(readPaths, that.readPaths);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder
+ .append("User name: ")
+ .append(userName)
+ .append(", write path: ")
+ .append(writePaths)
+ .append(", read path: ")
+ .append(readPaths);
+ return stringBuilder.toString();
+ }
}
diff --git
a/core/src/main/java/org/apache/gravitino/credential/config/CredentialConfig.java
b/core/src/main/java/org/apache/gravitino/credential/config/CredentialConfig.java
index d8823417cd..31a5183cc2 100644
---
a/core/src/main/java/org/apache/gravitino/credential/config/CredentialConfig.java
+++
b/core/src/main/java/org/apache/gravitino/credential/config/CredentialConfig.java
@@ -21,16 +21,23 @@ package org.apache.gravitino.credential.config;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.config.ConfigBuilder;
+import org.apache.gravitino.config.ConfigConstants;
+import org.apache.gravitino.config.ConfigEntry;
import org.apache.gravitino.connector.PropertyEntry;
import org.apache.gravitino.credential.CredentialConstants;
-public class CredentialConfig {
+public class CredentialConfig extends Config {
+
+ private static final long DEFAULT_CREDENTIAL_CACHE_MAX_SIZE = 10_000L;
+ private static final double DEFAULT_CREDENTIAL_CACHE_EXPIRE_RATIO = 0.15d;
public static final Map<String, PropertyEntry<?>>
CREDENTIAL_PROPERTY_ENTRIES =
new ImmutableMap.Builder<String, PropertyEntry<?>>()
.put(
CredentialConstants.CREDENTIAL_PROVIDERS,
- PropertyEntry.booleanPropertyEntry(
+ PropertyEntry.stringPropertyEntry(
CredentialConstants.CREDENTIAL_PROVIDERS,
"Credential providers for the Gravitino catalog, schema,
fileset, table, etc.",
false /* required */,
@@ -38,5 +45,50 @@ public class CredentialConfig {
null /* default value */,
false /* hidden */,
false /* reserved */))
+ .put(
+ CredentialConstants.CREDENTIAL_CACHE_EXPIRE_RATIO,
+ PropertyEntry.doublePropertyEntry(
+ CredentialConstants.CREDENTIAL_CACHE_EXPIRE_RATIO,
+ "Ratio of the credential's expiration time when Gravitino
remove credential from the cache.",
+ false /* required */,
+ false /* immutable */,
+ DEFAULT_CREDENTIAL_CACHE_EXPIRE_RATIO /* default value */,
+ false /* hidden */,
+ false /* reserved */))
+ .put(
+ CredentialConstants.CREDENTIAL_CACHE_MAX_SIZE,
+ PropertyEntry.longPropertyEntry(
+ CredentialConstants.CREDENTIAL_CACHE_MAX_SIZE,
+ "Max size for the credential cache.",
+ false /* required */,
+ false /* immutable */,
+ DEFAULT_CREDENTIAL_CACHE_MAX_SIZE /* default value */,
+ false /* hidden */,
+ false /* reserved */))
.build();
+
+ public static final ConfigEntry<Double> CREDENTIAL_CACHE_EXPIRE_RATIO =
+ new ConfigBuilder(CredentialConstants.CREDENTIAL_CACHE_EXPIRE_RATIO)
+ .doc(
+ "Ratio of the credential's expiration time when Gravitino remove
credential from the "
+ + "cache.")
+ .version(ConfigConstants.VERSION_0_8_0)
+ .doubleConf()
+ .checkValue(
+ ratio -> ratio >= 0 && ratio < 1,
+ "Ratio of the credential's expiration time should greater than
or equal to 0 "
+ + "and less than 1.")
+ .createWithDefault(DEFAULT_CREDENTIAL_CACHE_EXPIRE_RATIO);
+
+ public static final ConfigEntry<Long> CREDENTIAL_CACHE_MAX_SIZE =
+ new ConfigBuilder(CredentialConstants.CREDENTIAL_CACHE_MAX_SIZE)
+ .doc("Max cache size for the credential.")
+ .version(ConfigConstants.VERSION_0_8_0)
+ .longConf()
+ .createWithDefault(DEFAULT_CREDENTIAL_CACHE_MAX_SIZE);
+
+ public CredentialConfig(Map<String, String> properties) {
+ super(false);
+ loadFromMap(properties, k -> true);
+ }
}
diff --git
a/core/src/test/java/org/apache/gravitino/credential/TestCredentialCacheKey.java
b/core/src/test/java/org/apache/gravitino/credential/TestCredentialCacheKey.java
new file mode 100644
index 0000000000..b29c660a97
--- /dev/null
+++
b/core/src/test/java/org/apache/gravitino/credential/TestCredentialCacheKey.java
@@ -0,0 +1,56 @@
+/*
+ * 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.gravitino.credential;
+
+import java.util.Set;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
+
+public class TestCredentialCacheKey {
+
+ @Test
+ void testCredentialCacheKey() {
+
+ PathBasedCredentialContext context =
+ new PathBasedCredentialContext("user1", ImmutableSet.of("path1"),
ImmutableSet.of("path2"));
+ PathBasedCredentialContext contextWithDiffUser =
+ new PathBasedCredentialContext("user2", ImmutableSet.of("path1"),
ImmutableSet.of("path2"));
+ PathBasedCredentialContext contextWithDiffPath =
+ new PathBasedCredentialContext("user1", ImmutableSet.of("path3"),
ImmutableSet.of("path4"));
+
+ CredentialCacheKey key1 = new CredentialCacheKey("s3-token", context);
+
+ Set<CredentialCacheKey> cache = ImmutableSet.of(key1);
+ Assertions.assertTrue(cache.contains(key1));
+
+ // different user
+ CredentialCacheKey key2 = new CredentialCacheKey("s3-token",
contextWithDiffUser);
+ Assertions.assertFalse(cache.contains(key2));
+
+ // different path
+ CredentialCacheKey key3 = new CredentialCacheKey("s3-token",
contextWithDiffPath);
+ Assertions.assertFalse(cache.contains(key3));
+
+ // different credential type
+ CredentialCacheKey key4 = new CredentialCacheKey("s3-token1", context);
+ Assertions.assertFalse(cache.contains(key4));
+ }
+}