This is an automated email from the ASF dual-hosted git repository.
wenjin272 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/flink-agents.git
The following commit(s) were added to refs/heads/main by this push:
new 334c0cd3 [e2e] Add nightly e2e test for submitting examples to flink
standalone cluster (#708)
334c0cd3 is described below
commit 334c0cd370c475ea94b9721d9c04f6a1d25ab34f
Author: Howie Wang <[email protected]>
AuthorDate: Thu Aug 6 12:05:10 2026 +0800
[e2e] Add nightly e2e test for submitting examples to flink standalone
cluster (#708)
---
.github/workflows/nightly-e2e.yml | 80 +++
.../test-scripts/test_submit_examples_to_flink.sh | 573 +++++++++++++++++++++
2 files changed, 653 insertions(+)
diff --git a/.github/workflows/nightly-e2e.yml
b/.github/workflows/nightly-e2e.yml
new file mode 100644
index 00000000..1adf197e
--- /dev/null
+++ b/.github/workflows/nightly-e2e.yml
@@ -0,0 +1,80 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+name: Nightly E2E Tests
+
+on:
+ schedule:
+ - cron: '0 0 * * *'
+ workflow_dispatch:
+ inputs:
+ flink-version:
+ description: 'Flink version to test (e.g., 2.2.0, 2.1.1)'
+ required: false
+ default: '2.2.0'
+ type: string
+ pull_request:
+ branches: [main]
+ paths:
+ - 'examples/**'
+ - 'python/flink_agents/examples/**'
+ - 'tools/install.sh'
+ - 'e2e-test/test-scripts/test_submit_examples_to_flink.sh'
+ - '.github/workflows/nightly-e2e.yml'
+
+jobs:
+ submit-examples-to-flink:
+ name: submit-examples [flink-${{ matrix.flink-version }}]
+ runs-on: ubuntu-latest
+ # Budget: download ~5m + build ~15m + cluster/submit ~5m + buffer.
+ timeout-minutes: 60
+ env:
+ SKIP_SPOTLESS_CHECK: true
+ strategy:
+ fail-fast: false
+ matrix:
+ flink-version: ['2.2.0']
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install java
+ uses: actions/setup-java@v4
+ with:
+ java-version: '11'
+ distribution: 'adopt'
+ - name: Install python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.11'
+ - name: Install uv
+ run: |
+ curl -LsSf https://astral.sh/uv/0.11.0/install.sh | sh
+ echo "$HOME/.local/bin" >> $GITHUB_PATH
+ - name: Run submit-examples E2E test
+ env:
+ FLINK_VERSION: ${{ github.event.inputs.flink-version ||
matrix.flink-version }}
+ # Use a lightweight chat model on GitHub-hosted runners (CPU only,
+ # ~7GB RAM). The script aliases this to the names examples hardcode.
+ OLLAMA_CHAT_MODEL: 'qwen3:0.6b'
+ OLLAMA_EMBED_MODEL: 'nomic-embed-text'
+ run: bash e2e-test/test-scripts/test_submit_examples_to_flink.sh
+ - name: Upload Flink logs on failure
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: flink-logs-${{ matrix.flink-version }}
+ path: flink-logs-*.tar.gz
+ if-no-files-found: ignore
diff --git a/e2e-test/test-scripts/test_submit_examples_to_flink.sh
b/e2e-test/test-scripts/test_submit_examples_to_flink.sh
new file mode 100755
index 00000000..68cadb04
--- /dev/null
+++ b/e2e-test/test-scripts/test_submit_examples_to_flink.sh
@@ -0,0 +1,573 @@
+#!/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.
+#
+#
+# Submits all Java/Python examples to a local Flink standalone cluster, runs
+# them against a local Ollama server and waits for each job to FINISH.
+# Examples are auto-discovered from the examples directories.
+#
+# Env: FLINK_VERSION (default 2.2.0), FLINK_HOME (reuse existing install),
+# VERBOSE=1 (set -x).
+
+set -euo pipefail
+
+if [[ "${VERBOSE:-0}" == "1" ]]; then
+ set -x
+fi
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[0;33m'
+BLUE='\033[0;34m'
+NC='\033[0m'
+
+log_info() { printf "${BLUE}[INFO]${NC} %s\n" "$*" >&2; }
+log_ok() { printf "${GREEN}[OK]${NC} %s\n" "$*" >&2; }
+log_warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$*" >&2; }
+log_error() { printf "${RED}[ERROR]${NC} %s\n" "$*" >&2; }
+log_section() {
+ printf
"\n${BLUE}==============================================================${NC}\n"
>&2
+ printf "${BLUE}>>> %s${NC}\n" "$*" >&2
+ printf
"${BLUE}==============================================================${NC}\n"
>&2
+}
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+ROOT_DIR="$(cd "$SCRIPT_DIR/../.."; pwd)"
+log_info "Project root: $ROOT_DIR"
+
+FLINK_VERSION="${FLINK_VERSION:-2.2.0}"
+FLINK_MAJOR_MINOR="${FLINK_VERSION%.*}"
+SUBMIT_TIMEOUT="${SUBMIT_TIMEOUT:-180}"
+JOB_FINISH_TIMEOUT="${JOB_FINISH_TIMEOUT:-300}"
+
+# Models to pull for Ollama. Override these in CI to use lighter models.
+# The script aliases the pulled chat model to the names hardcoded in
+# examples (e.g. qwen3:8b, qwen3.5:9b) so example code stays untouched.
+OLLAMA_CHAT_MODEL="${OLLAMA_CHAT_MODEL:-qwen3:8b}"
+OLLAMA_EMBED_MODEL="${OLLAMA_EMBED_MODEL:-nomic-embed-text}"
+
+# Model names referenced (hardcoded) by example code. The pulled chat model
+# is aliased to each of these via `ollama cp` when it differs.
+OLLAMA_CHAT_MODEL_ALIASES=("qwen3:8b" "qwen3.5:9b")
+
+# Bash 3 (default on macOS) lacks associative arrays.
+RESULT_NAMES=()
+RESULT_STATES=()
+SUBMITTED_JOB_IDS=()
+OLLAMA_PID=""
+
+cleanup() {
+ local exit_code=$?
+ log_section "Cleanup"
+
+ if [[ -n "${FLINK_HOME:-}" && -x "$FLINK_HOME/bin/flink" ]]; then
+ for jid in "${SUBMITTED_JOB_IDS[@]:-}"; do
+ [[ -n "$jid" ]] || continue
+ log_info "Cancelling job $jid"
+ "$FLINK_HOME/bin/flink" cancel "$jid" >/dev/null 2>&1 || true
+ done
+
+ if [[ -x "$FLINK_HOME/bin/stop-cluster.sh" ]]; then
+ log_info "Stopping Flink cluster"
+ "$FLINK_HOME/bin/stop-cluster.sh" >/dev/null 2>&1 || true
+ fi
+
+ if [[ -d "$FLINK_HOME/log" ]]; then
+ local log_archive="$ROOT_DIR/flink-logs-$(date
+%Y%m%d-%H%M%S).tar.gz"
+ tar -czf "$log_archive" -C "$FLINK_HOME" log >/dev/null 2>&1 \
+ && log_info "Flink logs archived to: $log_archive" \
+ || log_warn "Failed to archive Flink logs"
+ fi
+ fi
+
+ [[ -n "${OLLAMA_PID:-}" ]] && kill "$OLLAMA_PID" 2>/dev/null || true
+
+ print_summary
+ exit "$exit_code"
+}
+trap cleanup EXIT
+
+print_summary() {
+ log_section "Test summary"
+ local total=${#RESULT_NAMES[@]}
+ if (( total == 0 )); then
+ log_error "Test setup failed before any example was submitted"
+ return
+ fi
+ local passed=0
+ local failed=0
+ local i
+ for (( i = 0; i < total; i++ )); do
+ local name="${RESULT_NAMES[$i]}"
+ local state="${RESULT_STATES[$i]}"
+ if [[ "$state" == "PASS" ]]; then
+ printf " ${GREEN}PASS${NC} %s\n" "$name"
+ passed=$((passed + 1))
+ else
+ printf " ${RED}FAIL${NC} %s\n" "$name"
+ failed=$((failed + 1))
+ fi
+ done
+ printf "\nTotal: %d Passed: %d Failed: %d\n" "$total" "$passed"
"$failed"
+
+ if (( failed > 0 )); then
+ log_error "$failed example(s) failed to submit"
+ exit 1
+ fi
+}
+
+record_result() {
+ RESULT_NAMES+=("$1")
+ RESULT_STATES+=("$2")
+}
+
+install_flink() {
+ log_section "Step 1: install Flink standalone (version $FLINK_VERSION)"
+
+ if [[ -n "${FLINK_HOME:-}" && -x "$FLINK_HOME/bin/flink" ]]; then
+ log_info "Reusing existing FLINK_HOME: $FLINK_HOME"
+ export FLINK_HOME
+ return 0
+ fi
+
+ # Anchor VENV_DIR to the repo so we can find it after install.sh exits.
+ export VENV_DIR="${VENV_DIR:-$ROOT_DIR/.flink-agents-env}"
+
+ log_info "Running tools/install.sh --non-interactive --install-flink
--enable-pyflink"
+ FLINK_VERSION="$FLINK_VERSION" bash "$ROOT_DIR/tools/install.sh" \
+ --non-interactive --install-flink --enable-pyflink
+
+ local install_dir="${INSTALL_DIR:-$HOME/.local/flink}"
+ export FLINK_HOME="${install_dir}/flink-${FLINK_VERSION}"
+
+ if [[ ! -x "$FLINK_HOME/bin/flink" ]]; then
+ log_error "Flink installation not found at expected path: $FLINK_HOME"
+ exit 1
+ fi
+ log_ok "Flink installed at: $FLINK_HOME"
+
+ # `flink run -py` shells out to a Python interpreter that must have
+ # pyflink importable. Activate the venv install.sh provisioned and
+ # point PYFLINK_CLIENT_EXECUTABLE at it.
+ if [[ ! -x "$VENV_DIR/bin/python" ]]; then
+ log_error "Expected Python venv not found at: $VENV_DIR"
+ exit 1
+ fi
+ # shellcheck disable=SC1091
+ source "$VENV_DIR/bin/activate"
+ export PYFLINK_CLIENT_EXECUTABLE="$VENV_DIR/bin/python"
+ log_ok "Activated PyFlink venv: $VENV_DIR"
+}
+
+build_project() {
+ log_section "Step 2: build flink-agents (Java + Python)"
+ (
+ cd "$ROOT_DIR"
+ SKIP_SPOTLESS_CHECK=true bash tools/build.sh
+ )
+ log_ok "Build completed"
+}
+
+locate_examples_jar() {
+ log_section "Step 3: locate examples jar"
+ EXAMPLES_JAR=$(find "$ROOT_DIR/examples/target" -name
"flink-agents-examples-*.jar" \
+ ! -name "*sources*" ! -name "*javadoc*" ! -name "original-*" | head -n
1)
+ [[ -n "$EXAMPLES_JAR" ]] || { log_error "Examples JAR not found after
build"; exit 1; }
+ log_ok "Examples JAR: $(basename "$EXAMPLES_JAR")"
+}
+
+stage_dist_jars() {
+ log_section "Step 4: stage dist uber jar into \$FLINK_HOME/lib"
+
+ local project_version
+ project_version=$(sed -n 's/.*<version>\(.*\)<\/version>.*/\1/p' \
+ "$ROOT_DIR/pom.xml" | head -n 2 | tail -n 1)
+ log_info "Detected project version: $project_version"
+
+ # The flink-version uber jar already bundles the common deps.
+ local
flink_jar="$ROOT_DIR/dist/flink-${FLINK_MAJOR_MINOR}/target/flink-agents-dist-flink-${FLINK_MAJOR_MINOR}-${project_version}.jar"
+
+ if [[ ! -f "$flink_jar" ]]; then
+ log_error "Flink dist jar not found: $flink_jar"
+ exit 1
+ fi
+
+ # Remove any pre-existing flink-agents-dist jars to avoid classpath
conflicts
+ rm -f "$FLINK_HOME/lib/"/flink-agents-dist-*.jar
+ cp "$flink_jar" "$FLINK_HOME/lib/"
+ log_ok "Staged: $(basename "$flink_jar")"
+}
+
+start_ollama() {
+ log_section "Step 5: start Ollama server"
+ curl -fsSL https://ollama.com/install.sh | sh
+ ollama serve &
+ OLLAMA_PID=$!
+
+ local retries=30
+ for i in $(seq 1 $retries); do
+ if curl -sf http://localhost:11434/api/tags >/dev/null 2>&1; then
+ log_ok "Ollama server is ready"
+ break
+ fi
+ (( i == retries )) && { log_error "Ollama server failed to start";
exit 1; }
+ sleep 2
+ done
+
+ log_info "Pulling chat model: $OLLAMA_CHAT_MODEL"
+ ollama pull "$OLLAMA_CHAT_MODEL"
+ log_ok "Chat model $OLLAMA_CHAT_MODEL pulled successfully"
+
+ # Alias the pulled chat model to the names examples hardcode so we can
+ # run a lighter model in CI without touching example sources.
+ local alias_name
+ for alias_name in "${OLLAMA_CHAT_MODEL_ALIASES[@]}"; do
+ if [[ "$OLLAMA_CHAT_MODEL" != "$alias_name" ]]; then
+ log_info "Creating model alias: $alias_name -> $OLLAMA_CHAT_MODEL"
+ ollama cp "$OLLAMA_CHAT_MODEL" "$alias_name"
+ fi
+ done
+
+ log_info "Pulling embedding model: $OLLAMA_EMBED_MODEL"
+ ollama pull "$OLLAMA_EMBED_MODEL"
+ log_ok "Embedding model $OLLAMA_EMBED_MODEL pulled successfully"
+}
+
+start_cluster() {
+ log_section "Step 6: start Flink standalone cluster"
+ "$FLINK_HOME/bin/start-cluster.sh"
+
+ local rest_url="http://localhost:8081"
+ log_info "Waiting for JobManager REST API at $rest_url ..."
+ local i
+ for (( i = 0; i < 60; i++ )); do
+ if curl -fsS "$rest_url/overview" >/dev/null 2>&1; then
+ log_ok "Flink cluster is up"
+ return 0
+ fi
+ sleep 2
+ done
+
+ log_error "Flink cluster did not become ready in time"
+ exit 1
+}
+
+extract_job_id() {
+ # "Job has been submitted with JobID <id>"
+ grep -Eo 'JobID [0-9a-f]{32}' "$1" | tail -n 1 | awk '{print $2}'
+}
+
+# ---------------------------------------------------------------------------
+# Check Flink logs for unexpected errors/exceptions after a job completes.
+# Inspired by Apache Flink's e2e test-scripts/common.sh approach.
+# Returns 0 (success) when no unexpected errors are found; returns 1 otherwise.
+# ---------------------------------------------------------------------------
+check_logs_for_errors() {
+ local job_name="${1:-unknown}"
+
+ if [[ ! -d "$FLINK_HOME/log" ]]; then
+ log_warn "Flink log directory not found, skipping error log check"
+ return 0
+ fi
+
+ # Known benign patterns that should be excluded from error detection
+ local -a allowed_patterns=(
+ "org.apache.flink.shaded.netty"
+ "org.apache.flink.runtime.rest.handler.legacy"
+ "javax.management"
+ "akka.remote"
+ "ClassNotFoundException.*org.apache.hadoop"
+ "NoClassDefFoundError.*org.apache.hadoop"
+ "Unable to load native-hadoop"
+ )
+
+ # Build a single grep-exclude regex
+ local exclude_regex
+ exclude_regex=$(printf '%s|' "${allowed_patterns[@]}")
+ exclude_regex="${exclude_regex%|}" # trim trailing pipe
+
+ local error_lines=""
+ local logfile
+ while IFS= read -r logfile; do
+ [[ -f "$logfile" ]] || continue
+ local matches
+ matches=$(grep -E "(Exception|ERROR|Fatal)" "$logfile" \
+ | grep -Ev "$exclude_regex" 2>/dev/null || true)
+ if [[ -n "$matches" ]]; then
+ error_lines+="--- $(basename "$logfile") ---"$'\n'
+ error_lines+="$matches"$'\n'
+ fi
+ done < <(find "$FLINK_HOME/log" -name "*.log" -type f 2>/dev/null)
+
+ if [[ -n "$error_lines" ]]; then
+ log_warn "$job_name: Unexpected errors found in Flink logs:"
+ echo "$error_lines" | head -30 >&2
+ local total_lines
+ total_lines=$(echo "$error_lines" | wc -l)
+ if (( total_lines > 30 )); then
+ log_warn "... ($((total_lines - 30)) more lines truncated)"
+ fi
+ return 1
+ fi
+ return 0
+}
+
+wait_for_job_finish() {
+ local job_id="$1" name="$2" timeout_sec="${3:-$JOB_FINISH_TIMEOUT}"
+ local elapsed=0
+ while (( elapsed < timeout_sec )); do
+ local status
+ status=$("$FLINK_HOME/bin/flink" list -a 2>/dev/null | grep "$job_id"
|| true)
+ if echo "$status" | grep -q "FINISHED"; then
+ log_ok "$name reached FINISHED status"
+ sleep 2 # allow log flush
+ if check_logs_for_errors "$name"; then
+ log_ok "$name completed successfully (no unexpected errors in
logs)"
+ else
+ log_warn "$name finished but with warnings in logs (non-fatal)"
+ fi
+ return 0
+ elif echo "$status" | grep -q "FAILED\|CANCELED"; then
+ log_error "$name ended with unexpected status"
+ check_logs_for_errors "$name" || true
+ return 1
+ fi
+ sleep 5
+ elapsed=$((elapsed + 5))
+ done
+ log_error "$name timed out after ${timeout_sec}s"
+ return 1
+}
+
+submit_java_example() {
+ local class_name="$1"
+ local label="java:${class_name##*.}"
+ log_section "Submitting Java example: $class_name"
+
+ local out
+ out=$(mktemp)
+ local rc=0
+ timeout "$SUBMIT_TIMEOUT" "$FLINK_HOME/bin/flink" run \
+ --detached \
+ -c "$class_name" \
+ "$EXAMPLES_JAR" >"$out" 2>&1 || rc=$?
+ cat "$out"
+
+ if (( rc != 0 )); then
+ log_error "$label submission failed (exit $rc)"
+ record_result "$label" "FAIL"
+ rm -f "$out"
+ return 0
+ fi
+
+ local job_id
+ job_id=$(extract_job_id "$out") || true
+ rm -f "$out"
+ if [[ -z "$job_id" ]]; then
+ log_error "$label: could not extract job id"
+ record_result "$label" "FAIL"
+ return 0
+ fi
+ SUBMITTED_JOB_IDS+=("$job_id")
+
+ # Submission succeeded — record PASS immediately
+ log_ok "$label submitted successfully (JobID: $job_id)"
+ record_result "$label" "PASS"
+
+ # Optionally wait and report final status (informational only, does not
affect PASS/FAIL)
+ if wait_for_job_finish "$job_id" "$label" "$JOB_FINISH_TIMEOUT"; then
+ log_ok "$label job reached FINISHED status"
+ else
+ log_warn "$label job did not reach FINISHED (expected with lightweight
CI model)"
+ fi
+}
+
+submit_python_example() {
+ local script_path="$1"
+ local label="python:$(basename "$script_path" .py)"
+ log_section "Submitting Python example: $script_path"
+
+ if [[ ! -f "$script_path" ]]; then
+ log_error "Python example not found: $script_path"
+ record_result "$label" "FAIL"
+ return 0
+ fi
+
+ local out
+ out=$(mktemp)
+ local rc=0
+ timeout "$SUBMIT_TIMEOUT" "$FLINK_HOME/bin/flink" run \
+ --detached \
+ -py "$script_path" >"$out" 2>&1 || rc=$?
+ cat "$out"
+
+ if (( rc != 0 )); then
+ log_error "$label submission failed (exit $rc)"
+ record_result "$label" "FAIL"
+ rm -f "$out"
+ return 0
+ fi
+
+ local job_id
+ job_id=$(extract_job_id "$out") || true
+ rm -f "$out"
+ if [[ -z "$job_id" ]]; then
+ log_error "$label: could not extract job id"
+ record_result "$label" "FAIL"
+ return 0
+ fi
+ SUBMITTED_JOB_IDS+=("$job_id")
+
+ # Submission succeeded — record PASS
+ log_ok "$label submitted successfully (JobID: $job_id)"
+ record_result "$label" "PASS"
+
+ # Informational: wait and report final status
+ if wait_for_job_finish "$job_id" "$label" "$JOB_FINISH_TIMEOUT"; then
+ log_ok "$label job reached FINISHED status"
+ else
+ log_warn "$label job did not reach FINISHED (expected with lightweight
CI model)"
+ fi
+}
+
+# RAG examples run end-to-end (not as detached jobs) via flink run -py.
+# Success criterion: the script exits with code 0.
+submit_python_rag_example() {
+ local script_path="$1"
+ local label="python:$(basename "$script_path" .py)"
+ log_section "Running RAG example: $script_path"
+
+ if [[ ! -f "$script_path" ]]; then
+ log_error "RAG example not found: $script_path"
+ record_result "$label" "FAIL"
+ return 0
+ fi
+
+ local out
+ out=$(mktemp)
+ local rc=0
+ timeout "$JOB_FINISH_TIMEOUT" "$FLINK_HOME/bin/flink" run \
+ -py "$script_path" >"$out" 2>&1 || rc=$?
+ cat "$out"
+ rm -f "$out"
+
+ if (( rc != 0 )); then
+ log_error "$label execution failed (exit $rc)"
+ record_result "$label" "FAIL"
+ else
+ log_ok "$label completed successfully"
+ record_result "$label" "PASS"
+ fi
+ return 0
+}
+
+discover_java_examples() {
+ # Find all example classes by scanning the jar's manifest or known package
+ # Convention: all classes directly under org.apache.flink.agents.examples
that end with "Example"
+ local classes=()
+ while IFS= read -r class; do
+ classes+=("$class")
+ done < <(jar -tf "$EXAMPLES_JAR" \
+ | grep '^org/apache/flink/agents/examples/[^/]*Example\.class$' \
+ | sed 's|/|.|g; s|\.class$||')
+
+ if [[ ${#classes[@]} -eq 0 ]]; then
+ log_error "No Java example classes found in $EXAMPLES_JAR"
+ exit 1
+ fi
+ log_info "Discovered ${#classes[@]} Java example(s): ${classes[*]}"
+ printf '%s\n' "${classes[@]}"
+}
+
+discover_python_quickstart_examples() {
+ local dir="$ROOT_DIR/python/flink_agents/examples/quickstart"
+ local scripts=()
+ while IFS= read -r f; do
+ scripts+=("$f")
+ done < <(find "$dir" -maxdepth 1 -name '*_example.py' -type f | sort)
+
+ if [[ ${#scripts[@]} -eq 0 ]]; then
+ log_error "No Python quickstart examples found in $dir"
+ exit 1
+ fi
+ log_info "Discovered ${#scripts[@]} Python quickstart example(s)"
+ printf '%s\n' "${scripts[@]}"
+}
+
+discover_python_rag_examples() {
+ local dir="$ROOT_DIR/python/flink_agents/examples/rag"
+ if [[ ! -d "$dir" ]]; then
+ log_info "No RAG examples directory found, skipping"
+ return
+ fi
+ local scripts=()
+ while IFS= read -r f; do
+ scripts+=("$f")
+ done < <(find "$dir" -maxdepth 1 -name '*_example.py' -type f | sort)
+
+ if [[ ${#scripts[@]} -eq 0 ]]; then
+ log_info "No RAG examples found in $dir"
+ return
+ fi
+ log_info "Discovered ${#scripts[@]} Python RAG example(s)"
+ printf '%s\n' "${scripts[@]}"
+}
+
+setup_rag_knowledge_base() {
+ local
setup_script="$ROOT_DIR/python/flink_agents/examples/rag/knowledge_base_setup.py"
+ if [[ ! -f "$setup_script" ]]; then
+ log_warn "RAG knowledge_base_setup.py not found, skipping RAG setup"
+ return 1
+ fi
+ log_info "Setting up RAG knowledge base"
+ python "$setup_script" || { log_error "RAG knowledge base setup failed";
return 1; }
+ log_ok "RAG knowledge base ready"
+}
+
+main() {
+ install_flink
+ build_project
+ locate_examples_jar
+ stage_dist_jars
+ start_ollama
+ start_cluster
+
+ # Auto-discover and submit Java examples
+ log_section "Step 7: submit Java examples"
+ while IFS= read -r class; do
+ submit_java_example "$class"
+ done < <(discover_java_examples)
+
+ # Auto-discover and submit Python quickstart examples
+ log_section "Step 8: submit Python quickstart examples"
+ while IFS= read -r script; do
+ submit_python_example "$script"
+ done < <(discover_python_quickstart_examples)
+
+ # Auto-discover and run Python RAG examples (these run end-to-end, not as
detached jobs)
+ log_section "Step 9: run Python RAG examples"
+ if setup_rag_knowledge_base; then
+ while IFS= read -r script; do
+ submit_python_rag_example "$script"
+ done < <(discover_python_rag_examples)
+ else
+ log_warn "Skipping RAG examples due to setup failure"
+ fi
+}
+
+main "$@"