Skip to content

Commit 5f91d8b

Browse files
contrueCTCopilotimbajin
authored
refactor(server): disable GraphSpaceAPI and ManagerAPI in standalone mode (#2966)
* feat(api): disable GraphSpaceAPI and ManagerAPI in standalone mode - Add public isUsePD() accessor to GraphManager to expose PD status - Add checkPdModeEnabled() helper in API base class - Call checkPdModeEnabled() in all public methods of GraphSpaceAPI (list/get/create/manage/delete) - Call checkPdModeEnabled() in all public methods of ManagerAPI (createManager/delete/list/checkRole/getRolesInGs) - Returns HTTP 400 with message 'GraphSpace management is not supported in standalone mode' - Add standalone-mode rejection tests in GraphSpaceApiTest and ManagerApiTest --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: imbajin <jin@apache.org>
1 parent 0154c06 commit 5f91d8b

File tree

10 files changed

+312
-49
lines changed

10 files changed

+312
-49
lines changed

hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ public class API {
8686
MetricsUtil.registerMeter(API.class, "expected-error");
8787
private static final Meter unknownErrorMeter =
8888
MetricsUtil.registerMeter(API.class, "unknown-error");
89+
private static final String STANDALONE_ERROR =
90+
"GraphSpace management is not supported in standalone mode";
8991

9092
public static HugeGraph graph(GraphManager manager, String graphSpace,
9193
String graph) {
@@ -241,6 +243,20 @@ public static boolean checkAndParseAction(String action) {
241243
}
242244
}
243245

246+
/**
247+
* Ensures the graph manager is available and PD mode is enabled.
248+
*
249+
* @param manager the graph manager of current request
250+
* @throws IllegalArgumentException if the graph manager is null
251+
* @throws HugeException if PD mode is disabled
252+
*/
253+
protected static void ensurePdModeEnabled(GraphManager manager) {
254+
E.checkArgumentNotNull(manager, "Graph manager can't be null");
255+
if (!manager.isPDEnabled()) {
256+
throw new HugeException(STANDALONE_ERROR);
257+
}
258+
}
259+
244260
public static boolean hasAdminPerm(GraphManager manager, String user) {
245261
return manager.authManager().isAdminManager(user);
246262
}

hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/ManagerAPI.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public String createManager(@Context GraphManager manager,
6868
@PathParam("graphspace") String graphSpace,
6969
JsonManager jsonManager) {
7070
LOG.debug("Create manager: {}", jsonManager);
71+
ensurePdModeEnabled(manager);
7172
String user = jsonManager.user;
7273
HugePermission type = jsonManager.type;
7374
// graphSpace now comes from @PathParam instead of JsonManager
@@ -123,6 +124,7 @@ public void delete(@Context GraphManager manager,
123124
@Parameter(description = "The manager type: SPACE, SPACE_MEMBER, or ADMIN")
124125
@QueryParam("type") HugePermission type) {
125126
LOG.debug("Delete graph manager: {} {} {}", user, type, graphSpace);
127+
ensurePdModeEnabled(manager);
126128
E.checkArgument(!"admin".equals(user) ||
127129
type != HugePermission.ADMIN,
128130
"User 'admin' can't be removed from ADMIN");
@@ -168,7 +170,7 @@ public String list(@Context GraphManager manager,
168170
@Parameter(description = "The manager type: SPACE, SPACE_MEMBER or ADMIN")
169171
@QueryParam("type") HugePermission type) {
170172
LOG.debug("list graph manager: {} {}", type, graphSpace);
171-
173+
ensurePdModeEnabled(manager);
172174
AuthManager authManager = manager.authManager();
173175
validType(type);
174176
List<String> adminManagers;
@@ -201,7 +203,7 @@ public String checkRole(@Context GraphManager manager,
201203
"SPACE, SPACE_MEMBER, or ADMIN")
202204
@QueryParam("type") HugePermission type) {
203205
LOG.debug("check if current user is graph manager: {} {}", type, graphSpace);
204-
206+
ensurePdModeEnabled(manager);
205207
validType(type);
206208
AuthManager authManager = manager.authManager();
207209
String user = HugeGraphAuthProxy.username();
@@ -235,6 +237,7 @@ public String getRolesInGs(@Context GraphManager manager,
235237
@Parameter(description = "The user name") @QueryParam("user")
236238
String user) {
237239
LOG.debug("get user [{}]'s role in graph space [{}]", user, graphSpace);
240+
ensurePdModeEnabled(manager);
238241
AuthManager authManager = manager.authManager();
239242
List<HugePermission> result = new ArrayList<>();
240243
validGraphSpace(manager, graphSpace);

hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ public class GraphSpaceAPI extends API {
7878
@Produces(APPLICATION_JSON_WITH_CHARSET)
7979
public Object list(@Context GraphManager manager,
8080
@Context SecurityContext sc) {
81+
ensurePdModeEnabled(manager);
8182
Set<String> spaces = manager.graphSpaces();
8283
return ImmutableMap.of("graphSpaces", spaces);
8384
}
@@ -89,6 +90,7 @@ public Object list(@Context GraphManager manager,
8990
public Object get(@Context GraphManager manager,
9091
@Parameter(description = "The name of the graph space")
9192
@PathParam("graphspace") String graphSpace) {
93+
ensurePdModeEnabled(manager);
9294
manager.getSpaceStorage(graphSpace);
9395
GraphSpace gs = space(manager, graphSpace);
9496

@@ -111,6 +113,7 @@ public Object listProfile(@Context GraphManager manager,
111113
"name or nickname prefix")
112114
@QueryParam("prefix") String prefix,
113115
@Context SecurityContext sc) {
116+
ensurePdModeEnabled(manager);
114117
Set<String> spaces = manager.graphSpaces();
115118
List<Map<String, Object>> spaceList = new ArrayList<>();
116119
List<Map<String, Object>> result = new ArrayList<>();
@@ -160,7 +163,7 @@ public Object listProfile(@Context GraphManager manager,
160163
@RolesAllowed({"admin"})
161164
public String create(@Context GraphManager manager,
162165
JsonGraphSpace jsonGraphSpace) {
163-
166+
ensurePdModeEnabled(manager);
164167
jsonGraphSpace.checkCreate(false);
165168

166169
String creator = HugeGraphAuthProxy.username();
@@ -192,7 +195,7 @@ public Map<String, Object> manage(@Context GraphManager manager,
192195
@Parameter(description = "The name of the graph space")
193196
@PathParam("name") String name,
194197
Map<String, Object> actionMap) {
195-
198+
ensurePdModeEnabled(manager);
196199
E.checkArgument(actionMap != null && actionMap.size() == 2 &&
197200
actionMap.containsKey(GRAPH_SPACE_ACTION),
198201
"Invalid request body '%s'", actionMap);
@@ -322,6 +325,7 @@ public Map<String, Object> manage(@Context GraphManager manager,
322325
public void delete(@Context GraphManager manager,
323326
@Parameter(description = "The name of the graph space")
324327
@PathParam("name") String name) {
328+
ensurePdModeEnabled(manager);
325329
manager.dropGraphSpace(name);
326330
}
327331

hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ private static String serviceId(String graphSpace, Service.ServiceType type,
276276
.replace("_", "-").toLowerCase();
277277
}
278278

279-
private boolean usePD() {
279+
public boolean isPDEnabled() {
280280
return this.PDExist;
281281
}
282282

@@ -1227,7 +1227,7 @@ private void dropGraphLocal(HugeGraph graph) {
12271227

12281228
public HugeGraph createGraph(String graphSpace, String name, String creator,
12291229
Map<String, Object> configs, boolean init) {
1230-
if (!usePD()) {
1230+
if (!isPDEnabled()) {
12311231
// Extract nickname from configs
12321232
String nickname;
12331233
if (configs.get("nickname") != null) {
@@ -1937,7 +1937,7 @@ public Set<String> getServiceUrls(String graphSpace, String service,
19371937
public HugeGraph graph(String graphSpace, String name) {
19381938
String key = String.join(DELIMITER, graphSpace, name);
19391939
Graph graph = this.graphs.get(key);
1940-
if (graph == null && usePD()) {
1940+
if (graph == null && isPDEnabled()) {
19411941
Map<String, Map<String, Object>> configs =
19421942
this.metaManager.graphConfigs(graphSpace);
19431943
// If current server registered graph space is not DEFAULT, only load graph creation
@@ -1981,7 +1981,7 @@ public void dropGraphLocal(String name) {
19811981
}
19821982

19831983
public void dropGraph(String graphSpace, String name, boolean clear) {
1984-
if (!usePD()) {
1984+
if (!isPDEnabled()) {
19851985
dropGraphLocal(name);
19861986
return;
19871987
}
@@ -2086,7 +2086,7 @@ private void checkOptionsUnique(String graphSpace,
20862086
public Set<String> graphs(String graphSpace) {
20872087
Set<String> graphs = new HashSet<>();
20882088

2089-
if (!usePD()) {
2089+
if (!isPDEnabled()) {
20902090
for (String key : this.graphs.keySet()) {
20912091
String[] parts = key.split(DELIMITER);
20922092
if (parts[0].equals(graphSpace)) {
@@ -2103,7 +2103,7 @@ public Set<String> graphs(String graphSpace) {
21032103
}
21042104

21052105
public GraphSpace graphSpace(String name) {
2106-
if (!usePD()) {
2106+
if (!isPDEnabled()) {
21072107
return new GraphSpace("DEFAULT");
21082108
}
21092109
GraphSpace space = this.graphSpaces.get(name);
@@ -2152,7 +2152,7 @@ private MapConfiguration buildConfig(Map<String, Object> configs) {
21522152
public void graphReadMode(String graphSpace, String graphName,
21532153
GraphReadMode readMode) {
21542154

2155-
if (!usePD()) {
2155+
if (!isPDEnabled()) {
21562156
HugeGraph g = this.graph(spaceGraphName(graphSpace, graphName));
21572157
g.readMode(readMode);
21582158
return;

hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/ApiTestSuite.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
CypherApiTest.class,
4343
ArthasApiTest.class,
4444
GraphSpaceApiTest.class,
45+
GraphSpaceApiStandaloneTest.class,
46+
ManagerApiStandaloneTest.class,
4547
})
4648
public class ApiTestSuite {
4749

hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/BaseApiTest.java

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.junit.After;
4040
import org.junit.AfterClass;
4141
import org.junit.Assert;
42+
import org.junit.Assume;
4243
import org.junit.BeforeClass;
4344

4445
import com.fasterxml.jackson.databind.JavaType;
@@ -61,9 +62,9 @@ public class BaseApiTest {
6162
protected static final String BASE_URL = "http://127.0.0.1:8080";
6263
private static final String GRAPH = "hugegraph";
6364
private static final String GRAPHSPACE = "DEFAULT";
64-
private static final String USERNAME = "admin";
6565
protected static final String URL_PREFIX = "graphspaces/" + GRAPHSPACE + "/graphs/" + GRAPH;
6666
protected static final String TRAVERSERS_API = URL_PREFIX + "/traversers";
67+
private static final String USERNAME = "admin";
6768
private static final String PASSWORD = "pa";
6869
private static final int NO_LIMIT = -1;
6970
private static final String SCHEMA_PKS = "/schema/propertykeys";
@@ -73,6 +74,8 @@ public class BaseApiTest {
7374
private static final String GRAPH_VERTEX = "/graph/vertices";
7475
private static final String GRAPH_EDGE = "/graph/edges";
7576
private static final String BATCH = "/batch";
77+
static final String STANDALONE_ERROR =
78+
"GraphSpace management is not supported in standalone mode";
7679

7780
private static final String ROCKSDB_CONFIG_TEMPLATE =
7881
"{ \"gremlin.graph\": \"org.apache.hugegraph.HugeFactory\"," +
@@ -82,10 +85,8 @@ public class BaseApiTest {
8285
"\"rocksdb.wal_path\": \"rocksdbtest-data-%s\"," +
8386
"\"search.text_analyzer\": \"jieba\"," +
8487
"\"search.text_analyzer_mode\": \"INDEX\" }";
85-
86-
protected static RestClient client;
87-
8888
private static final ObjectMapper MAPPER = new ObjectMapper();
89+
protected static RestClient client;
8990

9091
@BeforeClass
9192
public static void init() {
@@ -99,19 +100,10 @@ public static void clear() throws Exception {
99100
client = null;
100101
}
101102

102-
@After
103-
public void teardown() throws Exception {
104-
BaseApiTest.clearData();
105-
}
106-
107103
public static String baseUrl() {
108104
return BASE_URL;
109105
}
110106

111-
public RestClient client() {
112-
return client;
113-
}
114-
115107
public static RestClient newClient() {
116108
return new RestClient(BASE_URL);
117109
}
@@ -193,7 +185,8 @@ protected static void waitTaskStatus(int task, Set<String> expectedStatus) {
193185
Assert.fail(String.format("Failed to wait for task %s " +
194186
"due to timeout", task));
195187
}
196-
} while (!expectedStatus.contains(status));
188+
}
189+
while (!expectedStatus.contains(status));
197190
}
198191

199192
protected static void initVertexLabel() {
@@ -748,6 +741,30 @@ public static RestClient analystClient(String graphSpace, String username) {
748741
return analystClient;
749742
}
750743

744+
/**
745+
* Skips the current test when the server backend is not known to be in
746+
* standalone mode. Treats both {@code "hstore"} and {@code null}
747+
* (i.e. the backend property is not provided/unknown) as PD/distributed
748+
* mode and skips the test for safety.
749+
* Call this from a {@code @Before} method in standalone-only test classes.
750+
*/
751+
public static void assumeStandaloneMode() {
752+
String backend = System.getProperty("backend");
753+
boolean isPdMode = backend == null || "hstore".equals(backend);
754+
Assume.assumeFalse(
755+
"Skip when backend is hstore (PD/distributed) or not specified",
756+
isPdMode);
757+
}
758+
759+
@After
760+
public void teardown() throws Exception {
761+
BaseApiTest.clearData();
762+
}
763+
764+
public RestClient client() {
765+
return client;
766+
}
767+
751768
public static class RestClient {
752769

753770
private final Client client;
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.hugegraph.api;
19+
20+
import com.google.common.collect.ImmutableMap;
21+
22+
import org.junit.Assert;
23+
import org.junit.Before;
24+
import org.junit.Test;
25+
26+
import jakarta.ws.rs.core.Response;
27+
28+
/**
29+
* Tests that GraphSpaceAPI returns a friendly HTTP 400 error in standalone
30+
* mode (i.e. when the server is started without PD / hstore backend).
31+
* <p>
32+
* This class intentionally does NOT have a class-level Assume guard so that
33+
* the tests are actually executed in non-hstore CI runs.
34+
*/
35+
public class GraphSpaceApiStandaloneTest extends BaseApiTest {
36+
37+
private static final String PATH = "graphspaces";
38+
39+
@Before
40+
public void skipForPdMode() {
41+
assumeStandaloneMode();
42+
}
43+
44+
@Test
45+
public void testProfileReturnsFriendlyError() {
46+
Response r = this.client().get(PATH + "/profile");
47+
String content = assertResponseStatus(400, r);
48+
Assert.assertTrue(content.contains(STANDALONE_ERROR));
49+
}
50+
51+
@Test
52+
public void testListReturnsFriendlyError() {
53+
Response r = this.client().get(PATH);
54+
String content = assertResponseStatus(400, r);
55+
Assert.assertTrue(content.contains(STANDALONE_ERROR));
56+
}
57+
58+
@Test
59+
public void testGetReturnsFriendlyError() {
60+
Response r = this.client().get(PATH, "DEFAULT");
61+
String content = assertResponseStatus(400, r);
62+
Assert.assertTrue(content.contains(STANDALONE_ERROR));
63+
}
64+
65+
@Test
66+
public void testCreateReturnsFriendlyError() {
67+
String body = "{\"name\":\"test_standalone\",\"nickname\":\"test\","
68+
+ "\"description\":\"test\",\"cpu_limit\":10,"
69+
+ "\"memory_limit\":10,\"storage_limit\":10,"
70+
+ "\"max_graph_number\":10,\"max_role_number\":10,"
71+
+ "\"auth\":false,\"configs\":{}}";
72+
Response r = this.client().post(PATH, body);
73+
String content = assertResponseStatus(400, r);
74+
Assert.assertTrue(content.contains(STANDALONE_ERROR));
75+
}
76+
77+
@Test
78+
public void testManageReturnsFriendlyError() {
79+
String body = "{\"action\":\"update\",\"update\":{\"name\":\"DEFAULT\"}}";
80+
Response r = this.client().put(PATH, "DEFAULT", body, ImmutableMap.of());
81+
String content = assertResponseStatus(400, r);
82+
Assert.assertTrue(content.contains(STANDALONE_ERROR));
83+
}
84+
85+
@Test
86+
public void testDeleteReturnsFriendlyError() {
87+
Response r = this.client().delete(PATH, "nonexistent");
88+
String content = assertResponseStatus(400, r);
89+
Assert.assertTrue(content.contains(STANDALONE_ERROR));
90+
}
91+
}

hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/api/GraphSpaceApiTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public void removeSpaces() {
4242
Response r = this.client().get(PATH);
4343
String result = r.readEntity(String.class);
4444
Map<String, Object> resultMap = JsonUtil.fromJson(result, Map.class);
45-
List<String> spaces = (List<String>) resultMap.get("graphSpaces");
45+
List<String> spaces = (List<String>)resultMap.get("graphSpaces");
4646
for (String space : spaces) {
4747
if (!"DEFAULT".equals(space)) {
4848
this.client().delete(PATH, space);

0 commit comments

Comments
 (0)