blob: bfb2c6a3af7ec5c9f14763ba3150e75e8854fd22 [file]
#!/bin/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.
#
################################################################################
##############################################################################
# Elasticsearch Reindex Script
# This script reindexes data from Elasticsearch 7 to Elasticsearch 9
# It handles both regular indices and rollover indices with their aliases
##############################################################################
# Ensure we're running with bash
if [ -z "$BASH_VERSION" ]; then
echo "Error: This script requires bash. Please run with: bash $0"
exit 1
fi
set -e # Exit on error
##############################################################################
# CONFIGURATION
##############################################################################
# Environment prefix (configurable for multiple environments)
INDEX_PREFIX="${INDEX_PREFIX:-context-}"
# Elasticsearch source (ES7) configuration
ES7_HOST="${ES7_HOST:-http://localhost:9200}"
ES7_HOST_FROM_ES9="${ES7_HOST_FROM_ES9:-${ES7_HOST}}"
ES7_USER="${ES7_USER:-elastic}"
ES7_PASSWORD="${ES7_PASSWORD:-password}"
# Elasticsearch destination (ES9) configuration
ES9_HOST="${ES9_HOST:-http://localhost:9201}"
ES9_USER="${ES9_USER:-elastic}"
ES9_PASSWORD="${ES9_PASSWORD:-password}"
# Batch size for reindexing
BATCH_SIZE="${BATCH_SIZE:-1000}"
##############################################################################
# INDICES CONFIGURATION
##############################################################################
# Regular indices (no rollover)
REGULAR_INDICES=(
"profile"
"systemitems"
"personasession"
"profilealias"
"geonameentry"
)
# Rollover indices patterns
# Format: "pattern:alias_name"
# The script will automatically discover all matching indices
ROLLOVER_PATTERNS=(
"session-*:session"
"event-*:event"
)
##############################################################################
# FUNCTIONS
##############################################################################
# Function to log messages with timestamp
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1"
}
# Function to log errors
log_error() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1" >&2
}
# Function to get index stats
get_index_stats() {
local host=$1
local user=$2
local password=$3
local index=$4
response=$(curl -s \
-u "${user}:${password}" \
"${host}/${index}/_stats")
if [ $? -eq 0 ]; then
echo "$response"
return 0
else
return 1
fi
}
# Function to extract key stats from index stats response
extract_index_stats() {
local stats_json=$1
local index=$2
local doc_count=$(echo "$stats_json" | jq -r ".indices[\"${index}\"].primaries.docs.count // 0")
local doc_deleted=$(echo "$stats_json" | jq -r ".indices[\"${index}\"].primaries.docs.deleted // 0")
local store_size=$(echo "$stats_json" | jq -r ".indices[\"${index}\"].primaries.store.size_in_bytes // 0")
local store_size_mb=$((store_size / 1024 / 1024))
echo "${doc_count}|${doc_deleted}|${store_size_mb}"
}
# Temporary files to store source and destination stats
STATS_DIR=$(mktemp -d)
SOURCE_STATS_FILE="${STATS_DIR}/source_stats.txt"
DEST_STATS_FILE="${STATS_DIR}/dest_stats.txt"
# Cleanup function
cleanup() {
rm -rf "$STATS_DIR"
}
# Register cleanup on exit
trap cleanup EXIT
# Function to discover indices matching a pattern
discover_indices() {
local host=$1
local user=$2
local password=$3
local pattern=$4
response=$(curl -s \
-u "${user}:${password}" \
"${host}/_cat/indices/${pattern}?h=index&format=json")
# Extract index names
echo "$response" | jq -r '.[].index' 2>/dev/null || echo ""
}
# Function to check if index exists
check_index_exists() {
local host=$1
local user=$2
local password=$3
local index=$4
response=$(curl -s -o /dev/null -w "%{http_code}" \
-u "${user}:${password}" \
"${host}/${index}")
if [ "$response" == "200" ]; then
return 0
else
return 1
fi
}
# Function to get index settings
get_index_settings() {
local host=$1
local user=$2
local password=$3
local index=$4
curl -s -u "${user}:${password}" \
"${host}/${index}/_settings" | jq .
}
# Function to get index mappings
get_index_mappings() {
local host=$1
local user=$2
local password=$3
local index=$4
curl -s -u "${user}:${password}" \
"${host}/${index}/_mapping" | jq .
}
# Function to get index aliases
get_index_aliases() {
local host=$1
local user=$2
local password=$3
local index=$4
curl -s -u "${user}:${password}" \
"${host}/${index}/_alias" | jq .
}
# Function to get ILM policy
get_ilm_policy() {
local host=$1
local user=$2
local password=$3
local policy_name=$4
response=$(curl -s -w "\n%{http_code}" \
-u "${user}:${password}" \
"${host}/_ilm/policy/${policy_name}")
http_code=$(echo "$response" | tail -n 1)
response_body=$(echo "$response" | sed '$d')
if [ "$http_code" == "200" ]; then
echo "$response_body" | jq -r --arg policy "$policy_name" '.[$policy].policy'
return 0
else
return 1
fi
}
# Function to create ILM policy
create_ilm_policy() {
local policy_name=$1
local policy_body=$2
log "Creating ILM policy ${policy_name} on ES9..."
response=$(curl -s -w "\n%{http_code}" \
-u "${ES9_USER}:${ES9_PASSWORD}" \
-X PUT "${ES9_HOST}/_ilm/policy/${policy_name}" \
-H 'Content-Type: application/json' \
-d "{\"policy\": ${policy_body}}")
http_code=$(echo "$response" | tail -n 1)
response_body=$(echo "$response" | sed '$d')
if [ "$http_code" == "200" ]; then
log "ILM policy ${policy_name} created successfully"
return 0
else
log_error "Failed to create ILM policy ${policy_name}: ${response_body}"
return 1
fi
}
# Function to check if ILM policy exists
check_ilm_policy_exists() {
local host=$1
local user=$2
local password=$3
local policy_name=$4
response=$(curl -s -o /dev/null -w "%{http_code}" \
-u "${user}:${password}" \
"${host}/_ilm/policy/${policy_name}")
if [ "$response" == "200" ]; then
return 0
else
return 1
fi
}
# Function to get ILM policy name from index settings
get_index_ilm_policy() {
local host=$1
local user=$2
local password=$3
local index=$4
settings=$(get_index_settings "$host" "$user" "$password" "$index")
policy_name=$(echo "$settings" | jq -r ".[\"${index}\"].settings.index.lifecycle.name // \"\"")
if [ -n "$policy_name" ] && [ "$policy_name" != "null" ] && [ "$policy_name" != "" ]; then
echo "$policy_name"
return 0
else
return 1
fi
}
# Function to create index on destination
create_index() {
local index=$1
local settings=$2
local mappings=$3
local ilm_policy=${4:-""}
log "Creating index ${index} on ES9..."
# If ILM policy is provided, add it to settings
if [ -n "$ilm_policy" ] && [ "$ilm_policy" != "null" ]; then
settings=$(echo "$settings" | jq --arg policy "$ilm_policy" --arg rollover_alias "$(echo $index| sed -r "s/(.*)-[0-9]+/\1/")" '. + {"lifecycle": {"name": $policy,"rollover_alias": $rollover_alias}}')
log "Index will be associated with ILM policy: ${ilm_policy}"
fi
# Prepare the request body
local body=$(jq -n \
--argjson settings "$settings" \
--argjson mappings "$mappings" \
'{settings: $settings, mappings: $mappings}')
response=$(curl -s -w "\n%{http_code}" \
-u "${ES9_USER}:${ES9_PASSWORD}" \
-X PUT "${ES9_HOST}/${index}" \
-H 'Content-Type: application/json' \
-d "$body")
http_code=$(echo "$response" | tail -n 1)
response_body=$(echo "$response" | sed '$d')
if [ "$http_code" == "200" ] || [ "$http_code" == "201" ]; then
log "Index ${index} created successfully"
return 0
else
log_error "Failed to create index ${index}: ${response_body}"
return 1
fi
}
# Function to create alias
create_alias() {
local index=$1
local alias=$2
local is_write_index=${3:-false}
log "Creating alias ${alias} for index ${index} (is_write_index=${is_write_index})..."
local alias_config="{\"index\": \"${index}\", \"alias\": \"${alias}\""
if [ "$is_write_index" == "true" ]; then
alias_config="${alias_config}, \"is_write_index\": true"
fi
alias_config="${alias_config}}"
response=$(curl -s -w "\n%{http_code}" \
-u "${ES9_USER}:${ES9_PASSWORD}" \
-X POST "${ES9_HOST}/_aliases" \
-H 'Content-Type: application/json' \
-d "{
\"actions\": [
{
\"add\": ${alias_config}
}
]
}")
http_code=$(echo "$response" | tail -n 1)
if [ "$http_code" == "200" ]; then
log "Alias ${alias} created successfully"
return 0
else
log_error "Failed to create alias ${alias}"
return 1
fi
}
# Function to check task status
check_task_status() {
local task_id=$1
response=$(curl -s -w "\n%{http_code}" \
-u "${ES9_USER}:${ES9_PASSWORD}" \
"${ES9_HOST}/_tasks/${task_id}")
http_code=$(echo "$response" | tail -n 1)
response_body=$(echo "$response" | sed '$d')
if [ "$http_code" == "200" ]; then
echo "$response_body" | tr -d '\000-\037'
return 0
else
log_error "Failed to get task status: ${response_body}"
return 1
fi
}
# Function to wait for task completion
wait_for_task() {
local task_id=$1
local check_interval=5 # Check every 5 seconds
log "Waiting for task ${task_id} to complete..."
while true; do
sleep $check_interval
task_status=$(check_task_status "$task_id")
if [ $? -ne 0 ]; then
log_error "Failed to check task status"
return 1
fi
completed=$(echo "$task_status" | jq -r '.completed')
if [ "$completed" == "true" ]; then
log "Task completed successfully"
# Get task response
response_data=$(echo "$task_status" | jq -r '.response')
# Parse and display reindex results
total=$(echo "$response_data" | jq -r '.total // 0')
created=$(echo "$response_data" | jq -r '.created // 0')
updated=$(echo "$response_data" | jq -r '.updated // 0')
failures=$(echo "$response_data" | jq -r '.failures | length')
log "Reindex results: Total=${total}, Created=${created}, Updated=${updated}, Failures=${failures}"
if [ "$failures" -gt 0 ]; then
log_error "Some documents failed to reindex:"
echo "$response_data" | jq '.failures'
fi
return 0
fi
# Display progress
task=$(echo "$task_status" | jq -r '.task')
status=$(echo "$task" | jq -r '.status')
total=$(echo "$status" | jq -r '.total // 0')
created=$(echo "$status" | jq -r '.created // 0')
updated=$(echo "$status" | jq -r '.updated // 0')
log "Progress: ${created}/${total} documents created, ${updated} updated"
done
}
# Function to reindex data
reindex_data() {
local source_index=$1
local dest_index=$2
log "Starting reindex from ${source_index} to ${dest_index}..."
response=$(curl -s -w "\n%{http_code}" \
-u "${ES9_USER}:${ES9_PASSWORD}" \
-X POST "${ES9_HOST}/_reindex?wait_for_completion=false" \
-H 'Content-Type: application/json' \
-d "{
\"source\": {
\"remote\": {
\"host\": \"${ES7_HOST_FROM_ES9}\",
\"username\": \"${ES7_USER}\",
\"password\": \"${ES7_PASSWORD}\"
},
\"index\": \"${source_index}\",
\"size\": ${BATCH_SIZE}
},
\"dest\": {
\"index\": \"${dest_index}\"
}
}")
http_code=$(echo "$response" | tail -n 1)
response_body=$(echo "$response" | sed '$d')
if [ "$http_code" == "200" ]; then
# Get task ID
task_id=$(echo "$response_body" | jq -r '.task')
log "Reindex task created with ID: ${task_id}"
# Wait for task completion
wait_for_task "$task_id"
return $?
else
log_error "Failed to start reindex: ${response_body}"
return 1
fi
}
# Function to process regular index
process_regular_index() {
local index_name=$1
local full_index_name="${INDEX_PREFIX}${index_name}"
log "=========================================="
log "Processing regular index: ${full_index_name}"
log "=========================================="
# Check if source index exists
if ! check_index_exists "$ES7_HOST" "$ES7_USER" "$ES7_PASSWORD" "$full_index_name"; then
log_error "Source index ${full_index_name} does not exist on ES7. Skipping..."
return 1
fi
# Collect source stats
collect_index_stats "$ES7_HOST" "$ES7_USER" "$ES7_PASSWORD" "$full_index_name" "SOURCE"
# Get settings and mappings from source
log "Retrieving settings and mappings from source..."
settings=$(get_index_settings "$ES7_HOST" "$ES7_USER" "$ES7_PASSWORD" "$full_index_name" | \
jq ".[\"${full_index_name}\"].settings.index |
del(.creation_date, .uuid, .version, .provided_name, .lifecycle)")
mappings=$(get_index_mappings "$ES7_HOST" "$ES7_USER" "$ES7_PASSWORD" "$full_index_name" | \
jq ".[\"${full_index_name}\"].mappings")
# Check if index has an ILM policy
ilm_policy_name=""
if ilm_policy_name=$(get_index_ilm_policy "$ES7_HOST" "$ES7_USER" "$ES7_PASSWORD" "$full_index_name"); then
log "Index has ILM policy: ${ilm_policy_name}"
# Check if policy exists on ES9, if not, migrate it
if ! check_ilm_policy_exists "$ES9_HOST" "$ES9_USER" "$ES9_PASSWORD" "$ilm_policy_name"; then
log "ILM policy ${ilm_policy_name} does not exist on ES9, migrating it..."
if ilm_policy_body=$(get_ilm_policy "$ES7_HOST" "$ES7_USER" "$ES7_PASSWORD" "$ilm_policy_name"); then
create_ilm_policy "$ilm_policy_name" "$ilm_policy_body"
else
log_error "Failed to retrieve ILM policy ${ilm_policy_name} from ES7"
ilm_policy_name=""
fi
else
log "ILM policy ${ilm_policy_name} already exists on ES9"
fi
else
log "Index does not have an ILM policy"
fi
# Create index on destination if it doesn't exist
if ! check_index_exists "$ES9_HOST" "$ES9_USER" "$ES9_PASSWORD" "$full_index_name"; then
create_index "$full_index_name" "$settings" "$mappings" "$ilm_policy_name"
else
log "Index ${full_index_name} already exists on ES9. Skipping creation..."
fi
# Reindex data
reindex_data "$full_index_name" "$full_index_name"
}
# Function to collect stats for an index
collect_index_stats() {
local host=$1
local user=$2
local password=$3
local index=$4
local array_name=$5
if check_index_exists "$host" "$user" "$password" "$index"; then
local stats_json=$(get_index_stats "$host" "$user" "$password" "$index")
local stats=$(extract_index_stats "$stats_json" "$index")
if [ "$array_name" == "SOURCE" ]; then
echo "${index}:${stats}" >> "$SOURCE_STATS_FILE"
else
echo "${index}:${stats}" >> "$DEST_STATS_FILE"
fi
fi
}
# Function to display comparison report
display_comparison_report() {
log "=========================================="
log "MIGRATION COMPARISON REPORT"
log "=========================================="
printf "%-40s | %15s | %15s | %15s | %10s\n" "Index" "Source Docs" "Dest Docs" "Difference" "Status"
printf "%-40s-+-%15s-+-%15s-+-%15s-+-%10s\n" "----------------------------------------" "---------------" "---------------" "---------------" "----------"
local total_issues=0
# Read source stats file
if [ -f "$SOURCE_STATS_FILE" ]; then
while IFS=':' read -r index source_stats; do
# Find corresponding dest stats
dest_stats="0|0|0"
if [ -f "$DEST_STATS_FILE" ]; then
dest_line=$(grep "^${index}:" "$DEST_STATS_FILE" 2>/dev/null || echo "")
if [ -n "$dest_line" ]; then
dest_stats="${dest_line#*:}"
fi
fi
IFS='|' read -r source_docs source_deleted source_size <<< "$source_stats"
IFS='|' read -r dest_docs dest_deleted dest_size <<< "$dest_stats"
local diff=$((dest_docs - source_docs))
local status="✓ OK"
if [ "$dest_docs" -ne "$source_docs" ]; then
status="✗ MISMATCH"
((total_issues++))
fi
printf "%-40s | %15s | %15s | %+15s | %10s\n" "$index" "$source_docs" "$dest_docs" "$diff" "$status"
done < "$SOURCE_STATS_FILE"
fi
log "=========================================="
if [ $total_issues -eq 0 ]; then
log "✓ All indices migrated successfully!"
else
log_error "✗ ${total_issues} index(es) have mismatched document counts"
fi
log "=========================================="
}
# Function to process rollover index
process_rollover_index() {
local index_name=$1
local alias_name=$2
local full_index_name="${INDEX_PREFIX}${index_name}"
local full_alias_name="${INDEX_PREFIX}${alias_name}"
log "=========================================="
log "Processing rollover index: ${full_index_name}"
log "Alias: ${full_alias_name}"
log "=========================================="
# Check if source index exists
if ! check_index_exists "$ES7_HOST" "$ES7_USER" "$ES7_PASSWORD" "$full_index_name"; then
log_error "Source index ${full_index_name} does not exist on ES7. Skipping..."
return 1
fi
# Collect source stats
collect_index_stats "$ES7_HOST" "$ES7_USER" "$ES7_PASSWORD" "$full_index_name" "SOURCE"
# Get settings, mappings and aliases from source
log "Retrieving settings, mappings and aliases from source..."
settings=$(get_index_settings "$ES7_HOST" "$ES7_USER" "$ES7_PASSWORD" "$full_index_name" | \
jq ".[\"${full_index_name}\"].settings.index |
del(.creation_date, .uuid, .version, .provided_name, .lifecycle)")
mappings=$(get_index_mappings "$ES7_HOST" "$ES7_USER" "$ES7_PASSWORD" "$full_index_name" | \
jq ".[\"${full_index_name}\"].mappings")
# Get alias information from source to check if it's a write index
aliases_info=$(get_index_aliases "$ES7_HOST" "$ES7_USER" "$ES7_PASSWORD" "$full_index_name")
is_write_index=$(echo "$aliases_info" | jq -r ".[\"${full_index_name}\"].aliases[\"${full_alias_name}\"].is_write_index // false")
log "Index ${full_index_name} is_write_index: ${is_write_index}"
# Check if index has an ILM policy
ilm_policy_name=""
if ilm_policy_name=$(get_index_ilm_policy "$ES7_HOST" "$ES7_USER" "$ES7_PASSWORD" "$full_index_name"); then
log "Index has ILM policy: ${ilm_policy_name}"
# Check if policy exists on ES9, if not, migrate it
if ! check_ilm_policy_exists "$ES9_HOST" "$ES9_USER" "$ES9_PASSWORD" "$ilm_policy_name"; then
log "ILM policy ${ilm_policy_name} does not exist on ES9, migrating it..."
if ilm_policy_body=$(get_ilm_policy "$ES7_HOST" "$ES7_USER" "$ES7_PASSWORD" "$ilm_policy_name"); then
create_ilm_policy "$ilm_policy_name" "$ilm_policy_body"
else
log_error "Failed to retrieve ILM policy ${ilm_policy_name} from ES7"
ilm_policy_name=""
fi
else
log "ILM policy ${ilm_policy_name} already exists on ES9"
fi
else
log "Index does not have an ILM policy"
fi
# Create index on destination if it doesn't exist
if ! check_index_exists "$ES9_HOST" "$ES9_USER" "$ES9_PASSWORD" "$full_index_name"; then
create_index "$full_index_name" "$settings" "$mappings" "$ilm_policy_name"
else
log "Index ${full_index_name} already exists on ES9. Skipping creation..."
fi
# Create alias with is_write_index parameter
create_alias "$full_index_name" "$full_alias_name" "$is_write_index"
# Reindex data
reindex_data "$full_index_name" "$full_index_name"
}
##############################################################################
# MAIN EXECUTION
##############################################################################
main() {
log "=========================================="
log "Elasticsearch Reindex Script Started"
log "=========================================="
log "Configuration:"
log " Index Prefix: ${INDEX_PREFIX}"
log " ES7 Host: ${ES7_HOST}"
log " ES7 Host From ES9: ${ES7_HOST_FROM_ES9}"
log " ES9 Host: ${ES9_HOST}"
log " Batch Size: ${BATCH_SIZE}"
log "=========================================="
# Check if jq is installed
if ! command -v jq &> /dev/null; then
log_error "jq is required but not installed. Please install jq."
exit 1
fi
# Process regular indices
log "Processing regular indices..."
for index_name in "${REGULAR_INDICES[@]}"; do
process_regular_index "$index_name"
echo ""
done
# Process rollover indices
log "Processing rollover indices..."
for pattern_config in "${ROLLOVER_PATTERNS[@]}"; do
IFS=':' read -r pattern alias_name <<< "$pattern_config"
# Add prefix to pattern
full_pattern="${INDEX_PREFIX}${pattern}"
log "Discovering indices matching pattern: ${full_pattern}"
discovered_indices=$(discover_indices "$ES7_HOST" "$ES7_USER" "$ES7_PASSWORD" "$full_pattern")
if [ -z "$discovered_indices" ]; then
log "No indices found matching pattern: ${full_pattern}"
continue
fi
# Process each discovered index
while IFS= read -r full_index_name; do
# Remove prefix to get the index name without prefix
index_name="${full_index_name#$INDEX_PREFIX}"
process_rollover_index "$index_name" "$alias_name"
echo ""
done <<< "$discovered_indices"
done
log "=========================================="
log "Collecting destination statistics..."
log "=========================================="
# Collect destination stats for all migrated indices
if [ -f "$SOURCE_STATS_FILE" ]; then
while IFS=':' read -r index stats; do
collect_index_stats "$ES9_HOST" "$ES9_USER" "$ES9_PASSWORD" "$index" "DEST"
done < "$SOURCE_STATS_FILE"
fi
# Display comparison report
display_comparison_report
log "=========================================="
log "Elasticsearch Reindex Script Completed"
log "=========================================="
}
# Run main function
main