-
Notifications
You must be signed in to change notification settings - Fork 580
feat(server): add gs profile api #2950
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,13 +19,17 @@ | |
|
|
||
| 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; | ||
|
|
||
| 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.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 Object get(@Context GraphManager manager, | |
| 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; | ||
|
Comment on lines
+110
to
+147
|
||
| } | ||
|
|
||
| @POST | ||
| @Timed | ||
| @Status(Status.CREATED) | ||
|
|
@@ -275,6 +329,12 @@ private String getDpUserName(String graphSpace) { | |
| "_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") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,10 +22,13 @@ | |
| 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 void removeSpaces() { | |
| 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 void testAddSpaceNamespace() { | |
|
|
||
| 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 void testDeleteSpace() { | |
| 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 void testCreateSpaceWithSameName() { | |
| 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 void testSpaceResourceLimits() { | |
| // 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 void testSpaceResourceLimits() { | |
| // 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 void testInvalidSpaceCreation() { | |
| 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")); | ||
| } | ||
|
Comment on lines
291
to
306
|
||
| } | ||
|
|
||
| @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); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The date format pattern uses
hh(12-hour clock) without an AM/PM marker, which can produce ambiguous timestamps. Also, elsewhere in the codebase (e.g., HugeGraphSONModule) dates are formatted withyyyy-MM-dd HH:mm:ss.SSS; consider using a consistent 24-hour format (and ideally the same formatter) forcreate_time/update_time, or avoid overriding theDatevalues fromgs.info()and let the configured JSON Date serializer handle formatting.