Skip to content
Merged
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 @@ -164,6 +164,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 @@ -350,7 +371,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 @@ -362,7 +383,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 @@ -372,6 +395,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 @@ -389,7 +413,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 @@ -424,6 +448,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 hubble.enabled=false --set server.auth.enabled=false | grep -q 'Authorization'; then
Expand Down
43 changes: 43 additions & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,49 @@ 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

Server image builds containing this configuration mapping 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.
The full service ID, including `/` and `-`, is retained when service metadata is
reloaded or its graph space is cleared.
`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).

Use a chart and Server image from the same source version containing these
mappings. Older release images without them do not apply these environment
values; select the matching image build explicitly.
The Server's existing `SERVICE_ID` normalization (`GraphManager.serviceId()`) is
unchanged and can map different raw names to the same identifier. Select
`GRAPHSPACE` and the complete raw `SERVICE_NAME` in discovery queries;
identifier-only lookup does not provide a uniqueness or isolation guarantee.

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
42 changes: 42 additions & 0 deletions helm/hugegraph/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,46 @@ to use `emptyDir`. Component storage flags override that global setting.
PVC failures never cause an automatic fallback. Do not switch initialized
PD/Store data between persistent and temporary storage.

### Server service identity

`server.serviceId` and `server.graphSpace` select the Server's PD service metadata.
Both default to `DEFAULT`, which preserves the image configuration. Non-default
values inject `HG_SERVER_SERVICE_ID` and `HG_SERVER_GRAPHSPACE` for the image
entrypoint. Service IDs accept printable ASCII, including spaces and backslashes;
the full name, including `/` and `-`, survives metadata reload and graph-space
clearing. Dollar signs remain literal through Kubernetes environment expansion.
Graph spaces must be `DEFAULT` or up to 48 lowercase letters, digits and
underscores, starting with a letter. Create the target space before starting its
assigned Server; these settings do not create it.

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

Use a chart and Server image build from the same source version containing these
mappings. Older release images without them ignore these environment settings;
select a matching image build explicitly. The chart does not detect old images.
The Server's existing `SERVICE_ID` normalization (`GraphManager.serviceId()`) is
unchanged: select `GRAPHSPACE` and the complete raw `SERVICE_NAME` in discovery
queries. Identifier-only lookup has no uniqueness or isolation guarantee.

`server.testGraphAlias` selects an existing Gremlin binding for every Server Pod,
defaulting to `DEFAULT-hugegraph`. It does not create a graph or infer its binding
from `server.graphSpace`. Match the binding's case: `Demo.properties` can register
`DEFAULT-Demo`, while REST-created graph names are lowercase. A successful query
of the default local binding does not verify a graph in `team_a`. Before replacing
a Pod after `Could not rebind`, verify that the graph exists and the selected alias
matches its binding; restarting cannot create a missing graph or correct an alias.

These settings do not change the REST default path graph space or Hubble's
selected service. Use explicit `/graphspaces/team_a/...` paths and the matching
Hubble selection. Non-default discovery, authentication and graph-space isolation
still need verification with a running Linux cluster. See the
[container identity contract](../../docker/README.md#server-service-identity).

## Modes and entry points

`mode: easy` is the default. `mode: strict` changes Server/Hubble to ClusterIP
Expand Down Expand Up @@ -146,6 +186,8 @@ with `-f`; see [values.yaml](values.yaml) for all fields and their comments.
| `pd.storage.enabled` / `store.storage.enabled` / `hubble.persistence.enabled` | Explicit component storage override | `null` (inherits) |
| `*.storageClassName` | Cluster default when empty; `"-"` means no class | `""` |
| `pd.storage.size` / `store.storage.size` / `hubble.persistence.size` | Volume capacities | `10Gi` / `50Gi` / `1Gi` |
| `server.serviceId` / `server.graphSpace` | PD service name and existing graph space | `DEFAULT` / `DEFAULT` |
| `server.testGraphAlias` | Existing Gremlin binding checked by `helm test` | `DEFAULT-hugegraph` |
| `server.storageWaitTimeoutSeconds` | Storage-ready deadline | `300` |
| `networkPolicy.enabled` | Explicit policy override; null inherits mode | resolved `false` |

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 @@ -946,7 +946,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" "WAIT_STORAGE_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" "WAIT_STORAGE_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 @@ -259,6 +259,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
Comment thread
imbajin marked this conversation as resolved.
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 @@ -78,7 +78,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-$$'
31 changes: 31 additions & 0 deletions helm/hugegraph/tests/test_hook_gremlin_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,37 @@ tests:
path: spec.containers[0].command[2]
pattern: "FLOOR=\"3\""

- it: checks the explicitly selected graph in a non-default graph space
template: tests/test-connection.yaml
set:
server.graphSpace: team_a
server.testGraphAlias: team_a-demo
asserts:
- matchRegex:
path: spec.containers[0].command[2]
pattern: '"graph":"team_a-demo"'
- notMatchRegex:
path: spec.containers[0].command[2]
pattern: "DEFAULT-hugegraph"

- it: preserves the default binding when graphSpace changes without a selected graph
template: tests/test-connection.yaml
set:
server.graphSpace: team_a
asserts:
- matchRegex:
path: spec.containers[0].command[2]
pattern: '"graph":"DEFAULT-hugegraph"'

- it: preserves a static graph binding with an uppercase local file name
template: tests/test-connection.yaml
set:
server.testGraphAlias: DEFAULT-Demo
asserts:
- matchRegex:
path: spec.containers[0].command[2]
pattern: '"graph":"DEFAULT-Demo"'

- it: floors the per-Pod count at the HPA minimum when HPA is enabled
template: tests/test-connection.yaml
set:
Expand Down
17 changes: 17 additions & 0 deletions helm/hugegraph/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,23 @@
"probes"
],
"properties": {
"serviceId": {
"type": "string",
"minLength": 1,
"pattern": "^[ -~]+$",
Comment thread
imbajin marked this conversation as resolved.
"description": "Printable ASCII PD service name (bytes 0x20-0x7E), including spaces and backslashes. DEFAULT preserves the image config."
},
"graphSpace": {
"type": "string",
"minLength": 1,
"pattern": "^(DEFAULT|[a-z][a-z0-9_]{0,47})$",
"description": "Existing PD graph space: DEFAULT or up to 48 lowercase letters, digits and underscores, starting with a letter. DEFAULT preserves the image config."
},
"testGraphAlias": {
"type": "string",
"pattern": "^(DEFAULT|[a-z][a-z0-9_]{0,47})-[A-Za-z][A-Za-z0-9_]{0,47}$",
Comment thread
imbajin marked this conversation as resolved.
"description": "Existing Gremlin graph binding checked by helm test, as <graph-space>-<graph-name>. Choose the target graph explicitly; server.graphSpace does not create or infer this binding."
},
"replicas": {
"type": "integer",
"minimum": 1
Expand Down
Loading
Loading