Files

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)