diff --git a/addons/mysql/scripts-ut-spec/init_mysql_instance_for_orc_spec.sh b/addons/mysql/scripts-ut-spec/init_mysql_instance_for_orc_spec.sh new file mode 100644 index 000000000..0c2cd7170 --- /dev/null +++ b/addons/mysql/scripts-ut-spec/init_mysql_instance_for_orc_spec.sh @@ -0,0 +1,149 @@ +# shellcheck shell=bash +# shellcheck disable=SC2034 + +# validate shell version +if ! validate_shell_type_and_version "bash" 4 &>/dev/null; then + echo "init_mysql_instance_for_orc_spec.sh skip cases because dependency bash version 4 or higher is not installed." + exit 0 +fi + +source ./utils.sh + +# Generate common library +common_library_file="./common.sh" +generate_common_library $common_library_file + +Describe "MySQL Instance Initialization Tests" + Include $common_library_file + Include ../scripts/init-mysql-instance-for-orc.sh + + init() { + ut_mode="true" + } + BeforeAll "init" + + cleanup() { + rm -f $common_library_file + } + AfterAll 'cleanup' + + Describe "validate_env_vars()" + Context "when required environment variables are not set" + It "exits with error when MYSQL_ROOT_USER is not set" + unset MYSQL_ROOT_USER + export MYSQL_ROOT_PASSWORD="password" + export ORC_TOPOLOGY_USER="orc" + export ORC_TOPOLOGY_PASSWORD="password" + + When run validate_env_vars + The status should be failure + The stderr should include "Required environment variables MYSQL_ROOT_USER or MYSQL_ROOT_PASSWORD not set" + End + End + End + + Describe "create_orc_user()" + Context "when mysql command succeeds" + mysql() { + return 0 + } + + It "creates orchestrator user successfully" + When call create_orc_user + The status should be success + The output should include "Created orchestrator user successfully" + End + End + + Context "when mysql command fails" + mysql() { + return 1 + } + + It "exits with error when mysql command fails" + When run create_orc_user + The status should be failure + The stderr should include "Failed to create orchestrator user" + End + End + End + + Describe "wait_for_mysql()" + Context "when mysql becomes available" + mysqladmin() { + return 0 + } + + It "succeeds when mysql is available" + When call wait_for_mysql + The status should be success + The output should include "MySQL is now available" + End + End + + Context "when mysql timeout occurs" + mysqladmin() { + return 1 + } + + It "exits with error on timeout" + When run wait_for_mysql + The status should be failure + The stderr should include "Timeout waiting for MySQL to be available" + End + End + End + + Describe "get_master_from_orc()" + Context "when orchestrator returns valid topology" + orchestrator-client() { + echo "[ok,ok,5.7.21,rw,mod,master,GTID,GTIDMOD] master-1:3306" + } + + It "parses master info successfully" + When call get_master_from_orc + The status should be success + The variable master_from_orc should eq "master-1" + End + End + + Context "when orchestrator returns error" + orchestrator-client() { + echo "ERROR: cluster not found" + } + + It "returns without error on orchestrator failure" + When call get_master_from_orc + The status should be success + The variable master_from_orc should be undefined + End + End + End + + Describe "setup_replication()" + Context "when mysql commands succeed" + mysql() { + return 0 + } + + It "configures replication successfully" + When call setup_replication "master-1" + The status should be success + The output should include "Configured replication successfully" + End + End + + Context "when mysql commands fail" + mysql() { + return 1 + } + + It "exits with error when mysql commands fail" + When run setup_replication "master-1" + The status should be failure + The stderr should include "Failed to configure replication" + End + End + End + +End \ No newline at end of file diff --git a/addons/mysql/scripts-ut-spec/mysql_orchestrator_register_spec.sh b/addons/mysql/scripts-ut-spec/mysql_orchestrator_register_spec.sh new file mode 100644 index 000000000..3a2ca0d66 --- /dev/null +++ b/addons/mysql/scripts-ut-spec/mysql_orchestrator_register_spec.sh @@ -0,0 +1,152 @@ +# shellcheck shell=bash +# shellcheck disable=SC2034 + +# validate shell version +if ! validate_shell_type_and_version "bash" 4 &>/dev/null; then + echo "mysql_orchestrator_register_spec.sh skip cases because dependency bash version 4 or higher is not installed." + exit 0 +fi + +source ./utils.sh + +# Generate common library +common_library_file="./common.sh" +generate_common_library $common_library_file + +Describe "MySQL Orchestrator Registration Tests" + Include $common_library_file + Include ../scripts/mysql-orchestrator-register.sh + + init() { + ut_mode="true" + } + BeforeAll "init" + + cleanup() { + rm -f $common_library_file + } + AfterAll 'cleanup' + + Describe "validate_env_vars()" + Context "when required environment variables are not set" + It "exits with error when ORC_ENDPOINTS is not set" + unset ORC_ENDPOINTS + export ORC_PORTS="3000" + export MYSQL_POD_FQDN_LIST="mysql-0" + export KB_CLUSTER_COMP_NAME="test" + export KB_NAMESPACE="default" + + When run validate_env_vars + The status should be failure + The stderr should include "Required environment variables ORC_ENDPOINTS or ORC_PORTS not set" + End + + It "exits with error when MYSQL_POD_FQDN_LIST is not set" + export ORC_ENDPOINTS="orc:3000" + export ORC_PORTS="3000" + unset MYSQL_POD_FQDN_LIST + export KB_CLUSTER_COMP_NAME="test" + export KB_NAMESPACE="default" + + When run validate_env_vars + The status should be failure + The stderr should include "Required environment variable MYSQL_POD_FQDN_LIST not set" + End + End + + Context "when all required environment variables are set" + It "succeeds with valid environment variables" + export ORC_ENDPOINTS="orc:3000" + export ORC_PORTS="3000" + export MYSQL_POD_FQDN_LIST="mysql-0" + export KB_CLUSTER_COMP_NAME="test" + export KB_NAMESPACE="default" + + When call validate_env_vars + The status should be success + End + End + End + + Describe "get_orchestrator_endpoint()" + It "returns correct endpoint" + export ORC_ENDPOINTS="orc.default:3000" + export ORC_PORTS="3306" + + When call get_orchestrator_endpoint + The output should eq "orc.default:3306" + End + End + + Describe "get_first_mysql_instance()" + It "returns correct first instance FQDN" + export MYSQL_POD_FQDN_LIST="mysql-0,mysql-1,mysql-2" + export KB_CLUSTER_COMP_NAME="test" + export KB_NAMESPACE="default" + + When call get_first_mysql_instance + The output should eq "test-mysql-0.default" + End + End + + Describe "register_to_orchestrator()" + Context "when registration succeeds" + curl() { + echo "200" + } + + It "successfully registers instance" + When call register_to_orchestrator "test-mysql-0.default" + The status should be success + The output should include "Registration successful for test-mysql-0.default" + End + End + + Context "when registration times out" + curl() { + echo "404" + } + + It "exits with error on timeout" + When run register_to_orchestrator "test-mysql-0.default" + The status should be failure + The stderr should include "Timeout waiting for test-mysql-0.default to become available" + End + End + End + + Describe "register_first_mysql_instance()" + Context "when registration succeeds" + curl() { + echo "200" + } + + It "successfully registers first instance" + export ORC_ENDPOINTS="orc:3000" + export ORC_PORTS="3306" + export MYSQL_POD_FQDN_LIST="mysql-0,mysql-1" + export KB_CLUSTER_COMP_NAME="test" + export KB_NAMESPACE="default" + + When call register_first_mysql_instance + The status should be success + The output should include "First MySQL instance registered successfully" + End + End + + Context "when getting first instance fails" + It "exits with error when MYSQL_POD_FQDN_LIST is empty" + export ORC_ENDPOINTS="orc:3000" + export ORC_PORTS="3306" + export MYSQL_POD_FQDN_LIST="" + export KB_CLUSTER_COMP_NAME="test" + export KB_NAMESPACE="default" + + When run register_first_mysql_instance + The status should be failure + The stderr should include "Failed to get first MySQL instance FQDN" + End + End + End + +End \ No newline at end of file diff --git a/addons/mysql/scripts/init-mysql-instance-for-orc.sh b/addons/mysql/scripts/init-mysql-instance-for-orc.sh index 90b101e3e..0e2b6257a 100644 --- a/addons/mysql/scripts/init-mysql-instance-for-orc.sh +++ b/addons/mysql/scripts/init-mysql-instance-for-orc.sh @@ -1,208 +1,190 @@ #!/bin/sh +# This script initializes MySQL instances for use with Orchestrator. +# It handles instance configuration, user creation, replication setup, +# and cluster metadata initialization. set -ex -# logging functions +# Logging functions for different message levels. +# These functions add timestamps and message types to log output. mysql_log() { local type="$1"; shift - # accept argument string or stdin local text="$*"; if [ "$#" -eq 0 ]; then text="$(cat)"; fi local dt; dt="$(date --rfc-3339=seconds)" printf '%s [%s] [Entrypoint]: %s\n' "$dt" "$type" "$text" } -mysql_note() { - mysql_log Note "$@" -} -mysql_warn() { - mysql_log Warn "$@" >&2 -} -mysql_error() { - mysql_log ERROR "$@" >&2 - exit 1 -} - -mysql_port="3306" -topology_user="$ORC_TOPOLOGY_USER" -topology_password="$ORC_TOPOLOGY_PASSWORD" +# Wrapper functions for different log levels +mysql_note() { mysql_log Note "$@"; } +mysql_warn() { mysql_log Warn "$@" >&2; } +mysql_error() { mysql_log ERROR "$@" >&2; exit 1; } + +# Validates that all required environment variables are set. +# Checks for MySQL root credentials and Orchestrator topology user credentials. +validate_env_vars() { + if [ -z "$MYSQL_ROOT_USER" ] || [ -z "$MYSQL_ROOT_PASSWORD" ]; then + mysql_error "Required environment variables MYSQL_ROOT_USER or MYSQL_ROOT_PASSWORD not set" + fi + if [ -z "$ORC_TOPOLOGY_USER" ] || [ -z "$ORC_TOPOLOGY_PASSWORD" ]; then + mysql_error "Required environment variables ORC_TOPOLOGY_USER or ORC_TOPOLOGY_PASSWORD not set" + fi +} +# Creates the Orchestrator topology user and grants necessary permissions. +# This user is used by Orchestrator to monitor and manage MySQL replication. +create_orc_user() { + mysql_note "Creating orchestrator user and granting permissions..." + + mysql -P 3306 -u "$MYSQL_ROOT_USER" -p"$MYSQL_ROOT_PASSWORD" << EOF +CREATE USER IF NOT EXISTS '$ORC_TOPOLOGY_USER'@'%' IDENTIFIED BY '$ORC_TOPOLOGY_PASSWORD'; +GRANT SUPER, PROCESS, REPLICATION SLAVE, RELOAD ON *.* TO '$ORC_TOPOLOGY_USER'@'%'; +GRANT SELECT ON mysql.slave_master_info TO '$ORC_TOPOLOGY_USER'@'%'; +GRANT DROP ON _pseudo_gtid_.* to '$ORC_TOPOLOGY_USER'@'%'; +GRANT ALL ON kb_orc_meta_cluster.* TO '$ORC_TOPOLOGY_USER'@'%'; +EOF -# create orchestrator user in mysql -create_mysql_user() { - local service_name=$(echo "${KB_CLUSTER_COMP_NAME}_MYSQL_${i}" | tr '-' '_' | tr '[:lower:]' '[:upper:]') + if [ $? -ne 0 ]; then + mysql_error "Failed to create orchestrator user" + fi + mysql_note "Created orchestrator user successfully" +} - mysql_note "Create MySQL User and Grant Permissions..." +# Creates the ProxySQL user and grants required permissions. +# This user is used by ProxySQL to monitor MySQL instances and manage connections. +create_proxy_user() { + mysql_note "Creating ProxySQL user and granting permissions..." - mysql -P 3306 -u $MYSQL_ROOT_USER -p$MYSQL_ROOT_PASSWORD << EOF -CREATE USER IF NOT EXISTS '$topology_user'@'%' IDENTIFIED BY '$topology_password'; -GRANT SUPER, PROCESS, REPLICATION SLAVE, RELOAD ON *.* TO '$topology_user'@'%'; -GRANT SELECT ON mysql.slave_master_info TO '$topology_user'@'%'; -GRANT DROP ON _pseudo_gtid_.* to '$topology_user'@'%'; -GRANT ALL ON kb_orc_meta_cluster.* TO '$topology_user'@'%'; + mysql -P 3306 -u "$MYSQL_ROOT_USER" -p"$MYSQL_ROOT_PASSWORD" << EOF CREATE USER IF NOT EXISTS 'proxysql'@'%' IDENTIFIED BY 'proxysql'; GRANT SELECT ON performance_schema.* TO 'proxysql'@'%'; GRANT SELECT ON sys.* TO 'proxysql'@'%'; set global slave_net_timeout = 4; EOF - mysql_note "Create MySQL User and Grant Permissions completed." - + if [ $? -ne 0 ]; then + mysql_error "Failed to create ProxySQL user" + fi + mysql_note "Created ProxySQL user successfully" } -init_cluster_info_database() { - service_name=$1 - mysql_note "init cluster info database" - mysql -P 3306 -u $MYSQL_ROOT_USER -p$MYSQL_ROOT_PASSWORD << EOF +# Initializes the cluster information database. +# Creates and populates the kb_orc_meta_cluster database with cluster metadata. +init_cluster_info_db() { + local service_name=$1 + mysql_note "Initializing cluster info database..." + + mysql -P 3306 -u "$MYSQL_ROOT_USER" -p"$MYSQL_ROOT_PASSWORD" << EOF CREATE DATABASE IF NOT EXISTS kb_orc_meta_cluster; EOF - mysql -P 3306 -u $MYSQL_ROOT_USER -p$MYSQL_ROOT_PASSWORD -e 'source /scripts/cluster-info.sql' - # if [ "${MYSQL_MAJOR}" = '5.7' ]; then - # mysql -P 3306 -u $MYSQL_ROOT_USER -p$MYSQL_ROOT_PASSWORD -e 'source /scripts/addition_to_sys_v5.sql' - # else - # mysql -P 3306 -u $MYSQL_ROOT_USER -p$MYSQL_ROOT_PASSWORD -e 'source /scripts/addition_to_sys_v8.sql' - # fi - mysql -P 3306 -u $MYSQL_ROOT_USER -p$MYSQL_ROOT_PASSWORD << EOF + + if [ $? -ne 0 ]; then + mysql_error "Failed to create kb_orc_meta_cluster database" + fi + + mysql -P 3306 -u "$MYSQL_ROOT_USER" -p"$MYSQL_ROOT_PASSWORD" -e 'source /scripts/cluster-info.sql' + if [ $? -ne 0 ]; then + mysql_error "Failed to import cluster-info.sql" + fi + + mysql -P 3306 -u "$MYSQL_ROOT_USER" -p"$MYSQL_ROOT_PASSWORD" << EOF USE kb_orc_meta_cluster; -INSERT INTO kb_orc_meta_cluster (anchor,host_name,cluster_name, cluster_domain, data_center) +INSERT INTO kb_orc_meta_cluster (anchor,host_name,cluster_name,cluster_domain,data_center) VALUES (1, '$service_name', '$KB_CLUSTER_NAME', '', '') ON DUPLICATE KEY UPDATE - cluster_name = VALUES(cluster_name), - cluster_domain = VALUES(cluster_domain), - data_center = VALUES(data_center); + cluster_name = VALUES(cluster_name), + cluster_domain = VALUES(cluster_domain), + data_center = VALUES(data_center); EOF + if [ $? -ne 0 ]; then + mysql_error "Failed to insert cluster info" + fi + mysql_note "Initialized cluster info database successfully" } -# wait for mysql to be available -wait_for_connectivity() { - local timeout=600 - local start_time=$(date +%s) - local current_time - - while true; do - current_time=$(date +%s) - if [ $((current_time - start_time)) -gt $timeout ]; then - exit 1 - fi - - # Send PING and check for mysql response - if mysqladmin -P 3306 -u "$MYSQL_ROOT_USER" -p"$MYSQL_ROOT_PASSWORD" PING | grep -q "mysqld is alive"; then - mysql_note "mysql is reachable." - break - fi - - sleep 5 - done -} - -setup_master_slave() { - - mysql -P 3306 -u $MYSQL_ROOT_USER -p$MYSQL_ROOT_PASSWORD -e "STOP SLAVE;RESET MASTER;RESET SLAVE ALL;"; - - mysql_note "setup_master_slave" - - IFS=',' read -r -a replicas <<< "${MYSQL_POD_FQDN_LIST}" - - master_fqdn=${replicas[0]} - master_last_digit=${master_fqdn##*-} - master_host=$(echo "${KB_CLUSTER_COMP_NAME}_MYSQL_${master_last_digit}" | tr '_' '-' | tr '[:upper:]' '[:lower:]' ) - master_from_orc="" - get_master_from_orc - - self_last_digit=${SYNCER_POD_NAME##*-} - self_service_name=$(echo "${KB_CLUSTER_COMP_NAME}_MYSQL_${self_last_digit}" | tr '_' '-' | tr '[:upper:]' '[:lower:]' ) - - # If the cluster is already registered to the Orchestrator and the Master of the cluster is itself, then no action is required. - if [ "$master_from_orc" == "${self_service_name}" ]; then - return 0 - fi - - # If master_from_orc is not empty, then replace master_host with master_from_orc. - if [[ $master_from_orc != "" ]]; then - master_host=$master_from_orc - fi - - - # If the master_host is empty, then this pod is the first one in the cluster, init cluster info database and create user. - if [[ $master_from_orc == "" && $self_last_digit -eq 0 ]]; then - echo "Create MySQL User and Grant Permissions" - - if mysql -P 3306 -u $MYSQL_ROOT_USER -p$MYSQL_ROOT_PASSWORD -e "SELECT 1 FROM mysql.user WHERE user='$topology_user'" 2>/dev/null | grep $topology_user >/dev/null; then - return 0 - fi - create_mysql_user - init_cluster_info_database self_service_name - # If the master_host is not empty, change master to the master_host. - else - mysql_note "Wait for master to be ready" - change_master "$master_host" - fi - return 0 +# Waits for MySQL to become available. +# Repeatedly attempts to connect until success or timeout. +wait_for_mysql() { + local timeout=600 + local start_time=$(date +%s) + local current_time + + mysql_note "Waiting for MySQL to be available..." + while true; do + current_time=$(date +%s) + if [ $((current_time - start_time)) -gt $timeout ]; then + mysql_error "Timeout waiting for MySQL to be available" + fi + + if mysqladmin -P 3306 -u "$MYSQL_ROOT_USER" -p"$MYSQL_ROOT_PASSWORD" ping &>/dev/null; then + mysql_note "MySQL is now available" + break + fi + sleep 5 + done } +# Retrieves master information from Orchestrator. +# Queries Orchestrator's topology API and parses the response to find the current master. get_master_from_orc() { - local timeout=50 - local start_time=$(date +%s) - local current_time - - while true; do - current_time=$(date +%s) - if [ $((current_time - start_time)) -gt $timeout ]; then - mysql_note "Timeout waiting for $host to become available." - return 0 - fi - - topology_info=$(/scripts/orchestrator-client -c topology -i $KB_CLUSTER_NAME) || true - if [[ $topology_info == "" ]]; then - return 0 - fi - if [[ $topology_info =~ ^ERROR ]]; then - return 0 - fi - # Extract the first line - first_line=$(echo "$topology_info" | head -n 1) - - # Remove square brackets and split by comma - cleaned_line=$(echo "$first_line" | tr -d '[]') - - # Parse the status variables using comma as the delimiter - IFS=',' read -ra status_array <<< "$cleaned_line" - - # Save individual status variables - lag="${status_array[0]}" - status="${status_array[1]}" - version="${status_array[2]}" - rw="${status_array[3]}" - mod="${status_array[4]}" - type="${status_array[5]}" - GTID="${status_array[6]}" - GTIDMOD="${status_array[7]}" - - address_port=$(echo "$first_line" | awk '{print $1}') - address="${address_port%*:}" - port="${address_port#*:}" - - if [ -z "$address_port" ]; then - return 0 - fi - - if [ "$status" == "ok" ]; then - master_from_orc="${address_port%:*}" - break - fi - sleep 5 - done - return 0 + local timeout=50 + local start_time=$(date +%s) + local current_time + + while true; do + current_time=$(date +%s) + if [ $((current_time - start_time)) -gt $timeout ]; then + mysql_note "Timeout waiting for master info from orchestrator" + return 0 + fi + + topology_info=$(/scripts/orchestrator-client -c topology -i "$KB_CLUSTER_NAME") || true + if [ -z "$topology_info" ] || [[ $topology_info =~ ^ERROR ]]; then + return 0 + fi + + parse_topology_info "$topology_info" + if [ -n "$master_from_orc" ] && [ "$status" = "ok" ]; then + break + fi + sleep 5 + done + return 0 } -change_master() { - mysql_note "Change master" - master_host=$1 - master_port=3306 +# Parses topology information from Orchestrator's response. +# Extracts status information and master node details from the topology output. +parse_topology_info() { + local topology_info=$1 + + # Extract first line + local first_line=$(echo "$topology_info" | head -n 1) + local cleaned_line=$(echo "$first_line" | tr -d '[]') + + # Parse status variables + IFS=',' read -ra status_array <<< "$cleaned_line" + + lag="${status_array[0]}" + status="${status_array[1]}" + version="${status_array[2]}" + rw="${status_array[3]}" + mod="${status_array[4]}" + type="${status_array[5]}" + GTID="${status_array[6]}" + GTIDMOD="${status_array[7]}" + + local address_port=$(echo "$first_line" | awk '{print $1}') + if [ -n "$address_port" ]; then + master_from_orc="${address_port%:*}" + fi +} - username=$mysql_username - password=$mysql_password +# Configures MySQL replication with the specified master. +# Sets up GTID-based replication and starts the slave process. +setup_replication() { + local master_host=$1 + mysql_note "Configuring replication with master $master_host..." - mysql -u "$MYSQL_ROOT_USER" -p"$MYSQL_ROOT_PASSWORD" << EOF + mysql -u "$MYSQL_ROOT_USER" -p"$MYSQL_ROOT_PASSWORD" << EOF SET GLOBAL READ_ONLY=1; STOP SLAVE; CHANGE MASTER TO @@ -211,19 +193,43 @@ MASTER_AUTO_POSITION=1, MASTER_CONNECT_RETRY=1, MASTER_RETRY_COUNT=86400, MASTER_HOST='$master_host', -MASTER_PORT=$master_port, +MASTER_PORT=3306, MASTER_USER='$MYSQL_ROOT_USER', MASTER_PASSWORD='$MYSQL_ROOT_PASSWORD'; START SLAVE; EOF - mysql_note "CHANGE MASTER successful for $master_host." + if [ $? -ne 0 ]; then + mysql_error "Failed to configure replication" + fi + mysql_note "Configured replication successfully" } -main() { - wait_for_connectivity - setup_master_slave - echo "init mysql instance for orc completed" +# Main function that coordinates the MySQL instance initialization process. +# Handles environment validation, MySQL availability check, and either +# initializes the first instance or configures replication for subsequent instances. +main() { + validate_env_vars + wait_for_mysql + + # Get pod info + local self_last_digit=${SYNCER_POD_NAME##*-} + local self_service_name=$(echo "${KB_CLUSTER_COMP_NAME}_MYSQL_${self_last_digit}" | tr '_' '-' | tr '[:upper:]' '[:lower:]') + + # Get master info + local master_from_orc="" + get_master_from_orc + + # Initialize first pod or configure replication + if [ -z "$master_from_orc" ] && [ "$self_last_digit" -eq 0 ]; then + create_orc_user + create_proxy_user + init_cluster_info_db "$self_service_name" + else + setup_replication "$master_from_orc" + fi + + mysql_note "MySQL instance initialization completed successfully" } main diff --git a/addons/mysql/scripts/mysql-orchestrator-register.sh b/addons/mysql/scripts/mysql-orchestrator-register.sh index 87fa624e0..64bc7e25e 100644 --- a/addons/mysql/scripts/mysql-orchestrator-register.sh +++ b/addons/mysql/scripts/mysql-orchestrator-register.sh @@ -1,52 +1,110 @@ #!/bin/sh +# This script handles the registration of MySQL instances with Orchestrator. +# It validates required environment variables, gets the first MySQL instance, +# and registers it with the Orchestrator API. set -ex -# register a mysql instance to orchestrator -register_to_orchestrator() { - local host_ip=$1 +# Logging functions for different message levels. +mysql_log() { + local type="$1"; shift + local text="$*"; if [ "$#" -eq 0 ]; then text="$(cat)"; fi + local dt; dt="$(date --rfc-3339=seconds)" + printf '%s [%s] [Orchestrator]: %s\n' "$dt" "$type" "$text" +} - local timeout=100 - local start_time=$(date +%s) - local current_time +# Wrapper functions for different log levels +mysql_note() { mysql_log Note "$@"; } +mysql_warn() { mysql_log Warn "$@" >&2; } +mysql_error() { mysql_log ERROR "$@" >&2; exit 1; } +# Validates that all required environment variables are set. +# This includes ORC_ENDPOINTS, ORC_PORTS for Orchestrator connection, +# MYSQL_POD_FQDN_LIST for pod information, and cluster-related variables. +validate_env_vars() { + if [ -z "$ORC_ENDPOINTS" ] || [ -z "$ORC_PORTS" ]; then + mysql_error "Required environment variables ORC_ENDPOINTS or ORC_PORTS not set" + fi + if [ -z "$MYSQL_POD_FQDN_LIST" ]; then + mysql_error "Required environment variable MYSQL_POD_FQDN_LIST not set" + fi + if [ -z "$KB_CLUSTER_COMP_NAME" ] || [ -z "$KB_NAMESPACE" ]; then + mysql_error "Required environment variables KB_CLUSTER_COMP_NAME or KB_NAMESPACE not set" +} + +# Constructs and returns the Orchestrator endpoint URL by combining +# the host from ORC_ENDPOINTS with the port from ORC_PORTS. +get_orchestrator_endpoint() { endpoint=${ORC_ENDPOINTS%%:*}:${ORC_PORTS} + echo "$endpoint" +} +# Registers a MySQL instance with Orchestrator by making API calls. +# This function will retry the registration for a specified timeout period, +# checking the instance availability through Orchestrator's API. +# If registration succeeds, it logs success; if it times out, it exits with error. +register_to_orchestrator() { + local host_ip=$1 + local endpoint=$(get_orchestrator_endpoint) local url="http://${endpoint}/api/discover/$host_ip/3306" local instance_url="http://${endpoint}/api/instance/$host_ip/3306" - echo "register first mysql pod to orchestrator..." + mysql_note "Registering MySQL instance $host_ip to orchestrator..." + + local timeout=100 + local start_time=$(date +%s) + local current_time + local response while true; do - # register to Orchestrator - echo "register $pod_name ($host_ip) to Orchestrator..." current_time=$(date +%s) if [ $((current_time - start_time)) -gt $timeout ]; then - echo "Timeout waiting for $host to become available." - exit 1 + mysql_error "Timeout waiting for $host_ip to become available" fi # send request to orchestrator for discovery - response=$(curl -s -o /dev/null -w "%{http_code}" $url) - if [ $response -eq 200 ]; then - echo "response success" - break + response=$(curl -s -o /dev/null -w "%{http_code}" "$url") + if [ "$response" -eq 200 ]; then + mysql_note "Registration successful for $host_ip" + return 0 fi sleep 5 done - echo "register $pod_name ($host_ip) to Orchestrator successful." } -# register the first MySQL instance to Orchestrator, -# and Orchestrator will obtain the entire MySQL cluster topology info from this instance. +# Get first MySQL instance FQDN +get_first_mysql_instance() { + IFS=',' read -r -a replicas <<< "${MYSQL_POD_FQDN_LIST}" + + local fqdn_name=${replicas[0]} + local last_digit=${fqdn_name##*-} + echo "${KB_CLUSTER_COMP_NAME}-mysql-${last_digit}.${KB_NAMESPACE}" +} + +# Coordinates the registration of the first MySQL instance with Orchestrator. +# This function validates environment variables, gets the first instance's FQDN, +# and triggers the registration process. It ensures proper error handling and +# logging throughout the process. register_first_mysql_instance() { + # Source environment preparation script + source "$(dirname "$0")/prepare_env.sh" + validate_env_vars + + local first_mysql_instance + first_mysql_instance=$(get_first_mysql_instance) + + if [ -z "$first_mysql_instance" ]; then + mysql_error "Failed to get first MySQL instance FQDN" + fi - IFS=',' read -r -a replicas <<< "${MYSQL_POD_FQDN_LIST}" - fqdn_name=${replicas[0]} - last_digit=${fqdn_name##*-} - first_mysql_instance=${KB_CLUSTER_COMP_NAME}-mysql-${last_digit}.${KB_NAMESPACE} register_to_orchestrator "$first_mysql_instance" + mysql_note "First MySQL instance registered successfully" +} - echo "Initialization script completed!" +# Main entry point for the script. +# Executes the registration process and ensures proper completion logging. +main() { + register_first_mysql_instance + mysql_note "Orchestrator registration completed successfully" } -register_first_mysql_instance +main diff --git a/addons/mysql/scripts/orchestrator-client.sh b/addons/mysql/scripts/orchestrator-client.sh index 4f1f7473a..5a8e0ac25 100644 --- a/addons/mysql/scripts/orchestrator-client.sh +++ b/addons/mysql/scripts/orchestrator-client.sh @@ -1,3 +1,4 @@ + #!/bin/bash # # orchestrator-client: a wrapper script for calling upon orchestrator's API @@ -45,17 +46,6 @@ myname=$(basename $0) [ -f /etc/profile.d/orchestrator-client.sh ] && . /etc/profile.d/orchestrator-client.sh -prepare_orchestrator_env() { - if [[ -z "$ORCHESTRATOR_API" ]]; then - ORCHESTRATOR_API=$(echo "http://${ORC_ENDPOINTS%%:*}:${ORC_PORTS}" | tr '_' '-' | tr '[:upper:]' '[:lower:]') - fi - export ORCHESTRATOR_API=$ORCHESTRATOR_API -} - -if [[ -z "$ORCHESTRATOR_API" ]]; then - prepare_orchestrator_env -fi - orchestrator_api="${ORCHESTRATOR_API:-http://localhost:3000}" leader_api= @@ -254,7 +244,7 @@ function detect_leader_api { return fi done - leader_api=${apis[0]} + fail "Cannot determine leader from $orchestrator_api" } function urlencode { @@ -1039,7 +1029,7 @@ function run_command { } function main { - + check_requirements detect_leader_api instance_hostport=$(to_hostport $instance) diff --git a/addons/mysql/scripts/prepare_env.sh b/addons/mysql/scripts/prepare_env.sh new file mode 100644 index 000000000..f90f34cf6 --- /dev/null +++ b/addons/mysql/scripts/prepare_env.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# This script prepares environment variables for Orchestrator client. +# It ensures ORCHESTRATOR_API is properly set using ORC_ENDPOINTS and ORC_PORTS. + +# Prepares the ORCHESTRATOR_API environment variable if not already set. +# Constructs API URL from ORC_ENDPOINTS and ORC_PORTS, converting to lowercase +# and replacing underscores with hyphens. +prepare_orchestrator_env() { + if [[ -z "$ORCHESTRATOR_API" ]]; then + ORCHESTRATOR_API=$(echo "http://${ORC_ENDPOINTS%%:*}:${ORC_PORTS}" | tr '_' '-' | tr '[:upper:]' '[:lower:]') + fi + export ORCHESTRATOR_API=$ORCHESTRATOR_API +} + +# Check and prepare ORCHESTRATOR_API if not set +if [[ -z "$ORCHESTRATOR_API" ]]; then + prepare_orchestrator_env +fi \ No newline at end of file diff --git a/addons/mysql/templates/_helpers.tpl b/addons/mysql/templates/_helpers.tpl index 53dcfa34c..6393f9218 100644 --- a/addons/mysql/templates/_helpers.tpl +++ b/addons/mysql/templates/_helpers.tpl @@ -263,6 +263,7 @@ roleProbe: - /bin/bash - -c - | + source /scripts/prepare_env.sh topology_info=$(/kubeblocks/orchestrator-client -c topology -i $KB_CLUSTER_NAME) || true if [[ $topology_info == "" ]]; then echo -n "secondary" @@ -292,6 +293,7 @@ memberLeave: - /bin/bash - -c - | + source /scripts/prepare_env.sh set +e master_from_orc=$(/kubeblocks/orchestrator-client -c which-cluster-master -i $KB_CLUSTER_NAME) last_digit=${KB_LEAVE_MEMBER_POD_NAME##*-}