diff --git a/.github/workflows/issue-auto-closer.yml b/.github/workflows/issue-auto-closer.yml deleted file mode 100644 index c1497bc4..00000000 --- a/.github/workflows/issue-auto-closer.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Autocloser -on: [issues] -jobs: - autoclose: - runs-on: ubuntu-latest - steps: - - name: Autoclose issues that did not follow issue template - uses: roots/issue-closer-action@v1.2 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - issue-close-message: "@${issue.user.login} this issue was automatically closed because it did not follow the issue template." - issue-pattern: "(.*Problem.*)|(.*Expected Behavior or What you need to ask.*)|(.*Using Fluentd and ES plugin versions.*)|(.*Is your feature request related to a problem? Please describe.*)|(.*Describe the solution you'd like.*)|(.*Describe alternatives you've considered.*)" diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index e0d1e39c..0f3cdf0f 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -1,6 +1,5 @@ name: Testing on Ubuntu on: - - push - pull_request permissions: contents: read diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml deleted file mode 100644 index 2cbc2b2b..00000000 --- a/.github/workflows/macos.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Testing on macOS -on: - - push - - pull_request -permissions: - contents: read - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - ruby: [ '3.0', '3.1', '3.2', '3.3', '3.4' ] - os: - - macOS-latest - name: Ruby ${{ matrix.ruby }} unit testing on ${{ matrix.os }} - steps: - - uses: actions/checkout@v5 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - - name: unit testing - env: - CI: true - run: | - gem install bundler rake - bundle install --jobs 4 --retry 3 - bundle exec rake test diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml deleted file mode 100644 index 19eec108..00000000 --- a/.github/workflows/windows.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Testing on Windows -on: - - push - - pull_request -permissions: - contents: read - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - ruby: [ '3.0', '3.1', '3.2', '3.3', '3.4' ] - os: - - windows-latest - name: Ruby ${{ matrix.ruby }} unit testing on ${{ matrix.os }} - steps: - - uses: actions/checkout@v5 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - - name: unit testing - env: - CI: true - run: | - gem install bundler rake - bundle install --jobs 4 --retry 3 - bundle exec rake test diff --git a/README.compat.md b/README.compat.md new file mode 100644 index 00000000..7457e5a8 --- /dev/null +++ b/README.compat.md @@ -0,0 +1,77 @@ +# Compat + +## Mixed Elasticsearch Version Environments + +### Compatibility Matrix + +| Elasticsearch Gem | ES 7.x Server | ES 8.x Server | ES 9.x Server | +|-------------------|---------------|---------------|---------------| +| gem 7.17.x | ✅ Works | ✅ Works | ❌ No | +| gem 8.15.x | ✅ Works | ✅ Works | ❌ No | +| gem 9.1.x + patch | ✅ With config| ✅ With config| ✅ Works | + +### Configuration Options + +#### force_content_type + +Type: String +Default: nil (auto-detect) +Valid values: "application/json", "application/x-ndjson" +Manually override the Content-Type header. Required when using elasticsearch gem 9.x with ES 7.x or 8.x servers. + +```ruby + + @type elasticsearch + force_content_type application/json + +``` + +#### ignore_version_content_type_mismatch + +Type: Bool +Default: false +Automatically fallback to application/json if Content-Type version mismatch occurs. Enables seamless operation across mixed ES 7/8/9 environments. + +Example: + +```ruby + + @type elasticsearch + force_content_type application/json + ignore_version_content_type_mismath true + +``` + +### Recommended Configuration + +#### For ES 7/8 environments (Recommended) + +Use elasticsearch gem 8.x - works with both versions, no configuration needed: + +```ruby +# Gemfile +gem 'elasticsearch', '~> 8.15.0' + +# fluent.conf + + @type elasticsearch + hosts es7:9200,es8:9200 + # No special config needed! + +``` + +#### For gem 9.x with ES 7/8 (Not recommended, but supported) + +```ruby +# Gemfile +gem 'elasticsearch', '~> 9.1.0' + +# fluent.conf + + @type elasticsearch + hosts es7:9200,es8:9200 + + # REQUIRED: Force compatible content type + force_content_type application/json + +``` diff --git a/lib/fluent/plugin/elasticsearch_api_bugfix.rb b/lib/fluent/plugin/elasticsearch_api_bugfix.rb new file mode 100644 index 00000000..dff59e9b --- /dev/null +++ b/lib/fluent/plugin/elasticsearch_api_bugfix.rb @@ -0,0 +1,42 @@ +# Elasticsearch API 9.x Compatibility Patch +# +# Fixes crashes in elasticsearch-api gem 9.1.2 when connecting to ES 7.x/8.x servers. +# +# Bug: The gem expects ES 9 headers and crashes with NoMethodError when they're nil +# +# This patch is only needed if using elasticsearch gem 9.x +# Not needed if using elasticsearch gem 7.x or 8.x + +require 'elasticsearch/api' + +module Elasticsearch + module API + module Utils + class << self + if method_defined?(:update_ndjson_headers!) + alias_method :original_update_ndjson_headers!, :update_ndjson_headers! + + def update_ndjson_headers!(headers, client_headers) + return headers unless client_headers.is_a?(Hash) + + current_content = client_headers.keys.find { |c| c.to_s.match?(/content[-_]?type/i) } + return headers unless current_content + + content_value = client_headers[current_content] + return headers unless content_value + + # ES 7/8 compatibility: Only process ES9-specific headers + # If no "compatible-with" present, this is ES 7/8 format + return headers unless content_value.to_s.include?('compatible-with') + + # ES 9 detected, safe to call original + original_update_ndjson_headers!(headers, client_headers) + rescue StandardError => e + warn "[elasticsearch-api-compat] Failed to update headers: #{e.class} - #{e.message}" + headers + end + end + end + end + end +end diff --git a/lib/fluent/plugin/out_elasticsearch.rb b/lib/fluent/plugin/out_elasticsearch.rb index 5fef8528..ef732a57 100644 --- a/lib/fluent/plugin/out_elasticsearch.rb +++ b/lib/fluent/plugin/out_elasticsearch.rb @@ -27,6 +27,7 @@ require_relative 'elasticsearch_index_lifecycle_management' require_relative 'elasticsearch_tls' require_relative 'elasticsearch_fallback_selector' +require_relative 'elasticsearch_api_bugfix' begin require_relative 'oj_serializer' rescue LoadError @@ -177,6 +178,12 @@ def initialize(retry_stream) config_param :use_legacy_template, :bool, :default => true config_param :catch_transport_exception_on_retry, :bool, :default => true config_param :target_index_affinity, :bool, :default => false + config_param :force_content_type, :string, :default => nil, + :desc => "Force specific Content-Type header (e.g., 'application/json' or 'application/x-ndjson'). " \ + "Overrides automatic version detection. Useful for mixed ES environments." + config_param :ignore_version_content_type_mismatch, :bool, :default => false, + :desc => "Automatically fallback to application/json if Content-Type version mismatch occurs. " \ + "Enables seamless operation across mixed ES 7/8/9 environments." config_section :metadata, param_name: :metainfo, multi: false do config_param :include_chunk_id, :bool, :default => false @@ -361,14 +368,31 @@ class << self @type_name = nil end @accept_type = nil - if @content_type != ES9_CONTENT_TYPE + + # Only set ES9 content type if not overridden and mismatch handling is not enabled + if @content_type.to_s != ES9_CONTENT_TYPE && !@ignore_version_content_type_mismatch log.trace "Detected ES 9.x or above: Content-Type will be adjusted." @content_type = ES9_CONTENT_TYPE @accept_type = ES9_CONTENT_TYPE + elsif @ignore_version_content_type_mismatch + log.info "Ignoring ES version for Content-Type, using application/json for compatibility" + @content_type = :'application/json' + @accept_type = nil end end end + if @content_type.nil? + log.warn "content_type was nil, defaulting to application/json" + @content_type = :'application/json' + end + + if @force_content_type + log.info "Forcing Content-Type to: #{@force_content_type}" + @content_type = @force_content_type + @accept_type = nil + end + if @validate_client_version && !dry_run? if @last_seen_major_version != client_library_version.to_i raise Fluent::ConfigError, <<-EOC @@ -623,11 +647,17 @@ def client(host = nil, compress_connection = false) else {} end - headers = { 'Content-Type' => @content_type.to_s } + + content_type_value = @content_type ? @content_type.to_s : 'application/json' + accept_type_value = @accept_type ? @accept_type.to_s : nil + content_type_value = 'application/json' if content_type_value.strip.empty? + + headers = { 'Content-Type' => content_type_value } .merge(@custom_headers) .merge(@api_key_header) .merge(gzip_headers) - headers.merge!('Accept' => @accept_type) if @accept_type + + headers.merge!('Accept' => accept_type_value) if accept_type_value && !accept_type_value.strip.empty? ssl_options = { verify: @ssl_verify, ca_file: @ca_file}.merge(@ssl_version_options) diff --git a/test/es-compat/docker-compose.yaml b/test/es-compat/docker-compose.yaml new file mode 100644 index 00000000..aab77e87 --- /dev/null +++ b/test/es-compat/docker-compose.yaml @@ -0,0 +1,40 @@ +services: + elasticsearch7: + image: docker.elastic.co/elasticsearch/elasticsearch:7.17.16 + container_name: es7 + environment: + - discovery.type=single-node + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + - xpack.security.enabled=false + ports: + - "9207:9200" + networks: + - elastic + + elasticsearch8: + image: docker.elastic.co/elasticsearch/elasticsearch:8.12.0 + container_name: es8 + environment: + - discovery.type=single-node + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + - xpack.security.enabled=false + ports: + - "9208:9200" + networks: + - elastic + + elasticsearch9: + image: docker.elastic.co/elasticsearch/elasticsearch:9.1.5 + container_name: es9 + environment: + - discovery.type=single-node + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + - xpack.security.enabled=false + ports: + - "9209:9200" + networks: + - elastic + +networks: + elastic: + driver: bridge diff --git a/test/es-compat/es-compat-test.sh b/test/es-compat/es-compat-test.sh new file mode 100755 index 00000000..03fa8fea --- /dev/null +++ b/test/es-compat/es-compat-test.sh @@ -0,0 +1,545 @@ +#!/bin/bash +# e2e_test.sh - End-to-End Test for Elasticsearch Plugin +# Tests fluent-plugin-elasticsearch against ES 7.x, 8.x, and 9.x + +set -e + +# Color output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +TOTAL_TESTS=0 +PASSED_TESTS=0 +FAILED_TESTS=0 +declare -a FAILED_TEST_NAMES +declare -a PASSED_TEST_NAMES + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TEST_DIR="${SCRIPT_DIR}/e2e_test_temp" + +find_project_root() { + local dir="$SCRIPT_DIR" + while [ "$dir" != "/" ]; do + if [ -d "$dir/lib/fluent/plugin" ]; then + echo "$dir" + return 0 + fi + dir="$(dirname "$dir")" + done + + echo "$SCRIPT_DIR" +} + +PROJECT_ROOT=$(find_project_root) +LIB_PATH="${PROJECT_ROOT}/lib" + +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[✓]${NC} $1" +} + +log_error() { + echo -e "${RED}[✗]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[!]${NC} $1" +} + +print_banner() { + echo "" + echo -e "${CYAN}╔════════════════════════════════════════════════════════════════╗${NC}" + echo -e "${CYAN}║${NC} $1" + echo -e "${CYAN}╚════════════════════════════════════════════════════════════════╝${NC}" + echo "" +} + +print_section() { + echo "" + echo -e "${BLUE}========================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}========================================${NC}" +} + +print_subsection() { + echo "" + echo -e "${YELLOW}--- $1 ---${NC}" +} + +increment_test() { + TOTAL_TESTS=$((TOTAL_TESTS + 1)) +} + +pass_test() { + PASSED_TESTS=$((PASSED_TESTS + 1)) + PASSED_TEST_NAMES+=("$1") + log_success "$1" +} + +fail_test() { + FAILED_TESTS=$((FAILED_TESTS + 1)) + FAILED_TEST_NAMES+=("$1") + log_error "$1" +} + +cleanup() { + print_section "Cleanup" + + if [ -d "$TEST_DIR" ]; then + log_info "Removing test directory: $TEST_DIR" + rm -rf "$TEST_DIR" + fi + + log_info "Stopping Docker containers..." + docker-compose down -v 2>/dev/null || true + + for port in 9207 9208 9209; do + if curl -s "http://localhost:${port}/_cat/health" > /dev/null 2>&1; then + log_info "Cleaning up test indices on port ${port}..." + curl -s -X DELETE "http://localhost:${port}/test-*,fluentd-*" > /dev/null 2>&1 || true + fi + done + + log_success "Cleanup complete" +} + +trap cleanup EXIT + +check_prerequisites() { + print_section "Checking Prerequisites" + + local missing_deps=() + + if ! command -v docker &> /dev/null; then + missing_deps+=("docker") + fi + + if ! command -v docker-compose &> /dev/null; then + missing_deps+=("docker-compose") + fi + + if ! command -v curl &> /dev/null; then + missing_deps+=("curl") + fi + + if ! command -v jq &> /dev/null; then + missing_deps+=("jq") + fi + + if ! command -v bundle &> /dev/null; then + missing_deps+=("bundle") + fi + + if [ ${#missing_deps[@]} -ne 0 ]; then + log_error "Missing required dependencies: ${missing_deps[*]}" + log_info "Please install missing dependencies and try again" + exit 1 + fi + + if [ ! -d "$LIB_PATH/fluent/plugin" ]; then + log_error "Cannot find lib/fluent/plugin directory" + log_error "Expected at: $LIB_PATH" + log_error "Please run this script from the project root or a subdirectory" + exit 1 + fi + + local es_version=$(bundle exec ruby -e "require 'elasticsearch'; puts Elasticsearch::VERSION" 2>/dev/null || echo "unknown") + log_info "Elasticsearch gem version: $es_version" + + local gem_major=$(echo "$es_version" | cut -d. -f1) + + log_success "All prerequisites satisfied" + log_info "Project root: $PROJECT_ROOT" + log_info "Lib path: $LIB_PATH" +} + +setup_test_environment() { + print_section "Setting Up Test Environment" + + mkdir -p "$TEST_DIR" + log_success "Created test directory: $TEST_DIR" +} + +start_elasticsearch() { + print_section "Starting Elasticsearch Containers" + + log_info "Starting Docker Compose..." + docker-compose up -d + + log_info "Waiting for Elasticsearch instances to be ready..." + + local ports=(9207 9208 9209) + local names=("ES 7.x" "ES 8.x" "ES 9.x") + + for i in "${!ports[@]}"; do + local port=${ports[$i]} + local name=${names[$i]} + local max_attempts=60 + local attempt=0 + + log_info "Checking ${name} on port ${port}..." + + while [ $attempt -lt $max_attempts ]; do + if curl -s "http://localhost:${port}/_cluster/health" > /dev/null 2>&1; then + local version=$(curl -s "http://localhost:${port}" | jq -r '.version.number' 2>/dev/null || echo "unknown") + log_success "${name} is ready (version: ${version})" + break + fi + attempt=$((attempt + 1)) + if [ $((attempt % 10)) -eq 0 ]; then + echo -n "." + fi + sleep 1 + done + + if [ $attempt -eq $max_attempts ]; then + log_error "${name} failed to start after ${max_attempts} seconds" + exit 1 + fi + done + + echo "" +} + +create_test_script() { + local es_version=$1 + local es_port=$2 + local test_name=$3 + local extra_config=$4 + + cat > "${TEST_DIR}/test_${test_name}.rb" << EOF +require 'bundler/setup' +require 'fluent/test' +require 'fluent/test/driver/output' + +# Add lib directory to load path +\$LOAD_PATH.unshift('${LIB_PATH}') +require 'fluent/plugin/out_elasticsearch' + +config = %[ + host localhost + port ${es_port} + logstash_format true + logstash_prefix test-${test_name} + type_name _doc + ${extra_config} +] + +driver = Fluent::Test::Driver::Output.new(Fluent::Plugin::ElasticsearchOutput).configure(config) + +begin + driver.run(default_tag: 'test') do + driver.feed(Time.now.to_i, { + "message" => "Test message for ${test_name}", + "version" => "${es_version}", + "test_name" => "${test_name}", + "timestamp" => Time.now.iso8601 + }) + end + puts "SUCCESS" + exit 0 +rescue => e + puts "FAILED: \#{e.class}: \#{e.message}" + puts e.backtrace.first(5).join("\n") if ENV['DEBUG'] + exit 1 +end +EOF +} + +run_test() { + local test_name=$1 + local es_version=$2 + local es_port=$3 + local extra_config=$4 + + increment_test + + create_test_script "$es_version" "$es_port" "$test_name" "$extra_config" + + if timeout 30 bundle exec ruby "${TEST_DIR}/test_${test_name}.rb" > "${TEST_DIR}/${test_name}.log" 2>&1; then + pass_test "${test_name}" + return 0 + else + fail_test "${test_name}" + if [ -f "${TEST_DIR}/${test_name}.log" ]; then + # Show abbreviated error + local error_msg=$(grep -E "FAILED:|Error|error" "${TEST_DIR}/${test_name}.log" | head -3) + if [ -n "$error_msg" ]; then + echo " └─ $error_msg" + fi + fi + return 1 + fi +} + +verify_data() { + local port=$1 + local index_pattern=$2 + local expected_count=$3 + local test_description=$4 + + increment_test + + sleep 2 + + local result=$(curl -s "http://localhost:${port}/${index_pattern}/_search?size=0" || echo '{}') + local actual_count=$(echo "$result" | jq -r '.hits.total.value // .hits.total // 0' 2>/dev/null || echo "0") + + if [ "$actual_count" -ge "$expected_count" ]; then + pass_test "${test_description}: Found ${actual_count} docs" + return 0 + else + fail_test "${test_description}: Found ${actual_count} docs (expected >= ${expected_count})" + return 1 + fi +} + +test_baseline() { + print_subsection "Test 1: Baseline - Default Configuration" + + run_test "es7-baseline" "7" "9207" "" + verify_data "9207" "test-es7-baseline-*" 1 "ES7 Baseline Data" + + run_test "es8-baseline" "8" "9208" "" + verify_data "9208" "test-es8-baseline-*" 1 "ES8 Baseline Data" + + run_test "es9-baseline" "9" "9209" "" + verify_data "9209" "test-es9-baseline-*" 1 "ES9 Baseline Data" +} + +test_without_logstash_format() { + print_subsection "Test 2: Without Logstash Format" + + local config="logstash_format false + index_name test-direct" + + run_test "es7-direct" "7" "9207" "$config" + verify_data "9207" "test-direct" 1 "ES7 Direct Index" + + run_test "es8-direct" "8" "9208" "$config" + verify_data "9208" "test-direct" 1 "ES8 Direct Index" + + run_test "es9-direct" "9" "9209" "$config" + verify_data "9209" "test-direct" 1 "ES9 Direct Index" +} + +test_bulk_writes() { + print_subsection "Test 3: Bulk Writes (10 documents)" + + for es_ver in 7 8 9; do + local port=$((9207 + es_ver - 7)) + local test_name="es${es_ver}-bulk" + + increment_test + + cat > "${TEST_DIR}/test_${test_name}.rb" << EOF +require 'bundler/setup' +require 'fluent/test' +require 'fluent/test/driver/output' + +\$LOAD_PATH.unshift('${LIB_PATH}') +require 'fluent/plugin/out_elasticsearch' + +config = %[ + host localhost + port ${port} + logstash_format true + logstash_prefix test-${test_name} + type_name _doc +] + +driver = Fluent::Test::Driver::Output.new(Fluent::Plugin::ElasticsearchOutput).configure(config) + +begin + driver.run(default_tag: 'test') do + 10.times do |i| + driver.feed(Time.now.to_i, { + "message" => "Bulk message \#{i}", + "index" => i, + "version" => "${es_ver}", + "test_name" => "${test_name}" + }) + end + end + puts "SUCCESS" + exit 0 +rescue => e + puts "FAILED: \#{e.class}: \#{e.message}" + exit 1 +end +EOF + + if timeout 30 bundle exec ruby "${TEST_DIR}/test_${test_name}.rb" > "${TEST_DIR}/${test_name}.log" 2>&1; then + pass_test "${test_name}" + else + fail_test "${test_name}" + fi + + verify_data "$port" "test-${test_name}-*" 10 "ES${es_ver} Bulk Data" + done +} + +show_data_summary() { + print_section "Data Summary in Elasticsearch" + + for es_ver in 7 8 9; do + local port=$((9207 + es_ver - 7)) + print_subsection "ES ${es_ver}.x (port ${port})" + + local indices=$(curl -s "http://localhost:${port}/_cat/indices/test-*?h=index,docs.count,store.size" 2>/dev/null || echo "") + + if [ -n "$indices" ]; then + echo "$indices" | while read -r line; do + echo " ${line}" + done + else + echo " No test indices found" + fi + done +} + +create_compatibility_report() { + print_section "Compatibility Report" + + local gem_version=$(bundle exec ruby -e "require 'elasticsearch'; puts Elasticsearch::VERSION" 2>/dev/null || echo "unknown") + + echo "" + echo "Gem Version: elasticsearch ${gem_version}" + echo "" + + local es7_pass=0 + local es7_fail=0 + local es8_pass=0 + local es8_fail=0 + local es9_pass=0 + local es9_fail=0 + + for test_name in "${PASSED_TEST_NAMES[@]}"; do + if [[ "$test_name" == *"es7"* ]]; then + es7_pass=$((es7_pass + 1)) + elif [[ "$test_name" == *"es8"* ]]; then + es8_pass=$((es8_pass + 1)) + elif [[ "$test_name" == *"es9"* ]]; then + es9_pass=$((es9_pass + 1)) + fi + done + + for test_name in "${FAILED_TEST_NAMES[@]}"; do + if [[ "$test_name" == *"es7"* ]]; then + es7_fail=$((es7_fail + 1)) + elif [[ "$test_name" == *"es8"* ]]; then + es8_fail=$((es8_fail + 1)) + elif [[ "$test_name" == *"es9"* ]]; then + es9_fail=$((es9_fail + 1)) + fi + done + + printf "%-15s | %-6s | %-6s | %-10s\n" "ES Version" "Passed" "Failed" "Status" + printf "%-15s-+-%-6s-+-%-6s-+-%-10s\n" "---------------" "------" "------" "----------" + + local es7_status="✓ OK" + [ $es7_fail -gt 0 ] && es7_status="✗ FAILED" + printf "%-15s | %-6s | %-6s | %-10s\n" "ES 7.x" "$es7_pass" "$es7_fail" "$es7_status" + + local es8_status="✓ OK" + [ $es8_fail -gt 0 ] && es8_status="✗ FAILED" + printf "%-15s | %-6s | %-6s | %-10s\n" "ES 8.x" "$es8_pass" "$es8_fail" "$es8_status" + + local es9_status="✓ OK" + [ $es9_fail -gt 0 ] && es9_status="✗ FAILED" + printf "%-15s | %-6s | %-6s | %-10s\n" "ES 9.x" "$es9_pass" "$es9_fail" "$es9_status" + + echo "" + + if [ $es7_fail -gt 0 ] || [ $es8_fail -gt 0 ]; then + log_warning "Some tests failed with ES 7.x/8.x" + if [[ "$gem_version" == 9.* ]]; then + log_warning "You're using elasticsearch gem 9.x which has compatibility issues with ES 7/8" + log_info "Recommendation: Use elasticsearch gem ~> 7.17 or ~> 8.x for ES 7/8 servers" + fi + fi + + if [ $es9_fail -gt 0 ]; then + log_warning "Some tests failed with ES 9.x" + if [[ "$gem_version" == 7.* ]] || [[ "$gem_version" == 8.* ]]; then + log_info "Note: ES 9.x is not released yet, testing against ES 8.x placeholder" + fi + fi +} + +print_summary() { + print_banner "Test Results Summary" + + echo -e "${CYAN}Total Tests:${NC} ${TOTAL_TESTS}" + echo -e "${GREEN}Passed:${NC} ${PASSED_TESTS}" + echo -e "${RED}Failed:${NC} ${FAILED_TESTS}" + + local pass_rate=0 + if [ $TOTAL_TESTS -gt 0 ]; then + pass_rate=$((PASSED_TESTS * 100 / TOTAL_TESTS)) + fi + + echo -e "${CYAN}Pass Rate:${NC} ${pass_rate}%" + + if [ $FAILED_TESTS -gt 0 ]; then + echo "" + echo -e "${RED}Failed Tests:${NC}" + for test_name in "${FAILED_TEST_NAMES[@]}"; do + echo -e " ${RED}✗${NC} ${test_name}" + if [ -f "${TEST_DIR}/${test_name}.log" ]; then + echo -e " └─ Log: ${TEST_DIR}/${test_name}.log" + fi + done + echo "" + echo -e "${YELLOW}To view detailed logs:${NC}" + echo " cat ${TEST_DIR}/.log" + fi + + echo "" + + create_compatibility_report + + if [ $FAILED_TESTS -eq 0 ]; then + echo -e "${GREEN}╔════════════════════════════════════╗${NC}" + echo -e "${GREEN}║ 🎉 ALL TESTS PASSED! 🎉 ║${NC}" + echo -e "${GREEN}╚════════════════════════════════════╝${NC}" + return 0 + else + echo -e "${RED}╔════════════════════════════════════╗${NC}" + echo -e "${RED}║ ❌ SOME TESTS FAILED ║${NC}" + echo -e "${RED}╚════════════════════════════════════╝${NC}" + return 1 + fi +} + +main() { + print_banner "Elasticsearch Plugin E2E Test Suite (Vanilla)" + + check_prerequisites + setup_test_environment + start_elasticsearch + + print_section "Running Tests" + + test_baseline + test_without_logstash_format + test_bulk_writes + + show_data_summary + print_summary + + if [ $FAILED_TESTS -gt 0 ]; then + exit 1 + else + exit 0 + fi +} + +main