This is an automated email from the ASF dual-hosted git repository.
jshao 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 082bbdc157 [#5620] feat(fileset): Support credential vending for
fileset catalog (#5682)
082bbdc157 is described below
commit 082bbdc157206b43d07997025acb608aa8478e2a
Author: FANNG <[email protected]>
AuthorDate: Thu Dec 26 12:19:27 2024 +0800
[#5620] feat(fileset): Support credential vending for fileset catalog
(#5682)
### What changes were proposed in this pull request?
Support credential vending for fileset catalog
1. add `credential-providers` properties for the fileset catalog,
schema, and fileset.
2. try to get `credential-providers` from the order of fileset, schema,
and catalog.
3. The user could set multi-credential providers
### Why are the changes needed?
Fix: #5620
### Does this PR introduce _any_ user-facing change?
will add document after this PR is merged
### How was this patch tested?
Add IT and test with local setup Gravitino server
---
bundles/aws-bundle/build.gradle.kts | 2 +
.../gravitino/credential/CredentialConstants.java | 1 +
.../hadoop/HadoopCatalogPropertiesMetadata.java | 2 +
.../hadoop/HadoopFilesetPropertiesMetadata.java | 2 +
.../hadoop/HadoopSchemaPropertiesMetadata.java | 2 +
.../hadoop/SecureHadoopCatalogOperations.java | 63 ++++++--
.../test/FilesetCatalogCredentialIT.java | 160 +++++++++++++++++++++
.../java/org/apache/gravitino/GravitinoEnv.java | 15 +-
.../apache/gravitino/catalog/CatalogManager.java | 5 +
.../gravitino/catalog/CredentialManager.java | 53 -------
.../apache/gravitino/connector/BaseCatalog.java | 19 +++
.../connector/credential/PathContext.java | 63 ++++++++
.../credential/SupportsPathBasedCredentials.java | 43 ++++++
.../credential/CatalogCredentialManager.java | 70 +++++++++
.../credential/CredentialOperationDispatcher.java | 124 ++++++++++++++++
...edentialUtils.java => CredentialPrivilege.java} | 14 +-
.../gravitino/credential/CredentialUtils.java | 63 +++++++-
.../credential/config/CredentialConfig.java | 42 ++++++
.../credential/Dummy2CredentialProvider.java | 89 ++++++++++++
.../gravitino/credential/TestCredentialUtils.java | 66 +++++++++
....apache.gravitino.credential.CredentialProvider | 3 +-
.../apache/gravitino/server/GravitinoServer.java | 6 +-
.../rest/MetadataObjectCredentialOperations.java | 24 +++-
.../TestMetadataObjectCredentialOperations.java | 13 +-
24 files changed, 845 insertions(+), 99 deletions(-)
diff --git a/bundles/aws-bundle/build.gradle.kts
b/bundles/aws-bundle/build.gradle.kts
index 94c7d1cb2c..3af5c8b4f3 100644
--- a/bundles/aws-bundle/build.gradle.kts
+++ b/bundles/aws-bundle/build.gradle.kts
@@ -37,6 +37,7 @@ dependencies {
implementation(libs.aws.iam)
implementation(libs.aws.policy)
implementation(libs.aws.sts)
+ implementation(libs.commons.lang3)
implementation(libs.hadoop3.aws)
implementation(project(":catalogs:catalog-common")) {
exclude("*")
@@ -46,6 +47,7 @@ dependencies {
tasks.withType(ShadowJar::class.java) {
isZip64 = true
configurations = listOf(project.configurations.runtimeClasspath.get())
+ relocate("org.apache.commons",
"org.apache.gravitino.aws.shaded.org.apache.commons")
archiveClassifier.set("")
}
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 29f9241c89..d2753f24b5 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
@@ -21,6 +21,7 @@ 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 S3_TOKEN_CREDENTIAL_PROVIDER = "s3-token";
public static final String S3_TOKEN_EXPIRE_IN_SECS =
"s3-token-expire-in-secs";
diff --git
a/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/HadoopCatalogPropertiesMetadata.java
b/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/HadoopCatalogPropertiesMetadata.java
index 397e13aa4a..22cf0d5b2c 100644
---
a/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/HadoopCatalogPropertiesMetadata.java
+++
b/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/HadoopCatalogPropertiesMetadata.java
@@ -27,6 +27,7 @@ import
org.apache.gravitino.catalog.hadoop.fs.FileSystemProvider;
import org.apache.gravitino.catalog.hadoop.fs.LocalFileSystemProvider;
import org.apache.gravitino.connector.BaseCatalogPropertiesMetadata;
import org.apache.gravitino.connector.PropertyEntry;
+import org.apache.gravitino.credential.config.CredentialConfig;
public class HadoopCatalogPropertiesMetadata extends
BaseCatalogPropertiesMetadata {
@@ -84,6 +85,7 @@ public class HadoopCatalogPropertiesMetadata extends
BaseCatalogPropertiesMetada
// The following two are about authentication.
.putAll(KERBEROS_PROPERTY_ENTRIES)
.putAll(AuthenticationConfig.AUTHENTICATION_PROPERTY_ENTRIES)
+ .putAll(CredentialConfig.CREDENTIAL_PROPERTY_ENTRIES)
.build();
@Override
diff --git
a/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/HadoopFilesetPropertiesMetadata.java
b/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/HadoopFilesetPropertiesMetadata.java
index 250a48d292..84862dd094 100644
---
a/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/HadoopFilesetPropertiesMetadata.java
+++
b/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/HadoopFilesetPropertiesMetadata.java
@@ -24,6 +24,7 @@ import
org.apache.gravitino.catalog.hadoop.authentication.AuthenticationConfig;
import
org.apache.gravitino.catalog.hadoop.authentication.kerberos.KerberosConfig;
import org.apache.gravitino.connector.BasePropertiesMetadata;
import org.apache.gravitino.connector.PropertyEntry;
+import org.apache.gravitino.credential.config.CredentialConfig;
public class HadoopFilesetPropertiesMetadata extends BasePropertiesMetadata {
@@ -32,6 +33,7 @@ public class HadoopFilesetPropertiesMetadata extends
BasePropertiesMetadata {
ImmutableMap.Builder<String, PropertyEntry<?>> builder =
ImmutableMap.builder();
builder.putAll(KerberosConfig.KERBEROS_PROPERTY_ENTRIES);
builder.putAll(AuthenticationConfig.AUTHENTICATION_PROPERTY_ENTRIES);
+ builder.putAll(CredentialConfig.CREDENTIAL_PROPERTY_ENTRIES);
return builder.build();
}
}
diff --git
a/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/HadoopSchemaPropertiesMetadata.java
b/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/HadoopSchemaPropertiesMetadata.java
index 8892433ac6..9028cc48f3 100644
---
a/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/HadoopSchemaPropertiesMetadata.java
+++
b/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/HadoopSchemaPropertiesMetadata.java
@@ -24,6 +24,7 @@ import
org.apache.gravitino.catalog.hadoop.authentication.AuthenticationConfig;
import
org.apache.gravitino.catalog.hadoop.authentication.kerberos.KerberosConfig;
import org.apache.gravitino.connector.BasePropertiesMetadata;
import org.apache.gravitino.connector.PropertyEntry;
+import org.apache.gravitino.credential.config.CredentialConfig;
public class HadoopSchemaPropertiesMetadata extends BasePropertiesMetadata {
@@ -49,6 +50,7 @@ public class HadoopSchemaPropertiesMetadata extends
BasePropertiesMetadata {
false /* hidden */))
.putAll(KerberosConfig.KERBEROS_PROPERTY_ENTRIES)
.putAll(AuthenticationConfig.AUTHENTICATION_PROPERTY_ENTRIES)
+ .putAll(CredentialConfig.CREDENTIAL_PROPERTY_ENTRIES)
.build();
@Override
diff --git
a/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/SecureHadoopCatalogOperations.java
b/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/SecureHadoopCatalogOperations.java
index 2180e45d42..7ae10805b5 100644
---
a/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/SecureHadoopCatalogOperations.java
+++
b/catalogs/catalog-hadoop/src/main/java/org/apache/gravitino/catalog/hadoop/SecureHadoopCatalogOperations.java
@@ -20,11 +20,16 @@
package org.apache.gravitino.catalog.hadoop;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
import java.io.IOException;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
import javax.security.auth.Subject;
+import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.Entity;
import org.apache.gravitino.EntityStore;
@@ -38,6 +43,9 @@ import org.apache.gravitino.connector.CatalogInfo;
import org.apache.gravitino.connector.CatalogOperations;
import org.apache.gravitino.connector.HasPropertyMetadata;
import org.apache.gravitino.connector.SupportsSchemas;
+import org.apache.gravitino.connector.credential.PathContext;
+import org.apache.gravitino.connector.credential.SupportsPathBasedCredentials;
+import org.apache.gravitino.credential.CredentialUtils;
import org.apache.gravitino.exceptions.FilesetAlreadyExistsException;
import org.apache.gravitino.exceptions.NoSuchCatalogException;
import org.apache.gravitino.exceptions.NoSuchEntityException;
@@ -50,13 +58,14 @@ import org.apache.gravitino.file.FilesetCatalog;
import org.apache.gravitino.file.FilesetChange;
import org.apache.gravitino.meta.FilesetEntity;
import org.apache.gravitino.meta.SchemaEntity;
+import org.apache.gravitino.utils.NameIdentifierUtil;
import org.apache.gravitino.utils.PrincipalUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("removal")
public class SecureHadoopCatalogOperations
- implements CatalogOperations, SupportsSchemas, FilesetCatalog {
+ implements CatalogOperations, SupportsSchemas, FilesetCatalog,
SupportsPathBasedCredentials {
public static final Logger LOG =
LoggerFactory.getLogger(SecureHadoopCatalogOperations.class);
@@ -66,6 +75,8 @@ public class SecureHadoopCatalogOperations
private UserContext catalogUserContext;
+ private Map<String, String> catalogProperties;
+
public SecureHadoopCatalogOperations() {
this.hadoopCatalogOperations = new HadoopCatalogOperations();
}
@@ -74,6 +85,20 @@ public class SecureHadoopCatalogOperations
this.hadoopCatalogOperations = new HadoopCatalogOperations(store);
}
+ @Override
+ public void initialize(
+ Map<String, String> config, CatalogInfo info, HasPropertyMetadata
propertiesMetadata)
+ throws RuntimeException {
+ hadoopCatalogOperations.initialize(config, info, propertiesMetadata);
+ this.catalogUserContext =
+ UserContext.getUserContext(
+ NameIdentifier.of(info.namespace(), info.name()),
+ config,
+ hadoopCatalogOperations.getHadoopConf(),
+ info);
+ this.catalogProperties = info.properties();
+ }
+
@VisibleForTesting
public HadoopCatalogOperations getBaseHadoopCatalogOperations() {
return hadoopCatalogOperations;
@@ -163,19 +188,6 @@ public class SecureHadoopCatalogOperations
}
}
- @Override
- public void initialize(
- Map<String, String> config, CatalogInfo info, HasPropertyMetadata
propertiesMetadata)
- throws RuntimeException {
- hadoopCatalogOperations.initialize(config, info, propertiesMetadata);
- catalogUserContext =
- UserContext.getUserContext(
- NameIdentifier.of(info.namespace(), info.name()),
- config,
- hadoopCatalogOperations.getHadoopConf(),
- info);
- }
-
@Override
public Fileset alterFileset(NameIdentifier ident, FilesetChange... changes)
throws NoSuchFilesetException, IllegalArgumentException {
@@ -245,6 +257,29 @@ public class SecureHadoopCatalogOperations
hadoopCatalogOperations.testConnection(catalogIdent, type, provider,
comment, properties);
}
+ @Override
+ public List<PathContext> getPathContext(NameIdentifier filesetIdentifier) {
+ Fileset fileset = loadFileset(filesetIdentifier);
+ String path = fileset.storageLocation();
+ Preconditions.checkState(
+ StringUtils.isNotBlank(path), "The location of fileset should not be
empty.");
+
+ Set<String> providers =
+ CredentialUtils.getCredentialProvidersByOrder(
+ () -> fileset.properties(),
+ () -> {
+ Namespace namespace = filesetIdentifier.namespace();
+ NameIdentifier schemaIdentifier =
+ NameIdentifierUtil.ofSchema(
+ namespace.level(0), namespace.level(1),
namespace.level(2));
+ return loadSchema(schemaIdentifier).properties();
+ },
+ () -> catalogProperties);
+ return providers.stream()
+ .map(provider -> new PathContext(path, provider))
+ .collect(Collectors.toList());
+ }
+
/**
* Add the user to the subject so that we can get the last user in the
subject. Hadoop catalog
* uses this method to pass api user from the client side, so that we can
get the user in the
diff --git
a/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/integration/test/FilesetCatalogCredentialIT.java
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/integration/test/FilesetCatalogCredentialIT.java
new file mode 100644
index 0000000000..94239fef28
--- /dev/null
+++
b/clients/filesystem-hadoop3/src/test/java/org/apache/gravitino/filesystem/hadoop/integration/test/FilesetCatalogCredentialIT.java
@@ -0,0 +1,160 @@
+/*
+ * 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.filesystem.hadoop.integration.test;
+
+import static
org.apache.gravitino.catalog.hadoop.HadoopCatalogPropertiesMetadata.FILESYSTEM_PROVIDERS;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.client.GravitinoMetalake;
+import org.apache.gravitino.credential.Credential;
+import org.apache.gravitino.credential.CredentialConstants;
+import org.apache.gravitino.credential.S3SecretKeyCredential;
+import org.apache.gravitino.credential.S3TokenCredential;
+import org.apache.gravitino.file.Fileset;
+import org.apache.gravitino.integration.test.util.BaseIT;
+import org.apache.gravitino.integration.test.util.GravitinoITUtils;
+import org.apache.gravitino.storage.S3Properties;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@EnabledIfEnvironmentVariable(named = "GRAVITINO_TEST_CLOUD_IT", matches =
"true")
+public class FilesetCatalogCredentialIT extends BaseIT {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(FilesetCatalogCredentialIT.class);
+
+ public static final String BUCKET_NAME = System.getenv("S3_BUCKET_NAME");
+ public static final String S3_ACCESS_KEY = System.getenv("S3_ACCESS_KEY_ID");
+ public static final String S3_SECRET_KEY =
System.getenv("S3_SECRET_ACCESS_KEY");
+ public static final String S3_ROLE_ARN = System.getenv("S3_ROLE_ARN");
+
+ private String metalakeName =
GravitinoITUtils.genRandomName("gvfs_it_metalake");
+ private String catalogName = GravitinoITUtils.genRandomName("catalog");
+ private String schemaName = GravitinoITUtils.genRandomName("schema");
+ private GravitinoMetalake metalake;
+
+ @BeforeAll
+ public void startIntegrationTest() {
+ // Do nothing
+ }
+
+ @BeforeAll
+ public void startUp() throws Exception {
+ copyBundleJarsToHadoop("aws-bundle");
+ // Need to download jars to gravitino server
+ super.startIntegrationTest();
+
+ metalakeName = GravitinoITUtils.genRandomName("gvfs_it_metalake");
+ catalogName = GravitinoITUtils.genRandomName("catalog");
+ schemaName = GravitinoITUtils.genRandomName("schema");
+
+ Assertions.assertFalse(client.metalakeExists(metalakeName));
+ metalake = client.createMetalake(metalakeName, "metalake comment",
Collections.emptyMap());
+ Assertions.assertTrue(client.metalakeExists(metalakeName));
+
+ Map<String, String> properties = Maps.newHashMap();
+ properties.put(FILESYSTEM_PROVIDERS, "s3");
+ properties.put(
+ CredentialConstants.CREDENTIAL_PROVIDERS,
+ S3TokenCredential.S3_TOKEN_CREDENTIAL_TYPE
+ + ","
+ + S3SecretKeyCredential.S3_SECRET_KEY_CREDENTIAL_TYPE);
+ properties.put(
+ CredentialConstants.CREDENTIAL_PROVIDER_TYPE,
+ S3SecretKeyCredential.S3_SECRET_KEY_CREDENTIAL_TYPE);
+ properties.put(S3Properties.GRAVITINO_S3_ACCESS_KEY_ID, S3_ACCESS_KEY);
+ properties.put(S3Properties.GRAVITINO_S3_SECRET_ACCESS_KEY, S3_SECRET_KEY);
+ properties.put(S3Properties.GRAVITINO_S3_ENDPOINT,
"s3.ap-southeast-2.amazonaws.com");
+ properties.put(S3Properties.GRAVITINO_S3_REGION, "ap-southeast-2");
+ properties.put(S3Properties.GRAVITINO_S3_ROLE_ARN, S3_ROLE_ARN);
+
+ Catalog catalog =
+ metalake.createCatalog(
+ catalogName, Catalog.Type.FILESET, "hadoop", "catalog comment",
properties);
+ Assertions.assertTrue(metalake.catalogExists(catalogName));
+
+ catalog.asSchemas().createSchema(schemaName, "schema comment", properties);
+ Assertions.assertTrue(catalog.asSchemas().schemaExists(schemaName));
+ }
+
+ @AfterAll
+ public void tearDown() throws IOException {
+ Catalog catalog = metalake.loadCatalog(catalogName);
+ catalog.asSchemas().dropSchema(schemaName, true);
+ metalake.dropCatalog(catalogName, true);
+ client.dropMetalake(metalakeName, true);
+
+ if (client != null) {
+ client.close();
+ client = null;
+ }
+
+ try {
+ closer.close();
+ } catch (Exception e) {
+ LOG.error("Exception in closing CloseableGroup", e);
+ }
+ }
+
+ protected String genStorageLocation(String fileset) {
+ return String.format("s3a://%s/%s", BUCKET_NAME, fileset);
+ }
+
+ @Test
+ void testGetCatalogCredential() {
+ Catalog catalog = metalake.loadCatalog(catalogName);
+ Credential[] credentials = catalog.supportsCredentials().getCredentials();
+ Assertions.assertEquals(1, credentials.length);
+ Assertions.assertTrue(credentials[0] instanceof S3SecretKeyCredential);
+ }
+
+ @Test
+ void testGetFilesetCredential() {
+ String filesetName =
GravitinoITUtils.genRandomName("test_fileset_credential");
+ NameIdentifier filesetIdent = NameIdentifier.of(schemaName, filesetName);
+ Catalog catalog = metalake.loadCatalog(catalogName);
+ String storageLocation = genStorageLocation(filesetName);
+ catalog
+ .asFilesetCatalog()
+ .createFileset(
+ filesetIdent,
+ "fileset comment",
+ Fileset.Type.MANAGED,
+ storageLocation,
+ ImmutableMap.of(
+ CredentialConstants.CREDENTIAL_PROVIDERS,
+ S3TokenCredential.S3_TOKEN_CREDENTIAL_TYPE));
+
+ Fileset fileset = catalog.asFilesetCatalog().loadFileset(filesetIdent);
+ Credential[] credentials = fileset.supportsCredentials().getCredentials();
+ Assertions.assertEquals(1, credentials.length);
+ Assertions.assertTrue(credentials[0] instanceof S3TokenCredential);
+ }
+}
diff --git a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
index 96c60b834f..57f04a0cfb 100644
--- a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
+++ b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
@@ -28,7 +28,6 @@ import org.apache.gravitino.auxiliary.AuxiliaryServiceManager;
import org.apache.gravitino.catalog.CatalogDispatcher;
import org.apache.gravitino.catalog.CatalogManager;
import org.apache.gravitino.catalog.CatalogNormalizeDispatcher;
-import org.apache.gravitino.catalog.CredentialManager;
import org.apache.gravitino.catalog.FilesetDispatcher;
import org.apache.gravitino.catalog.FilesetNormalizeDispatcher;
import org.apache.gravitino.catalog.FilesetOperationDispatcher;
@@ -47,6 +46,7 @@ import org.apache.gravitino.catalog.TableOperationDispatcher;
import org.apache.gravitino.catalog.TopicDispatcher;
import org.apache.gravitino.catalog.TopicNormalizeDispatcher;
import org.apache.gravitino.catalog.TopicOperationDispatcher;
+import org.apache.gravitino.credential.CredentialOperationDispatcher;
import org.apache.gravitino.hook.AccessControlHookDispatcher;
import org.apache.gravitino.hook.CatalogHookDispatcher;
import org.apache.gravitino.hook.FilesetHookDispatcher;
@@ -108,7 +108,7 @@ public class GravitinoEnv {
private MetalakeDispatcher metalakeDispatcher;
- private CredentialManager credentialManager;
+ private CredentialOperationDispatcher credentialOperationDispatcher;
private TagDispatcher tagDispatcher;
@@ -264,12 +264,12 @@ public class GravitinoEnv {
}
/**
- * Get the {@link CredentialManager} associated with the Gravitino
environment.
+ * Get the {@link CredentialOperationDispatcher} associated with the
Gravitino environment.
*
- * @return The {@link CredentialManager} instance.
+ * @return The {@link CredentialOperationDispatcher} instance.
*/
- public CredentialManager credentialManager() {
- return credentialManager;
+ public CredentialOperationDispatcher credentialOperationDispatcher() {
+ return credentialOperationDispatcher;
}
/**
@@ -432,7 +432,8 @@ public class GravitinoEnv {
new CatalogNormalizeDispatcher(catalogHookDispatcher);
this.catalogDispatcher = new CatalogEventDispatcher(eventBus,
catalogNormalizeDispatcher);
- this.credentialManager = new CredentialManager(catalogManager,
entityStore, idGenerator);
+ this.credentialOperationDispatcher =
+ new CredentialOperationDispatcher(catalogManager, entityStore,
idGenerator);
SchemaOperationDispatcher schemaOperationDispatcher =
new SchemaOperationDispatcher(catalogManager, entityStore,
idGenerator);
diff --git
a/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
b/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
index 4a46952f87..1e9c1d9d94 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/CatalogManager.java
@@ -126,6 +126,7 @@ public class CatalogManager implements CatalogDispatcher,
Closeable {
/** Wrapper class for a catalog instance and its class loader. */
public static class CatalogWrapper {
+
private BaseCatalog catalog;
private IsolatedClassLoader classLoader;
@@ -169,6 +170,10 @@ public class CatalogManager implements CatalogDispatcher,
Closeable {
});
}
+ public <R> R doWithCredentialOps(ThrowableFunction<BaseCatalog, R> fn)
throws Exception {
+ return classLoader.withClassLoader(cl -> fn.apply(catalog));
+ }
+
public <R> R doWithTopicOps(ThrowableFunction<TopicCatalog, R> fn) throws
Exception {
return classLoader.withClassLoader(
cl -> {
diff --git
a/core/src/main/java/org/apache/gravitino/catalog/CredentialManager.java
b/core/src/main/java/org/apache/gravitino/catalog/CredentialManager.java
deleted file mode 100644
index 808fc96fb0..0000000000
--- a/core/src/main/java/org/apache/gravitino/catalog/CredentialManager.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.catalog;
-
-import java.util.List;
-import org.apache.commons.lang3.NotImplementedException;
-import org.apache.gravitino.EntityStore;
-import org.apache.gravitino.NameIdentifier;
-import org.apache.gravitino.connector.BaseCatalog;
-import org.apache.gravitino.credential.Credential;
-import org.apache.gravitino.exceptions.NoSuchCatalogException;
-import org.apache.gravitino.storage.IdGenerator;
-import org.apache.gravitino.utils.NameIdentifierUtil;
-
-/** Get credentials with the specific catalog classloader. */
-public class CredentialManager extends OperationDispatcher {
-
- public CredentialManager(
- CatalogManager catalogManager, EntityStore store, IdGenerator
idGenerator) {
- super(catalogManager, store, idGenerator);
- }
-
- public List<Credential> getCredentials(NameIdentifier identifier) {
- return doWithCatalog(
- NameIdentifierUtil.getCatalogIdentifier(identifier),
- c -> getCredentials(c.catalog(), identifier),
- NoSuchCatalogException.class);
- }
-
- private List<Credential> getCredentials(BaseCatalog catalog, NameIdentifier
identifier) {
- throw new NotImplementedException(
- String.format(
- "Load credentials is not implemented for catalog: %s, identifier:
%s",
- catalog.name(), identifier));
- }
-}
diff --git a/core/src/main/java/org/apache/gravitino/connector/BaseCatalog.java
b/core/src/main/java/org/apache/gravitino/connector/BaseCatalog.java
index 218c2a428b..14b1912b4d 100644
--- a/core/src/main/java/org/apache/gravitino/connector/BaseCatalog.java
+++ b/core/src/main/java/org/apache/gravitino/connector/BaseCatalog.java
@@ -31,6 +31,7 @@ import org.apache.gravitino.annotation.Evolving;
import org.apache.gravitino.connector.authorization.AuthorizationPlugin;
import org.apache.gravitino.connector.authorization.BaseAuthorization;
import org.apache.gravitino.connector.capability.Capability;
+import org.apache.gravitino.credential.CatalogCredentialManager;
import org.apache.gravitino.meta.CatalogEntity;
import org.apache.gravitino.utils.IsolatedClassLoader;
import org.slf4j.Logger;
@@ -51,6 +52,7 @@ import org.slf4j.LoggerFactory;
@Evolving
public abstract class BaseCatalog<T extends BaseCatalog>
implements Catalog, CatalogProvider, HasPropertyMetadata, Closeable {
+
private static final Logger LOG = LoggerFactory.getLogger(BaseCatalog.class);
// This variable is used as a key in properties of catalogs to inject custom
operation to
@@ -72,6 +74,8 @@ public abstract class BaseCatalog<T extends BaseCatalog>
private volatile Map<String, String> properties;
+ private volatile CatalogCredentialManager catalogCredentialManager;
+
private static String ENTITY_IS_NOT_SET = "entity is not set";
// Any Gravitino configuration that starts with this prefix will be trim and
passed to the
@@ -225,6 +229,10 @@ public abstract class BaseCatalog<T extends BaseCatalog>
authorizationPlugin.close();
authorizationPlugin = null;
}
+ if (catalogCredentialManager != null) {
+ catalogCredentialManager.close();
+ catalogCredentialManager = null;
+ }
}
public Capability capability() {
@@ -239,6 +247,17 @@ public abstract class BaseCatalog<T extends BaseCatalog>
return capability;
}
+ public CatalogCredentialManager catalogCredentialManager() {
+ if (catalogCredentialManager == null) {
+ synchronized (this) {
+ if (catalogCredentialManager == null) {
+ this.catalogCredentialManager = new CatalogCredentialManager(name(),
properties());
+ }
+ }
+ }
+ return catalogCredentialManager;
+ }
+
private CatalogOperations createOps(Map<String, String> conf) {
String customCatalogOperationClass = conf.get(CATALOG_OPERATION_IMPL);
return Optional.ofNullable(customCatalogOperationClass)
diff --git
a/core/src/main/java/org/apache/gravitino/connector/credential/PathContext.java
b/core/src/main/java/org/apache/gravitino/connector/credential/PathContext.java
new file mode 100644
index 0000000000..5c520d6bfd
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/connector/credential/PathContext.java
@@ -0,0 +1,63 @@
+/*
+ * 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.connector.credential;
+
+import org.apache.gravitino.annotation.DeveloperApi;
+
+/**
+ * The {@code PathContext} class represents the path and its associated
credential type to generate
+ * a credential for {@link
org.apache.gravitino.credential.CredentialOperationDispatcher}.
+ */
+@DeveloperApi
+public class PathContext {
+
+ private final String path;
+
+ private final String credentialType;
+
+ /**
+ * Constructs a new {@code PathContext} instance with the given path and
credential type.
+ *
+ * @param path The path string.
+ * @param credentialType The type of the credential.
+ */
+ public PathContext(String path, String credentialType) {
+ this.path = path;
+ this.credentialType = credentialType;
+ }
+
+ /**
+ * Gets the path string.
+ *
+ * @return The path associated with this instance.
+ */
+ public String path() {
+ return path;
+ }
+
+ /**
+ * Gets the credential type.
+ *
+ * @return The credential type associated with this instance.
+ */
+ public String credentialType() {
+ return credentialType;
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/connector/credential/SupportsPathBasedCredentials.java
b/core/src/main/java/org/apache/gravitino/connector/credential/SupportsPathBasedCredentials.java
new file mode 100644
index 0000000000..93e08a3906
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/connector/credential/SupportsPathBasedCredentials.java
@@ -0,0 +1,43 @@
+/*
+ * 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.connector.credential;
+
+import java.util.List;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+
+/** The catalog operation should implement this interface to generate the path
based credentials. */
+@DeveloperApi
+public interface SupportsPathBasedCredentials {
+
+ /**
+ * Get {@link PathContext} lists.
+ *
+ * <p>In most cases there will be only one element in the list. For catalogs
which support multi
+ * locations like fileset, there may be multiple elements.
+ *
+ * <p>The name identifier is the identifier of the resource like fileset,
table, etc. not include
+ * metalake, catalog, schema.
+ *
+ * @param nameIdentifier, The identifier for fileset, table, etc.
+ * @return A list of {@link PathContext}
+ */
+ List<PathContext> getPathContext(NameIdentifier nameIdentifier);
+}
diff --git
a/core/src/main/java/org/apache/gravitino/credential/CatalogCredentialManager.java
b/core/src/main/java/org/apache/gravitino/credential/CatalogCredentialManager.java
new file mode 100644
index 0000000000..2fe6fedccd
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/credential/CatalogCredentialManager.java
@@ -0,0 +1,70 @@
+/*
+ * 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.google.common.base.Preconditions;
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Map;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Manage lifetime of the credential provider in one catalog, dispatch
credential request to the
+ * corresponding credential provider.
+ */
+public class CatalogCredentialManager implements Closeable {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(CatalogCredentialManager.class);
+
+ 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);
+ }
+
+ 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);
+ }
+
+ @Override
+ public void close() {
+ credentialProviders
+ .values()
+ .forEach(
+ credentialProvider -> {
+ try {
+ credentialProvider.close();
+ } catch (IOException e) {
+ LOG.warn(
+ "Close credential provider failed, catalog: {}, credential
provider: {}",
+ catalogName,
+ credentialProvider.credentialType(),
+ e);
+ }
+ });
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/credential/CredentialOperationDispatcher.java
b/core/src/main/java/org/apache/gravitino/credential/CredentialOperationDispatcher.java
new file mode 100644
index 0000000000..2ec76aeb4a
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/credential/CredentialOperationDispatcher.java
@@ -0,0 +1,124 @@
+/*
+ * 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.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.ws.rs.NotAuthorizedException;
+import javax.ws.rs.NotSupportedException;
+import org.apache.gravitino.EntityStore;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.catalog.CatalogManager;
+import org.apache.gravitino.catalog.OperationDispatcher;
+import org.apache.gravitino.connector.BaseCatalog;
+import org.apache.gravitino.connector.credential.PathContext;
+import org.apache.gravitino.connector.credential.SupportsPathBasedCredentials;
+import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.storage.IdGenerator;
+import org.apache.gravitino.utils.NameIdentifierUtil;
+import org.apache.gravitino.utils.PrincipalUtils;
+
+/** Get credentials with the specific catalog classloader. */
+public class CredentialOperationDispatcher extends OperationDispatcher {
+
+ public CredentialOperationDispatcher(
+ CatalogManager catalogManager, EntityStore store, IdGenerator
idGenerator) {
+ super(catalogManager, store, idGenerator);
+ }
+
+ public List<Credential> getCredentials(NameIdentifier identifier) {
+ CredentialPrivilege privilege =
+ getCredentialPrivilege(PrincipalUtils.getCurrentUserName(),
identifier);
+ return doWithCatalog(
+ NameIdentifierUtil.getCatalogIdentifier(identifier),
+ catalogWrapper ->
+ catalogWrapper.doWithCredentialOps(
+ baseCatalog -> getCredentials(baseCatalog, identifier,
privilege)),
+ NoSuchCatalogException.class);
+ }
+
+ private List<Credential> getCredentials(
+ BaseCatalog baseCatalog, NameIdentifier nameIdentifier,
CredentialPrivilege privilege) {
+ Map<String, CredentialContext> contexts =
+ getCredentialContexts(baseCatalog, nameIdentifier, privilege);
+ return contexts.entrySet().stream()
+ .map(
+ entry ->
+ baseCatalog
+ .catalogCredentialManager()
+ .getCredential(entry.getKey(), entry.getValue()))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ private Map<String, CredentialContext> getCredentialContexts(
+ BaseCatalog baseCatalog, NameIdentifier nameIdentifier,
CredentialPrivilege privilege) {
+ if
(nameIdentifier.equals(NameIdentifierUtil.getCatalogIdentifier(nameIdentifier)))
{
+ return getCatalogCredentialContexts(baseCatalog.properties());
+ }
+
+ if (baseCatalog.ops() instanceof SupportsPathBasedCredentials) {
+ List<PathContext> pathContexts =
+ ((SupportsPathBasedCredentials)
baseCatalog.ops()).getPathContext(nameIdentifier);
+ return getPathBasedCredentialContexts(privilege, pathContexts);
+ }
+ throw new NotSupportedException(
+ String.format("Catalog %s doesn't support generate credentials",
baseCatalog.name()));
+ }
+
+ private Map<String, CredentialContext> getCatalogCredentialContexts(
+ Map<String, String> catalogProperties) {
+ CatalogCredentialContext context =
+ new CatalogCredentialContext(PrincipalUtils.getCurrentUserName());
+ Set<String> providers = CredentialUtils.getCredentialProvidersByOrder(()
-> catalogProperties);
+ return providers.stream().collect(Collectors.toMap(provider -> provider,
provider -> context));
+ }
+
+ public static Map<String, CredentialContext> getPathBasedCredentialContexts(
+ CredentialPrivilege privilege, List<PathContext> pathContexts) {
+ return pathContexts.stream()
+ .collect(
+ Collectors.toMap(
+ pathContext -> pathContext.credentialType(),
+ pathContext -> {
+ String path = pathContext.path();
+ Set<String> writePaths = new HashSet<>();
+ Set<String> readPaths = new HashSet<>();
+ if (CredentialPrivilege.WRITE.equals(privilege)) {
+ writePaths.add(path);
+ } else {
+ readPaths.add(path);
+ }
+ return new PathBasedCredentialContext(
+ PrincipalUtils.getCurrentUserName(), writePaths,
readPaths);
+ }));
+ }
+
+ @SuppressWarnings("UnusedVariable")
+ private CredentialPrivilege getCredentialPrivilege(String user,
NameIdentifier identifier)
+ throws NotAuthorizedException {
+ // TODO: will implement in another PR
+ return CredentialPrivilege.WRITE;
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/credential/CredentialUtils.java
b/core/src/main/java/org/apache/gravitino/credential/CredentialPrivilege.java
similarity index 63%
copy from
core/src/main/java/org/apache/gravitino/credential/CredentialUtils.java
copy to
core/src/main/java/org/apache/gravitino/credential/CredentialPrivilege.java
index 09439d58ae..3ff77cd3e8 100644
--- a/core/src/main/java/org/apache/gravitino/credential/CredentialUtils.java
+++
b/core/src/main/java/org/apache/gravitino/credential/CredentialPrivilege.java
@@ -19,14 +19,8 @@
package org.apache.gravitino.credential;
-import com.google.common.collect.ImmutableSet;
-import org.apache.gravitino.utils.PrincipalUtils;
-
-public class CredentialUtils {
- public static Credential vendCredential(CredentialProvider
credentialProvider, String[] path) {
- PathBasedCredentialContext pathBasedCredentialContext =
- new PathBasedCredentialContext(
- PrincipalUtils.getCurrentUserName(), ImmutableSet.copyOf(path),
ImmutableSet.of());
- return credentialProvider.getCredential(pathBasedCredentialContext);
- }
+/** Represents the privilege to get credential from credential providers. */
+public enum CredentialPrivilege {
+ READ,
+ WRITE,
}
diff --git
a/core/src/main/java/org/apache/gravitino/credential/CredentialUtils.java
b/core/src/main/java/org/apache/gravitino/credential/CredentialUtils.java
index 09439d58ae..9a202ec974 100644
--- a/core/src/main/java/org/apache/gravitino/credential/CredentialUtils.java
+++ b/core/src/main/java/org/apache/gravitino/credential/CredentialUtils.java
@@ -19,14 +19,75 @@
package org.apache.gravitino.credential;
+import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
import org.apache.gravitino.utils.PrincipalUtils;
public class CredentialUtils {
+
+ private static final Splitter splitter = Splitter.on(",");
+
public static Credential vendCredential(CredentialProvider
credentialProvider, String[] path) {
PathBasedCredentialContext pathBasedCredentialContext =
new PathBasedCredentialContext(
- PrincipalUtils.getCurrentUserName(), ImmutableSet.copyOf(path),
ImmutableSet.of());
+ PrincipalUtils.getCurrentUserName(), ImmutableSet.copyOf(path),
Collections.emptySet());
return credentialProvider.getCredential(pathBasedCredentialContext);
}
+
+ public static Map<String, CredentialProvider> loadCredentialProviders(
+ Map<String, String> catalogProperties) {
+ Set<String> credentialProviders =
+ CredentialUtils.getCredentialProvidersByOrder(() -> catalogProperties);
+
+ return credentialProviders.stream()
+ .collect(
+ Collectors.toMap(
+ String::toString,
+ credentialType ->
+ CredentialProviderFactory.create(credentialType,
catalogProperties)));
+ }
+
+ /**
+ * Get Credential providers from properties supplier.
+ *
+ * <p>If there are multiple properties suppliers, will try to get the
credential providers in the
+ * input order.
+ *
+ * @param propertiesSuppliers The properties suppliers.
+ * @return A set of credential providers.
+ */
+ public static Set<String> getCredentialProvidersByOrder(
+ Supplier<Map<String, String>>... propertiesSuppliers) {
+
+ for (Supplier<Map<String, String>> supplier : propertiesSuppliers) {
+ Map<String, String> properties = supplier.get();
+ Set<String> providers = getCredentialProvidersFromProperties(properties);
+ if (!providers.isEmpty()) {
+ return providers;
+ }
+ }
+
+ return Collections.emptySet();
+ }
+
+ private static Set<String> getCredentialProvidersFromProperties(Map<String,
String> properties) {
+ if (properties == null) {
+ return Collections.emptySet();
+ }
+
+ String providers =
properties.get(CredentialConstants.CREDENTIAL_PROVIDERS);
+ if (providers == null) {
+ return Collections.emptySet();
+ }
+ return splitter
+ .trimResults()
+ .omitEmptyStrings()
+ .splitToStream(providers)
+ .collect(Collectors.toSet());
+ }
}
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
new file mode 100644
index 0000000000..d8823417cd
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/credential/config/CredentialConfig.java
@@ -0,0 +1,42 @@
+/*
+ * 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.config;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import org.apache.gravitino.connector.PropertyEntry;
+import org.apache.gravitino.credential.CredentialConstants;
+
+public class CredentialConfig {
+
+ public static final Map<String, PropertyEntry<?>>
CREDENTIAL_PROPERTY_ENTRIES =
+ new ImmutableMap.Builder<String, PropertyEntry<?>>()
+ .put(
+ CredentialConstants.CREDENTIAL_PROVIDERS,
+ PropertyEntry.booleanPropertyEntry(
+ CredentialConstants.CREDENTIAL_PROVIDERS,
+ "Credential providers for the Gravitino catalog, schema,
fileset, table, etc.",
+ false /* required */,
+ false /* immutable */,
+ null /* default value */,
+ false /* hidden */,
+ false /* reserved */))
+ .build();
+}
diff --git
a/core/src/test/java/org/apache/gravitino/credential/Dummy2CredentialProvider.java
b/core/src/test/java/org/apache/gravitino/credential/Dummy2CredentialProvider.java
new file mode 100644
index 0000000000..63f63d61d0
--- /dev/null
+++
b/core/src/test/java/org/apache/gravitino/credential/Dummy2CredentialProvider.java
@@ -0,0 +1,89 @@
+/*
+ * 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.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import java.util.Set;
+import javax.ws.rs.NotSupportedException;
+import lombok.Getter;
+
+public class Dummy2CredentialProvider implements CredentialProvider {
+ Map<String, String> properties;
+ static final String CREDENTIAL_TYPE = "dummy2";
+
+ @Override
+ public void initialize(Map<String, String> properties) {
+ this.properties = properties;
+ }
+
+ @Override
+ public void close() {}
+
+ @Override
+ public String credentialType() {
+ return CREDENTIAL_TYPE;
+ }
+
+ @Override
+ public Credential getCredential(CredentialContext context) {
+ Preconditions.checkArgument(
+ context instanceof PathBasedCredentialContext
+ || context instanceof CatalogCredentialContext,
+ "Doesn't support context: " + context.getClass().getSimpleName());
+ if (context instanceof PathBasedCredentialContext) {
+ return new Dummy2Credential((PathBasedCredentialContext) context);
+ }
+ return null;
+ }
+
+ public static class Dummy2Credential implements Credential {
+
+ @Getter private Set<String> writeLocations;
+ @Getter private Set<String> readLocations;
+
+ public Dummy2Credential(PathBasedCredentialContext locationContext) {
+ this.writeLocations = locationContext.getWritePaths();
+ this.readLocations = locationContext.getReadPaths();
+ }
+
+ @Override
+ public String credentialType() {
+ return Dummy2CredentialProvider.CREDENTIAL_TYPE;
+ }
+
+ @Override
+ public long expireTimeInMs() {
+ return 0;
+ }
+
+ @Override
+ public Map<String, String> credentialInfo() {
+ return ImmutableMap.of(
+ "writeLocation", writeLocations.toString(), "readLocation",
readLocations.toString());
+ }
+
+ @Override
+ public void initialize(Map<String, String> credentialInfo, long
expireTimeInMs) {
+ throw new NotSupportedException();
+ }
+ }
+}
diff --git
a/core/src/test/java/org/apache/gravitino/credential/TestCredentialUtils.java
b/core/src/test/java/org/apache/gravitino/credential/TestCredentialUtils.java
new file mode 100644
index 0000000000..c31affdc15
--- /dev/null
+++
b/core/src/test/java/org/apache/gravitino/credential/TestCredentialUtils.java
@@ -0,0 +1,66 @@
+/*
+ * 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.Map;
+import java.util.Set;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableMap;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
+
+public class TestCredentialUtils {
+
+ @Test
+ void testLoadCredentialProviders() {
+ Map<String, String> catalogProperties =
+ ImmutableMap.of(
+ CredentialConstants.CREDENTIAL_PROVIDERS,
+ DummyCredentialProvider.CREDENTIAL_TYPE
+ + ","
+ + Dummy2CredentialProvider.CREDENTIAL_TYPE);
+ Map<String, CredentialProvider> providers =
+ CredentialUtils.loadCredentialProviders(catalogProperties);
+ Assertions.assertTrue(providers.size() == 2);
+
+
Assertions.assertTrue(providers.containsKey(DummyCredentialProvider.CREDENTIAL_TYPE));
+ Assertions.assertTrue(
+ DummyCredentialProvider.CREDENTIAL_TYPE.equals(
+
providers.get(DummyCredentialProvider.CREDENTIAL_TYPE).credentialType()));
+
Assertions.assertTrue(providers.containsKey(Dummy2CredentialProvider.CREDENTIAL_TYPE));
+ Assertions.assertTrue(
+ Dummy2CredentialProvider.CREDENTIAL_TYPE.equals(
+
providers.get(Dummy2CredentialProvider.CREDENTIAL_TYPE).credentialType()));
+ }
+
+ @Test
+ void testGetCredentialProviders() {
+ Map<String, String> filesetProperties = ImmutableMap.of();
+ Map<String, String> schemaProperties =
+ ImmutableMap.of(CredentialConstants.CREDENTIAL_PROVIDERS, "a,b");
+ Map<String, String> catalogProperties =
+ ImmutableMap.of(CredentialConstants.CREDENTIAL_PROVIDERS, "a,b,c");
+
+ Set<String> credentialProviders =
+ CredentialUtils.getCredentialProvidersByOrder(
+ () -> filesetProperties, () -> schemaProperties, () ->
catalogProperties);
+ Assertions.assertEquals(credentialProviders, ImmutableSet.of("a", "b"));
+ }
+}
diff --git
a/core/src/test/resources/META-INF/services/org.apache.gravitino.credential.CredentialProvider
b/core/src/test/resources/META-INF/services/org.apache.gravitino.credential.CredentialProvider
index cbdbff0bee..6e1fdde4bd 100644
---
a/core/src/test/resources/META-INF/services/org.apache.gravitino.credential.CredentialProvider
+++
b/core/src/test/resources/META-INF/services/org.apache.gravitino.credential.CredentialProvider
@@ -16,4 +16,5 @@
# specific language governing permissions and limitations
# under the License.
#
-org.apache.gravitino.credential.DummyCredentialProvider
\ No newline at end of file
+org.apache.gravitino.credential.DummyCredentialProvider
+org.apache.gravitino.credential.Dummy2CredentialProvider
diff --git
a/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java
b/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java
index 16a2096f32..63e53aefd5 100644
--- a/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java
+++ b/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java
@@ -26,12 +26,12 @@ import javax.servlet.Servlet;
import org.apache.gravitino.Configs;
import org.apache.gravitino.GravitinoEnv;
import org.apache.gravitino.catalog.CatalogDispatcher;
-import org.apache.gravitino.catalog.CredentialManager;
import org.apache.gravitino.catalog.FilesetDispatcher;
import org.apache.gravitino.catalog.PartitionDispatcher;
import org.apache.gravitino.catalog.SchemaDispatcher;
import org.apache.gravitino.catalog.TableDispatcher;
import org.apache.gravitino.catalog.TopicDispatcher;
+import org.apache.gravitino.credential.CredentialOperationDispatcher;
import org.apache.gravitino.metalake.MetalakeDispatcher;
import org.apache.gravitino.metrics.MetricsSystem;
import org.apache.gravitino.metrics.source.MetricsSource;
@@ -115,7 +115,9 @@ public class GravitinoServer extends ResourceConfig {
bind(gravitinoEnv.filesetDispatcher()).to(FilesetDispatcher.class).ranked(1);
bind(gravitinoEnv.topicDispatcher()).to(TopicDispatcher.class).ranked(1);
bind(gravitinoEnv.tagDispatcher()).to(TagDispatcher.class).ranked(1);
-
bind(gravitinoEnv.credentialManager()).to(CredentialManager.class).ranked(1);
+ bind(gravitinoEnv.credentialOperationDispatcher())
+ .to(CredentialOperationDispatcher.class)
+ .ranked(1);
}
});
register(JsonProcessingExceptionMapper.class);
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectCredentialOperations.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectCredentialOperations.java
index 7c6ea4a8eb..1046bbba1a 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectCredentialOperations.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectCredentialOperations.java
@@ -21,11 +21,14 @@ package org.apache.gravitino.server.web.rest;
import com.codahale.metrics.annotation.ResponseMetered;
import com.codahale.metrics.annotation.Timed;
+import com.google.common.collect.ImmutableSet;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
+import javax.ws.rs.NotSupportedException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
@@ -34,8 +37,8 @@ import javax.ws.rs.core.Response;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.MetadataObjects;
import org.apache.gravitino.NameIdentifier;
-import org.apache.gravitino.catalog.CredentialManager;
import org.apache.gravitino.credential.Credential;
+import org.apache.gravitino.credential.CredentialOperationDispatcher;
import org.apache.gravitino.dto.credential.CredentialDTO;
import org.apache.gravitino.dto.responses.CredentialResponse;
import org.apache.gravitino.dto.util.DTOConverters;
@@ -51,15 +54,18 @@ public class MetadataObjectCredentialOperations {
private static final Logger LOG =
LoggerFactory.getLogger(MetadataObjectCredentialOperations.class);
- private CredentialManager credentialManager;
+ private static final Set<MetadataObject.Type>
supportsCredentialMetadataTypes =
+ ImmutableSet.of(MetadataObject.Type.CATALOG,
MetadataObject.Type.FILESET);
+
+ private CredentialOperationDispatcher credentialOperationDispatcher;
@SuppressWarnings("unused")
@Context
private HttpServletRequest httpRequest;
@Inject
- public MetadataObjectCredentialOperations(CredentialManager dispatcher) {
- this.credentialManager = dispatcher;
+ public MetadataObjectCredentialOperations(CredentialOperationDispatcher
dispatcher) {
+ this.credentialOperationDispatcher = dispatcher;
}
@GET
@@ -83,9 +89,13 @@ public class MetadataObjectCredentialOperations {
MetadataObject object =
MetadataObjects.parse(
fullName,
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
+ if (!supportsCredentialOperations(object)) {
+ throw new NotSupportedException(
+ "Doesn't support credential operations for metadata object
type");
+ }
NameIdentifier identifier =
MetadataObjectUtil.toEntityIdent(metalake, object);
- List<Credential> credentials =
credentialManager.getCredentials(identifier);
+ List<Credential> credentials =
credentialOperationDispatcher.getCredentials(identifier);
if (credentials == null) {
return Utils.ok(new CredentialResponse(new CredentialDTO[0]));
}
@@ -97,4 +107,8 @@ public class MetadataObjectCredentialOperations {
return ExceptionHandlers.handleCredentialException(OperationType.GET,
fullName, e);
}
}
+
+ private static boolean supportsCredentialOperations(MetadataObject
metadataObject) {
+ return supportsCredentialMetadataTypes.contains(metadataObject.type());
+ }
}
diff --git
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectCredentialOperations.java
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectCredentialOperations.java
index 1ac5d38135..464ccd8698 100644
---
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectCredentialOperations.java
+++
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectCredentialOperations.java
@@ -31,8 +31,8 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.MetadataObjects;
-import org.apache.gravitino.catalog.CredentialManager;
import org.apache.gravitino.credential.Credential;
+import org.apache.gravitino.credential.CredentialOperationDispatcher;
import org.apache.gravitino.credential.S3SecretKeyCredential;
import org.apache.gravitino.dto.responses.CredentialResponse;
import org.apache.gravitino.dto.responses.ErrorConstants;
@@ -59,7 +59,8 @@ public class TestMetadataObjectCredentialOperations extends
JerseyTest {
}
}
- private CredentialManager credentialManager = mock(CredentialManager.class);
+ private CredentialOperationDispatcher credentialOperationDispatcher =
+ mock(CredentialOperationDispatcher.class);
private String metalake = "test_metalake";
@@ -78,7 +79,7 @@ public class TestMetadataObjectCredentialOperations extends
JerseyTest {
new AbstractBinder() {
@Override
protected void configure() {
- bind(credentialManager).to(CredentialManager.class).ranked(2);
+
bind(credentialOperationDispatcher).to(CredentialOperationDispatcher.class).ranked(2);
bindFactory(MockServletRequestFactory.class).to(HttpServletRequest.class);
}
});
@@ -101,7 +102,7 @@ public class TestMetadataObjectCredentialOperations extends
JerseyTest {
S3SecretKeyCredential credential = new S3SecretKeyCredential("access-id",
"secret-key");
// Test return one credential
-
when(credentialManager.getCredentials(any())).thenReturn(Arrays.asList(credential));
+
when(credentialOperationDispatcher.getCredentials(any())).thenReturn(Arrays.asList(credential));
Response response =
target(basePath(metalake))
.path(metadataObject.type().toString())
@@ -123,7 +124,7 @@ public class TestMetadataObjectCredentialOperations extends
JerseyTest {
Assertions.assertEquals(0, credentialToTest.expireTimeInMs());
// Test doesn't return credential
- when(credentialManager.getCredentials(any())).thenReturn(null);
+ when(credentialOperationDispatcher.getCredentials(any())).thenReturn(null);
response =
target(basePath(metalake))
.path(metadataObject.type().toString())
@@ -140,7 +141,7 @@ public class TestMetadataObjectCredentialOperations extends
JerseyTest {
// Test throws NoSuchCredentialException
doThrow(new NoSuchCredentialException("mock error"))
- .when(credentialManager)
+ .when(credentialOperationDispatcher)
.getCredentials(any());
response =
target(basePath(metalake))