Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/scripts/ci-policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@
TRAVIS + "test-check-jacoco-report.sh": {"struct"},
TRAVIS + "test-codecov-upload-config.sh": {"struct"},
TRAVIS + "test-riscv64-java-runtime.sh": {"server", "riscv64"},
TRAVIS + "test-store-docker-entrypoint.sh": {"store"},
TRAVIS + "test-start-server.sh": {"server"},
}


Expand Down
13 changes: 8 additions & 5 deletions .github/scripts/test_ci_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,14 +356,16 @@ def test_contract_scripts_reach_their_actual_inner_jobs_and_steps(self):
("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"})]
("test-riscv64-java-runtime.sh", {"server", "riscv64"}),
("test-store-docker-entrypoint.sh", {"store"}),
("test-start-server.sh", {"server"})]
for name, suites in cases:
script = policy.TRAVIS + name
with self.subTest(script=script):
plan = self.plan_for([script])
self.assertEqual(suites, set(plan["selected"]))
callers = self.coordinator_jobs(plan)
if "struct" in suites:
if "struct" in suites or "store" in suites:
self.assertEqual({"pd_store"}, callers)
root = (workflows / "server-ci.yml").read_text()
caller = re.search(r"^ pd_store:\n(.*?)(?=^ [\w-]+:|\Z)", root, re.M | re.S).group(1)
Expand All @@ -373,12 +375,13 @@ def test_contract_scripts_reach_their_actual_inner_jobs_and_steps(self):
binding = binding.replace("needs.preflight.outputs." + key, repr(str(plan[key]).lower()))
inputs[key] = eval(binding, {"__builtins__": {}})
workflow = (workflows / "pd-store-ci.yml").read_text()
struct = workflow.split("\n struct:\n", 1)[1].split("\n pd:\n", 1)[0]
condition = re.search(r"^ if: (.+)$", struct, re.M).group(1)
job = "struct" if "struct" in suites else "store"
body = re.search(r"^ " + job + r":\n(.*?)(?=^ [\w-]+:|\Z)", workflow, re.M | re.S).group(1)
condition = re.search(r"^ if: (.+)$", body, re.M).group(1)
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 Path(script).name in step)
step = next(step for step in body.split("\n - ") if Path(script).name in step)
self.assertNotRegex(step, r"(?m)^ if:")
else:
self.assertIn("server-rocksdb", callers)
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/pd-store-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,10 @@ jobs:
run: |
mvn editorconfig:check -pl hugegraph-store/hg-store-test -am -ntp

- name: Run Store docker entrypoint rocksdb memory tests
Comment thread
byteayan marked this conversation as resolved.
run: |
$TRAVIS_DIR/test-store-docker-entrypoint.sh

- name: Check startup test prerequisites (Store)
id: store-preflight
run: |
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/server-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ jobs:
run: |
"$TRAVIS_DIR/test-wait-storage.sh"
"$TRAVIS_DIR/test-riscv64-java-runtime.sh"
"$TRAVIS_DIR/test-start-server.sh"
bash "$TRAVIS_DIR/test-tinkerpop-reports.sh"

- name: Validate Docker integration
Expand Down
8 changes: 5 additions & 3 deletions helm/hugegraph/values-cluster.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ store:
-XX:MaxMetaspaceSize=256m
-XX:MaxDirectMemorySize=512m
-XX:+UseContainerSupport
# Container limits do not configure JVM heap or RocksDB native cache budgets.
# Size this preset for the workload and configure those application budgets
# separately; javaOpts accepts explicit JVM/system-property overrides.
# Container limits do not configure the JVM heap; javaOpts does. Store images
# with auto-sized RocksDB memory (apache/hugegraph#3254) give RocksDB half of
# the container limit left after heap, direct memory and a 1.5 GiB reserve;
# with this preset that is 2.5 GiB. Set store.extraEnv
# HG_STORE_ROCKSDB_TOTAL_MEMORY_SIZE (bytes) to use a different value.
resources:
requests:
cpu: "1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ function sed_in_place() {
esac
}

# Leave exactly one key=value line: run-api-test.sh starts the same directory
# more than once, and a key appended twice reaches HugeConfig as a list.
function set_property() {
local file=$1
local key=$2
local value=$3
local pattern

pattern=$(printf '%s' "$key" | sed 's/[][\.*^$/]/\\&/g')
sed_in_place "/^[[:space:]]*${pattern}[[:space:]]*[=:]/d" "$file"
echo "${key}=${value}" >> "$file"
}

case "$BACKEND" in
memory)
SERIALIZER=text
Expand All @@ -64,18 +77,18 @@ write_property "$CONF" "serializer" "$SERIALIZER"

# Set timeout for hbase
if [ "$BACKEND" == "hbase" ]; then
echo "restserver.request_timeout=200" >> $REST_CONF
echo "gremlinserver.timeout=200" >> $REST_CONF
set_property "$REST_CONF" "restserver.request_timeout" "200"
set_property "$REST_CONF" "gremlinserver.timeout" "200"
sed_in_place 's/evaluationTimeout.*/evaluationTimeout: 200000/' "$GREMLIN_CONF"
fi

# Set usePD=true for hstore
if [ "$BACKEND" == "hstore" ]; then
echo "usePD=true" >> $REST_CONF
set_property "$REST_CONF" "usePD" "true"
fi

# Append schema.sync_deletion=true to config file
echo "schema.sync_deletion=true" >> $CONF
set_property "$CONF" "schema.sync_deletion" "true"

JACOCO_OPTION=""
if [ -n "$JACOCO_PORT" ]; then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,23 @@ JAVA
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 custom-java-options user-options default-serial default-zgc \
g1 G1 zgc ZGC invalid missing-module-options; do
g1 G1 zgc ZGC direct-default direct-heap-lower direct-heap-raised \
direct-heap-tab direct-heap-alias direct-explicit-cap \
invalid missing-module-options; do
capture="$fixture/$option.args"
local -a args=(-d false)
jvm_options="-Xms64m -Xmx64m"
case "$option" in
default) jvm_options="" ;;
custom-java-options) jvm_options="$jvm_options -Dlauncher.test.custom=true" ;;
user-options) jvm_options=""; args+=(-j "-Xmx96m -Dlauncher.test=true") ;;
# The direct-memory cap follows the heap the JVM actually gets (calc_xmx: 1024)
direct-default) jvm_options="" ;;
direct-heap-lower) jvm_options=""; args+=(-j "-Xmx512m") ;;
direct-heap-raised) jvm_options=""; args+=(-j "-Dlauncher.test=true -Xmx4g") ;;
direct-heap-tab) jvm_options=""; args+=(-j $'-Dlauncher.test=true\t-Xmx4g') ;;
direct-heap-alias) jvm_options=""; args+=(-j "-XX:MaxHeapSize=4g") ;;
direct-explicit-cap) jvm_options=""; args+=(-j "-XX:MaxDirectMemorySize=256m") ;;
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" ;;
Expand Down Expand Up @@ -217,6 +226,21 @@ JAVA
elif [[ "$option" == user-options ]] &&
! grep -Fxq -- '-Dlauncher.test=true' "$capture"; then
fail "launcher dropped custom -j options"
elif [[ "$option" == direct-* ]]; then
local cap want
# The JVM uses the last -XX:MaxDirectMemorySize on the command line
cap=$(grep -E '^-XX:MaxDirectMemorySize=' "$capture" | tail -1)
case "$option" in
direct-default) want='-XX:MaxDirectMemorySize=1024m' ;;
direct-explicit-cap) want='-XX:MaxDirectMemorySize=256m' ;;
# A heap-only -j leaves the cap to the JVM, which defaults it to that heap
*) want='' ;;
esac
if [[ "$cap" == "$want" ]]; then
pass "launcher '$option' sets direct memory to '${want:-the JVM default (-j heap)}'"
else
fail "launcher '$option' set direct memory to '${cap:-none}', expected '${want:-none}'"
fi
elif [[ "$option" == zgc || "$option" == ZGC ]]; then
if grep -Fxq -- '-XX:+UseZGC' "$capture" && \
! grep -Fxq -- '-XX:InitiatingHeapOccupancyPercent=50' "$capture"; then
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
#
# run-api-test.sh starts the same Server directory twice. Run start-server.sh
# twice on one fixture, with init-store and the launcher stubbed, and check
# that every key it writes appears exactly once: a duplicate usePD reached
# HugeConfig as '[true, true]' and failed the second init-store.
set -euo pipefail

TRAVIS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
STATIC_BIN="${TRAVIS_DIR}/../static/bin"
FIXTURE=$(mktemp -d "${TMPDIR:-/tmp}/start-server-test.XXXXXX")
trap 'rm -rf "${FIXTURE}"' EXIT

PASS=0
FAIL=0

expect_once() {
# expect_once <file> <key> <value>
local lines
lines=$(grep -E "^[[:space:]]*${2//./\\.}[[:space:]]*[=:]" "$1" || true)
if [[ "${lines}" == "$2=$3" ]]; then
PASS=$((PASS + 1))
else
echo " FAIL $(basename "$1"): expected one '$2=$3', got: ${lines:-nothing}" >&2
FAIL=$((FAIL + 1))
fi
}

for backend in hstore hbase; do
base="${FIXTURE}/${backend}"
mkdir -p "${base}/bin" "${base}/conf/graphs" "${base}/travis"
cp "${TRAVIS_DIR}/start-server.sh" "${base}/travis/"
cp "${STATIC_BIN}/util.sh" "${base}/bin/"
printf '#!/bin/bash\ncat >/dev/null\n' > "${base}/bin/init-store.sh"
printf '#!/bin/bash\nexit 0\n' > "${base}/bin/start-hugegraph.sh"
chmod +x "${base}/bin/"*.sh "${base}/travis/start-server.sh"
printf 'backend=rocksdb\nserializer=binary\n' > "${base}/conf/graphs/hugegraph.properties"
printf 'restserver.url=http://127.0.0.1:8080\n' > "${base}/conf/rest-server.properties"
printf 'evaluationTimeout: 30000\n' > "${base}/conf/gremlin-server.yaml"

for run in 1 2; do
if ! bash "${base}/travis/start-server.sh" "${base}" "${backend}" "" \
> "${base}/start-${run}.log" 2>&1; then
echo " FAIL ${backend}: start-server.sh run ${run} failed" >&2
tail -5 "${base}/start-${run}.log" >&2
FAIL=$((FAIL + 1))
fi
done

graph="${base}/conf/graphs/hugegraph.properties"
rest="${base}/conf/rest-server.properties"
expect_once "${graph}" "backend" "${backend}"
expect_once "${graph}" "schema.sync_deletion" "true"
if [[ "${backend}" == hstore ]]; then
expect_once "${rest}" "usePD" "true"
else
expect_once "${rest}" "restserver.request_timeout" "200"
expect_once "${rest}" "gremlinserver.timeout" "200"
fi
done

echo "start-server.sh twice on one directory: ${PASS} passed, ${FAIL} failed"
[[ "${FAIL}" -eq 0 ]]
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env 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.
#
# Checks that the Store Docker entrypoint passes HG_STORE_ROCKSDB_TOTAL_MEMORY_SIZE
# into SPRING_APPLICATION_JSON as rocksdb.total_memory_size, leaves the rocksdb key
# out when the variable is unset (so conf/application-pd.yml or auto-sizing applies),
# and refuses values that are not a plain number of bytes.

set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../../.." && pwd)"
ENTRYPOINT="${REPO_ROOT}/hugegraph-store/hg-store-dist/docker/docker-entrypoint.sh"
TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/store-entrypoint-test.XXXXXX")
trap 'rm -rf "${TMP_DIR}"' EXIT

PASS=0
FAIL=0

[[ -f "${ENTRYPOINT}" ]] || { echo "entrypoint not found at ${ENTRYPOINT}" >&2; exit 1; }
command -v python3 >/dev/null || { echo "python3 required" >&2; exit 1; }

mkdir -p "${TMP_DIR}/bin"
cp "${ENTRYPOINT}" "${TMP_DIR}/docker-entrypoint.sh"
# Stand in for the launcher: record the generated config instead of starting Store
cat > "${TMP_DIR}/bin/start-hugegraph-store.sh" <<'STUB'
#!/usr/bin/env bash
printf '%s' "${SPRING_APPLICATION_JSON}" > ./spring.json
STUB
chmod +x "${TMP_DIR}/bin/start-hugegraph-store.sh" "${TMP_DIR}/docker-entrypoint.sh"

run_entrypoint() {
rm -f "${TMP_DIR}/spring.json"
# Drop any value from the caller's environment so "unset" really is unset
(cd "${TMP_DIR}" && env -u HG_STORE_ROCKSDB_TOTAL_MEMORY_SIZE \
HG_STORE_PD_ADDRESS=pd0:8686 \
HG_STORE_GRPC_HOST=store0 \
HG_STORE_RAFT_ADDRESS=store0:8510 \
"$@" \
./docker-entrypoint.sh 2>&1)
}

expect_memory() {
# expect_memory <name> <expected value, or "absent"> [NAME=VALUE ...]
local name="$1" expected="$2"
shift 2
local out
if ! out=$(run_entrypoint "$@"); then
echo " FAIL ${name}: entrypoint exited non-zero"
printf '%s\n' "${out}" | tail -3
FAIL=$((FAIL + 1))
return
fi
if ! EXPECTED="${expected}" python3 - "${TMP_DIR}/spring.json" <<'PY'
import json, os, sys
with open(sys.argv[1], encoding="utf-8") as fh:
doc = json.load(fh)
want = os.environ["EXPECTED"]
got = doc.get("rocksdb", {}).get("total_memory_size", "absent")
if got != want:
print(" rocksdb.total_memory_size is %r, expected %r" % (got, want))
sys.exit(1)
if doc["app"]["data-path"] != "/hugegraph-store/storage":
print(" app.data-path was lost")
sys.exit(1)
PY
then
echo " FAIL ${name}: invalid JSON or wrong rocksdb.total_memory_size"
FAIL=$((FAIL + 1))
return
fi
echo " PASS ${name}"
PASS=$((PASS + 1))
}

echo "Store docker-entrypoint rocksdb memory override"
expect_memory "unset leaves the key out" absent
expect_memory "empty leaves the key out" absent HG_STORE_ROCKSDB_TOTAL_MEMORY_SIZE=
expect_memory "bytes are passed through" 2147483648 \
HG_STORE_ROCKSDB_TOTAL_MEMORY_SIZE=2147483648
expect_memory "0 is passed through for auto-sizing" 0 HG_STORE_ROCKSDB_TOTAL_MEMORY_SIZE=0

for bad in '4GB' '-1' '1.5e9' '1 2' '1"}'; do
if run_entrypoint HG_STORE_ROCKSDB_TOTAL_MEMORY_SIZE="${bad}" >/dev/null; then
echo " FAIL '${bad}' was accepted"
FAIL=$((FAIL + 1))
else
echo " PASS '${bad}' is refused"
PASS=$((PASS + 1))
fi
done

echo "${PASS} passed, ${FAIL} failed"
[[ "${FAIL}" -eq 0 ]]
4 changes: 4 additions & 0 deletions hugegraph-store/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,10 @@ docker run -d \
hugegraph/store:latest
```

Add `-e HG_STORE_ROCKSDB_TOTAL_MEMORY_SIZE=<bytes>` to pin the off-heap RocksDB budget.
Without it the Store sizes the budget from the container memory limit; see
[Store Configuration](docs/deployment-guide.md#store-configuration) in the deployment guide.

**Exposed Ports**:
- `8520`: REST API (management, metrics)
- `8500`: gRPC (client connections)
Expand Down
Loading
Loading