diff --git a/.github/scripts/ci-policy.py b/.github/scripts/ci-policy.py index dd2fd27331..5b6f961e08 100644 --- a/.github/scripts/ci-policy.py +++ b/.github/scripts/ci-policy.py @@ -90,6 +90,9 @@ TRAVIS = "hugegraph-server/hugegraph-dist/src/assembly/travis/" # Contract tests follow their executing suites, without runtime consumer fan-out. CONTRACT_SUITES = { + TRAVIS + "test-check-java-runtime-logs.sh": {"struct"}, + TRAVIS + "check-java-runtime-logs.sh": {"server", "commons", "struct", "pd", "store", "hstore", "cluster", + "macos", "macos_full", "hbase", "riscv64"}, TRAVIS + "test-stop-hugegraph-store.sh": {"struct"}, TRAVIS + "test-check-jacoco-report.sh": {"struct"}, TRAVIS + "test-codecov-upload-config.sh": {"struct"}, @@ -238,13 +241,14 @@ def event_selection(project, paths, event, event_name): if preset not in PRESETS: raise ValueError("unknown test-set: " + str(preset)) return set(PRESETS[preset]) - selected = select(project, paths) if event_name == "push" and event.get("ref") == "refs/heads/master": + # Test contracts do not change deployed runtime inputs. + selected = select(project, [path for path in paths if path not in CONTRACT_SUITES]) smoke = selected.intersection({"docker", "helm", "dependency_license", "macos", "distributed_smoke"}) if selected.intersection({"server", "commons", "struct", "pd", "store", "hstore", "cluster"}): smoke.add("distributed_smoke") return smoke - return selected + return select(project, paths) def suites(project, selected): diff --git a/.github/scripts/test_ci_policy.py b/.github/scripts/test_ci_policy.py index a60486ebec..25067c20c7 100644 --- a/.github/scripts/test_ci_policy.py +++ b/.github/scripts/test_ci_policy.py @@ -373,6 +373,7 @@ def test_legacy_memory_bridge_requires_real_results_and_retires_without_a_runner def test_contract_scripts_reach_their_actual_inner_jobs_and_steps(self): workflows = Path(__file__).resolve().parents[1] / "workflows" cases = [("test-stop-hugegraph-store.sh", {"struct"}), + ("test-check-java-runtime-logs.sh", {"struct"}), ("test-check-jacoco-report.sh", {"struct"}), ("test-codecov-upload-config.sh", {"struct"}), ("test-riscv64-java-runtime.sh", {"server", "riscv64"})] @@ -397,7 +398,7 @@ def test_contract_scripts_reach_their_actual_inner_jobs_and_steps(self): condition = condition.replace("inputs.run-struct", str(inputs["struct"])) condition = condition.replace("inputs.run-store", str(inputs["store"])) self.assertTrue(eval(condition.replace("||", "or"), {"__builtins__": {}})) - step = next(step for step in struct.split("\n - ") if script in step) + step = next(step for step in struct.split("\n - ") if Path(script).name in step) self.assertNotRegex(step, r"(?m)^ if:") else: self.assertIn("server-rocksdb", callers) @@ -413,6 +414,17 @@ def test_contract_scripts_reach_their_actual_inner_jobs_and_steps(self): {"__builtins__": {}})) mixed = self.plan_for([policy.TRAVIS + "test-stop-hugegraph-store.sh", "hugegraph-pd/src/A.java"]) self.assertTrue({"struct", "pd", "store", "hstore", "cluster"}.issubset(mixed["selected"])) + checker = self.plan_for([policy.TRAVIS + "check-java-runtime-logs.sh"]) + self.assertEqual({"build-server", "server-rocksdb", "commons", "pd_store", "cluster", "compatibility"}, + self.coordinator_jobs(checker)) + self.assertTrue(checker["hbase"] and checker["riscv64"] and checker["macos_full"]) + master = {"ref": "refs/heads/master"} + for script in policy.CONTRACT_SUITES: + self.assertEqual([], self.plan_for([script], "push", master)["expected"]) + runtime = "hugegraph-server/hugegraph-core/src/main/A.java" + self.assertEqual(self.plan_for([runtime], "push", master)["selected"], + self.plan_for([runtime, policy.TRAVIS + "check-java-runtime-logs.sh"], + "push", master)["selected"]) def test_coordinator_reads_and_validates_real_java_input(self): workflow = (Path(__file__).resolve().parents[1] / "workflows/server-ci.yml").read_text() diff --git a/.github/workflows/cluster-test-ci.yml b/.github/workflows/cluster-test-ci.yml index 29c7c5bf66..85c4ef8881 100644 --- a/.github/workflows/cluster-test-ci.yml +++ b/.github/workflows/cluster-test-ci.yml @@ -39,13 +39,19 @@ jobs: - name: Run simple cluster test run: | + set -o pipefail + suite_log="$RUNNER_TEMP/simple-cluster-test.log" timeout 45m mvn test -pl hugegraph-cluster-test/hugegraph-clustertest-test \ - -am -P simple-cluster-test + -am -P simple-cluster-test 2>&1 | tee "$suite_log" + hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh "$suite_log" - name: Run multi cluster test run: | + set -o pipefail + suite_log="$RUNNER_TEMP/multi-cluster-test.log" timeout 45m mvn test -pl hugegraph-cluster-test/hugegraph-clustertest-test \ - -am -P multi-cluster-test + -am -P multi-cluster-test 2>&1 | tee "$suite_log" + hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh "$suite_log" - name: Show cluster diagnostics on failure if: failure() diff --git a/.github/workflows/commons-ci.yml b/.github/workflows/commons-ci.yml index d84c937af4..afb6945f06 100644 --- a/.github/workflows/commons-ci.yml +++ b/.github/workflows/commons-ci.yml @@ -17,6 +17,7 @@ jobs: runs-on: ubuntu-24.04 env: USE_STAGE: 'false' # Whether to include the stage repository. + TRAVIS_DIR: hugegraph-server/hugegraph-dist/src/assembly/travis strategy: fail-fast: false @@ -50,13 +51,19 @@ jobs: - name: Run common test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-commons/hugegraph-common -Dtest=UnitTestSuite \ - -DskipCommonsTests=false -Dsurefire.failIfNoSpecifiedTests=true + -DskipCommonsTests=false -Dsurefire.failIfNoSpecifiedTests=true 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run rpc test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-commons/hugegraph-rpc -Dtest=UnitTestSuite \ - -DskipCommonsTests=false -Dsurefire.failIfNoSpecifiedTests=true + -DskipCommonsTests=false -Dsurefire.failIfNoSpecifiedTests=true 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Upload coverage to Codecov uses: codecov/codecov-action@v7 diff --git a/.github/workflows/pd-store-ci.yml b/.github/workflows/pd-store-ci.yml index 00adccfe64..56edd7a85d 100644 --- a/.github/workflows/pd-store-ci.yml +++ b/.github/workflows/pd-store-ci.yml @@ -80,6 +80,7 @@ jobs: runs-on: ubuntu-24.04 env: USE_STAGE: 'false' + TRAVIS_DIR: hugegraph-server/hugegraph-dist/src/assembly/travis steps: - name: Checkout uses: actions/checkout@v7 @@ -97,6 +98,9 @@ jobs: - name: Verify Store stop failure propagation and PID retention run: bash hugegraph-server/hugegraph-dist/src/assembly/travis/test-stop-hugegraph-store.sh + - name: Run Java runtime log checker tests + run: bash "$TRAVIS_DIR/test-check-java-runtime-logs.sh" + - name: Run JaCoCo report validator tests run: hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-jacoco-report.sh @@ -129,7 +133,10 @@ jobs: - name: Run hugegraph-struct test if: ${{ hashFiles('hugegraph-struct/pom.xml') != '' }} run: | - mvn -U -ntp -pl hugegraph-struct test + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + mvn -U -ntp -pl hugegraph-struct test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" pd: if: inputs.run-pd @@ -176,21 +183,30 @@ jobs: # clean package so their JaCoCo data survives until the final report. - name: Run common test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-pd/hg-pd-test -am \ - -P pd-common-test -Djacoco.sessionId=pd-common-test + -P pd-common-test -Djacoco.sessionId=pd-common-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run core test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-pd/hg-pd-test -am \ - -P pd-core-test -Djacoco.sessionId=pd-core-test + -P pd-core-test -Djacoco.sessionId=pd-core-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Verify Surefire single-class routing run: | SUREFIRE_DIR=hugegraph-pd/hg-pd-test/target/surefire-reports REPORT="$SUREFIRE_DIR/TEST-org.apache.hugegraph.pd.core.PDConfigTest.xml" rm -f "$REPORT" + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-pd/hg-pd-test -am \ - -P pd-core-test -Dtest=PDConfigTest -ntp + -P pd-core-test -Dtest=PDConfigTest -ntp 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" test -s "$REPORT" || { echo "::error::PD single-class test produced no report" exit 1 @@ -251,16 +267,24 @@ jobs: - name: Prepare env and service run: | $TRAVIS_DIR/start-pd.sh + VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + echo "PD_DIR=hugegraph-pd/apache-hugegraph-pd-$VERSION" >> "$GITHUB_ENV" - name: Run client test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-pd/hg-pd-test -am \ - -P pd-client-test -Djacoco.sessionId=pd-client-test + -P pd-client-test -Djacoco.sessionId=pd-client-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" - name: Run rest test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-pd/hg-pd-test -am \ - -P pd-rest-test -Djacoco.sessionId=pd-rest-test + -P pd-rest-test -Djacoco.sessionId=pd-rest-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" - name: Generate aggregate coverage report run: | @@ -354,8 +378,11 @@ jobs: SUREFIRE_DIR=hugegraph-store/hg-store-test/target/surefire-reports REPORT="$SUREFIRE_DIR/TEST-org.apache.hugegraph.store.raftcore.ZeroByteStringHelperTest.xml" rm -f "$REPORT" + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ - -P store-raftcore-test -Dtest=ZeroByteStringHelperTest -ntp + -P store-raftcore-test -Dtest=ZeroByteStringHelperTest -ntp 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" test -s "$REPORT" || { echo "::error::Store single-class test produced no report" exit 1 @@ -414,43 +441,67 @@ jobs: run: | $TRAVIS_DIR/start-pd.sh $TRAVIS_DIR/start-store.sh + VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + echo "PD_DIR=hugegraph-pd/apache-hugegraph-pd-$VERSION" >> "$GITHUB_ENV" + echo "STORE_DIR=hugegraph-store/apache-hugegraph-store-$VERSION" >> "$GITHUB_ENV" - name: Run historical Store ingress tests run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-node -am \ -Dtest=StoredRowIngressTest -Dsurefire.failIfNoSpecifiedTests=false \ -Djacoco.sessionId=store-node-test \ - -Djacoco.destFile="$PWD/hugegraph-store/hg-store-test/target/store-node-test.exec" + -Djacoco.destFile="$PWD/hugegraph-store/hg-store-test/target/store-node-test.exec" 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run common test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ - -P store-common-test -Djacoco.sessionId=store-common-test + -P store-common-test -Djacoco.sessionId=store-common-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run client test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ - -P store-client-test -Djacoco.sessionId=store-client-test + -P store-client-test -Djacoco.sessionId=store-client-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run rocksdb test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ - -P store-rocksdb-test -Djacoco.sessionId=store-rocksdb-test + -P store-rocksdb-test -Djacoco.sessionId=store-rocksdb-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run raft-core test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ - -P store-raftcore-test -Djacoco.sessionId=store-raftcore-test + -P store-raftcore-test -Djacoco.sessionId=store-raftcore-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run core test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ - -P store-core-test -Djacoco.sessionId=store-core-test + -P store-core-test -Djacoco.sessionId=store-core-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run server lifecycle and shutdown tests run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ - -P store-server-test -Djacoco.sessionId=store-server-test + -P store-server-test -Djacoco.sessionId=store-server-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Generate aggregate coverage report run: | @@ -598,10 +649,16 @@ jobs: echo "[ci] open-file limit for HStore:" \ "soft=$(ulimit -Sn), hard=$(ulimit -Hn)" $TRAVIS_DIR/install-backend.sh $BACKEND + VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + echo "PD_DIR=hugegraph-pd/apache-hugegraph-pd-$VERSION" >> "$GITHUB_ENV" + echo "STORE_DIR=hugegraph-store/apache-hugegraph-store-$VERSION" >> "$GITHUB_ENV" - name: Run unit test run: | - $TRAVIS_DIR/run-unit-test.sh $BACKEND + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-unit-test.sh $BACKEND 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run core test # Bound stuck HStore scans so the diagnostic steps below can run @@ -610,16 +667,25 @@ jobs: env: HUGEGRAPH_TEST_TASK_LOG_LEVEL: WARN run: | - $TRAVIS_DIR/run-core-test.sh $BACKEND + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-core-test.sh $BACKEND 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run api test run: | - $TRAVIS_DIR/run-api-test.sh $BACKEND $REPORT_DIR + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-api-test.sh $BACKEND $REPORT_DIR 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run raft test if: ${{ env.BACKEND == 'rocksdb' }} run: | - $TRAVIS_DIR/run-api-test-for-raft.sh $BACKEND $REPORT_DIR + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-api-test-for-raft.sh $BACKEND $REPORT_DIR 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" # Include HStore in the upgrade compatibility gate. The dedicated HStore # service setup above makes its structure/process suites exercise the @@ -632,9 +698,11 @@ jobs: env: HUGEGRAPH_TEST_TASK_LOG_LEVEL: WARN run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") # Emit lightweight samples during execution: a lost runner cannot # upload the failure-only diagnostics below. - python3 - "$TRAVIS_DIR/run-tinkerpop-test.sh" "$BACKEND" structure <<'PYTHON' + python3 - "$TRAVIS_DIR/run-tinkerpop-test.sh" "$BACKEND" structure 2>&1 <<'PYTHON' | tee "$RUNTIME_LOG" import datetime import os import pathlib @@ -677,18 +745,25 @@ jobs: result = test.returncode sys.exit(result if result >= 0 else 128 - result) PYTHON + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run TinkerPop process standard test if: ${{ env.RUN_TINKERPOP_TESTS == 'true' }} timeout-minutes: 120 run: | - $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND process-standard + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND process-standard 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run TinkerPop process feature test if: ${{ env.RUN_TINKERPOP_TESTS == 'true' }} timeout-minutes: 120 run: | - $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND process-feature + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND process-feature 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Collect HStore diagnostics on failure or timeout if: ${{ failure() || cancelled() }} diff --git a/.github/workflows/riscv64-ci.yml b/.github/workflows/riscv64-ci.yml index 634251fb4e..9b8bce79b0 100644 --- a/.github/workflows/riscv64-ci.yml +++ b/.github/workflows/riscv64-ci.yml @@ -106,10 +106,10 @@ jobs: -P riscv64-protobuf-tools \ -pl hugegraph-server/hugegraph-dist -am \ -Dmaven.test.skip=true -Dmaven.javadoc.skip=true - VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) - SERVER_DIR="hugegraph-server/apache-hugegraph-server-$VERSION" - hugegraph-server/hugegraph-dist/src/assembly/travis/check-rocksdb-only-dist.sh "$SERVER_DIR" - hugegraph-server/hugegraph-dist/src/assembly/travis/run-native-runtime-smoke-test.sh "$SERVER_DIR" + 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 diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index f86d0904bb..c027d6c521 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -109,8 +109,11 @@ jobs: SUREFIRE_DIR=hugegraph-server/hugegraph-test/target/surefire-reports REPORT="$SUREFIRE_DIR/TEST-org.apache.hugegraph.unit.auth.HugeGraphAuthProxyTest.xml" rm -f "$REPORT" + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-server/hugegraph-test -am \ - -P unit-test -Dtest=HugeGraphAuthProxyTest -ntp + -P unit-test -Dtest=HugeGraphAuthProxyTest -ntp 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" test -s "$REPORT" || { echo "::error::Server single-class test produced no report" exit 1 @@ -168,36 +171,58 @@ jobs: - name: Run unit test run: | - $TRAVIS_DIR/run-unit-test.sh $BACKEND + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-unit-test.sh $BACKEND 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run core test run: | - $TRAVIS_DIR/run-core-test.sh $BACKEND + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-core-test.sh $BACKEND 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run api test run: | - if [ "$BACKEND" = "rocksdb" ]; then - $TRAVIS_DIR/run-api-test.sh $BACKEND $REPORT_DIR true - else - $TRAVIS_DIR/run-api-test.sh $BACKEND $REPORT_DIR - fi + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + { + if [ "$BACKEND" = "rocksdb" ]; then + $TRAVIS_DIR/run-api-test.sh $BACKEND $REPORT_DIR true + else + $TRAVIS_DIR/run-api-test.sh $BACKEND $REPORT_DIR + fi + } 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" # TODO: disable raft test in normal PR due to the always timeout problem - name: Run raft test if: ${{ env.RAFT_MODE == 'true' && env.BACKEND == 'rocksdb' }} timeout-minutes: 45 run: | - $TRAVIS_DIR/run-api-test-for-raft.sh $BACKEND $REPORT_DIR + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-api-test-for-raft.sh $BACKEND $REPORT_DIR 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run TinkerPop structure test if: ${{ env.RUN_TINKERPOP_TESTS == 'true' && (env.BACKEND == 'memory' || env.BACKEND == 'rocksdb') }} timeout-minutes: 60 - run: $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND structure + run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND structure 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run TinkerPop process test if: ${{ env.RUN_TINKERPOP_TESTS == 'true' && (env.BACKEND == 'memory' || env.BACKEND == 'rocksdb') }} timeout-minutes: 180 - run: $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND process + run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND process 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Upload coverage to Codecov uses: codecov/codecov-action@v7 diff --git a/docs/ci.md b/docs/ci.md index a6dd9fc1e5..d426ac4950 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -75,6 +75,27 @@ does not accept a PR number, apply labels or reuse another workflow's results. Automatic retries are disabled. Use GitHub's native rerun controls for failed jobs or a run; a rerun retains its original commit and event, subject to the preflight freshness checks. +## Java runtime compatibility + +A positive suite passes only when its tests succeed and its current-run output has no +Java runtime compatibility errors. Server, PD/Store, Commons, Struct and Cluster test +steps capture both stdout and stderr before running `check-java-runtime-logs.sh`. +Startup and Console smoke tests also inspect their output before cleanup. Cluster nodes +forward their text runtime logs to the captured suite output before deletion. +Missing or unreadable required logs fail the check. + +The gate rejects denied module access and unknown or removed JVM options, including +exceptions caught and printed by dependencies. Ordinary business errors and deliberate +negative fixtures are not compatibility failures. Native smoke additionally uses `--native` +for its existing native linkage checks. + +Launchers and tests share each component's `jvm-module.options`: PD and Store open +`java.lang` for Hessian, while Console and dump-store reuse Server's existing options. +Required module options are independent of custom `JAVA_OPTIONS` or `-j` flags; Surefire +retains the JaCoCo agent. CI continues to use the project Java runtime and its existing +suite-selection and required-check policy. + + ## Freshness, runner use and budgets PR selection uses the cumulative diff against the checked-out merge commit's base. A source diff --git a/docs/server-runtime.md b/docs/server-runtime.md index f370951a7f..b212664afa 100644 --- a/docs/server-runtime.md +++ b/docs/server-runtime.md @@ -11,6 +11,25 @@ The runtime uses TinkerPop 3.8.1 and Groovy 4.0.25. Build and run every module w 17. Applications migrating from TinkerPop 3.5.1 must review the full version interval, including the configuration changes below. +### Required JVM module options + +Server, store initialization, store dumping and Gremlin Console always load the +distribution's `bin/jvm-module.options`. Keep this file with the shipped scripts; +each launcher rejects a missing or unreadable file. Required module permissions +are independent of caller options, so custom `JAVA_OPTIONS` or the Server launcher's +`-j` option do not replace them. Gremlin Console retains its existing +`JAVA_OPTIONS` defaults; store dumping retains its existing command-line interface. +The shared file opens `java.nio` for HBase's reflective `java.nio.Bits.unaligned()` +check during `HBasePlatformDependent` initialization, including on x86 Java 17. +It also opens `java.lang` for SOFA RPC's `ProviderInvoker` initialization, which +reflects on `Throwable.cause`. + +PD and Store use their own `bin/jvm-module.options` for the module permission needed +by Hessian serialization on Java 17. Their tests and cluster launchers use the same +component files. CI checks positive-test and startup logs for Java module-access +errors and unsupported JVM options, including errors printed without failing the +operation. + ### Launcher GC options Use `bin/start-hugegraph.sh -g g1` (or `-g G1`) to select G1 explicitly. diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/AbstractNodeWrapper.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/AbstractNodeWrapper.java index 81d3a73923..15efdfc8b4 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/AbstractNodeWrapper.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/AbstractNodeWrapper.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.NoSuchFileException; @@ -160,6 +161,13 @@ public boolean isStarted() { return false; } + protected String moduleOptions(Path options) throws IOException { + if (!Files.isRegularFile(options) || !Files.isReadable(options)) { + throw new IOException("Missing or unreadable JVM module options: " + options); + } + return "@" + options.toAbsolutePath(); + } + public void stop() { if (this.instance == null) { return; @@ -167,8 +175,9 @@ public void stop() { if (!this.instance.isAlive()) { System.out.printf("[cluster-test] %s stopped unexpectedly: %s%n", getID(), processStatus()); - dumpLog(); - deleteDir(); + if (dumpRuntimeLogs()) { + deleteDir(); + } return; } this.instance.destroy(); @@ -180,7 +189,37 @@ public void stop() { Thread.currentThread().interrupt(); LOG.error("Waiting node to shutdown error.", e); } - deleteDir(); + if (dumpRuntimeLogs()) { + deleteDir(); + } + } + + private boolean dumpRuntimeLogs() { + Path startupLog = Paths.get(getLogPath()); + try { + if (this.instance.isAlive() || !Files.isReadable(startupLog)) { + throw new IOException("Node still running or startup log unavailable: " + getID()); + } + // Stream complete text logs into the suite output before deletion. + // The suite's existing checker validates them without an archive. + try (Stream logs = Files.walk(startupLog.getParent())) { + for (Path log : (Iterable) logs.filter(Files::isRegularFile)::iterator) { + if (!log.getFileName().toString().matches(".*(\\.log(\\.[0-9]+)?|\\.out|\\.err|\\.txt)")) { + continue; + } + System.out.println("[cluster-test] runtime log: " + log); + try (Stream lines = Files.lines(log, StandardCharsets.UTF_8)) { + lines.forEach(System.out::println); + } + } + } + return true; + } catch (IOException | UncheckedIOException e) { + // Keep the original files and continue stopping the other nodes. + // This explicit marker makes the suite's log checker fail. + System.out.println("Unable to inspect cluster runtime logs: " + e.getMessage()); + return false; + } } public boolean isAlive() { diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/PDNodeWrapper.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/PDNodeWrapper.java index e7a9e0f24f..43ecc2be6c 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/PDNodeWrapper.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/PDNodeWrapper.java @@ -17,7 +17,9 @@ package org.apache.hugegraph.ct.node; +import static org.apache.hugegraph.ct.base.ClusterConstant.BIN_DIR; import static org.apache.hugegraph.ct.base.ClusterConstant.CONF_DIR; +import static org.apache.hugegraph.ct.base.ClusterConstant.JVM_MODULE_OPTIONS_FILE; import static org.apache.hugegraph.ct.base.ClusterConstant.JAVA_CMD; import static org.apache.hugegraph.ct.base.ClusterConstant.LOG4J_FILE; import static org.apache.hugegraph.ct.base.ClusterConstant.PD_JAR_PREFIX; @@ -67,6 +69,9 @@ public void start() { return; } + startCmd.add(moduleOptions(Paths.get(workPath).getParent() + .resolve(BIN_DIR).resolve(JVM_MODULE_OPTIONS_FILE))); + String pdNodeJarPath = getFileInDir(workPath, PD_JAR_PREFIX); startCmd.addAll(Arrays.asList( "-Dname=HugeGraphPD" + this.index, diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/ServerNodeWrapper.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/ServerNodeWrapper.java index bc093cb801..99f31dbf4f 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/ServerNodeWrapper.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/ServerNodeWrapper.java @@ -149,8 +149,8 @@ public void start() { startCmd.addAll(Arrays.asList( "-Dname=HugeGraphServer" + this.index, - "@" + Paths.get(SERVER_PACKAGE_PATH, BIN_DIR, - JVM_MODULE_OPTIONS_FILE), + moduleOptions(Paths.get(SERVER_PACKAGE_PATH, BIN_DIR, + JVM_MODULE_OPTIONS_FILE)), "-cp", storeClassPath, "org.apache.hugegraph.dist.HugeGraphServer", "./conf/gremlin-server.yaml", diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/StoreNodeWrapper.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/StoreNodeWrapper.java index d76ceaa0b8..ed7225f5a6 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/StoreNodeWrapper.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/StoreNodeWrapper.java @@ -17,7 +17,9 @@ package org.apache.hugegraph.ct.node; +import static org.apache.hugegraph.ct.base.ClusterConstant.BIN_DIR; import static org.apache.hugegraph.ct.base.ClusterConstant.CONF_DIR; +import static org.apache.hugegraph.ct.base.ClusterConstant.JVM_MODULE_OPTIONS_FILE; import static org.apache.hugegraph.ct.base.ClusterConstant.JAVA_CMD; import static org.apache.hugegraph.ct.base.ClusterConstant.LOG4J_FILE; import static org.apache.hugegraph.ct.base.ClusterConstant.STORE_JAR_PREFIX; @@ -64,6 +66,9 @@ public void start() { return; } + startCmd.add(moduleOptions(Paths.get(workPath).getParent() + .resolve(BIN_DIR).resolve(JVM_MODULE_OPTIONS_FILE))); + String storeNodeJarPath = getFileInDir(workPath, STORE_JAR_PREFIX); startCmd.addAll(Arrays.asList( "-Dname=HugeGraphStore" + this.index, diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/AbstractNodeWrapperTest.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/AbstractNodeWrapperTest.java index 2d452f04d3..485810112b 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/AbstractNodeWrapperTest.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/AbstractNodeWrapperTest.java @@ -19,10 +19,12 @@ import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; import java.io.PrintStream; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.concurrent.TimeUnit; @@ -80,6 +82,58 @@ public void testStopDumpsLogOfUnexpectedlyExitedProcess() throws Exception { Assert.assertTrue(diagnostics, diagnostics.contains(failure)); } + @Test + public void testModuleOptionsRejectMissingFile() throws Exception { + TestNodeWrapper wrapper = new TestNodeWrapper(newNodeDirectory()); + Path options = temporaryFolder.newFile("jvm-module.options").toPath(); + Assert.assertEquals("@" + options.toAbsolutePath(), wrapper.moduleOptions(options)); + Files.delete(options); + Assert.assertThrows(IOException.class, () -> wrapper.moduleOptions(options)); + } + + @Test + public void testStopForwardsStartupLogBeforeCleanup() throws Exception { + File nodeDirectory = newNodeDirectory(); + TestNodeWrapper wrapper = new TestNodeWrapper(nodeDirectory); + Files.writeString(Paths.get(wrapper.getLogPath()), + "startup diagnostic\n" + "operation succeeded\n".repeat(250)); + wrapper.attach(startSleeperProcess()); + PrintStream originalOut = System.out; + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try { + System.setOut(new PrintStream(output, true, StandardCharsets.UTF_8.name())); + wrapper.stop(); + } finally { + System.setOut(originalOut); + } + Assert.assertTrue(output.toString(StandardCharsets.UTF_8.name()).contains("startup diagnostic")); + Assert.assertFalse(nodeDirectory.exists()); + } + + @Test + public void testStopRetainsDirectoryWhenLogIsUnavailable() throws Exception { + for (boolean malformed : new boolean[]{false, true}) { + File nodeDirectory = newNodeDirectory(); + TestNodeWrapper wrapper = new TestNodeWrapper(nodeDirectory); + if (malformed) { + Files.write(Paths.get(wrapper.getLogPath()), new byte[]{(byte) 0xff}); + } + wrapper.attach(startSleeperProcess()); + PrintStream originalOut = System.out; + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try { + System.setOut(new PrintStream(output, true, StandardCharsets.UTF_8.name())); + wrapper.stop(); + } finally { + System.setOut(originalOut); + } + Assert.assertFalse(wrapper.isAlive()); + Assert.assertTrue(nodeDirectory.exists()); + Assert.assertTrue(output.toString(StandardCharsets.UTF_8.name()) + .contains("Unable to inspect cluster runtime logs:")); + } + } + private File newNodeDirectory() throws Exception { File nodeDirectory = temporaryFolder.newFolder(); File logDirectory = new File(nodeDirectory, "logs"); diff --git a/hugegraph-pd/README.md b/hugegraph-pd/README.md index 3e6ea03289..9d556bd7d7 100644 --- a/hugegraph-pd/README.md +++ b/hugegraph-pd/README.md @@ -91,6 +91,10 @@ bin/start-hugegraph-pd.sh [-g GC_TYPE] [-j "JVM_OPTIONS"] [-y ENABLE_OTEL] [-d D - `-y`: Enable OpenTelemetry tracing (`true` or `false`, default: `false`) - `-d`: Daemon mode (`true` = daemon, `false` = foreground; default: `true`). Set to `false` when running under Docker or a process supervisor so the container exits if Java dies. +The launcher always loads `bin/jvm-module.options`, including with custom `JAVA_OPTIONS` or `-j`. +Keep this file in the distribution: it grants the Java 17 access required by Hessian Raft serialization; +startup fails if the file is missing or unreadable. Direct Java launches must include `@bin/jvm-module.options`. + ### Configuration Key configuration file: `conf/application.yml` diff --git a/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/jvm-module.options b/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/jvm-module.options new file mode 100644 index 0000000000..b41209f448 --- /dev/null +++ b/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/jvm-module.options @@ -0,0 +1,19 @@ +# +# 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. +# + +# Hessian reflects into StackTraceElement when serializing Raft operations. +--add-opens=java.base/java.lang=ALL-UNNAMED diff --git a/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/start-hugegraph-pd.sh b/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/start-hugegraph-pd.sh index 10d23f17d6..d687db42cb 100755 --- a/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/start-hugegraph-pd.sh +++ b/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/start-hugegraph-pd.sh @@ -60,6 +60,12 @@ LOGS="$TOP/logs" OUTPUT=${LOGS}/hugegraph-pd-stdout.log GITHUB="https://github.com" PID_FILE="$BIN/pid" +JVM_MODULE_OPTIONS="$BIN/jvm-module.options" + +if [[ ! -f "$JVM_MODULE_OPTIONS" || ! -r "$JVM_MODULE_OPTIONS" ]]; then + echo "Error: JVM module options file is missing or unreadable: $JVM_MODULE_OPTIONS" >&2 + exit 1 +fi . "$BIN"/util.sh @@ -193,10 +199,10 @@ fi if [[ $DAEMON == "true" ]]; then echo "Starting HugeGraphPDServer in daemon mode..." if [[ "${STDOUT_MODE:-false}" == "true" ]]; then - exec ${JAVA} -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + exec ${JAVA} "@$JVM_MODULE_OPTIONS" -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ -Dspring.config.location=${CONF}/application.yml "${JAVA_MAIN[@]}" "$@" & else - exec ${JAVA} -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + exec ${JAVA} "@$JVM_MODULE_OPTIONS" -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ -Dspring.config.location=${CONF}/application.yml "${JAVA_MAIN[@]}" "$@" >> ${OUTPUT} 2>&1 & fi PID="$!" @@ -209,10 +215,10 @@ else echo "$$" > "$PID_FILE" echo "[+pid] $$" if [[ "${STDOUT_MODE:-false}" == "true" ]]; then - exec ${JAVA} -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + exec ${JAVA} "@$JVM_MODULE_OPTIONS" -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ -Dspring.config.location=${CONF}/application.yml "${JAVA_MAIN[@]}" "$@" else - exec ${JAVA} -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + exec ${JAVA} "@$JVM_MODULE_OPTIONS" -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ -Dspring.config.location=${CONF}/application.yml "${JAVA_MAIN[@]}" "$@" >> ${OUTPUT} 2>&1 fi fi diff --git a/hugegraph-pd/hg-pd-test/pom.xml b/hugegraph-pd/hg-pd-test/pom.xml index a248bcb87b..bca134ce4e 100644 --- a/hugegraph-pd/hg-pd-test/pom.xml +++ b/hugegraph-pd/hg-pd-test/pom.xml @@ -166,6 +166,12 @@ org.apache.maven.plugins maven-surefire-plugin + + + @{argLine} + "@${project.basedir}/../hg-pd-dist/src/assembly/static/bin/jvm-module.options" + + org.apache.maven.surefire 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 f612557e82..eb05bb4568 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 @@ -29,6 +29,12 @@ BIN=`abs_path` TOP="$(cd $BIN/../ && pwd)" CONF=$TOP/conf LIB=$TOP/lib +JVM_MODULE_OPTIONS="${BIN}/jvm-module.options" + +if [[ ! -r "${JVM_MODULE_OPTIONS}" ]]; then + echo "Missing or unreadable JVM module options file: ${JVM_MODULE_OPTIONS}" >&2 + exit 1 +fi . "${BIN}"/util.sh @@ -52,5 +58,6 @@ 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 source "$BIN/preload-topling.sh" || exit 1 -exec "$JAVA" -cp "${TOPLING_RUNTIME_CLASSPATH:+$TOPLING_RUNTIME_CLASSPATH:}$dump_store_ext_jar_path" \ +exec "$JAVA" @"${JVM_MODULE_OPTIONS}" \ +-cp "${TOPLING_RUNTIME_CLASSPATH:+$TOPLING_RUNTIME_CLASSPATH:}$dump_store_ext_jar_path" \ org.apache.hugegraph.cmd.StoreDumper "$@" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/gremlin-console.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/gremlin-console.sh index 7a92b20068..40ec2ff7d9 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/gremlin-console.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/gremlin-console.sh @@ -30,6 +30,11 @@ while [ -h "$SOURCE" ]; do [[ $SOURCE != /* ]] && SOURCE="$BIN/$SOURCE" done BIN="$( cd -P "$( dirname "$SOURCE" )" && pwd )" +JVM_MODULE_OPTIONS="${BIN}/jvm-module.options" +if [[ ! -r "${JVM_MODULE_OPTIONS}" ]]; then + echo "Missing or unreadable JVM module options file: ${JVM_MODULE_OPTIONS}" >&2 + exit 1 +fi # Set $CFG to $BIN/../conf cd -P $BIN/../conf CFG=$(pwd) @@ -122,4 +127,4 @@ if [ -n "$SCRIPT_DEBUG" ]; then fi # Start the JVM, execute the application, and return its exit code -exec $JAVA $JAVA_OPTIONS $MAIN_CLASS "$@" +exec $JAVA @"${JVM_MODULE_OPTIONS}" $JAVA_OPTIONS $MAIN_CLASS "$@" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/jvm-module.options b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/jvm-module.options index eeb6114c5c..9da05e1d8e 100644 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/jvm-module.options +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/jvm-module.options @@ -18,3 +18,7 @@ --add-exports=java.base/jdk.internal.reflect=ALL-UNNAMED --add-modules=jdk.unsupported --add-exports=java.base/sun.nio.ch=ALL-UNNAMED +# HBasePlatformDependent reflects on java.nio.Bits.unaligned() during initialization. +--add-opens=java.base/java.nio=ALL-UNNAMED +# SOFA RPC ProviderInvoker reflects on Throwable.cause during initialization. +--add-opens=java.base/java.lang=ALL-UNNAMED diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh new file mode 100755 index 0000000000..1d51d0ccaa --- /dev/null +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh @@ -0,0 +1,78 @@ +#!/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 -uo pipefail + +# Inspect only logs from a positive run, never a negative-test fixture or a +# whole workspace. Libraries can print these exceptions and still return 0. +PATTERN='java\.lang\.reflect\.InaccessibleObjectException:' +PATTERN+='|java\.lang\.(IllegalAccessError|IllegalAccessException):.*module .*does not' +PATTERN+='|Unrecognized VM option|Unrecognized option:' +PATTERN+='|Ignoring option .*support was removed|VM option .*is (not supported|obsolete|removed)' +PATTERN+='|Unable to inspect cluster runtime logs:' +if [[ "${1:-}" == --native ]]; then + # Keep native-linkage checks out of suites with intentional missing classes. + PATTERN+='|undefined symbol|UnsatisfiedLinkError|UnsupportedClassVersionError|NoClassDefFoundError' + shift +fi +if [[ $# -eq 0 ]]; then + echo "USAGE: $0 [--native] LOG_FILE_OR_DIRECTORY..." >&2 + exit 2 +fi + +STATUS=0 +check_file() { + local file="$1" matches code + if [[ ! -f "$file" || ! -r "$file" ]]; then + echo "Cannot read runtime log: $file" >&2 + STATUS=2 + return + fi + # Scan text logs even when logged keys contain NUL bytes. + matches=$(LC_ALL=C grep -Eina -- "$PATTERN" "$file" 2>&1) + code=$? + if [[ $code -eq 0 ]]; then + echo "Java runtime compatibility error in $file:" >&2 + printf '%s\n' "$matches" >&2 + [[ $STATUS -eq 2 ]] || STATUS=1 + elif [[ $code -ne 1 ]]; then + printf '%s\n' "$matches" >&2 + STATUS=2 + fi +} + +for INPUT in "$@"; do + if [[ -d "$INPUT" ]]; then + # Exclude heap dumps and compressed rotations, even if they contain + # exception class names. Explicit file inputs can use any text suffix. + FILES=$(find "$INPUT" -type f \ + \( -name '*.log' -o -name '*.log.[0-9]*' -o -name '*.out' -o -name '*.err' -o -name '*.txt' \) \ + ! -name '*.gz' ! -name '*.zip' ! -name '*.bz2' ! -name '*.xz' ! -name '*.hprof*' -print) + if [[ $? -ne 0 || -z "$FILES" ]]; then + echo "Cannot collect runtime logs: $INPUT" >&2 + STATUS=2 + continue + fi + while IFS= read -r FILE; do + check_file "$FILE" + done <<<"$FILES" + else + check_file "$INPUT" + fi +done +exit "$STATUS" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/run-api-test-for-raft.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-api-test-for-raft.sh index a48894728e..e884727ac7 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/run-api-test-for-raft.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-api-test-for-raft.sh @@ -65,3 +65,6 @@ $TRAVIS_DIR/build-report.sh $BACKEND $JACOCO_PORT $REPORT_FILE $TRAVIS_DIR/stop-server.sh $RAFT1_DIR $TRAVIS_DIR/stop-server.sh $RAFT2_DIR $TRAVIS_DIR/stop-server.sh $RAFT3_DIR + +# Inspect service output after shutdown so background failures cannot pass the suite. +bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RAFT1_DIR/logs" "$RAFT2_DIR/logs" "$RAFT3_DIR/logs" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/run-api-test.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-api-test.sh index 527f9120ed..30520e2cc5 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/run-api-test.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-api-test.sh @@ -104,3 +104,6 @@ $TRAVIS_DIR/build-report.sh $BACKEND $JACOCO_PORT $REPORT_FILE # stop server $TRAVIS_DIR/stop-server.sh $SERVER_DIR + +# Inspect service output after shutdown so background failures cannot pass the suite. +bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$SERVER_DIR/logs" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/run-gremlin-console-smoke-test.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-gremlin-console-smoke-test.sh index 35066abe58..7380a73cc6 100644 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/run-gremlin-console-smoke-test.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-gremlin-console-smoke-test.sh @@ -20,6 +20,7 @@ set -euo pipefail SERVER_DIR=${1:?Usage: run-gremlin-console-smoke-test.sh } SMOKE_MARKER="gremlin-console-smoke-ok" REMOTE_CONFIG="$SERVER_DIR/conf/remote.yaml" +TRAVIS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) if [ ! -d "$SERVER_DIR" ]; then echo "Server directory does not exist: $SERVER_DIR" @@ -60,6 +61,9 @@ EOF cat > "$SMOKE_SCRIPT" <&1 | tee "$SMOKE_LOG" +bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$SMOKE_LOG" +grep -q "gremlin-console-auth-ok" "$SMOKE_LOG" grep -q "$SMOKE_MARKER" "$SMOKE_LOG" grep -q "gremlin-console-gstring-ok" "$SMOKE_LOG" 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 index 524b478846..f5c8d697ca 100755 --- 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 @@ -27,6 +27,7 @@ SERVER_DIR=$(cd "$1" && pwd) SERVER_URL=${SERVER_URL:-http://127.0.0.1:8080} EXPECTED_ARCH=${EXPECTED_ARCH:-riscv64} SERVER_START_ATTEMPTED=false +SERVER_START_COUNT=0 SERVER_STARTUP_TIMEOUT=${SERVER_STARTUP_TIMEOUT:-300} SERVER_START_COMMAND_TIMEOUT=$((SERVER_STARTUP_TIMEOUT + 30)) STORE_DUMP_TIMEOUT=${STORE_DUMP_TIMEOUT:-120} @@ -59,14 +60,20 @@ start_server() { if [[ -n "${SERVER_JAVA_OPTIONS:-}" ]]; then start_options+=(-j "$SERVER_JAVA_OPTIONS") fi + SERVER_START_COUNT=$((SERVER_START_COUNT + 1)) + local startup_log="$WORK_DIR/startup-$SERVER_START_COUNT.log" status=0 + # Daemon stdout must go to a file so a pipe reader does not wait for JVM exit. "$TIMEOUT_COMMAND" --foreground --kill-after=15s \ "${SERVER_START_COMMAND_TIMEOUT}s" \ - "$SERVER_DIR/bin/start-hugegraph.sh" "${start_options[@]}" + "$SERVER_DIR/bin/start-hugegraph.sh" "${start_options[@]}" \ + >"$startup_log" 2>&1 || status=$? + cat "$startup_log" + return "$status" } -"$TRAVIS_DIR/run-rocksdb-jni-smoke-test.sh" "$SERVER_DIR" +"$TRAVIS_DIR/run-rocksdb-jni-smoke-test.sh" "$SERVER_DIR" 2>&1 | tee "$WORK_DIR/jni.log" -"$SERVER_DIR/bin/init-store.sh" +"$SERVER_DIR/bin/init-store.sh" 2>&1 | tee "$WORK_DIR/init-store.log" SERVER_START_ATTEMPTED=true start_server "$TRAVIS_DIR/run-server-e2e-smoke-test.sh" "$SERVER_URL" create "$RUN_ID" | \ @@ -75,7 +82,7 @@ grep -q '^server-e2e-smoke-create-ok$' "$WORK_DIR/create.log" "$SERVER_DIR/bin/stop-hugegraph.sh" -m false "$TIMEOUT_COMMAND" --foreground --kill-after=15s "${STORE_DUMP_TIMEOUT}s" \ - "$SERVER_DIR/bin/dump-store.sh" | tee "$WORK_DIR/store-dump.log" + "$SERVER_DIR/bin/dump-store.sh" 2>&1 | 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" @@ -86,14 +93,11 @@ start_server tee "$WORK_DIR/verify.log" grep -q '^server-e2e-smoke-verify-ok$' "$WORK_DIR/verify.log" -# Heap dumps under logs/heapdump_*/ contain class names as plain strings, so -# leave them out of the search. -LINKAGE_ERRORS='undefined symbol|UnsatisfiedLinkError|UnsupportedClassVersionError|NoClassDefFoundError' -if [[ -d "$SERVER_DIR/logs" ]] && \ - grep -Eirq --exclude='*.hprof*' "$LINKAGE_ERRORS" "$SERVER_DIR/logs"; then - echo "Native linkage or Java compatibility error found in $SERVER_DIR/logs" >&2 - grep -Eirn --exclude='*.hprof*' "$LINKAGE_ERRORS" "$SERVER_DIR/logs" >&2 - exit 1 +LOG_INPUTS=("$WORK_DIR") +if [[ "${STDOUT_MODE:-false}" != true ]] || \ + [[ -n "$(find "$SERVER_DIR/logs" -type f -name '*.log' -print -quit 2>/dev/null)" ]]; then + LOG_INPUTS+=("$SERVER_DIR/logs") fi +"$TRAVIS_DIR/check-java-runtime-logs.sh" --native "${LOG_INPUTS[@]}" echo "native-runtime-smoke-ok" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-java-runtime-logs.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-java-runtime-logs.sh new file mode 100755 index 0000000000..7fb8efa0e1 --- /dev/null +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-java-runtime-logs.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 + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +CHECKER="$SCRIPT_DIR/check-java-runtime-logs.sh" +WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/java-runtime-logs.XXXXXX") +trap 'rm -rf "$WORK_DIR"' EXIT + +check_case() { + local expected="$1" status=0 + shift + "$CHECKER" "$@" >"$WORK_DIR/result" 2>&1 || status=$? + if [[ $status -ne $expected ]]; then + cat "$WORK_DIR/result" >&2 + echo "FAIL: expected $expected, got $status for $*" >&2 + exit 1 + fi +} + +printf '%s\n' '[ERROR] Expected validation error: invalid input' \ + 'java.lang.IllegalAccessException: expected business access failure' \ + 'InaccessibleObjectException is a class name in this message' >"$WORK_DIR/normal.log" +check_case 0 "$WORK_DIR/normal.log" +touch "$WORK_DIR/empty.log" +check_case 0 "$WORK_DIR/empty.log" + +for MESSAGE in \ + 'java.lang.reflect.InaccessibleObjectException: module java.base does not "opens java.lang"' \ + 'java.lang.IllegalAccessError: class A cannot access class B because module java.base does not export p' \ + 'java.lang.IllegalAccessException: module java.base does not export p to unnamed module' \ + "Unrecognized VM option 'OldGCOption'" \ + 'Unrecognized option: --unknown-option' \ + 'OpenJDK warning: Ignoring option --illegal-access=permit; support was removed in 17.0' \ + 'Unable to inspect cluster runtime logs: Node still running or startup log unavailable'; do + # A child returning success after logging only to stderr reproduces the + # failure mode that result assertions and exit-code checks cannot catch. + bash -c 'printf "%s\n" "$1" >&2' bash "$MESSAGE" >"$WORK_DIR/positive.log" 2>&1 + check_case 1 "$WORK_DIR/positive.log" + grep -q '^1:' "$WORK_DIR/result" +done + +printf '%s\n' 'java.lang.NoClassDefFoundError: intentionally missing test dependency' >"$WORK_DIR/native.log" +check_case 0 "$WORK_DIR/native.log" +check_case 1 --native "$WORK_DIR/native.log" +printf '%s\n' 'undefined symbol: rocksdb_open' >"$WORK_DIR/native.log" +check_case 1 --native "$WORK_DIR/native.log" + +mkdir -p "$WORK_DIR/logs with spaces/nested" "$WORK_DIR/no-logs" +cp "$WORK_DIR/normal.log" "$WORK_DIR/logs with spaces/application.log" +printf '%s\n' 'java.lang.reflect.InaccessibleObjectException: module java.base does not opens p' \ + >"$WORK_DIR/logs with spaces/nested/application.log.1" +check_case 1 "$WORK_DIR/logs with spaces" +rm "$WORK_DIR/logs with spaces/nested/application.log.1" +for SUFFIX in hprof log.gz log.zip log.bz2 log.xz; do + cp "$WORK_DIR/positive.log" "$WORK_DIR/logs with spaces/heap.$SUFFIX" +done +check_case 0 "$WORK_DIR/logs with spaces" +# NUL bytes from logged keys must not hide a denied-access line elsewhere in +# the same text log. Keep the NUL-free control and explicit/directory inputs. +printf '%s\n' 'java.lang.reflect.InaccessibleObjectException: module java.base does not opens p' \ + >"$WORK_DIR/logs with spaces/binary.log" +check_case 1 "$WORK_DIR/logs with spaces/binary.log" +printf 'INFO key=\0\0\1 written\n' >>"$WORK_DIR/logs with spaces/binary.log" +check_case 1 "$WORK_DIR/logs with spaces/binary.log" +grep -q '^1:' "$WORK_DIR/result" +check_case 1 "$WORK_DIR/logs with spaces" +printf '\0java.lang.reflect.InaccessibleObjectException:\0' >"$WORK_DIR/logs with spaces/binary.log" +check_case 1 "$WORK_DIR/logs with spaces" +printf 'INFO key=\0\0\1 written\n' >"$WORK_DIR/logs with spaces/binary.log" +check_case 0 "$WORK_DIR/logs with spaces/binary.log" +check_case 0 "$WORK_DIR/logs with spaces" +check_case 2 "$WORK_DIR/no-logs" +check_case 2 "$WORK_DIR/missing.log" +check_case 2 +echo 'PASS: Java runtime log checks reject compatibility errors without rejecting business failures' diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh index d47b7e78dd..b5d74bc928 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh @@ -141,6 +141,8 @@ assert_argument "--add-exports=java.base/jdk.internal.reflect=ALL-UNNAMED" \ assert_argument "--add-modules=jdk.unsupported" "$JVM_MODULE_OPTIONS" assert_argument "--add-exports=java.base/sun.nio.ch=ALL-UNNAMED" \ "$JVM_MODULE_OPTIONS" +assert_argument "--add-opens=java.base/java.nio=ALL-UNNAMED" "$JVM_MODULE_OPTIONS" +assert_argument "--add-opens=java.base/java.lang=ALL-UNNAMED" "$JVM_MODULE_OPTIONS" if [[ -n "$SOURCE_ROOT_INPUT" ]]; then if [[ ! -d "$SOURCE_ROOT_INPUT" ]]; then @@ -185,7 +187,7 @@ if [[ -n "$SOURCE_ROOT_INPUT" ]]; then assert_surefire_arg_lines "$COMMONS_POM" \ '@${project.parent.basedir}/../hugegraph-server/hugegraph-test/conf/jvm-test-module.options' assert_source_consumer "$CLUSTER_WRAPPER" \ - '"@" + Paths.get(SERVER_PACKAGE_PATH, BIN_DIR,' + 'moduleOptions(Paths.get(SERVER_PACKAGE_PATH, BIN_DIR,' assert_no_inline_module_options \ "$SERVER_LAUNCHER_SOURCE" "$INIT_STORE_SOURCE" "$SUREFIRE_POM" \ "$COMMONS_POM" "$CLUSTER_WRAPPER" "$SERVER_DOCKERFILE" \ diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java17-upgrade-contracts.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java17-upgrade-contracts.sh index 926a96de22..e51d369ce0 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java17-upgrade-contracts.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java17-upgrade-contracts.sh @@ -165,17 +165,21 @@ assert_surefire_execution_scope \ MODULE_OPTIONS="${SERVER_ROOT}/bin/jvm-module.options" SERVER_SCRIPT="${SERVER_ROOT}/bin/hugegraph-server.sh" INIT_STORE_SCRIPT="${SERVER_ROOT}/bin/init-store.sh" +CONSOLE_SCRIPT="${SERVER_ROOT}/bin/gremlin-console.sh" +DUMP_STORE_SCRIPT="${SERVER_ROOT}/bin/dump-store.sh" UTIL_SCRIPT="${SERVER_ROOT}/bin/util.sh" PRELOAD_SCRIPT="${SERVER_ROOT}/bin/preload-topling.sh" CONF_SOURCE="${SERVER_ROOT}/conf" for source_file in "$MODULE_OPTIONS" "$SERVER_SCRIPT" \ - "$INIT_STORE_SCRIPT" "$UTIL_SCRIPT" "$PRELOAD_SCRIPT"; do + "$INIT_STORE_SCRIPT" "$CONSOLE_SCRIPT" "$DUMP_STORE_SCRIPT" \ + "$UTIL_SCRIPT" "$PRELOAD_SCRIPT"; do [[ -f "$source_file" ]] || fail "runtime asset is missing: $source_file" done [[ -d "$CONF_SOURCE" ]] || fail "server conf is missing: $CONF_SOURCE" TEMP_DIR=$(mktemp -d) +TEMP_DIR=$(cd "$TEMP_DIR" && pwd -P) trap 'rm -rf "$TEMP_DIR"' EXIT DIST_ROOT="${TEMP_DIR}/server" @@ -183,10 +187,10 @@ MOCK_JAVA_HOME="${TEMP_DIR}/mock-java-home" mkdir -p "${DIST_ROOT}"/{bin,conf,ext,lib,logs,plugins} \ "${MOCK_JAVA_HOME}/bin" cp "$MODULE_OPTIONS" "$SERVER_SCRIPT" "$INIT_STORE_SCRIPT" "$UTIL_SCRIPT" \ - "$PRELOAD_SCRIPT" "${DIST_ROOT}/bin/" + "$CONSOLE_SCRIPT" "$DUMP_STORE_SCRIPT" "$PRELOAD_SCRIPT" "${DIST_ROOT}/bin/" cp -R "${CONF_SOURCE}/." "${DIST_ROOT}/conf/" -# Model a full pre-Phase-2 conf/ directory: it has no module argfile. Both +# Model a full pre-Phase-2 conf/ directory: it has no module argfile. All # launchers must get the immutable runtime copy from bin/ instead. if [[ -e "${DIST_ROOT}/conf/jvm-module.options" ]]; then fail "legacy conf unexpectedly contains jvm-module.options" @@ -203,7 +207,8 @@ done printf '%s\n' "$@" > "${CAPTURE_FILE:?}" MOCK chmod +x "${MOCK_JAVA_HOME}/bin/java" "${DIST_ROOT}/bin/hugegraph-server.sh" \ - "${DIST_ROOT}/bin/init-store.sh" + "${DIST_ROOT}/bin/init-store.sh" "${DIST_ROOT}/bin/gremlin-console.sh" \ + "${DIST_ROOT}/bin/dump-store.sh" SERVER_CAPTURE="${TEMP_DIR}/server.args" CAPTURE_FILE="$SERVER_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ @@ -218,4 +223,40 @@ CAPTURE_FILE="$INIT_STORE_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ "${DIST_ROOT}/bin/init-store.sh" >/dev/null assert_argument "@${DIST_ROOT}/bin/jvm-module.options" "$INIT_STORE_CAPTURE" +for launcher in gremlin-console dump-store; do + for options in "" "-Doperator.marker=preserved"; do + capture="${TEMP_DIR}/${launcher}-${options:+custom}.args" + CAPTURE_FILE="$capture" JAVA_HOME="$MOCK_JAVA_HOME" \ + JAVA_OPTIONS="$options" "${DIST_ROOT}/bin/${launcher}.sh" >/dev/null + assert_argument "@${DIST_ROOT}/bin/jvm-module.options" "$capture" + if [[ "$launcher" == gremlin-console && -n "$options" ]]; then + assert_argument "$options" "$capture" + fi + done +done + +# Required options survive the Server launcher's -j argument as well. +SERVER_J_CAPTURE="${TEMP_DIR}/server-j.args" +CAPTURE_FILE="$SERVER_J_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ + JAVA_OPTIONS="" STDOUT_MODE=true "${DIST_ROOT}/bin/hugegraph-server.sh" \ + "${DIST_ROOT}/conf/gremlin-server.yaml" \ + "${DIST_ROOT}/conf/rest-server.properties" false \ + "-Xms512m -Xmx512m -Doperator.marker=preserved" >/dev/null +assert_argument "@${DIST_ROOT}/bin/jvm-module.options" "$SERVER_J_CAPTURE" +assert_argument "-Doperator.marker=preserved" "$SERVER_J_CAPTURE" + +mv "${DIST_ROOT}/bin/jvm-module.options" "${TEMP_DIR}/jvm-module.options" +for launcher in gremlin-console dump-store; do + capture="${TEMP_DIR}/${launcher}-missing.args" + output="${TEMP_DIR}/${launcher}-missing.log" + if CAPTURE_FILE="$capture" JAVA_HOME="$MOCK_JAVA_HOME" \ + JAVA_OPTIONS="-Doperator.marker=preserved" \ + "${DIST_ROOT}/bin/${launcher}.sh" >"$output" 2>&1; then + fail "$launcher accepted a missing JVM module options file" + fi + [[ ! -e "$capture" ]] || fail "$launcher invoked Java without required options" + grep -q "Missing or unreadable JVM module options file" "$output" || \ + fail "$launcher did not explain the missing module options file" +done + echo "PASS: Java 17 upgrade contracts" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-launcher-gc.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-launcher-gc.sh index 4e4addf512..4d277596fc 100644 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-launcher-gc.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-launcher-gc.sh @@ -51,7 +51,6 @@ for component in server pd store; do server) source_bin="$SOURCE_ROOT/hugegraph-server/hugegraph-dist/src/assembly/static/bin" launcher=hugegraph-server.sh - cp "$source_bin/jvm-module.options" "$FIXTURE/bin/" ;; pd) source_bin="$SOURCE_ROOT/hugegraph-pd/hg-pd-dist/src/assembly/static/bin" @@ -65,7 +64,7 @@ for component in server pd store; do touch "$FIXTURE/lib/hg-store-node-test.jar" ;; esac - cp "$source_bin/$launcher" "$source_bin/util.sh" "$FIXTURE/bin/" + cp "$source_bin/$launcher" "$source_bin/util.sh" "$source_bin/jvm-module.options" "$FIXTURE/bin/" # Keep the computed-heap/-j path deterministic without probing host memory. printf '\nfree_memory() { echo 2048; }\n' >> "$FIXTURE/bin/util.sh" cp "$SOURCE_ROOT/hugegraph-server/hugegraph-dist/src/assembly/static/bin/preload-topling.sh" "$FIXTURE/bin/" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-riscv64-java-runtime.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-riscv64-java-runtime.sh index cc33c98d17..f84bb084ac 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-riscv64-java-runtime.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-riscv64-java-runtime.sh @@ -190,12 +190,13 @@ START_LOG="$WORK_DIR/start.log" STOP_LOG="$WORK_DIR/stop.log" PHASE_LOG="$WORK_DIR/phase.log" RUN_ID_FILE="$WORK_DIR/run-id" -mkdir -p "$RUNTIME_BIN" "$RUNTIME_TRAVIS" "$WORK_DIR/tmp" -for command in dirname date mktemp rm tee grep awk cut getconf; do +mkdir -p "$RUNTIME_BIN" "$RUNTIME_TRAVIS" "$WORK_DIR/tmp" "$SERVER_DIR/logs" +printf '%s\n' 'mock service log' > "$SERVER_DIR/logs/server.log" +for command in dirname date mktemp rm tee grep awk cut getconf find; do ln -s "$(command -v "$command")" "$RUNTIME_BIN/$command" done cp "$MOCK_BIN/uname" "$RUNTIME_BIN/uname" -cp "$NATIVE_SMOKE_SCRIPT" "$SMOKE_SCRIPT" "$RUNTIME_TRAVIS/" +cp "$NATIVE_SMOKE_SCRIPT" "$SMOKE_SCRIPT" "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_TRAVIS/" cat > "$RUNTIME_BIN/timeout" <<'EOF' #!/bin/bash @@ -221,6 +222,7 @@ set -euo pipefail printf 'arch=%s' "$EXPECTED_ARCH" >> "$START_LOG" printf ' <%s>' "$@" >> "$START_LOG" printf '\n' >> "$START_LOG" +printf '%s\n' "${MOCK_START_OUTPUT:-}" EOF cat > "$SERVER_DIR/bin/stop-hugegraph.sh" <<'EOF' @@ -283,6 +285,14 @@ grep -q '^native-runtime-smoke-ok$' <<< "$ARM_OUTPUT" [[ $(grep -c '^gtimeout ' "$RUNTIME_LOG") -eq 3 ]] [[ $(cat "$PHASE_LOG") == $'create\nverify' ]] +if COMPATIBILITY_OUTPUT=$(run_restart_smoke MOCK_ARCH=arm64 EXPECTED_ARCH=arm64 \ + 'MOCK_START_OUTPUT=java.lang.reflect.InaccessibleObjectException: denied' 2>&1); then + echo "$COMPATIBILITY_OUTPUT" >&2 + echo "Native smoke accepted a caught Java compatibility error" >&2 + exit 1 +fi +grep -Fq 'Java runtime compatibility error' <<< "$COMPATIBILITY_OUTPUT" + set +e TIMEOUT_OUTPUT=$(run_restart_smoke MOCK_ARCH=arm64 EXPECTED_ARCH=arm64 \ MOCK_START_TIMEOUT=true 2>&1) diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-pd.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-pd.sh index 008be829ac..9677959aa9 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-pd.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-pd.sh @@ -33,6 +33,8 @@ PD_URL="http://localhost:8620" STARTUP_WAIT=60 # seconds to wait for PD HTTP to respond SETTLE_WAIT=5 # seconds after kill before checking exit WAIT_TIMEOUT=30 # seconds for wait_script_exit timeout +RUNTIME_LOG_CHECK="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/check-java-runtime-logs.sh" +ACTIVE_RUN=false PASS=0 FAIL=0 @@ -63,6 +65,13 @@ section() { echo "── $1 ──" } +start_service() { + mkdir -p "$PD_ROOT/logs" + ACTIVE_RUN=true + "$START_SCRIPT" "$@" > "$PD_ROOT/logs/launcher.out" 2>&1 & + SCRIPT_PID=$! +} + cleanup() { info "Cleaning up..." "$STOP_SCRIPT" >/dev/null 2>&1 || true @@ -70,7 +79,6 @@ cleanup() { kill "$(cat "$PID_FILE")" 2>/dev/null || true fi rm -f "$PID_FILE" - rm -rf "$PD_ROOT/logs/" # kill anything still holding the PD port: fuser on Linux, lsof on macOS, # which ships no fuser. Only the Linux path had to lose its lsof dependency. if [[ "$(uname)" == "Darwin" ]]; then @@ -90,6 +98,15 @@ cleanup() { echo "Warning: Neither lsof nor fuser available for PD port cleanup" fi sleep 3 + if [[ "$ACTIVE_RUN" == true ]]; then + if bash "$RUNTIME_LOG_CHECK" "$PD_ROOT/logs"; then + pass "startup logs contain no Java runtime compatibility errors" + else + fail "startup logs contain Java runtime compatibility errors or could not be checked" + fi + ACTIVE_RUN=false + fi + rm -rf "$PD_ROOT/logs/" } # Wait until PD health endpoint responds or timeout @@ -169,12 +186,16 @@ fi check_gc_options() { local fixture option capture status jvm_options fixture=$(mktemp -d) || { fail "could not create GC test fixture"; return 1; } + fixture=$(cd "$fixture" && pwd -P) if ! mkdir -p "$fixture/bin" "$fixture/conf" "$fixture/lib" "$fixture/jdk/bin" || - ! cp "$START_SCRIPT" "$BIN/util.sh" "$BIN/preload-topling.sh" "$fixture/bin/"; then + ! cp "$START_SCRIPT" "$BIN/util.sh" "$BIN/preload-topling.sh" \ + "$BIN/jvm-module.options" "$fixture/bin/"; then fail "could not prepare GC test fixture" rm -rf "$fixture" return 1 fi + # Make default memory selection independent of host memory and OS. + printf '%s\n' 'calc_xmx() { echo 1024; }' >> "$fixture/bin/util.sh" touch "$fixture/lib/hg-pd-service-test.jar" cat > "$fixture/jdk/bin/java" <<'JAVA' #!/bin/bash @@ -182,17 +203,26 @@ if [[ "$1" == "-version" ]]; then echo 'openjdk version "17.0.1"' >&2 else printf '%s\n' "$@" > "$CAPTURE_FILE" + for arg in "$@"; do + if [[ "$arg" == @* ]]; then + sed '/^[[:space:]]*#/d; /^[[:space:]]*$/d' "${arg#@}" >> "$CAPTURE_FILE" + fi + done fi JAVA # Avoid the Store allocator download, which is unrelated to GC selection. printf '%s\n' '#!/bin/sh' 'echo launcher-test' > "$fixture/jdk/bin/uname" chmod +x "$fixture/jdk/bin/"* "$fixture/bin/start-hugegraph-pd.sh" - for option in default default-serial default-zgc g1 G1 zgc ZGC invalid; do + for option in default custom-java-options user-options default-serial default-zgc \ + g1 G1 zgc ZGC invalid missing-module-options; do capture="$fixture/$option.args" local -a args=(-d false) jvm_options="-Xms64m -Xmx64m" case "$option" in - default) ;; + default) jvm_options="" ;; + custom-java-options) jvm_options="$jvm_options -Dlauncher.test.custom=true" ;; + user-options) jvm_options=""; args+=(-j "-Xmx96m -Dlauncher.test=true") ;; + missing-module-options) rm "$fixture/bin/jvm-module.options" ;; default-serial) jvm_options="$jvm_options -XX:+UseSerialGC" ;; default-zgc) jvm_options="$jvm_options -XX:+UseZGC" ;; *) args+=(-g "$option") ;; @@ -204,7 +234,14 @@ JAVA PATH="$fixture/jdk/bin:$PATH" \ bash "$fixture/bin/start-hugegraph-pd.sh" "${args[@]}" > "$fixture/output" 2>&1 status=$? - if [[ "$option" == invalid ]]; then + if [[ "$option" == missing-module-options ]]; then + if [[ $status -eq 1 && ! -e "$capture" ]] && + grep -Fq 'JVM module options file is missing or unreadable' "$fixture/output"; then + pass "missing JVM module options are rejected before application launch" + else + fail "missing JVM module options were not rejected correctly" + fi + elif [[ "$option" == invalid ]]; then if [[ $status -eq 1 && ! -e "$capture" ]] && \ grep -Fq "Unrecognized gc option: 'invalid'" "$fixture/logs/hugegraph-pd-stdout.log"; then pass "invalid GC is rejected before application launch" @@ -213,6 +250,15 @@ JAVA fi elif [[ $status -ne 0 || ! -s "$capture" ]]; then fail "GC '$option' did not reach Java (exit $status)" + elif ! grep -Fxq -- "@$fixture/bin/jvm-module.options" "$capture" || + ! grep -Fxq -- '--add-opens=java.base/java.lang=ALL-UNNAMED' "$capture"; then + fail "launcher '$option' dropped mandatory JVM module access" + elif [[ "$option" == custom-java-options ]] && + ! grep -Fxq -- '-Dlauncher.test.custom=true' "$capture"; then + fail "launcher dropped custom JAVA_OPTIONS" + elif [[ "$option" == user-options ]] && + ! grep -Fxq -- '-Dlauncher.test=true' "$capture"; then + fail "launcher dropped custom -j options" elif [[ "$option" == zgc || "$option" == ZGC ]]; then if grep -Fxq -- '-XX:+UseZGC' "$capture" && \ ! grep -Fxq -- '-XX:InitiatingHeapOccupancyPercent=50' "$capture"; then @@ -254,6 +300,11 @@ if [[ "${2:-}" == --gc-only ]]; then [[ $FAIL -eq 0 ]] && exit 0 || exit 1 fi +if [[ ! -r "$RUNTIME_LOG_CHECK" ]]; then + echo "ERROR: runtime log checker not found: $RUNTIME_LOG_CHECK" + exit 1 +fi + if [[ "$(uname)" == "Darwin" ]]; then _prereq_tools="lsof curl java jcmd" else @@ -273,8 +324,7 @@ cleanup section "Test 1 — daemon mode regression" info "Starting in daemon mode (no -d flag, default behavior)..." -"$START_SCRIPT" >/dev/null 2>&1 & -SCRIPT_PID=$! +start_service # daemon mode: script backgrounds Java and exits quickly sleep 5 @@ -321,8 +371,7 @@ cleanup section "Test 2 — foreground mode blocks until Java exits" info "Starting in foreground mode (-d false -g g1)..." -"$START_SCRIPT" -d false -g g1 >/dev/null 2>&1 & -SCRIPT_PID=$! +start_service -d false -g g1 info "Waiting up to ${STARTUP_WAIT}s for PD to come up..." if wait_for_pd; then @@ -379,8 +428,7 @@ cleanup section "Test 3 — exit code propagates from Java" info "Starting in foreground mode (-d false -g ZGC)..." -"$START_SCRIPT" -d false -g ZGC >/dev/null 2>&1 & -SCRIPT_PID=$! +start_service -d false -g ZGC info "Waiting up to ${STARTUP_WAIT}s for PD..." if wait_for_pd; then @@ -434,8 +482,7 @@ cleanup section "Test 4 — SIGTERM forwarded to Java in foreground mode" info "Starting in foreground mode (-d false)..." -"$START_SCRIPT" -d false >/dev/null 2>&1 & -SCRIPT_PID=$! +start_service -d false info "Waiting up to ${STARTUP_WAIT}s for PD..." if wait_for_pd; then diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-store.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-store.sh index 1f1e87786e..6e1bc0394d 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-store.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-store.sh @@ -31,6 +31,8 @@ PD_URL="http://localhost:8620" STARTUP_WAIT=60 # seconds to wait for Store HTTP to respond SETTLE_WAIT=5 # seconds after kill before checking exit WAIT_TIMEOUT=30 # seconds for wait_script_exit timeout +RUNTIME_LOG_CHECK="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/check-java-runtime-logs.sh" +ACTIVE_RUN=false PASS=0 FAIL=0 @@ -46,6 +48,13 @@ fail() { echo -e "${RED} FAIL${NC} $1"; ERRORS+=("$1"); FAIL=$((FAIL + 1)); } info() { echo -e "${YELLOW} ....${NC} $1"; } section() { echo ""; echo "── $1 ──"; } +start_service() { + mkdir -p "$STORE_ROOT/logs" + ACTIVE_RUN=true + "$START_SCRIPT" "$@" > "$STORE_ROOT/logs/launcher.out" 2>&1 & + SCRIPT_PID=$! +} + cleanup() { info "Cleaning up..." "$STOP_SCRIPT" >/dev/null 2>&1 || true @@ -53,7 +62,6 @@ cleanup() { kill "$(cat "$PID_FILE")" 2>/dev/null || true fi rm -f "$PID_FILE" - rm -rf "$STORE_ROOT/logs/" # kill anything holding Store ports (8520 REST, 8510 raft, 8500 gRPC) if [[ "$(uname)" == "Darwin" ]]; then local port pids pid @@ -73,6 +81,15 @@ cleanup() { echo "Warning: Neither lsof nor fuser available for Store port cleanup" fi sleep 3 + if [[ "$ACTIVE_RUN" == true ]]; then + if bash "$RUNTIME_LOG_CHECK" "$STORE_ROOT/logs"; then + pass "startup logs contain no Java runtime compatibility errors" + else + fail "startup logs contain Java runtime compatibility errors or could not be checked" + fi + ACTIVE_RUN=false + fi + rm -rf "$STORE_ROOT/logs/" } wait_for_store() { @@ -127,12 +144,16 @@ fi check_gc_options() { local fixture option capture status jvm_options fixture=$(mktemp -d) || { fail "could not create GC test fixture"; return 1; } + fixture=$(cd "$fixture" && pwd -P) if ! mkdir -p "$fixture/bin" "$fixture/conf" "$fixture/lib" "$fixture/jdk/bin" || - ! cp "$START_SCRIPT" "$BIN/util.sh" "$BIN/preload-topling.sh" "$fixture/bin/"; then + ! cp "$START_SCRIPT" "$BIN/util.sh" "$BIN/preload-topling.sh" \ + "$BIN/jvm-module.options" "$fixture/bin/"; then fail "could not prepare GC test fixture" rm -rf "$fixture" return 1 fi + # Make default memory selection independent of host memory and OS. + printf '%s\n' 'calc_xmx() { echo 1024; }' >> "$fixture/bin/util.sh" touch "$fixture/lib/hg-store-node-test.jar" cat > "$fixture/jdk/bin/java" <<'JAVA' #!/bin/bash @@ -140,17 +161,26 @@ if [[ "$1" == "-version" ]]; then echo 'openjdk version "17.0.1"' >&2 else printf '%s\n' "$@" > "$CAPTURE_FILE" + for arg in "$@"; do + if [[ "$arg" == @* ]]; then + sed '/^[[:space:]]*#/d; /^[[:space:]]*$/d' "${arg#@}" >> "$CAPTURE_FILE" + fi + done fi JAVA # Avoid the Store allocator download, which is unrelated to GC selection. printf '%s\n' '#!/bin/sh' 'echo launcher-test' > "$fixture/jdk/bin/uname" chmod +x "$fixture/jdk/bin/"* "$fixture/bin/start-hugegraph-store.sh" - for option in default default-serial default-zgc g1 G1 zgc ZGC invalid; do + for option in default custom-java-options user-options default-serial default-zgc \ + g1 G1 zgc ZGC invalid missing-module-options; do capture="$fixture/$option.args" local -a args=(-d false) jvm_options="-Xms64m -Xmx64m" case "$option" in - default) ;; + default) jvm_options="" ;; + custom-java-options) jvm_options="$jvm_options -Dlauncher.test.custom=true" ;; + user-options) jvm_options=""; args+=(-j "-Xmx96m -Dlauncher.test=true") ;; + missing-module-options) rm "$fixture/bin/jvm-module.options" ;; default-serial) jvm_options="$jvm_options -XX:+UseSerialGC" ;; default-zgc) jvm_options="$jvm_options -XX:+UseZGC" ;; *) args+=(-g "$option") ;; @@ -162,7 +192,14 @@ JAVA PATH="$fixture/jdk/bin:$PATH" \ bash "$fixture/bin/start-hugegraph-store.sh" "${args[@]}" > "$fixture/output" 2>&1 status=$? - if [[ "$option" == invalid ]]; then + if [[ "$option" == missing-module-options ]]; then + if [[ $status -eq 1 && ! -e "$capture" ]] && + grep -Fq 'JVM module options file is missing or unreadable' "$fixture/output"; then + pass "missing JVM module options are rejected before application launch" + else + fail "missing JVM module options were not rejected correctly" + fi + elif [[ "$option" == invalid ]]; then if [[ $status -eq 1 && ! -e "$capture" ]] && \ grep -Fq "Unrecognized gc option: 'invalid'" "$fixture/logs/hugegraph-store-server.log"; then pass "invalid GC is rejected before application launch" @@ -171,6 +208,15 @@ JAVA fi elif [[ $status -ne 0 || ! -s "$capture" ]]; then fail "GC '$option' did not reach Java (exit $status)" + elif ! grep -Fxq -- "@$fixture/bin/jvm-module.options" "$capture" || + ! grep -Fxq -- '--add-opens=java.base/java.lang=ALL-UNNAMED' "$capture"; then + fail "launcher '$option' dropped mandatory JVM module access" + elif [[ "$option" == custom-java-options ]] && + ! grep -Fxq -- '-Dlauncher.test.custom=true' "$capture"; then + fail "launcher dropped custom JAVA_OPTIONS" + elif [[ "$option" == user-options ]] && + ! grep -Fxq -- '-Dlauncher.test=true' "$capture"; then + fail "launcher dropped custom -j options" elif [[ "$option" == zgc || "$option" == ZGC ]]; then if grep -Fxq -- '-XX:+UseZGC' "$capture" && \ ! grep -Fxq -- '-XX:InitiatingHeapOccupancyPercent=50' "$capture"; then @@ -212,6 +258,11 @@ if [[ "${2:-}" == --gc-only ]]; then [[ $FAIL -eq 0 ]] && exit 0 || exit 1 fi +if [[ ! -r "$RUNTIME_LOG_CHECK" ]]; then + echo "ERROR: runtime log checker not found: $RUNTIME_LOG_CHECK" + exit 1 +fi + if [[ "$(uname)" == "Darwin" ]]; then _prereq_tools="lsof curl java jcmd" else @@ -250,8 +301,7 @@ cleanup section "Test 1 — daemon mode regression" info "Starting in daemon mode (no -d flag, default behavior)..." -"$START_SCRIPT" >/dev/null 2>&1 & -SCRIPT_PID=$! +start_service sleep 5 if ! ps -p "$SCRIPT_PID" >/dev/null 2>&1; then @@ -294,8 +344,7 @@ cleanup section "Test 2 — foreground mode blocks until Java exits" info "Starting in foreground mode (-d false -g g1)..." -"$START_SCRIPT" -d false -g g1 >/dev/null 2>&1 & -SCRIPT_PID=$! +start_service -d false -g g1 info "Waiting up to ${STARTUP_WAIT}s for Store to come up..." if wait_for_store; then @@ -350,8 +399,7 @@ cleanup section "Test 3 — exit code propagates from Java" info "Starting in foreground mode (-d false -g ZGC)..." -"$START_SCRIPT" -d false -g ZGC >/dev/null 2>&1 & -SCRIPT_PID=$! +start_service -d false -g ZGC info "Waiting up to ${STARTUP_WAIT}s for Store..." if wait_for_store; then @@ -405,8 +453,7 @@ cleanup section "Test 4 — SIGTERM forwarded to Java in foreground mode" info "Starting in foreground mode (-d false)..." -"$START_SCRIPT" -d false >/dev/null 2>&1 & -SCRIPT_PID=$! +start_service -d false info "Waiting up to ${STARTUP_WAIT}s for Store..." if wait_for_store; then diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph.sh index db65ec51e8..1f54b81f90 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph.sh @@ -45,6 +45,8 @@ WAIT_TIMEOUT=30 # seconds for timeout on wait calls PASS=0 FAIL=0 ERRORS=() +STARTUP_LOG="$HUGEGRAPH_ROOT/logs/startup-test.log" +RUNTIME_STARTED=false GREEN='\033[0;32m' RED='\033[0;31m' @@ -102,6 +104,23 @@ cleanup() { fi sleep 3 crontab -l 2>/dev/null | grep -v monitor-hugegraph | crontab - 2>/dev/null || true + if [[ "$RUNTIME_STARTED" == true ]]; then + local logs=("$STARTUP_LOG") + if [[ "${STDOUT_MODE:-false}" != true ]]; then + logs+=("$HUGEGRAPH_ROOT/logs/hugegraph-server.log" + "$HUGEGRAPH_ROOT/logs/hugegraph-server-stdout.log") + fi + if bash "$SCRIPT_DIR/check-java-runtime-logs.sh" "${logs[@]}"; then + pass "startup has no Java runtime compatibility errors" + else + fail "startup log contains a compatibility error or is unavailable" + fi + RUNTIME_STARTED=false + fi + # Each positive start must be checked independently of previous runs. + mkdir -p "$HUGEGRAPH_ROOT/logs" + rm -f "$STARTUP_LOG" "$HUGEGRAPH_ROOT/logs/hugegraph-server.log" \ + "$HUGEGRAPH_ROOT/logs/hugegraph-server-stdout.log" } # Wait until server HTTP endpoint responds (200 or 401) or timeout @@ -195,9 +214,15 @@ fi # Run init-store.sh once if RocksDB has not been initialized yet if [[ ! -d "$HUGEGRAPH_ROOT/rocksdb-data" ]]; then info "RocksDB not initialized — running init-store.sh..." - if "$BIN/init-store.sh" >/dev/null 2>&1; then + mkdir -p "$HUGEGRAPH_ROOT/logs" + INIT_LOG="$HUGEGRAPH_ROOT/logs/startup-init.log" + if "$BIN/init-store.sh" >"$INIT_LOG" 2>&1; then + if ! bash "$SCRIPT_DIR/check-java-runtime-logs.sh" "$INIT_LOG"; then + exit 1 + fi pass "init-store.sh completed successfully" else + cat "$INIT_LOG" echo -e "${RED}ERROR:${NC} init-store.sh failed. Cannot run tests." exit 1 fi @@ -212,7 +237,8 @@ cleanup section "Test 1 — daemon mode regression" info "Starting in daemon mode (waiting up to ${STARTUP_WAIT}s for HTTP)..." -"$START_SCRIPT" -d true -t "$STARTUP_WAIT" >/dev/null 2>&1 +RUNTIME_STARTED=true +"$START_SCRIPT" -d true -t "$STARTUP_WAIT" >"$STARTUP_LOG" 2>&1 EXIT_CODE=$? if [[ $EXIT_CODE -eq 0 ]]; then @@ -259,7 +285,8 @@ cleanup section "Test 2 — foreground mode blocks until Java exits" info "Starting in foreground mode (backgrounded for observation)..." -"$START_SCRIPT" -d false >/dev/null 2>&1 & +RUNTIME_STARTED=true +"$START_SCRIPT" -d false >"$STARTUP_LOG" 2>&1 & SCRIPT_PID=$! info "Waiting up to ${STARTUP_WAIT}s for server to come up..." @@ -314,7 +341,8 @@ cleanup section "Test 3 — foreground PID tracking and exit propagation" info "Starting in foreground mode..." -"$START_SCRIPT" -d false >/dev/null 2>&1 & +RUNTIME_STARTED=true +"$START_SCRIPT" -d false >"$STARTUP_LOG" 2>&1 & SCRIPT_PID=$! info "Waiting up to ${STARTUP_WAIT}s for server..." @@ -372,7 +400,8 @@ info "Clearing crontab..." crontab -l 2>/dev/null | grep -v monitor-hugegraph | crontab - 2>/dev/null || true info "Starting in daemon mode with -m true (waiting up to ${STARTUP_WAIT}s)..." -"$START_SCRIPT" -d true -m true -t "$STARTUP_WAIT" >/dev/null 2>&1 +RUNTIME_STARTED=true +"$START_SCRIPT" -d true -m true -t "$STARTUP_WAIT" >"$STARTUP_LOG" 2>&1 EXIT_CODE=$? if [[ $EXIT_CODE -eq 0 ]]; then @@ -398,7 +427,8 @@ cleanup section "Test 5 — SIGTERM forwarded to Java in foreground mode" info "Starting in foreground mode..." -"$START_SCRIPT" -d false >/dev/null 2>&1 & +RUNTIME_STARTED=true +"$START_SCRIPT" -d false >"$STARTUP_LOG" 2>&1 & SCRIPT_PID=$! info "Waiting up to ${STARTUP_WAIT}s for server..." diff --git a/hugegraph-server/hugegraph-test/conf/jvm-test-module.options b/hugegraph-server/hugegraph-test/conf/jvm-test-module.options index 32b2b8b036..ab1bbb0162 100644 --- a/hugegraph-server/hugegraph-test/conf/jvm-test-module.options +++ b/hugegraph-server/hugegraph-test/conf/jvm-test-module.options @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Test-only access required by TinkerPop and its Kryo dependencies on -# Java 17. Keep these permissions out of the Server runtime argument file. +# Access required by TinkerPop and its Kryo dependencies in Java 17 tests. +# Runtime argument files contain only the subset required by production dependencies. --add-exports=java.base/sun.security.x509=ALL-UNNAMED --add-exports=java.base/sun.security.action=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED diff --git a/hugegraph-store/README.md b/hugegraph-store/README.md index bda2066c67..e742391348 100644 --- a/hugegraph-store/README.md +++ b/hugegraph-store/README.md @@ -252,6 +252,10 @@ bin/start-hugegraph-store.sh [-g GC_TYPE] [-j "JVM_OPTIONS"] [-d DAEMON] - `-j`: Custom JVM options (e.g., `-j "-Xmx16g -Xms8g"`) - `-d`: Daemon mode (`true` = daemon, `false` = foreground; default: `true`). Set to `false` when running under Docker or a process supervisor so the container exits if Java dies. +The launcher always loads `bin/jvm-module.options`, including with custom `JAVA_OPTIONS` or `-j`. +Keep this file in the distribution: it grants the Java 17 access required by Hessian Raft serialization; +startup fails if the file is missing or unreadable. Direct Java launches must include `@bin/jvm-module.options`. + On supported architectures, startup reuses a checksum-verified jemalloc library or tries to download it. The optional download allows 5 seconds to connect and 30 seconds for the complete transfer. If it fails or times out, Store continues without adding jemalloc to `LD_PRELOAD`; partial downloads are discarded. diff --git a/hugegraph-store/hg-store-dist/src/assembly/static/bin/jvm-module.options b/hugegraph-store/hg-store-dist/src/assembly/static/bin/jvm-module.options new file mode 100644 index 0000000000..b41209f448 --- /dev/null +++ b/hugegraph-store/hg-store-dist/src/assembly/static/bin/jvm-module.options @@ -0,0 +1,19 @@ +# +# 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. +# + +# Hessian reflects into StackTraceElement when serializing Raft operations. +--add-opens=java.base/java.lang=ALL-UNNAMED diff --git a/hugegraph-store/hg-store-dist/src/assembly/static/bin/start-hugegraph-store.sh b/hugegraph-store/hg-store-dist/src/assembly/static/bin/start-hugegraph-store.sh index 6846e1f47c..d1ccc651c7 100755 --- a/hugegraph-store/hg-store-dist/src/assembly/static/bin/start-hugegraph-store.sh +++ b/hugegraph-store/hg-store-dist/src/assembly/static/bin/start-hugegraph-store.sh @@ -36,6 +36,12 @@ LOGS="$TOP/logs" OUTPUT=${LOGS}/hugegraph-store-server.log GITHUB="https://github.com" PID_FILE="$BIN/pid" +JVM_MODULE_OPTIONS="$BIN/jvm-module.options" + +if [[ ! -f "$JVM_MODULE_OPTIONS" || ! -r "$JVM_MODULE_OPTIONS" ]]; then + echo "Error: JVM module options file is missing or unreadable: $JVM_MODULE_OPTIONS" >&2 + exit 1 +fi . "$BIN"/util.sh @@ -260,11 +266,11 @@ fi if [[ $DAEMON == "true" ]]; then echo "Starting HugeGraphStoreServer in daemon mode..." if [[ "${STDOUT_MODE:-false}" == "true" ]]; then - exec ${JAVA} -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + exec ${JAVA} "@$JVM_MODULE_OPTIONS" -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ -Dspring.config.location=${CONF}/application.yml \ "${JAVA_MAIN[@]}" "$@" & else - exec ${JAVA} -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + exec ${JAVA} "@$JVM_MODULE_OPTIONS" -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ -Dspring.config.location=${CONF}/application.yml \ "${JAVA_MAIN[@]}" "$@" >> ${OUTPUT} 2>&1 & fi @@ -278,11 +284,11 @@ else echo "$$" > "$PID_FILE" echo "[+pid] $$" if [[ "${STDOUT_MODE:-false}" == "true" ]]; then - exec ${JAVA} -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + exec ${JAVA} "@$JVM_MODULE_OPTIONS" -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ -Dspring.config.location=${CONF}/application.yml \ "${JAVA_MAIN[@]}" "$@" else - exec ${JAVA} -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + exec ${JAVA} "@$JVM_MODULE_OPTIONS" -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ -Dspring.config.location=${CONF}/application.yml \ "${JAVA_MAIN[@]}" "$@" >> ${OUTPUT} 2>&1 fi diff --git a/hugegraph-store/hg-store-dist/src/assembly/travis/test-store-preloads.sh b/hugegraph-store/hg-store-dist/src/assembly/travis/test-store-preloads.sh index e3fdc42c21..e5ad4e2495 100755 --- a/hugegraph-store/hg-store-dist/src/assembly/travis/test-store-preloads.sh +++ b/hugegraph-store/hg-store-dist/src/assembly/travis/test-store-preloads.sh @@ -50,7 +50,7 @@ run_launcher() { local -a provider_env=(-u TOPLINGDB_ROCKSDB_PROVIDER) [ "$provider" = default ] || provider_env=("TOPLINGDB_ROCKSDB_PROVIDER=$provider") mkdir -p "$top/bin" "$top/conf" "$top/lib" "$top/topling" - cp "$SOURCE_BIN/start-hugegraph-store.sh" "$top/bin/" + cp "$SOURCE_BIN/start-hugegraph-store.sh" "$SOURCE_BIN/jvm-module.options" "$top/bin/" cp "$SHARED_BIN/preload-topling.sh" "$top/bin/" cat > "$top/bin/util.sh" <<'MOCK' # Substitute only utility calls needed by this launcher fixture. diff --git a/hugegraph-store/hg-store-test/pom.xml b/hugegraph-store/hg-store-test/pom.xml index 17dbdb32ae..28ca7102bf 100644 --- a/hugegraph-store/hg-store-test/pom.xml +++ b/hugegraph-store/hg-store-test/pom.xml @@ -200,6 +200,12 @@ org.apache.maven.plugins maven-surefire-plugin + + + @{argLine} + "@${project.basedir}/../hg-store-dist/src/assembly/static/bin/jvm-module.options" + + org.apache.maven.surefire