This is an automated email from the ASF dual-hosted git repository.
jshao pushed a commit to branch branch-lance-namepspace-dev
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-lance-namepspace-dev by
this push:
new fc0f6179d6 [#9009]improvement(lance-rest-server): add ITs for LRS
namespace operations (#9008)
fc0f6179d6 is described below
commit fc0f6179d62e3536c2964f2f2bd877fbe1c982b1
Author: mchades <[email protected]>
AuthorDate: Tue Nov 4 18:09:25 2025 +0800
[#9009]improvement(lance-rest-server): add ITs for LRS namespace operations
(#9008)
### What changes were proposed in this pull request?
add ITs for LRS namespace operations
### Why are the changes needed?
Fix: #9009
### Does this PR introduce _any_ user-facing change?
no
### How was this patch tested?
CI pass
---
.../integration/test/CatalogIcebergBaseIT.java | 2 +-
.../test/CatalogIcebergKerberosHiveIT.java | 2 +-
.../connector/integration/test/FlinkEnvIT.java | 2 +-
integration-test-common/build.gradle.kts | 3 +
.../gravitino/integration/test/MiniGravitino.java | 80 ++++-
.../integration/test/MiniGravitinoContext.java | 11 +-
.../gravitino/integration/test/util/BaseIT.java | 25 +-
.../gravitino/GravitinoLanceNamespaceWrapper.java | 19 +-
lance/lance-rest-server/build.gradle.kts | 17 +
.../lance/integration/test/LanceRESTServiceIT.java | 391 +++++++++++++++++++++
.../service/rest/ServletRequestFactoryBase.java | 21 +-
.../service/rest/TestLanceNamespaceOperations.java | 326 +++++++++++++++++
.../connector/integration/test/SparkEnvIT.java | 2 +-
13 files changed, 865 insertions(+), 36 deletions(-)
diff --git
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergBaseIT.java
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergBaseIT.java
index b0d4f3b5eb..82aedaff7d 100644
---
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergBaseIT.java
+++
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergBaseIT.java
@@ -124,7 +124,7 @@ public abstract class CatalogIcebergBaseIT extends BaseIT {
@BeforeAll
public void startup() throws Exception {
- super.ignoreAuxRestService = false;
+ super.ignoreIcebergAuxRestService = false;
super.startIntegrationTest();
containerSuite.startHiveContainer();
initIcebergCatalogProperties();
diff --git
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergKerberosHiveIT.java
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergKerberosHiveIT.java
index cce787ec85..5591a4f26f 100644
---
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergKerberosHiveIT.java
+++
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergKerberosHiveIT.java
@@ -127,7 +127,7 @@ public class CatalogIcebergKerberosHiveIT extends BaseIT {
// Config kerberos configuration for Gravitino server
addKerberosConfig();
- super.ignoreAuxRestService = false;
+ super.ignoreIcebergAuxRestService = false;
// Start Gravitino server
super.startIntegrationTest();
} catch (Exception e) {
diff --git
a/flink-connector/flink/src/test/java/org/apache/gravitino/flink/connector/integration/test/FlinkEnvIT.java
b/flink-connector/flink/src/test/java/org/apache/gravitino/flink/connector/integration/test/FlinkEnvIT.java
index ca79144f5b..db04715a4c 100644
---
a/flink-connector/flink/src/test/java/org/apache/gravitino/flink/connector/integration/test/FlinkEnvIT.java
+++
b/flink-connector/flink/src/test/java/org/apache/gravitino/flink/connector/integration/test/FlinkEnvIT.java
@@ -112,7 +112,7 @@ public abstract class FlinkEnvIT extends BaseIT {
protected abstract String getProvider();
private void initIcebergRestServiceEnv() {
- super.ignoreAuxRestService = false;
+ super.ignoreIcebergAuxRestService = false;
Map<String, String> icebergRestServiceConfigs = new HashMap<>();
icebergRestServiceConfigs.put(
"gravitino."
diff --git a/integration-test-common/build.gradle.kts
b/integration-test-common/build.gradle.kts
index bd15dc2a34..8aac2cafad 100644
--- a/integration-test-common/build.gradle.kts
+++ b/integration-test-common/build.gradle.kts
@@ -30,6 +30,9 @@ dependencies {
testImplementation(project(":clients:client-java"))
testImplementation(project(":common"))
testImplementation(project(":core"))
+ testImplementation(project(":lance:lance-common")) {
+ exclude("*")
+ }
testImplementation(project(":server"))
testImplementation(project(":server-common"))
testImplementation(libs.bundles.jetty)
diff --git
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitino.java
b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitino.java
index 3cff6940c6..eb26fb37e4 100644
---
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitino.java
+++
b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitino.java
@@ -19,6 +19,10 @@
package org.apache.gravitino.integration.test;
import static
com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly;
+import static
org.apache.gravitino.lance.common.config.LanceConfig.GRAVITINO_NAMESPACE_BACKEND;
+import static
org.apache.gravitino.lance.common.config.LanceConfig.LANCE_CONFIG_PREFIX;
+import static
org.apache.gravitino.lance.common.config.LanceConfig.NAMESPACE_BACKEND;
+import static
org.apache.gravitino.lance.common.config.LanceConfig.NAMESPACE_BACKEND_URI;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
@@ -27,6 +31,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -50,6 +55,7 @@ import org.apache.gravitino.rest.RESTUtils;
import org.apache.gravitino.server.GravitinoServer;
import org.apache.gravitino.server.ServerConfig;
import org.apache.gravitino.server.web.JettyServerConfig;
+import org.junit.platform.commons.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -78,11 +84,32 @@ public class MiniGravitino {
mockConfDir.mkdirs();
}
- private void removeAuxRestConfiguration(Properties properties) {
- // Disable Iceberg REST service
- properties.remove(
- AuxiliaryServiceManager.GRAVITINO_AUX_SERVICE_PREFIX
- + AuxiliaryServiceManager.AUX_SERVICE_NAMES);
+ private void removeAuxRestConfiguration(Properties properties, String
serviceToRemove) {
+ String value =
+ properties.getProperty(
+ AuxiliaryServiceManager.GRAVITINO_AUX_SERVICE_PREFIX
+ + AuxiliaryServiceManager.AUX_SERVICE_NAMES);
+ if (StringUtils.isNotBlank(value) &&
StringUtils.isNotBlank(serviceToRemove)) {
+ List<String> serviceNames = COMMA.splitToList(value);
+ List<String> updatedServiceNames = new ArrayList<>();
+ for (String serviceName : serviceNames) {
+ if (!serviceName.equalsIgnoreCase(serviceToRemove)) {
+ updatedServiceNames.add(serviceName);
+ }
+ }
+
+ String updatedValue = String.join(",", updatedServiceNames);
+ if (StringUtils.isBlank(updatedValue)) {
+ properties.remove(
+ AuxiliaryServiceManager.GRAVITINO_AUX_SERVICE_PREFIX
+ + AuxiliaryServiceManager.AUX_SERVICE_NAMES);
+ } else {
+ properties.setProperty(
+ AuxiliaryServiceManager.GRAVITINO_AUX_SERVICE_PREFIX
+ + AuxiliaryServiceManager.AUX_SERVICE_NAMES,
+ updatedValue);
+ }
+ }
}
public void start() throws Exception {
@@ -103,9 +130,18 @@ public class MiniGravitino {
serverConfig.loadPropertiesFromFile(
new File(ITUtils.joinPath(mockConfDir.getAbsolutePath(),
"gravitino.conf")));
- // Disable auxiliary rest service.
- if (context.ignoreAuxRestService) {
- removeAuxRestConfiguration(properties);
+ if (context.ignoreIcebergAuxRestService) {
+ // Disable auxiliary rest service.
+ removeAuxRestConfiguration(properties, "iceberg-rest");
+ LOG.info("Iceberg auxiliary REST service is disabled for
MiniGravitino.");
+ ITUtils.overwriteConfigFile(
+ ITUtils.joinPath(mockConfDir.getAbsolutePath(), "gravitino.conf"),
properties);
+ }
+
+ if (context.ignoreLanceAuxRestService) {
+ // Disable auxiliary rest service.
+ removeAuxRestConfiguration(properties, "lance-rest");
+ LOG.info("Lance auxiliary REST service is disabled for MiniGravitino.");
ITUtils.overwriteConfigFile(
ITUtils.joinPath(mockConfDir.getAbsolutePath(), "gravitino.conf"),
properties);
}
@@ -230,20 +266,38 @@ public class MiniGravitino {
return customConfigs;
}
- private Map<String, String> getLanceRestServiceConfigs() throws IOException {
+ private Map<String, String> getLanceRestServiceConfigs(Map<String, String>
configMap)
+ throws IOException {
+ if (context.ignoreLanceAuxRestService) {
+ return Collections.emptyMap();
+ }
+
Map<String, String> customConfigs = new HashMap<>();
String lanceJarPath = Paths.get("lance", "lance-rest-server", "build",
"libs").toString();
String lanceConfigPath =
Paths.get("lance", "lance-rest-server", "src", "main",
"resources").toString();
customConfigs.put(
- "gravitino.lance-rest." +
AuxiliaryServiceManager.AUX_SERVICE_CLASSPATH,
+ LANCE_CONFIG_PREFIX + AuxiliaryServiceManager.AUX_SERVICE_CLASSPATH,
String.join(",", lanceJarPath, lanceConfigPath));
customConfigs.put(
- "gravitino.lance-rest." +
JettyServerConfig.WEBSERVER_HTTP_PORT.getKey(),
+ LANCE_CONFIG_PREFIX + JettyServerConfig.WEBSERVER_HTTP_PORT.getKey(),
String.valueOf(RESTUtils.findAvailablePort(4000, 5000)));
- return customConfigs;
+
+ if (GRAVITINO_NAMESPACE_BACKEND.equals(
+ configMap.getOrDefault(NAMESPACE_BACKEND.getKey(),
NAMESPACE_BACKEND.getDefaultValue()))) {
+ // Set the Lance REST service to use the Gravitino server URI
+ String gravitinoUri =
+ String.format(
+ "http://%s:%s",
+ "localhost",
+ configMap.get(
+ GravitinoServer.WEBSERVER_CONF_PREFIX
+ + JettyServerConfig.WEBSERVER_HTTP_PORT.getKey()));
+ customConfigs.put(LANCE_CONFIG_PREFIX + NAMESPACE_BACKEND_URI.getKey(),
gravitinoUri);
+ }
+ return ImmutableMap.copyOf(customConfigs);
}
// Customize the config file
@@ -255,7 +309,7 @@ public class MiniGravitino {
String.valueOf(RESTUtils.findAvailablePort(2000, 3000)));
configMap.putAll(getIcebergRestServiceConfigs());
- configMap.putAll(getLanceRestServiceConfigs());
+ configMap.putAll(getLanceRestServiceConfigs(configMap));
configMap.putAll(context.customConfig);
ITUtils.rewriteConfigFile(configTempFileName, configFileName, configMap);
diff --git
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitinoContext.java
b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitinoContext.java
index 601138ecfd..cf02bb3f3c 100644
---
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitinoContext.java
+++
b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitinoContext.java
@@ -23,10 +23,15 @@ import java.util.Map;
public class MiniGravitinoContext {
Map<String, String> customConfig;
- final boolean ignoreAuxRestService;
+ final boolean ignoreIcebergAuxRestService;
+ final boolean ignoreLanceAuxRestService;
- public MiniGravitinoContext(Map<String, String> customConfig, boolean
ignoreAuxRestService) {
+ public MiniGravitinoContext(
+ Map<String, String> customConfig,
+ boolean ignoreIcebergAuxRestService,
+ boolean ignoreLanceAuxRestService) {
this.customConfig = customConfig;
- this.ignoreAuxRestService = ignoreAuxRestService;
+ this.ignoreIcebergAuxRestService = ignoreIcebergAuxRestService;
+ this.ignoreLanceAuxRestService = ignoreLanceAuxRestService;
}
}
diff --git
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/util/BaseIT.java
b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/util/BaseIT.java
index 1ae60a0fec..9497d5bd23 100644
---
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/util/BaseIT.java
+++
b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/util/BaseIT.java
@@ -21,6 +21,8 @@ package org.apache.gravitino.integration.test.util;
import static org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_PATH;
import static
org.apache.gravitino.integration.test.util.TestDatabaseName.PG_CATALOG_POSTGRESQL_IT;
import static
org.apache.gravitino.integration.test.util.TestDatabaseName.PG_JDBC_BACKEND;
+import static
org.apache.gravitino.lance.common.config.LanceConfig.LANCE_CONFIG_PREFIX;
+import static
org.apache.gravitino.lance.common.config.LanceConfig.METALAKE_NAME;
import static
org.apache.gravitino.server.GravitinoServer.WEBSERVER_CONF_PREFIX;
import com.google.common.base.Splitter;
@@ -102,7 +104,9 @@ public class BaseIT {
protected Map<String, String> customConfigs = new HashMap<>();
- protected boolean ignoreAuxRestService = true;
+ protected boolean ignoreIcebergAuxRestService = true;
+
+ protected boolean ignoreLanceAuxRestService = true;
public String DOWNLOAD_MYSQL_JDBC_DRIVER_URL =
"https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.26/mysql-connector-java-8.0.26.jar";
@@ -133,6 +137,16 @@ public class BaseIT {
customConfigs.putAll(configs);
}
+ protected int getLanceRESTServerPort() {
+ JettyServerConfig lanceServerConfig =
+ JettyServerConfig.fromConfig(serverConfig, LANCE_CONFIG_PREFIX);
+ return lanceServerConfig.getHttpPort();
+ }
+
+ protected String getLanceRESTServerMetalakeName() {
+ return serverConfig.getRawString(LANCE_CONFIG_PREFIX +
METALAKE_NAME.getKey());
+ }
+
private void rewriteGravitinoServerConfig() throws IOException {
String gravitinoHome = System.getenv("GRAVITINO_HOME");
Path configPath = Paths.get(gravitinoHome, "conf",
GravitinoServer.CONF_FILE);
@@ -329,8 +343,15 @@ public class BaseIT {
serverConfig = new ServerConfig();
customConfigs.put(ENTITY_RELATIONAL_JDBC_BACKEND_PATH.getKey(),
file.getAbsolutePath());
+ if (!ignoreLanceAuxRestService) {
+ customConfigs.put(
+ LANCE_CONFIG_PREFIX + METALAKE_NAME.getKey(),
+ GravitinoITUtils.genRandomName("LanceRESTService_metalake"));
+ }
if (testMode != null && testMode.equals(ITUtils.EMBEDDED_TEST_MODE)) {
- MiniGravitinoContext context = new MiniGravitinoContext(customConfigs,
ignoreAuxRestService);
+ MiniGravitinoContext context =
+ new MiniGravitinoContext(
+ customConfigs, ignoreIcebergAuxRestService,
ignoreLanceAuxRestService);
miniGravitino = new MiniGravitino(context);
miniGravitino.start();
serverConfig = miniGravitino.getServerConfig();
diff --git
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceNamespaceWrapper.java
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceNamespaceWrapper.java
index 865313b31c..dd2c4629b4 100644
---
a/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceNamespaceWrapper.java
+++
b/lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceNamespaceWrapper.java
@@ -74,6 +74,7 @@ import org.apache.gravitino.Schema;
import org.apache.gravitino.SchemaChange;
import org.apache.gravitino.client.GravitinoClient;
import org.apache.gravitino.exceptions.CatalogAlreadyExistsException;
+import org.apache.gravitino.exceptions.CatalogInUseException;
import org.apache.gravitino.exceptions.NoSuchCatalogException;
import org.apache.gravitino.exceptions.NoSuchSchemaException;
import org.apache.gravitino.exceptions.NonEmptyCatalogException;
@@ -332,7 +333,8 @@ public class GravitinoLanceNamespaceWrapper extends
NamespaceWrapper
// Catalog exists, handle based on mode
switch (mode) {
case EXIST_OK:
- response.setProperties(Maps.newHashMap());
+ response.setProperties(
+
Optional.ofNullable(catalog.properties()).orElse(Collections.emptyMap()));
return response;
case CREATE:
throw LanceNamespaceException.conflict(
@@ -344,7 +346,7 @@ public class GravitinoLanceNamespaceWrapper extends
NamespaceWrapper
CatalogChange[] changes =
buildChanges(
properties,
- catalog.properties(),
+ removeInUseProperty(catalog.properties()),
CatalogChange::setProperty,
CatalogChange::removeProperty,
CatalogChange[]::new);
@@ -356,6 +358,12 @@ public class GravitinoLanceNamespaceWrapper extends
NamespaceWrapper
}
}
+ private Map<String, String> removeInUseProperty(Map<String, String>
properties) {
+ return properties.entrySet().stream()
+ .filter(e -> !e.getKey().equalsIgnoreCase(Catalog.PROPERTY_IN_USE))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+
private CreateNamespaceResponse createOrUpdateSchema(
String catalogName,
String schemaName,
@@ -378,7 +386,8 @@ public class GravitinoLanceNamespaceWrapper extends
NamespaceWrapper
// Schema exists, handle based on mode
switch (mode) {
case EXIST_OK:
- response.setProperties(Maps.newHashMap());
+ response.setProperties(
+
Optional.ofNullable(schema.properties()).orElse(Collections.emptyMap()));
return response;
case CREATE:
throw LanceNamespaceException.conflict(
@@ -422,9 +431,9 @@ public class GravitinoLanceNamespaceWrapper extends
NamespaceWrapper
}
return new DropNamespaceResponse(); // SKIP mode
}
- } catch (NonEmptyCatalogException e) {
+ } catch (NonEmptyCatalogException | CatalogInUseException e) {
throw LanceNamespaceException.badRequest(
- String.format("Catalog %s is not empty.", catalogName),
+ String.format("Catalog %s is not empty or in used", catalogName),
NonEmptyCatalogException.class.getSimpleName(),
catalogName,
CommonUtil.formatCurrentStackTrace());
diff --git a/lance/lance-rest-server/build.gradle.kts
b/lance/lance-rest-server/build.gradle.kts
index 7befc28b35..b3e06f6707 100644
--- a/lance/lance-rest-server/build.gradle.kts
+++ b/lance/lance-rest-server/build.gradle.kts
@@ -53,7 +53,24 @@ dependencies {
implementation(libs.jackson.datatype.jdk8)
implementation(libs.jackson.datatype.jsr310)
+ testImplementation(project(":clients:client-java"))
+ testImplementation(project(":server"))
+ testImplementation(project(":integration-test-common", "testArtifacts"))
+
+ testImplementation(libs.commons.io)
+ testImplementation(libs.jersey.test.framework.core) {
+ exclude(group = "org.junit.jupiter")
+ }
+ testImplementation(libs.jersey.test.framework.provider.jetty) {
+ exclude(group = "org.junit.jupiter")
+ }
+
testImplementation(libs.junit.jupiter.api)
+ testImplementation(libs.mockito.inline)
+ testImplementation(libs.mysql.driver)
+ testImplementation(libs.postgresql.driver)
+ testImplementation(libs.testcontainers)
+
testRuntimeOnly(libs.junit.jupiter.engine)
}
diff --git
a/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/integration/test/LanceRESTServiceIT.java
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/integration/test/LanceRESTServiceIT.java
new file mode 100644
index 0000000000..f1fe008782
--- /dev/null
+++
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/integration/test/LanceRESTServiceIT.java
@@ -0,0 +1,391 @@
+/*
+ * 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.lance.integration.test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.lancedb.lance.namespace.LanceNamespace;
+import com.lancedb.lance.namespace.LanceNamespaceException;
+import com.lancedb.lance.namespace.LanceNamespaces;
+import com.lancedb.lance.namespace.model.CreateNamespaceRequest;
+import com.lancedb.lance.namespace.model.CreateNamespaceResponse;
+import com.lancedb.lance.namespace.model.DescribeNamespaceRequest;
+import com.lancedb.lance.namespace.model.DescribeNamespaceResponse;
+import com.lancedb.lance.namespace.model.DropNamespaceRequest;
+import com.lancedb.lance.namespace.model.DropNamespaceResponse;
+import com.lancedb.lance.namespace.model.ListNamespacesRequest;
+import com.lancedb.lance.namespace.model.ListNamespacesResponse;
+import com.lancedb.lance.namespace.model.NamespaceExistsRequest;
+import com.lancedb.lance.namespace.rest.RestNamespaceConfig;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Schema;
+import org.apache.gravitino.client.GravitinoMetalake;
+import org.apache.gravitino.integration.test.util.BaseIT;
+import org.apache.gravitino.integration.test.util.GravitinoITUtils;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class LanceRESTServiceIT extends BaseIT {
+
+ private GravitinoMetalake metalake;
+ private Map<String, String> properties =
+ new HashMap<>() {
+ {
+ put("key1", "value1");
+ }
+ };
+ private final BufferAllocator allocator = new RootAllocator(Long.MAX_VALUE);
+ private LanceNamespace ns;
+ private Path tempDir;
+
+ @BeforeAll
+ public void startIntegrationTest() throws Exception {
+ super.ignoreLanceAuxRestService = false;
+ super.startIntegrationTest();
+ this.metalake = createMetalake(getLanceRESTServerMetalakeName());
+
+ HashMap<String, String> props = Maps.newHashMap();
+ props.put(RestNamespaceConfig.URI, getLanceRestServiceUrl());
+ props.put(RestNamespaceConfig.DELIMITER,
RestNamespaceConfig.DELIMITER_DEFAULT);
+ this.ns = LanceNamespaces.connect("rest", props, null, allocator);
+
+ this.tempDir = Files.createTempDirectory("test_lance_rest_service_it_");
+ }
+
+ @AfterAll
+ public void clean() {
+ client.dropMetalake(getLanceRESTServerMetalakeName(), true);
+ tempDir.toFile().deleteOnExit();
+ }
+
+ @AfterEach
+ public void clearMetalake() {
+ Arrays.stream(metalake.listCatalogs()).forEach(c ->
metalake.dropCatalog(c, true));
+ }
+
+ @Test
+ public void testListNamespaces() {
+ Catalog catalog1 =
createCatalog(GravitinoITUtils.genRandomName("lance_catalog_1"));
+ Catalog catalog2 =
createCatalog(GravitinoITUtils.genRandomName("lance_catalog_2"));
+ Schema schema1 =
+ catalog1
+ .asSchemas()
+ .createSchema("lance_schema_1", "schema for lance rest service
tests", null);
+
+ // test list catalogs via lance rest namespace client
+ ListNamespacesRequest listNamespacesReq = new ListNamespacesRequest();
+ ListNamespacesResponse listNamespacesResp =
ns.listNamespaces(listNamespacesReq);
+
+ Assertions.assertEquals(
+ Sets.newHashSet(catalog1.name(), catalog2.name()),
listNamespacesResp.getNamespaces());
+
+ // test list schemas via lance rest namespace client
+ listNamespacesReq.addIdItem(catalog1.name());
+ listNamespacesResp = ns.listNamespaces(listNamespacesReq);
+
+ Assertions.assertEquals(Sets.newHashSet(schema1.name()),
listNamespacesResp.getNamespaces());
+ }
+
+ @Test
+ public void testDescribeNamespace() {
+ Catalog catalog =
createCatalog(GravitinoITUtils.genRandomName("lance_catalog"));
+ Map<String, String> schemaProps =
+ new HashMap<>() {
+ {
+ put("schema_key1", "schema_value1");
+ }
+ };
+ Schema schema = catalog.asSchemas().createSchema("lance_schema", null,
schemaProps);
+
+ // test describe catalog via lance rest namespace client
+ DescribeNamespaceRequest describeNamespaceReq = new
DescribeNamespaceRequest();
+ describeNamespaceReq.addIdItem(catalog.name());
+ DescribeNamespaceResponse describeNamespaceResp =
ns.describeNamespace(describeNamespaceReq);
+
+ Assertions.assertEquals(catalog.properties(),
describeNamespaceResp.getProperties());
+
+ // test describe schema via lance rest namespace client
+ describeNamespaceReq.addIdItem(schema.name());
+ describeNamespaceResp = ns.describeNamespace(describeNamespaceReq);
+
+ Assertions.assertEquals(schema.properties(),
describeNamespaceResp.getProperties());
+
+ // test describe the root namespace
+ DescribeNamespaceRequest rootDescNamespaceReq = new
DescribeNamespaceRequest();
+ LanceNamespaceException exception =
+ Assertions.assertThrows(
+ LanceNamespaceException.class, () ->
ns.describeNamespace(rootDescNamespaceReq));
+
+ Assertions.assertEquals(400, exception.getCode());
+ Assertions.assertTrue(exception.getErrorResponse().isPresent());
+ Assertions.assertTrue(
+ exception
+ .getErrorResponse()
+ .get()
+ .getError()
+ .contains("Expected at most 2-level and at least 1-level
namespace"));
+ Assertions.assertEquals(
+ IllegalArgumentException.class.getSimpleName(),
+ exception.getErrorResponse().get().getType());
+
+ // test describe a non-existent catalog namespace
+ DescribeNamespaceRequest nonExistentCatalogReq = new
DescribeNamespaceRequest();
+ nonExistentCatalogReq.addIdItem("non_existent_catalog");
+ exception =
+ Assertions.assertThrows(
+ LanceNamespaceException.class, () ->
ns.describeNamespace(nonExistentCatalogReq));
+ Assertions.assertEquals(404, exception.getCode());
+
+ // test describe a non-existent schema namespace
+ DescribeNamespaceRequest nonExistentSchemaReq = new
DescribeNamespaceRequest();
+ nonExistentSchemaReq.addIdItem(catalog.name());
+ nonExistentSchemaReq.addIdItem("non_existent_schema");
+ exception =
+ Assertions.assertThrows(
+ LanceNamespaceException.class, () ->
ns.describeNamespace(nonExistentSchemaReq));
+ Assertions.assertEquals(404, exception.getCode());
+ }
+
+ @Test
+ public void testCreateNamespace() {
+ String catalogName = GravitinoITUtils.genRandomName("lance_catalog");
+ Map<String, String> catalogProps =
+ new HashMap<>() {
+ {
+ put("catalog_key1", "catalog_value1");
+ }
+ };
+
+ // test create catalog via lance rest namespace client
+ CreateNamespaceRequest createNamespaceReq = new CreateNamespaceRequest();
+ createNamespaceReq.addIdItem(catalogName);
+ createNamespaceReq.setProperties(catalogProps);
+ CreateNamespaceResponse createNamespaceResp =
ns.createNamespace(createNamespaceReq);
+
+ Catalog catalog = metalake.loadCatalog(catalogName);
+ Assertions.assertEquals(catalog.properties(),
createNamespaceResp.getProperties());
+
+ // create catalog again with default mode (create) should fail
+ LanceNamespaceException exception =
+ Assertions.assertThrows(
+ LanceNamespaceException.class, () ->
ns.createNamespace(createNamespaceReq));
+ Assertions.assertEquals(409, exception.getCode());
+
+ // create catalog again with exist_ok mode should succeed
+ createNamespaceReq.setMode(CreateNamespaceRequest.ModeEnum.EXIST_OK);
+ createNamespaceResp = ns.createNamespace(createNamespaceReq);
+ Assertions.assertEquals(catalog.properties(),
createNamespaceResp.getProperties());
+
+ // create catalog again with overwrite mode should succeed and update
properties
+ Map<String, String> newProps =
+ new HashMap<>(catalogProps) {
+ {
+ put("catalog_key2", "catalog_value2");
+ }
+ };
+ createNamespaceReq.setMode(CreateNamespaceRequest.ModeEnum.OVERWRITE);
+ createNamespaceReq.setProperties(newProps);
+ createNamespaceResp = ns.createNamespace(createNamespaceReq);
+
+ catalog = metalake.loadCatalog(catalogName);
+ Assertions.assertEquals(catalog.properties(),
createNamespaceResp.getProperties());
+ Assertions.assertEquals(catalog.properties(),
createNamespaceResp.getProperties());
+
+ // test create schema via lance rest namespace client
+ CreateNamespaceRequest createSchemaReq = new CreateNamespaceRequest();
+ String schemaName = "lance_schema";
+ Map<String, String> schemaProps =
+ new HashMap<>() {
+ {
+ put("schema_key1", "schema_value1");
+ }
+ };
+ createSchemaReq.addIdItem(catalogName);
+ createSchemaReq.addIdItem(schemaName);
+ createSchemaReq.setProperties(schemaProps);
+ createNamespaceResp = ns.createNamespace(createSchemaReq);
+
+ Schema schema = catalog.asSchemas().loadSchema(schemaName);
+ Assertions.assertEquals(schema.properties(),
createNamespaceResp.getProperties());
+
+ // create schema again with default mode (create) should fail
+ exception =
+ Assertions.assertThrows(
+ LanceNamespaceException.class, () ->
ns.createNamespace(createSchemaReq));
+ Assertions.assertEquals(409, exception.getCode());
+
+ // create schema again with exist_ok mode should succeed
+ createSchemaReq.setMode(CreateNamespaceRequest.ModeEnum.EXIST_OK);
+ createNamespaceResp = ns.createNamespace(createSchemaReq);
+ Assertions.assertEquals(schema.properties(),
createNamespaceResp.getProperties());
+
+ // create schema again with overwrite mode should succeed and update
properties
+ Map<String, String> newSchemaProps =
+ new HashMap<>(schemaProps) {
+ {
+ put("schema_key2", "schema_value2");
+ }
+ };
+ createSchemaReq.setMode(CreateNamespaceRequest.ModeEnum.OVERWRITE);
+ createSchemaReq.setProperties(newSchemaProps);
+ createNamespaceResp = ns.createNamespace(createSchemaReq);
+
+ schema = catalog.asSchemas().loadSchema(schemaName);
+ Assertions.assertEquals(schema.properties(),
createNamespaceResp.getProperties());
+ }
+
+ @Test
+ public void testDropNamespace() {
+ Catalog catalog =
createCatalog(GravitinoITUtils.genRandomName("lance_catalog"));
+ Schema schema = catalog.asSchemas().createSchema("lance_schema", null,
null);
+
+ // test drop a non-existent namespace (catalog) with default mode (FAIL)
should fail
+ DropNamespaceRequest dropNamespaceReq = new DropNamespaceRequest();
+ dropNamespaceReq.addIdItem("non_existent_catalog");
+ LanceNamespaceException exception =
+ Assertions.assertThrows(
+ LanceNamespaceException.class, () ->
ns.dropNamespace(dropNamespaceReq));
+ Assertions.assertEquals(404, exception.getCode());
+
+ // test drop a non-existent namespace (catalog) with SKIP mode should
succeed
+ dropNamespaceReq.setMode(DropNamespaceRequest.ModeEnum.SKIP);
+ DropNamespaceResponse dropNamespaceResp =
ns.dropNamespace(dropNamespaceReq);
+ Assertions.assertTrue(dropNamespaceResp.getTransactionId().isEmpty());
+
+ // test drop a non-existent namespace (schema) with default mode (FAIL)
should fail
+ DropNamespaceRequest dropSchemaReq = new DropNamespaceRequest();
+ dropSchemaReq.addIdItem(catalog.name());
+ dropSchemaReq.addIdItem("non_existent_schema");
+ exception =
+ Assertions.assertThrows(
+ LanceNamespaceException.class, () ->
ns.dropNamespace(dropSchemaReq));
+ Assertions.assertEquals(404, exception.getCode());
+
+ // test drop a non-existent namespace (schema) with SKIP mode should
succeed
+ dropSchemaReq.setMode(DropNamespaceRequest.ModeEnum.SKIP);
+ dropNamespaceResp = ns.dropNamespace(dropSchemaReq);
+ Assertions.assertTrue(dropNamespaceResp.getTransactionId().isEmpty());
+
+ // test drop a non-empty namespace (catalog) with default behavior
(RESTRICT) should fail
+ DropNamespaceRequest dropNonEmptyCatalogReq = new DropNamespaceRequest();
+ dropNonEmptyCatalogReq.addIdItem(catalog.name());
+ exception =
+ Assertions.assertThrows(
+ LanceNamespaceException.class, () ->
ns.dropNamespace(dropNonEmptyCatalogReq));
+ Assertions.assertEquals(400, exception.getCode());
+
+ // test drop a non-empty namespace (catalog) with CASCADE behavior should
succeed
+
dropNonEmptyCatalogReq.setBehavior(DropNamespaceRequest.BehaviorEnum.CASCADE);
+ dropNamespaceResp = ns.dropNamespace(dropNonEmptyCatalogReq);
+ Assertions.assertTrue(dropNamespaceResp.getTransactionId().isEmpty());
+ Assertions.assertFalse(metalake.catalogExists(catalog.name()));
+
+ // recreate catalog, schema, and table for next test
+ catalog = createCatalog(catalog.name());
+ schema = catalog.asSchemas().createSchema(schema.name(), null, null);
+ String tableName = GravitinoITUtils.genRandomName("test_lance_table");
+ String tableLocation =
+ Path.of(tempDir.toString(), catalog.name(), schema.name(),
tableName).toString();
+ catalog
+ .asTableCatalog()
+ .createTable(
+ NameIdentifier.of(schema.name(), tableName),
+ null,
+ null,
+ ImmutableMap.of("location", tableLocation, "format", "lance"));
+ // test drop a non-empty namespace (schema) with default behavior
(RESTRICT) should fail
+ DropNamespaceRequest dropNonEmptySchemaReq = new DropNamespaceRequest();
+ dropNonEmptySchemaReq.addIdItem(catalog.name());
+ dropNonEmptySchemaReq.addIdItem(schema.name());
+ exception =
+ Assertions.assertThrows(
+ LanceNamespaceException.class, () ->
ns.dropNamespace(dropNonEmptySchemaReq));
+ Assertions.assertEquals(400, exception.getCode());
+ Assertions.assertTrue(catalog.asSchemas().schemaExists(schema.name()));
+
+ // test drop a non-empty namespace (schema) with CASCADE behavior should
succeed
+
dropNonEmptySchemaReq.setBehavior(DropNamespaceRequest.BehaviorEnum.CASCADE);
+ dropNamespaceResp = ns.dropNamespace(dropNonEmptySchemaReq);
+ Assertions.assertTrue(dropNamespaceResp.getTransactionId().isEmpty());
+ Assertions.assertFalse(catalog.asSchemas().schemaExists(schema.name()));
+ }
+
+ @Test
+ public void testNamespaceExists() {
+ Catalog catalog =
createCatalog(GravitinoITUtils.genRandomName("lance_catalog"));
+ Schema schema = catalog.asSchemas().createSchema("lance_schema", null,
null);
+
+ // test existing catalog
+ NamespaceExistsRequest catalogExistsReq = new NamespaceExistsRequest();
+ catalogExistsReq.addIdItem(catalog.name());
+ Assertions.assertDoesNotThrow(() -> ns.namespaceExists(catalogExistsReq));
+
+ // test non-existing catalog
+ NamespaceExistsRequest nonExistentCatalogReq = new
NamespaceExistsRequest();
+ nonExistentCatalogReq.addIdItem("non_existent_catalog");
+ LanceNamespaceException exception =
+ Assertions.assertThrows(
+ LanceNamespaceException.class, () ->
ns.namespaceExists(nonExistentCatalogReq));
+ Assertions.assertEquals(404, exception.getCode());
+
+ // test existing schema
+ NamespaceExistsRequest schemaExistsReq = new NamespaceExistsRequest();
+ schemaExistsReq.addIdItem(catalog.name());
+ schemaExistsReq.addIdItem(schema.name());
+ Assertions.assertDoesNotThrow(() -> ns.namespaceExists(schemaExistsReq));
+
+ // test non-existing schema
+ NamespaceExistsRequest nonExistentSchemaReq = new NamespaceExistsRequest();
+ nonExistentSchemaReq.addIdItem(catalog.name());
+ nonExistentSchemaReq.addIdItem("non_existent_schema");
+ exception =
+ Assertions.assertThrows(
+ LanceNamespaceException.class, () ->
ns.namespaceExists(nonExistentSchemaReq));
+ Assertions.assertEquals(404, exception.getCode());
+ }
+
+ private GravitinoMetalake createMetalake(String metalakeName) {
+ return client.createMetalake(metalakeName, "metalake for lance rest
service tests", null);
+ }
+
+ private Catalog createCatalog(String catalogName) {
+ return metalake.createCatalog(
+ catalogName,
+ Catalog.Type.RELATIONAL,
+ "generic-lakehouse",
+ "catalog for lance rest service tests",
+ properties);
+ }
+
+ private String getLanceRestServiceUrl() {
+ return String.format("http://%s:%d/lance", "localhost",
getLanceRESTServerPort());
+ }
+}
diff --git
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitinoContext.java
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/ServletRequestFactoryBase.java
similarity index 65%
copy from
integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitinoContext.java
copy to
lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/ServletRequestFactoryBase.java
index 601138ecfd..68b557955c 100644
---
a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/MiniGravitinoContext.java
+++
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/ServletRequestFactoryBase.java
@@ -16,17 +16,20 @@
* specific language governing permissions and limitations
* under the License.
*/
+package org.apache.gravitino.lance.service.rest;
-package org.apache.gravitino.integration.test;
+import java.util.function.Supplier;
+import javax.servlet.http.HttpServletRequest;
+import org.glassfish.hk2.api.Factory;
-import java.util.Map;
+abstract class ServletRequestFactoryBase
+ implements Factory<HttpServletRequest>, Supplier<HttpServletRequest> {
-public class MiniGravitinoContext {
- Map<String, String> customConfig;
- final boolean ignoreAuxRestService;
-
- public MiniGravitinoContext(Map<String, String> customConfig, boolean
ignoreAuxRestService) {
- this.customConfig = customConfig;
- this.ignoreAuxRestService = ignoreAuxRestService;
+ @Override
+ public HttpServletRequest provide() {
+ return get();
}
+
+ @Override
+ public void dispose(HttpServletRequest instance) {}
}
diff --git
a/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/TestLanceNamespaceOperations.java
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/TestLanceNamespaceOperations.java
new file mode 100644
index 0000000000..0ba8bf79b7
--- /dev/null
+++
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/service/rest/TestLanceNamespaceOperations.java
@@ -0,0 +1,326 @@
+/*
+ * 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.lance.service.rest;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+import com.lancedb.lance.namespace.model.CreateNamespaceRequest;
+import com.lancedb.lance.namespace.model.CreateNamespaceResponse;
+import com.lancedb.lance.namespace.model.DescribeNamespaceResponse;
+import com.lancedb.lance.namespace.model.DropNamespaceRequest;
+import com.lancedb.lance.namespace.model.DropNamespaceResponse;
+import com.lancedb.lance.namespace.model.ErrorResponse;
+import com.lancedb.lance.namespace.model.ListNamespacesResponse;
+import java.io.IOException;
+import java.util.regex.Pattern;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.lance.common.ops.NamespaceWrapper;
+import org.apache.gravitino.rest.RESTUtils;
+import org.glassfish.jersey.internal.inject.AbstractBinder;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+public class TestLanceNamespaceOperations extends JerseyTest {
+ private static class MockServletRequestFactory extends
ServletRequestFactoryBase {
+ @Override
+ public HttpServletRequest get() {
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ when(request.getRemoteUser()).thenReturn(null);
+ return request;
+ }
+ }
+
+ private static NamespaceWrapper namespaceWrapper =
mock(NamespaceWrapper.class);
+ private static
org.apache.gravitino.lance.common.ops.LanceNamespaceOperations namespaceOps =
+
mock(org.apache.gravitino.lance.common.ops.LanceNamespaceOperations.class);
+
+ @Override
+ protected Application configure() {
+ try {
+ forceSet(
+ TestProperties.CONTAINER_PORT,
String.valueOf(RESTUtils.findAvailablePort(2000, 3000)));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ ResourceConfig resourceConfig = new ResourceConfig();
+ resourceConfig.register(LanceNamespaceOperations.class);
+ resourceConfig.register(
+ new AbstractBinder() {
+ @Override
+ protected void configure() {
+ bind(namespaceWrapper).to(NamespaceWrapper.class).ranked(2);
+
bindFactory(MockServletRequestFactory.class).to(HttpServletRequest.class);
+ }
+ });
+
+ return resourceConfig;
+ }
+
+ @BeforeAll
+ public static void setup() {
+ when(namespaceWrapper.asNamespaceOps()).thenReturn(namespaceOps);
+ }
+
+ @Test
+ public void testListNamespaces() {
+ String namespaceId = "ns1.ns2";
+ String delimiter = ".";
+ ListNamespacesResponse listNamespacesResp = new ListNamespacesResponse();
+
listNamespacesResp.setNamespaces(Sets.newHashSet(namespaceId.split(delimiter)));
+
+ when(namespaceOps.listNamespaces(any(), any(), any(),
any())).thenReturn(listNamespacesResp);
+
+ Response resp =
+ target("/v1/namespace/ns1.ns2/list")
+ .queryParam("delimiter", delimiter)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .get();
+
+ Mockito.verify(namespaceOps)
+ .listNamespaces(eq(namespaceId), eq(Pattern.quote(delimiter)), any(),
any());
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp.getMediaType());
+
+ ListNamespacesResponse respEntity =
resp.readEntity(ListNamespacesResponse.class);
+ Assertions.assertEquals(listNamespacesResp.getNamespaces(),
respEntity.getNamespaces());
+ Assertions.assertEquals(listNamespacesResp.getPageToken(),
respEntity.getPageToken());
+
+ // list namespaces under root
+ resp =
+ target("/v1/namespace/./list")
+ .queryParam("delimiter", delimiter)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .get();
+
+ Mockito.verify(namespaceOps)
+ .listNamespaces(eq("."), eq(Pattern.quote(delimiter)), any(), any());
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp.getMediaType());
+ respEntity = resp.readEntity(ListNamespacesResponse.class);
+ Assertions.assertEquals(listNamespacesResp.getNamespaces(),
respEntity.getNamespaces());
+ Assertions.assertEquals(listNamespacesResp.getPageToken(),
respEntity.getPageToken());
+
+ // test throw exception
+ when(namespaceOps.listNamespaces(any(), any(), any(), any()))
+ .thenThrow(new RuntimeException("Test exception"));
+ resp =
+ target("/v1/namespace/ns1.ns2/list")
+ .queryParam("delimiter", delimiter)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .get();
+
+ Assertions.assertEquals(
+ Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
resp.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp.getMediaType());
+
+ ErrorResponse errorResp = resp.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(500, errorResp.getCode());
+ Assertions.assertEquals("Test exception", errorResp.getError());
+ Assertions.assertEquals(RuntimeException.class.getSimpleName(),
errorResp.getType());
+ Assertions.assertEquals("ns1.ns2", errorResp.getInstance());
+ Assertions.assertNotNull(errorResp.getDetail());
+ Assertions.assertTrue(errorResp.getDetail().contains("Test exception"));
+ }
+
+ @Test
+ public void testDescribeNamespace() {
+ String namespaceId = "ns1.ns2";
+ String delimiter = ".";
+ DescribeNamespaceResponse describeNamespaceResp = new
DescribeNamespaceResponse();
+ describeNamespaceResp.setProperties(ImmutableMap.of("key", "value"));
+
+ when(namespaceOps.describeNamespace(any(),
any())).thenReturn(describeNamespaceResp);
+
+ Response resp =
+ target("/v1/namespace/ns1.ns2/describe")
+ .queryParam("delimiter", delimiter)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .post(null);
+
+ Mockito.verify(namespaceOps).describeNamespace(eq(namespaceId),
eq(Pattern.quote(delimiter)));
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp.getMediaType());
+
+ DescribeNamespaceResponse respEntity =
resp.readEntity(DescribeNamespaceResponse.class);
+ Assertions.assertEquals(describeNamespaceResp.getProperties(),
respEntity.getProperties());
+
+ // test throw exception
+ when(namespaceOps.describeNamespace(any(), any()))
+ .thenThrow(new RuntimeException("Test exception"));
+ resp =
+ target("/v1/namespace/ns1.ns2/describe")
+ .queryParam("delimiter", delimiter)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .post(null);
+
+ Assertions.assertEquals(
+ Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
resp.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp.getMediaType());
+
+ ErrorResponse errorResp = resp.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(500, errorResp.getCode());
+ Assertions.assertEquals("Test exception", errorResp.getError());
+ Assertions.assertEquals(RuntimeException.class.getSimpleName(),
errorResp.getType());
+ }
+
+ @Test
+ public void testCreateNamespace() {
+ String namespaceId = "ns1.ns2";
+ String delimiter = ".";
+ CreateNamespaceRequest createNamespaceReq = new CreateNamespaceRequest();
+ createNamespaceReq.setProperties(ImmutableMap.of("key", "value"));
+
+ CreateNamespaceResponse createNamespaceResp = new
CreateNamespaceResponse();
+ createNamespaceResp.setProperties(ImmutableMap.of("key", "value"));
+
+ when(namespaceOps.createNamespace(any(), any(), any(),
any())).thenReturn(createNamespaceResp);
+
+ Response resp =
+ target("/v1/namespace/ns1.ns2/create")
+ .queryParam("delimiter", delimiter)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .post(Entity.entity(createNamespaceReq,
MediaType.APPLICATION_JSON_TYPE));
+
+ Mockito.verify(namespaceOps)
+ .createNamespace(
+ eq(namespaceId),
+ eq(Pattern.quote(delimiter)),
+ eq(CreateNamespaceRequest.ModeEnum.CREATE),
+ eq(createNamespaceReq.getProperties()));
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp.getMediaType());
+
+ CreateNamespaceResponse respEntity =
resp.readEntity(CreateNamespaceResponse.class);
+ Assertions.assertEquals(createNamespaceResp.getProperties(),
respEntity.getProperties());
+
+ // test throw exception
+ when(namespaceOps.createNamespace(any(), any(), any(), any()))
+ .thenThrow(new RuntimeException("Test exception"));
+ resp =
+ target("/v1/namespace/ns1.ns2/create")
+ .queryParam("delimiter", delimiter)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .post(Entity.entity(createNamespaceReq,
MediaType.APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(
+ Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
resp.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp.getMediaType());
+
+ ErrorResponse errorResp = resp.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(500, errorResp.getCode());
+ Assertions.assertEquals("Test exception", errorResp.getError());
+ Assertions.assertEquals(RuntimeException.class.getSimpleName(),
errorResp.getType());
+ }
+
+ @Test
+ public void testNamespaceExists() {
+ String namespaceId = "ns1.ns2";
+ String delimiter = ".";
+
+ doNothing().when(namespaceOps).namespaceExists(any(), any());
+
+ Response resp =
+ target("/v1/namespace/ns1.ns2/exists")
+ .queryParam("delimiter", delimiter)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .post(null);
+
+ Mockito.verify(namespaceOps).namespaceExists(eq(namespaceId),
eq(Pattern.quote(delimiter)));
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+
+ // test throw exception
+ doThrow(new NoSuchCatalogException("Not found"))
+ .when(namespaceOps)
+ .namespaceExists(any(), any());
+ resp =
+ target("/v1/namespace/ns1.ns2/exists")
+ .queryParam("delimiter", delimiter)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .post(null);
+
+ Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
resp.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp.getMediaType());
+
+ ErrorResponse errorResp = resp.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(404, errorResp.getCode());
+ Assertions.assertEquals("Not found", errorResp.getError());
+ Assertions.assertEquals(NoSuchCatalogException.class.getSimpleName(),
errorResp.getType());
+ }
+
+ @Test
+ public void testDropNamespace() {
+ String namespaceId = "ns1.ns2";
+ String delimiter = ".";
+ DropNamespaceRequest dropNamespaceReq = new DropNamespaceRequest();
+
+ DropNamespaceResponse dropNamespaceResp = new DropNamespaceResponse();
+ when(namespaceOps.dropNamespace(any(), any(), any(),
any())).thenReturn(dropNamespaceResp);
+
+ Response resp =
+ target("/v1/namespace/ns1.ns2/drop")
+ .queryParam("delimiter", delimiter)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .post(Entity.entity(dropNamespaceReq,
MediaType.APPLICATION_JSON_TYPE));
+
+ Mockito.verify(namespaceOps)
+ .dropNamespace(
+ eq(namespaceId),
+ eq(Pattern.quote(delimiter)),
+ eq(DropNamespaceRequest.ModeEnum.FAIL),
+ eq(DropNamespaceRequest.BehaviorEnum.RESTRICT));
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp.getMediaType());
+
+ // test throw exception
+ when(namespaceOps.dropNamespace(any(), any(), any(), any()))
+ .thenThrow(new RuntimeException("Test exception"));
+ resp =
+ target("/v1/namespace/ns1.ns2/drop")
+ .queryParam("delimiter", delimiter)
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .post(Entity.entity(dropNamespaceReq,
MediaType.APPLICATION_JSON_TYPE));
+
+ Assertions.assertEquals(
+ Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
resp.getStatus());
+ Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp.getMediaType());
+
+ ErrorResponse errorResp = resp.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(500, errorResp.getCode());
+ Assertions.assertEquals("Test exception", errorResp.getError());
+ Assertions.assertEquals(RuntimeException.class.getSimpleName(),
errorResp.getType());
+ }
+}
diff --git
a/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/integration/test/SparkEnvIT.java
b/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/integration/test/SparkEnvIT.java
index 9c2d2d5110..f3d891ad39 100644
---
a/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/integration/test/SparkEnvIT.java
+++
b/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/integration/test/SparkEnvIT.java
@@ -155,7 +155,7 @@ public abstract class SparkEnvIT extends SparkUtilIT {
protected void initCatalogEnv() throws Exception {}
private void initIcebergRestServiceEnv() {
- super.ignoreAuxRestService = false;
+ super.ignoreIcebergAuxRestService = false;
Map<String, String> icebergRestServiceConfigs = new HashMap<>();
icebergRestServiceConfigs.put(
"gravitino."