mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-06-29 15:31:05 +08:00
Enhancement: update ci for parallel test execution (#16133)
### What problem does this PR solve? split ci into multiple jobs ### Type of change - [x] Performance Improvement
This commit is contained in:
483
.github/workflows/tests.yml
vendored
483
.github/workflows/tests.yml
vendored
@@ -29,12 +29,14 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
ragflow_tests:
|
||||
name: ragflow_tests
|
||||
ragflow_preflight:
|
||||
name: ragflow_preflight
|
||||
# https://docs.github.com/en/actions/using-jobs/using-conditions-to-control-job-execution
|
||||
# https://github.com/orgs/community/discussions/26261
|
||||
if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'ci')) }}
|
||||
if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'ci') && (github.event.action != 'labeled' || github.event.label.name == 'ci')) }}
|
||||
runs-on: [ "self-hosted", "ragflow-test" ]
|
||||
outputs:
|
||||
http_api_test_level: ${{ steps.test_level.outputs.http_api_test_level }}
|
||||
steps:
|
||||
- name: Ensure workspace ownership
|
||||
run: |
|
||||
@@ -157,61 +159,17 @@ jobs:
|
||||
echo "No Go files changed"
|
||||
fi
|
||||
|
||||
- name: Build ragflow go server
|
||||
- name: Set test level
|
||||
id: test_level
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BUILDER_CONTAINER=ragflow_build_$(od -An -N4 -tx4 /dev/urandom | tr -d ' ')
|
||||
cleanup_builder() {
|
||||
if [[ -n "${BUILDER_CONTAINER:-}" ]]; then
|
||||
sudo docker rm -f -v "${BUILDER_CONTAINER}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
trap cleanup_builder EXIT
|
||||
|
||||
echo "BUILDER_CONTAINER=${BUILDER_CONTAINER}" >> "${GITHUB_ENV}"
|
||||
TZ=${TZ:-$(readlink -f /etc/localtime | awk -F '/zoneinfo/' '{print $2}')}
|
||||
sudo docker run --privileged -d --name "${BUILDER_CONTAINER}" \
|
||||
-e TZ="${TZ}" \
|
||||
-e UV_INDEX=https://mirrors.aliyun.com/pypi/simple \
|
||||
-v "${PWD}:/ragflow" \
|
||||
-v "${PWD}/internal/cpp/resource:/usr/share/infinity/resource" \
|
||||
infiniflow/infinity_builder:ubuntu22_clang20
|
||||
sudo docker exec "${BUILDER_CONTAINER}" bash -c 'git config --global safe.directory "*" && cd /ragflow && ./build.sh --cpp'
|
||||
./build.sh --go
|
||||
|
||||
# - name: Prepare test resources
|
||||
# run: |
|
||||
# RESOURCE_REPO=https://github.com/infiniflow/resource.git
|
||||
# RESOURCE_REF=549feaaf998954d65b668667f009125bc84a9c5e
|
||||
# rm -rf /tmp/resource
|
||||
# git clone "${RESOURCE_REPO}" /tmp/resource
|
||||
# git -C /tmp/resource checkout "${RESOURCE_REF}"
|
||||
# sudo mkdir -p /usr/share/infinity
|
||||
# sudo ln -sf /tmp/resource /usr/share/infinity/resource
|
||||
# mkdir -p resource
|
||||
# ln -sf /tmp/resource/wordnet resource/wordnet
|
||||
#
|
||||
# - name: Test Go packages
|
||||
# run: |
|
||||
# set -euo pipefail
|
||||
# packages=$(go list ./internal/... | grep -vE '/storage(/|$)')
|
||||
# CGO_ENABLED=1 GOPROXY=${GOPROXY:-https://goproxy.cn,https://proxy.golang.org,direct} \
|
||||
# go test -count=1 ${packages}
|
||||
|
||||
- name: Build ragflow:nightly
|
||||
run: |
|
||||
RUNNER_WORKSPACE_PREFIX=${RUNNER_WORKSPACE_PREFIX:-${HOME}}
|
||||
RAGFLOW_IMAGE=infiniflow/ragflow:${GITHUB_RUN_ID}
|
||||
echo "RAGFLOW_IMAGE=${RAGFLOW_IMAGE}" >> ${GITHUB_ENV}
|
||||
sudo docker pull ubuntu:24.04
|
||||
sudo DOCKER_BUILDKIT=1 docker build --build-arg NEED_MIRROR=1 --build-arg HTTPS_PROXY=${HTTPS_PROXY} --build-arg HTTP_PROXY=${HTTP_PROXY} -f Dockerfile -t ${RAGFLOW_IMAGE} .
|
||||
if [[ ${GITHUB_EVENT_NAME} == "schedule" ]]; then
|
||||
export HTTP_API_TEST_LEVEL=p3
|
||||
else
|
||||
export HTTP_API_TEST_LEVEL=p2
|
||||
fi
|
||||
echo "HTTP_API_TEST_LEVEL=${HTTP_API_TEST_LEVEL}" >> ${GITHUB_ENV}
|
||||
echo "RAGFLOW_CONTAINER=${GITHUB_RUN_ID}-ragflow-cpu-1" >> ${GITHUB_ENV}
|
||||
echo "http_api_test_level=${HTTP_API_TEST_LEVEL}" >> ${GITHUB_OUTPUT}
|
||||
|
||||
- name: Prepare Python test environment
|
||||
run: |
|
||||
@@ -225,23 +183,92 @@ jobs:
|
||||
echo "Start to run unit test"
|
||||
python3 run_tests.py -i
|
||||
|
||||
|
||||
|
||||
ragflow_tests_infinity:
|
||||
name: ragflow_tests_infinity
|
||||
needs: ragflow_preflight
|
||||
if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'ci') && (github.event.action != 'labeled' || github.event.label.name == 'ci')) }}
|
||||
runs-on: [ "self-hosted", "ragflow-test" ]
|
||||
env:
|
||||
DOC_ENGINE: infinity
|
||||
RAGFLOW_IMAGE: infiniflow/ragflow:${{ github.run_id }}-infinity
|
||||
HTTP_API_TEST_LEVEL: ${{ needs.ragflow_preflight.outputs.http_api_test_level }}
|
||||
steps:
|
||||
- name: Ensure workspace ownership
|
||||
run: |
|
||||
echo "Workflow triggered by ${{ github.event_name }}"
|
||||
echo "chown -R ${USER} ${GITHUB_WORKSPACE}" && sudo chown -R ${USER} ${GITHUB_WORKSPACE}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && format('refs/pull/{0}/merge', github.event.pull_request.number) || github.sha }}
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Build ragflow go server
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BUILDER_CONTAINER=ragflow_build_${GITHUB_RUN_ID}_${DOC_ENGINE}_$(od -An -N4 -tx4 /dev/urandom | tr -d ' ')
|
||||
cleanup_builder() {
|
||||
if [[ -n "${BUILDER_CONTAINER:-}" ]]; then
|
||||
sudo docker rm -f -v "${BUILDER_CONTAINER}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
trap cleanup_builder EXIT
|
||||
|
||||
TZ=${TZ:-$(readlink -f /etc/localtime | awk -F '/zoneinfo/' '{print $2}')}
|
||||
sudo docker run --privileged -d --name "${BUILDER_CONTAINER}" \
|
||||
-e TZ="${TZ}" \
|
||||
-e UV_INDEX=https://mirrors.aliyun.com/pypi/simple \
|
||||
-v "${PWD}:/ragflow" \
|
||||
-v "${PWD}/internal/cpp/resource:/usr/share/infinity/resource" \
|
||||
infiniflow/infinity_builder:ubuntu22_clang20
|
||||
sudo docker exec "${BUILDER_CONTAINER}" bash -c 'git config --global safe.directory "*" && cd /ragflow && ./build.sh --cpp'
|
||||
./build.sh --go
|
||||
|
||||
- name: Build ragflow:nightly
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo docker pull ubuntu:24.04
|
||||
sudo DOCKER_BUILDKIT=1 docker build --build-arg NEED_MIRROR=1 --build-arg HTTPS_PROXY=${HTTPS_PROXY} --build-arg HTTP_PROXY=${HTTP_PROXY} -f Dockerfile -t ${RAGFLOW_IMAGE} .
|
||||
|
||||
- name: Prepare Python test environment
|
||||
run: |
|
||||
uv sync --python 3.13 --group test --frozen
|
||||
uv pip install -e sdk/python
|
||||
|
||||
- name: Prepare function test environment
|
||||
working-directory: docker
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# install ss
|
||||
sudo apt update && sudo apt install -y iproute2
|
||||
sudo apt update && sudo apt install -y iproute2
|
||||
RUNNER_WORKSPACE_PREFIX=${RUNNER_WORKSPACE_PREFIX:-${HOME}}
|
||||
COMPOSE_PROJECT_NAME="${GITHUB_RUN_ID}-${DOC_ENGINE}"
|
||||
echo "COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME}" >> ${GITHUB_ENV}
|
||||
echo "RAGFLOW_CONTAINER=${COMPOSE_PROJECT_NAME}-ragflow-cpu-1" >> ${GITHUB_ENV}
|
||||
ARTIFACTS_DIR=${RUNNER_WORKSPACE_PREFIX}/artifacts/${GITHUB_REPOSITORY}/${GITHUB_RUN_ID}/${DOC_ENGINE}
|
||||
echo "ARTIFACTS_DIR=${ARTIFACTS_DIR}" >> ${GITHUB_ENV}
|
||||
rm -rf "${ARTIFACTS_DIR}" && mkdir -p "${ARTIFACTS_DIR}"
|
||||
|
||||
# Determine runner number (default to 1 if not found)
|
||||
RUNNER_NUM=$(sudo docker inspect $(hostname) --format '{{index .Config.Labels "com.docker.compose.container-number"}}' 2>/dev/null || true)
|
||||
RUNNER_NUM=${RUNNER_NUM:-1}
|
||||
|
||||
# Per-runner seed plus per-workflow-run offset avoids most clashes when
|
||||
# multiple CI jobs share the same self-hosted runner concurrently. Probe
|
||||
# the final host ports too, because stale compose projects can still hold
|
||||
# a deterministic port from a previous run.
|
||||
PORT_BASES=(1200 1201 23817 23820 5432 5455 9000 9001 6379 6380 6601 9380 9381 9382 9384 9383 9385 80 443)
|
||||
MAX_PORT_OFFSET=$((65000 - 23820))
|
||||
PORT_OFFSET=$(( (GITHUB_RUN_ID % 4000) + RUNNER_NUM * 1000 ))
|
||||
OFFSET_FOUND=false
|
||||
# Engine-specific offset partitions keep concurrent engine jobs from
|
||||
# choosing the same host ports when they land on the same self-hosted runner.
|
||||
# A lock plus reservation file closes the check/start race between parallel jobs.
|
||||
PORT_BASES=(1200 1201 23817 23820 5432 5455 9000 9001 6379 6380 6601 9380 9381 9382 9384 9383 9385 80 443 4222)
|
||||
PARTITION_SIZE=6000
|
||||
case "${DOC_ENGINE}" in
|
||||
elasticsearch) PARTITION_BASE=1000 ;;
|
||||
infinity) PARTITION_BASE=31000 ;;
|
||||
*) echo "Unsupported DOC_ENGINE=${DOC_ENGINE}" >&2; exit 1 ;;
|
||||
esac
|
||||
PORT_LOCK_DIR=${RUNNER_WORKSPACE_PREFIX}/artifacts/${GITHUB_REPOSITORY}/port-locks
|
||||
mkdir -p "${PORT_LOCK_DIR}"
|
||||
|
||||
port_offset_available() {
|
||||
local offset=$1
|
||||
@@ -255,25 +282,43 @@ jobs:
|
||||
return 0
|
||||
}
|
||||
|
||||
for ATTEMPT in $(seq 0 9); do
|
||||
CANDIDATE_OFFSET=$(( (PORT_OFFSET + ATTEMPT * 4000) % MAX_PORT_OFFSET ))
|
||||
if [ "${CANDIDATE_OFFSET}" -lt 1000 ]; then
|
||||
CANDIDATE_OFFSET=$((CANDIDATE_OFFSET + 1000))
|
||||
fi
|
||||
cleanup_stale_port_locks() {
|
||||
local now stale_after lock lock_ts
|
||||
now=$(date -u +%s)
|
||||
stale_after=$((6 * 60 * 60))
|
||||
for lock in "${PORT_LOCK_DIR}"/*.lock; do
|
||||
[[ -e "${lock}" ]] || continue
|
||||
lock_ts=$(awk '{print $3}' "${lock}" 2>/dev/null || true)
|
||||
if [[ "${lock_ts}" =~ ^[0-9]+$ ]] && (( now - lock_ts > stale_after )); then
|
||||
rm -f "${lock}"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
if port_offset_available "${CANDIDATE_OFFSET}"; then
|
||||
PORT_OFFSET=${CANDIDATE_OFFSET}
|
||||
OFFSET_FOUND=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
reserve_port_offset() {
|
||||
local attempt candidate reservation
|
||||
cleanup_stale_port_locks
|
||||
for attempt in $(seq 0 59); do
|
||||
candidate=$(( PARTITION_BASE + ((GITHUB_RUN_ID + RUNNER_NUM * 1000 + attempt * 97) % PARTITION_SIZE) ))
|
||||
reservation="${PORT_LOCK_DIR}/${candidate}.lock"
|
||||
if ( set -o noclobber; echo "${GITHUB_RUN_ID} ${DOC_ENGINE} $(date -u +%s)" > "${reservation}" ) 2>/dev/null; then
|
||||
if port_offset_available "${candidate}"; then
|
||||
PORT_OFFSET=${candidate}
|
||||
PORT_RESERVATION=${reservation}
|
||||
return 0
|
||||
fi
|
||||
rm -f "${reservation}"
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
if [ "${OFFSET_FOUND}" != "true" ]; then
|
||||
echo "Failed to find a free host port range for docker compose" >&2
|
||||
if ! reserve_port_offset; then
|
||||
echo "Failed to reserve a free host port range for ${DOC_ENGINE} docker compose" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Using host port offset ${PORT_OFFSET}"
|
||||
echo "PORT_RESERVATION=${PORT_RESERVATION}" >> ${GITHUB_ENV}
|
||||
echo "Using ${DOC_ENGINE} host port offset ${PORT_OFFSET}"
|
||||
ES_PORT=$((1200 + PORT_OFFSET))
|
||||
OS_PORT=$((1201 + PORT_OFFSET))
|
||||
INFINITY_THRIFT_PORT=$((23817 + PORT_OFFSET))
|
||||
@@ -283,6 +328,7 @@ jobs:
|
||||
MINIO_PORT=$((9000 + PORT_OFFSET))
|
||||
MINIO_CONSOLE_PORT=$((9001 + PORT_OFFSET))
|
||||
REDIS_PORT=$((6379 + PORT_OFFSET))
|
||||
NATS_PORT=$((4222 + PORT_OFFSET))
|
||||
TEI_PORT=$((6380 + PORT_OFFSET))
|
||||
KIBANA_PORT=$((6601 + PORT_OFFSET))
|
||||
SVR_HTTP_PORT=$((9380 + PORT_OFFSET))
|
||||
@@ -294,31 +340,36 @@ jobs:
|
||||
SVR_WEB_HTTP_PORT=$((80 + PORT_OFFSET))
|
||||
SVR_WEB_HTTPS_PORT=$((443 + PORT_OFFSET))
|
||||
|
||||
# Persist computed ports into .env so docker-compose uses the correct host bindings
|
||||
echo "" >> .env
|
||||
echo -e "ES_PORT=${ES_PORT}" >> .env
|
||||
echo -e "OS_PORT=${OS_PORT}" >> .env
|
||||
echo -e "INFINITY_THRIFT_PORT=${INFINITY_THRIFT_PORT}" >> .env
|
||||
echo -e "INFINITY_HTTP_PORT=${INFINITY_HTTP_PORT}" >> .env
|
||||
echo -e "INFINITY_PSQL_PORT=${INFINITY_PSQL_PORT}" >> .env
|
||||
echo -e "EXPOSE_MYSQL_PORT=${EXPOSE_MYSQL_PORT}" >> .env
|
||||
echo -e "MINIO_PORT=${MINIO_PORT}" >> .env
|
||||
echo -e "MINIO_CONSOLE_PORT=${MINIO_CONSOLE_PORT}" >> .env
|
||||
echo -e "REDIS_PORT=${REDIS_PORT}" >> .env
|
||||
echo -e "TEI_PORT=${TEI_PORT}" >> .env
|
||||
echo -e "KIBANA_PORT=${KIBANA_PORT}" >> .env
|
||||
echo -e "SVR_HTTP_PORT=${SVR_HTTP_PORT}" >> .env
|
||||
echo -e "ADMIN_SVR_HTTP_PORT=${ADMIN_SVR_HTTP_PORT}" >> .env
|
||||
echo -e "SVR_MCP_PORT=${SVR_MCP_PORT}" >> .env
|
||||
echo -e "GO_HTTP_PORT=${GO_HTTP_PORT}" >> .env
|
||||
echo -e "GO_ADMIN_PORT=${GO_ADMIN_PORT}" >> .env
|
||||
echo -e "SANDBOX_EXECUTOR_MANAGER_PORT=${SANDBOX_EXECUTOR_MANAGER_PORT}" >> .env
|
||||
echo -e "SVR_WEB_HTTP_PORT=${SVR_WEB_HTTP_PORT}" >> .env
|
||||
echo -e "SVR_WEB_HTTPS_PORT=${SVR_WEB_HTTPS_PORT}" >> .env
|
||||
|
||||
echo -e "COMPOSE_PROFILES=\${COMPOSE_PROFILES},tei-cpu" >> .env
|
||||
echo -e "TEI_MODEL=BAAI/bge-small-en-v1.5" >> .env
|
||||
echo -e "RAGFLOW_IMAGE=${RAGFLOW_IMAGE}" >> .env
|
||||
# Persist computed ports into .env so docker-compose uses the correct host bindings.
|
||||
# Remove previous CI overrides first; docker compose uses the last duplicate key.
|
||||
sed -i '/^ES_PORT=/d;/^OS_PORT=/d;/^INFINITY_THRIFT_PORT=/d;/^INFINITY_HTTP_PORT=/d;/^INFINITY_PSQL_PORT=/d;/^EXPOSE_MYSQL_PORT=/d;/^MINIO_PORT=/d;/^MINIO_CONSOLE_PORT=/d;/^REDIS_PORT=/d;/^TEI_PORT=/d;/^KIBANA_PORT=/d;/^SVR_HTTP_PORT=/d;/^ADMIN_SVR_HTTP_PORT=/d;/^SVR_MCP_PORT=/d;/^GO_HTTP_PORT=/d;/^GO_ADMIN_PORT=/d;/^SANDBOX_EXECUTOR_MANAGER_PORT=/d;/^SVR_WEB_HTTP_PORT=/d;/^SVR_WEB_HTTPS_PORT=/d;/^NATS_PORT=/d;/^COMPOSE_PROFILES=/d;/^TEI_MODEL=/d;/^RAGFLOW_IMAGE=/d;/^DOC_ENGINE=/d' .env
|
||||
{
|
||||
echo ""
|
||||
echo "ES_PORT=${ES_PORT}"
|
||||
echo "OS_PORT=${OS_PORT}"
|
||||
echo "INFINITY_THRIFT_PORT=${INFINITY_THRIFT_PORT}"
|
||||
echo "INFINITY_HTTP_PORT=${INFINITY_HTTP_PORT}"
|
||||
echo "INFINITY_PSQL_PORT=${INFINITY_PSQL_PORT}"
|
||||
echo "EXPOSE_MYSQL_PORT=${EXPOSE_MYSQL_PORT}"
|
||||
echo "MINIO_PORT=${MINIO_PORT}"
|
||||
echo "MINIO_CONSOLE_PORT=${MINIO_CONSOLE_PORT}"
|
||||
echo "REDIS_PORT=${REDIS_PORT}"
|
||||
echo "NATS_PORT=${NATS_PORT}"
|
||||
echo "TEI_PORT=${TEI_PORT}"
|
||||
echo "KIBANA_PORT=${KIBANA_PORT}"
|
||||
echo "SVR_HTTP_PORT=${SVR_HTTP_PORT}"
|
||||
echo "ADMIN_SVR_HTTP_PORT=${ADMIN_SVR_HTTP_PORT}"
|
||||
echo "SVR_MCP_PORT=${SVR_MCP_PORT}"
|
||||
echo "GO_HTTP_PORT=${GO_HTTP_PORT}"
|
||||
echo "GO_ADMIN_PORT=${GO_ADMIN_PORT}"
|
||||
echo "SANDBOX_EXECUTOR_MANAGER_PORT=${SANDBOX_EXECUTOR_MANAGER_PORT}"
|
||||
echo "SVR_WEB_HTTP_PORT=${SVR_WEB_HTTP_PORT}"
|
||||
echo "SVR_WEB_HTTPS_PORT=${SVR_WEB_HTTPS_PORT}"
|
||||
echo "COMPOSE_PROFILES=${DOC_ENGINE},cpu,tei-cpu"
|
||||
echo "TEI_MODEL=BAAI/bge-small-en-v1.5"
|
||||
echo "RAGFLOW_IMAGE=${RAGFLOW_IMAGE}"
|
||||
echo "DOC_ENGINE=${DOC_ENGINE}"
|
||||
} >> .env
|
||||
echo "HOST_ADDRESS=http://host.docker.internal:${SVR_HTTP_PORT}" >> ${GITHUB_ENV}
|
||||
|
||||
# Patch entrypoint.sh for coverage
|
||||
@@ -327,10 +378,9 @@ jobs:
|
||||
|
||||
- name: Start ragflow:nightly for Infinity
|
||||
run: |
|
||||
sed -i 's/^DOC_ENGINE=.*$/DOC_ENGINE=infinity/' docker/.env
|
||||
sudo docker compose -f docker/docker-compose.yml -p ${GITHUB_RUN_ID} down -v || true
|
||||
sudo docker ps -a --filter "label=com.docker.compose.project=${GITHUB_RUN_ID}" -q | xargs -r sudo docker rm -f
|
||||
sudo docker compose -f docker/docker-compose.yml -p ${GITHUB_RUN_ID} up -d
|
||||
sudo docker compose -f docker/docker-compose.yml -p ${COMPOSE_PROJECT_NAME} down -v || true
|
||||
sudo docker ps -a --filter "label=com.docker.compose.project=${COMPOSE_PROJECT_NAME}" -q | xargs -r sudo docker rm -f
|
||||
sudo docker compose -f docker/docker-compose.yml -p ${COMPOSE_PROJECT_NAME} up -d
|
||||
|
||||
- name: Run sdk tests against Infinity
|
||||
run: |
|
||||
@@ -495,7 +545,7 @@ jobs:
|
||||
else
|
||||
echo "ragflow_server.py not found!"
|
||||
fi
|
||||
sudo docker compose -f docker/docker-compose.yml -p ${GITHUB_RUN_ID} stop
|
||||
sudo docker compose -f docker/docker-compose.yml -p ${COMPOSE_PROJECT_NAME} stop
|
||||
|
||||
- name: Generate server coverage report Infinity
|
||||
if: ${{ !cancelled() }}
|
||||
@@ -538,15 +588,213 @@ jobs:
|
||||
if: always() # always run this step even if previous steps failed
|
||||
run: |
|
||||
# Sometimes `docker compose down` fail due to hang container, heavy load etc. Need to remove such containers to release resources(for example, listen ports).
|
||||
sudo docker compose -f docker/docker-compose.yml -p ${GITHUB_RUN_ID} down -v || true
|
||||
sudo docker ps -a --filter "label=com.docker.compose.project=${GITHUB_RUN_ID}" -q | xargs -r sudo docker rm -f
|
||||
sudo docker compose -f docker/docker-compose.yml -p ${COMPOSE_PROJECT_NAME} down -v || true
|
||||
sudo docker ps -a --filter "label=com.docker.compose.project=${COMPOSE_PROJECT_NAME}" -q | xargs -r sudo docker rm -f
|
||||
if [[ -n ${RAGFLOW_IMAGE} ]]; then
|
||||
sudo docker rmi -f ${RAGFLOW_IMAGE}
|
||||
fi
|
||||
if [[ -n ${PORT_RESERVATION:-} ]]; then
|
||||
rm -f "${PORT_RESERVATION}"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
ragflow_tests_elasticsearch:
|
||||
name: ragflow_tests_elasticsearch
|
||||
needs: ragflow_preflight
|
||||
if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'ci') && (github.event.action != 'labeled' || github.event.label.name == 'ci')) }}
|
||||
runs-on: [ "self-hosted", "ragflow-test" ]
|
||||
env:
|
||||
DOC_ENGINE: elasticsearch
|
||||
RAGFLOW_IMAGE: infiniflow/ragflow:${{ github.run_id }}-elasticsearch
|
||||
HTTP_API_TEST_LEVEL: ${{ needs.ragflow_preflight.outputs.http_api_test_level }}
|
||||
steps:
|
||||
- name: Ensure workspace ownership
|
||||
run: |
|
||||
echo "Workflow triggered by ${{ github.event_name }}"
|
||||
echo "chown -R ${USER} ${GITHUB_WORKSPACE}" && sudo chown -R ${USER} ${GITHUB_WORKSPACE}
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && format('refs/pull/{0}/merge', github.event.pull_request.number) || github.sha }}
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Build ragflow go server
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BUILDER_CONTAINER=ragflow_build_${GITHUB_RUN_ID}_${DOC_ENGINE}_$(od -An -N4 -tx4 /dev/urandom | tr -d ' ')
|
||||
cleanup_builder() {
|
||||
if [[ -n "${BUILDER_CONTAINER:-}" ]]; then
|
||||
sudo docker rm -f -v "${BUILDER_CONTAINER}" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
trap cleanup_builder EXIT
|
||||
|
||||
TZ=${TZ:-$(readlink -f /etc/localtime | awk -F '/zoneinfo/' '{print $2}')}
|
||||
sudo docker run --privileged -d --name "${BUILDER_CONTAINER}" \
|
||||
-e TZ="${TZ}" \
|
||||
-e UV_INDEX=https://mirrors.aliyun.com/pypi/simple \
|
||||
-v "${PWD}:/ragflow" \
|
||||
-v "${PWD}/internal/cpp/resource:/usr/share/infinity/resource" \
|
||||
infiniflow/infinity_builder:ubuntu22_clang20
|
||||
sudo docker exec "${BUILDER_CONTAINER}" bash -c 'git config --global safe.directory "*" && cd /ragflow && ./build.sh --cpp'
|
||||
./build.sh --go
|
||||
|
||||
- name: Build ragflow:nightly
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo docker pull ubuntu:24.04
|
||||
sudo DOCKER_BUILDKIT=1 docker build --build-arg NEED_MIRROR=1 --build-arg HTTPS_PROXY=${HTTPS_PROXY} --build-arg HTTP_PROXY=${HTTP_PROXY} -f Dockerfile -t ${RAGFLOW_IMAGE} .
|
||||
|
||||
- name: Prepare Python test environment
|
||||
run: |
|
||||
uv sync --python 3.13 --group test --frozen
|
||||
uv pip install -e sdk/python
|
||||
|
||||
- name: Prepare function test environment
|
||||
working-directory: docker
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# install ss
|
||||
sudo apt update && sudo apt install -y iproute2
|
||||
RUNNER_WORKSPACE_PREFIX=${RUNNER_WORKSPACE_PREFIX:-${HOME}}
|
||||
COMPOSE_PROJECT_NAME="${GITHUB_RUN_ID}-${DOC_ENGINE}"
|
||||
echo "COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME}" >> ${GITHUB_ENV}
|
||||
echo "RAGFLOW_CONTAINER=${COMPOSE_PROJECT_NAME}-ragflow-cpu-1" >> ${GITHUB_ENV}
|
||||
ARTIFACTS_DIR=${RUNNER_WORKSPACE_PREFIX}/artifacts/${GITHUB_REPOSITORY}/${GITHUB_RUN_ID}/${DOC_ENGINE}
|
||||
echo "ARTIFACTS_DIR=${ARTIFACTS_DIR}" >> ${GITHUB_ENV}
|
||||
rm -rf "${ARTIFACTS_DIR}" && mkdir -p "${ARTIFACTS_DIR}"
|
||||
|
||||
# Determine runner number (default to 1 if not found)
|
||||
RUNNER_NUM=$(sudo docker inspect $(hostname) --format '{{index .Config.Labels "com.docker.compose.container-number"}}' 2>/dev/null || true)
|
||||
RUNNER_NUM=${RUNNER_NUM:-1}
|
||||
|
||||
# Engine-specific offset partitions keep concurrent engine jobs from
|
||||
# choosing the same host ports when they land on the same self-hosted runner.
|
||||
# A lock plus reservation file closes the check/start race between parallel jobs.
|
||||
PORT_BASES=(1200 1201 23817 23820 5432 5455 9000 9001 6379 6380 6601 9380 9381 9382 9384 9383 9385 80 443 4222)
|
||||
PARTITION_SIZE=6000
|
||||
case "${DOC_ENGINE}" in
|
||||
elasticsearch) PARTITION_BASE=1000 ;;
|
||||
infinity) PARTITION_BASE=31000 ;;
|
||||
*) echo "Unsupported DOC_ENGINE=${DOC_ENGINE}" >&2; exit 1 ;;
|
||||
esac
|
||||
PORT_LOCK_DIR=${RUNNER_WORKSPACE_PREFIX}/artifacts/${GITHUB_REPOSITORY}/port-locks
|
||||
mkdir -p "${PORT_LOCK_DIR}"
|
||||
|
||||
port_offset_available() {
|
||||
local offset=$1
|
||||
local base port
|
||||
for base in "${PORT_BASES[@]}"; do
|
||||
port=$((base + offset))
|
||||
if ss -ltnH "sport = :${port}" | grep -q .; then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
cleanup_stale_port_locks() {
|
||||
local now stale_after lock lock_ts
|
||||
now=$(date -u +%s)
|
||||
stale_after=$((6 * 60 * 60))
|
||||
for lock in "${PORT_LOCK_DIR}"/*.lock; do
|
||||
[[ -e "${lock}" ]] || continue
|
||||
lock_ts=$(awk '{print $3}' "${lock}" 2>/dev/null || true)
|
||||
if [[ "${lock_ts}" =~ ^[0-9]+$ ]] && (( now - lock_ts > stale_after )); then
|
||||
rm -f "${lock}"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
reserve_port_offset() {
|
||||
local attempt candidate reservation
|
||||
cleanup_stale_port_locks
|
||||
for attempt in $(seq 0 59); do
|
||||
candidate=$(( PARTITION_BASE + ((GITHUB_RUN_ID + RUNNER_NUM * 1000 + attempt * 97) % PARTITION_SIZE) ))
|
||||
reservation="${PORT_LOCK_DIR}/${candidate}.lock"
|
||||
if ( set -o noclobber; echo "${GITHUB_RUN_ID} ${DOC_ENGINE} $(date -u +%s)" > "${reservation}" ) 2>/dev/null; then
|
||||
if port_offset_available "${candidate}"; then
|
||||
PORT_OFFSET=${candidate}
|
||||
PORT_RESERVATION=${reservation}
|
||||
return 0
|
||||
fi
|
||||
rm -f "${reservation}"
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
if ! reserve_port_offset; then
|
||||
echo "Failed to reserve a free host port range for ${DOC_ENGINE} docker compose" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "PORT_RESERVATION=${PORT_RESERVATION}" >> ${GITHUB_ENV}
|
||||
echo "Using ${DOC_ENGINE} host port offset ${PORT_OFFSET}"
|
||||
ES_PORT=$((1200 + PORT_OFFSET))
|
||||
OS_PORT=$((1201 + PORT_OFFSET))
|
||||
INFINITY_THRIFT_PORT=$((23817 + PORT_OFFSET))
|
||||
INFINITY_HTTP_PORT=$((23820 + PORT_OFFSET))
|
||||
INFINITY_PSQL_PORT=$((5432 + PORT_OFFSET))
|
||||
EXPOSE_MYSQL_PORT=$((5455 + PORT_OFFSET))
|
||||
MINIO_PORT=$((9000 + PORT_OFFSET))
|
||||
MINIO_CONSOLE_PORT=$((9001 + PORT_OFFSET))
|
||||
REDIS_PORT=$((6379 + PORT_OFFSET))
|
||||
NATS_PORT=$((4222 + PORT_OFFSET))
|
||||
TEI_PORT=$((6380 + PORT_OFFSET))
|
||||
KIBANA_PORT=$((6601 + PORT_OFFSET))
|
||||
SVR_HTTP_PORT=$((9380 + PORT_OFFSET))
|
||||
ADMIN_SVR_HTTP_PORT=$((9381 + PORT_OFFSET))
|
||||
SVR_MCP_PORT=$((9382 + PORT_OFFSET))
|
||||
GO_HTTP_PORT=$((9384 + PORT_OFFSET))
|
||||
GO_ADMIN_PORT=$((9383 + PORT_OFFSET))
|
||||
SANDBOX_EXECUTOR_MANAGER_PORT=$((9385 + PORT_OFFSET))
|
||||
SVR_WEB_HTTP_PORT=$((80 + PORT_OFFSET))
|
||||
SVR_WEB_HTTPS_PORT=$((443 + PORT_OFFSET))
|
||||
|
||||
# Persist computed ports into .env so docker-compose uses the correct host bindings.
|
||||
# Remove previous CI overrides first; docker compose uses the last duplicate key.
|
||||
sed -i '/^ES_PORT=/d;/^OS_PORT=/d;/^INFINITY_THRIFT_PORT=/d;/^INFINITY_HTTP_PORT=/d;/^INFINITY_PSQL_PORT=/d;/^EXPOSE_MYSQL_PORT=/d;/^MINIO_PORT=/d;/^MINIO_CONSOLE_PORT=/d;/^REDIS_PORT=/d;/^TEI_PORT=/d;/^KIBANA_PORT=/d;/^SVR_HTTP_PORT=/d;/^ADMIN_SVR_HTTP_PORT=/d;/^SVR_MCP_PORT=/d;/^GO_HTTP_PORT=/d;/^GO_ADMIN_PORT=/d;/^SANDBOX_EXECUTOR_MANAGER_PORT=/d;/^SVR_WEB_HTTP_PORT=/d;/^SVR_WEB_HTTPS_PORT=/d;/^NATS_PORT=/d;/^COMPOSE_PROFILES=/d;/^TEI_MODEL=/d;/^RAGFLOW_IMAGE=/d;/^DOC_ENGINE=/d' .env
|
||||
{
|
||||
echo ""
|
||||
echo "ES_PORT=${ES_PORT}"
|
||||
echo "OS_PORT=${OS_PORT}"
|
||||
echo "INFINITY_THRIFT_PORT=${INFINITY_THRIFT_PORT}"
|
||||
echo "INFINITY_HTTP_PORT=${INFINITY_HTTP_PORT}"
|
||||
echo "INFINITY_PSQL_PORT=${INFINITY_PSQL_PORT}"
|
||||
echo "EXPOSE_MYSQL_PORT=${EXPOSE_MYSQL_PORT}"
|
||||
echo "MINIO_PORT=${MINIO_PORT}"
|
||||
echo "MINIO_CONSOLE_PORT=${MINIO_CONSOLE_PORT}"
|
||||
echo "REDIS_PORT=${REDIS_PORT}"
|
||||
echo "NATS_PORT=${NATS_PORT}"
|
||||
echo "TEI_PORT=${TEI_PORT}"
|
||||
echo "KIBANA_PORT=${KIBANA_PORT}"
|
||||
echo "SVR_HTTP_PORT=${SVR_HTTP_PORT}"
|
||||
echo "ADMIN_SVR_HTTP_PORT=${ADMIN_SVR_HTTP_PORT}"
|
||||
echo "SVR_MCP_PORT=${SVR_MCP_PORT}"
|
||||
echo "GO_HTTP_PORT=${GO_HTTP_PORT}"
|
||||
echo "GO_ADMIN_PORT=${GO_ADMIN_PORT}"
|
||||
echo "SANDBOX_EXECUTOR_MANAGER_PORT=${SANDBOX_EXECUTOR_MANAGER_PORT}"
|
||||
echo "SVR_WEB_HTTP_PORT=${SVR_WEB_HTTP_PORT}"
|
||||
echo "SVR_WEB_HTTPS_PORT=${SVR_WEB_HTTPS_PORT}"
|
||||
echo "COMPOSE_PROFILES=${DOC_ENGINE},cpu,tei-cpu"
|
||||
echo "TEI_MODEL=BAAI/bge-small-en-v1.5"
|
||||
echo "RAGFLOW_IMAGE=${RAGFLOW_IMAGE}"
|
||||
echo "DOC_ENGINE=${DOC_ENGINE}"
|
||||
} >> .env
|
||||
echo "HOST_ADDRESS=http://host.docker.internal:${SVR_HTTP_PORT}" >> ${GITHUB_ENV}
|
||||
|
||||
# Patch entrypoint.sh for coverage
|
||||
sed -i '/"\$PY" api\/ragflow_server.py \${INIT_SUPERUSER_ARGS} &/c\ echo "Ensuring coverage is installed..."\n "$PY" -m pip install coverage -i https://mirrors.aliyun.com/pypi/simple\n export COVERAGE_FILE=/ragflow/logs/.coverage\n echo "Starting ragflow_server with coverage..."\n "$PY" -m coverage run --source=./api/apps --omit="*/tests/*,*/migrations/*" -a api/ragflow_server.py ${INIT_SUPERUSER_ARGS} &' ./entrypoint.sh
|
||||
|
||||
|
||||
- name: Start ragflow:nightly for Elasticsearch
|
||||
run: |
|
||||
sed -i 's/^DOC_ENGINE=.*$/DOC_ENGINE=elasticsearch/' docker/.env
|
||||
sudo docker compose -f docker/docker-compose.yml -p ${GITHUB_RUN_ID} down -v || true
|
||||
sudo docker ps -a --filter "label=com.docker.compose.project=${GITHUB_RUN_ID}" -q | xargs -r sudo docker rm -f
|
||||
sudo docker compose -f docker/docker-compose.yml -p ${GITHUB_RUN_ID} up -d
|
||||
sudo docker compose -f docker/docker-compose.yml -p ${COMPOSE_PROJECT_NAME} down -v || true
|
||||
sudo docker ps -a --filter "label=com.docker.compose.project=${COMPOSE_PROJECT_NAME}" -q | xargs -r sudo docker rm -f
|
||||
sudo docker compose -f docker/docker-compose.yml -p ${COMPOSE_PROJECT_NAME} up -d
|
||||
|
||||
- name: Run sdk tests against Elasticsearch
|
||||
run: |
|
||||
@@ -566,8 +814,8 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
echo "Start to run test sdk on Elasticsearch"
|
||||
source .venv/bin/activate && set -o pipefail; pytest -s --tb=short --level=${HTTP_API_TEST_LEVEL} --junitxml=pytest-infinity-sdk.xml --cov=sdk/python/ragflow_sdk --cov-branch --cov-report=xml:coverage-es-sdk.xml test/testcases/test_sdk_api 2>&1 | tee es_sdk_test.log
|
||||
|
||||
source .venv/bin/activate && set -o pipefail; pytest -s --tb=short --level=${HTTP_API_TEST_LEVEL} --junitxml=pytest-es-sdk.xml --cov=sdk/python/ragflow_sdk --cov-branch --cov-report=xml:coverage-es-sdk.xml test/testcases/test_sdk_api 2>&1 | tee es_sdk_test.log
|
||||
|
||||
- name: Run New RESTFUL api tests against Elasticsearch
|
||||
run: |
|
||||
export http_proxy=""; export https_proxy=""; export no_proxy=""; export HTTP_PROXY=""; export HTTPS_PROXY=""; export NO_PROXY=""
|
||||
@@ -711,7 +959,7 @@ jobs:
|
||||
else
|
||||
echo "ragflow_server.py not found!"
|
||||
fi
|
||||
sudo docker compose -f docker/docker-compose.yml -p ${GITHUB_RUN_ID} stop
|
||||
sudo docker compose -f docker/docker-compose.yml -p ${COMPOSE_PROJECT_NAME} stop
|
||||
|
||||
- name: Generate server coverage report Elasticsearch
|
||||
if: ${{ !cancelled() }}
|
||||
@@ -733,7 +981,7 @@ jobs:
|
||||
else
|
||||
echo ".coverage file not found!"
|
||||
fi
|
||||
|
||||
|
||||
- name: Collect ragflow log Elasticsearch
|
||||
if: ${{ !cancelled() }}
|
||||
run: |
|
||||
@@ -749,8 +997,11 @@ jobs:
|
||||
if: always() # always run this step even if previous steps failed
|
||||
run: |
|
||||
# Sometimes `docker compose down` fail due to hang container, heavy load etc. Need to remove such containers to release resources(for example, listen ports).
|
||||
sudo docker compose -f docker/docker-compose.yml -p ${GITHUB_RUN_ID} down -v || true
|
||||
sudo docker ps -a --filter "label=com.docker.compose.project=${GITHUB_RUN_ID}" -q | xargs -r sudo docker rm -f
|
||||
sudo docker compose -f docker/docker-compose.yml -p ${COMPOSE_PROJECT_NAME} down -v || true
|
||||
sudo docker ps -a --filter "label=com.docker.compose.project=${COMPOSE_PROJECT_NAME}" -q | xargs -r sudo docker rm -f
|
||||
if [[ -n ${RAGFLOW_IMAGE} ]]; then
|
||||
sudo docker rmi -f ${RAGFLOW_IMAGE}
|
||||
fi
|
||||
if [[ -n ${PORT_RESERVATION:-} ]]; then
|
||||
rm -f "${PORT_RESERVATION}"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user