文前说明
作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
分析整理的版本为 Ovirt 3.4.5 版本。
1. 实现原理分析
- 执行逻辑包含在 create_schema.sh 中。
设置数据库环境变量
#setting defaults
set_defaults
#common.sh
set_defaults() {
ME=$(basename $0)
SERVERNAME="localhost"
PORT="5432"
DATABASE="engine"
USERNAME="engine"
VERBOSE=false
LOGFILE="$ME.log"
if [ -n "${ENGINE_PGPASS}" ]; then
export PGPASSFILE="${ENGINE_PGPASS}"
unset PGPASSWORD
else
export PGPASSFILE="/etc/ovirt-engine/.pgpass"
if [ ! -r "${PGPASSFILE}" ]; then
export PGPASSFILE="${HOME}/.pgpass"
fi
fi
}
设置数据库最小错误级别等
createlang -w --host=${SERVERNAME} --port=${PORT} --dbname=${DATABASE} --echo --username=${USERNAME} plpgsql >& /dev/null
#set database min error level
CMD="ALTER DATABASE \"${DATABASE}\" SET client_min_messages=ERROR;"
execute_command "${CMD}" ${DATABASE} ${SERVERNAME} ${PORT}> /dev/null
创建数据库表
- 执行 create_tables.sql。
printf "Creating tables...\n"
execute_file "create_tables.sql" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
创建数据库函数
- 执行 create_functions.sql。
printf "Creating functions...\n"
drop_old_uuid_functions
execute_file "create_functions.sql" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
创建数据库公用函数
- 执行 common_sp.sql。
printf "Creating common functions...\n"
execute_file "common_sp.sql" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
插入初始化数据
- 执行 insert_data.sql 和 insert_predefined_roles.sql
- insert_predefined_roles 中包含了 roles、roles_groups、permissions 等角色权限表数据的初始化。
#inserting initial data
insert_initial_data
#dbcustomfunction.sh
insert_initial_data() {
printf "Inserting data ...\n"
execute_file "insert_data.sql" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
printf "Inserting pre-defined roles ...\n"
execute_file "insert_predefined_roles.sql" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
}
删除清除和安装过程中生成的用于校验的 md5 文件
#remove checksum file in clean install in order to run views/sp creation
rm -f .${DATABASE}.scripts.md5 >& /dev/null
1.1 运行升级脚本
- 执行逻辑包含在 run_upgrade_files 方法中。
# Running upgrade scripts
printf "Running upgrade scripts...\n"
run_upgrade_files
设置升级脚本版本的初始信息
set_version
#dbfunction.sh
set_version() {
execute_file upgrade/03_02_0000_set_version.sql ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
if [ -n "${VERSION}" ]; then
CMD="update schema_version set current=true where version=trim('${VERSION}');"
execute_command "${CMD}" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
fi
}
- schema_version 表由 create_tables.sql 创建。
- schema_version 表中第一条版本信息,由 insert_data.sql 初始化生成,版本号为 03020000。
- 由 03_02_0000_set_version.sql 中指定升级起始版本为 03020000。
升级条件验证
- 查看 upgrade 目录中用于数据库升级的 .sql 脚本文件是否大于 0 个(一个 .sql 脚本对应一个升级脚本版本)。
res=$(find upgrade/ -name "*" | grep -E ".sql|.sh" | wc -l)
- 验证升级脚本版本的唯一性。
validate_version_uniqueness
#dbfunction.sh
validate_version_uniqueness() {
prev=""
files=$(get_files "upgrade" 1)
for file in $(ls -1 $files) ; do
ver="${file:8:2}${file:11:2}${file:14:4}"
if [ "$ver" = "$prev" ]; then
echo "Operation aborted, found duplicate version : $ver"
exit 1
fi
prev=$ver
done
}
- NOMD5 变量在 set_version 方法中初始化为 false。
- 生成临时的 MD5 值与默认的 MD5 值做对比。
- 检查视图和函数的 sql 文件的 MD5 是否有变化。
if [ "${NOMD5}" = "false" ]; then
is_view_or_sp_changed
fi
#dbfunction.sh
is_view_or_sp_changed() {
files=$(get_files "upgrade" 3)
md5sum_file="${MD5DIR}/.${DATABASE}.scripts.md5"
md5sum_tmp_file="${md5sum_file}.tmp"
md5sum $files create_*views.sql *_sp.sql > ${md5sum_tmp_file}
diff -s -q "${md5sum_file}" "${md5sum_tmp_file}" >& /dev/null
result=$?
# 0 - identical , 1 - differ , 2 - error
if [ $result -eq 0 ] ; then
rm -f "${md5sum_tmp_file}"
else
# there is a diff or md5 file does not exist
mv -f "${md5sum_tmp_file}" "${md5sum_file}"
fi
return $result
}
- 升级脚本检测到配置、视图或存储过程中有更改。
- 保存数据库对象的自定义用户权限。
permissions="$(get_custom_user_permissions)"
#dbfuncation.sh
get_custom_user_permissions() {
pg_dump -w -s --host="${SERVVERNAME}" --port="${PORT}" --username="${USERNAME}" "${DATABASE}" | \
sed -n -e '/^grant/Ip' | sed -e "/to \(public\|postgres\)\|${USERNAME};/Id"
}
运行升级前处理
- <1.1.1> 中说明
获取当前升级脚本版本
- 从 schema_version 表中获取 current 为 true 的版本信息。
current=$(get_current_version)
# we should remove leading blank (from select result) and zero in order not to treat number as octal
last="${current:2:7}"
#dbfuncation.sh
get_current_version() {
echo "select version from schema_version where current = true order by id LIMIT 1;" |
psql -w -U ${USERNAME} --pset=tuples_only=on ${DATABASE} -h ${SERVERNAME} -p ${PORT}
}
获取需要升级的脚本
- 获取需要升级的脚本进行循环处理。
files=$(get_files "upgrade" 1)
for file in $(ls -1 $files); do
...
done
- 具体实现在 <1.1.2> 中说明。
设置升级后的最终当前版本
set_last_version
#dbfunction
set_last_version() {
id=$(get_last_installed_id)
CMD="update schema_version set current=(id=$id);"
execute_command "${CMD}" ${DATABASE} ${SERVERNAME} ${PORT}> /dev/null
}
get_last_installed_id() {
echo "select max(id) from schema_version where state in ('INSTALLED','SKIPPED')" | psql -w -U ${USERNAME} --pset=tuples_only=on ${DATABASE} -h ${SERVERNAME} -p ${PORT}
}
删除视图和存储过程恢复
- 对在 <1.1.1> 中为了支持升级而删除的视图和存储过程进行恢复。
- 具体实现在 <1.1.3> 中说明。
1.1.1 运行升级前处理
- 执行逻辑包含在 run_pre_upgrade 方法中。
删除所有的视图
- 在升级或刷新操作之前删除视图。
- 首先执行公共存储过程(使新添加的函数有效)
drop_views
#dbfuncation.sh
drop_views() {
# common stored procedures are executed first (for new added functions to be valid)
execute_file "common_sp.sql" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
CMD="select * from generate_drop_all_views_syntax();"
execute_command "$CMD" ${DATABASE} ${SERVERNAME} ${PORT} > drop_all_views.sql
execute_file "drop_all_views.sql" ${DATABASE} ${SERVERNAME} ${PORT}> /dev/null
\rm -f drop_all_views.sql
}
删除所有存储过程
- 在升级或刷新操作之前删除存储过程。
- 重新创建函数。(使新添加的函数有效)
drop_sps
#dbfunction.sh
#drops sps before upgrade or refresh operations
drop_sps() {
# common stored procedures are executed first (for new added functions to be valid)
execute_file "common_sp.sql" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
local drop_all_functions=$(mktemp)
CMD="select * from generate_drop_all_functions_syntax();"
execute_command "$CMD" ${DATABASE} ${SERVERNAME} ${PORT} > "${drop_all_functions}"
execute_file "${drop_all_functions}" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
\rm -f "${drop_all_functions}"
drop_old_uuid_functions
# recreate generic functions
execute_file "create_functions.sql" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
}
执行公共函数
- 使新添加的函数有效。
install_common_func
#dbfunction.sh
install_common_func() {
# common stored procedures are executed first (for new added functions to be valid)
execute_file "common_sp.sql" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
}
更新序列号
update_sequence_numbers
#dbcustomfunction.sh
update_sequence_numbers() {
execute_file "update_sequence_numbers.sql" ${DATABASE} ${SERVERNAME} ${PORT}> /dev/null
}
运行升级前处理脚本
- 执行目录 upgrade/pre_upgrade 或在 upgrade/pre-upgrade 中所有脚本。
execute_commands_in_dir 'pre_upgrade' 'pre-upgrade'
#dbfunction.sh
execute_commands_in_dir() {
if [ -d upgrade/$1 ]; then
files=$(get_files "upgrade/${1}" 1)
for execFile in $(ls $files | sort); do
run_file $execFile
done
fi
}
快照物化视图支持
- 将快照物化视图支持添加到 Postgres。
install_materialized_views_func
#dbcustomfunction.sh
install_materialized_views_func() {
execute_file "materialized_views_sp.sql" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
}
- 删除具体化视图以支持升级中的视图更改,这些视图在升级后步骤 <1.1.3> 中会恢复。
- DropAllMaterializedViews 函数在 materialized_views_sp.sql 中由定义。
drop_materialized_views
#dbcustomfunction.sh
drop_materialized_views() {
echo "Dropping materialized views..."
CMD="select DropAllMaterializedViews();"
execute_command "${CMD}" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
}
清除任务元数据
- 如果参数中设置了清除任务元数据,则删除 async_tasks(异步任务) 和 business_entity_snapshot(补偿机制) 表中数据并释放空间。
if [[ -n "${CLEAN_TASKS}" ]]; then
echo "Cleaning tasks metadata..."
delete_async_tasks_and_compensation_data
fi
#dbfunction.sh
delete_async_tasks_and_compensation_data() {
execute_file "delete_async_tasks_and_compensation_data.sql" ${DATABASE} ${SERVERNAME} ${PORT}> /dev/null
}
1.1.2 升级脚本处理
获取升级该脚本的起始时间
before=$(get_db_time)
#dbfunction.sh
get_db_time(){
echo "select now();" | psql -w -U ${USERNAME} --pset=tuples_only=on ${DATABASE} -h ${SERVERNAME} -p ${PORT}
}
获取升级脚本文件的 MD5 值
checksum=$(md5sum $file | cut -d " " -f1)
根据脚本文件名称获得对应的版本
- 当该版本大于当前版本时,说明未执行该升级脚本,需要执行升级。
# upgrade/dd_dd_dddd* => dddddddd
ver="${file:8:2}${file:11:2}${file:14:4}"
if [ "$ver" -gt "$current" ] ; then
...
fi
去除版本中的前后导零
- 为了不把数字当作八进制,去掉前后导零。
xver="${ver:1:7}"
# taking major revision , i.e 03010000=>301
xverMajor="${xver:0:3}"
lastMajor="${last:0:3}"
检查版本之间的差距
- 主版本相等,次要版本大于 10。
- 设置之前的最大版本为升级的最终版本。
- 规定了升级脚本的版本间隔不能大于 10。
if [ ${xverMajor} -eq ${lastMajor} ]; then
if [ $(($xver - $last)) -gt 10 ]; then
set_last_version
echo "Illegal script version number ${ver},version should be in max 10 gap from last installed version: 0${last}"
echo "Please fix numbering to interval 0$(( $last + 1)) to 0$(( $last + 10)) and run the upgrade script."
exit 1
fi
fi
检查该脚本是否已经安装
- 这里涉及到一个数据库表 schema_version,用于记录升级的所有脚本信息。
- 此表在 create_tables.sql 中被创建。
字段 | 说明 |
---|---|
id | 升级版本 ID。 |
version | 升级版本。 |
script | 脚本全路径。 |
checksum | 脚本文件 MD5 值。 |
installed_by | 安装影响的数据库名称(engine)。 |
started_at | 执行脚本的开始时间。 |
ended_at | 执行脚本的结束时间。 |
state | 版本状态(INSTALLED 已安装,SKIPPED 跳过)。 |
current | 是否为当前版本。 |
comment | 备注说明(状态为 SKIPPED 的版本,写入 Installed already by xxx 版本) |
- 根据升级脚本的 MD5 值,查询是否之前已经安装过(以其他的版本号安装)。
- 如果是则跳过,并且在数据库中记录该数据,设置状态为 SKIPPED。
# check if script was already installed with other version name.
installed_version=$(get_installed_version $checksum)
if [[ -n "${installed_version}" ]]; then
echo "Skipping upgrade script $file, already installed by ${installed_version}"
state="SKIPPED"
after=$(get_db_time)
last=$xver
comment="Installed already by ${installed_version}"
...
- 如果未安装过该脚本,但是此时又处于数据库其他升级过程中(或在还原),则强行执行升级前处理,步骤与 <1.1.1> 中一致。
if [ $updated -eq 0 ]; then
echo "Saving custom users permissions on database objects..."
permissions="$(get_custom_user_permissions)"
run_pre_upgrade
updated=1
fi
运行升级脚本
- 如果未安装过该脚本,又不是处于其他升级过程中,那么就执行该脚本。
run_required_scripts $file
run_file $file
#dbfunction.sh
run_required_scripts() {
local script=${1}
# check for helper functions that the script needs
# source scripts must be defined in the first lines of the script
while read line; do
expr=$(echo $line | cut -d " " -f1 |grep "\-\-#source")
if [[ -z "${expr}" ]] ; then
break;
else
sql=$(echo $line | cut -d " " -f2)
valid=$(echo $sql | grep "_sp.sql")
if [[ -z "${valid}" ]]; then
echo "invalid source file $sql in $file , source files must end with '_sp.sql'"
exit 1
fi
echo "Running helper functions from $sql for $file "
execute_file $sql ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
fi
done < "$script"
}
run_file() {
local execFile=${1}
if [ -x "${execFile}" ]; then
echo "Running upgrade shell script $execFile ..."
export DATABASE="${DATABASE}" SERVERNAME="${SERVERNAME}" PORT="${PORT}" USERNAME="${USERNAME}"
./$execFile
else
echo "Running upgrade sql script $execFile ..."
execute_file $execFile ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
fi
}
- 检查脚本如果需要助手函数,必须在脚本的第一行定义源脚本。
- 可以通过 --#source 关键字定义,如下,这样就可以直接使用 action_version_map_sp.sql 中定义的 fn_db_delete_version_map 函数了。
- 助手函数所在源脚本的名称必须 _sp.sql 结尾。
- 原理就是直接执行通过该关键字定义的源脚本。
--#source action_version_map_sp.sql
------------------------------------------------------------------------------------
-- Cleanup deprecated action version section
------------------------------------------------------------------------------------
select fn_db_delete_version_map('2.2','2.2');
执行完成
- 执行成功后,则设置状态为 INSTALLED,保存到数据中,如果失败则设置之前的最大版本为升级的最终版本。
code=$?
if [ $code -eq 0 ]; then
state="INSTALLED"
after=$(get_db_time)
last=$xver
comment=""
else
set_last_version
exit $code
fi
CMD="insert into schema_version(version,script,checksum,installed_by,started_at,ended_at,state,current,comment)
values (trim('$ver'),'$file','$checksum','${USERNAME}',
cast(trim('$before') as timestamp),cast(trim('$after') as timestamp),'$state',false,'$comment');"
psql -w -U ${USERNAME} --pset=tuples_only=on -h ${SERVERNAME} -p ${PORT} -c "${CMD}" ${DATABASE} > /dev/null
1.1.3 运行升级后处理
- 执行逻辑包含在 run_post_upgrade 方法中。
- 在运行升级后处理后,应用用户自定义权限(之前有做保存)。
# restore views & SPs if dropped
if [ $updated -eq 1 ]; then
run_post_upgrade
echo "Applying custom users permissions on database objects..."
execute_command "${permissions}" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
else
echo "database is up to date."
fi
刷新视图
- 执行 create_views.sql、create_dwh_views.sql 脚本文件。
#refreshes views
refresh_views() {
printf "Creating views...\n"
execute_file "create_views.sql" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
execute_file "create_dwh_views.sql" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
}
刷新存储过程
- 执行所有 sp.sql 结尾的存储过程脚本。
- 同时再执行 common_sp.sql 脚本。
#refreshes sps
refresh_sps() {
printf "Creating stored procedures...\n"
for sql in $(ls *sp.sql); do
printf "Creating stored procedures from $sql ...\n"
execute_file $sql ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
done
execute_file "common_sp.sql" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
}
运行升级后脚本
- 执行运行后处理中的所有脚本文件。
#Running post-upgrade scripts
execute_commands_in_dir 'post_upgrade' 'post-upgrade'
#dbfunction.sh
# Runs all the SQL scripts in directory upgrade/$1/
# The second argument is the label to use while notifying
# the user about the running of the script
execute_commands_in_dir() {
if [ -d upgrade/$1 ]; then
files=$(get_files "upgrade/${1}" 1)
for execFile in $(ls $files | sort); do
run_file $execFile
done
fi
}
恢复物化视图
- 恢复 <1.1.1> 中删除的自定义物化视图。
- 需要定义在 create_materialized_views.sql 文件中。
custom_materialized_views_file="upgrade/post_upgrade/custom/create_materialized_views.sql"
if [ -f ${custom_materialized_views_file} ]; then
echo "running custom materialized views from ${custom_materialized_views_file} ..."
psql -w -U ${USERNAME} --pset=tuples_only=on --set ON_ERROR_STOP=1 -h ${SERVERNAME} -p ${PORT} -f "${custom_materialized_views_file}" ${DATABASE} > /dev/null
if [ $? -ne 0 ] ; then
#drop all custom views
psql -w -U ${USERNAME} -h ${SERVERNAME} -p ${PORT} -c "select DropAllCustomMaterializedViews();" ${DATABASE} > /dev/null
echo "Illegal syntax in custom Materialized Views, Custom Materialized Views were dropped."
fi
fi
刷新物化视图
refresh_materialized_views
#dbcustomfunction.sh
refresh_materialized_views() {
echo "Refreshing materialized views..."
CMD="select RefreshAllMaterializedViews(true);"
execute_command "${CMD}" ${DATABASE} ${SERVERNAME} ${PORT} > /dev/null
}
1.1.4 小结
- 数据库表的修改或者数据的插入、修改、删除等操作可以单独创建升级脚本文件,按照 dd_dd_dddd_xxxxx.sql 的格式命名放在 dbscript/upgrade 文件夹中,在 run_upgrade_files 函数调用中可以执行。
- 增加的存储过程/函数可以以 xxxx_sp.sql 的格式命名放在 dbscript 文件夹中,在 run_post_upgrade 函数调用中可以执行。
- 对存储过程/函数的修改需要修改对应的 xxxx_sp.sql 文件,在 run_post_upgrade 函数调用中可以执行。
- 对视图的增加和修改都需要定义在 create_views.sql 中,run_post_upgrade 函数调用中可以执行。