Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 47 additions & 3 deletions .github/workflows/helm-chart-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,27 @@ jobs:
helm template ci helm/hugegraph \
--set "${component}.probes.readiness.successThreshold=2" > /dev/null
done
# These deployment inputs must serialize unchanged through the properties reader.
must_fail --set-string server.serviceId=café
must_fail --set-string server.serviceId=😀
must_fail --set-string "$(printf 'server.serviceId=ops\tteam')"
# Match the Server API graph-space names, including its 48-character limit.
must_fail --set-string server.graphSpace=TeamA
must_fail --set-string server.graphSpace=team-a
must_fail --set-string server.graphSpace=1team
graph_space_max=$(printf 'a%.0s' {1..48})
helm template ci helm/hugegraph --set-string "server.graphSpace=${graph_space_max}" > /dev/null
must_fail --set-string "server.graphSpace=${graph_space_max}a"
# The hook queries an explicit, fully qualified existing graph binding.
must_fail --set-string server.testGraphAlias=
must_fail --set-string server.testGraphAlias=team_a
must_fail --set-string server.testGraphAlias=team_a-demo-name
must_fail --set-string "server.testGraphAlias=team_a-demo'"
graph_name_max=$(printf 'g%.0s' {1..48})
helm template ci helm/hugegraph \
--set-string "server.testGraphAlias=${graph_space_max}-${graph_name_max}" > /dev/null
must_fail --set-string "server.testGraphAlias=${graph_space_max}a-demo"
must_fail --set-string "server.testGraphAlias=team_a-${graph_name_max}g"
must_fail --set pd.replicas=100
must_fail --set pd.pdb.minAvailable=3
must_fail --set server.hpa.enabled=true
Expand Down Expand Up @@ -334,7 +355,7 @@ jobs:
printf '#!/bin/sh\nfor ip in ${STUB_IPS}; do printf "%%s STREAM host\\n" "$ip"; done\n' > /tmp/stub/getent
chmod +x /tmp/stub/getent
cat > /tmp/fixture.py <<'PY'
import http.server, os, sys, threading, time
import http.server, json, os, sys, threading, time
calls = {}
class H(http.server.BaseHTTPRequestHandler):
def _send(self, code, body):
Expand All @@ -346,7 +367,9 @@ jobs:
def do_GET(self):
self._send(200, b'{"versions":{}}')
def do_POST(self):
self.rfile.read(int(self.headers.get('Content-Length', 0)))
body = json.loads(self.rfile.read(int(self.headers.get('Content-Length', 0))))
expected_alias = ('team_a-demo' if os.path.exists('/tmp/selected-graph')
else 'DEFAULT-hugegraph')
ip = self.server.server_address[0]
# broken-<ip>: every Gremlin call fails. flip-odd-<ip> /
# flip-even-<ip>: the odd or even calls fail, counted from
Expand All @@ -356,6 +379,7 @@ jobs:
even = os.path.exists('/tmp/flip-even-' + ip)
n = calls[ip] = (calls.get(ip, 0) + 1) if (odd or even) else 0
broken = (os.path.exists('/tmp/broken-' + ip)
or body.get('aliases', {}).get('graph') != expected_alias
or (odd and n % 2 == 1)
or (even and n % 2 == 0))
if broken:
Expand All @@ -373,7 +397,7 @@ jobs:
sleep 1
run_hook() {
PATH="/tmp/stub:${PATH}" PASSWORD=ci-password HG_TEST_DEADLINE_S=8 \
STUB_IPS="$1" sh /tmp/hook.sh
STUB_IPS="$1" sh "${2:-/tmp/hook.sh}"
}
# Healthy: three Pods, all Gremlin-bound.
run_hook "127.0.0.1 127.0.0.2 127.0.0.3"
Expand Down Expand Up @@ -408,6 +432,26 @@ jobs:
exit 1
fi
run_hook "127.0.0.1 127.0.0.2 127.0.0.3 ::1 ::2 ::3"
# A non-default graph space must query the explicitly selected graph.
# A missing alias still fails rather than weakening the per-Pod sweep.
touch /tmp/selected-graph
for alias in team_a-demo team_a-missing; do
helm template ci helm/hugegraph -s templates/tests/test-connection.yaml \
--set-string server.graphSpace=team_a \
--set-string "server.testGraphAlias=${alias}" > "/tmp/hook-${alias}.yaml"
done
python3 - <<'PY'
import yaml
for alias in ('team_a-demo', 'team_a-missing'):
d = yaml.safe_load(open('/tmp/hook-' + alias + '.yaml'))
open('/tmp/hook-' + alias + '.sh', 'w').write(d['spec']['containers'][0]['command'][2])
PY
run_hook "127.0.0.1 127.0.0.2 127.0.0.3" /tmp/hook-team_a-demo.sh
if run_hook "127.0.0.1 127.0.0.2 127.0.0.3" /tmp/hook-team_a-missing.sh; then
echo "expected the hook to fail on a missing selected graph" >&2
exit 1
fi
rm /tmp/selected-graph
# The auth-off render must carry no credentials.
if helm template ci helm/hugegraph -s templates/tests/test-connection.yaml \
--set server.auth.enabled=false | grep -q 'Authorization'; then
Expand Down
33 changes: 33 additions & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,39 @@ Standalone stores RocksDB data at `/hugegraph-server/rocksdb-data`. The HStore t

`docker compose down` keeps named-volume data. `docker compose down -v` intentionally deletes it.

### Server service identity

Images built from current master accept `HG_SERVER_SERVICE_ID` and
`HG_SERVER_GRAPHSPACE`, which write `server.service_id` and `server.graphspace`
in `conf/rest-server.properties`. A property keeps its existing value when both
its `HG_SERVER_*` variable and its older name are unset or empty; the shipped
defaults are both `DEFAULT`.
The older `SERVICE_ID` and `GRAPH_SPACE` variables emitted by the Kubernetes driver
are also accepted. A non-empty `HG_SERVER_*` value takes precedence over its older name.
These container identity inputs accept printable ASCII (bytes 0x20–0x7E);
spaces and backslashes in service IDs round-trip unchanged. Non-ASCII and control
characters are rejected before the entrypoint writes configuration, because the
Server reads properties using ISO-8859-1. This is the container input's
serialization limit; it does not change the core service-name contract.
`HG_SERVER_GRAPHSPACE` and a migrated `GRAPH_SPACE` must also match the Server
API graph-space rule: `DEFAULT`, or up to 48 lowercase letters, digits and
underscores, starting with a letter. Invalid environment values are rejected
before writing configuration; existing mounted properties are preserved.

For a PD-backed Server, add the variables to the Server container's environment,
for example `HG_SERVER_SERVICE_ID=analytics` and `HG_SERVER_GRAPHSPACE=team_a`.
Create `team_a` in the existing cluster before starting the assigned Server;
these settings do not create graph spaces. The Compose files do not pass these
variables through from the host, so add explicit `environment:` entries to a
local Compose override. The Helm chart exposes the same settings as
[`server.serviceId` and `server.graphSpace`](../helm/hugegraph/README.md#server-service-identity).

This selects the Server's PD service metadata. It does not change
`server.path_graphspace` for REST paths that omit `/graphspaces/<name>`, or
configure Hubble's chosen service. Actual non-default service discovery,
authentication and graph-space isolation still require verification with a
running Linux cluster.

### Server logs and crash files

The Server container owns its foreground launcher and returns the JVM's exit status after startup,
Expand Down
56 changes: 52 additions & 4 deletions helm/hugegraph/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,10 @@ or recovery. Hubble health probes and the Server hook do not verify UI access.

Besides the Service checks, the test resolves a headless Server Service and
requires an authenticated, graph-bound Gremlin answer from every Server Pod
(at least the replica floor), because a Server that started during a PD roll
(at least the replica floor). `server.testGraphAlias` selects that binding and
defaults to `DEFAULT-hugegraph`. Set it to an existing target graph's full alias,
such as `team_a-demo`, to check that graph instead. The hook does not create a
graph or infer an alias from `server.graphSpace`. A Server that started during a PD roll
can pass readiness and serve REST while every Gremlin call on it fails (see
Troubleshooting, "Could not rebind"). Transient failures are retried for
150 seconds before the test fails and names the failing Pod IPs. Large HPA
Expand Down Expand Up @@ -567,6 +570,8 @@ default values.
| `server.port` | Server REST port, container port, and Service port | `8080` |
| `server.readinessPath` | Path the Server readinessProbe hits. Set `/readiness` once the Server image serves it (apache/hugegraph#3212); it answers 503 while the Server cannot serve graph traffic. Startup and liveness stay on `/versions` | `/versions` |
| `server.backend` | Storage backend | `hstore` |
| `server.serviceId` | Non-default PD service name; `DEFAULT` preserves the image config | `DEFAULT` |
| `server.graphSpace` | Existing non-default graph space for the PD service; `DEFAULT` preserves the image config | `DEFAULT` |
| `server.resources` | Server resources. `requests.cpu` is required when HPA is enabled | `{}` |
| `server.podSecurityContext` | Pod-level securityContext, rendered only when set | `{}` |
| `server.securityContext` | Container-level securityContext. Hardened by default; `runAsNonRoot` is not set because the published images run as root | `allowPrivilegeEscalation: false`, `capabilities.drop: [ALL]`, `seccompProfile: RuntimeDefault` |
Expand Down Expand Up @@ -615,13 +620,50 @@ default values.
| `server.probes.startup.failureThreshold` | Raised automatically so the guaranteed budget, `(failureThreshold - 1) * periodSeconds` (the first probe can fail immediately), is at least 450s | `91` |
| `server.probes.startup.periodSeconds` | Startup probe interval | `5` |
| `server.probes.*` | Same optional probe keys as PD | see `values.yaml` |
| `server.testGraphAlias` | Existing Gremlin graph binding checked on every Server Pod by `helm test` | `DEFAULT-hugegraph` |

When `server.hpa.enabled` is `true` the Deployment omits `spec.replicas`, so a
Helm upgrade does not overwrite the autoscaler's live replica count. Enabling
utilization-based HPA requires a strictly positive
`server.resources.requests.cpu`.


### Server service identity

`server.serviceId` and `server.graphSpace` select the service metadata that
Servers use with PD. Both default to `DEFAULT`, which leaves the image's
`server.service_id` and `server.graphspace` untouched. A non-default value
injects `HG_SERVER_SERVICE_ID` or `HG_SERVER_GRAPHSPACE` for the image entrypoint.
`server.serviceId` accepts printable ASCII (bytes 0x20–0x7E), including spaces
and backslashes. Dollar signs are escaped in the manifest so Kubernetes passes
the service ID literally, including `$(POD_IP)` and `$$`. The chart and entrypoint
reject non-ASCII and control characters
to prevent the Server's ISO-8859-1 properties reader from changing the value;
this is a deployment input serialization limit, not a change to core service names:

```bash
helm upgrade --install hg ./helm/hugegraph \
--set-string server.serviceId=analytics \
--set-string server.graphSpace=team_a
```

Use images built with these entrypoint mappings. Create the non-default graph
space in the existing cluster before deploying Servers assigned to it; the chart
does not create it. A graph-space name is `DEFAULT` or up to 48 lowercase letters,
digits and underscores, starting with a letter, as required by the Server API. All replicas in this Server Deployment use the same identity.
Changing the graph space changes the Server's assigned space; it does not move
existing graphs or change `server.path_graphspace`. Use explicit
`/graphspaces/team_a/...` REST paths, and select the matching space and service in
Hubble. Rendering and entrypoint checks verify configuration only; non-default
PD discovery, authentication and isolation require a running Linux cluster.

To validate a graph in the selected space, create the graph there and set
`server.testGraphAlias` to its actual Gremlin binding, for example
`--set-string server.testGraphAlias=team_a-demo`. Creating `team_a` does not
create a `hugegraph` graph. The default hook checks the local `DEFAULT-hugegraph`
binding, which may exist independently of the Server's assigned PD graph space;
its success alone does not validate graphs in `team_a`.

### Reaching Hubble (pick one path)

Most people should stop at **1**. Use **2** only if Hubble must run outside
Expand Down Expand Up @@ -765,7 +807,8 @@ before anything reaches the cluster:
the whole membership); disable the PD PDB or use an odd replica count.
- `extraEnv` must not set chart-managed variable names (for example
`HG_SERVER_INIT_STORE_ENABLED` or the PD/Store identity and topology
variables): entries render after the chart-owned variables and the last
variables, including the legacy Server names `SERVICE_ID` and `GRAPH_SPACE`):
entries render after the chart-owned variables and the last
duplicate wins, so an override would silently bypass a validated contract.
`JAVA_OPTS` and `JAVA_OPTIONS` are reserved for the same reason: the
component start scripts skip automatic heap sizing and drop the chart's
Expand Down Expand Up @@ -1060,8 +1103,13 @@ curl -s --compressed -u "admin:${PASSWORD}" -H 'Content-Type: application/json'
-d '{"gremlin":"graph.traversal().V().limit(1).count()","aliases":{"graph":"DEFAULT-hugegraph"}}'
```

A healthy Pod answers with `result.data`; delete a Pod that answers
`Could not rebind`, and its replacement binds normally once PD is stable.
A healthy Pod answers with `result.data`. This example uses the default
`server.testGraphAlias`; replace `DEFAULT-hugegraph` with your configured alias.
If a Pod answers `Could not rebind`, first confirm that the requested graph
exists and the alias matches its binding. For a missing binding caused by the
PD startup failure above, delete the affected Pod once PD is stable so its
replacement can bind the graph. Restarting a Pod cannot create a missing graph
or correct the requested alias.
`helm test` runs this same graph-bound query against every Server Pod
through the headless Service, so a stuck replica now fails the release
check instead of hiding behind the load balancer. The measurements behind
Expand Down
2 changes: 1 addition & 1 deletion helm/hugegraph/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,7 @@ start-hugegraph-pd.sh, start-hugegraph-store.sh, and hugegraph-server.sh).
{{- $reservedEnv := dict
"pd" (list "HG_PD_GRPC_HOST" "HG_PD_GRPC_PORT" "HG_PD_REST_PORT" "HG_PD_RAFT_ADDRESS" "HG_PD_RAFT_PEERS_LIST" "HG_PD_INITIAL_STORE_LIST" "HG_PD_INITIAL_STORE_COUNT" "HG_PD_DATA_PATH" "HG_PD_AUTH_SECRET_KEY" "JAVA_OPTS" "JAVA_OPTIONS")
"store" (list "HG_STORE_PD_ADDRESS" "HG_STORE_GRPC_HOST" "HG_STORE_GRPC_PORT" "HG_STORE_REST_PORT" "HG_STORE_RAFT_ADDRESS" "HG_STORE_DATA_PATH" "JAVA_OPTS" "JAVA_OPTIONS")
"server" (list "POD_IP" "HG_SERVER_BACKEND" "HG_SERVER_PD_PEERS" "HG_SERVER_PD_REST_ENDPOINT" "STORE_REST" "HG_SERVER_INIT_STORE_ENABLED" "HG_SERVER_URLS_TO_PD" "HG_SERVER_STARTUP_TIMEOUT_S" "PD_AUTH_PASSWORD" "PASSWORD" "HG_SERVER_AUTH_TOKEN_SECRET" "JAVA_OPTS" "JAVA_OPTIONS")
"server" (list "POD_IP" "HG_SERVER_SERVICE_ID" "HG_SERVER_GRAPHSPACE" "SERVICE_ID" "GRAPH_SPACE" "HG_SERVER_BACKEND" "HG_SERVER_PD_PEERS" "HG_SERVER_PD_REST_ENDPOINT" "STORE_REST" "HG_SERVER_INIT_STORE_ENABLED" "HG_SERVER_URLS_TO_PD" "HG_SERVER_STARTUP_TIMEOUT_S" "PD_AUTH_PASSWORD" "PASSWORD" "HG_SERVER_AUTH_TOKEN_SECRET" "JAVA_OPTS" "JAVA_OPTIONS")
"hubble" (list "HG_HUBBLE_PD_PEERS" "HG_HUBBLE_PD_SERVER" "HG_HUBBLE_PD_PASSWORD" "HG_HUBBLE_STORE_TARGETS" "HG_HUBBLE_SERVER_URL" "SPRING_DATASOURCE_URL") -}}
{{- range $component, $reserved := $reservedEnv -}}
{{- $componentValues := get $.Values $component | default dict -}}
Expand Down
8 changes: 8 additions & 0 deletions helm/hugegraph/templates/server-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,14 @@ spec:
fieldPath: status.podIP
- name: HG_SERVER_BACKEND
value: {{ .Values.server.backend | quote }}
{{- if ne (.Values.server.serviceId | default "DEFAULT") "DEFAULT" }}
- name: HG_SERVER_SERVICE_ID
value: {{ .Values.server.serviceId | replace "$" "$$" | quote }}
{{- end }}
{{- if ne (.Values.server.graphSpace | default "DEFAULT") "DEFAULT" }}
- name: HG_SERVER_GRAPHSPACE
value: {{ .Values.server.graphSpace | quote }}
Comment thread
imbajin marked this conversation as resolved.
{{- end }}
- name: HG_SERVER_PD_PEERS
value: {{ include "hugegraph.pd.grpcPeersList" . | quote }}
- name: HG_SERVER_PD_REST_ENDPOINT
Expand Down
2 changes: 1 addition & 1 deletion helm/hugegraph/templates/tests/test-connection.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ spec:
PORT="{{ .Values.server.port }}"
FLOOR="{{ include "hugegraph.server.replicaFloor" . }}"
DEADLINE_S="${HG_TEST_DEADLINE_S:-150}"
BODY='{"gremlin":"graph.traversal().V().limit(1).count()","aliases":{"graph":"DEFAULT-hugegraph"}}'
BODY={{ dict "gremlin" "graph.traversal().V().limit(1).count()" "aliases" (dict "graph" (.Values.server.testGraphAlias | default "DEFAULT-hugegraph")) | toJson | squote }}
{{- if .Values.server.auth.enabled }}
AUTH_HEADER=$(printf 'admin:%s' "${PASSWORD}" | base64 | tr -d '\r\n')
request() {
Expand Down
72 changes: 72 additions & 0 deletions helm/hugegraph/tests/server_discovery_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,75 @@ tests:
asserts:
- failedTemplate:
errorPattern: "POD_IP"

- it: preserves the image service identity by default
asserts:
- notExists:
path: spec.template.spec.containers[0].env[?(@.name=="HG_SERVER_SERVICE_ID")]
- notExists:
path: spec.template.spec.containers[0].env[?(@.name=="HG_SERVER_GRAPHSPACE")]

- it: selects a non-default service identity
set:
server.serviceId: analytics
server.graphSpace: team_a
asserts:
- equal:
path: spec.template.spec.containers[0].env[?(@.name=="HG_SERVER_SERVICE_ID")].value
value: analytics
- equal:
path: spec.template.spec.containers[0].env[?(@.name=="HG_SERVER_GRAPHSPACE")].value
value: team_a

- it: refuses an extraEnv that shadows the service ID
set:
server.extraEnv:
- name: HG_SERVER_SERVICE_ID
value: shadow
asserts:
- failedTemplate:
errorPattern: "HG_SERVER_SERVICE_ID"

- it: refuses an extraEnv that shadows the graph space
set:
server.extraEnv:
- name: HG_SERVER_GRAPHSPACE
value: shadow
asserts:
- failedTemplate:
errorPattern: "HG_SERVER_GRAPHSPACE"

- it: retains a service name containing spaces
set:
server.serviceId: ops team
server.graphSpace: team_a
asserts:
- equal:
path: spec.template.spec.containers[0].env[?(@.name=="HG_SERVER_SERVICE_ID")].value
value: ops team

- it: refuses an extraEnv that shadows the legacy service ID
set:
server.extraEnv:
- name: SERVICE_ID
value: shadow
asserts:
- failedTemplate:
errorPattern: "SERVICE_ID"

- it: refuses an extraEnv that shadows the legacy graph space
set:
server.extraEnv:
- name: GRAPH_SPACE
value: shadow
asserts:
- failedTemplate:
errorPattern: "GRAPH_SPACE"

- it: escapes all dollars so Kubernetes retains a literal service ID
set:
server.serviceId: 'x-$(POD_IP)-$$(POD_IP)-$(UNKNOWN)-$name-$'
asserts:
- equal:
path: spec.template.spec.containers[0].env[?(@.name=="HG_SERVER_SERVICE_ID")].value
value: 'x-$$(POD_IP)-$$$$(POD_IP)-$$(UNKNOWN)-$$name-$$'
Loading
Loading