diff --git a/.github/workflows/riscv64-ci.yml b/.github/workflows/riscv64-ci.yml new file mode 100644 index 0000000000..9f8ca12057 --- /dev/null +++ b/.github/workflows/riscv64-ci.yml @@ -0,0 +1,126 @@ +# +# 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. +# + +name: "RISC-V Server CI" + +on: + workflow_call: + +permissions: + contents: read + +env: + BINFMT_IMAGE: >- + tonistiigi/binfmt@sha256:400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0 + RISCV64_BASE_IMAGE: >- + ubuntu@sha256:4edded5722eb644868b7b976033d241d2ab3fff0a170924df69b200a59a2b994 + DRAGONWELL_RISCV64_ARCHIVE: >- + Alibaba_Dragonwell_Extended_11.0.31.28.11_riscv64_linux.tar.gz + DRAGONWELL_RISCV64_SHA256: >- + 7df2d308f0dca7a779d2854e6da19214f99cd77aa6d4982c3bf981a77266a79b + DRAGONWELL_RISCV64_URL: >- + https://github.com/dragonwell-project/dragonwell11/releases/download/dragonwell-extended-11.0.31.28_jdk-11.0.31-ga/Alibaba_Dragonwell_Extended_11.0.31.28.11_riscv64_linux.tar.gz + RISCV64_CONTAINER: >- + hugegraph-riscv64-ci-${{ github.run_id }}-${{ github.run_attempt }} + +jobs: + build-server-riscv64: + runs-on: ubuntu-24.04 + timeout-minutes: 90 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Start isolated RISC-V environment + run: | + docker run --privileged --rm "$BINFMT_IMAGE" --install riscv64 + docker run --detach --init --platform linux/riscv64 \ + --name "$RISCV64_CONTAINER" "$RISCV64_BASE_IMAGE" sleep infinity + test "$(docker exec "$RISCV64_CONTAINER" uname -m)" = riscv64 + + - name: Prepare source and Java 11 + run: | + ARCHIVE_PATH="$RUNNER_TEMP/$DRAGONWELL_RISCV64_ARCHIVE" + curl --fail --location --retry 3 --show-error \ + "$DRAGONWELL_RISCV64_URL" --output "$ARCHIVE_PATH" + echo "$DRAGONWELL_RISCV64_SHA256 $ARCHIVE_PATH" | sha256sum -c - + + docker exec "$RISCV64_CONTAINER" mkdir -p /workspace /opt/dragonwell + git ls-files -z | tar --null --files-from=- -cf - | \ + docker exec -i "$RISCV64_CONTAINER" tar -xf - -C /workspace + docker cp "$ARCHIVE_PATH" \ + "$RISCV64_CONTAINER:/tmp/$DRAGONWELL_RISCV64_ARCHIVE" + + - name: Build and smoke test on RISC-V + run: | + docker exec \ + --env DRAGONWELL_RISCV64_ARCHIVE="$DRAGONWELL_RISCV64_ARCHIVE" \ + --env DRAGONWELL_RISCV64_SHA256="$DRAGONWELL_RISCV64_SHA256" \ + "$RISCV64_CONTAINER" bash -euo pipefail -c ' + test "$(uname -m)" = riscv64 + apt-get -q update + apt-get -q install -y --no-install-recommends \ + ca-certificates curl jq libatomic1 libgcc-s1 libstdc++6 \ + lsof maven procps \ + protobuf-compiler protobuf-compiler-grpc-java-plugin + + ARCHIVE_PATH="/tmp/$DRAGONWELL_RISCV64_ARCHIVE" + echo "$DRAGONWELL_RISCV64_SHA256 $ARCHIVE_PATH" | sha256sum -c - + tar -xzf "$ARCHIVE_PATH" --strip-components=1 -C /opt/dragonwell + rm "$ARCHIVE_PATH" + export JAVA_HOME=/opt/dragonwell + export PATH="$JAVA_HOME/bin:$PATH" + + java -XshowSettings:vm -version + test -x /usr/bin/protoc + test -x /usr/bin/grpc_java_plugin + + cd /workspace + MAVEN_OPTS="-XX:TieredStopAtLevel=1 -XX:ActiveProcessorCount=2" \ + mvn clean package -B -ntp -Drocksdb-only \ + -P riscv64-protobuf-tools \ + -pl hugegraph-server/hugegraph-dist -am \ + -Dmaven.test.skip=true -Dmaven.javadoc.skip=true + hugegraph-server/hugegraph-dist/src/assembly/travis/check-rocksdb-only-dist.sh \ + hugegraph-server/apache-hugegraph-server-*/ + hugegraph-server/hugegraph-dist/src/assembly/travis/run-native-runtime-smoke-test.sh \ + hugegraph-server/apache-hugegraph-server-*/ + ' + + - name: Show server log on failure + if: failure() + run: | + if docker container inspect "$RISCV64_CONTAINER" >/dev/null 2>&1; then + docker exec "$RISCV64_CONTAINER" bash -c ' + find /workspace -path "*/logs/hugegraph-server.log" \ + -exec tail -n 200 {} \; + ' || true + fi + + - name: Clean up RISC-V environment + if: always() + run: | + if docker container inspect "$RISCV64_CONTAINER" >/dev/null 2>&1; then + docker rm --force --volumes "$RISCV64_CONTAINER" + fi + if docker container inspect "$RISCV64_CONTAINER" >/dev/null 2>&1; then + echo "RISC-V CI container still exists: $RISCV64_CONTAINER" >&2 + exit 1 + fi diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml index 586e36da49..0d6c54b51c 100644 --- a/.github/workflows/server-ci.yml +++ b/.github/workflows/server-ci.yml @@ -203,3 +203,8 @@ jobs: if [ -f "$SERVER_DIR/bin/pid" ]; then $TRAVIS_DIR/stop-server.sh $SERVER_DIR || true fi + + build-server-riscv64: + uses: ./.github/workflows/riscv64-ci.yml + permissions: + contents: read diff --git a/hugegraph-pd/hg-pd-grpc/pom.xml b/hugegraph-pd/hg-pd-grpc/pom.xml index 7df8622e19..be8ccc44fd 100644 --- a/hugegraph-pd/hg-pd-grpc/pom.xml +++ b/hugegraph-pd/hg-pd-grpc/pom.xml @@ -32,7 +32,7 @@ - 1.6.0 + 1.7.1 1.39.0 3.17.2 0.6.1 @@ -127,4 +127,39 @@ + + + + riscv64-protobuf-tools + + + riscv64 + + + + + 1.47.0 + 3.21.7 + + + + com.google.protobuf + protobuf-java + ${riscv64.protobuf.java.version} + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + + /usr/bin/protoc + /usr/bin/grpc_java_plugin + + + + + + diff --git a/hugegraph-server/README.md b/hugegraph-server/README.md index c145190bf0..ef6c9f111d 100644 --- a/hugegraph-server/README.md +++ b/hugegraph-server/README.md @@ -30,3 +30,46 @@ HUGEGRAPH_VERSION=1.7.0 docker compose -f docker-compose-3pd-3store-3server.yml ``` See [docker/README.md](../docker/README.md) for the full setup guide. + +## RISC-V Development and Testing + +The end-to-end RISC-V CI validation currently runs on 64-bit `linux/riscv64` LP64D with +Ubuntu 24.04 and glibc 2.39. It covers HugeGraph Server and the embedded RocksDB backend. +PD, Store, HStore, other backends, musl/Alpine, and 32-bit RISC-V are out of scope. + +The dedicated [RISC-V Server CI](../.github/workflows/riscv64-ci.yml) runs a RocksDB-only +native build and runtime smoke test in an isolated QEMU environment. It uses the +checksum-pinned Alibaba Dragonwell 11 Extended Server VM and installs `libatomic1` for the +packaged RocksDB JNI library. QEMU is for correctness testing and is not a performance +benchmark. + +The packaged `rocksdbjni:8.10.2` RISC-V ELF references glibc symbols through +`GLIBC_2.30`. This is an inferred minimum for that JNI artifact only, not a validated +compatibility floor for the complete HugeGraph, Dragonwell, and system-library runtime. + +The repository Dockerfile and published HugeGraph image do not include RISC-V support. +Image publication requires separate multi-architecture design and validation. + +For the same validation on a native Debian-derived RISC-V system, use Dragonwell 11 +Extended `11.0.31.28.11` with the archive checksum declared in the CI workflow, then +install the build and runtime dependencies: + +```bash +sudo apt-get update +sudo apt-get install -y ca-certificates curl jq libatomic1 libgcc-s1 libstdc++6 \ + lsof maven procps protobuf-compiler protobuf-compiler-grpc-java-plugin +``` + +After selecting Dragonwell as `JAVA_HOME`, build and verify the RocksDB-only distribution: + +```bash +test "$(uname -m)" = riscv64 +"$JAVA_HOME/bin/java" -XshowSettings:vm -version +mvn clean package -Drocksdb-only -pl hugegraph-server/hugegraph-dist -am \ + -P riscv64-protobuf-tools \ + -Dmaven.test.skip=true -Dmaven.javadoc.skip=true +hugegraph-server/hugegraph-dist/src/assembly/travis/check-rocksdb-only-dist.sh \ + hugegraph-server/apache-hugegraph-server-*/ +hugegraph-server/hugegraph-dist/src/assembly/travis/run-native-runtime-smoke-test.sh \ + hugegraph-server/apache-hugegraph-server-*/ +``` diff --git a/hugegraph-server/hugegraph-core/pom.xml b/hugegraph-server/hugegraph-core/pom.xml index 2334496329..14f5274cb4 100644 --- a/hugegraph-server/hugegraph-core/pom.xml +++ b/hugegraph-server/hugegraph-core/pom.xml @@ -335,7 +335,7 @@ kr.motd.maven os-maven-plugin - 1.5.0.Final + 1.7.1 @@ -408,4 +408,26 @@ + + + + riscv64-protobuf-tools + + + riscv64 + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + + /usr/bin/protoc + + + + + + diff --git a/hugegraph-server/hugegraph-dist/pom.xml b/hugegraph-server/hugegraph-dist/pom.xml index 5914b038be..430a213900 100644 --- a/hugegraph-server/hugegraph-dist/pom.xml +++ b/hugegraph-server/hugegraph-dist/pom.xml @@ -34,6 +34,8 @@ ${assembly.dir}/descriptor ${assembly.dir}/static 2.4.0 + 0.3.2 + rocksdb, hbase, hstore @@ -59,53 +61,11 @@ - - org.apache.hugegraph - hugegraph-cassandra - ${revision} - - - metrics-core - io.dropwizard.metrics - - - - - org.apache.hugegraph - hugegraph-scylladb - ${revision} - org.apache.hugegraph hugegraph-rocksdb ${revision} - - org.apache.hugegraph - hugegraph-mysql - ${revision} - - - org.apache.hugegraph - hugegraph-palo - ${revision} - - - org.apache.hugegraph - hugegraph-hbase - ${revision} - - - org.apache.hugegraph - hugegraph-postgresql - ${revision} - - - org.apache.hugegraph - hugegraph-hstore - ${revision} - - org.apache.tinkerpop gremlin-console @@ -133,6 +93,13 @@ ${jansi.version} runtime + + + com.github.jbellis + jamm + ${jamm.version} + runtime + @@ -144,6 +111,16 @@ ${basedir}/src/main/resources false + + backend.properties + + + + ${basedir}/src/main/resources + true + + backend.properties + @@ -289,6 +266,68 @@ + + rocksdb-only + + + rocksdb-only + + + + rocksdb + + + + all-backends + + + !rocksdb-only + + + + + org.apache.hugegraph + hugegraph-cassandra + ${revision} + + + metrics-core + io.dropwizard.metrics + + + + + org.apache.hugegraph + hugegraph-scylladb + ${revision} + + + org.apache.hugegraph + hugegraph-mysql + ${revision} + + + org.apache.hugegraph + hugegraph-palo + ${revision} + + + org.apache.hugegraph + hugegraph-hbase + ${revision} + + + org.apache.hugegraph + hugegraph-postgresql + ${revision} + + + org.apache.hugegraph + hugegraph-hstore + ${revision} + + + assembly-hugegraph diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/dump-store.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/dump-store.sh index 425ad9f157..14b6ac1f79 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/dump-store.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/dump-store.sh @@ -30,16 +30,20 @@ TOP="$(cd $BIN/../ && pwd)" CONF=$TOP/conf LIB=$TOP/lib +. "${BIN}"/util.sh + +configure_riscv64_libatomic || exit 1 + if [ -n "$JAVA_HOME" ]; then JAVA="$JAVA_HOME"/bin/java else JAVA=java fi -conf=$1 if [ $# -eq 0 ]; then - conf=$CONF/hugegraph.properties + set -- "$CONF/graphs/hugegraph.properties" fi +conf=$1 cd $TOP @@ -47,5 +51,5 @@ echo "Dumping HugeGraph Store($conf)..." dump_store_ext_jar_path=$LIB/hugegraph-dist-*.jar for i in $LIB/*.jar; do dump_store_ext_jar_path=$dump_store_ext_jar_path:$i; export dump_store_ext_jar_path; done -exec $JAVA -cp dump_store_ext_jar_path \ -org.apache.hugegraph.cmd.StoreDumper $conf $2 $3 $4 +exec "$JAVA" -cp "$dump_store_ext_jar_path" \ +org.apache.hugegraph.cmd.StoreDumper "$@" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh index c5f84d4933..3915a5f63e 100644 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh @@ -45,6 +45,8 @@ GITHUB="https://github.com" export HUGEGRAPH_HOME="$TOP" . "${BIN}"/util.sh +configure_riscv64_libatomic || exit 1 + # Parse the server arguments in array way SERVER_ARGS=("$@") GREMLIN_SERVER_CONF="${SERVER_ARGS[0]:-}" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/init-store.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/init-store.sh index 9aefd1160b..b9da84166b 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/init-store.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/init-store.sh @@ -33,6 +33,7 @@ PLUGINS="$TOP/plugins" . "${BIN}"/util.sh +configure_riscv64_libatomic || exit 1 ensure_path_writable "${PLUGINS}" if [[ -n "$JAVA_HOME" ]]; then diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/stop-hugegraph.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/stop-hugegraph.sh index 7407746348..c63035e072 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/stop-hugegraph.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/stop-hugegraph.sh @@ -63,6 +63,8 @@ fi PID=$(cat $PID_FILE) -if kill_process_and_wait "HugeGraphServer" "$PID" "$SERVER_SHUTDOWN_TIMEOUT_S"; then - rm "$PID_FILE" +if ! kill_process_and_wait "HugeGraphServer" "$PID" "$SERVER_SHUTDOWN_TIMEOUT_S"; then + exit 1 fi + +rm "$PID_FILE" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/util.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/util.sh index e2655c69d3..91c0c3efbf 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/util.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/util.sh @@ -25,6 +25,45 @@ function command_available() { fi } +function configure_riscv64_libatomic() { + if [[ "$(uname -s)" != "Linux" || "$(uname -m)" != "riscv64" ]]; then + return 0 + fi + + if [[ "${LD_PRELOAD:-}" == *"libatomic.so.1"* ]]; then + return 0 + fi + + local libatomic="" + if command_available "ldconfig"; then + libatomic=$(ldconfig -p 2>/dev/null | \ + awk '/libatomic\.so\.1 .*=>/ && !path {path=$NF} + END {if (path) print path}') + fi + + if [[ -z "$libatomic" ]]; then + local candidate + for candidate in /lib/riscv64-linux-gnu/libatomic.so.1 \ + /usr/lib/riscv64-linux-gnu/libatomic.so.1 \ + /lib64/lp64d/libatomic.so.1 \ + /usr/lib64/lp64d/libatomic.so.1 \ + /lib64/libatomic.so.1 \ + /usr/lib64/libatomic.so.1; do + if [[ -r "$candidate" ]]; then + libatomic="$candidate" + break + fi + done + fi + + if [[ -z "$libatomic" ]]; then + echo "RISC-V RocksDB requires libatomic.so.1; install libatomic1" >&2 + return 1 + fi + + export LD_PRELOAD="${LD_PRELOAD:+${LD_PRELOAD}:}${libatomic}" +} + # read a property from .properties file function read_property() { # file path diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/check-rocksdb-only-dist.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/check-rocksdb-only-dist.sh new file mode 100755 index 0000000000..133739422d --- /dev/null +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/check-rocksdb-only-dist.sh @@ -0,0 +1,97 @@ +#!/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. +# + +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "USAGE: $0 SERVER_DIR" >&2 + exit 1 +fi + +SERVER_DIR=$1 +LIB_DIR="$SERVER_DIR/lib" +DIST_JAR="" +WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/hugegraph-dist-check.XXXXXX") + +cleanup() { + rm -rf "$WORK_DIR" +} +trap cleanup EXIT + +if [[ ! -d "$LIB_DIR" ]]; then + echo "Distribution lib directory does not exist: $LIB_DIR" >&2 + exit 1 +fi + +function require_jar() { + local pattern=$1 + if ! find "$LIB_DIR" -maxdepth 1 -type f -name "$pattern" -print -quit | grep -q .; then + echo "Missing required RocksDB distribution artifact: $pattern" >&2 + exit 1 + fi +} + +function forbid_jar() { + local pattern=$1 + local match + match=$(find "$LIB_DIR" -maxdepth 1 -type f -name "$pattern" -print -quit) + if [[ -n "$match" ]]; then + echo "Unrelated backend artifact found in RocksDB-only distribution: $match" >&2 + exit 1 + fi +} + +for pattern in hugegraph-core-*.jar \ + hugegraph-api-*.jar \ + hugegraph-rocksdb-*.jar \ + hugegraph-dist-*.jar \ + jamm-*.jar \ + rocksdbjni-*.jar; do + require_jar "$pattern" +done + +DIST_JAR=$(find "$LIB_DIR" -maxdepth 1 -type f \ + -name 'hugegraph-dist-*.jar' -print -quit) +DIST_JAR=$(cd "$(dirname "$DIST_JAR")" && pwd)/$(basename "$DIST_JAR") +if command -v unzip >/dev/null 2>&1; then + BACKEND_PROPERTIES=$(unzip -p "$DIST_JAR" backend.properties) +elif command -v jar >/dev/null 2>&1; then + (cd "$WORK_DIR" && jar xf "$DIST_JAR" backend.properties) + BACKEND_PROPERTIES=$(cat "$WORK_DIR/backend.properties") +else + echo "The distribution check requires unzip or a JDK jar command" >&2 + exit 1 +fi + +if ! grep -qx 'backends=\[rocksdb\]' <<< "$BACKEND_PROPERTIES"; then + echo "RocksDB-only distribution has an invalid backend registry" >&2 + printf '%s\n' "$BACKEND_PROPERTIES" >&2 + exit 1 +fi + +for pattern in hugegraph-cassandra-*.jar \ + hugegraph-scylladb-*.jar \ + hugegraph-mysql-*.jar \ + hugegraph-palo-*.jar \ + hugegraph-hbase-*.jar \ + hugegraph-postgresql-*.jar \ + hugegraph-hstore-*.jar; do + forbid_jar "$pattern" +done + +echo "RocksDB-only distribution contains the required backend and no unrelated backend JARs" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/rocksdb-jni-smoke.groovy b/hugegraph-server/hugegraph-dist/src/assembly/travis/rocksdb-jni-smoke.groovy new file mode 100644 index 0000000000..1baa856bf9 --- /dev/null +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/rocksdb-jni-smoke.groovy @@ -0,0 +1,55 @@ +// +// 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. +// + +import java.nio.charset.StandardCharsets +import java.util.Arrays + +import org.rocksdb.Options +import org.rocksdb.RocksDB + +rocksdbSmokePath = System.getenv('ROCKSDB_SMOKE_DIR') +if (!rocksdbSmokePath) { + throw new IllegalArgumentException('ROCKSDB_SMOKE_DIR is required') +} + +rocksdbSmokeKey = 'hugegraph-riscv64-key'.getBytes(StandardCharsets.UTF_8) +rocksdbSmokeExpected = 'hugegraph-riscv64-value'.getBytes(StandardCharsets.UTF_8) + +RocksDB.loadLibrary() + +rocksdbSmokeOptions = new Options().setCreateIfMissing(true) +rocksdbSmokeDb = null +try { + rocksdbSmokeDb = RocksDB.open(rocksdbSmokeOptions, rocksdbSmokePath) + rocksdbSmokeDb.put(rocksdbSmokeKey, rocksdbSmokeExpected) + def actual = rocksdbSmokeDb.get(rocksdbSmokeKey) + if (!Arrays.equals(rocksdbSmokeExpected, actual)) { + throw new IllegalStateException('RocksDB value differs after put/get') + } + rocksdbSmokeDb.close() + rocksdbSmokeDb = null + + rocksdbSmokeDb = RocksDB.open(rocksdbSmokeOptions, rocksdbSmokePath) + actual = rocksdbSmokeDb.get(rocksdbSmokeKey) + if (!Arrays.equals(rocksdbSmokeExpected, actual)) { + throw new IllegalStateException('RocksDB value differs after reopen') + } + println('rocksdb-jni-smoke-ok') +} finally { + rocksdbSmokeDb?.close() + rocksdbSmokeOptions.close() +} diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/run-native-runtime-smoke-test.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-native-runtime-smoke-test.sh new file mode 100755 index 0000000000..7cf09e30ee --- /dev/null +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-native-runtime-smoke-test.sh @@ -0,0 +1,91 @@ +#!/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. +# +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "USAGE: $0 SERVER_DIR" >&2 + exit 1 +fi + +TRAVIS_DIR=$(cd "$(dirname "$0")" && pwd) +SERVER_DIR=$(cd "$1" && pwd) +SERVER_URL=${SERVER_URL:-http://127.0.0.1:8080} +EXPECTED_ARCH=${EXPECTED_ARCH:-riscv64} +EXPECTED_JAVA_MAJOR=${EXPECTED_JAVA_MAJOR:-11} +SERVER_START_ATTEMPTED=false +SERVER_STARTUP_TIMEOUT=${SERVER_STARTUP_TIMEOUT:-300} +SERVER_START_COMMAND_TIMEOUT=$((SERVER_STARTUP_TIMEOUT + 30)) +STORE_DUMP_TIMEOUT=${STORE_DUMP_TIMEOUT:-120} +WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/hugegraph-native-runtime-smoke.XXXXXX") +RUN_ID="$(date +%s)_$$" + +export EXPECTED_ARCH EXPECTED_JAVA_MAJOR + +cleanup() { + local status=$? + trap - EXIT + if [[ "$SERVER_START_ATTEMPTED" == "true" ]]; then + "$SERVER_DIR/bin/stop-hugegraph.sh" -m false >/dev/null 2>&1 || status=1 + fi + rm -rf "$WORK_DIR" + exit "$status" +} +trap cleanup EXIT + +start_server() { + if ! command -v timeout >/dev/null 2>&1; then + echo "Required command is unavailable: timeout" >&2 + return 1 + fi + timeout --foreground --kill-after=15s \ + "${SERVER_START_COMMAND_TIMEOUT}s" \ + "$SERVER_DIR/bin/start-hugegraph.sh" -t "$SERVER_STARTUP_TIMEOUT" +} + +"$TRAVIS_DIR/run-rocksdb-jni-smoke-test.sh" "$SERVER_DIR" + +"$SERVER_DIR/bin/init-store.sh" +SERVER_START_ATTEMPTED=true +start_server +"$TRAVIS_DIR/run-server-e2e-smoke-test.sh" "$SERVER_URL" create "$RUN_ID" | \ + tee "$WORK_DIR/create.log" +grep -q '^server-e2e-smoke-create-ok$' "$WORK_DIR/create.log" + +"$SERVER_DIR/bin/stop-hugegraph.sh" -m false +timeout --foreground --kill-after=15s "${STORE_DUMP_TIMEOUT}s" \ + "$SERVER_DIR/bin/dump-store.sh" | tee "$WORK_DIR/store-dump.log" +grep -q 'Dump table VERTEX (offset 0 limit 20):' "$WORK_DIR/store-dump.log" +grep -Fq "riscv-smoke-v1-$RUN_ID" "$WORK_DIR/store-dump.log" +grep -Fq "riscv-smoke-v2-$RUN_ID" "$WORK_DIR/store-dump.log" +echo "store-dump-smoke-ok" + +start_server +"$TRAVIS_DIR/run-server-e2e-smoke-test.sh" "$SERVER_URL" verify "$RUN_ID" | \ + tee "$WORK_DIR/verify.log" +grep -q '^server-e2e-smoke-verify-ok$' "$WORK_DIR/verify.log" + +if [[ -d "$SERVER_DIR/logs" ]] && \ + grep -Eirq 'undefined symbol|UnsatisfiedLinkError|UnsupportedClassVersionError|NoClassDefFoundError' \ + "$SERVER_DIR/logs"; then + echo "Native linkage or Java compatibility error found in $SERVER_DIR/logs" >&2 + grep -Eirn 'undefined symbol|UnsatisfiedLinkError|UnsupportedClassVersionError|NoClassDefFoundError' \ + "$SERVER_DIR/logs" >&2 + exit 1 +fi + +echo "native-runtime-smoke-ok" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/run-rocksdb-jni-smoke-test.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-rocksdb-jni-smoke-test.sh new file mode 100755 index 0000000000..5603431d5d --- /dev/null +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-rocksdb-jni-smoke-test.sh @@ -0,0 +1,130 @@ +#!/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. +# +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "USAGE: $0 SERVER_DIR" >&2 + exit 1 +fi + +TRAVIS_DIR=$(cd "$(dirname "$0")" && pwd) +SERVER_DIR=$(cd "$1" && pwd) +EXPECTED_ARCH=${EXPECTED_ARCH:-} +EXPECTED_JAVA_MAJOR=${EXPECTED_JAVA_MAJOR:-11} +ACTUAL_ARCH=$(uname -m) + +if [[ -n "${JAVA_HOME:-}" ]]; then + JAVA_CMD="$JAVA_HOME/bin/java" +else + JAVA_CMD=$(command -v java || true) +fi +if [[ -z "$JAVA_CMD" || ! -x "$JAVA_CMD" ]]; then + echo "Java executable is unavailable: ${JAVA_CMD:-not found}" >&2 + exit 1 +fi + +if [[ -n "$EXPECTED_ARCH" && "$ACTUAL_ARCH" != "$EXPECTED_ARCH" ]]; then + echo "Expected architecture $EXPECTED_ARCH, got $ACTUAL_ARCH" >&2 + exit 1 +fi + +JAVA_VERSION=$("$JAVA_CMD" -version 2>&1 | \ + awk -F '"' '/version/ {print $2; exit}') +JAVA_MAJOR=${JAVA_VERSION%%.*} +if [[ "$JAVA_MAJOR" == "1" ]]; then + JAVA_MAJOR=$(echo "$JAVA_VERSION" | cut -d. -f2) +fi +if [[ "$JAVA_MAJOR" != "$EXPECTED_JAVA_MAJOR" ]]; then + echo "Expected Java $EXPECTED_JAVA_MAJOR, got $JAVA_VERSION" >&2 + exit 1 +fi + +JAVA_PROPERTIES=$("$JAVA_CMD" -XshowSettings:properties -version 2>&1) +java_property() { + local key=$1 + awk -F ' = ' -v key="$key" '{ + name = $1 + sub(/^[[:space:]]*/, "", name) + if (name == key) { + print $2 + exit + } + }' <<< "$JAVA_PROPERTIES" +} + +JAVA_VM_NAME=$(java_property java.vm.name) +JAVA_VM_VENDOR=$(java_property java.vm.vendor) +JAVA_VM_VERSION=$(java_property java.vm.version) +JAVA_VM_INFO=$(java_property java.vm.info) + +if [[ -z "$JAVA_VM_NAME" || -z "$JAVA_VM_VENDOR" || -z "$JAVA_VM_INFO" ]]; then + echo "Failed to read Java VM properties" >&2 + exit 1 +fi + +echo "Architecture: $ACTUAL_ARCH" +echo "Java: $JAVA_VERSION" +echo "Java VM: $JAVA_VM_NAME ($JAVA_VM_VENDOR, $JAVA_VM_VERSION, $JAVA_VM_INFO)" +if command -v getconf >/dev/null 2>&1; then + echo "C library: $(getconf GNU_LIBC_VERSION 2>/dev/null || echo unknown)" +fi + +. "$SERVER_DIR/bin/util.sh" +configure_riscv64_libatomic + +if [[ "$ACTUAL_ARCH" == "riscv64" ]]; then + EXPECTED_RISCV64_JAVA_VERSION=${EXPECTED_RISCV64_JAVA_VERSION:-11.0.31.28} + if [[ "$JAVA_VERSION" != "$EXPECTED_RISCV64_JAVA_VERSION" ]]; then + echo "Expected RISC-V Java $EXPECTED_RISCV64_JAVA_VERSION, got $JAVA_VERSION" >&2 + exit 1 + fi + if [[ "$JAVA_VM_NAME" != "OpenJDK 64-Bit Server VM" ]]; then + echo "Expected RISC-V Server VM, got $JAVA_VM_NAME" >&2 + exit 1 + fi + if [[ "$JAVA_VM_VENDOR" != "Alibaba" ]]; then + echo "Expected RISC-V Java vendor Alibaba, got $JAVA_VM_VENDOR" >&2 + exit 1 + fi + if [[ "$JAVA_VM_INFO" != *"mixed mode"* ]]; then + echo "Expected RISC-V Java mixed mode, got $JAVA_VM_INFO" >&2 + exit 1 + fi + if [[ "${LD_PRELOAD:-}" != *"libatomic.so.1"* ]]; then + echo "libatomic.so.1 was not added to LD_PRELOAD on riscv64" >&2 + exit 1 + fi + echo "LD_PRELOAD: $LD_PRELOAD" +fi + +WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/hugegraph-rocksdb-jni-smoke.XXXXXX") +SMOKE_LOG="$WORK_DIR/rocksdb-jni-smoke.log" +export ROCKSDB_SMOKE_DIR="$WORK_DIR/db" + +cleanup() { + rm -rf "$WORK_DIR" +} +trap cleanup EXIT + +"$JAVA_CMD" -cp "$SERVER_DIR/lib/*" groovy.ui.GroovyMain \ + "$TRAVIS_DIR/rocksdb-jni-smoke.groovy" | tee "$SMOKE_LOG" + +if ! grep -q '^rocksdb-jni-smoke-ok$' "$SMOKE_LOG"; then + echo "RocksDB JNI smoke marker not found" >&2 + exit 1 +fi diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/run-server-e2e-smoke-test.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-server-e2e-smoke-test.sh new file mode 100755 index 0000000000..9762e4fa26 --- /dev/null +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-server-e2e-smoke-test.sh @@ -0,0 +1,191 @@ +#!/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. +# +set -euo pipefail + +if [[ $# -ne 3 || ( "$2" != "create" && "$2" != "verify" ) ]]; then + echo "USAGE: $0 SERVER_URL create|verify RUN_ID" >&2 + exit 1 +fi + +SERVER_URL=${1%/} +MODE=$2 +RUN_ID=$3 + +if [[ ! "$RUN_ID" =~ ^[a-zA-Z0-9_]+$ ]]; then + echo "RUN_ID must contain only letters, numbers, and underscores" >&2 + exit 1 +fi + +GRAPH_PATH=/graphspaces/DEFAULT/graphs/hugegraph +WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/hugegraph-server-smoke.XXXXXX") +RESPONSE_FILE="$WORK_DIR/response.json" +PROPERTY_KEY="riscv_smoke_name_$RUN_ID" +VERTEX_LABEL="riscv_smoke_node_$RUN_ID" +EDGE_LABEL="riscv_smoke_link_$RUN_ID" +VERTEX_ONE="riscv-smoke-v1-$RUN_ID" +VERTEX_TWO="riscv-smoke-v2-$RUN_ID" +CURL_CONNECT_TIMEOUT=3 +CURL_PROBE_TIMEOUT=5 +CURL_REQUEST_TIMEOUT=60 +SERVER_READY_TIMEOUT=240 + +cleanup() { + rm -rf "$WORK_DIR" +} +trap cleanup EXIT + +for command in curl jq; do + if ! command -v "$command" >/dev/null 2>&1; then + echo "Required command is unavailable: $command" >&2 + exit 1 + fi +done + +if [[ -n "${HUGEGRAPH_USERNAME:-}" || -n "${HUGEGRAPH_PASSWORD:-}" ]]; then + if [[ -z "${HUGEGRAPH_USERNAME:-}" || -z "${HUGEGRAPH_PASSWORD:-}" ]]; then + echo "Set both HUGEGRAPH_USERNAME and HUGEGRAPH_PASSWORD" >&2 + exit 1 + fi +fi + +curl_request() { + if [[ -n "${HUGEGRAPH_USERNAME:-}" ]]; then + curl --compressed --user "$HUGEGRAPH_USERNAME:$HUGEGRAPH_PASSWORD" "$@" + else + curl --compressed "$@" + fi +} + +wait_for_server() { + local deadline=$((SECONDS + SERVER_READY_TIMEOUT)) + while ((SECONDS < deadline)); do + if curl_request --silent --show-error --fail \ + --connect-timeout "$CURL_CONNECT_TIMEOUT" \ + --max-time "$CURL_PROBE_TIMEOUT" \ + "$SERVER_URL/versions" >/dev/null 2>&1; then + return 0 + fi + sleep 1 + done + echo "HugeGraph did not become ready at $SERVER_URL" >&2 + return 1 +} + +request() { + local method=$1 + local path=$2 + local expected_status=$3 + local body=${4:-} + local status + local curl_args=(--silent --show-error --output "$RESPONSE_FILE" \ + --write-out '%{http_code}' --request "$method" \ + --connect-timeout "$CURL_CONNECT_TIMEOUT" \ + --max-time "$CURL_REQUEST_TIMEOUT" \ + --header 'Content-Type: application/json') + + if [[ -n "$body" ]]; then + curl_args+=(--data "$body") + fi + status=$(curl_request "${curl_args[@]}" "$SERVER_URL$path") + if [[ "$status" != "$expected_status" ]]; then + echo "$method $path returned HTTP $status, expected $expected_status" >&2 + cat "$RESPONSE_FILE" >&2 + return 1 + fi +} + +assert_json() { + local expression=$1 + shift + if ! jq --exit-status "$@" "$expression" "$RESPONSE_FILE" >/dev/null; then + echo "JSON assertion failed: $expression" >&2 + cat "$RESPONSE_FILE" >&2 + return 1 + fi +} + +verify_graph() { + request GET "$GRAPH_PATH/graph/vertices/%22$VERTEX_ONE%22" 200 + assert_json '.id == $id and .label == $label and .properties[$key] == "first"' \ + --arg id "$VERTEX_ONE" --arg label "$VERTEX_LABEL" \ + --arg key "$PROPERTY_KEY" + + request GET "$GRAPH_PATH/graph/vertices/%22$VERTEX_TWO%22" 200 + assert_json '.id == $id and .label == $label and .properties[$key] == "second"' \ + --arg id "$VERTEX_TWO" --arg label "$VERTEX_LABEL" \ + --arg key "$PROPERTY_KEY" + + request GET "$GRAPH_PATH/graph/edges" 200 + assert_json '.edges | any(.label == $label and .outV == $out and .inV == $in)' \ + --arg label "$EDGE_LABEL" --arg out "$VERTEX_ONE" \ + --arg in "$VERTEX_TWO" + + request POST /gremlin 200 \ + "$(jq -cn --arg query "g.V().hasLabel('$VERTEX_LABEL').count()" \ + '{gremlin:$query, bindings:{}, language:"gremlin-groovy", + aliases:{g:"__g_DEFAULT-hugegraph"}}')" + assert_json '.result.data == [2]' +} + +wait_for_server +request GET /versions 200 +assert_json 'type == "object" and length > 0' + +if [[ "$MODE" == "create" ]]; then + request POST "$GRAPH_PATH/schema/propertykeys" 202 \ + "$(jq -cn --arg name "$PROPERTY_KEY" \ + '{name:$name, data_type:"TEXT", cardinality:"SINGLE", properties:[]}')" + assert_json '.property_key.name == $name and .task_id == 0' \ + --arg name "$PROPERTY_KEY" + + request POST "$GRAPH_PATH/schema/vertexlabels" 201 \ + "$(jq -cn --arg name "$VERTEX_LABEL" --arg key "$PROPERTY_KEY" \ + '{name:$name, id_strategy:"CUSTOMIZE_STRING", properties:[$key], + primary_keys:[], nullable_keys:[]}')" + assert_json '.name == $name' --arg name "$VERTEX_LABEL" + + request POST "$GRAPH_PATH/schema/edgelabels" 201 \ + "$(jq -cn --arg name "$EDGE_LABEL" --arg label "$VERTEX_LABEL" \ + '{name:$name, source_label:$label, target_label:$label, + frequency:"SINGLE", properties:[], sort_keys:[], nullable_keys:[]}')" + assert_json '.name == $name' --arg name "$EDGE_LABEL" + + request POST "$GRAPH_PATH/graph/vertices" 201 \ + "$(jq -cn --arg id "$VERTEX_ONE" --arg label "$VERTEX_LABEL" \ + --arg key "$PROPERTY_KEY" \ + '{id:$id, label:$label, properties:{($key):"first"}}')" + assert_json '.id == $id' --arg id "$VERTEX_ONE" + + request POST "$GRAPH_PATH/graph/vertices" 201 \ + "$(jq -cn --arg id "$VERTEX_TWO" --arg label "$VERTEX_LABEL" \ + --arg key "$PROPERTY_KEY" \ + '{id:$id, label:$label, properties:{($key):"second"}}')" + assert_json '.id == $id' --arg id "$VERTEX_TWO" + + request POST "$GRAPH_PATH/graph/edges" 201 \ + "$(jq -cn --arg label "$EDGE_LABEL" --arg vertexLabel "$VERTEX_LABEL" \ + --arg out "$VERTEX_ONE" --arg in "$VERTEX_TWO" \ + '{label:$label, outVLabel:$vertexLabel, inVLabel:$vertexLabel, + outV:$out, inV:$in, properties:{}}')" + assert_json '.label == $label and .outV == $out and .inV == $in' \ + --arg label "$EDGE_LABEL" --arg out "$VERTEX_ONE" \ + --arg in "$VERTEX_TWO" +fi + +verify_graph +echo "server-e2e-smoke-$MODE-ok" diff --git a/hugegraph-server/hugegraph-dist/src/main/resources/backend.properties b/hugegraph-server/hugegraph-dist/src/main/resources/backend.properties index 45060f90c2..119ee02441 100644 --- a/hugegraph-server/hugegraph-dist/src/main/resources/backend.properties +++ b/hugegraph-server/hugegraph-dist/src/main/resources/backend.properties @@ -15,4 +15,4 @@ # limitations under the License. # -backends=[rocksdb, hbase, hstore] +backends=[${hugegraph.backends}]