Files
OpenStackScripts/3.4.sh
T

401 lines
16 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
# OpenStack 控制节点(CentOS 7.9 / Rocky)Nova 计算服务部署检测
# 用法:以 root 执行 bash 3.4.sh。仅检查,不安装、同步数据库或重启服务。
# 默认实验密码:数据库 root=123456,管理员 admin=admin。
# 可通过 CHECK_DB_ROOT_PASSWORD、CHECK_ADMIN_PASSWORD 覆盖以上密码。
# 退出码:0=本次检查项全部通过,1=有失败。
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
set -o pipefail
# 学生可见输出:每组一行,失败时列出原因;仅统计实际检查结果。
RED='\033[0;31m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
NC='\033[0m'
PASS_CNT=0
FAIL_CNT=0
SECTION_TITLE=''
SECTION_PASS=0
SECTION_FAIL=0
SECTION_DETAILS=()
pass() { PASS_CNT=$((PASS_CNT+1)); }
fail() {
FAIL_CNT=$((FAIL_CNT+1))
if [ -n "$SECTION_TITLE" ]; then
SECTION_DETAILS+=("$1")
else
printf '%b[✘ 失败] %s%b\n' "$RED" "$1" "$NC"
fi
}
section_summary() {
[ -n "$SECTION_TITLE" ] || return 0
local passed=$((PASS_CNT-SECTION_PASS)) failed=$((FAIL_CNT-SECTION_FAIL)) message
# 没有实际检查结果的分组不显示,也不记为通过。
if [ "$failed" -gt 0 ]; then
printf '%b[✘ 失败] %s:通过 %s,失败 %s%b\n' "$RED" "$SECTION_TITLE" "$passed" "$failed" "$NC"
for message in "${SECTION_DETAILS[@]}"; do
printf '%b · %s%b\n' "$RED" "$message" "$NC"
done
elif [ "$passed" -gt 0 ]; then
printf '%b[✔ 通过] %s(%s 项)%b\n' "$GREEN" "$SECTION_TITLE" "$passed" "$NC"
fi
}
info() {
section_summary
SECTION_TITLE=$1
SECTION_PASS=$PASS_CNT
SECTION_FAIL=$FAIL_CNT
SECTION_DETAILS=()
}
finish() {
section_summary
printf '\n检查完毕:通过 %b%s%b 项,失败 %b%s%b 项。\n' \
"$GREEN" "$PASS_CNT" "$NC" "$RED" "$FAIL_CNT" "$NC"
if [ "$FAIL_CNT" -gt 0 ]; then
printf '%b请按失败提示修正后重试。%b\n' "$RED" "$NC"
exit 1
fi
printf '%b本次检查项全部通过。%b\n' "$GREEN" "$NC"
exit 0
}
printf '%bOpenStack Nova 控制节点自测%b\n' "$CYAN" "$NC"
if [ "$EUID" -ne 0 ]; then
fail '请在控制节点上使用 root 权限执行此脚本。'
exit 1
fi
CONF=/etc/nova/nova.conf
APACHE_CONF=/etc/httpd/conf.d/00-nova-placement-api.conf
DB_ROOT_PASSWORD=${CHECK_DB_ROOT_PASSWORD-123456}
ADMIN_PASSWORD=${CHECK_ADMIN_PASSWORD-admin}
# 远程查询均设置超时,避免服务不可达时一直等待。
for cmd in timeout mysql openstack rpm ip systemctl httpd nova-manage curl; do
if ! command -v "$cmd" >/dev/null 2>&1; then
fail "缺少命令 ${cmd},请安装对应软件包"
fi
done
[ "$FAIL_CNT" -eq 0 ] || finish
db_root() {
MYSQL_PWD="$DB_ROOT_PASSWORD" timeout 15 mysql --connect-timeout=5 \
-uroot -N -B -e "$1" 2>/dev/null
}
db_query() {
local database=$1 user=nova password=nova
[ "$database" != placement ] || { user=placement; password=placement; }
MYSQL_PWD="$password" timeout 15 mysql --connect-timeout=5 --protocol=TCP \
-h controller -u "$user" -N -B "$database" -e "$2" 2>/dev/null
}
# 隔离调用者旧的 OS_TOKEN、OS_CLOUD、OS_*_ID 等变量,避免它们覆盖本次凭证。
# 与参考脚本一样使用实验管理员凭证,不执行凭证文件里的任意命令。
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 NO_PROXY=localhost,127.0.0.1,controller no_proxy=localhost,127.0.0.1,controller
timeout 30 openstack "$@" 2>/dev/null
)
# 按节读取最后一个有效值,避免注释、其他节中的同名键造成误判。
ini_value() {
awk -v section="$1" -v key="$2" '
/^[[: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 }
' "$CONF" 2>/dev/null
}
check_config() {
local section=$1 key=$2 expected=$3 actual
actual=$(ini_value "$section" "$key")
case "$key" in
enabled|use_neutron|project_domain_name|user_domain_name)
actual=$(printf '%s' "$actual" | tr '[:upper:]' '[:lower:]')
expected=$(printf '%s' "$expected" | tr '[:upper:]' '[:lower:]');;
enabled_apis) actual=$(printf '%s' "$actual" | tr -d '[:space:]');;
esac
if [ "$actual" = "$expected" ]; then
pass "[$section] $key 配置正确"
else
# 不回显实际值,避免泄露连接串或口令。
fail "[$section] $key 缺失或与手册不符,请检查 $CONF"
fi
}
info '数据库与授权'
ROOT_OK=0
if db_root 'SELECT 1;' >/dev/null; then
ROOT_OK=1
pass '数据库 root 可登录'
else
fail '数据库 root 登录失败,请检查 MariaDB、密码(默认 123456)或 CHECK_DB_ROOT_PASSWORD'
fi
for database in nova_api nova nova_cell0 placement; do
if [ "$ROOT_OK" -eq 1 ]; then
if [ "$(db_root "SELECT COUNT(*) FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='$database';")" = 1 ]; then
pass "$database 数据库已创建"
else
fail "$database 数据库不存在或查询失败"
fi
user=nova
[ "$database" != placement ] || user=placement
grants=$(db_root "SHOW GRANTS FOR '$user'@'%';")
if printf '%s\n' "$grants" | tr -d '\140' | grep -Fq "GRANT ALL PRIVILEGES ON $database.* TO "; then
pass "'$user'@'%' 已获得 $database 的所有权限"
else
fail "'$user'@'%' 缺少 $database 的 ALL PRIVILEGES 授权,或授权查询失败"
fi
fi
if db_query "$database" 'SELECT 1;' >/dev/null; then
pass "使用手册服务账号经 controller 的 TCP 连接可访问 $database"
else
fail "无法访问 ${database},请检查建库、账号密码、授权及 controller 解析"
fi
done
info '服务凭证与端点'
AUTH_OK=0
if os_cli token issue >/dev/null; then
AUTH_OK=1
pass '管理员凭证可获取 Token'
else
fail '管理员认证失败,请检查 Keystone 服务及 admin 密码'
fi
check_identity() {
local user=$1 type=$2 url=$3
local user_id service_id service_type roles endpoints interface
if [ "$AUTH_OK" -ne 1 ]; then
return
fi
user_id=$(os_cli user show --domain default "$user" -f value -c id)
if [ -n "$user_id" ]; then
pass "default 域内的 $user 用户已创建"
# 用实际服务密码签发 service 项目的令牌,同时确认账号可用。
if os_cli --os-username "$user" --os-password "$user" --os-project-name service token issue >/dev/null; then
pass "$user 可使用手册密码在 service 项目完成认证"
else
fail "$user 认证失败,请检查密码、启用状态及 service 项目角色"
fi
if roles=$(os_cli role assignment list --user "$user_id" --project service --role admin -f value) && [ -n "$roles" ]; then
pass "$user 已绑定 service 项目的 admin 角色"
else
fail "$user 未绑定 service 项目的 admin 角色,或查询失败"
fi
else
fail "未找到 default 域的 $user 用户,或查询失败"
fi
service_id=$(os_cli service show "$user" -f value -c id)
service_type=$(os_cli service show "$user" -f value -c type)
if [ -n "$service_id" ] && [ "$service_type" = "$type" ]; then
pass "$user 服务实体存在,类型为 $type"
else
fail "$user 服务实体不存在、重名或类型不是 $type"
return
fi
for interface in public internal admin; do
if endpoints=$(os_cli endpoint list --service "$service_id" --region RegionOne \
--interface "$interface" -f value -c Enabled -c URL) && \
printf '%s\n' "$endpoints" | awk -v url="$url" '
NF { count++; sub(/\/$/, "", $2)
if (NF == 2 && tolower($1) == "true" && $2 == url) valid++ }
END { exit !(count == 1 && valid == 1) }
'; then
pass "$user 的 RegionOne/$interface 端点为 $url"
else
fail "$user 的 RegionOne/$interface 端点缺失、重复、未启用或 URL 错误,应为 $url"
fi
done
}
check_identity nova compute http://controller:8774/v2.1
check_identity placement placement http://controller:8778
info '软件包与配置'
for package in openstack-nova-api openstack-nova-conductor openstack-nova-novncproxy \
openstack-nova-scheduler openstack-nova-placement-api; do
if rpm -q "$package" >/dev/null 2>&1; then
pass "$package 已安装"
else
fail "$package 未安装"
fi
done
if [ -r "$CONF" ]; then
while IFS='|' read -r section key expected; do
check_config "$section" "$key" "$expected"
done <<'CONFIG'
DEFAULT|use_neutron|true
DEFAULT|firewall_driver|nova.virt.firewall.NoopFirewallDriver
DEFAULT|enabled_apis|osapi_compute,metadata
DEFAULT|transport_url|rabbit://openstack:openstack@controller
api_database|connection|mysql+pymysql://nova:nova@controller/nova_api
database|connection|mysql+pymysql://nova:nova@controller/nova
placement_database|connection|mysql+pymysql://placement:placement@controller/placement
api|auth_strategy|keystone
keystone_authtoken|auth_url|http://controller:5000/v3
keystone_authtoken|memcached_servers|controller:11211
keystone_authtoken|auth_type|password
keystone_authtoken|project_domain_name|Default
keystone_authtoken|user_domain_name|Default
keystone_authtoken|project_name|service
keystone_authtoken|username|nova
keystone_authtoken|password|nova
vnc|enabled|true
glance|api_servers|http://controller:9292
oslo_concurrency|lock_path|/var/lib/nova/tmp
placement|region_name|RegionOne
placement|project_domain_name|Default
placement|project_name|service
placement|auth_type|password
placement|user_domain_name|Default
placement|auth_url|http://controller:5000/v3
placement|username|placement
placement|password|placement
CONFIG
my_ip=$(ini_value DEFAULT my_ip)
if [ -n "$my_ip" ] && ip -o -4 addr show scope global 2>/dev/null | \
awk '{split($4,a,"/"); print a[1]}' | grep -Fxq "$my_ip"; then
pass '[DEFAULT] my_ip 是本机已配置的 IPv4 地址'
else
fail '[DEFAULT] my_ip 为空或不是本机 IPv4 地址,应填写自己的控制节点管理 IP'
fi
for key in server_listen server_proxyclient_address; do
value=$(ini_value vnc "$key")
if [ -n "$my_ip" ] && { [ "$value" = '$my_ip' ] || [ "$value" = "\${my_ip}" ] || [ "$value" = "$my_ip" ]; }; then
pass "[vnc] $key 引用 my_ip 或使用相同地址"
else
fail "[vnc] $key 应引用 \$my_ip 或填写相同的管理 IP"
fi
done
else
fail "$CONF 不存在或不可读"
fi
# 仅在指定 Directory 块内检查,注释及其他目录的授权不能算作通过。
if [ -r "$APACHE_CONF" ] && awk '
/^[[:space:]]*#/ { next }
{ line=tolower($0); gsub(/^[[:space:]]+|[[:space:]]+$/,"",line) }
line ~ /^<directory[[:space:]]+"?\/usr\/bin"?[[:space:]]*>$/ { inside=1; req=ord=allow=0; next }
inside && line == "require all granted" { req=1 }
inside && line == "order allow,deny" { ord=1 }
inside && line == "allow from all" { allow=1 }
inside && line == "</directory>" { if(req && ord && allow) found=1; inside=0 }
END { exit !found }
' "$APACHE_CONF"; then
pass '/usr/bin 的 Directory 块包含手册要求的访问授权指令'
else
fail "$APACHE_CONF 缺少手册中的 /usr/bin Directory 授权配置"
fi
if timeout 15 httpd -t >/dev/null 2>&1; then
pass 'Apache 配置语法检查通过'
else
fail 'Apache 配置语法检查失败,请执行 httpd -t 查看具体错误'
fi
if systemctl is-active --quiet httpd 2>/dev/null; then
pass 'httpd 正在运行'
else
fail 'httpd 未运行,请检查配置并按手册重启'
fi
http_code=$(curl --noproxy '*' -s -o /dev/null -w '%{http_code}' \
--connect-timeout 5 --max-time 10 http://controller:8778/ 2>/dev/null)
case "$http_code" in
200) pass 'Placement API 根路径返回 HTTP 200';;
401) : ;; # 认证要求不能证明接口功能正常,此项不计分。
*) fail "Placement API 返回 HTTP ${http_code:-000},请检查授权配置、httpd 日志及 8778 端口";;
esac
info '数据库同步与 Cells'
check_tables() {
local database=$1 tables table missing=''
shift
if tables=$(db_query "$database" 'SHOW TABLES;'); then
for table in "$@"; do
if ! printf '%s\n' "$tables" | grep -Fxq "$table"; then missing="$missing $table"; fi
done
if [ -z "$missing" ]; then
pass "$database 已生成关键数据表"
else
fail "$database 缺少表:${missing},请检查数据库同步"
fi
else
fail "$database 数据表查询失败,请检查连接和权限"
fi
}
check_tables nova_api cell_mappings instance_mappings
check_tables placement resource_providers inventories allocations
check_tables nova_cell0 instances instance_extra
check_tables nova instances compute_nodes services
# 查询数据库中的实际映射;不执行 map_cell0/create_cell/db sync 等写操作。
if cells=$(db_query nova_api 'SELECT name,uuid,transport_url,database_connection FROM cell_mappings;'); then
for cell in cell0 cell1; do
if [ "$cell" = cell0 ]; then
expected_db=mysql+pymysql://nova:nova@controller/nova_cell0
expected_transport=none:///
else
expected_db=mysql+pymysql://nova:nova@controller/nova
expected_transport=$(ini_value DEFAULT transport_url)
fi
if printf '%s\n' "$cells" | awk -F '\t' -v name="$cell" -v db="$expected_db" -v transport="$expected_transport" '
$1 == name { count++; if($4 == db && $3 == transport && $2 != "" &&
(name != "cell0" || $2 == "00000000-0000-0000-0000-000000000000")) valid++ }
END { exit !(count == 1 && valid == 1) }
'; then
pass "$cell 唯一存在,数据库及消息队列映射正确"
else
fail "$cell 缺失、重复或映射错误,请检查 nova-manage cell_v2 list_cells"
fi
done
else
fail 'cell_mappings 查询失败,请检查数据库权限和同步结果'
fi
if timeout 30 su -s /bin/sh -c 'nova-manage cell_v2 list_cells' nova >/dev/null 2>&1; then
pass 'nova 用户可成功执行 nova-manage cell_v2 list_cells'
else
fail 'nova 用户执行 list_cells 失败,请检查 nova.conf、文件权限及数据库连接'
fi
info '服务运行与自启'
for service in openstack-nova-api openstack-nova-scheduler openstack-nova-conductor openstack-nova-novncproxy; do
if systemctl is-active --quiet "$service" 2>/dev/null; then
pass "$service 正在运行"
else
fail "$service 未正常运行"
fi
if [ "$(systemctl is-enabled "$service" 2>/dev/null)" = enabled ]; then
pass "$service 已设置开机自启"
else
fail "$service 未设置持久的开机自启"
fi
done
info '结果自测:Compute API'
if [ "$AUTH_OK" -eq 1 ]; then
if os_cli compute service list >/dev/null; then
pass '管理员可查询计算服务列表'
else
fail '计算服务列表查询失败,请检查 API、端点和认证配置'
fi
fi
finish