imbajin commented on code in PR #3104: URL: https://github.com/apache/hugegraph/pull/3104#discussion_r3637852114
########## hugegraph-server/hugegraph-dist/src/assembly/travis/run-riscv64-smoke.sh: ########## @@ -0,0 +1,518 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# run-riscv64-smoke.sh — Disposable RISC-V (linux/riscv64) end-to-end smoke test. +# +# Builds the HugeGraph Server distribution on the contributor's native host +# (x86-64 / arm64), then runs it inside an emulated linux/riscv64 container to +# verify the RocksDB backend + Gremlin stack actually work on RISC-V via QEMU. +# +# This is a CORRECTNESS gate, not a performance benchmark. QEMU user-mode +# emulation is several times slower than native; expect the full run (build + +# init + REST + Gremlin + restart) to take many minutes the first time. +# +# It requires Docker with QEMU/binfmt_misc multi-platform support (Docker +# Desktop ships this; on Linux install it with: +# docker run --privileged --rm tonistiigi/binfmt --install all +# No host RISC-V packages are installed by this script. +# +# It removes ONLY what it creates: a uniquely-named container, volume, and +# locally-built image. The QEMU emulator registration is left untouched +# (remove it with: docker run --privileged --rm tonistiigi/binfmt --uninstall all). +# +# Usage: +# ./run-riscv64-smoke.sh [path-to-server-dist.tar.gz] +# ./run-riscv64-smoke.sh # builds the dist under hugegraph-server/ +# +set -euo pipefail + +ARCH=riscv64 +PLATFORM="linux/${ARCH}" +# Ubuntu 24.04 publishes openjdk-11-jre-headless for linux/riscv64 (glibc). +BASE_IMAGE="ubuntu:24.04" + +REPO_ROOT=$(cd "$(dirname "$0")/../../../../.." && pwd) + +# Use disk-backed temp: the extracted server (~1GB) blows out a small RAM tmpfs +# /tmp. Default to a work dir on the same (large) filesystem as the repo. +: "${TMPDIR:=$REPO_ROOT/.riscv64-smoke-tmp}" +mkdir -p "$TMPDIR" +export TMPDIR + +# Portable in-place sed: avoids GNU sed -i which breaks on macOS/BSD. +sedi() { + local expr="$1" file="$2" + local tmp; tmp=$(mktemp "${TMPDIR:-/tmp}/sedi.XXXXXX") + if sed "$expr" "$file" > "$tmp"; then + mv "$tmp" "$file" + else + rm -f "$tmp" + return 1 + fi +} + +# Unique, traceable names so cleanup removes only our own resources. +# Timestamp + random hex: collision-resistant, still human-readable for debugging. +STAMP=$(date +%Y%m%d%H%M%S)-$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' \n') +TAG="hugegraph-riscv64-smoke:${STAMP}" +# KEEP: don't auto-cleanup so a failed/finished run stays pokeable. +VOL="hugegraph-riscv64-data-${STAMP}" +CONTAINER="hugegraph-riscv64-smoke-${STAMP}" + +DIST_ARG="${1:-}" +DIST_TAR="" +SERVER_DIR="" +BUILD_CTX="" +ROCKS_BUILD="" + +GREEN=$'\033[0;32m'; RED=$'\033[0;31m'; NC=$'\033[0m' +pass() { echo "${GREEN}PASS${NC} $*"; } +fail() { echo "${RED}FAIL${NC} $*" >&2; } + +# Check if the container is still alive (returns 0 if running). +container_alive() { + $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER" +} + +# Run docker exec with container-liveness guard + exit-code logging. +# Usage: safe_exec exec <container> <cmd> [args...] +# DOCKER is split into words without eval; handles "docker" or "sudo docker". +# Returns the command's output on stdout; logs failures to stderr. +safe_exec() { + local rc + if ! container_alive; then + fail "container '$CONTAINER' is no longer running (before: $*)" + dump_health_diagnostics + return 1 + fi + set +e + local -a docker_parts=($DOCKER) + "${docker_parts[@]}" "$@" + rc=$? + set -e + if [[ $rc -ne 0 ]]; then + fail "command failed (exit $rc): $*" + dump_health_diagnostics + return 1 + fi +} + +# DOCKER: allow callers to override (e.g. "sudo docker"). Defaults to plain docker. +DOCKER=${DOCKER:-docker} + +# Dump container diagnostics to stderr. Defined early so the EXIT trap can +# call it even if the script fails before the main body. +dump_health_diagnostics() { + echo "----- health-check diagnostics ($CONTAINER) -----" >&2 + $DOCKER inspect --format \ + 'state={{.State.Status}} exit={{.State.ExitCode}} error={{.State.Error}}' \ + "$CONTAINER" >&2 || true + + if $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER"; then + echo "----- listening processes -----" >&2 + $DOCKER exec "$CONTAINER" sh -c \ + 'lsof -nP -iTCP:8080 -sTCP:LISTEN || true' >&2 || true + for endpoint in versions graphs; do + echo "----- GET /$endpoint -----" >&2 + $DOCKER exec "$CONTAINER" curl -i -sS --connect-timeout 5 --max-time 15 \ + "http://127.0.0.1:8080/$endpoint" >&2 || true + done + fi + + echo "----- recent container logs -----" >&2 + $DOCKER logs --tail 100 "$CONTAINER" >&2 || true + echo "----- end health-check diagnostics -----" >&2 +} + +_EXIT_CODE=0 +_exit_handler() { + local rc=$? + # If the script failed, log the exit code so the root cause isn't lost. + if [[ $rc -ne 0 ]]; then + echo "" >&2 + echo "==> Script failed with exit code $rc" >&2 + if container_alive; then + dump_health_diagnostics + else + echo "==> container '$CONTAINER' is not running (may have been OOM-killed or crashed)" >&2 + # Best-effort: check docker inspect for OOMKilled + dump any surviving logs + $DOCKER inspect --format \ + 'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}} Status={{.State.Status}}' \ + "$CONTAINER" >&2 2>/dev/null || true + $DOCKER logs --tail 80 "$CONTAINER" >&2 2>/dev/null || true + fi + fi + _cleanup +} + +_cleanup() { + # KEEP=1: leave everything standing for live poking. Still dump logs so a + # failure's root cause is visible; skip all teardown. + if [[ "${KEEP:-}" == "1" ]]; then + echo "==> KEEP=1 set — skipping cleanup. Resources left standing:" >&2 + echo " container: $CONTAINER" >&2 + echo " volume: $VOL" >&2 + echo " image: $TAG" >&2 + return 0 + fi + # Dump container logs first so a failure's root cause isn't destroyed by cleanup. + if $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER"; then + echo "----- container logs ($CONTAINER) -----" >&2 + $DOCKER logs --tail 60 "$CONTAINER" >&2 || true + echo "----- end logs -----" >&2 + # The startup wrapper hides the real error in the server log file; dump it + # from the stopped container (docker cp works on exited containers). + echo "----- hugegraph-server.log -----" >&2 + $DOCKER cp "$CONTAINER:/server/logs/hugegraph-server.log" - 2>/dev/null \ + | tar -xO 2>/dev/null | tail -n 80 >&2 || true + echo "----- end hugegraph-server.log -----" >&2 + fi + local ok=1 + $DOCKER rm -f "$CONTAINER" >/dev/null 2>&1 || ok=0 + # Only remove the volume we created. + $DOCKER volume rm "$VOL" >/dev/null 2>&1 || ok=0 + # Only remove the image we tagged; never prune the host's image store. + $DOCKER rmi "$TAG" >/dev/null 2>&1 || ok=0 + # Remove disk-backed temp work dirs (build context holds the ~1GB extract). + [[ -n "$BUILD_CTX" && -d "$BUILD_CTX" ]] && rm -rf "$BUILD_CTX" + [[ -n "$ROCKS_BUILD" && -d "$ROCKS_BUILD" ]] && rm -rf "$ROCKS_BUILD" + if [[ "$ok" == "1" ]]; then + pass "cleanup: container, volume, and image removed" + else + fail "cleanup: some resources may remain (container=$CONTAINER vol=$VOL image=$TAG)" Review Comment: ⚠️ A failed container, volume, or image deletion only sets `ok=0`; `fail` is an `echo` that returns successfully, and `_exit_handler` does not propagate `_cleanup`'s status. An equivalent EXIT-trap reproduction prints the cleanup failure but still exits 0, so callers can record a passing disposable smoke run while resources remain. Please make `_cleanup` return nonzero and have `_exit_handler` convert a successful main run into failure when cleanup fails, while preserving an existing main failure code. ########## hugegraph-server/hugegraph-dist/src/assembly/travis/run-riscv64-smoke.sh: ########## @@ -0,0 +1,518 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# run-riscv64-smoke.sh — Disposable RISC-V (linux/riscv64) end-to-end smoke test. +# +# Builds the HugeGraph Server distribution on the contributor's native host +# (x86-64 / arm64), then runs it inside an emulated linux/riscv64 container to +# verify the RocksDB backend + Gremlin stack actually work on RISC-V via QEMU. +# +# This is a CORRECTNESS gate, not a performance benchmark. QEMU user-mode +# emulation is several times slower than native; expect the full run (build + +# init + REST + Gremlin + restart) to take many minutes the first time. +# +# It requires Docker with QEMU/binfmt_misc multi-platform support (Docker +# Desktop ships this; on Linux install it with: +# docker run --privileged --rm tonistiigi/binfmt --install all +# No host RISC-V packages are installed by this script. +# +# It removes ONLY what it creates: a uniquely-named container, volume, and +# locally-built image. The QEMU emulator registration is left untouched +# (remove it with: docker run --privileged --rm tonistiigi/binfmt --uninstall all). +# +# Usage: +# ./run-riscv64-smoke.sh [path-to-server-dist.tar.gz] +# ./run-riscv64-smoke.sh # builds the dist under hugegraph-server/ +# +set -euo pipefail + +ARCH=riscv64 +PLATFORM="linux/${ARCH}" +# Ubuntu 24.04 publishes openjdk-11-jre-headless for linux/riscv64 (glibc). +BASE_IMAGE="ubuntu:24.04" + +REPO_ROOT=$(cd "$(dirname "$0")/../../../../.." && pwd) + +# Use disk-backed temp: the extracted server (~1GB) blows out a small RAM tmpfs +# /tmp. Default to a work dir on the same (large) filesystem as the repo. +: "${TMPDIR:=$REPO_ROOT/.riscv64-smoke-tmp}" +mkdir -p "$TMPDIR" +export TMPDIR + +# Portable in-place sed: avoids GNU sed -i which breaks on macOS/BSD. +sedi() { + local expr="$1" file="$2" + local tmp; tmp=$(mktemp "${TMPDIR:-/tmp}/sedi.XXXXXX") + if sed "$expr" "$file" > "$tmp"; then + mv "$tmp" "$file" + else + rm -f "$tmp" + return 1 + fi +} + +# Unique, traceable names so cleanup removes only our own resources. +# Timestamp + random hex: collision-resistant, still human-readable for debugging. +STAMP=$(date +%Y%m%d%H%M%S)-$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' \n') +TAG="hugegraph-riscv64-smoke:${STAMP}" +# KEEP: don't auto-cleanup so a failed/finished run stays pokeable. +VOL="hugegraph-riscv64-data-${STAMP}" +CONTAINER="hugegraph-riscv64-smoke-${STAMP}" + +DIST_ARG="${1:-}" +DIST_TAR="" +SERVER_DIR="" +BUILD_CTX="" +ROCKS_BUILD="" + +GREEN=$'\033[0;32m'; RED=$'\033[0;31m'; NC=$'\033[0m' +pass() { echo "${GREEN}PASS${NC} $*"; } +fail() { echo "${RED}FAIL${NC} $*" >&2; } + +# Check if the container is still alive (returns 0 if running). +container_alive() { + $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER" +} + +# Run docker exec with container-liveness guard + exit-code logging. +# Usage: safe_exec exec <container> <cmd> [args...] +# DOCKER is split into words without eval; handles "docker" or "sudo docker". +# Returns the command's output on stdout; logs failures to stderr. +safe_exec() { + local rc + if ! container_alive; then + fail "container '$CONTAINER' is no longer running (before: $*)" + dump_health_diagnostics + return 1 + fi + set +e + local -a docker_parts=($DOCKER) + "${docker_parts[@]}" "$@" + rc=$? + set -e + if [[ $rc -ne 0 ]]; then + fail "command failed (exit $rc): $*" + dump_health_diagnostics + return 1 + fi +} + +# DOCKER: allow callers to override (e.g. "sudo docker"). Defaults to plain docker. +DOCKER=${DOCKER:-docker} + +# Dump container diagnostics to stderr. Defined early so the EXIT trap can +# call it even if the script fails before the main body. +dump_health_diagnostics() { + echo "----- health-check diagnostics ($CONTAINER) -----" >&2 + $DOCKER inspect --format \ + 'state={{.State.Status}} exit={{.State.ExitCode}} error={{.State.Error}}' \ + "$CONTAINER" >&2 || true + + if $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER"; then + echo "----- listening processes -----" >&2 + $DOCKER exec "$CONTAINER" sh -c \ + 'lsof -nP -iTCP:8080 -sTCP:LISTEN || true' >&2 || true + for endpoint in versions graphs; do + echo "----- GET /$endpoint -----" >&2 + $DOCKER exec "$CONTAINER" curl -i -sS --connect-timeout 5 --max-time 15 \ + "http://127.0.0.1:8080/$endpoint" >&2 || true + done + fi + + echo "----- recent container logs -----" >&2 + $DOCKER logs --tail 100 "$CONTAINER" >&2 || true + echo "----- end health-check diagnostics -----" >&2 +} + +_EXIT_CODE=0 +_exit_handler() { + local rc=$? + # If the script failed, log the exit code so the root cause isn't lost. + if [[ $rc -ne 0 ]]; then + echo "" >&2 + echo "==> Script failed with exit code $rc" >&2 + if container_alive; then + dump_health_diagnostics + else + echo "==> container '$CONTAINER' is not running (may have been OOM-killed or crashed)" >&2 + # Best-effort: check docker inspect for OOMKilled + dump any surviving logs + $DOCKER inspect --format \ + 'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}} Status={{.State.Status}}' \ + "$CONTAINER" >&2 2>/dev/null || true + $DOCKER logs --tail 80 "$CONTAINER" >&2 2>/dev/null || true + fi + fi + _cleanup +} + +_cleanup() { + # KEEP=1: leave everything standing for live poking. Still dump logs so a + # failure's root cause is visible; skip all teardown. + if [[ "${KEEP:-}" == "1" ]]; then + echo "==> KEEP=1 set — skipping cleanup. Resources left standing:" >&2 + echo " container: $CONTAINER" >&2 + echo " volume: $VOL" >&2 + echo " image: $TAG" >&2 + return 0 + fi + # Dump container logs first so a failure's root cause isn't destroyed by cleanup. + if $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER"; then + echo "----- container logs ($CONTAINER) -----" >&2 + $DOCKER logs --tail 60 "$CONTAINER" >&2 || true + echo "----- end logs -----" >&2 + # The startup wrapper hides the real error in the server log file; dump it + # from the stopped container (docker cp works on exited containers). + echo "----- hugegraph-server.log -----" >&2 + $DOCKER cp "$CONTAINER:/server/logs/hugegraph-server.log" - 2>/dev/null \ + | tar -xO 2>/dev/null | tail -n 80 >&2 || true + echo "----- end hugegraph-server.log -----" >&2 + fi + local ok=1 + $DOCKER rm -f "$CONTAINER" >/dev/null 2>&1 || ok=0 + # Only remove the volume we created. + $DOCKER volume rm "$VOL" >/dev/null 2>&1 || ok=0 + # Only remove the image we tagged; never prune the host's image store. + $DOCKER rmi "$TAG" >/dev/null 2>&1 || ok=0 + # Remove disk-backed temp work dirs (build context holds the ~1GB extract). + [[ -n "$BUILD_CTX" && -d "$BUILD_CTX" ]] && rm -rf "$BUILD_CTX" + [[ -n "$ROCKS_BUILD" && -d "$ROCKS_BUILD" ]] && rm -rf "$ROCKS_BUILD" + if [[ "$ok" == "1" ]]; then + pass "cleanup: container, volume, and image removed" + else + fail "cleanup: some resources may remain (container=$CONTAINER vol=$VOL image=$TAG)" + fi +} +trap _exit_handler EXIT + +need() { command -v "$1" >/dev/null 2>&1 || { fail "missing required command: $1"; exit 1; }; } +need docker +# buildx is required: the legacy builder ignores --platform. +$DOCKER buildx version >/dev/null 2>&1 || { fail "docker buildx is required (pacman -S docker-buildx)"; exit 1; } + +# Register QEMU user-mode emulation for riscv64 if the Docker daemon can't +# already run it (otherwise the build/run fails with 'exec format error'). +# binfmt handlers do NOT survive reboots, so a fresh checkout needs this — +# makes the smoke test self-contained on any x86 host. Pinned tag: :latest +# 403s on Docker Hub. +# +# Probe through the Docker daemon, not the host /proc: Docker Desktop keeps +# binfmt inside its Linux VM; a remote context can have the opposite state. +if ! $DOCKER run --rm --platform "$PLATFORM" "$BASE_IMAGE" uname -m 2>/dev/null \ + | grep -q riscv64; then + echo "==> Registering QEMU riscv64 emulation (binfmt)" + $DOCKER run --privileged --rm tonistiigi/binfmt:qemu-v8.1.5 --install riscv64 >/dev/null +fi + +# 1. Resolve the Server distribution tarball (build on host if not supplied). +# Build from the ROOT pom (not hugegraph-server/): the root parent pom +# `hugegraph` and the root sibling `hugegraph-struct` must be installed +# into ~/.m2 first, or module resolution fails on the ${revision} parent. +if [[ -n "$DIST_ARG" ]]; then + DIST_TAR="$DIST_ARG" +else + echo "==> Building HugeGraph Server distribution on host (native platform)" + mvn -f "$REPO_ROOT/pom.xml" package -e -B -ntp \ + -Dmaven.test.skip=true -Dmaven.javadoc.skip=true -Drat.skip=true \ + -pl hugegraph-server/hugegraph-dist -am + DIST_TAR=$(ls -t "$REPO_ROOT"/hugegraph-server/apache-hugegraph-server-*.tar.gz 2>/dev/null | head -n1 || true) +fi +[[ -f "$DIST_TAR" ]] || { fail "server distribution tarball not found"; exit 1; } +echo "==> Using distribution: $DIST_TAR" + +# Extract straight into the build context's server/ dir — no second copy +# (the extracted tree is ~1GB; copying it twice doubled the temp footprint). +BUILD_CTX=$(mktemp -d "${TMPDIR:-/tmp}/hugegraph-riscv64-ctx.XXXXXX") +SERVER_DIR="$BUILD_CTX/server" +mkdir -p "$SERVER_DIR" +tar -xzf "$DIST_TAR" -C "$SERVER_DIR" --strip-components=1 + +# Force the RocksDB backend for the smoke run. +CONF="$SERVER_DIR/conf/graphs/hugegraph.properties" +grep -qE '^[[:space:]]*backend[[:space:]]*=' "$CONF" \ + && sedi 's/^[[:space:]]*backend[[:space:]]*=.*/backend=rocksdb/' "$CONF" \ + || echo "backend=rocksdb" >> "$CONF" +grep -qE '^[[:space:]]*serializer[[:space:]]*=' "$CONF" \ + && sedi 's/^[[:space:]]*serializer[[:space:]]*=.*/serializer=binary/' "$CONF" \ + || echo "serializer=binary" >> "$CONF" + +# 2. Build a tiny RISC-V runtime image on top of the glibc base. +# libatomic1 is installed and exposed via LD_LIBRARY_PATH (+ a /usr/lib +# symlink) so RocksDB's __atomic_compare_exchange_1 symbol resolves +# automatically — no manual step for the user. If the JNI still lacks the +# symbol, the RocksDB step below fails loudly instead of being treated as +# the goal. +# Reuse the repo's own container entrypoint: it runs init-store.sh (create the +# RocksDB store) BEFORE start-hugegraph.sh. Hand-rolling the start command +# skips init and the server aborts with an empty log (the failure we hit). +cp "$REPO_ROOT/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh" \ + "$BUILD_CTX/server/docker-entrypoint.sh" +# The entrypoint hardcodes 'start-hugegraph.sh ... -t 120'. Under QEMU emulation +# the analyzer dictionary loads twice (~3.5 min each) plus Gremlin/Groovy warmup, +# so the server needs well over 600s to bind 8080. Raise the startup timeout. +sedi 's/-t 120/-t 1800/' "$BUILD_CTX/server/docker-entrypoint.sh" +cat > "$BUILD_CTX/Dockerfile" <<EOF +FROM ${BASE_IMAGE} +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get -q update && apt-get -q install -y --no-install-recommends --no-install-suggests openjdk-11-jre-headless libatomic1 curl jq dumb-init lsof procps && apt-get clean && rm -rf /var/lib/apt/lists/* +# Force libatomic to load before the JNI: rocksdbjni:8.10.2's RISC-V +# .so has an undefined __atomic_compare_exchange_1 that only libatomic +# satisfies. LD_LIBRARY_PATH alone is NOT enough (verified: symbol stays +# unresolved); LD_PRELOAD guarantees libatomic loads first into every +# Java process (the RocksDB smoke test AND the server). +# Path is the constant riscv64 multiarch dir confirmed inside the image; +# dpkg-architecture is unreliable under QEMU emulation, so hardcode it. +RUN ln -sf /usr/lib/riscv64-linux-gnu/libatomic.so.1 /usr/lib/libatomic.so.1 +WORKDIR /server +COPY server/ /server/ +RUN chmod 755 /server/docker-entrypoint.sh && sed -i "s#^restserver.url.*#restserver.url=http://127.0.0.1:8080#" /server/conf/rest-server.properties +# The Ubuntu openjdk-11 RISC-V build lacks -XX:G1RSetUpdatingPauseTimePercent, +# which start-hugegraph.sh always appends for the default G1GC. It is a tuning +# hint, not required for correctness, so strip it to let the JVM launch. +RUN sed -i "s/-XX:G1RSetUpdatingPauseTimePercent=5//" /server/bin/hugegraph-server.sh +# JAVA_OPTS mirrors the official image (auth proxy needs --add-exports on JDK 11). +# STDOUT_MODE=true routes the server log to stdout so 'docker logs' shows failures. +ENV JAVA_OPTS="-XX:+UnlockExperimentalVMOptions -XX:+UseContainerSupport -XX:MaxRAMPercentage=50 --add-exports=java.base/jdk.internal.reflect=ALL-UNNAMED" LD_PRELOAD=/usr/lib/libatomic.so.1 HUGEGRAPH_HOME=server STDOUT_MODE=true +EXPOSE 8080 +ENTRYPOINT ["/usr/bin/dumb-init", "--"] +CMD ["/server/docker-entrypoint.sh"] +EOF + +echo "==> Building RISC-V runtime image $TAG" +$DOCKER buildx build --platform "$PLATFORM" -t "$TAG" "$BUILD_CTX" --load + +# 3. Start the server in the background; trap handles cleanup. +echo "==> Starting server container on ${PLATFORM}" +$DOCKER run -d --name "$CONTAINER" --platform "$PLATFORM" \ + -p 8080 --memory=4g -v "$VOL:/server" "$TAG" Review Comment: ‼️ `-p 8080` publishes the unauthenticated test REST service on a random host port bound to all host interfaces for up to the one-hour wait, while every probe in this script uses `docker exec` and does not need a host mapping. Please remove this publish flag; if host access is intentionally required, bind it explicitly to `127.0.0.1`. ########## .gitignore: ########## @@ -118,3 +118,6 @@ codeium-instructions.md *.ai-prompt.md WARP.md .mcp.json +hugegraph-riscv64-ctx.*/ Review Comment: 🧹 These two unanchored patterns can hide matching directories anywhere in the repository, and they are redundant for the default path because both temporary directories are created beneath the already-ignored `.riscv64-smoke-tmp/`. Please keep only a root-anchored `/.riscv64-smoke-tmp/` entry unless another concrete location must be ignored. ########## hugegraph-server/hugegraph-dist/src/assembly/travis/run-riscv64-smoke.sh: ########## @@ -0,0 +1,518 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# run-riscv64-smoke.sh — Disposable RISC-V (linux/riscv64) end-to-end smoke test. +# +# Builds the HugeGraph Server distribution on the contributor's native host +# (x86-64 / arm64), then runs it inside an emulated linux/riscv64 container to +# verify the RocksDB backend + Gremlin stack actually work on RISC-V via QEMU. +# +# This is a CORRECTNESS gate, not a performance benchmark. QEMU user-mode +# emulation is several times slower than native; expect the full run (build + +# init + REST + Gremlin + restart) to take many minutes the first time. +# +# It requires Docker with QEMU/binfmt_misc multi-platform support (Docker +# Desktop ships this; on Linux install it with: +# docker run --privileged --rm tonistiigi/binfmt --install all +# No host RISC-V packages are installed by this script. +# +# It removes ONLY what it creates: a uniquely-named container, volume, and +# locally-built image. The QEMU emulator registration is left untouched +# (remove it with: docker run --privileged --rm tonistiigi/binfmt --uninstall all). +# +# Usage: +# ./run-riscv64-smoke.sh [path-to-server-dist.tar.gz] +# ./run-riscv64-smoke.sh # builds the dist under hugegraph-server/ +# +set -euo pipefail + +ARCH=riscv64 +PLATFORM="linux/${ARCH}" +# Ubuntu 24.04 publishes openjdk-11-jre-headless for linux/riscv64 (glibc). +BASE_IMAGE="ubuntu:24.04" + +REPO_ROOT=$(cd "$(dirname "$0")/../../../../.." && pwd) + +# Use disk-backed temp: the extracted server (~1GB) blows out a small RAM tmpfs +# /tmp. Default to a work dir on the same (large) filesystem as the repo. +: "${TMPDIR:=$REPO_ROOT/.riscv64-smoke-tmp}" +mkdir -p "$TMPDIR" +export TMPDIR + +# Portable in-place sed: avoids GNU sed -i which breaks on macOS/BSD. +sedi() { + local expr="$1" file="$2" + local tmp; tmp=$(mktemp "${TMPDIR:-/tmp}/sedi.XXXXXX") + if sed "$expr" "$file" > "$tmp"; then + mv "$tmp" "$file" + else + rm -f "$tmp" + return 1 + fi +} + +# Unique, traceable names so cleanup removes only our own resources. +# Timestamp + random hex: collision-resistant, still human-readable for debugging. +STAMP=$(date +%Y%m%d%H%M%S)-$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' \n') +TAG="hugegraph-riscv64-smoke:${STAMP}" +# KEEP: don't auto-cleanup so a failed/finished run stays pokeable. +VOL="hugegraph-riscv64-data-${STAMP}" +CONTAINER="hugegraph-riscv64-smoke-${STAMP}" + +DIST_ARG="${1:-}" +DIST_TAR="" +SERVER_DIR="" +BUILD_CTX="" +ROCKS_BUILD="" + +GREEN=$'\033[0;32m'; RED=$'\033[0;31m'; NC=$'\033[0m' +pass() { echo "${GREEN}PASS${NC} $*"; } +fail() { echo "${RED}FAIL${NC} $*" >&2; } + +# Check if the container is still alive (returns 0 if running). +container_alive() { + $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER" +} + +# Run docker exec with container-liveness guard + exit-code logging. +# Usage: safe_exec exec <container> <cmd> [args...] +# DOCKER is split into words without eval; handles "docker" or "sudo docker". +# Returns the command's output on stdout; logs failures to stderr. +safe_exec() { + local rc + if ! container_alive; then + fail "container '$CONTAINER' is no longer running (before: $*)" + dump_health_diagnostics + return 1 + fi + set +e + local -a docker_parts=($DOCKER) + "${docker_parts[@]}" "$@" + rc=$? + set -e + if [[ $rc -ne 0 ]]; then + fail "command failed (exit $rc): $*" + dump_health_diagnostics + return 1 + fi +} + +# DOCKER: allow callers to override (e.g. "sudo docker"). Defaults to plain docker. +DOCKER=${DOCKER:-docker} + +# Dump container diagnostics to stderr. Defined early so the EXIT trap can +# call it even if the script fails before the main body. +dump_health_diagnostics() { + echo "----- health-check diagnostics ($CONTAINER) -----" >&2 + $DOCKER inspect --format \ + 'state={{.State.Status}} exit={{.State.ExitCode}} error={{.State.Error}}' \ + "$CONTAINER" >&2 || true + + if $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER"; then + echo "----- listening processes -----" >&2 + $DOCKER exec "$CONTAINER" sh -c \ + 'lsof -nP -iTCP:8080 -sTCP:LISTEN || true' >&2 || true + for endpoint in versions graphs; do + echo "----- GET /$endpoint -----" >&2 + $DOCKER exec "$CONTAINER" curl -i -sS --connect-timeout 5 --max-time 15 \ + "http://127.0.0.1:8080/$endpoint" >&2 || true + done + fi + + echo "----- recent container logs -----" >&2 + $DOCKER logs --tail 100 "$CONTAINER" >&2 || true + echo "----- end health-check diagnostics -----" >&2 +} + +_EXIT_CODE=0 +_exit_handler() { + local rc=$? + # If the script failed, log the exit code so the root cause isn't lost. + if [[ $rc -ne 0 ]]; then + echo "" >&2 + echo "==> Script failed with exit code $rc" >&2 + if container_alive; then + dump_health_diagnostics + else + echo "==> container '$CONTAINER' is not running (may have been OOM-killed or crashed)" >&2 + # Best-effort: check docker inspect for OOMKilled + dump any surviving logs + $DOCKER inspect --format \ + 'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}} Status={{.State.Status}}' \ + "$CONTAINER" >&2 2>/dev/null || true + $DOCKER logs --tail 80 "$CONTAINER" >&2 2>/dev/null || true + fi + fi + _cleanup +} + +_cleanup() { + # KEEP=1: leave everything standing for live poking. Still dump logs so a + # failure's root cause is visible; skip all teardown. + if [[ "${KEEP:-}" == "1" ]]; then + echo "==> KEEP=1 set — skipping cleanup. Resources left standing:" >&2 + echo " container: $CONTAINER" >&2 + echo " volume: $VOL" >&2 + echo " image: $TAG" >&2 + return 0 + fi + # Dump container logs first so a failure's root cause isn't destroyed by cleanup. + if $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER"; then + echo "----- container logs ($CONTAINER) -----" >&2 + $DOCKER logs --tail 60 "$CONTAINER" >&2 || true + echo "----- end logs -----" >&2 + # The startup wrapper hides the real error in the server log file; dump it + # from the stopped container (docker cp works on exited containers). + echo "----- hugegraph-server.log -----" >&2 + $DOCKER cp "$CONTAINER:/server/logs/hugegraph-server.log" - 2>/dev/null \ + | tar -xO 2>/dev/null | tail -n 80 >&2 || true + echo "----- end hugegraph-server.log -----" >&2 + fi + local ok=1 + $DOCKER rm -f "$CONTAINER" >/dev/null 2>&1 || ok=0 + # Only remove the volume we created. + $DOCKER volume rm "$VOL" >/dev/null 2>&1 || ok=0 + # Only remove the image we tagged; never prune the host's image store. + $DOCKER rmi "$TAG" >/dev/null 2>&1 || ok=0 + # Remove disk-backed temp work dirs (build context holds the ~1GB extract). + [[ -n "$BUILD_CTX" && -d "$BUILD_CTX" ]] && rm -rf "$BUILD_CTX" + [[ -n "$ROCKS_BUILD" && -d "$ROCKS_BUILD" ]] && rm -rf "$ROCKS_BUILD" + if [[ "$ok" == "1" ]]; then + pass "cleanup: container, volume, and image removed" + else + fail "cleanup: some resources may remain (container=$CONTAINER vol=$VOL image=$TAG)" + fi +} +trap _exit_handler EXIT + +need() { command -v "$1" >/dev/null 2>&1 || { fail "missing required command: $1"; exit 1; }; } +need docker +# buildx is required: the legacy builder ignores --platform. +$DOCKER buildx version >/dev/null 2>&1 || { fail "docker buildx is required (pacman -S docker-buildx)"; exit 1; } + +# Register QEMU user-mode emulation for riscv64 if the Docker daemon can't +# already run it (otherwise the build/run fails with 'exec format error'). +# binfmt handlers do NOT survive reboots, so a fresh checkout needs this — +# makes the smoke test self-contained on any x86 host. Pinned tag: :latest +# 403s on Docker Hub. +# +# Probe through the Docker daemon, not the host /proc: Docker Desktop keeps +# binfmt inside its Linux VM; a remote context can have the opposite state. +if ! $DOCKER run --rm --platform "$PLATFORM" "$BASE_IMAGE" uname -m 2>/dev/null \ + | grep -q riscv64; then + echo "==> Registering QEMU riscv64 emulation (binfmt)" + $DOCKER run --privileged --rm tonistiigi/binfmt:qemu-v8.1.5 --install riscv64 >/dev/null Review Comment: ‼️ This runs a mutable image tag with `--privileged`, which gives that image host-level access to alter the Docker daemon's binfmt/kernel state. It also happens before required host tools such as the conditional `mvn` and the always-used Java compiler are validated, so a doomed run can make the persistent privileged change first. Please preflight all applicable host tools before this point and pin the privileged image to a verified immutable digest (preferably with signature verification). ########## hugegraph-server/hugegraph-dist/src/assembly/travis/run-riscv64-smoke.sh: ########## @@ -0,0 +1,518 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# run-riscv64-smoke.sh — Disposable RISC-V (linux/riscv64) end-to-end smoke test. +# +# Builds the HugeGraph Server distribution on the contributor's native host +# (x86-64 / arm64), then runs it inside an emulated linux/riscv64 container to +# verify the RocksDB backend + Gremlin stack actually work on RISC-V via QEMU. +# +# This is a CORRECTNESS gate, not a performance benchmark. QEMU user-mode +# emulation is several times slower than native; expect the full run (build + +# init + REST + Gremlin + restart) to take many minutes the first time. +# +# It requires Docker with QEMU/binfmt_misc multi-platform support (Docker +# Desktop ships this; on Linux install it with: +# docker run --privileged --rm tonistiigi/binfmt --install all +# No host RISC-V packages are installed by this script. +# +# It removes ONLY what it creates: a uniquely-named container, volume, and +# locally-built image. The QEMU emulator registration is left untouched +# (remove it with: docker run --privileged --rm tonistiigi/binfmt --uninstall all). +# +# Usage: +# ./run-riscv64-smoke.sh [path-to-server-dist.tar.gz] +# ./run-riscv64-smoke.sh # builds the dist under hugegraph-server/ +# +set -euo pipefail + +ARCH=riscv64 +PLATFORM="linux/${ARCH}" +# Ubuntu 24.04 publishes openjdk-11-jre-headless for linux/riscv64 (glibc). +BASE_IMAGE="ubuntu:24.04" + +REPO_ROOT=$(cd "$(dirname "$0")/../../../../.." && pwd) + +# Use disk-backed temp: the extracted server (~1GB) blows out a small RAM tmpfs +# /tmp. Default to a work dir on the same (large) filesystem as the repo. +: "${TMPDIR:=$REPO_ROOT/.riscv64-smoke-tmp}" +mkdir -p "$TMPDIR" +export TMPDIR + +# Portable in-place sed: avoids GNU sed -i which breaks on macOS/BSD. +sedi() { + local expr="$1" file="$2" + local tmp; tmp=$(mktemp "${TMPDIR:-/tmp}/sedi.XXXXXX") + if sed "$expr" "$file" > "$tmp"; then + mv "$tmp" "$file" + else + rm -f "$tmp" + return 1 + fi +} + +# Unique, traceable names so cleanup removes only our own resources. +# Timestamp + random hex: collision-resistant, still human-readable for debugging. +STAMP=$(date +%Y%m%d%H%M%S)-$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' \n') +TAG="hugegraph-riscv64-smoke:${STAMP}" +# KEEP: don't auto-cleanup so a failed/finished run stays pokeable. +VOL="hugegraph-riscv64-data-${STAMP}" +CONTAINER="hugegraph-riscv64-smoke-${STAMP}" + +DIST_ARG="${1:-}" +DIST_TAR="" +SERVER_DIR="" +BUILD_CTX="" +ROCKS_BUILD="" + +GREEN=$'\033[0;32m'; RED=$'\033[0;31m'; NC=$'\033[0m' +pass() { echo "${GREEN}PASS${NC} $*"; } +fail() { echo "${RED}FAIL${NC} $*" >&2; } + +# Check if the container is still alive (returns 0 if running). +container_alive() { + $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER" +} + +# Run docker exec with container-liveness guard + exit-code logging. +# Usage: safe_exec exec <container> <cmd> [args...] +# DOCKER is split into words without eval; handles "docker" or "sudo docker". +# Returns the command's output on stdout; logs failures to stderr. +safe_exec() { + local rc + if ! container_alive; then + fail "container '$CONTAINER' is no longer running (before: $*)" + dump_health_diagnostics + return 1 + fi + set +e + local -a docker_parts=($DOCKER) + "${docker_parts[@]}" "$@" + rc=$? + set -e + if [[ $rc -ne 0 ]]; then + fail "command failed (exit $rc): $*" + dump_health_diagnostics + return 1 + fi +} + +# DOCKER: allow callers to override (e.g. "sudo docker"). Defaults to plain docker. +DOCKER=${DOCKER:-docker} + +# Dump container diagnostics to stderr. Defined early so the EXIT trap can +# call it even if the script fails before the main body. +dump_health_diagnostics() { + echo "----- health-check diagnostics ($CONTAINER) -----" >&2 + $DOCKER inspect --format \ + 'state={{.State.Status}} exit={{.State.ExitCode}} error={{.State.Error}}' \ + "$CONTAINER" >&2 || true + + if $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER"; then + echo "----- listening processes -----" >&2 + $DOCKER exec "$CONTAINER" sh -c \ + 'lsof -nP -iTCP:8080 -sTCP:LISTEN || true' >&2 || true + for endpoint in versions graphs; do + echo "----- GET /$endpoint -----" >&2 + $DOCKER exec "$CONTAINER" curl -i -sS --connect-timeout 5 --max-time 15 \ + "http://127.0.0.1:8080/$endpoint" >&2 || true + done + fi + + echo "----- recent container logs -----" >&2 + $DOCKER logs --tail 100 "$CONTAINER" >&2 || true + echo "----- end health-check diagnostics -----" >&2 +} + +_EXIT_CODE=0 +_exit_handler() { + local rc=$? + # If the script failed, log the exit code so the root cause isn't lost. + if [[ $rc -ne 0 ]]; then + echo "" >&2 + echo "==> Script failed with exit code $rc" >&2 + if container_alive; then + dump_health_diagnostics + else + echo "==> container '$CONTAINER' is not running (may have been OOM-killed or crashed)" >&2 + # Best-effort: check docker inspect for OOMKilled + dump any surviving logs + $DOCKER inspect --format \ + 'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}} Status={{.State.Status}}' \ + "$CONTAINER" >&2 2>/dev/null || true + $DOCKER logs --tail 80 "$CONTAINER" >&2 2>/dev/null || true + fi + fi + _cleanup +} + +_cleanup() { + # KEEP=1: leave everything standing for live poking. Still dump logs so a + # failure's root cause is visible; skip all teardown. + if [[ "${KEEP:-}" == "1" ]]; then + echo "==> KEEP=1 set — skipping cleanup. Resources left standing:" >&2 + echo " container: $CONTAINER" >&2 + echo " volume: $VOL" >&2 + echo " image: $TAG" >&2 + return 0 + fi + # Dump container logs first so a failure's root cause isn't destroyed by cleanup. + if $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER"; then + echo "----- container logs ($CONTAINER) -----" >&2 + $DOCKER logs --tail 60 "$CONTAINER" >&2 || true + echo "----- end logs -----" >&2 + # The startup wrapper hides the real error in the server log file; dump it + # from the stopped container (docker cp works on exited containers). + echo "----- hugegraph-server.log -----" >&2 + $DOCKER cp "$CONTAINER:/server/logs/hugegraph-server.log" - 2>/dev/null \ + | tar -xO 2>/dev/null | tail -n 80 >&2 || true + echo "----- end hugegraph-server.log -----" >&2 + fi + local ok=1 + $DOCKER rm -f "$CONTAINER" >/dev/null 2>&1 || ok=0 + # Only remove the volume we created. + $DOCKER volume rm "$VOL" >/dev/null 2>&1 || ok=0 + # Only remove the image we tagged; never prune the host's image store. + $DOCKER rmi "$TAG" >/dev/null 2>&1 || ok=0 + # Remove disk-backed temp work dirs (build context holds the ~1GB extract). + [[ -n "$BUILD_CTX" && -d "$BUILD_CTX" ]] && rm -rf "$BUILD_CTX" + [[ -n "$ROCKS_BUILD" && -d "$ROCKS_BUILD" ]] && rm -rf "$ROCKS_BUILD" + if [[ "$ok" == "1" ]]; then + pass "cleanup: container, volume, and image removed" + else + fail "cleanup: some resources may remain (container=$CONTAINER vol=$VOL image=$TAG)" + fi +} +trap _exit_handler EXIT + +need() { command -v "$1" >/dev/null 2>&1 || { fail "missing required command: $1"; exit 1; }; } +need docker +# buildx is required: the legacy builder ignores --platform. +$DOCKER buildx version >/dev/null 2>&1 || { fail "docker buildx is required (pacman -S docker-buildx)"; exit 1; } + +# Register QEMU user-mode emulation for riscv64 if the Docker daemon can't +# already run it (otherwise the build/run fails with 'exec format error'). +# binfmt handlers do NOT survive reboots, so a fresh checkout needs this — +# makes the smoke test self-contained on any x86 host. Pinned tag: :latest +# 403s on Docker Hub. +# +# Probe through the Docker daemon, not the host /proc: Docker Desktop keeps +# binfmt inside its Linux VM; a remote context can have the opposite state. +if ! $DOCKER run --rm --platform "$PLATFORM" "$BASE_IMAGE" uname -m 2>/dev/null \ + | grep -q riscv64; then + echo "==> Registering QEMU riscv64 emulation (binfmt)" + $DOCKER run --privileged --rm tonistiigi/binfmt:qemu-v8.1.5 --install riscv64 >/dev/null +fi + +# 1. Resolve the Server distribution tarball (build on host if not supplied). +# Build from the ROOT pom (not hugegraph-server/): the root parent pom +# `hugegraph` and the root sibling `hugegraph-struct` must be installed +# into ~/.m2 first, or module resolution fails on the ${revision} parent. +if [[ -n "$DIST_ARG" ]]; then + DIST_TAR="$DIST_ARG" +else + echo "==> Building HugeGraph Server distribution on host (native platform)" + mvn -f "$REPO_ROOT/pom.xml" package -e -B -ntp \ + -Dmaven.test.skip=true -Dmaven.javadoc.skip=true -Drat.skip=true \ + -pl hugegraph-server/hugegraph-dist -am + DIST_TAR=$(ls -t "$REPO_ROOT"/hugegraph-server/apache-hugegraph-server-*.tar.gz 2>/dev/null | head -n1 || true) +fi +[[ -f "$DIST_TAR" ]] || { fail "server distribution tarball not found"; exit 1; } +echo "==> Using distribution: $DIST_TAR" + +# Extract straight into the build context's server/ dir — no second copy +# (the extracted tree is ~1GB; copying it twice doubled the temp footprint). +BUILD_CTX=$(mktemp -d "${TMPDIR:-/tmp}/hugegraph-riscv64-ctx.XXXXXX") +SERVER_DIR="$BUILD_CTX/server" +mkdir -p "$SERVER_DIR" +tar -xzf "$DIST_TAR" -C "$SERVER_DIR" --strip-components=1 + +# Force the RocksDB backend for the smoke run. +CONF="$SERVER_DIR/conf/graphs/hugegraph.properties" +grep -qE '^[[:space:]]*backend[[:space:]]*=' "$CONF" \ + && sedi 's/^[[:space:]]*backend[[:space:]]*=.*/backend=rocksdb/' "$CONF" \ + || echo "backend=rocksdb" >> "$CONF" +grep -qE '^[[:space:]]*serializer[[:space:]]*=' "$CONF" \ + && sedi 's/^[[:space:]]*serializer[[:space:]]*=.*/serializer=binary/' "$CONF" \ + || echo "serializer=binary" >> "$CONF" + +# 2. Build a tiny RISC-V runtime image on top of the glibc base. +# libatomic1 is installed and exposed via LD_LIBRARY_PATH (+ a /usr/lib +# symlink) so RocksDB's __atomic_compare_exchange_1 symbol resolves +# automatically — no manual step for the user. If the JNI still lacks the +# symbol, the RocksDB step below fails loudly instead of being treated as +# the goal. +# Reuse the repo's own container entrypoint: it runs init-store.sh (create the +# RocksDB store) BEFORE start-hugegraph.sh. Hand-rolling the start command +# skips init and the server aborts with an empty log (the failure we hit). +cp "$REPO_ROOT/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh" \ + "$BUILD_CTX/server/docker-entrypoint.sh" +# The entrypoint hardcodes 'start-hugegraph.sh ... -t 120'. Under QEMU emulation +# the analyzer dictionary loads twice (~3.5 min each) plus Gremlin/Groovy warmup, +# so the server needs well over 600s to bind 8080. Raise the startup timeout. +sedi 's/-t 120/-t 1800/' "$BUILD_CTX/server/docker-entrypoint.sh" +cat > "$BUILD_CTX/Dockerfile" <<EOF +FROM ${BASE_IMAGE} +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get -q update && apt-get -q install -y --no-install-recommends --no-install-suggests openjdk-11-jre-headless libatomic1 curl jq dumb-init lsof procps && apt-get clean && rm -rf /var/lib/apt/lists/* +# Force libatomic to load before the JNI: rocksdbjni:8.10.2's RISC-V +# .so has an undefined __atomic_compare_exchange_1 that only libatomic +# satisfies. LD_LIBRARY_PATH alone is NOT enough (verified: symbol stays +# unresolved); LD_PRELOAD guarantees libatomic loads first into every +# Java process (the RocksDB smoke test AND the server). +# Path is the constant riscv64 multiarch dir confirmed inside the image; +# dpkg-architecture is unreliable under QEMU emulation, so hardcode it. +RUN ln -sf /usr/lib/riscv64-linux-gnu/libatomic.so.1 /usr/lib/libatomic.so.1 +WORKDIR /server +COPY server/ /server/ +RUN chmod 755 /server/docker-entrypoint.sh && sed -i "s#^restserver.url.*#restserver.url=http://127.0.0.1:8080#" /server/conf/rest-server.properties +# The Ubuntu openjdk-11 RISC-V build lacks -XX:G1RSetUpdatingPauseTimePercent, +# which start-hugegraph.sh always appends for the default G1GC. It is a tuning +# hint, not required for correctness, so strip it to let the JVM launch. +RUN sed -i "s/-XX:G1RSetUpdatingPauseTimePercent=5//" /server/bin/hugegraph-server.sh +# JAVA_OPTS mirrors the official image (auth proxy needs --add-exports on JDK 11). +# STDOUT_MODE=true routes the server log to stdout so 'docker logs' shows failures. +ENV JAVA_OPTS="-XX:+UnlockExperimentalVMOptions -XX:+UseContainerSupport -XX:MaxRAMPercentage=50 --add-exports=java.base/jdk.internal.reflect=ALL-UNNAMED" LD_PRELOAD=/usr/lib/libatomic.so.1 HUGEGRAPH_HOME=server STDOUT_MODE=true +EXPOSE 8080 +ENTRYPOINT ["/usr/bin/dumb-init", "--"] +CMD ["/server/docker-entrypoint.sh"] +EOF + +echo "==> Building RISC-V runtime image $TAG" +$DOCKER buildx build --platform "$PLATFORM" -t "$TAG" "$BUILD_CTX" --load + +# 3. Start the server in the background; trap handles cleanup. +echo "==> Starting server container on ${PLATFORM}" +$DOCKER run -d --name "$CONTAINER" --platform "$PLATFORM" \ + -p 8080 --memory=4g -v "$VOL:/server" "$TAG" + +# Wait until /graphs answers 200 (server auto-inits + starts via entrypoint). +# This is the same readiness endpoint used by start-hugegraph.sh. /versions is +# asserted separately after the server has completed its startup sequence. +wait_health() { + # This probe starts while the entrypoint is still running init-store.sh. + # Under QEMU the analyzer dictionary loads once during initialization and + # again during server startup, so the full end-to-end wait needs to exceed + # the entrypoint's 1800s startup timeout as well as initialization time. + local limit=3600 + local start=$SECONDS + while (( SECONDS - start < limit )); do + # Bail fast if the container died instead of polling a corpse for 10 min. + if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER"; then + echo "==> container '$CONTAINER' is no longer running" >&2 + dump_health_diagnostics + return 1 + fi + local code + code=$($DOCKER exec "$CONTAINER" \ + curl -s --connect-timeout 10 --max-time 60 \ + -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/graphs 2>/dev/null || true) + code=${code:-000} + [[ "$code" == "200" ]] && return 0 + sleep 5 + done + echo "==> health check timed out after ${limit}s (last /graphs HTTP status: $code)" >&2 + dump_health_diagnostics + return 1 +} + +# 4. Architecture assertion. +echo "==> Verifying architecture" +container_alive || { fail "container died before architecture check"; exit 1; } +ARCH_OK=$($DOCKER exec "$CONTAINER" uname -m) +[[ "$ARCH_OK" == "riscv64" ]] || { fail "architecture=$ARCH_OK (expected riscv64)"; exit 1; } +pass "architecture: riscv64" + +# 5. RocksDB open/put/get/close via a real Java program against the JNI. +echo "==> RocksDB open/put/get/close test" +# If the JNI is not linked to libatomic this step's java process dies with +# "undefined symbol: __atomic_compare_exchange_1" — that is the signal to fix +# the linkage, NOT the expected final result. +ROCKS_TEST='import org.rocksdb.*; +public class RocksSmoke { + public static void main(String[] a) throws Exception { + RocksDB.loadLibrary(); + try (Options o = new Options().setCreateIfMissing(true); + RocksDB db = RocksDB.open(o, "/tmp/rocksdb-smoke")) { + byte[] k = "key".getBytes(java.nio.charset.StandardCharsets.UTF_8); + byte[] v = "value".getBytes(java.nio.charset.StandardCharsets.UTF_8); + db.put(k, v); + byte[] got = db.get(k); + if (!java.util.Arrays.equals(v, got)) + throw new AssertionError("rocksdb get mismatch: " + new String(got)); + db.close(); + } + System.out.println("ROCKS_OK"); + } +}' +container_alive || { fail "container died before RocksDB test"; exit 1; } +ROCKSJAR=$($DOCKER exec "$CONTAINER" sh -c 'ls /server/lib/rocksdbjni-*.jar 2>/dev/null | head -n1') +[[ -n "$ROCKSJAR" ]] || { fail "rocksdbjni jar not found in dist"; exit 1; } +# Compile the smoke class on the HOST (which has a JDK); the runtime image +# ships only a JRE (no javac). The .class is arch-independent bytecode, +# so an x86-compiled class runs fine under the riscv64 JRE. +ROCKS_BUILD=$(mktemp -d "${TMPDIR:-/tmp}/rocks-smoke.XXXXXX") +echo "$ROCKS_TEST" > "$ROCKS_BUILD/RocksSmoke.java" +javac -cp "$SERVER_DIR"/lib/rocksdbjni-*.jar "$ROCKS_BUILD/RocksSmoke.java" -d "$ROCKS_BUILD" \ + || { fail "failed to compile RocksSmoke on host (need a JDK)"; exit 1; } +$DOCKER cp "$ROCKS_BUILD/RocksSmoke.class" "$CONTAINER:/tmp/RocksSmoke.class" +# ponytail: set +e around the pipeline so pipefail doesn't kill the script +# before the grep below can report the actual failure. +set +e +safe_exec exec "$CONTAINER" java -cp "/tmp:$ROCKSJAR" RocksSmoke 2>&1 \ + | tee "$ROCKS_BUILD/rocks-out.log" +JAVA_RC=${PIPESTATUS[0]} +set -e +grep -q "ROCKS_OK" "$ROCKS_BUILD/rocks-out.log" || { + fail "RocksDB open/put/get/close failed (java exit=$JAVA_RC, check JNI/libatomic linkage)" + exit 1 +} +[[ "$JAVA_RC" == "0" ]] || { + fail "RocksDB JVM exited with $JAVA_RC despite printing ROCKS_OK" + exit 1 +} +pass "RocksDB: open / put / get / close" + +# 6. init already ran via entrypoint; confirm health, then REST CRUD + Gremlin. +wait_health || { fail "server did not become healthy (GET /graphs)"; exit 1; } +pass "HugeGraph: init / health (GET /graphs -> 200)" + +# ponytail: --max-time 300 because QEMU makes every REST call slow; upgrade to +# per-endpoint tuning if individual calls need more headroom. +container_alive || { fail "container died before REST phase"; exit 1; } +# Wait for /versions to return valid JSON — the server's /graphs health check +# may pass before the REST API layer is fully initialized. +REST_READY="" +for _retry in 1 2 3 4 5 6 7 8 9 10; do + REST_READY=$($DOCKER exec "$CONTAINER" sh -c 'curl -s --connect-timeout 10 --max-time 30 http://localhost:8080/versions && echo' || true) + if [[ "$REST_READY" == *'"core"'* ]]; then break; fi + echo " waiting for REST API (attempt $_retry)..." >&2 + sleep 10 +done +[[ "$REST_READY" == *'"core"'* ]] || { fail "REST API not ready after retries: $REST_READY"; exit 1; } + +# Create schema, a vertex, and an edge; read them back. +# Schema order matters (commits are synchronous, but each element must exist +# before the one that references it): property key "name" -> vertex label +# "person" (declares name) -> edge label "knows" -> vertices -> edge. +# Each POST asserts HTTP 2xx so a bad request fails loud, not silent. +echo "==> REST schema + CRUD" +rest_post() { # $1=path $2=json + local body code response rc + set +e + response=$($DOCKER exec "$CONTAINER" curl -sS --connect-timeout 10 --max-time 300 \ + -w $'\n%{http_code}' -X POST \ + "http://localhost:8080/graphs/hugegraph/$1" \ + -H 'Content-Type: application/json' -d "$2") + rc=$? + set -e + if [[ $rc -ne 0 ]]; then + fail "REST POST $1 failed (curl/docker exec exit $rc): $response" + exit 1 + fi + code=${response##*$'\n'} + body=${response%$'\n'*} + # Tolerate "already exists" (400 + ExistedException) for idempotent schema ops. + if [[ "$code" == 400 && "$body" == *ExistedException* ]]; then + printf '%s\n' "$body" + return 0 + fi + [[ "$code" == 2* ]] || { fail "REST POST $1 failed (HTTP $code): $body"; exit 1; } + printf '%s\n' "$body" +} + +# GET a REST endpoint, capture response to a temp file. +# Usage: rest_get <path> [curl-extra-args...] +# Sets REST_BODY (response body) and REST_CODE (HTTP status). +rest_get() { + local rsp + rsp=$($DOCKER exec "$CONTAINER" curl -sS --compressed \ + --connect-timeout 10 --max-time 300 \ + -w $'\n%{http_code}' \ + -H 'Accept: application/json' "$@") + REST_CODE=${rsp##*$'\n'} Review Comment: ⚠️ `rest_get` parses `REST_CODE`, but no caller checks it; the CRUD and restart gates only assert JSON body shape. That allows a non-2xx response with a compatible body to pass even though the stated REST contract failed. Please make this helper require a successful Docker/curl exit and a 2xx status before any JSON assertion, and include the status and response body in failures. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
