Files
openobserve/.github/workflows/api-testing.yml
T
Neha PandClaude Sonnet 4.6 146d5e7612 feat(tests): vortex file format test suite (scenarios 1–83) (#12472)
## Summary

- Adds **239 new tests** covering the vortex columnar file format across
78 scenarios from the test plan
- **`tests/api-testing/tests/vortex/`** — 9 test modules (83 tests):
format conversion, schema evolution, data integrity, FTS, downsampling,
API format awareness, server config, and performance/stress
- **`tests/api-testing/tests/query_agent/test_3_vortex.py`** — 156
tests: the full 155-query SQL correctness suite + scenario 39
(self-join) run post-flush against vortex files
- **`api-testing.yml`** — adds `--ignore=tests/vortex` to the OSS
default-set run; vortex tests require the enterprise binary with
`ZO_FILE_FORMAT=vortex` and are gated to enterprise CI only

## Test plan coverage

| Module | Scenarios |
|--------|-----------|
| `test_format_conversion.py` | 1–13, 7, 8, 9, 10 |
| `test_schema_evolution.py` | 14–25 |
| `test_data_integrity.py` | 55–62 |
| `test_fts.py` | 51–54 |
| `test_downsampling.py` | 47–50 |
| `test_api_format.py` | 71–73 |
| `test_config.py` | 66–67 |
| `test_performance.py` | 74–78 |
| `test_3_vortex.py` | 26–46 (155 queries + JOIN) |

## Enterprise CI

Two dedicated jobs added in the companion `openobserve/o2-enterprise` PR
on the same branch:
- **`api_query_agent_vortex`** — runs `test_3_vortex.py` with
`ZO_FILE_FORMAT=vortex`
- **`api_vortex_format_tests`** — runs the full `tests/vortex/` suite
with 4-phase server lifecycle (parquet→vortex→parquet→vortex+compaction)

OSS CI is unchanged except for the ignore rule — no vortex tests run
against the OSS binary.

## Test checklist

- [ ] `tests/vortex/` collects cleanly: `83 tests collected`
- [ ] `tests/query_agent/test_3_vortex.py` collects cleanly: `156 tests
collected`
- [ ] Enterprise CI `api_query_agent_vortex` job passes
- [ ] Enterprise CI `api_vortex_format_tests` job passes

🤖 Generated with [Claude Code](https://claude.ai/claude-code)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 13:57:17 +00:00

478 lines
17 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: API Integration Test
on:
push:
branches:
- "main"
pull_request:
branches:
- "**"
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.merge_group.head_ref || github.ref }}
cancel-in-progress: true
env:
COLUMNS: 150
ZO_ROOT_USER_EMAIL: root@example.com
ZO_ROOT_USER_PASSWORD: Complexpass#123
ZO_BASE_URL: http://localhost:5080/
ZO_BASE_URL_SC: http://localhost:5080/
WS_ZO_BASE_URL: ws://localhost:5080/
ZO_USAGE_REPORTING_ENABLED: true
ZO_USAGE_PUBLISH_INTERVAL: 5
ZO_ALERT_SCHEDULE_INTERVAL: 3
ZO_SMTP_ENABLED: true
ZO_CREATE_ORG_THROUGH_INGESTION: true
ZO_INGEST_ALLOWED_UPTO: 48 # Allow backdated data ingestion for backfill tests
ZO_SSRF_ALLOW_LOOPBACK: true # Allow localhost destinations in API tests
jobs:
check_changes:
name: check_changes
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
has_changes: ${{ steps.check.outputs.has_changes }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- id: check
run: |
CHANGED=$(git diff --name-only origin/${{ github.base_ref || 'main' }} ${{ github.sha }})
if [ -z "$CHANGED" ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
elif echo "$CHANGED" | grep -qvE '(\.md$|^docs/|^web/|^tests/ui-testing/|^tests/playwright-tests/)'; then
echo "has_changes=true" >> $GITHUB_OUTPUT
else
echo "has_changes=false" >> $GITHUB_OUTPUT
fi
api_integration_tests:
name: api_integration_tests
needs: [check_changes]
if: needs.check_changes.outputs.has_changes == 'true'
runs-on: ubicloud-standard-4
permissions:
contents: read # only need to checkout the repo
steps:
- name: Remove unused tools
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
- name: Checkout git repo
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Rust Toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-05-20
- name: Setup Rust Cache
uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
shared-key: backend-debug-mimalloc
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install Protoc
run: |
bash ./.github/protoc.sh
- name: Build OpenObserve debug binary
run: cargo build --features mimalloc
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11.6"
- name: Setup rye
uses: eifinger/setup-rye@v4
with:
enable-cache: true
working-directory: "tests/api-testing/"
version: "0.27.0"
- name: Pin cpython
run: rye pin cpython@3.11.6
working-directory: tests/api-testing/
- name: Rye sync
run: rye sync
working-directory: tests/api-testing/
- name: Run default-set tests
# Phase 6.b: regression matrix tests moved to their own
# api_regression_tests job below — they now run in parallel
# with this default-set job instead of sequentially within it.
# Cuts ~8 min off CI wall time at the cost of more runner minutes.
# NB: --force-sugar is intentionally omitted. pytest-sugar is already
# auto-loaded via the entry point; passing --force-sugar registers it
# a second time, which inflates the progress counter (CI displayed
# ~3x the real test count). Letting the plugin load via discovery
# alone keeps the counter honest.
run: |
${{ github.workspace }}/target/debug/openobserve > o2_default.log 2>&1 &
SERVER_PID_DEFAULT=$!
echo "OpenObserve PID for default tests: $SERVER_PID_DEFAULT"
for i in $(seq 1 60); do
if curl -sf http://localhost:5080/healthz > /dev/null 2>&1; then
echo "Server ready after ${i}s"; break
fi
sleep 1
done
rye run pytest --ignore=tests/regression --ignore=tests/query_agent --ignore=tests/vortex
kill $SERVER_PID_DEFAULT || true
working-directory: tests/api-testing/
- name: Upload OpenObserve server logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: openobserve-server-logs
path: tests/api-testing/o2_*.log
if-no-files-found: warn
retention-days: 14
# Dedicated query agent SQL regression suite. Runs in parallel with
# the default-set and regression jobs so the 310-case (155 queries ×
# 2 phases: memtable + parquet) suite doesn't extend CI wall time.
api_query_agent_tests:
name: api_query_agent_tests
needs: [check_changes]
if: needs.check_changes.outputs.has_changes == 'true'
runs-on: ubicloud-standard-4
permissions:
contents: read
steps:
- name: Remove unused tools
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
- name: Checkout git repo
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Rust Toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-05-20
- name: Setup Rust Cache
uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
shared-key: backend-debug-mimalloc
save-if: false # main api_integration_tests job writes the cache
- name: Install Protoc
run: bash ./.github/protoc.sh
- name: Build OpenObserve debug binary
run: cargo build --features mimalloc
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11.6"
- name: Setup rye
uses: eifinger/setup-rye@v4
with:
enable-cache: true
working-directory: "tests/api-testing/"
version: "0.27.0"
- name: Pin cpython
run: rye pin cpython@3.11.6
working-directory: tests/api-testing/
- name: Rye sync
run: rye sync
working-directory: tests/api-testing/
- name: Regenerate query-agent expected values
run: |
cd ${{ github.workspace }}/tests/test-data/query-agent
${{ github.workspace }}/tests/api-testing/.venv/bin/python compute_counts.py
- name: Run query agent tests
run: |
${{ github.workspace }}/target/debug/openobserve > o2_query_agent.log 2>&1 &
SERVER_PID=$!
for i in $(seq 1 60); do
if curl -sf http://localhost:5080/healthz > /dev/null 2>&1; then
echo "Server ready after ${i}s"; break
fi
sleep 1
done
rye run pytest tests/query_agent/ -v
kill $SERVER_PID || true
working-directory: tests/api-testing/
- name: Upload server log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: openobserve-query-agent-log
path: tests/api-testing/o2_query_agent.log
if-no-files-found: warn
retention-days: 14
# Phase 6.b: regression matrix split. The 4 env-flag-sensitive test
# configurations now run as 4 parallel jobs instead of 4 sequential
# restarts in one job. Each job builds OO once and runs its single
# configured pytest target.
api_regression_tests:
name: "api_regression / ${{ matrix.config.label }}"
needs: [check_changes]
if: needs.check_changes.outputs.has_changes == 'true'
runs-on: ubicloud-standard-4
permissions:
contents: read # only need to checkout the repo
strategy:
fail-fast: false
matrix:
config:
- label: utf8_view_false
env: "ZO_FEATURE_JOIN_MATCH_ONE_ENABLED=true ZO_UTF8_VIEW_ENABLED=false"
target: tests/regression/test_subquery_join_match.py
- label: utf8_view_true
env: "ZO_FEATURE_JOIN_MATCH_ONE_ENABLED=true ZO_UTF8_VIEW_ENABLED=true"
target: tests/regression/test_subquery_join_match.py
- label: join_match_one_true
env: "ZO_FEATURE_JOIN_MATCH_ONE_ENABLED=true"
target: tests/regression/test_logs_to_logs_join.py
- label: join_match_one_false
env: "ZO_FEATURE_JOIN_MATCH_ONE_ENABLED=false"
target: tests/regression/test_logs_to_logs_join.py
steps:
- name: Remove unused tools
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
- uses: actions/checkout@v5
with: { fetch-depth: 0 }
- name: Setup Rust Toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-05-20
- name: Setup Rust Cache
uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
shared-key: backend-debug-mimalloc
save-if: false # main api_integration_tests job writes the cache
- name: Install Protoc
run: bash ./.github/protoc.sh
- name: Build OpenObserve debug binary
run: cargo build --features mimalloc
- uses: actions/setup-python@v5
with: { python-version: "3.11.6" }
- name: Setup rye
uses: eifinger/setup-rye@v4
with:
enable-cache: true
working-directory: "tests/api-testing/"
version: "0.27.0"
- name: Pin cpython
run: rye pin cpython@3.11.6
working-directory: tests/api-testing/
- name: Rye sync
run: rye sync
working-directory: tests/api-testing/
- name: "Run ${{ matrix.config.label }} regression test"
# Server binary lives at the repo root (target/debug/openobserve);
# this step uses working-directory: tests/api-testing/, so we must
# invoke it via ${{ github.workspace }} or the shell will fail with
# "No such file or directory". --force-sugar omitted on purpose —
# pytest-sugar auto-loads via entry point, and passing the flag
# registers it twice and inflates the progress counter ~3x.
run: |
${{ matrix.config.env }} ${{ github.workspace }}/target/debug/openobserve > "o2_${{ matrix.config.label }}.log" 2>&1 &
SERVER_PID=$!
for i in $(seq 1 60); do
if curl -sf http://localhost:5080/healthz > /dev/null 2>&1; then
echo "Server ready after ${i}s"; break
fi
sleep 1
done
rye run pytest ${{ matrix.config.target }} -v
kill $SERVER_PID || true
working-directory: tests/api-testing/
- name: Upload server log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: "openobserve-regression-${{ matrix.config.label }}-log"
path: "tests/api-testing/o2_${{ matrix.config.label }}.log"
if-no-files-found: warn
retention-days: 14
# Phase 5: schemathesis fuzz job — informational (continue-on-error),
# surfaces 5xx panics across the 150 OpenAPI endpoints. Decoupled from
# api_integration_tests so its findings don't block merges yet; teams
# can promote it to blocking after the noise floor is understood.
api_fuzz_tests:
name: api_fuzz_tests
needs: [check_changes]
if: needs.check_changes.outputs.has_changes == 'true'
runs-on: ubicloud-standard-4
continue-on-error: true
permissions:
contents: read # only need to checkout the repo
steps:
- name: Remove unused tools
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
- name: Checkout git repo
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Rust Toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-05-20
- name: Setup Rust Cache
uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
shared-key: backend-debug-mimalloc
save-if: false # don't write cache from this job (the main job does)
- name: Install Protoc
run: bash ./.github/protoc.sh
- name: Build OpenObserve debug binary
run: cargo build --features mimalloc
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11.6"
- name: Setup rye
uses: eifinger/setup-rye@v4
with:
enable-cache: true
working-directory: "tests/api-testing/"
version: "0.27.0"
- name: Pin cpython
run: rye pin cpython@3.11.6
working-directory: tests/api-testing/
- name: Rye sync (schemathesis dep installed)
run: rye sync
working-directory: tests/api-testing/
- name: Start OpenObserve
run: target/debug/openobserve > o2_fuzz.log 2>&1 &
- name: Wait for /healthz
run: |
for i in $(seq 1 60); do
if curl -sf http://localhost:5080/healthz > /dev/null 2>&1; then exit 0; fi
sleep 1
done
exit 1
- name: Run schemathesis fuzz tests
# Schemathesis 4.x CLI flag renames vs the 3.x originals:
# --base-url -> --url / -u
# --auth-type basic -> removed (USER:PASS implies basic)
# --hypothesis-max-examples -> --max-examples / -n
# --hypothesis-deadline -> no equivalent; use --request-timeout
# (seconds, not ms) to bound per-request
# --data-generation-method -> --mode / -m
# `--checks not_a_server_error` keeps focus on 5xx panics (actual
# backend bugs, not spec-conformance nits). `-n 20` bounds runtime;
# `--mode positive` keeps inputs realistic (negative inputs are a
# separate exercise once the noise floor here is understood).
run: |
rye run schemathesis run \
-u http://localhost:5080 \
--auth "$ZO_ROOT_USER_EMAIL:$ZO_ROOT_USER_PASSWORD" \
--checks not_a_server_error \
-n 20 \
--request-timeout 5 \
-m positive \
--suppress-health-check=filter_too_much \
--exclude-path "/api/{org_id}/v1/logs" \
--exclude-path "/api/{org_id}/v1/metrics" \
--exclude-path "/api/{org_id}/v1/traces" \
--exclude-path "/api/{org_id}/loki/api/v1/push" \
--exclude-path "/api/{org_id}/prometheus/api/v1/write" \
--exclude-path "/api/{org_id}/dashboards" \
--exclude-path "/api/{org_id}/dashboards/{dashboard_id}" \
--exclude-path "/api/{org_id}/dashboards/{dashboard_id}/panels" \
--exclude-path "/api/{org_id}/dashboards/{dashboard_id}/panels/{panel_id}" \
--exclude-path "/api/{org_id}/pipelines/history" \
--exclude-path "/api/{org_id}/organizations/assume_service_account" \
http://localhost:5080/api-doc/openapi.json
working-directory: tests/api-testing/
- name: Upload OpenObserve server log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: openobserve-fuzz-log
path: tests/api-testing/o2_fuzz.log
if-no-files-found: warn
retention-days: 14
api_tests_summary:
runs-on: ubuntu-latest
permissions: {}
needs: [check_changes, api_integration_tests, api_query_agent_tests, api_regression_tests]
if: always()
steps:
- name: Check test results
run: |
if [ "${{ needs.check_changes.result }}" != "success" ]; then
echo "check_changes job did not succeed: ${{ needs.check_changes.result }}"
exit 1
fi
# All three test jobs must pass (or be skipped).
ok() { [ "$1" == "success" ] || [ "$1" == "skipped" ]; }
if ok "${{ needs.api_integration_tests.result }}" && ok "${{ needs.api_query_agent_tests.result }}" && ok "${{ needs.api_regression_tests.result }}"; then
echo "All API test jobs passed or skipped"
exit 0
else
echo "API tests failed: integration=${{ needs.api_integration_tests.result }}, query_agent=${{ needs.api_query_agent_tests.result }}, regression=${{ needs.api_regression_tests.result }}"
exit 1
fi