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
161 changes: 158 additions & 3 deletions .github/workflows/server-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,18 +95,20 @@ jobs:
rendered="$(mktemp)"

if env -u HUGEGRAPH_ADMIN_PASSWORD \
docker compose -f "$file" config -q >/dev/null 2>&1; then
docker compose --env-file /dev/null -f "$file" config -q >/dev/null 2>&1; then
echo "$file accepted an unset admin password" >&2
return 1
fi
if HUGEGRAPH_ADMIN_PASSWORD= \
docker compose -f "$file" config -q >/dev/null 2>&1; then
docker compose --env-file /dev/null -f "$file" config -q >/dev/null 2>&1; then
echo "$file accepted an empty admin password" >&2
return 1
fi

HUGEGRAPH_ADMIN_PASSWORD=ci-test-password \
docker compose -f "$file" config --format json > "$rendered"
env -u HUBBLE_IMAGE -u HUBBLE_PULL_POLICY -u HUBBLE_PUBLISH_HOST \
-u HUGEGRAPH_SERVER_IMAGE -u HUGEGRAPH_SERVER_PULL_POLICY \
docker compose --env-file /dev/null -f "$file" config --format json > "$rendered"
jq -e \
--arg server_policy "$server_policy" \
--arg hubble_policy "$hubble_policy" '
Expand Down Expand Up @@ -138,6 +140,159 @@ jobs:
check_compose docker/docker-compose.yml always always
check_compose docker/docker-compose.dev.yml build missing

check_cluster_compose() {
local cluster="docker/docker-compose-3pd-3store-3server.yml"
local addon="docker/docker-compose-hubble.yml"
local rendered
rendered="$(mktemp)"

# RETURN only: an EXIT trap would fire after this function's
# `local rendered` has gone out of scope, which `set -u` turns
# into an "unbound variable" error. A hard errexit abort can
# therefore still leak one temp file, which is acceptable on an
# ephemeral runner.
trap 'rm -f "$rendered"' RETURN

# --env-file /dev/null on every invocation: Compose otherwise reads
# docker/.env automatically. Without it the add-on render below —
# which deliberately unsets overrides to assert their defaults —
# would read whatever HUGEGRAPH_NETWORK/HUGEGRAPH_VERSION that
# .env holds. Pinning an empty env file makes both renders depend
# only on what each invocation sets explicitly.

# The add-on alone must define Hubble and nothing else, join the
# shared external network with its default name, and need no
# credentials or overrides.
env -u HUGEGRAPH_ADMIN_PASSWORD -u HUGEGRAPH_AUTH_TOKEN_SECRET \
-u HUGEGRAPH_NETWORK -u HUGEGRAPH_VERSION \
-u HUBBLE_IMAGE -u HUBBLE_PULL_POLICY -u HUBBLE_PUBLISH_HOST \
-u HUBBLE_DB_VOLUME -u HUBBLE_UPLOAD_VOLUME \
docker compose --env-file /dev/null -f "$addon" config --format json > "$rendered"
jq -e '
(.services | keys) == ["hubble"] and
.networks."hg-net".external == true and
.networks."hg-net".name == "hugegraph-net" and
(.services.hubble.networks | has("hg-net")) and
(.services.hubble | has("depends_on") | not) and
.volumes."hg-hubble-db".name == "hugegraph-hubble-db" and
.volumes."hg-hubble-upload-files".name ==
"hugegraph-hubble-upload-files" and
.services.hubble.environment.SPRING_DATASOURCE_URL ==
"jdbc:h2:file:./db/hubble;DB_CLOSE_ON_EXIT=FALSE" and
any(.services.hubble.volumes[];
.target == "/hubble/conf/hugegraph-hubble.properties" and
(.source | endswith("hugegraph-hubble-3x3.properties")))
and any(.services.hubble.volumes[];
.source == "hg-hubble-db" and
.target == "/hubble/db")
and any(.services.hubble.volumes[];
.source == "hg-hubble-upload-files" and
.target == "/hubble/upload-files")
' "$rendered" >/dev/null

# The cluster's own default network name must match the add-on's,
# or the attach flow and the cluster land on different networks.
# The combined render below pins an override, so it cannot catch a
# drifting default.
env -u HUGEGRAPH_NETWORK -u HUBBLE_IMAGE -u HUBBLE_PULL_POLICY \
-u HUBBLE_PUBLISH_HOST -u HUGEGRAPH_ADMIN_PASSWORD \
-u HUGEGRAPH_AUTH_TOKEN_SECRET \
docker compose --env-file /dev/null -f "$cluster" \
config --format json > "$rendered"
jq -e '.networks."hg-net".name == "hugegraph-net"' \
"$rendered" >/dev/null

# The combined render carries PD-registration on every server
# replica, keeps Server anonymous (no PASSWORD), and keeps Hubble
# on loopback. Rendered with non-default HUGEGRAPH_NETWORK /
# HUGEGRAPH_VERSION so CI fails if any file stops honoring the
# overrides (the add-on render above covers the defaults).
HUGEGRAPH_NETWORK=ci-test-net \
HUGEGRAPH_VERSION=ci-test-tag \
env -u HUBBLE_IMAGE -u HUBBLE_PULL_POLICY -u HUBBLE_PUBLISH_HOST \
-u HUBBLE_DB_VOLUME -u HUBBLE_UPLOAD_VOLUME \
-u HUGEGRAPH_ADMIN_PASSWORD -u HUGEGRAPH_AUTH_TOKEN_SECRET \
docker compose --env-file /dev/null -f "$cluster" -f "$addon" \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ The new cluster contract remains render-only. Evidence: the combined check stops at docker compose config plus jq/grep and never creates the external network, starts the services, probes Hubble /about, verifies PD registration, or checks authenticated graph access. Add a Docker-capable smoke job for the combined and attach flows so entrypoint, DNS, registration, and auth failures cannot remain green.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Render‑only cannot catch runtime auth/DNS failures. For this PR I’ll keep and strengthen the compose contract assertions (including the in‑scope ones above). A full combined or attached Docker smoke job is useful but adds heavier CI overhead; I’d prefer a follow‑up rather than blocking the add‑on here.

config --format json > "$rendered"
jq -e '
. as $root |
.name == "hugegraph-3x3" and
(.services | keys | length) == 10 and
.networks."hg-net".external == true and
.networks."hg-net".name == "ci-test-net" and
all(.services[]; .networks | has("hg-net")) and
.services.pd0.image == "hugegraph/pd:ci-test-tag" and
.services.store0.image == "hugegraph/store:ci-test-tag" and
.services.server0.image == "hugegraph/server:ci-test-tag" and
.services.hubble.image == "hugegraph/hubble:ci-test-tag" and
all(["server0", "server1", "server2"][];
$root.services[.].environment.HG_SERVER_USE_PD == "true" and
$root.services[.].environment.HG_SERVER_CLUSTER == "hg" and
$root.services[.].environment.HG_SERVER_INIT_STORE_ENABLED ==
"false" and
($root.services[.].environment.PASSWORD == null) and
($root.services[.].environment.HG_SERVER_AUTH_TOKEN_SECRET ==
null)) and
.services.server0.environment.HG_SERVER_REST_URL ==
"http://server0:8080" and
.services.server1.environment.HG_SERVER_REST_URL ==
"http://server1:8080" and
.services.server2.environment.HG_SERVER_REST_URL ==
"http://server2:8080" and
(.services.hubble | has("depends_on") | not) and
.services.hubble.pull_policy == "missing" and
.volumes."hg-hubble-db".name == "hugegraph-hubble-db" and
.volumes."hg-hubble-upload-files".name ==
"hugegraph-hubble-upload-files" and
.services.hubble.environment.SPRING_DATASOURCE_URL ==
"jdbc:h2:file:./db/hubble;DB_CLOSE_ON_EXIT=FALSE" and
(.services.hubble.healthcheck.test[1] |
contains("http://127.0.0.1:8088/about") and
contains("\"status\":200") and
contains("\"name\":\"hugegraph-hubble\"")) and
any(.services.hubble.ports[];
.target == 8088 and .published == "8088" and
.host_ip == "127.0.0.1")
' "$rendered" >/dev/null

# Hubble's properties file is mounted, not rendered, so Compose
# validation alone cannot catch it drifting from the services it
# describes. Tie the two together: renaming a service, changing a
# container hostname, or moving a REST port must be reflected in
# both places or CI fails here. Values are derived from the
# rendered model (hostnames and ports included) so the assertions
# cannot silently agree with a stale file.
local props="docker/hugegraph-hubble-3x3.properties"
local pd_peers store_targets cluster_name pd_rest
assert_props() { # assert_props <exact-line> <what-it-must-match>
grep -Fqx "$1" "$props" ||
{ echo "$props: expected line '$1' ($2)" >&2; return 1; }
}
cluster_name="$(jq -r '.services.server0.environment.HG_SERVER_CLUSTER' \
"$rendered")"
pd_peers="$(jq -r '.services.server0.environment.HG_SERVER_PD_PEERS' \
"$rendered")"
store_targets="[$(jq -r '[.services | to_entries[]
| select(.key | startswith("store"))
| "http://" + .value.hostname + ":"
+ (.value.environment.HG_STORE_REST_PORT)]
| sort | join(",")' "$rendered")]"
pd_rest="$(jq -r --arg h "$(printf '%s' "${pd_peers}" | cut -d, -f1 | cut -d: -f1)" \
'.services | to_entries[]
| select(.value.hostname == $h)
| .value.hostname + ":" + .value.environment.HG_PD_REST_PORT' \
"$rendered")"
assert_props "cluster=${cluster_name}" "matches HG_SERVER_CLUSTER"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ The Hubble properties consistency check can pass while PD mode is disabled. Evidence: assert_props validates cluster, pd.peers, pd.server, and Store targets, but never asserts pd.enabled=true; the current server.direct_url is only meaningful when PD mode is false. Add the explicit mode assertion and a runtime smoke check so Hubble cannot silently fall back to one direct Server.

@bitflicker64 bitflicker64 Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ll add an explicit assert_props "pd.enabled=true" so the Hubble properties check cannot pass with PD mode disabled.

assert_props "pd.enabled=true" "keeps Hubble in PD mode"
assert_props "auth.enabled=false" "matches the anonymous cluster"
assert_props "pd.peers=${pd_peers}" "matches HG_SERVER_PD_PEERS"
assert_props "pd.server=${pd_rest}" "names a real PD REST endpoint"
assert_props "operations.store.allowed_targets=${store_targets}" \
"lists every Store REST endpoint"
}

check_cluster_compose

- name: Run check_port unit tests
if: ${{ env.BACKEND == 'rocksdb' }}
run: |
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ For advanced Docker configurations, see:
* [Docker README](./docker/README.md)
* [Server Docker README](hugegraph-server/hugegraph-dist/docker/README.md)

> **Docker Desktop (Mac/Windows)**: The 3-node distributed cluster (`docker/docker-compose-3pd-3store-3server.yml`) uses Docker bridge networking and works on all platforms including Docker Desktop. Allocate at least 12 GB memory to Docker Desktop.
> **Docker Desktop (Mac/Windows)**: The 3-node distributed cluster (`docker/docker-compose-3pd-3store-3server.yml`) joins a pre-created external Docker network shared with the Hubble add-on (see the [Docker README](./docker/README.md) quickstart) and works on all platforms including Docker Desktop. Allocate at least 12 GB memory to Docker Desktop.

> **Note**: Docker images are convenience releases, not **official ASF distribution artifacts**. See [ASF Release Distribution Policy](https://infra.apache.org/release-distribution.html#dockerhub) for details.
>
Expand Down
Loading
Loading