feat(server): adapt Hubble 2.0 frontend APIs and implement default graph/role management#3008
feat(server): adapt Hubble 2.0 frontend APIs and implement default graph/role management#3008Yeaury wants to merge 18 commits intoapache:masterfrom
Conversation
- Add listProfile endpoint with default graph sorting and prefix filtering - Add setDefault/unsetDefault/getDefault endpoints for default graph management - Add manage(PUT) endpoint for graph nickname update - Add createByForm for form-urlencoded graph creation compatibility - Auto-fill HStore/PD defaults (backend/serializer/store) during graph creation
- Add setDefaultRole/checkDefaultRole/deleteDefaultRole in GraphSpaceAPI - Add checkDefaultRole endpoint in ManagerAPI - Add default role interfaces in AuthManager - Implement default role CRUD in StandardAuthManager and StandardAuthManagerV2 - Add stub proxy methods in HugeGraphAuthProxy
- Add new SchemaTemplateAPI with list/get/create/update/delete operations - Fix package path from api.profile to api.space - Use HugeGraphAuthProxy.username() instead of authManager.username()
There was a problem hiding this comment.
Pull request overview
This PR adds/extends HugeGraph Server REST endpoints and auth-layer capabilities needed by the Hubble 2.0 frontend, focusing on graph profile listing, default graph selection, default role management, and schema template CRUD within graphspaces.
Changes:
- Added graph profile listing + default-graph set/unset/query APIs, plus graph create compatibility tweaks.
- Implemented default-graph/default-role persistence methods in the
AuthManagerinterface and its implementations/proxies. - Introduced schema template CRUD API and corresponding
GraphManagerhelpers.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
| hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/util/ConfigUtil.java | Adds config-to-string helper used by graph profile listing. |
| hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/AuthManager.java | Extends auth interface for default graph/role operations. |
| hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManager.java | Implements new default graph/role methods (non-PD auth manager). |
| hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/auth/StandardAuthManagerV2.java | Implements new default graph/role methods (PD-mode auth manager). |
| hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/HugeGraphAuthProxy.java | Proxies/delegates new AuthManager methods. |
| hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java | Adds schema template management helpers. |
| hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/profile/GraphsAPI.java | Adds graph profile listing, default graph APIs, manage/update behavior, and create defaults. |
| hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/GraphSpaceAPI.java | Adds default role management endpoints and JSON tolerance. |
| hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/auth/ManagerAPI.java | Adds endpoint to query whether current user has a default role. |
| hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/space/SchemaTemplateAPI.java | New schema template CRUD endpoint implementation. |
Comments suppressed due to low confidence (1)
hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/profile/GraphsAPI.java:443
configsis only validated as non-null whenclone_graph_nameis empty. Ifclone_graph_nameis provided and the request body is omitted/empty,configscan be null andconvConfig(configs)will throw aNullPointerExceptionin the clone branch. Consider defaultingconfigsto an empty map (or makingconvConfig()null-safe) before using it for cloning.
// Check required parameters for creating graph
if (StringUtils.isEmpty(clone)) {
// Only check required parameters when creating new graph, not when cloning
E.checkArgument(configs != null, "Config parameters cannot be null");
// Auto-fill defaults for PD/HStore mode when not provided
configs.putIfAbsent("backend", "hstore");
configs.putIfAbsent("serializer", "binary");
configs.putIfAbsent("store", name);
// Map frontend 'schema' field to backend config key
Object schema = configs.remove("schema");
if (schema != null && !schema.toString().isEmpty()) {
configs.put("schema.init_template", schema.toString());
}
}
String creator = HugeGraphAuthProxy.username();
if (StringUtils.isNotEmpty(clone)) {
// Clone from existing graph
LOG.debug("Clone graph '{}' to '{}' in graph space '{}'", clone, name, graphSpace);
graph = manager.cloneGraph(graphSpace, clone, name, convConfig(configs));
} else {
// Create new graph
graph = manager.createGraph(graphSpace, name, creator,
convConfig(configs), true);
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…#3008) - fix: use @POST/@delete for setDefault/unsetDefault (REST semantics) - fix: add null/empty validation before role field access in GraphSpaceAPI to prevent NPE in setDefaultRole/checkDefaultRole/deleteDefaultRole - fix: change isPrefix to private static and guard nickname null in GraphSpaceAPI and GraphsAPI - fix: ConfigUtil.writeConfigToString always returns JSON regardless of whether config was loaded from file, fixing listProfile endpoint - fix: add @RolesAllowed annotations to SchemaTemplateAPI endpoints - fix: use ForbiddenException (403) instead of HugeException (400) for authorization failures in SchemaTemplateAPI and GraphSpaceAPI - fix: correct LOG placeholder count in SchemaTemplateAPI.delete - fix: use HugeException ('%s') format instead of SLF4J '{}' format - fix: replace com.alipay StringUtils with commons-lang3 in ManagerAPI - fix: add @consumes and checkUpdate() validation to SchemaTemplate.update - fix: add ensurePdModeEnabled guard to ManagerAPI.checkDefaultRole - fix: guard configs null access in GraphsAPI.create clone branch
新增 API 单机版(非 PD)兼容性对照目标:社区默认的 RocksDB 单机版通过 需要兼容单机版的端点(GraphsAPI)
PD 专属端点(无需兼容单机版)
总结:7 个 GraphsAPI 端点需要兼容单机版。其中 3 个需要代码改动(manage 需加 |
… mode ## Background Hubble 2.0 previously relied exclusively on PD mode (distributed HStore backend). This PR makes the server-side APIs fully compatible with the community default: single-node RocksDB without PD/HStore, so that Hubble remains functional out of the box for all deployment modes. ## Core bug fixes ### GraphsAPI - Fix `create()`: `backend=hstore` / `serializer=binary` defaults are now only injected when `manager.isPDEnabled()` is true, preventing graph creation failures on standalone RocksDB deployments. - Fix `manage()`: replace `exist.nickname(nickname)` (in-memory only) with `manager.updateGraphNickname()`, which persists the change to PD meta storage in distributed mode and gracefully falls back to in-memory update in standalone mode. - Fix `manage()`: relax `actionMap.size() == 2` validation to `containsKey(GRAPH_ACTION)`, so extra fields from the frontend no longer cause spurious 400 errors. - Guard `getDefaultGraph()`, `setDefault()`, `unsetDefault()`, and `getDefault()` with `isPDEnabled()` checks; return empty results in standalone mode instead of throwing NPE. - Fix `listProfile()`: guard `getDefaultGraph()` call with `isPDEnabled()`; add null-safe fallback for `gs.nickname()` in non-PD mode. ### GraphManager - `isExistedGraphNickname()`: add non-PD branch that scans in-memory graphs instead of accessing the uninitialized `metaManager`, preventing NPE in standalone mode. - New `updateGraphNickname()`: updates in-memory graph instance first, then persists nickname to `metaManager` only in PD mode. ### ConfigUtil - `writeConfigToString()`: always serializes config to JSON (previously could emit raw properties format), fixing `listProfile` deserialization. - New `isSensitiveKey()`: filters keys containing `password`, `secret`, `token`, `credential`, `private_key`, or `auth.key` from the serialized output to prevent credential leakage through the API. ### ManagerAPI - Add `ensurePdModeEnabled()` guard to all PD-specific endpoints (`createManager`, `deleteManager`, `list`, `checkRole`, `getRolesInGs`, `checkDefaultRole`). - Wrap `HugeDefaultRole.valueOf()` in try-catch to return HTTP 400 instead of HTTP 500 when an invalid role string is supplied. ### SchemaTemplateAPI - Fix incorrect `HugeException` import; replace with `ForbiddenException` for proper HTTP 403 semantics. - Add missing `@RolesAllowed` annotations and implement `checkUpdate()` validation. ### StandardAuthManager - Implement `setDefaultGraph` / `getDefaultGraph` / `unsetDefaultGraph` using marker-group pattern (HugeGroup + HugeBelong) for persistence. - Implement `createDefaultRole` / `createSpaceDefaultRole` / `isDefaultRole` / `deleteDefaultRole` with the same marker-group mechanism. - Add detailed design-note Javadoc explaining the workaround, its limitations, and the non-PD degradation path. ## Code quality improvements - Extract shared `isPrefix(Map, String)` helper and `DATE_FORMATTER` constant into the `API` base class, eliminating ~30 lines of duplicated code across `GraphsAPI` and `GraphSpaceAPI`. - Replace non-thread-safe `SimpleDateFormat` (constructed per-request) with a single static `DateTimeFormatter` (immutable, thread-safe). - Fix 12-hour clock format `hh` → 24-hour `HH` in `GraphSpaceAPI`.
… (ID vs name mismatch in standalone mode)
…raphs from PD Previously, HugeGremlinServer was started after HugeRestServer, causing ContextGremlinServer's GRAPH_CREATE listener to be registered too late. Graphs loaded from PD/meta during RestServer initialization fired their GRAPH_CREATE events before the listener existed, so they were never injected into Gremlin's global bindings. This caused: "Could not rebind [g] to [__g_DEFAULT-xx] as [__g_DEFAULT-xx] not in the Graph or TraversalSource global bindings" (HTTP 400) Fix: split HugeGremlinServer.start() into prepare() + startPrepared(). prepare() constructs ContextGremlinServer (registering listeners) before RestServer starts. startPrepared() is called after RestServer finishes loading graphs. This ensures all PD-loaded graphs are captured by the listener and injected correctly. Affected files: - HugeGraphServer: call prepare() before RestServer, startPrepared() after - HugeGremlinServer: add prepare()/startPrepared(), keep start() for compat - ContextGremlinServer: no behavioral change (listeners register on construct)
…ethods Root Cause: In StandardAuthManagerV2, the `createUser` (and other create methods: `createGroup`, `createTarget`, `createBelong`, `createAccess`) only initialized timestamps but failed to call `updateCreator()`. During serialization, `SchemaDefine.AuthElement.asMap` enforces a strict `creator != null` validation, resulting in an `IllegalStateException`. Changes: - Added `updateCreator(xxx)` calls immediately after `xxx.create(xxx.update())` in all 5 `create*` methods. - Hardened the `updateCreator` method: Introduced a fallback to "system" when `currentUsername()` returns null. This ensures the `creator` is always populated, preventing errors during system initialization when no HTTP context is available.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 15 changed files in this pull request and generated 10 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Add the private static isPrefix() method that was accidentally removed in a previous commit. This method is used by listProfile() to filter graphs by name or nickname prefix, with proper null handling for nickname field. This fixes a compilation error where isPrefix() was called but not defined.
Check if belong record exists before attempting to delete in unsetDefaultGraph() to prevent throwing exceptions when unsetting an already-unset default graph. This makes the API idempotent and prevents 500 errors when clients call unset multiple times. The fix aligns StandardAuthManagerV2 with StandardAuthManager, which already implements this idempotent behavior.
- SchemaTemplateAPI: add ensurePdModeEnabled() guard to all endpoints to prevent NPE when MetaManager is not initialized in standalone mode - SchemaTemplateAPI: add null body check in create() and update() to return 400 instead of NPE/500 on missing request body - GraphsAPI.listProfile: format default_update_time consistently with create_time using DATE_FORMATTER instead of raw Date serialization - GraphsAPI.manage: guard value.getClass() against null action value to prevent NPE when building the validation error message - GraphSpaceAPI.deleteDefaultRole: wrap HugeDefaultRole.valueOf() in try/catch to return 400 for invalid role values instead of 500 - GraphManager: add isPDEnabled() guard to all schema-template methods to prevent NPE when MetaManager is not available in standalone mode - GraphManager.updateGraphNickname: propagate PD persistence failure to caller with in-memory rollback, preventing silent state inconsistency
- GraphsAPI: remove duplicate private isPrefix() that shadowed the protected version in API base class, causing compilation failure - GraphsAPI: fix NPE in manage() when 'update' value is null by guarding value.getClass() in both action and update validation - GraphsAPI/GraphSpaceAPI: replace org.apache.commons.lang.StringUtils with commons-lang3 and remove org.apache.logging.log4j.util.Strings import (replaced with StringUtils equivalents) to fix potential classpath issues - GraphManager.updateGraphNickname: restore old nickname (not null) on PD persistence failure to avoid a third inconsistent state - StandardAuthManagerV2.setDefaultGraph: check existBelong() before createBelong() to make the operation idempotent; repeated POST calls for the same user/graph no longer throw HugeException - StandardAuthManagerV2.createDefaultRole: same idempotent guard via existBelong() check before createBelong() - ManagerAPI.checkDefaultRole: use @PathParam graphspace instead of @QueryParam to match the mounted path contract
StandardAuthManager (standalone RocksDB) fully implements setDefaultGraph/ unsetDefaultGraph/getDefaultGraph via HugeGroup+HugeBelong mechanism and does not require PD. The non-PD early-return guards in setDefault, unsetDefault, getDefault and listProfile were silently discarding user preferences, making the API a no-op in standalone mode. Remove the isPDEnabled() short-circuits and route all default graph operations directly through authManager, which dispatches correctly to either StandardAuthManager or StandardAuthManagerV2 depending on the deployment mode.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 15 changed files in this pull request and generated 8 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @POST | ||
| @Timed | ||
| @Path("{name}/default") | ||
| @Produces(APPLICATION_JSON_WITH_CHARSET) | ||
| @RolesAllowed({"space_member", "$owner=$name"}) | ||
| public Map<String, Object> setDefault(@Context GraphManager manager, | ||
| @Parameter(description = "The graph space name") | ||
| @PathParam("graphspace") String graphSpace, | ||
| @Parameter(description = "The graph name") | ||
| @PathParam("name") String name) { | ||
| LOG.debug("Set default graph '{}' in graph space '{}'", name, graphSpace); | ||
| E.checkArgument(manager.graph(graphSpace, name) != null, | ||
| "Graph '%s/%s' does not exist", graphSpace, name); | ||
| String user = HugeGraphAuthProxy.username(); | ||
| AuthManager authManager = manager.authManager(); | ||
| authManager.setDefaultGraph(graphSpace, name, user); | ||
| Map<String, Date> defaults = authManager.getDefaultGraph(graphSpace, user); | ||
| return ImmutableMap.of("default_graph", defaults.keySet()); | ||
| } | ||
|
|
||
| @DELETE | ||
| @Timed | ||
| @Path("{name}/default") | ||
| @Produces(APPLICATION_JSON_WITH_CHARSET) | ||
| @RolesAllowed({"space_member", "$owner=$name"}) | ||
| public Map<String, Object> unsetDefault(@Context GraphManager manager, |
| // Check required parameters for creating graph | ||
| if (StringUtils.isEmpty(clone)) { | ||
| // Only check required parameters when creating new graph, not when cloning | ||
| E.checkArgument(configs != null, "Config parameters cannot be null"); | ||
| String[] requiredKeys = {"backend", "serializer", "store"}; | ||
| for (String key : requiredKeys) { | ||
| Object value = configs.get(key); | ||
| E.checkArgument(value instanceof String && !StringUtils.isEmpty((String) value), | ||
| "Required parameter '%s' is missing or empty", key); | ||
| // Always required by TinkerPop's GraphFactory.open() | ||
| configs.putIfAbsent("gremlin.graph", | ||
| "org.apache.hugegraph.HugeFactory"); | ||
| if (manager.isPDEnabled()) { | ||
| // Auto-fill HStore/PD mode defaults only when in distributed mode | ||
| configs.putIfAbsent("backend", "hstore"); | ||
| } else { | ||
| // Auto-fill standalone (RocksDB) mode defaults | ||
| configs.putIfAbsent("backend", "rocksdb"); | ||
| } |
| @GET | ||
| @Timed | ||
| @Path("profile") | ||
| @Produces(APPLICATION_JSON_WITH_CHARSET) | ||
| @RolesAllowed({"space_member", "$dynamic"}) | ||
| public Object listProfile(@Context GraphManager manager, | ||
| @Parameter(description = "The graph space name") | ||
| @PathParam("graphspace") String graphSpace, | ||
| @Parameter(description = "Filter graphs by name or nickname prefix") | ||
| @QueryParam("prefix") String prefix, | ||
| @Context SecurityContext sc) { |
| @POST | ||
| @Timed | ||
| @StatusFilter.Status(StatusFilter.Status.CREATED) | ||
| @Consumes(APPLICATION_JSON) | ||
| @Produces(APPLICATION_JSON_WITH_CHARSET) | ||
| @RolesAllowed({"space_member", "$dynamic"}) | ||
| public String create(@Context GraphManager manager, | ||
| @PathParam("graphspace") String graphSpace, | ||
| JsonSchemaTemplate jsonSchemaTemplate) { | ||
| LOG.debug("Create schema template {} for graph space: '{}'", | ||
| jsonSchemaTemplate, graphSpace); | ||
| ensurePdModeEnabled(manager); | ||
| E.checkArgumentNotNull(jsonSchemaTemplate, | ||
| "Request body cannot be null or empty"); | ||
| jsonSchemaTemplate.checkCreate(false); | ||
|
|
||
| E.checkArgument(manager.graphSpace(graphSpace) != null, | ||
| "The graph space '%s' is not exist", graphSpace); | ||
|
|
||
| SchemaTemplate template = jsonSchemaTemplate.toSchemaTemplate(); | ||
| template.create(new Date()); | ||
| template.creator(HugeGraphAuthProxy.username()); | ||
| manager.createSchemaTemplate(graphSpace, template); | ||
| return manager.serializer().writeSchemaTemplate(template); | ||
| } |
…nding - Fix SchemaTemplateAPI logger using wrong class (RestServer -> SchemaTemplateAPI) - Fix StandardAuthManager fabricated belong ID: replace string-constructed IDs with actual graph traversal (findBelongBinding) to correctly detect/delete existing HugeBelong edges, making setDefaultGraph/unsetDefaultGraph/ createDefaultRole idempotent - Fix HugeUser.initSchemaIfNeeded: add incremental schema upgrade so existing deployments get the user_nickname property key without full schema recreation
Purpose of the PR
Adapt API endpoints required by the Hubble 2.0 frontend to achieve feature parity with the internal version. The community edition of HugeGraph Server currently lacks several critical APIs that the Hubble frontend depends on (graph profile listing, default graph management, default role management, schema templates, etc.), preventing the Hubble frontend from properly using core graph and role management features. This PR adds the missing APIs to enable full integration between the Hubble 2.0 frontend and the community edition Server.
Link apache/hugegraph-toolchain#632
Main Changes
1. GraphsAPI — Graph Management Endpoint Enhancements (
GraphsAPI.java)GET /graphspaces/{graphspace}/graphs/profile— New graph profile listing endpoint with prefix filtering, default-graph-first sorting, and full config info (nickname, create_time, default status, etc.)GET /graphspaces/{graphspace}/graphs/{name}/default— Set a graph as defaultGET /graphspaces/{graphspace}/graphs/{name}/undefault— Unset a graph as defaultGET /graphspaces/{graphspace}/graphs/default— Get current user's default graph listPUT /graphspaces/{graphspace}/graphs/{name}— Graph management operations (currently supportsupdateaction for nickname updates)POST /graphspaces/{graphspace}/graphs(form-urlencoded) — Hubble frontend form-based graph creation compatibilitybackend=hstore,serializer=binary,store={name}) during graph creation, and map frontendschemafield toschema.init_template2. GraphSpaceAPI — Default Role Management (
GraphSpaceAPI.java)POST /graphspaces/{graphspace}/role— Create default role (supports SPACE/ANALYST/OBSERVER)GET /graphspaces/{graphspace}/role— Check if a user/group has a specified default roleDELETE /graphspaces/{graphspace}/role— Delete default rolelistProfileendpoint@JsonIgnoreProperties(ignoreUnknown = true)to tolerate unknown fields from the frontend3. ManagerAPI — Role Query (
ManagerAPI.java)GET /auth/manager/default— New endpoint for Hubble frontend to query if the current user has a specified default role4. SchemaTemplateAPI — Schema Template CRUD (New File)
GET /graphspaces/{graphspace}/schematemplates— List all schema templatesGET /graphspaces/{graphspace}/schematemplates/{name}— Get a specific templatePOST /graphspaces/{graphspace}/schematemplates— Create templatePUT /graphspaces/{graphspace}/schematemplates/{name}— Update templateDELETE /graphspaces/{graphspace}/schematemplates/{name}— Delete template5. Authentication & Authorization Layer (
AuthManager.java,StandardAuthManager.java,StandardAuthManagerV2.java,HugeGraphAuthProxy.java)AuthManagerinterface:setDefaultGraph/unsetDefaultGraph/getDefaultGraphandcreateDefaultRole/createSpaceDefaultRole/isDefaultRole/deleteDefaultRole, etc.StandardAuthManagerandStandardAuthManagerV2implement the above interfaces using existingHugeGroup/HugeBelong/HugeRolemetadata mechanismsHugeGraphAuthProxy.AuthManagerProxyadds corresponding delegate methods to properly forward calls to the underlying authManager6. Utilities (
ConfigUtil.java,GraphManager.java)ConfigUtil.writeConfigToString()— New utility method to serialize graph configuration to string (used by the listProfile endpoint)GraphManager— Added graph management helper methodsVerifying these changes
Does this PR potentially affect the following parts?
Documentation Status
Doc - TODODoc - DoneDoc - No Need