Files
OpenStackScripts/3-5-controller.sh
T

251 lines
11 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# 3-5 控制节点评分:CentOS 7.9 / OpenStack Rocky,Bash 4+,满分 100。
# 用法:在 controller 上执行 bash 3-5-controller.sh
# 可选:CHECK_CONTROLLER_IP、CHECK_COMPUTE_IP(默认读取 hosts)
# CHECK_ADMIN_PASSWORD(默认 admin)、CHECK_NOVA_PASSWORD(默认 nova)
# 只读检查;不运行环境重置、discover_hosts、数据库同步或任何服务修改。
# 使用实验管理员凭证直接查询,不 source 学生编写的 admin-openstack.sh。
# 检查当前结果,无法证明学生执行过重置/加载环境变量/发现主机的命令。
# 退出码:0=100分,1=有扣分,2=权限/基础工具/参数错误,未完成评分。
# 分值:网络20、认证10、compute注册10、compute可用10、其他计算服务15、
# Cell映射15、服务目录10、nova-status10。
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
set -o pipefail
SCORE=0
TOTAL=0
PASS_CNT=0
FAIL_CNT=0
TASK_CNT=0
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[0;33m'
NC='\033[0m'
separator() {
printf '%b====================================================%b\n' "$YELLOW" "$NC"
}
banner() {
separator
printf '%b %s%b\n' "$YELLOW" "$1" "$NC"
separator
}
section() {
TASK_CNT=$((TASK_CNT+1))
printf '%b[➜] %d. %s%b\n' "$BLUE" "$TASK_CNT" "$1" "$NC"
}
error() {
printf '%b[✘ 失败] %s%b\n' "$RED" "$1" "$NC"
}
# 每个评分项分值固定;查询失败/依赖缺失也计入满分,不跳项缩小分母。
# 通过和失败各自使用明确的提示,逐项实时显示。
check() {
local points=$1 success_message=$2 failure_message=$3
shift 3
TOTAL=$((TOTAL+points))
if "$@" >/dev/null 2>&1; then
SCORE=$((SCORE+points)); PASS_CNT=$((PASS_CNT+1))
printf '%b[✔ 通过] %s%b\n' "$GREEN" "$success_message" "$NC"
else
FAIL_CNT=$((FAIL_CNT+1))
printf '%b[✘ 失败] %s%b\n' "$RED" "$failure_message" "$NC"
fi
}
finish() {
printf '\n'
separator
printf '%b总成绩:%d/100 分%b | 通过: %b%d%b | 失败: %b%d%b\n' "$YELLOW" "$SCORE" "$NC" "$GREEN" "$PASS_CNT" "$NC" "$RED" "$FAIL_CNT" "$NC"
if [ "$TOTAL" -ne 100 ]; then
error "评分项总分异常:$TOTAL,请联系教师检查脚本。"
separator
exit 2
fi
if [ "$FAIL_CNT" -eq 0 ]; then
separator
exit 0
fi
printf '%b请按红字提示修正。%b\n' "$RED" "$NC"
separator
exit 1
}
preflight() {
if [ "$EUID" -ne 0 ]; then error '请以 root 身份在对应节点执行。未评分。'; exit 2; fi
local cmd
for cmd in awk grep sort tr timeout; do
if ! command -v "$cmd" >/dev/null 2>&1; then error "缺少基础工具 $cmd,无法评分。"; exit 2; fi
done
}
valid_ipv4() {
printf '%s\n' "$1" | awk -F. '
NF!=4 { bad=1 }
{ for(i=1;i<=NF;i++) if($i !~ /^[0-9]+$/ || length($i)>3 || $i+0>255) bad=1 }
END { exit (NR!=1 || bad) }'
}
hosts_ip() {
awk -v name="$1" '{sub(/#.*/, ""); for(i=2;i<=NF;i++) if($i==name) print $1}' /etc/hosts 2>/dev/null | sort -u
}
resolved_as() {
local addresses
valid_ipv4 "$2" || return 1
addresses=$(timeout 5 getent ahostsv4 "$1" 2>/dev/null | awk '{print $1}' | sort -u) || return 1
[ "$addresses" = "$2" ]
}
# 按节读取最后一个有效赋值;忽略整行注释,不执行配置文件。
ini_value() {
awk -v section="$2" -v key="$3" '
/^[[:space:]]*[#;]/ { next }
{ sub(/\r$/, "") }
/^[[:space:]]*\[/ {
s=$0; sub(/^[[:space:]]*\[/,"",s); sub(/\].*$/,"",s)
gsub(/^[[:space:]]+|[[:space:]]+$/,"",s); next
}
s==section && index($0,"=") {
k=substr($0,1,index($0,"=")-1); gsub(/^[[:space:]]+|[[:space:]]+$/,"",k)
if(k==key) {
v=substr($0,index($0,"=")+1)
gsub(/^[[:space:]]+|[[:space:]]+$/,"",v)
}
}
END {print v}' "$1" 2>/dev/null
}
enabled_service() { [ "$(timeout 10 systemctl is-enabled "$1" 2>/dev/null)" = enabled ]; }
active_service() { timeout 10 systemctl is-active --quiet "$1"; }
banner 'OpenStack 控制节点评分'
preflight
# 自动检测失败(例如 hosts 冲突)仍继续评分,让解析项正常扣分。
CONTROLLER_IP=${CHECK_CONTROLLER_IP:-$(hosts_ip controller)}
if [ -z "${CHECK_CONTROLLER_IP:-}" ] && ! valid_ipv4 "$CONTROLLER_IP"; then
CONTROLLER_IP=192.168.30.129
fi
COMPUTE_IP=${CHECK_COMPUTE_IP:-$(hosts_ip compute)}
ADMIN_PASSWORD=${CHECK_ADMIN_PASSWORD-admin}
NOVA_PASSWORD=${CHECK_NOVA_PASSWORD-nova}
if ! valid_ipv4 "$CONTROLLER_IP"; then error '控制节点 IP 参数或 hosts 映射无效。未评分。'; exit 2; fi
if [ -n "${CHECK_COMPUTE_IP:-}" ] && ! valid_ipv4 "$CHECK_COMPUTE_IP"; then error 'CHECK_COMPUTE_IP 无效。未评分。'; exit 2; fi
# 在子 Shell 内隔离已有 OS_* 变量;不改变调用者环境,不输出密码或 Token。
os_cli() (
for variable in ${!OS_@}; do unset "$variable"; done
export OS_AUTH_URL=http://controller:5000/v3 OS_IDENTITY_API_VERSION=3
export OS_AUTH_TYPE=password OS_USERNAME=admin OS_PASSWORD="$ADMIN_PASSWORD"
export OS_PROJECT_NAME=admin OS_USER_DOMAIN_NAME=Default OS_PROJECT_DOMAIN_NAME=Default
export OS_REGION_NAME=RegionOne
export NO_PROXY="localhost,127.0.0.1,controller,compute,$CONTROLLER_IP,$COMPUTE_IP"
export no_proxy="$NO_PROXY"
timeout 30 openstack "$@" 2>/dev/null
)
# JSON 按字段名读取,兼容不同版本客户端改变列表列顺序。
# Nova/OpenStack 客户端已依赖 Python;兼容 CentOS 7 的 Python 2.7 和 Python 3。
json_values() {
local interpreter
interpreter=$(command -v python3 || command -v python) || return 1
"$interpreter" -c '
import json, sys
rows = json.load(sys.stdin)
if not isinstance(rows, list):
sys.exit(1)
for row in rows:
values = [str(row[key]) for key in sys.argv[1:]]
if any(any(c in value for c in "\t\r\n") for value in values):
sys.exit(1)
sys.stdout.write("\t".join(values) + "\n")
' "$@" 2>/dev/null
}
section '主机与网络(20分)'
hostname_ok() { [ "$(hostname)" = controller ] && [ "$(tr -d '[:space:]' < /etc/hostname)" = controller ]; }
controller_mapping() {
[ "$(hosts_ip controller)" = "$CONTROLLER_IP" ] &&
ip -o -4 addr show scope global | awk -v ip="$CONTROLLER_IP" '{split($4,a,"/"); if(a[1]==ip)found=1} END{exit !found}'
}
compute_mapping() { valid_ipv4 "$COMPUTE_IP" && [ "$COMPUTE_IP" != "$CONTROLLER_IP" ] && [ "$(hosts_ip compute)" = "$COMPUTE_IP" ]; }
both_resolve() { resolved_as controller "$CONTROLLER_IP" && resolved_as compute "$COMPUTE_IP"; }
check 4 '主机名 controller' '主机名应为 controller' hostname_ok
check 4 'controller hosts 映射' 'controller hosts 应指向本机管理 IP' controller_mapping
check 4 'compute hosts 映射' 'compute hosts 映射缺失或冲突' compute_mapping
check 4 '主机名解析' '主机名解析与 hosts 不一致' both_resolve
check 4 'ping compute' 'ping compute 不通' timeout 8 ping -4 -c 2 -W 2 compute
section '管理员认证(10分)'
AUTH_OK=0
if os_cli token issue >/dev/null; then AUTH_OK=1; fi
check 10 '管理员认证' '管理员认证失败,检查 Keystone 和密码' test "$AUTH_OK" -eq 1
# 只认完整 Host=compute 的行,避免 controller 上克隆遗留的 nova-compute 冒充。
# 使用 JSON 字段名,避免依赖表格边框或列位置。
SERVICES_OK=0
SERVICES=''
if [ "$AUTH_OK" -eq 1 ]; then
if SERVICES=$(os_cli compute service list -f json | json_values Binary Host Status State); then SERVICES_OK=1; fi
fi
compute_registered() {
[ "$SERVICES_OK" -eq 1 ] && printf '%s\n' "$SERVICES" | awk '
$1=="nova-compute" && $2=="compute" {n++}
END{exit (n!=1)}'
}
compute_ready() {
[ "$SERVICES_OK" -eq 1 ] && printf '%s\n' "$SERVICES" | awk '
$1=="nova-compute" && $2=="compute" {n++; if($3=="enabled" && $4=="up")good++}
END{exit !(n==1 && good==1)}'
}
section 'compute 注册(10分)'
check 10 'compute 服务注册' 'compute 未唯一注册或查询失败' compute_registered
section 'compute 状态(10分)'
check 10 'nova-compute enabled/up' 'nova-compute 未处于 enabled/up' compute_ready
section '服务状态(15分)'
control_component() {
[ "$SERVICES_OK" -eq 1 ] && printf '%s\n' "$SERVICES" | awk -v binary="$1" '
$1==binary && $2=="controller" {n++; if($3=="enabled" && $4=="up")good++}
END{exit !(n>=1 && n==good)}'
}
all_services_up() {
[ "$SERVICES_OK" -eq 1 ] && printf '%s\n' "$SERVICES" | awk '
NF {n++; if(NF!=4 || $3!="enabled" || $4!="up")bad=1}
END{exit !(n>0 && !bad)}'
}
check 5 'nova-scheduler enabled/up' 'nova-scheduler 缺失或未处于 enabled/up' control_component nova-scheduler
check 5 'nova-conductor enabled/up' 'nova-conductor 缺失或未处于 enabled/up' control_component nova-conductor
check 5 '全部计算服务 enabled/up' '服务列表为空、查询失败或有异常服务' all_services_up
section 'Cell 映射(15分)'
# discover_hosts 会写数据库,评分时仅查询 host_mappings,避免替学生完成实验。
cell_mapping() {
local result
result=$(MYSQL_PWD="$NOVA_PASSWORD" timeout 15 mysql --connect-timeout=5 --protocol=TCP \
-h controller -u nova -N -B nova_api -e \
"SELECT c.uuid FROM host_mappings h JOIN cell_mappings c ON c.id=h.cell_id WHERE h.host='compute';" 2>/dev/null) || return 1
printf '%s\n' "$result" | awk '
/^[0-9a-fA-F-]+$/ && length($0)==36 && $0!="00000000-0000-0000-0000-000000000000" {good++}
END{exit !(NR==1 && good==1)}'
}
check 15 'compute Cell 映射' 'Cell 映射异常,检查数据库和 discover_hosts' cell_mapping
section '服务目录(10分)'
CATALOG_OK=0
if [ "$AUTH_OK" -eq 1 ] && os_cli catalog list >/dev/null; then CATALOG_OK=1; fi
catalog_endpoints() {
local type=$1 port=$2 endpoints
[ "$CATALOG_OK" -eq 1 ] || return 1
endpoints=$(os_cli endpoint list --service "$type" -f json | json_values Region Interface URL Enabled) || return 1
printf '%s\n' "$endpoints" | awk -v port="$port" -v ip="$CONTROLLER_IP" '
$1=="RegionOne" && ($4=="True" || $4=="true") {
url=$3; sub(/\/$/,"",url)
expected="http://controller:"port; other="http://"ip":"port
if(port==8774){expected=expected"/v2.1"; other=other"/v2.1"}
if(url==expected || url==other) seen[$2]=1
}
END{exit !(seen["public"] && seen["internal"] && seen["admin"])}'
}
check 5 'compute 服务目录与端点' 'compute 服务目录或端点异常' catalog_endpoints compute 8774
check 5 'placement 服务目录与端点' 'placement 服务目录或端点异常' catalog_endpoints placement 8778
section '部署检查(10分)'
# Rocky 返回码:0=全部成功,1=警告,2=失败;仅 0 得分,命令缺失/超时也失败。
check 10 'nova-status upgrade check' 'nova-status 检查未全部成功' timeout 40 su -s /bin/sh -c 'nova-status upgrade check' nova
finish