Skip to content

Test Analysis Plugins #393

Test Analysis Plugins

Test Analysis Plugins #393

name: Test Analysis Plugins
defaults:
run:
shell: bash
on:
schedule:
- cron: '30 18 * * *'
workflow_dispatch:
inputs:
PRODUCT:
description: 'Product to test'
required: true
type: choice
options:
- elasticsearch
- opensearch
MANUAL_VERSION:
description: 'Version to test'
required: false
type: string
default: '7.10.2'
jobs:
build-test-matrix:
name: Build Test Matrix
runs-on: ubuntu-latest
outputs:
# This output will be a JSON string like '[{"product": "...", "version": "..."}, ...]'
# or '[]' if no tests are to be run.
matrix_json: ${{ steps.generate_matrix.outputs.matrix_json_string }}
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup bootstrap
uses: ./containers/bootstrap
- name: Get Date One Day Ago for Schedule
id: get_date_schedule
if: ${{ github.event_name == 'schedule' }}
run: |
if [[ "$(uname)" == "Darwin" ]]; then
echo "days_ago=$(date -u -v-3d +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
else
echo "days_ago=$(date -u -d '3 days ago' +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
fi
cat $GITHUB_OUTPUT
- name: Generate Test Matrix Configuration
id: generate_matrix
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_PRODUCT: ${{ inputs.PRODUCT }}
INPUT_MANUAL_VERSION: ${{ inputs.MANUAL_VERSION }}
SINCE_DATE_SCHEDULE: ${{ steps.get_date_schedule.outputs.days_ago }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
FINAL_MATRIX_ENTRIES="[]"
# Function to create a JSON entry for the matrix
generate_matrix_entry_json() {
local prod="$1"
local ver="$2"
# Ensure product and version are not empty before creating entry
if [[ -n "$prod" && -n "$ver" ]]; then
echo "{\"product\": \"$prod\", \"version\": \"$ver\"}"
fi
}
# Function to fetch latest stable version for a given product
fetch_latest_stable_version() {
local product_name="$1"; local repo_url="$2"; local version_prefix="$3"; local version_regex="$4";
local latest_version=""
echo "Fetching latest stable for $product_name (for dispatch without manual version)..."
RELEASES_PAYLOAD=$(curl -H "Authorization: Bearer $GH_TOKEN" "$repo_url?per_page=10") # Get a few recent
latest_version=$(echo "$RELEASES_PAYLOAD" \
| jq -r --arg prefix "$version_prefix" --arg regex "$version_regex" '[.[] |
select(.prerelease==false and .draft==false and (.tag_name | startswith($prefix)) and (.tag_name | test($regex)))
| .tag_name | sub($prefix; "")] | .[0]')
if [[ -n "$latest_version" && "$latest_version" != "null" ]]; then
echo "$latest_version"
fi
}
# Function to fetch recent (last 7 days) stable versions for a product
fetch_recent_schedule_versions() {
local product_name="$1"; local repo_url="$2"; local version_prefix="$3"; local version_regex="$4";
local current_matrix="$FINAL_MATRIX_ENTRIES" # Use the global accumulator
echo "Fetching $product_name releases since $SINCE_DATE_SCHEDULE..."
RELEASES_PAYLOAD=$(curl -H "Authorization: Bearer $GH_TOKEN" "$repo_url?per_page=10")
RECENT_VERSIONS_FOUND=$(echo "$RELEASES_PAYLOAD" \
| jq -r --arg since_date "$SINCE_DATE_SCHEDULE" --arg prefix "$version_prefix" --arg regex "$version_regex" '[.[] |
select(.prerelease==false and .draft==false and (.published_at >= $since_date) and (.tag_name | startswith($prefix)) and (.tag_name | test($regex)))
| .tag_name | sub($prefix; "")] | unique | .[]') # Get unique versions, one per line
if [[ -n "$RECENT_VERSIONS_FOUND" ]]; then
echo "Found recent stable $product_name versions: $RECENT_VERSIONS_FOUND"
for version_item in $RECENT_VERSIONS_FOUND; do
entry_json=$(generate_matrix_entry_json "$product_name" "$version_item")
if [[ -n "$entry_json" ]]; then
current_matrix=$(echo "$current_matrix" | jq --argjson entry "$entry_json" '. + [$entry]')
fi
done
else
echo "No stable $product_name versions found published since $SINCE_DATE_SCHEDULE for schedule."
fi
FINAL_MATRIX_ENTRIES="$current_matrix" # Update the global accumulator
}
if [[ "$EVENT_NAME" == 'schedule' ]]; then
fetch_recent_schedule_versions "elasticsearch" "https://api.github.com/repos/elastic/elasticsearch/releases" "v" "^v[0-9]+\\.[0-9]+\\.[0-9]+(\\.[0-9]+)?$"
fetch_recent_schedule_versions "opensearch" "https://api.github.com/repos/opensearch-project/OpenSearch/releases" "" "^[0-9]+\\.[0-9]+\\.[0-9]+(\\.[0-9]+)?$"
elif [[ "$EVENT_NAME" == 'workflow_dispatch' ]]; then
if [[ -n "$INPUT_MANUAL_VERSION" ]]; then
echo "Using manually provided version: $INPUT_MANUAL_VERSION for product: $INPUT_PRODUCT"
entry=$(generate_matrix_entry_json "$INPUT_PRODUCT" "$INPUT_MANUAL_VERSION")
if [[ -n "$entry" ]]; then FINAL_MATRIX_ENTRIES=$(echo "$FINAL_MATRIX_ENTRIES" | jq --argjson entry "$entry" '. + [$entry]'); fi
else
echo "Manual version not provided for dispatch. Fetching latest stable for $INPUT_PRODUCT..."
LATEST_VER=""
if [[ "$INPUT_PRODUCT" == "elasticsearch" ]]; then
LATEST_VER=$(fetch_latest_stable_version "elasticsearch" "https://api.github.com/repos/elastic/elasticsearch/releases" "v" "^v[0-9]+\\.[0-9]+\\.[0-9]+(\\.[0-9]+)?$")
elif [[ "$INPUT_PRODUCT" == "opensearch" ]]; then
LATEST_VER=$(fetch_latest_stable_version "opensearch" "https://api.github.com/repos/opensearch-project/OpenSearch/releases" "" "^[0-9]+\\.[0-9]+\\.[0-9]+(\\.[0-9]+)?$")
fi
if [[ -n "$LATEST_VER" ]]; then
echo "Latest stable for $INPUT_PRODUCT is $LATEST_VER"
entry=$(generate_matrix_entry_json "$INPUT_PRODUCT" "$LATEST_VER")
if [[ -n "$entry" ]]; then FINAL_MATRIX_ENTRIES=$(echo "$FINAL_MATRIX_ENTRIES" | jq --argjson entry "$entry" '. + [$entry]'); fi
else
echo "::error::Could not find latest stable version for $INPUT_PRODUCT on dispatch."
# FINAL_MATRIX_ENTRIES will remain '[]', so the next job won't run due to its 'if' condition
fi
fi
fi
echo "Generated matrix entries (multiline for readability):"
echo "$FINAL_MATRIX_ENTRIES"
SINGLE_LINE_JSON=$(echo "$FINAL_MATRIX_ENTRIES" | jq -c .)
echo "matrix_json_string=$SINGLE_LINE_JSON" >> $GITHUB_OUTPUT
test-engine-plugins:
name: Test Plugins on ${{ matrix.product }} @ ${{ matrix.version }}
needs: build-test-matrix
if: ${{ needs.build-test-matrix.outputs.matrix_json != '[]' }}
runs-on: ubuntu-latest
env:
SECURITY_ENABLED: true
ENGINE_PASSWORD: ${{ secrets.SEARCH_APIKEY }}
JAVA_OPTS: '-Xms1g -Xmx1g'
CONTAINER_NAME_BASE: 'search-engine-node'
PORT: '9200'
WAIT_SECONDS: '600'
PRODUCT_ENV: ${{ matrix.product }}
VERSION_ENV: ${{ matrix.version }}
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.build-test-matrix.outputs.matrix_json) }}
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup bootstrap
uses: ./containers/bootstrap
- name: Display Matrix Combination
run: |
echo
echo Testing Product: ${{ matrix.product }}, Version: ${{ matrix.version }}
if [[ "${{ matrix.product }}" == "elasticsearch" ]]; then
MAJOR_VERSION=$(echo "${{ matrix.version }}" | cut -d. -f1)
if [[ "$MAJOR_VERSION" -lt 8 ]]; then
echo SECURITY_ENABLED=false >> $GITHUB_ENV
echo "Security is not enabled for ${{ matrix.product }} when versions < 8."
fi
else
echo SECURITY_ENABLED=falase >> $GITHUB_ENV
echo "Security is not enabled for ${{ matrix.product }}."
fi
- name: Run Search Engine (${{ matrix.product }} @ ${{ matrix.version }})
uses: ./containers/search-engine
with:
engine-type: ${{ matrix.product }}
engine-version: ${{ matrix.version }}
security-enabled: ${{ env.SECURITY_ENABLED }}
engine_password: ${{ env.ENGINE_PASSWORD }}
port: ${{ env.PORT }}
java-opts: ${{ env.JAVA_OPTS }}
container-name: ${{ env.CONTAINER_NAME_BASE }}-${{ matrix.product }}-${{ matrix.version }}-${{ github.run_id }}
plugins: 'analysis-ik,analysis-pinyin,analysis-stconvert'
wait-for-seconds: ${{ env.WAIT_SECONDS }}
- name: Setup Test Environment Variables
id: setup_test_env
run: |
echo "Setting up test environment variables..."
PROTOCOL_VAR="http"
# Set default CURL options when use -no-rc to avoid .curlrc issues
CURL_OPTS_VAR="-q -s --fail --connect-timeout 10 --max-time 30"
AUTH_OPTS_VAR=""
CURRENT_USER_VAR=""
if [[ "${{ env.SECURITY_ENABLED }}" == "true" ]]; then
PROTOCOL_VAR="https"
CURL_OPTS_VAR="$CURL_OPTS_VAR -k"
if [[ -n "${{ env.ENGINE_PASSWORD }}" ]]; then
if [[ "${{ matrix.product }}" == "elasticsearch" ]]; then CURRENT_USER_VAR="elastic";
elif [[ "${{ matrix.product }}" == "opensearch" ]]; then CURRENT_USER_VAR="admin";
else echo "Error: Unknown product '${{ matrix.product }}' for user determination" && exit 1; fi
AUTH_OPTS_VAR="-u $CURRENT_USER_VAR:${{ env.ENGINE_PASSWORD }}"
else
echo "Error: Security enabled for ${{ matrix.product }} but ENGINE_PASSWORD_GLOBAL is not set!" && exit 1
fi
fi
echo "PROTOCOL=${PROTOCOL_VAR}" >> $GITHUB_OUTPUT
echo "CURL_OPTS=${CURL_OPTS_VAR}" >> $GITHUB_OUTPUT
echo "AUTH_OPTS=${AUTH_OPTS_VAR}" >> $GITHUB_OUTPUT
echo "BASE_URL=${PROTOCOL_VAR}://localhost:${{ env.PORT }}" >> $GITHUB_OUTPUT
- name: Verify Loaded Plugins
if: success()
env:
PROTOCOL: ${{ steps.setup_test_env.outputs.PROTOCOL }}
CURL_OPTS: ${{ steps.setup_test_env.outputs.CURL_OPTS }}
AUTH_OPTS: ${{ steps.setup_test_env.outputs.AUTH_OPTS }}
BASE_URL: ${{ steps.setup_test_env.outputs.BASE_URL }}
run: |
PRODUCT_ENV="${{ matrix.product }}"
echo "--- Verifying loaded plugins for $PRODUCT_ENV ---"
PLUGIN_LIST_CMD="curl $CURL_OPTS $AUTH_OPTS '${BASE_URL}/_cat/plugins?h=component&format=txt'"
echo "Executing: $PLUGIN_LIST_CMD"
PLUGIN_LIST_OUTPUT=$(eval "$PLUGIN_LIST_CMD")
if [ $? -ne 0 ]; then
echo "Error: Failed to fetch plugin list from $BASE_URL/_cat/plugins for $PRODUCT_ENV"
curl $CURL_OPTS $AUTH_OPTS "$BASE_URL" || echo "Engine root is also not responding for $PRODUCT_ENV."
exit 1
fi
echo "Installed plugins reported by $PRODUCT_ENV _cat/plugins:"
echo "$PLUGIN_LIST_OUTPUT"
echo "$PLUGIN_LIST_OUTPUT" | grep -q "analysis-ik" || (echo "Error: IK plugin not found for $PRODUCT_ENV!" && exit 1)
echo "$PLUGIN_LIST_OUTPUT" | grep -q "analysis-pinyin" || (echo "Error: Pinyin plugin not found for $PRODUCT_ENV!" && exit 1)
echo "$PLUGIN_LIST_OUTPUT" | grep -q "analysis-stconvert" || (echo "Error: Stconvert plugin not found for $PRODUCT_ENV!" && exit 1)
echo "All specified plugins are listed for $PRODUCT_ENV."
- name: Verify IK Analyzer
if: success()
env:
PROTOCOL: ${{ steps.setup_test_env.outputs.PROTOCOL }}
CURL_OPTS: ${{ steps.setup_test_env.outputs.CURL_OPTS }}
AUTH_OPTS: ${{ steps.setup_test_env.outputs.AUTH_OPTS }}
BASE_URL: ${{ steps.setup_test_env.outputs.BASE_URL }}
run: |
PRODUCT_ENV="${{ matrix.product }}"
echo "--- Verifying IK Analyzer for $PRODUCT_ENV ---"
ANALYSIS_PAYLOAD_IK='{"analyzer": "ik_smart", "text": "中华人民共和国国歌"}'
# Corrected: Use env vars for TNAME, VERSION_ENV, and github.run_id
IK_ANALYSIS_RESULT_FILE="/tmp/ik_analysis_result_${PRODUCT_ENV}_${VERSION_ENV//./_}_${{ github.run_id }}.json"
ANALYZE_CMD="curl $CURL_OPTS $AUTH_OPTS -w '%{http_code}' -o '$IK_ANALYSIS_RESULT_FILE' -X POST '${BASE_URL}/_analyze' -H 'Content-Type: application/json' -d '$ANALYSIS_PAYLOAD_IK'"
HTTP_STATUS=$(eval "$ANALYZE_CMD")
if [[ "$HTTP_STATUS" -ne 200 ]]; then
echo "Error: IK analysis request failed with HTTP status $HTTP_STATUS for $PRODUCT_ENV. Response:"
cat "$IK_ANALYSIS_RESULT_FILE"
exit 1
fi
# Check if the expected token is present in the analysis result
if ! cat "$IK_ANALYSIS_RESULT_FILE" | jq -e '.tokens[] | select(.token=="中华人民共和国")'; then
echo "Error: IK analysis for '中华人民共和国' failed on $PRODUCT_ENV." && exit 1
fi
echo "IK Analyzer test passed for $PRODUCT_ENV."
- name: Verify Pinyin Analyzer
if: success()
env:
PROTOCOL: ${{ steps.setup_test_env.outputs.PROTOCOL }}
CURL_OPTS: ${{ steps.setup_test_env.outputs.CURL_OPTS }}
AUTH_OPTS: ${{ steps.setup_test_env.outputs.AUTH_OPTS }}
BASE_URL: ${{ steps.setup_test_env.outputs.BASE_URL }}
run: |
PRODUCT_ENV="${{ matrix.product }}"
echo "--- Verifying Pinyin Analyzer for $PRODUCT_ENV ---"
ANALYSIS_PAYLOAD_PINYIN='{"analyzer": "pinyin", "text": "中华人民共和国国歌"}'
# Corrected: Use env vars for TNAME, VERSION_ENV, and github.run_id
PINYIN_ANALYSIS_RESULT_FILE="/tmp/pinyin_analysis_result_${PRODUCT_ENV}_${VERSION_ENV//./_}_${{ github.run_id }}.json"
ANALYZE_CMD="curl ${{ env.CURL_OPTS }} ${{ env.AUTH_OPTS }} -w '%{http_code}' -o '$PINYIN_ANALYSIS_RESULT_FILE' -X POST '${{ env.BASE_URL }}/_analyze' -H 'Content-Type: application/json' -d '$ANALYSIS_PAYLOAD_PINYIN'"
HTTP_STATUS=$(eval "$ANALYZE_CMD")
if [[ "$HTTP_STATUS" -ne 200 ]]; then
echo "Error: Pinyin analysis request failed with HTTP status $HTTP_STATUS for $PRODUCT_ENV. Response:"
cat "$PINYIN_ANALYSIS_RESULT_FILE"
exit 1
fi
# Check if the expected token part is present in the analysis result
if ! cat "$PINYIN_ANALYSIS_RESULT_FILE" | jq -e --arg token_part "zhrmghggg" '.tokens[] | select(.token | contains($token_part))'; then
echo "Error: Pinyin analysis for '中华人民共和国国歌' did not produce expected token part 'zhrmghggg' on $PRODUCT_ENV."
exit 1
fi
echo "Pinyin Analyzer test passed for $PRODUCT_ENV."
- name: Verify Stconvert Analyzer
if: success()
env:
PROTOCOL: ${{ steps.setup_test_env.outputs.PROTOCOL }}
CURL_OPTS: ${{ steps.setup_test_env.outputs.CURL_OPTS }}
AUTH_OPTS: ${{ steps.setup_test_env.outputs.AUTH_OPTS }}
BASE_URL: ${{ steps.setup_test_env.outputs.BASE_URL }}
run: |
PRODUCT_ENV="${{ matrix.product }}"
echo "--- Verifying Stconvert Analyzer for $PRODUCT_ENV ---"
INDEX_NAME="stconvert_index_${PRODUCT_ENV}_${VERSION_ENV//./_}_${{ github.run_id }}"
STCONVERT_RESULT_FILE_PREFIX="/tmp/${INDEX_NAME}"
# Define the index with stconvert analyzer
INDEX_SETTINGS_PAYLOAD='{"settings":{"analysis":{"analyzer":{"my_st_analyzer":{"tokenizer":"standard","filter":["lowercase","st_token_filter"],"char_filter":["st_char_filter"]}},"filter":{"st_token_filter":{"type":"stconvert","convert_type":"t2s","keep_both":false,"delimiter":","}},"char_filter":{"st_char_filter":{"type":"stconvert","convert_type":"t2s"}}}}}'
CREATE_INDEX_CMD="curl $CURL_OPTS $AUTH_OPTS -X PUT '${BASE_URL}/${INDEX_NAME}' -H 'Content-Type: application/json' -d '$INDEX_SETTINGS_PAYLOAD'"
HTTP_STATUS_CREATE=$(eval "$CREATE_INDEX_CMD -w '%{http_code}' -o '${STCONVERT_RESULT_FILE_PREFIX}_create.json'")
if [[ "$HTTP_STATUS_CREATE" -ne 200 && "$HTTP_STATUS_CREATE" -ne 201 ]]; then
echo "Error: Failed to create index $INDEX_NAME for stconvert test. HTTP status: $HTTP_STATUS_CREATE. Response:"
cat "${STCONVERT_RESULT_FILE_PREFIX}_create.json"
exit 1
fi
ANALYZE_PAYLOAD='{
"tokenizer": "keyword",
"filter": ["lowercase", {"type": "stconvert", "convert_type": "t2s", "keep_both": false}],
"char_filter": [{"type": "stconvert", "convert_type": "t2s"}],
"text": "中華人民共和國國歌"
}'
ANALYZE_PAYLOAD="curl $CURL_OPTS $AUTH_OPTS -w '%{http_code}' -o '${STCONVERT_RESULT_FILE_PREFIX}_analyze.json' -X POST '${BASE_URL}/_analyze' -H 'Content-Type: application/json' -d '$ANALYZE_PAYLOAD'"
HTTP_STATUS_ANALYZE=$(eval "$ANALYZE_PAYLOAD")
if [[ "$HTTP_STATUS_ANALYZE" -ne 200 ]]; then
echo "Error: Stconvert direct analysis (test 2) request failed with HTTP status $HTTP_STATUS_ANALYZE. Response:"
cat "${STCONVERT_RESULT_FILE_PREFIX}_analyze.json"
exit 1
fi
# Verify output (single token expected due to keyword tokenizer)
if ! cat "${STCONVERT_RESULT_FILE_PREFIX}_analyze.json" | jq -e --arg token_part "中华人民共和国国歌" '.tokens[] | select(.token | contains($token_part))'; then
echo "Error: Stconvert analysis for '中華人民共和國國歌' did not produce expected token part '中华人民共和国国歌' on $PRODUCT_ENV."
exit 1
fi
echo "Stconvert Analyzer test passed for $PRODUCT_ENV."
notify_on_failure:
runs-on: ubuntu-latest
needs: [build-test-matrix, test-engine-plugins]
if: failure()
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Notify message via feishu
env:
FEISHU_BOT_URL: ${{ secrets.FEISHU_BOT_WEBHOOK_URL }}
REPO_NAME: ${{ github.repository }}
WORKFLOW_NAME: ${{ github.workflow }}
RUN_ID: ${{ github.run_id }}
ACTOR: ${{ github.triggering_actor }}
SERVER_URL: ${{ github.server_url }}
run: $GITHUB_WORKSPACE/scripts/feishu_message.sh