349 lines
12 KiB
Python
349 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding:utf-8 -*-
|
|
#############################################################################
|
|
# Copyright (c) 2023 Huawei Technologies Co.,Ltd.
|
|
#
|
|
# openGauss is licensed under Mulan PSL v2.
|
|
# You can use this software according to the terms
|
|
# and conditions of the Mulan PSL v2.
|
|
# You may obtain a copy of Mulan PSL v2 at:
|
|
#
|
|
# http://license.coscl.org.cn/MulanPSL2
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OF ANY KIND,
|
|
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
# See the Mulan PSL v2 for more details.
|
|
# ----------------------------------------------------------------------------
|
|
# Description : gs_upgradechk is a utility to check meta data in gaussdb after upgrade.
|
|
#############################################################################
|
|
"""
|
|
标题: 报告总结
|
|
数据库信息:版本、节点名等
|
|
标准元数据地图:xxxxxxxxx
|
|
校验结果:通过 or 失败
|
|
内容摘要:
|
|
共校验xx个数据库。
|
|
累计xx个系统元数据表,其中xx通过,xx个失败。
|
|
校验规则共xx条,xx条通过,xx条失败。
|
|
Category一共xxx个,其中xx个完全通过,xx个存在失败内容
|
|
。。。
|
|
失败项摘要:
|
|
- 规则编号:1
|
|
数据库:postgres
|
|
校验规则: select ...
|
|
规则解读: 使用函数pg_get_ruledef校验重写规则的定义
|
|
规则结果:
|
|
行号 状态 内容
|
|
1 Failed 规则定义def:pg_catalog.gs_session_memory_detail(_RETURN) 缺失
|
|
2 ...
|
|
- 规则编号:8
|
|
数据库:postgres
|
|
校验规则: select ...
|
|
规则解读: ...
|
|
规则结果: ...
|
|
|
|
警告:
|
|
存在未分类的系统元数据表:
|
|
建议排查来源,并修改本工具,添加至分类列表中。
|
|
|
|
TIPS:详细校验原理与说明详见本工具的"README.md"。
|
|
|
|
详细内容:
|
|
数据库postgres
|
|
元数据系统表结构
|
|
共xx个系统表,校验项目xx条,通过xx条,失败xx条
|
|
- 规则编号:1
|
|
数据库:postgres
|
|
校验规则: select ...
|
|
规则解读: 使用函数pg_get_ruledef校验重写规则的定义
|
|
规则结果:
|
|
行号 状态 内容
|
|
1 Failed 规则定义def:pg_catalog.gs_session_memory_detail(_RETURN) 缺失
|
|
2 ...
|
|
|
|
分类内容
|
|
共xx个系统表,校验项目xx条,通过xx条,失败xx条
|
|
|
|
数据库对象
|
|
|
|
数据库user1db
|
|
元数据系统表结构
|
|
....
|
|
|
|
"""
|
|
|
|
from enum import Enum
|
|
from upgrade_checker.utils.param import ReportFormat, ReportMode
|
|
from upgrade_checker.log import logger
|
|
from upgrade_checker.rules.rule import StructRule, ContentRule, CommonRule, ConclusionState
|
|
from upgrade_checker.style.markdown import MarkDown
|
|
|
|
|
|
class ConclusionCardType(Enum):
|
|
SUMMARY = 0
|
|
STRUCTURE = 1
|
|
CONTENT = 2
|
|
COMMON = 3
|
|
|
|
|
|
class ConclusionCard(object):
|
|
error_num = 0
|
|
|
|
def __init__(self, id, conclusion, dbname=None, tbname=None, category=None):
|
|
self.id = id
|
|
self.dbname = dbname
|
|
self.tbname = tbname
|
|
self.category = category
|
|
|
|
self.conclusion = conclusion
|
|
self.sql = conclusion.rule.sql
|
|
self.sql_desc = conclusion.rule.sql_desc
|
|
self.results = conclusion.details
|
|
|
|
def serialize(self, style, cc_type):
|
|
res = ''
|
|
res += style.unordered_list([f'规则编号 {self.id}'])
|
|
|
|
if cc_type == ConclusionCardType.SUMMARY:
|
|
res += style.text('数据库:' + self.dbname)
|
|
elif cc_type == ConclusionCardType.STRUCTURE:
|
|
res += style.text('表名:' + self.conclusion.rule.rel_key)
|
|
elif cc_type == ConclusionCardType.CONTENT:
|
|
res += style.text('表名:' + self.conclusion.rule.rel_key)
|
|
res += style.text('类别:' + self.conclusion.rule.category.name)
|
|
elif cc_type == ConclusionCardType.CONTENT:
|
|
res += style.text('类别:' + self.conclusion.rule.category.name)
|
|
|
|
res += style.text('校验规则:' + self.sql)
|
|
res += style.text('规则解读:' + self.sql_desc)
|
|
res += style.text('规则结果:')
|
|
|
|
rows = []
|
|
for i, row in enumerate(self.results):
|
|
if cc_type == ConclusionCardType.SUMMARY:
|
|
if row['state'] == ConclusionState.SUCCESS:
|
|
continue
|
|
result_state = 'SUCCESS' if row['state'] == ConclusionState.SUCCESS else 'Falied'
|
|
rows.append((
|
|
i,
|
|
result_state,
|
|
row['summary']
|
|
))
|
|
|
|
res += style.table(["行号", "状态", "内容"], rows)
|
|
return res
|
|
|
|
|
|
class Report(object):
|
|
"""
|
|
报告
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.db_info = ''
|
|
self.vmap_info = ''
|
|
self.pci_suc_count = 0
|
|
self.pci_err_count = 0
|
|
self.rule_suc_count = 0
|
|
self.rule_err_count = 0
|
|
self.detail_suc_count = 0
|
|
self.detail_err_count = 0
|
|
self.err_rules_res = []
|
|
self.warnings = []
|
|
self.content = {} # { "dbname": [struct rules, content rules, common rules], ... }
|
|
|
|
def serialize(self, style):
|
|
rule_count = self.rule_suc_count + self.rule_err_count
|
|
detail_count = self.detail_suc_count + self.detail_err_count
|
|
verify_result = '成功' if self.rule_err_count == 0 else '失败'
|
|
db_summary = "共校验 %d 个数据库。" % len(self.content)
|
|
rul_summary = "校验规则累计执行%d条,%d条通过,%d条失败。" % (rule_count, self.rule_suc_count, self.rule_err_count)
|
|
detail_summary = "所有校验规则累计生成%d条内容小项,%d条通过,%d条失败。" % (detail_count, self.detail_suc_count, self.detail_err_count)
|
|
warning_summary = "警告信息%d条。" % len(self.warnings)
|
|
|
|
report = style.title(1, "校验报告")
|
|
report += style.title_paragraph("数据库信息", self.db_info)
|
|
report += style.title_paragraph("基准元数据地图", self.vmap_info)
|
|
report += style.title_paragraph("校验结果", verify_result)
|
|
report += style.emphasize("内容摘要:")
|
|
report += style.unordered_list([db_summary, rul_summary, detail_summary, warning_summary])
|
|
report += style.title(2, "失败项摘要:")
|
|
for conclusion_card in self.err_rules_res:
|
|
report += conclusion_card.serialize(style, ConclusionCardType.SUMMARY)
|
|
report += style.title(2, "警告:")
|
|
report += style.unordered_list(self.warnings)
|
|
report += style.title_paragraph("TIPS", "详细校验原理与说明参考本工具的《README.md》")
|
|
|
|
report += style.title(1, "详细数据")
|
|
for dbname, contents in self.content.items():
|
|
report += style.title(2, "数据库%s" % dbname)
|
|
report += style.title(3, "元数据系统表结构")
|
|
for conclusion_card in contents[0]:
|
|
report += conclusion_card.serialize(style, ConclusionCardType.STRUCTURE)
|
|
|
|
report += style.title(3, "元数据系统表内容")
|
|
for conclusion_card in contents[1]:
|
|
report += conclusion_card.serialize(style, ConclusionCardType.CONTENT)
|
|
|
|
report += style.title(3, "常规通用校验")
|
|
for conclusion_card in contents[2]:
|
|
report += conclusion_card.serialize(style, ConclusionCardType.COMMON)
|
|
|
|
return report
|
|
|
|
|
|
class StyleFactory(object):
|
|
"""
|
|
风格工厂
|
|
"""
|
|
@staticmethod
|
|
def produce(fmt):
|
|
if fmt == ReportFormat.MARKDOWN:
|
|
return MarkDown()
|
|
else:
|
|
return MarkDown()
|
|
|
|
|
|
class Reporter(object):
|
|
"""
|
|
报告生成器
|
|
"""
|
|
def __init__(self, file, fmt, granularity):
|
|
self._file_path = file
|
|
|
|
try:
|
|
self._file = open(file, 'a') # 结果文件
|
|
except FileNotFoundError:
|
|
logger.err('无法打开文件:' + file)
|
|
|
|
self._current_dbname = '' # 当前记录的数据库名称
|
|
self._report = Report() # 结果
|
|
self._style = StyleFactory.produce(fmt) # 风格
|
|
self._granularity = granularity # 粒度
|
|
|
|
def __del__(self):
|
|
if self._file is not None:
|
|
self._file.close()
|
|
|
|
def __str__(self):
|
|
return 'Reporter: granularity{0}, file({1})'.format(self._granularity, self._file_path)
|
|
|
|
def record_info(self, db_info='', vmap_info=''):
|
|
self._report.db_info = db_info
|
|
self._report.vmap_info = vmap_info
|
|
|
|
def _collect_statistic(self, old_errno, new_errno, dbname, conclusion):
|
|
"""
|
|
整理记录校验过程中的统计信息。
|
|
:param old_errno:
|
|
:param new_errno:
|
|
:param dbname:
|
|
:param conclusion:
|
|
:return:
|
|
"""
|
|
suc = True if old_errno == new_errno else False
|
|
if suc:
|
|
self._report.rule_suc_count += 1
|
|
else:
|
|
self._report.rule_err_count += 1
|
|
|
|
detail_err_count = new_errno - old_errno
|
|
detail_suc_count = len(conclusion.details) - detail_err_count
|
|
self._report.detail_suc_count += detail_suc_count
|
|
self._report.detail_err_count += detail_err_count
|
|
|
|
|
|
if self._granularity == ReportMode.DETAIL:
|
|
data = self._report.content.get(dbname)
|
|
assert data is not None
|
|
logger.debug('当前数据库{0}记录结论小项分别为{1},{2},{3}'.format(
|
|
dbname,
|
|
len(data[0]),
|
|
len(data[1]),
|
|
len(data[2]))
|
|
)
|
|
|
|
def _transform_conclusion(self, dbname, conclusion):
|
|
"""
|
|
|
|
:param dbname:
|
|
:param conclusion:
|
|
:return:
|
|
"""
|
|
row_num = 0
|
|
|
|
for row in conclusion.details:
|
|
row_num += 1
|
|
|
|
result_state = 'SUCCESS' if row['state'] == ConclusionState.SUCCESS else \
|
|
'ERROR(err-num-%d)' % self._current_errno
|
|
row_res_base = [
|
|
conclusion.rule.sql,
|
|
conclusion.rule.sql_desc,
|
|
row_num,
|
|
result_state,
|
|
row['summary']
|
|
]
|
|
if row['state'] == ConclusionState.FAILED:
|
|
self._report.err_rules_res.append([dbname] + row_res_base)
|
|
self._current_errno += 1
|
|
|
|
def record(self, dbname, conclusion):
|
|
"""
|
|
记录一条结论到report内。并更新统计数据
|
|
"""
|
|
if dbname != self._current_dbname:
|
|
self._report.content[dbname] = [[], [], []]
|
|
self._current_dbname = dbname
|
|
|
|
# statistic
|
|
if conclusion.all_success():
|
|
self._report.rule_suc_count += 1
|
|
else:
|
|
self._report.rule_err_count += 1
|
|
self._report.detail_suc_count += conclusion.suc_count
|
|
self._report.detail_err_count += conclusion.err_count
|
|
logger.debug('reporter 新增记录规则结论,结论成功状态{0}, 结论明细共计{1}条, 成功{2},失败{3}。'.format(
|
|
conclusion.all_success(),
|
|
len(conclusion.details),
|
|
conclusion.suc_count,
|
|
conclusion.err_count)
|
|
)
|
|
|
|
# record warnings
|
|
self._report.warnings += conclusion.warnings
|
|
|
|
# record details
|
|
if conclusion.all_success() and self._granularity == ReportMode.SUMMARY:
|
|
# if rule success and not need detail, just return
|
|
return
|
|
cc = ConclusionCard((self._report.rule_suc_count + self._report.rule_err_count), conclusion, dbname)
|
|
# generate summary
|
|
if not conclusion.all_success():
|
|
self._report.err_rules_res.append(cc)
|
|
# generate detail
|
|
if self._granularity != ReportMode.SUMMARY:
|
|
data = self._report.content.get(dbname)
|
|
assert data is not None
|
|
if isinstance(conclusion.rule, StructRule):
|
|
data[0].append(cc)
|
|
elif isinstance(conclusion.rule, ContentRule):
|
|
data[1].append(cc)
|
|
elif isinstance(conclusion.rule, CommonRule):
|
|
data[2].append(cc)
|
|
else:
|
|
assert False
|
|
|
|
def report(self):
|
|
"""
|
|
:return:
|
|
"""
|
|
logger.log('开始整理生成报告......')
|
|
if self._granularity == ReportMode.DETAIL:
|
|
logger.info('详细报告整理中,这可能需要较长时间......')
|
|
|
|
self._file.write(self._report.serialize(self._style))
|
|
logger.log('报告生成完成 %s。' % self._file_path)
|