blob: 0f2582a2fc967677baccf0d674128a8504ac39aa [file] [log] [blame]
#!/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.
set -eo pipefail
shopt -s nullglob
# Constant Definition
readonly DORIS_HOME="/opt/apache-doris"
readonly MAX_RETRY_TIMES=60
readonly RETRY_INTERVAL=1
readonly MYSQL_PORT=9030
readonly FE_CONFIG_FILE="${DORIS_HOME}/fe/conf/fe.conf"
# Log Function
log_message() {
local level="$1"
shift
local message="$*"
if [ "$#" -eq 0 ]; then
message="$(cat)"
fi
local timestamp="$(date -Iseconds)"
printf '%s [%s] [Entrypoint]: %s\n' "${timestamp}" "${level}" "${message}"
}
log_info() {
log_message "INFO" "$@"
}
log_warn() {
log_message "WARN" "$@" >&2
}
log_error() {
log_message "ERROR" "$@" >&2
exit 1
}
# Check if the parameter is empty
check_required_param() {
local param_name="$1"
local param_value="$2"
if [ -z "$param_value" ]; then
log_error "${param_name} is required but not set"
fi
}
# Check whether it is a source file call
is_sourced() {
[ "${#FUNCNAME[@]}" -ge 2 ] &&
[ "${FUNCNAME[0]}" = 'is_sourced' ] &&
[ "${FUNCNAME[1]}" = 'source' ]
}
# Initialize environment variables
init_environment() {
declare -g database_exists
if [ -d "${DORIS_HOME}/fe/doris-meta/image" ]; then
database_exists='true'
fi
}
# Verify IP address format
validate_ip_address() {
local ip="$1"
local ipv4_regex="^[1-2]?[0-9]?[0-9](\.[1-2]?[0-9]?[0-9]){3}$"
local ipv6_regex="^([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)$"
if [[ $ip =~ $ipv4_regex ]] || [[ $ip =~ $ipv6_regex ]]; then
return 0
fi
return 1
}
# Verify port number
validate_port() {
local port="$1"
if [[ $port =~ ^[1-6]?[0-9]{1,4}$ ]] && [ "$port" -le 65535 ]; then
return 0
fi
return 1
}
# Verify the necessary environment variables
validate_environment() {
declare -g run_mode
# RECOVERY Mode Verification
if [ -n "$RECOVERY" ]; then
if [[ $RECOVERY =~ ^([tT][rR][uU][eE]|1)$ ]]; then
run_mode="RECOVERY"
log_info "Running in Recovery mode"
return
fi
fi
# K8S mode verification
if [ -n "$BUILD_TYPE" ]; then
if [[ $BUILD_TYPE =~ ^([kK]8[sS])$ ]]; then
run_mode="K8S"
log_info "Running in K8S mode"
return
fi
log_error "Invalid BUILD_TYPE. Expected: k8s"
fi
# Election Mode Verification
if [[ -n "$FE_SERVERS" && -n "$FE_ID" ]]; then
validate_election_mode
return
fi
# Specifying a schema for validation
if [[ -n "$FE_MASTER_IP" && -n "$FE_MASTER_PORT" &&
-n "$FE_CURRENT_IP" && -n "$FE_CURRENT_PORT" ]]; then
validate_assign_mode
return
fi
log_error "Missing required parameters. Please check documentation."
}
# Verify election mode configuration
validate_election_mode() {
run_mode="ELECTION"
# Verify FE_SERVERS format
local fe_servers_regex="^.+:[1-2]{0,1}[0-9]{0,1}[0-9]{1}(\.[1-2]{0,1}[0-9]{0,1}[0-9]{1}){3}:[1-6]{0,1}[0-9]{1,4}(,.+:[1-2]{0,1}[0-9]{0,1}[0-9]{1}(\.[1-2]{0,1}[0-9]{0,1}[0-9]{1}){3}:[1-6]{0,1}[0-9]{1,4})*$"
if ! [[ $FE_SERVERS =~ $fe_servers_regex ]]; then
log_error "Invalid FE_SERVERS format. Expected: name:ip:port[,name:ip:port]..."
fi
# Verify FE_ID
if ! [[ $FE_ID =~ ^[1-9]{1}$ ]]; then
log_error "Invalid FE_ID. Must be a single digit between 1-9"
fi
log_info "Running in Election mode"
}
# Verify the specified mode configuration
validate_assign_mode() {
run_mode="ASSIGN"
# Verify IP Address
if ! validate_ip_address "$FE_MASTER_IP"; then
log_error "Invalid FE_MASTER_IP format"
fi
if ! validate_ip_address "$FE_CURRENT_IP"; then
log_error "Invalid FE_CURRENT_IP format"
fi
# Verify port
if ! validate_port "$FE_MASTER_PORT"; then
log_error "Invalid FE_MASTER_PORT"
fi
if ! validate_port "$FE_CURRENT_PORT"; then
log_error "Invalid FE_CURRENT_PORT"
fi
log_info "Running in Assign mode"
}
# Parse a colon-delimited string
parse_colon_separated() {
local input="$1"
local -n arr=$2 # 使用nameref来存储结果
local IFS=':'
read -r -a arr <<< "$input"
}
# Parsing a comma-delimited string
parse_comma_separated() {
local input="$1"
local -n arr=$2 # 使用nameref来存储结果
local IFS=','
read -r -a arr <<< "$input"
}
# Configuring the election mode
setup_election_mode() {
local fe_server_array
parse_comma_separated "$FE_SERVERS" fe_server_array
# Parsing master node information
local master_info
parse_colon_separated "${fe_server_array[0]}" master_info
master_fe_ip="${master_info[1]}"
master_fe_port="${master_info[2]}"
# Parse the current node information
local found=false
local node_info
for node in "${fe_server_array[@]}"; do
parse_colon_separated "$node" node_info
if [ "${node_info[0]}" = "fe${FE_ID}" ]; then
current_fe_ip="${node_info[1]}"
current_fe_port="${node_info[2]}"
found=true
break
fi
done
if [ "$found" = "false" ]; then
log_error "Could not find configuration for fe${FE_ID} in FE_SERVERS"
fi
is_master_fe=$([[ "$FE_ID" == "1" ]] && echo "true" || echo "false")
}
# Set up a preferred network
setup_priority_networks() {
local ip_parts
parse_colon_separated "$1" ip_parts
priority_networks="${ip_parts[0]}.${ip_parts[1]}.${ip_parts[2]}.0/24"
}
# Configure the specified mode
setup_assign_mode() {
master_fe_ip="$FE_MASTER_IP"
master_fe_port="$FE_MASTER_PORT"
current_fe_ip="$FE_CURRENT_IP"
current_fe_port="$FE_CURRENT_PORT"
is_master_fe=$([[ "$master_fe_ip" == "$current_fe_ip" ]] && echo "true" || echo "false")
}
# Add RECOVERY mode configuration function
setup_recovery_mode() {
# In recovery mode, we need to read the configuration from the metadata
local meta_dir="${DORIS_HOME}/fe/doris-meta"
if [ ! -d "$meta_dir" ] || [ -z "$(ls -A "$meta_dir")" ]; then
log_error "Cannot start in recovery mode: meta directory is empty or does not exist"
fi
log_info "Starting in recovery mode, using existing meta directory"
is_master_fe="true" # In recovery mode, it starts as the master node by default
}
# Configuring FE Nodes
setup_fe_node() {
declare -g master_fe_ip master_fe_port current_fe_ip current_fe_port
declare -g priority_networks is_master_fe
case $run_mode in
"ELECTION")
setup_election_mode
;;
"ASSIGN")
setup_assign_mode
;;
"RECOVERY")
setup_recovery_mode
;;
esac
# Set priority network (if not in recovery mode)
if [ "$run_mode" != "RECOVERY" ]; then
local ip_parts
IFS='.' read -r -a ip_parts <<< "$current_fe_ip"
priority_networks="${ip_parts[0]}.${ip_parts[1]}.${ip_parts[2]}.0/24"
fi
# Print key configuration information
log_info "==== FE Node Configuration ===="
log_info "Run Mode: ${run_mode}"
if [ "$run_mode" = "RECOVERY" ]; then
log_info "Recovery Mode: true"
log_info "Meta Directory: ${DORIS_HOME}/fe/doris-meta"
else
log_info "Is Master: ${is_master_fe}"
log_info "Master FE IP: ${master_fe_ip}"
log_info "Master FE Port: ${master_fe_port}"
log_info "Current FE IP: ${current_fe_ip}"
log_info "Current FE Port: ${current_fe_port}"
log_info "Priority Networks: ${priority_networks}"
if [ "$run_mode" = "ELECTION" ]; then
log_info "FE ID: ${FE_ID}"
log_info "FE Servers: ${FE_SERVERS}"
fi
fi
log_info "=========================="
}
# Start FE node
start_fe() {
if [ "$run_mode" = "RECOVERY" ]; then
log_info "Starting FE node in recovery mode"
${DORIS_HOME}/fe/bin/start_fe.sh --metadata_failure_recovery
return
fi
if [ "$is_master_fe" = "true" ]; then
log_info "Starting master FE node"
${DORIS_HOME}/fe/bin/start_fe.sh --console
else
log_info "Starting follower FE node"
${DORIS_HOME}/fe/bin/start_fe.sh --helper "${master_fe_ip}:${master_fe_port}" --console
fi
}
# Check whether the FE node is registered
check_fe_registered() {
local retry_count=0
while [ $retry_count -lt $MAX_RETRY_TIMES ]; do
# Query the existing FE node list
local query_result
query_result=$(mysql -uroot -P"${MYSQL_PORT}" -h"${master_fe_ip}" \
-N -e "SHOW FRONTENDS" 2>/dev/null | grep -w "${current_fe_ip}" | grep -w "${current_fe_port}" || true)
if [ -n "$query_result" ]; then
log_info "FE node ${current_fe_ip}:${current_fe_port} is already registered"
return 0
fi
retry_count=$((retry_count + 1))
if [ $((retry_count % 20)) -eq 1 ]; then
log_info "Waiting for master FE to be ready... ($retry_count/$MAX_RETRY_TIMES)"
fi
sleep "$RETRY_INTERVAL"
done
return 1
}
# Check the metadata directory
check_meta_dir() {
local meta_dir="${DORIS_HOME}/fe/doris-meta"
if [ -d "$meta_dir/image" ] && [ -n "$(ls -A "$meta_dir/image")" ]; then
log_info "Meta directory already exists and not empty"
return 0
fi
return 1
}
# Register FE Node
register_fe() {
if [ "$is_master_fe" = "true" ]; then
log_info "Master FE node does not need registration"
return
fi
# First check if the node is registered
if check_fe_registered; then
return
fi
local retry_count=0
while [ $retry_count -lt $MAX_RETRY_TIMES ]; do
if mysql -uroot -P"${MYSQL_PORT}" -h"${master_fe_ip}" \
-e "ALTER SYSTEM ADD FOLLOWER '${current_fe_ip}:${current_fe_port}'" 2>/dev/null; then
log_info "Successfully registered FE node"
return
fi
retry_count=$((retry_count + 1))
if [ $((retry_count % 20)) -eq 1 ]; then
log_warn "Failed to register FE node, retrying... ($retry_count/$MAX_RETRY_TIMES)"
fi
sleep "$RETRY_INTERVAL"
done
log_error "Failed to register FE node after ${MAX_RETRY_TIMES} attempts"
}
# Cleanup Function
cleanup() {
log_info "Stopping FE node"
${DORIS_HOME}/fe/bin/stop_fe.sh
}
# Main Function
main() {
validate_environment
trap cleanup SIGTERM SIGINT
if [ "$run_mode" = "K8S" ]; then
${DORIS_HOME}/fe/bin/start_fe.sh --console &
wait $!
elif [ "$run_mode" = "RECOVERY" ]; then
setup_fe_node
start_fe &
wait $!
else
init_environment
setup_fe_node
# Check the metadata directory
if check_meta_dir; then
log_info "Meta directory exists, starting FE directly"
start_fe &
wait $!
return
fi
# The metadata directory does not exist and needs to be initialized and registered.
log_info "Initializing meta directory"
if [ -z "$database_exists" ]; then
echo "priority_networks = ${priority_networks}" >> "$FE_CONFIG_FILE"
fi
register_fe
start_fe &
wait $!
fi
}
if ! is_sourced; then
main "$@"
fi