【Ovirt 笔记】engine-setup 的创建表空间过程的分析与整理

文前说明

作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。

本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。

分析整理的版本为 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.sqlinsert_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 函数调用中可以执行。

你可能感兴趣的:(【Ovirt 笔记】engine-setup 的创建表空间过程的分析与整理)