This is an automated email from the ASF dual-hosted git repository.
jin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-hugegraph.git
The following commit(s) were added to refs/heads/master by this push:
new ef2db2c9c feat(server): add gs profile api (#2950)
ef2db2c9c is described below
commit ef2db2c9c088c245fc8d608c24285fb4485694ab
Author: Tsukilc <[email protected]>
AuthorDate: Sat Feb 14 14:29:59 2026 +0800
feat(server): add gs profile api (#2950)
---
.../apache/hugegraph/api/space/GraphSpaceAPI.java | 60 +++++++
.../apache/hugegraph/api/GraphSpaceApiTest.java | 180 +++++++++++++++++++--
2 files changed, 228 insertions(+), 12 deletions(-)
diff --git
a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java
b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java
index 1471814cb..bd0fb4e84 100644
---
a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java
+++
b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java
@@ -19,6 +19,9 @@
package org.apache.hugegraph.api.space;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -26,6 +29,7 @@ import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.hugegraph.api.API;
import org.apache.hugegraph.api.filter.StatusFilter.Status;
+import org.apache.hugegraph.auth.AuthManager;
import org.apache.hugegraph.auth.HugeGraphAuthProxy;
import org.apache.hugegraph.core.GraphManager;
import org.apache.hugegraph.define.Checkable;
@@ -52,6 +56,7 @@ import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.SecurityContext;
@@ -93,6 +98,55 @@ public class GraphSpaceAPI extends API {
return gsInfo;
}
+ @GET
+ @Timed
+ @Path("profile")
+ @Produces(APPLICATION_JSON_WITH_CHARSET)
+ @RolesAllowed({"admin"})
+ public Object listProfile(@Context GraphManager manager,
+ @QueryParam("prefix") String prefix,
+ @Context SecurityContext sc) {
+ Set<String> spaces = manager.graphSpaces();
+ List<Map<String, Object>> spaceList = new ArrayList<>();
+ List<Map<String, Object>> result = new ArrayList<>();
+ String user = HugeGraphAuthProxy.username();
+ AuthManager authManager = manager.authManager();
+ // FIXME: defaultSpace related interface is not implemented
+ // String defaultSpace = authManager.getDefaultSpace(user);
+ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
+ for (String sp : spaces) {
+ manager.getSpaceStorage(sp);
+ GraphSpace gs = space(manager, sp);
+ Map<String, Object> gsProfile = gs.info();
+ boolean isManager = verifyPermission(user, authManager, sp);
+
+ // 设置当前用户的是否允许访问该空间
+ if (gs.auth() && !isManager) {
+ gsProfile.put("authed", false);
+ } else {
+ gsProfile.put("authed", true);
+ }
+
+ gsProfile.put("create_time", format.format(gs.createTime()));
+ gsProfile.put("update_time", format.format(gs.updateTime()));
+ if (!isPrefix(gsProfile, prefix)) {
+ continue;
+ }
+
+ gsProfile.put("default", false);
+ result.add(gsProfile);
+ //boolean defaulted = StringUtils.equals(sp, defaultSpace);
+ //gsProfile.put("default", defaulted);
+ //if (defaulted) {
+ // result.add(gsProfile);
+ //} else {
+ // spaceList.add(gsProfile);
+ //}
+ }
+ result.addAll(spaceList);
+ return result;
+ }
+
@POST
@Timed
@Status(Status.CREATED)
@@ -275,6 +329,12 @@ public class GraphSpaceAPI extends API {
"_dp" : graphSpace.toLowerCase() + "_dp";
}
+ private boolean verifyPermission(String user, AuthManager authManager,
String graphSpace) {
+ return authManager.isAdminManager(user) ||
+ authManager.isSpaceManager(graphSpace, user) ||
+ authManager.isSpaceMember(graphSpace, user);
+ }
+
private static class JsonGraphSpace implements Checkable {
@JsonProperty("name")
diff --git
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java
index d18409ff2..01782e7e0 100644
---
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java
+++
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java
@@ -22,10 +22,13 @@ import java.util.Map;
import java.util.Objects;
import org.apache.hugegraph.util.JsonUtil;
+import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
+import com.google.common.collect.ImmutableMap;
+
import jakarta.ws.rs.core.Response;
public class GraphSpaceApiTest extends BaseApiTest {
@@ -51,8 +54,8 @@ public class GraphSpaceApiTest extends BaseApiTest {
public void testAddSpaceNamespace() {
String body = "{\n" +
" \"name\": \"test_add_no_ns\",\n" +
- " \"nickname\":\"Test No Namespace\",\n" +
- " \"description\": \"no namespace\",\n" +
+ " \"nickname\":\"TestNoNamespace\",\n" +
+ " \"description\": \"nonamespace\",\n" +
" \"cpu_limit\": 1000,\n" +
" \"memory_limit\": 1024,\n" +
" \"storage_limit\": 1000,\n" +
@@ -73,8 +76,8 @@ public class GraphSpaceApiTest extends BaseApiTest {
String body2 = "{\n" +
" \"name\": \"test_add_has_ns\",\n" +
- " \"nickname\":\"Test With Namespace\",\n" +
- " \"description\": \"has namespace\",\n" +
+ " \"nickname\":\"TestWithNamespace\",\n" +
+ " \"description\": \"hasnamespace\",\n" +
" \"cpu_limit\": 1000,\n" +
" \"memory_limit\": 1024,\n" +
" \"storage_limit\": 1000,\n" +
@@ -105,8 +108,8 @@ public class GraphSpaceApiTest extends BaseApiTest {
String spaceName = "test_delete_space";
String body = "{"
+ "\"name\":\"" + spaceName + "\","
- + "\"nickname\":\"Test Delete Space\","
- + "\"description\":\"Test delete space\","
+ + "\"nickname\":\"TestDeleteSpace\","
+ + "\"description\":\"Testdeletespace\","
+ "\"cpu_limit\":1000,"
+ "\"memory_limit\":1024,"
+ "\"storage_limit\":1000,"
@@ -145,8 +148,8 @@ public class GraphSpaceApiTest extends BaseApiTest {
String spaceName = "duplicate_space";
String body = "{"
+ "\"name\":\"" + spaceName + "\","
- + "\"nickname\":\"Duplicate Test Space\","
- + "\"description\":\"Test duplicate space\","
+ + "\"nickname\":\"DuplicateTestSpace\","
+ + "\"description\":\"Testduplicatespace\","
+ "\"cpu_limit\":1000,"
+ "\"memory_limit\":1024,"
+ "\"storage_limit\":1000,"
@@ -179,8 +182,8 @@ public class GraphSpaceApiTest extends BaseApiTest {
// Test minimum limits
String minLimitsBody = "{"
+ "\"name\":\"" + spaceName + "_min\","
- + "\"nickname\":\"Minimum Limits Test\","
- + "\"description\":\"Test minimum limits\","
+ + "\"nickname\":\"MinimumLimitsTest\","
+ + "\"description\":\"Testminimumlimits\","
+ "\"cpu_limit\":1,"
+ "\"memory_limit\":1,"
+ "\"storage_limit\":1,"
@@ -203,8 +206,8 @@ public class GraphSpaceApiTest extends BaseApiTest {
// Test maximum limits
String maxLimitsBody = "{"
+ "\"name\":\"" + spaceName + "_max\","
- + "\"nickname\":\"Maximum Limits Test\","
- + "\"description\":\"Test maximum limits\","
+ + "\"nickname\":\"MaximumLimitsTest\","
+ + "\"description\":\"Testmaximumlimits\","
+ "\"cpu_limit\":999999,"
+ "\"memory_limit\":999999,"
+ "\"storage_limit\":999999,"
@@ -275,4 +278,157 @@ public class GraphSpaceApiTest extends BaseApiTest {
r = this.client().post(PATH, negativeLimitsBody);
assertResponseStatus(400, r);
}
+
+ @Test
+ public void testListProfile() {
+ // Get profile list without prefix
+ Response r = this.client().get(PATH + "/profile");
+ String result = assertResponseStatus(200, r);
+
+ @SuppressWarnings("unchecked")
+ List<Map<String, Object>> profiles = JsonUtil.fromJson(result,
List.class);
+
+ // Should contain at least the DEFAULT space
+ Assert.assertTrue("Expected at least one profile", profiles.size() >=
1);
+
+ // Verify profile structure
+ for (Map<String, Object> profile : profiles) {
+ Assert.assertTrue("Profile should contain 'name'",
+ profile.containsKey("name"));
+ Assert.assertTrue("Profile should contain 'authed'",
+ profile.containsKey("authed"));
+ Assert.assertTrue("Profile should contain 'create_time'",
+ profile.containsKey("create_time"));
+ Assert.assertTrue("Profile should contain 'update_time'",
+ profile.containsKey("update_time"));
+ Assert.assertTrue("Profile should contain 'default'",
+ profile.containsKey("default"));
+ }
+ }
+
+ @Test
+ public void testListProfileWithPrefix() {
+ // Create test spaces with different names
+ String space1 = "{"
+ + "\"name\":\"test_profile_space1\","
+ + "\"nickname\":\"TestProfileSpace\","
+ + "\"description\":\"Testprofilelisting\","
+ + "\"cpu_limit\":1000,"
+ + "\"memory_limit\":1024,"
+ + "\"storage_limit\":1000,"
+ + "\"compute_cpu_limit\":0,"
+ + "\"compute_memory_limit\":0,"
+ + "\"oltp_namespace\":null,"
+ + "\"olap_namespace\":null,"
+ + "\"storage_namespace\":null,"
+ + "\"operator_image_path\":\"test\","
+ + "\"internal_algorithm_image_url\":\"test\","
+ + "\"max_graph_number\":100,"
+ + "\"max_role_number\":100,"
+ + "\"auth\":false,"
+ + "\"configs\":{}"
+ + "}";
+
+ // Create a space that should NOT match the prefix filter
+ String space2 = "{"
+ + "\"name\":\"other_profile_space\","
+ + "\"nickname\":\"OtherProfileSpace\","
+ + "\"description\":\"Other profile listing\","
+ + "\"cpu_limit\":1000,"
+ + "\"memory_limit\":1024,"
+ + "\"storage_limit\":1000,"
+ + "\"compute_cpu_limit\":0,"
+ + "\"compute_memory_limit\":0,"
+ + "\"oltp_namespace\":null,"
+ + "\"olap_namespace\":null,"
+ + "\"storage_namespace\":null,"
+ + "\"operator_image_path\":\"test\","
+ + "\"internal_algorithm_image_url\":\"test\","
+ + "\"max_graph_number\":100,"
+ + "\"max_role_number\":100,"
+ + "\"auth\":false,"
+ + "\"configs\":{}"
+ + "}";
+
+ // Create spaces
+ Response r = this.client().post(PATH, space1);
+ assertResponseStatus(201, r);
+ r = this.client().post(PATH, space2);
+ assertResponseStatus(201, r);
+
+ // Test with prefix filter
+ r = this.client().get(PATH + "/profile",
+ ImmutableMap.of("prefix", "test"));
+ String result = assertResponseStatus(200, r);
+
+ @SuppressWarnings("unchecked")
+ List<Map<String, Object>> profiles = JsonUtil.fromJson(result,
List.class);
+ Assert.assertFalse("Expected non-empty profile list with prefix
filter",
+ profiles.isEmpty());
+
+ // Verify all returned profiles match the prefix
+ for (Map<String, Object> profile : profiles) {
+ String name = Objects.toString(profile.get("name"), "");
+ String nickname = Objects.toString(profile.get("nickname"), "");
+ boolean matchesPrefix = name.startsWith("test") ||
+ nickname.startsWith("test") ||
+ nickname.startsWith("Test");
+ Assert.assertTrue(
+ "Profile should match prefix 'test': " + profile,
+ matchesPrefix);
+
+ // Ensure the non-matching space is excluded
+ Assert.assertNotEquals("Non-matching space should be filtered out",
+ "other_profile_space", name);
+ }
+ }
+
+ @Test
+ public void testListProfileWithAuth() {
+ // Create a space with auth enabled
+ String authSpace = "{"
+ + "\"name\":\"auth_test_space\","
+ + "\"nickname\":\"AuthTestSpace\","
+ + "\"description\":\"Test auth in profile\","
+ + "\"cpu_limit\":1000,"
+ + "\"memory_limit\":1024,"
+ + "\"storage_limit\":1000,"
+ + "\"compute_cpu_limit\":0,"
+ + "\"compute_memory_limit\":0,"
+ + "\"oltp_namespace\":null,"
+ + "\"olap_namespace\":null,"
+ + "\"storage_namespace\":null,"
+ + "\"operator_image_path\":\"test\","
+ + "\"internal_algorithm_image_url\":\"test\","
+ + "\"max_graph_number\":100,"
+ + "\"max_role_number\":100,"
+ + "\"auth\":true,"
+ + "\"configs\":{}"
+ + "}";
+
+ Response r = this.client().post(PATH, authSpace);
+ assertResponseStatus(201, r);
+
+ // Get profile list
+ r = this.client().get(PATH + "/profile");
+ String result = assertResponseStatus(200, r);
+
+ @SuppressWarnings("unchecked")
+ List<Map<String, Object>> profiles = JsonUtil.fromJson(result,
List.class);
+
+ // Find the auth_test_space and verify authed field
+ boolean found = false;
+ for (Map<String, Object> profile : profiles) {
+ if ("auth_test_space".equals(profile.get("name"))) {
+ found = true;
+ // Admin user should be authed
+ Assert.assertTrue("Profile should contain 'authed' field",
+ profile.containsKey("authed"));
+ Assert.assertEquals("Admin user should be authorized",
+ true, profile.get("authed"));
+ break;
+ }
+ }
+ Assert.assertTrue("auth_test_space not found in profile list", found);
+ }
}