feat: add service restart, verification, and full rollback orchestration to native port takeover
This commit is contained in:
@@ -234,10 +234,110 @@ rollback_config_changes() {
|
||||
done
|
||||
}
|
||||
|
||||
NATIVE_SERVICE_NAME=""
|
||||
DID_RESTART_NATIVE=0
|
||||
DID_RESTART_DA=0
|
||||
# Guards rollback_service_state so it only acts while wired via the EXIT trap
|
||||
# below. NOTE: this is intentionally an EXIT trap, not an ERR trap - see the
|
||||
# comment above the trap installation in main() for why.
|
||||
ROLLBACK_ARMED=0
|
||||
|
||||
detect_native_service_name() {
|
||||
NATIVE_SERVICE_NAME="$(mysql_service_name)"
|
||||
log "Wykryta usługa natywnej MariaDB: $NATIVE_SERVICE_NAME."
|
||||
}
|
||||
|
||||
restart_native_service() {
|
||||
systemctl restart "$NATIVE_SERVICE_NAME" || die "Nie udało się zrestartować usługi $NATIVE_SERVICE_NAME."
|
||||
DID_RESTART_NATIVE=1
|
||||
}
|
||||
|
||||
verify_native_listening_new_port() {
|
||||
local i
|
||||
for i in $(seq 1 30); do
|
||||
if port_is_listening "$NEW_NATIVE_PORT" && ! port_is_listening 3306; then
|
||||
log "Natywna instancja nasłuchuje na porcie $NEW_NATIVE_PORT, port 3306 wolny."
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
die "Natywna instancja nie nasłuchuje na porcie $NEW_NATIVE_PORT po restarcie (lub port 3306 wciąż zajęty)."
|
||||
}
|
||||
|
||||
restart_directadmin_service() {
|
||||
systemctl restart directadmin || die "Nie udało się zrestartować usługi directadmin."
|
||||
DID_RESTART_DA=1
|
||||
}
|
||||
|
||||
verify_da_connectivity_new_port() {
|
||||
local bin
|
||||
bin="$(mysql_detect_binary mysql)" || die "Nie znaleziono klienta mysql/mariadb do weryfikacji łączności DA."
|
||||
MYSQL_PWD="$NATIVE_CURRENT_PASS" "$bin" \
|
||||
--user="$NATIVE_CURRENT_USER" \
|
||||
--protocol=TCP --host=127.0.0.1 --port="$NEW_NATIVE_PORT" \
|
||||
-e "SELECT 1" >/dev/null 2>>"$LOG_FILE" \
|
||||
|| die "DirectAdmin nie może połączyć się z natywną instancją na nowym porcie $NEW_NATIVE_PORT."
|
||||
log "Potwierdzono łączność DirectAdmina z natywną instancją na porcie $NEW_NATIVE_PORT."
|
||||
}
|
||||
|
||||
rollback_service_state() {
|
||||
[ "$ROLLBACK_ARMED" -eq 1 ] || return 0
|
||||
rollback_config_changes
|
||||
restore_custombuild_mysql_management
|
||||
if [ -n "$NATIVE_SERVICE_NAME" ]; then
|
||||
systemctl restart "$NATIVE_SERVICE_NAME" \
|
||||
|| log "OSTRZEŻENIE: nie udało się zrestartować $NATIVE_SERVICE_NAME podczas wycofywania zmian."
|
||||
fi
|
||||
if [ "$DID_RESTART_DA" -eq 1 ]; then
|
||||
systemctl restart directadmin \
|
||||
|| log "OSTRZEŻENIE: nie udało się zrestartować directadmin podczas wycofywania zmian."
|
||||
fi
|
||||
log "Wycofano zmiany przejęcia portu natywnej instancji MariaDB."
|
||||
}
|
||||
|
||||
main() {
|
||||
require_root
|
||||
check_os_guard
|
||||
read_native_mysql_conf
|
||||
read_current_mysql_inst
|
||||
|
||||
if already_migrated; then
|
||||
log "Natywna instancja już działa na porcie $NATIVE_CURRENT_PORT (nie 3306) - pomijam przejęcie."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
check_native_reachable
|
||||
preflight_new_port_free
|
||||
detect_native_service_name
|
||||
|
||||
# NOTE: deliberately an EXIT trap, not an ERR trap as originally planned.
|
||||
# die() (used by every failure path below) calls `exit` directly, and bash's
|
||||
# ERR trap does not fire for a function that terminates via an explicit
|
||||
# `exit` call - it only fires when a command's non-zero status propagates
|
||||
# through normal control flow. An EXIT trap fires on *any* shell exit
|
||||
# (explicit exit, errexit-triggered exit, or falling off the end of the
|
||||
# script), which is what "any failure from this point on triggers
|
||||
# rollback" actually requires here. ROLLBACK_ARMED guards it so it is a
|
||||
# no-op before this point and after a successful run.
|
||||
ROLLBACK_ARMED=1
|
||||
trap 'rollback_service_state' EXIT
|
||||
|
||||
disable_custombuild_mysql_management
|
||||
apply_config_mutations
|
||||
restart_native_service
|
||||
verify_native_listening_new_port
|
||||
restart_directadmin_service
|
||||
verify_da_connectivity_new_port
|
||||
|
||||
ROLLBACK_ARMED=0
|
||||
trap - EXIT
|
||||
log "Przejęcie portu natywnej instancji MariaDB zakończone powodzeniem: $NATIVE_CURRENT_PORT -> $NEW_NATIVE_PORT."
|
||||
}
|
||||
|
||||
[ -r "$COMMON_FILE" ] || die "missing helper: $COMMON_FILE"
|
||||
# shellcheck source=./mysql_common.sh
|
||||
source "$COMMON_FILE"
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
: # main() is added in Task 4.
|
||||
main "$@"
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
SCRIPT="$PLUGIN_DIR/scripts/setup/native_mariadb_port_takeover.sh"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
setup_fixture() {
|
||||
local dir="$1"
|
||||
mkdir -p "$dir/bin" "$dir/my.cnf.d"
|
||||
|
||||
cat > "$dir/my.cnf" <<'EOF'
|
||||
[client]
|
||||
port=3306
|
||||
|
||||
[mysqld]
|
||||
datadir=/var/lib/mysql
|
||||
port=3306
|
||||
socket=/var/lib/mysql/mysql.sock
|
||||
EOF
|
||||
|
||||
cat > "$dir/mysql.conf" <<'EOF'
|
||||
user=da_admin
|
||||
passwd=secret
|
||||
port=3306
|
||||
socket=/var/lib/mysql/mysql.sock
|
||||
host=
|
||||
EOF
|
||||
|
||||
cat > "$dir/options.conf" <<'EOF'
|
||||
mysql_inst=mariadb
|
||||
mariadb=10.6
|
||||
EOF
|
||||
|
||||
cat > "$dir/os-release" <<'EOF'
|
||||
ID="almalinux"
|
||||
VERSION_ID="9.4"
|
||||
EOF
|
||||
}
|
||||
|
||||
# run_takeover DIR
|
||||
# Runs the script under test against the fixture already prepared at DIR
|
||||
# (by a prior call to setup_fixture, possibly customized further by the
|
||||
# scenario since) - writes stdout+stderr to "$DIR/run.out". Does NOT touch
|
||||
# the fixture itself, so scenario-specific customizations made after
|
||||
# setup_fixture (e.g. editing mysql.conf, swapping os-release) are preserved.
|
||||
run_takeover() {
|
||||
local dir="$1"
|
||||
|
||||
env \
|
||||
NATIVE_TAKEOVER_LOG="$dir/takeover.log" \
|
||||
NATIVE_MY_CNF="$dir/my.cnf" \
|
||||
NATIVE_MY_CNF_D="$dir/my.cnf.d" \
|
||||
NATIVE_MYSQL_CONF="$dir/mysql.conf" \
|
||||
CUSTOMBUILD_OPTIONS_CONF="$dir/options.conf" \
|
||||
OS_RELEASE_FILE="$dir/os-release" \
|
||||
NEW_NATIVE_PORT="3307" \
|
||||
DA_CLI_BIN="$dir/bin/da" \
|
||||
PATH="$dir/bin:$PATH" \
|
||||
bash "$SCRIPT" > "$dir/run.out" 2>&1
|
||||
}
|
||||
|
||||
# --- Fake command stubs shared by scenarios; each scenario builds its own copies ---
|
||||
write_common_stubs() {
|
||||
local dir="$1"
|
||||
local mode="${2:-happy}"
|
||||
|
||||
cat > "$dir/bin/da" <<EOF
|
||||
#!/bin/bash
|
||||
echo "\$*" >> "$dir/da_calls.log"
|
||||
exit 0
|
||||
EOF
|
||||
chmod 755 "$dir/bin/da"
|
||||
|
||||
cat > "$dir/bin/mysql" <<EOF
|
||||
#!/bin/bash
|
||||
CURRENT_PORT_FILE="$dir/current_port"
|
||||
requested_port=""
|
||||
for arg in "\$@"; do
|
||||
case "\$arg" in
|
||||
--port=*) requested_port="\${arg#--port=}" ;;
|
||||
esac
|
||||
done
|
||||
if [ "\$requested_port" = "\$(cat "\$CURRENT_PORT_FILE" 2>/dev/null || echo 3306)" ]; then
|
||||
exit 0
|
||||
fi
|
||||
exit 1
|
||||
EOF
|
||||
chmod 755 "$dir/bin/mysql"
|
||||
echo "3306" > "$dir/current_port"
|
||||
|
||||
cat > "$dir/bin/systemctl" <<EOF
|
||||
#!/bin/bash
|
||||
# mysql_service_name() (mysql_common.sh) probes "mysqld", then "mariadb", then
|
||||
# "mysql" via 'systemctl list-unit-files <name>.service' and picks the first
|
||||
# that succeeds - only mariadb.service "exists" here, matching a real
|
||||
# AlmaLinux+MariaDB box where mysqld.service does not exist.
|
||||
if [ "\$1" = "list-unit-files" ]; then
|
||||
case "\$2" in
|
||||
mariadb.service) exit 0 ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
echo "systemctl \$*" >> "$dir/systemctl_calls.log"
|
||||
if [ "\$1" = "restart" ] && [ "\$2" = "mariadb" ] && [ "$mode" = "restart-fail" ]; then
|
||||
exit 1
|
||||
fi
|
||||
if [ "\$1" = "restart" ] && [ "\$2" = "mariadb" ]; then
|
||||
echo "3307" > "$dir/current_port"
|
||||
fi
|
||||
if [ "\$1" = "restart" ] && [ "\$2" = "directadmin" ] && [ "$mode" = "da-restart-fail" ]; then
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
EOF
|
||||
chmod 755 "$dir/bin/systemctl"
|
||||
|
||||
cat > "$dir/bin/ss" <<EOF
|
||||
#!/bin/bash
|
||||
current="\$(cat "$dir/current_port" 2>/dev/null || echo 3306)"
|
||||
for arg in "\$@"; do
|
||||
case "\$arg" in
|
||||
*":\$current"*) echo header; echo occupied; exit 0 ;;
|
||||
esac
|
||||
done
|
||||
exit 0
|
||||
EOF
|
||||
chmod 755 "$dir/bin/ss"
|
||||
}
|
||||
|
||||
# --- Scenario 1: happy path ---
|
||||
DIR="$TMP_DIR/happy"
|
||||
setup_fixture "$DIR"
|
||||
write_common_stubs "$DIR" happy
|
||||
run_takeover "$DIR"
|
||||
STATUS=$?
|
||||
[ "$STATUS" -eq 0 ] || fail "happy path should exit 0, got $STATUS: $(cat "$DIR/run.out")"
|
||||
grep -Fxq "port=3307" "$DIR/mysql.conf" || fail "happy path should leave mysql.conf on port 3307"
|
||||
grep -Fq "build set mysql_inst no" "$DIR/da_calls.log" || fail "happy path should call da build set mysql_inst no"
|
||||
grep -Fq "restart mariadb" "$DIR/systemctl_calls.log" || fail "happy path should restart mariadb"
|
||||
grep -Fq "restart directadmin" "$DIR/systemctl_calls.log" || fail "happy path should restart directadmin"
|
||||
|
||||
# --- Scenario 2: already migrated (idempotent no-op) ---
|
||||
DIR="$TMP_DIR/already"
|
||||
setup_fixture "$DIR"
|
||||
write_common_stubs "$DIR" happy
|
||||
sed -i.orig 's/^port=3306$/port=3307/' "$DIR/mysql.conf"
|
||||
run_takeover "$DIR"
|
||||
STATUS=$?
|
||||
[ "$STATUS" -eq 0 ] || fail "already-migrated case should exit 0, got $STATUS"
|
||||
grep -Fq "build set" "$DIR/da_calls.log" 2>/dev/null && fail "already-migrated case should not touch CustomBuild settings"
|
||||
|
||||
# --- Scenario 3: OS guard rejection ---
|
||||
DIR="$TMP_DIR/badose"
|
||||
setup_fixture "$DIR"
|
||||
write_common_stubs "$DIR" happy
|
||||
cat > "$DIR/os-release" <<'EOF'
|
||||
ID="cloudlinux"
|
||||
VERSION_ID="8.9"
|
||||
EOF
|
||||
set +e
|
||||
run_takeover "$DIR"
|
||||
STATUS=$?
|
||||
set -e
|
||||
[ "$STATUS" -ne 0 ] || fail "CloudLinux should be refused, not exit 0"
|
||||
grep -Fq "build set" "$DIR/da_calls.log" 2>/dev/null && fail "OS-guard rejection must not touch CustomBuild settings"
|
||||
|
||||
# --- Scenario 4: rollback when native restart fails ---
|
||||
DIR="$TMP_DIR/restartfail"
|
||||
setup_fixture "$DIR"
|
||||
write_common_stubs "$DIR" restart-fail
|
||||
set +e
|
||||
run_takeover "$DIR"
|
||||
STATUS=$?
|
||||
set -e
|
||||
[ "$STATUS" -ne 0 ] || fail "a failed native restart should not exit 0"
|
||||
grep -Fxq "port=3306" "$DIR/mysql.conf" \
|
||||
|| fail "mysql.conf should be rolled back to port 3306 after a failed native restart"
|
||||
grep -Fq "build set mysql_inst mariadb" "$DIR/da_calls.log" \
|
||||
|| fail "mysql_inst should be restored to mariadb after a failed native restart"
|
||||
|
||||
# --- Scenario 5: rollback when DirectAdmin restart fails ---
|
||||
DIR="$TMP_DIR/darestartfail"
|
||||
setup_fixture "$DIR"
|
||||
write_common_stubs "$DIR" da-restart-fail
|
||||
set +e
|
||||
run_takeover "$DIR"
|
||||
STATUS=$?
|
||||
set -e
|
||||
[ "$STATUS" -ne 0 ] || fail "a failed directadmin restart should not exit 0"
|
||||
grep -Fxq "port=3306" "$DIR/mysql.conf" \
|
||||
|| fail "mysql.conf should be rolled back to port 3306 after a failed directadmin restart"
|
||||
grep -Fq "build set mysql_inst mariadb" "$DIR/da_calls.log" \
|
||||
|| fail "mysql_inst should be restored to mariadb after a failed directadmin restart"
|
||||
|
||||
echo "native_mariadb_port_takeover_e2e_test: OK"
|
||||
Reference in New Issue
Block a user