This is an automated email from the ASF dual-hosted git repository.
liuxun 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 8cda4d4a2 [#5648] improvement(iceberg): generate credential according
to the data path and metadata path (#5698)
8cda4d4a2 is described below
commit 8cda4d4a2d1b8a5b6e8fa1347e1f5c593d524e26
Author: JUN <[email protected]>
AuthorDate: Thu Nov 28 20:43:01 2024 +0800
[#5648] improvement(iceberg): generate credential according to the data
path and metadata path (#5698)
### What changes were proposed in this pull request?
This PR updates the credential generation process to also consider
`write.data.path` and `write.metadata.path`.
### Why are the changes needed?
To provide greater flexibility for users.
Fixes: #5648
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
- Used Spark to create a table via Gravitino Iceberg REST server to AWS
S3 and verified that `write.data.path` and `write.metadata.path` are
working as expected.
- Added unit tests to check if table properties include
`write.data.path` and `write.metadata.path`.

---
.../gravitino/credential/CredentialUtils.java | 4 +-
.../service/rest/IcebergTableOperations.java | 17 ++++-
.../iceberg/integration/test/IcebergRESTS3IT.java | 72 ++++++++++++++++++++++
.../integration/test/IcebergRESTServiceIT.java | 2 +-
4 files changed, 89 insertions(+), 6 deletions(-)
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 ad81953ac..09439d58a 100644
--- a/core/src/main/java/org/apache/gravitino/credential/CredentialUtils.java
+++ b/core/src/main/java/org/apache/gravitino/credential/CredentialUtils.java
@@ -23,10 +23,10 @@ import com.google.common.collect.ImmutableSet;
import org.apache.gravitino.utils.PrincipalUtils;
public class CredentialUtils {
- public static Credential vendCredential(CredentialProvider
credentialProvider, String path) {
+ public static Credential vendCredential(CredentialProvider
credentialProvider, String[] path) {
PathBasedCredentialContext pathBasedCredentialContext =
new PathBasedCredentialContext(
- PrincipalUtils.getCurrentUserName(), ImmutableSet.of(path),
ImmutableSet.of());
+ PrincipalUtils.getCurrentUserName(), ImmutableSet.copyOf(path),
ImmutableSet.of());
return credentialProvider.getCredential(pathBasedCredentialContext);
}
}
diff --git
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/rest/IcebergTableOperations.java
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/rest/IcebergTableOperations.java
index 67dcfce02..12f9c5055 100644
---
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/rest/IcebergTableOperations.java
+++
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/rest/IcebergTableOperations.java
@@ -24,6 +24,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import java.util.Map;
+import java.util.stream.Stream;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
@@ -54,6 +55,8 @@ import
org.apache.gravitino.iceberg.service.dispatcher.IcebergTableOperationDisp
import org.apache.gravitino.iceberg.service.metrics.IcebergMetricsManager;
import org.apache.gravitino.listener.api.event.IcebergRequestContext;
import org.apache.gravitino.metrics.MetricNames;
+import org.apache.iceberg.TableMetadata;
+import org.apache.iceberg.TableProperties;
import org.apache.iceberg.catalog.Namespace;
import org.apache.iceberg.catalog.TableIdentifier;
import org.apache.iceberg.exceptions.ServiceUnavailableException;
@@ -294,9 +297,17 @@ public class IcebergTableOperations {
+ CredentialConstants.CREDENTIAL_PROVIDER_TYPE
+ " to the catalog configurations");
}
- Credential credential =
- CredentialUtils.vendCredential(
- credentialProvider, loadTableResponse.tableMetadata().location());
+
+ TableMetadata tableMetadata = loadTableResponse.tableMetadata();
+ String[] path =
+ Stream.of(
+ tableMetadata.location(),
+ tableMetadata.property(TableProperties.WRITE_DATA_LOCATION,
""),
+
tableMetadata.property(TableProperties.WRITE_METADATA_LOCATION, ""))
+ .filter(StringUtils::isNotBlank)
+ .toArray(String[]::new);
+
+ Credential credential = CredentialUtils.vendCredential(credentialProvider,
path);
if (credential == null) {
throw new ServiceUnavailableException(
"Couldn't generate credential for %s",
credentialProvider.credentialType());
diff --git
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTS3IT.java
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTS3IT.java
index 7941177a7..ab372f78c 100644
---
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTS3IT.java
+++
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTS3IT.java
@@ -19,8 +19,10 @@
package org.apache.gravitino.iceberg.integration.test;
+import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergConstants;
@@ -30,9 +32,13 @@ import org.apache.gravitino.integration.test.util.BaseIT;
import org.apache.gravitino.integration.test.util.DownloaderUtils;
import org.apache.gravitino.integration.test.util.ITUtils;
import org.apache.gravitino.storage.S3Properties;
+import org.apache.iceberg.TableProperties;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.junit.platform.commons.util.StringUtils;
+@SuppressWarnings("FormatStringAnnotation")
@EnabledIfEnvironmentVariable(named = "GRAVITINO_TEST_CLOUD_IT", matches =
"true")
public class IcebergRESTS3IT extends IcebergRESTJdbcCatalogIT {
@@ -124,4 +130,70 @@ public class IcebergRESTS3IT extends
IcebergRESTJdbcCatalogIT {
String envValue = System.getenv(envVar);
return Optional.ofNullable(envValue).orElse(defaultValue);
}
+
+ /**
+ * Parses a string representing table properties into a map of key-value
pairs.
+ *
+ * @param tableProperties A string representing the table properties in the
format:
+ * "[key1=value1,key2=value2,...]"
+ * @return A Map where each key is a property name (String) and the
corresponding value is the
+ * property value (String). Example input:
+ * "[write.data.path=path/to/data,write.metadata.path=path/to/metadata]"
Example output: {
+ * "write.data.path" -> "path/to/data", "write.metadata.path" ->
"path/to/metadata" }
+ */
+ private Map<String, String> parseTableProperties(String tableProperties) {
+ Map<String, String> propertiesMap = new HashMap<>();
+ String[] pairs = tableProperties.substring(1, tableProperties.length() -
1).split(",");
+ for (String pair : pairs) {
+ String[] keyValue = pair.split("=", 2); // Split at most once
+ if (keyValue.length == 2) {
+ propertiesMap.put(keyValue[0].trim(), keyValue[1].trim());
+ }
+ }
+ return propertiesMap;
+ }
+
+ @Test
+ void testCredentialWithMultiLocations() {
+ String namespaceName = ICEBERG_REST_NS_PREFIX + "credential";
+ String tableName = namespaceName + ".multi_location";
+
+ String writeDataPath = this.s3Warehouse + "/test_data_location";
+ String writeMetaDataPath = this.s3Warehouse + "/test_metadata_location";
+
+ sql("CREATE DATABASE IF NOT EXISTS " + namespaceName);
+ sql(
+ String.format(
+ "CREATE TABLE %s (id bigint) USING iceberg OPTIONS ('%s' = '%s',
'%s' = '%s')",
+ tableName,
+ TableProperties.WRITE_DATA_LOCATION,
+ writeDataPath,
+ TableProperties.WRITE_METADATA_LOCATION,
+ writeMetaDataPath));
+
+ Map<String, String> tableDetails =
+ convertToStringMap(sql("DESCRIBE TABLE EXTENDED " + tableName));
+ String tableProperties = tableDetails.get("Table Properties");
+ Assertions.assertNotNull(tableProperties, "Table Properties should not be
null");
+
+ Map<String, String> propertiesMap = parseTableProperties(tableProperties);
+ Assertions.assertEquals(
+ writeDataPath,
+ propertiesMap.get(TableProperties.WRITE_DATA_LOCATION),
+ String.format(
+ "Expected write.data.path to be '%s', but was '%s'",
+ writeDataPath,
propertiesMap.get(TableProperties.WRITE_DATA_LOCATION)));
+ Assertions.assertEquals(
+ writeMetaDataPath,
+ propertiesMap.get(TableProperties.WRITE_METADATA_LOCATION),
+ String.format(
+ "Expected write.metadata.path to be '%s', but was '%s'",
+ writeMetaDataPath,
propertiesMap.get(TableProperties.WRITE_METADATA_LOCATION)));
+
+ String value1 = "1";
+ String value2 = "2";
+ sql(String.format("INSERT INTO %s VALUES (%s), (%s);", tableName, value1,
value2));
+ List<String> result = convertToStringList(sql(String.format("SELECT * FROM
%s", tableName)), 0);
+ Assertions.assertEquals(result, ImmutableList.of((value1), (value2)));
+ }
}
diff --git
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTServiceIT.java
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTServiceIT.java
index 9b4900f4d..f8bd016a9 100644
---
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTServiceIT.java
+++
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTServiceIT.java
@@ -45,7 +45,7 @@ import org.junit.jupiter.api.condition.EnabledIf;
@TestInstance(Lifecycle.PER_CLASS)
public abstract class IcebergRESTServiceIT extends IcebergRESTServiceBaseIT {
- private static final String ICEBERG_REST_NS_PREFIX = "iceberg_rest_";
+ protected static final String ICEBERG_REST_NS_PREFIX = "iceberg_rest_";
@BeforeAll
void prepareSQLContext() {