From 3ded6b12a92bc9c340a3cedd010e08f916f61154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A0=E7=A3=8A?= Date: Thu, 11 Apr 2024 19:30:32 +0800 Subject: [PATCH 1/2] release 2.0.0 --- LICENSE | 90 +- README-CN.md | 10 +- README.md | 6 +- cmd.py | 692 ++++++++ common/base_rpc.py | 101 -- common/command.py | 309 ++-- common/config_helper.py | 89 +- common/constant.py | 3 +- handler/base_sql_handler.py => common/log.py | 19 +- common/logger.py | 80 - common/ob_connector.py | 11 +- {ocp => common/ocp}/__init__.py | 0 {ocp => common/ocp}/ocp_api.py | 0 {ocp => common/ocp}/ocp_task.py | 2 +- common/scene.py | 43 +- common/snowflake.py | 69 - common/ssh.py | 1080 +++++++++++++ common/status_rpc.py | 45 - common/tool.py | 1390 +++++++++++++++++ common/types.py | 427 +++++ utils/version_utils.py => common/version.py | 21 +- config.py | 255 +++ context.py | 152 ++ core.py | 353 +++++ dev_init.sh | 69 + docs/analyze_flt_trace.md | 35 +- docs/analyze_ob_log.md | 50 +- docs/check.md | 16 +- docs/gather_admin.md | 72 +- docs/gather_all.md | 44 +- docs/gather_awr.md | 49 +- docs/gather_ob_log.md | 44 +- docs/gather_obproxy_log.md | 45 +- docs/gather_sql_plan_monitor.md | 22 +- docs/gather_sysstat.md | 18 +- err.py | 93 ++ handler/analyzer/analyze_flt_trace.py | 162 +- handler/analyzer/analyze_log.py | 245 +-- handler/analyzer/log_parser/log_entry.py | 5 +- handler/analyzer/log_parser/tree.py | 6 +- handler/base_http_handler.py | 87 -- handler/base_shell_handler.py | 9 +- handler/checker/check_handler.py | 138 +- handler/checker/check_list.py | 26 +- handler/checker/check_report.py | 49 +- handler/checker/check_task.py | 41 +- handler/checker/result/result.py | 15 +- handler/checker/result/verify.py | 76 +- handler/checker/step/data_size.py | 72 + handler/checker/step/get_system_parameter.py | 35 +- handler/checker/step/sql.py | 29 +- handler/checker/step/ssh.py | 36 +- handler/checker/step/stepbase.py | 44 +- .../checker/tasks/obproxy_check_package.yaml | 0 .../observer/cluster/data_path_settings.yaml | 4 +- .../cluster/part_trans_action_max.yaml | 15 +- .../tasks/observer/system/parameter.yaml | 4 +- .../observer/system/ulimit_parameter.yaml | 4 +- .../checker/tasks/observer_check_package.yaml | 2 +- handler/gather/gather_awr.py | 144 +- handler/gather/gather_log.py | 354 +++-- handler/gather/gather_obadmin.py | 228 +-- handler/gather/gather_obproxy_log.py | 301 ++-- handler/gather/gather_obstack2.py | 193 +-- handler/gather/gather_perf.py | 154 +- handler/gather/gather_plan_monitor.py | 264 ++-- handler/gather/gather_scenes.py | 163 +- handler/gather/gather_sysstat.py | 164 +- handler/gather/scenes/base.py | 60 +- handler/gather/scenes/cpu_high.py | 58 +- handler/gather/scenes/list.py | 46 +- handler/gather/scenes/px_collect_log.py | 185 +++ handler/gather/scenes/register.py | 8 + handler/gather/scenes/sql_problem.py | 87 +- handler/gather/step/base.py | 64 +- handler/gather/step/sql.py | 25 +- handler/gather/step/ssh.py | 28 +- handler/gather/tasks/observer/compaction.yaml | 2 +- .../observer/delay_of_primary_and_backup.yaml | 2 +- handler/gather/tasks/observer/io.yaml | 2 +- handler/gather/tasks/observer/memory.yaml | 2 +- handler/rca/__init__.py | 2 +- {utils => handler/rca/plugins}/__init__.py | 5 +- handler/rca/plugins/gather.py | 140 ++ handler/rca/rca_handler.py | 294 +++- handler/rca/rca_list.py | 103 +- handler/rca/rca_scene/__init__.py | 26 - handler/rca/rca_scene/scene_base.py | 89 -- handler/rca/scene/ddl_disk_full_scene.py | 218 +++ .../disconnection_scene.py | 141 +- .../lock_conflict_scene.py | 82 +- .../{rca_scene => scene}/major_hold_scene.py | 126 +- init.sh | 15 +- init_obdiag_cmd.sh | 4 +- utils/password_util.py => main.py | 31 +- obdiag | 30 - obdiag_client.py | 561 ------- obdiag_main.py | 210 --- ocp/ocp_base.py | 40 - ocp/ocp_cluster.py | 105 -- ocp/ocp_host.py | 92 -- requirements3.txt | 4 + rpm/build.sh | 2 +- rpm/oceanbase-diagnostic-tool.spec | 21 +- stdio.py | 954 +++++++++++ telemetry/telemetry.py | 57 +- update/update.py | 81 +- utils/file_utils.py | 247 --- utils/network_utils.py | 45 - utils/parser_utils.py | 427 ----- utils/print_spin.py | 46 - utils/print_utils.py | 38 - utils/retry_utils.py | 47 - utils/shell_utils.py | 308 ---- utils/sql_utils.py | 60 - utils/string_utils.py | 95 -- utils/time_utils.py | 233 --- utils/utils.py | 220 --- utils/yaml_utils.py | 58 - 119 files changed, 8948 insertions(+), 5751 deletions(-) create mode 100644 cmd.py delete mode 100644 common/base_rpc.py rename handler/base_sql_handler.py => common/log.py (58%) delete mode 100644 common/logger.py rename {ocp => common/ocp}/__init__.py (100%) rename {ocp => common/ocp}/ocp_api.py (100%) rename {ocp => common/ocp}/ocp_task.py (99%) delete mode 100644 common/snowflake.py create mode 100644 common/ssh.py delete mode 100644 common/status_rpc.py create mode 100644 common/tool.py create mode 100644 common/types.py rename utils/version_utils.py => common/version.py (67%) create mode 100644 config.py create mode 100644 context.py create mode 100644 core.py create mode 100644 dev_init.sh create mode 100644 err.py delete mode 100644 handler/base_http_handler.py create mode 100644 handler/checker/step/data_size.py rename obproxy_check_package.yaml => handler/checker/tasks/obproxy_check_package.yaml (100%) rename observer_check_package.yaml => handler/checker/tasks/observer_check_package.yaml (98%) create mode 100644 handler/gather/scenes/px_collect_log.py rename {utils => handler/rca/plugins}/__init__.py (96%) create mode 100644 handler/rca/plugins/gather.py delete mode 100644 handler/rca/rca_scene/__init__.py delete mode 100644 handler/rca/rca_scene/scene_base.py create mode 100644 handler/rca/scene/ddl_disk_full_scene.py rename handler/rca/{rca_scene => scene}/disconnection_scene.py (76%) rename handler/rca/{rca_scene => scene}/lock_conflict_scene.py (64%) rename handler/rca/{rca_scene => scene}/major_hold_scene.py (84%) rename utils/password_util.py => main.py (53%) delete mode 100755 obdiag delete mode 100644 obdiag_client.py delete mode 100644 obdiag_main.py delete mode 100644 ocp/ocp_base.py delete mode 100644 ocp/ocp_cluster.py delete mode 100644 ocp/ocp_host.py create mode 100644 stdio.py delete mode 100644 utils/file_utils.py delete mode 100644 utils/network_utils.py delete mode 100644 utils/parser_utils.py delete mode 100644 utils/print_spin.py delete mode 100644 utils/print_utils.py delete mode 100644 utils/retry_utils.py delete mode 100644 utils/shell_utils.py delete mode 100644 utils/sql_utils.py delete mode 100644 utils/string_utils.py delete mode 100644 utils/time_utils.py delete mode 100644 utils/utils.py delete mode 100644 utils/yaml_utils.py diff --git a/LICENSE b/LICENSE index 91c7151b..d7248cbe 100644 --- a/LICENSE +++ b/LICENSE @@ -1,68 +1,4 @@ -木兰宽松许可证, 第2版 - -2020年1月 http://license.coscl.org.cn/MulanPSL2 - -您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: - -0. 定义 - -“软件” 是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 - -“贡献” 是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 - -“贡献者” 是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 - -“法人实体” 是指提交贡献的机构及其“关联实体”。 - -“关联实体” 是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 - -1. 授予版权许可 - -每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 - -2. 授予专利许可 - -每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 - -3. 无商标许可 - -“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 - -4. 分发限制 - -您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 - -5. 免责声明与责任限制 - -“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 - -6. 语言 - -“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 - -条款结束 - -如何将木兰宽松许可证,第2版,应用到您的软件 - -如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: - -1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; - -2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; - -3, 请将如下声明文本放入每个源文件的头部注释中。 - -``` -Copyright (c) [Year] [name of copyright holder] -[Software Name] 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. -``` + Mulan Permissive Software License,Version 2 Mulan Permissive Software License,Version 2 (Mulan PSL v2) @@ -106,26 +42,4 @@ THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. -END OF THE TERMS AND CONDITIONS - -How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software - -To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps: - -Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; - -Create a file named "LICENSE" which contains the whole context of this License in the first directory of your software package; - -Attach the statement to the appropriate annotated syntax at the beginning of each source file. - -``` -Copyright (c) [Year] [name of copyright holder] -[Software Name] 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. -``` +END OF THE TERMS AND CONDITIONS \ No newline at end of file diff --git a/README-CN.md b/README-CN.md index 8927dfc6..d14ecbcc 100644 --- a/README-CN.md +++ b/README-CN.md @@ -40,19 +40,19 @@ sudo yum install -y oceanbase-diagnostic-tool sh /usr/local/oceanbase-diagnostic-tool/init.sh ``` -## 方式二:源码编译安装 +## 方式二:源码安装 源码编译环境确保有如下依赖 - gcc - wget - python-devel - mysql-devel -源码编译需要在python >= 3.8的环境下进行 +源码安装需要在python >= 3.8的环境下进行 ```shell -pip install -r requirements3.txt -cd rpm && sh build.sh build_obdiag -sh init.sh +pip3 install -r requirements3.txt +sh dev_init.sh +source ~/.bashrc ``` # obdiag config diff --git a/README.md b/README.md index dd296b44..f5164359 100644 --- a/README.md +++ b/README.md @@ -52,9 +52,9 @@ Before you install obdiag by using the source code, make sure that you have inst To install obdiag on Python3.8, run these commands: ```shell -pip install -r requirements3.txt -cd rpm && sh build.sh build_obdiag -sh init.sh +pip3 install -r requirements3.txt +sh dev_init.sh +source ~/.bashrc ``` # obdiag config diff --git a/cmd.py b/cmd.py new file mode 100644 index 00000000..7c38d2b2 --- /dev/null +++ b/cmd.py @@ -0,0 +1,692 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -* +# Copyright (c) 2022 OceanBase +# OceanBase Diagnostic Tool 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. + +""" +@file: cmd.py +@desc: +""" + +from __future__ import absolute_import, division, print_function +from common.tool import Util + +import os +import sys +import textwrap +from uuid import uuid1 as uuid, UUID +from optparse import OptionParser, BadOptionError, Option, IndentedHelpFormatter +from core import ObdiagHome +from stdio import IO +from common.version import get_obdiag_version +from telemetry.telemetry import telemetry + +ROOT_IO = IO(1) +OBDIAG_HOME_PATH = os.path.join(os.getenv('HOME'), 'oceanbase-diagnostic-tool') + + +class OptionHelpFormatter(IndentedHelpFormatter): + + def format_option(self, option): + result = [] + opts = self.option_strings[option] + opt_width = self.help_position - self.current_indent - 2 + if len(opts) > opt_width: + opts = "%*s%s\n" % (self.current_indent, "", opts) + indent_first = self.help_position + else: + opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts) + indent_first = 0 + result.append(opts) + if option.help: + help_text = self.expand_default(option) + help_lines = help_text.split('\n') + if len(help_lines) == 1: + help_lines = textwrap.wrap(help_text, self.help_width) + result.append("%*s%s\n" % (indent_first, "", help_lines[0])) + result.extend(["%*s%s\n" % (self.help_position, "", line) + for line in help_lines[1:]]) + elif opts[-1] != "\n": + result.append("\n") + return "".join(result) + + +class AllowUndefinedOptionParser(OptionParser): + IS_TTY = sys.stdin.isatty() + + def __init__(self, + usage=None, + option_list=None, + option_class=Option, + version=None, + conflict_handler="resolve", + description=None, + formatter=None, + add_help_option=True, + prog=None, + epilog=None, + allow_undefine=True, + undefine_warn=True + ): + OptionParser.__init__( + self, usage, option_list, option_class, version, conflict_handler, + description, formatter, add_help_option, prog, epilog + ) + self.allow_undefine = allow_undefine + self.undefine_warn = undefine_warn + + def warn(self, msg, file=None): + if self.IS_TTY: + print("%s %s" % (IO.WARNING_PREV, msg)) + else: + print('warn: %s' % msg) + + def _process_long_opt(self, rargs, values): + try: + value = rargs[0] + OptionParser._process_long_opt(self, rargs, values) + except BadOptionError as e: + if self.allow_undefine: + key = e.opt_str + value = value[len(key) + 1:] + setattr(values, key.strip('-').replace('-', '_'), value if value != '' else True) + self.undefine_warn and self.warn(e) + else: + raise e + + def _process_short_opts(self, rargs, values): + try: + value = rargs[0] + OptionParser._process_short_opts(self, rargs, values) + except BadOptionError as e: + if self.allow_undefine: + key = e.opt_str + value = value[len(key) + 1:] + setattr(values, key.strip('-').replace('-', '_'), value if value != '' else True) + self.undefine_warn and self.warn(e) + else: + raise e + + +class BaseCommand(object): + + def __init__(self, name, summary): + self.name = name + self.summary = summary + self.args = [] + self.cmds = [] + self.opts = {} + self.prev_cmd = '' + self.is_init = False + self.hidden = False + self.has_trace = True + self.parser = AllowUndefinedOptionParser(add_help_option=True) + self.parser.add_option('-h', '--help', action='callback', callback=self._show_help, help='Show help and exit.') + self.parser.add_option('-v', '--verbose', action='callback', callback=self._set_verbose, help='Activate verbose output.') + + def _set_verbose(self, *args, **kwargs): + ROOT_IO.set_verbose_level(0xfffffff) + + def init(self, cmd, args): + if self.is_init is False: + self.prev_cmd = cmd + self.args = args + self.is_init = True + self.parser.prog = self.prev_cmd + option_list = self.parser.option_list[2:] + option_list.append(self.parser.option_list[0]) + option_list.append(self.parser.option_list[1]) + self.parser.option_list = option_list + return self + + def parse_command(self): + self.opts, self.cmds = self.parser.parse_args(self.args) + return self.opts + + def do_command(self): + raise NotImplementedError + + def _show_help(self, *args, **kwargs): + ROOT_IO.print(self._mk_usage()) + self.parser.exit(0) + + def _mk_usage(self): + return self.parser.format_help(OptionHelpFormatter()) + + +class ObdiagOriginCommand(BaseCommand): + OBDIAG_PATH = OBDIAG_HOME_PATH + + @property + def enable_log(self): + return True + + def parse_command(self): + return super(ObdiagOriginCommand, self).parse_command() + + def do_command(self): + self.parse_command() + trace_id = uuid() + ret = False + try: + log_directory = os.path.join(os.path.expanduser("~"), ".obdiag", "log") + if not os.path.exists(log_directory): + os.makedirs(log_directory, exist_ok=True) + log_path = os.path.join(log_directory, 'obdiag.log') + if self.enable_log: + ROOT_IO.init_trace_logger(log_path, 'obdiag', trace_id) + ROOT_IO.track_limit += 1 + ROOT_IO.verbose('cmd: %s' % self.cmds) + ROOT_IO.verbose('opts: %s' % self.opts) + config_path = os.path.expanduser('~/.obdiag/config.yml') + custom_config = Util.get_option(self.opts, 'c') + if custom_config: + config_path = custom_config + obdiag = ObdiagHome(stdio=ROOT_IO, config_path=config_path) + obdiag.set_options(self.opts) + obdiag.set_cmds(self.cmds) + ret = self._do_command(obdiag) + telemetry.put_data() + except NotImplementedError: + ROOT_IO.exception('command \'%s\' is not implemented' % self.prev_cmd) + except SystemExit: + pass + except KeyboardInterrupt: + ROOT_IO.exception('Keyboard Interrupt') + except: + e = sys.exc_info()[1] + ROOT_IO.exception('Running Error: %s' % e) + if self.has_trace: + ROOT_IO.print('Trace ID: %s' % trace_id) + ROOT_IO.print('If you want to view detailed obdiag logs, please run: obdiag display-trace %s' % trace_id) + return ret + + def _do_command(self, obdiag): + raise NotImplementedError + + def get_white_ip_list(self): + if self.opts.white: + return self.opts.white.split(',') + ROOT_IO.warn("Security Risk: the whitelist is empty and anyone can request this program!") + if ROOT_IO.confirm("Do you want to continue?"): + return [] + wthite_ip_list = ROOT_IO.read("Please enter the whitelist, eq: '192.168.1.1'") + raise wthite_ip_list.split(',') + + +class DisplayTraceCommand(ObdiagOriginCommand): + + def __init__(self): + super(DisplayTraceCommand, self).__init__('display-trace', 'display trace_id log.') + self.has_trace = False + + @property + def enable_log(self): + return False + + def _do_command(self, obdiag): + from common.ssh import LocalClient + if not self.cmds: + return self._show_help() + log_dir = os.path.expanduser('~/.obdiag/log') + trace_id = self.cmds[0] + ROOT_IO.verbose('Get log by trace_id') + try: + if UUID(trace_id).version != 1: + ROOT_IO.critical('%s is not trace id' % trace_id) + return False + except: + ROOT_IO.print('%s is not trace id' % trace_id) + return False + cmd = 'cd {} && grep -h "\[{}\]" $(ls -tr {}*) | sed "s/\[{}\] //g" '.format(log_dir, trace_id, log_dir, trace_id) + data = LocalClient.execute_command(cmd) + ROOT_IO.print(data.stdout) + return True + +class MajorCommand(BaseCommand): + + def __init__(self, name, summary): + super(MajorCommand, self).__init__(name, summary) + self.commands = {} + + def _mk_usage(self): + if self.commands: + usage = ['%s [options]\n\nAvailable commands:\n' % self.prev_cmd] + commands = [x for x in self.commands.values() if not (hasattr(x, 'hidden') and x.hidden)] + commands.sort(key=lambda x: x.name) + for command in commands: + if command.hidden is False: + usage.append("%-12s %s\n" % (command.name, command.summary)) + self.parser.set_usage('\n'.join(usage)) + return super(MajorCommand, self)._mk_usage() + + def do_command(self): + if not self.is_init: + ROOT_IO.error('%s command not init' % self.prev_cmd) + raise SystemExit('command not init') + if len(self.args) < 1: + ROOT_IO.print('You need to give some commands.\n\nTry `obdiag --help` for more information.') + self._show_help() + return False + base, args = self.args[0], self.args[1:] + if base not in self.commands: + self.parse_command() + self._show_help() + return False + cmd = '%s %s' % (self.prev_cmd, base) + ROOT_IO.track_limit += 1 + telemetry.push_cmd_info("cmd: {0}. args:{1}".format(cmd,args)) + return self.commands[base].init(cmd, args).do_command() + + def register_command(self, command): + self.commands[command.name] = command + + +class ObdiagGatherAllCommand(ObdiagOriginCommand): + + def init(self, cmd, args): + super(ObdiagGatherAllCommand, self).init(cmd, args) + return self + + def __init__(self): + super(ObdiagGatherAllCommand, self).__init__('all', 'Gather oceanbase diagnostic info') + self.parser.add_option('--from', type='string', help="specify the start of the time range. 'format: yyyy-mm-dd hh:mm:ss'") + self.parser.add_option('--to', type='string', help="specify the end of the time range. 'format: yyyy-mm-dd hh:mm:ss'") + self.parser.add_option('--since', type='string', help="Specify time range that from 'n' [d]ays, 'n' [h]ours or 'n' [m]inutes. before to now. format: . example: 1h.", default='30m') + self.parser.add_option('--scope', type='string', help="log type constrains, choices=[observer, election, rootservice, all]", default='all') + self.parser.add_option('--grep', action="append", type='string', help="specify keywords constrain") + self.parser.add_option('--encrypt', type='string', help="Whether the returned results need to be encrypted, choices=[true, false]", default="false") + self.parser.add_option('--store_dir', type='string', help='the dir to store gather result, current dir by default.', default='./') + self.parser.add_option('-c', type='string', help='obdiag custom config', default=os.path.expanduser('~/.obdiag/config.yml')) + + def init(self, cmd, args): + super(ObdiagGatherAllCommand, self).init(cmd, args) + self.parser.set_usage('%s [options]' % self.prev_cmd) + return self + + def _do_command(self, obdiag): + return obdiag.gather_function('gather_all', self.opts) + + +class ObdiagGatherLogCommand(ObdiagOriginCommand): + + def __init__(self): + super(ObdiagGatherLogCommand, self).__init__('log', 'Gather oceanbase logs from oceanbase machines') + self.parser.add_option('--from', type='string', help="specify the start of the time range. 'format: yyyy-mm-dd hh:mm:ss'") + self.parser.add_option('--to', type='string', help="specify the end of the time range. 'format: yyyy-mm-dd hh:mm:ss'") + self.parser.add_option('--since', type='string', help="Specify time range that from 'n' [d]ays, 'n' [h]ours or 'n' [m]inutes. before to now. format: . example: 1h.", default='30m') + self.parser.add_option('--scope', type='string', help="log type constrains, choices=[observer, election, rootservice, all]", default='all') + self.parser.add_option('--grep', action="append", type='string', help="specify keywords constrain") + self.parser.add_option('--encrypt', type='string', help="Whether the returned results need to be encrypted, choices=[true, false]", default="false") + self.parser.add_option('--store_dir', type='string', help='the dir to store gather result, current dir by default.', default='./') + self.parser.add_option('-c', type='string', help='obdiag custom config', default=os.path.expanduser('~/.obdiag/config.yml')) + + def init(self, cmd, args): + super(ObdiagGatherLogCommand, self).init(cmd, args) + self.parser.set_usage('%s [options]' % self.prev_cmd) + return self + + def _do_command(self, obdiag): + return obdiag.gather_function('gather_log', self.opts) + + +class ObdiagGatherSysStatCommand(ObdiagOriginCommand): + + def __init__(self): + super(ObdiagGatherSysStatCommand, self).__init__('sysstat', 'Gather Host information') + self.parser.add_option('--store_dir', type='string', help='the dir to store gather result, current dir by default.', default='./') + self.parser.add_option('-c', type='string', help='obdiag custom config', default=os.path.expanduser('~/.obdiag/config.yml')) + + def init(self, cmd, args): + super(ObdiagGatherSysStatCommand, self).init(cmd, args) + self.parser.set_usage('%s [options]' % self.prev_cmd) + return self + + def _do_command(self, obdiag): + return obdiag.gather_function('gather_sysstat', self.opts) + + +class ObdiagGatherStackCommand(ObdiagOriginCommand): + + def __init__(self): + super(ObdiagGatherStackCommand, self).__init__('stack', 'Gather stack') + + self.parser.add_option('--store_dir', type='string', help='the dir to store gather result, current dir by default.', default='./') + self.parser.add_option('-c', type='string', help='obdiag custom config', default=os.path.expanduser('~/.obdiag/config.yml')) + + def init(self, cmd, args): + super(ObdiagGatherStackCommand, self).init(cmd, args) + self.parser.set_usage('%s [options]' % self.prev_cmd) + return self + + def _do_command(self, obdiag): + return obdiag.gather_function('gather_obstack', self.opts) + + +class ObdiagGatherPerfCommand(ObdiagOriginCommand): + + def __init__(self): + super(ObdiagGatherPerfCommand, self).__init__('perf', 'Gather perf') + + self.parser.add_option('--store_dir', type='string', help='the dir to store gather result, current dir by default.', default='./') + self.parser.add_option('--scope', type='string', help="perf type constrains, choices=[sample, flame, pstack, all]", default='all') + self.parser.add_option('-c', type='string', help='obdiag custom config', default=os.path.expanduser('~/.obdiag/config.yml')) + + def init(self, cmd, args): + super(ObdiagGatherPerfCommand, self).init(cmd, args) + self.parser.set_usage('%s [options]' % self.prev_cmd) + return self + + def _do_command(self, obdiag): + return obdiag.gather_function('gather_perf', self.opts) + + +class ObdiagGatherSlogCommand(ObdiagOriginCommand): + + def __init__(self): + super(ObdiagGatherSlogCommand, self).__init__('slog', 'Gather slog') + self.parser.add_option('--from', type='string', help="specify the start of the time range. 'format: yyyy-mm-dd hh:mm:ss'") + self.parser.add_option('--to', type='string', help="specify the end of the time range. 'format: yyyy-mm-dd hh:mm:ss'") + self.parser.add_option('--since', type='string', help="Specify time range that from 'n' [d]ays, 'n' [h]ours or 'n' [m]inutes. before to now. format: . example: 1h.", default='30m') + self.parser.add_option('--encrypt', type='string', help="Whether the returned results need to be encrypted, choices=[true, false]", default="false") + self.parser.add_option('--store_dir', type='string', help='the dir to store gather result, current dir by default.', default='./') + self.parser.add_option('-c', type='string', help='obdiag custom config', default=os.path.expanduser('~/.obdiag/config.yml')) + + def init(self, cmd, args): + super(ObdiagGatherSlogCommand, self).init(cmd, args) + self.parser.set_usage('%s [options]' % self.prev_cmd) + return self + + def _do_command(self, obdiag): + return obdiag.gather_function('gather_slog', self.opts) + + +class ObdiagGatherClogCommand(ObdiagOriginCommand): + + def __init__(self): + super(ObdiagGatherClogCommand, self).__init__('clog', 'Gather clog') + self.parser.add_option('--from', type='string', help="specify the start of the time range. 'format: yyyy-mm-dd hh:mm:ss'") + self.parser.add_option('--to', type='string', help="specify the end of the time range. 'format: yyyy-mm-dd hh:mm:ss'") + self.parser.add_option('--since', type='string', help="Specify time range that from 'n' [d]ays, 'n' [h]ours or 'n' [m]inutes. before to now. format: . example: 1h.", default='30m') + self.parser.add_option('--encrypt', type='string', help="Whether the returned results need to be encrypted, choices=[true, false]", default="false") + self.parser.add_option('--store_dir', type='string', help='the dir to store gather result, current dir by default.', default='./') + self.parser.add_option('-c', type='string', help='obdiag custom config', default=os.path.expanduser('~/.obdiag/config.yml')) + + def init(self, cmd, args): + super(ObdiagGatherClogCommand, self).init(cmd, args) + self.parser.set_usage('%s [options]' % self.prev_cmd) + return self + + def _do_command(self, obdiag): + return obdiag.gather_function('gather_clog', self.opts) + + +class ObdiagGatherPlanMonitorCommand(ObdiagOriginCommand): + + def __init__(self): + super(ObdiagGatherPlanMonitorCommand, self).__init__('plan_monitor', 'Gather ParalleSQL information') + self.parser.add_option('--trace_id', type='string', help='sql trace id') + self.parser.add_option('--store_dir', type='string', help='the dir to store gather result, current dir by default.', default='./') + self.parser.add_option('--env', type='string', help='env, eg: "{env1=xxx, env2=xxx}"') + self.parser.add_option('-c', type='string', help='obdiag custom config', default=os.path.expanduser('~/.obdiag/config.yml')) + + def init(self, cmd, args): + super(ObdiagGatherPlanMonitorCommand, self).init(cmd, args) + self.parser.set_usage('%s [options]' % self.prev_cmd) + return self + + def _do_command(self, obdiag): + return obdiag.gather_function('gather_plan_monitor', self.opts) + + +class ObdiagGatherObproxyLogCommand(ObdiagOriginCommand): + + def __init__(self): + super(ObdiagGatherObproxyLogCommand, self).__init__('obproxy_log', 'Gather obproxy log from obproxy machines') + self.parser.add_option('--from', type='string', help="specify the start of the time range. 'format: yyyy-mm-dd hh:mm:ss'") + self.parser.add_option('--to', type='string', help="specify the end of the time range. 'format: yyyy-mm-dd hh:mm:ss'") + self.parser.add_option('--since', type='string', help="Specify time range that from 'n' [d]ays, 'n' [h]ours or 'n' [m]inutes. before to now. format: . example: 1h.", default='30m') + self.parser.add_option('--scope', type='string', help="log type constrains, choices=[obproxy, obproxy_limit, obproxy_stat, obproxy_digest, obproxy_slow, obproxy_diagnosis, obproxy_error, all]", default='all') + self.parser.add_option('--grep', action="append", type='string', help="specify keywords constrain") + self.parser.add_option('--encrypt', type='string', help="Whether the returned results need to be encrypted, choices=[true, false]", default="false") + self.parser.add_option('--store_dir', type='string', help='the dir to store gather result, current dir by default.', default='./') + self.parser.add_option('-c', type='string', help='obdiag custom config', default=os.path.expanduser('~/.obdiag/config.yml')) + + def init(self, cmd, args): + super(ObdiagGatherObproxyLogCommand, self).init(cmd, args) + self.parser.set_usage('%s [options]' % self.prev_cmd) + return self + + def _do_command(self, obdiag): + return obdiag.gather_function('gather_obproxy_log', self.opts) + + +class ObdiagGatherSceneListCommand(ObdiagOriginCommand): + + def __init__(self): + super(ObdiagGatherSceneListCommand, self).__init__('list', 'gather scene list') + + def init(self, cmd, args): + super(ObdiagGatherSceneListCommand, self).init(cmd, args) + return self + + def _do_command(self, obdiag): + return obdiag.gather_scenes_list(self.opts) + + +class ObdiagGatherSceneRunCommand(ObdiagOriginCommand): + + def __init__(self): + super(ObdiagGatherSceneRunCommand, self).__init__('run', 'gather scene run') + self.parser.add_option('--scene', type='string', help="Specify the scene to be gather") + self.parser.add_option('--from', type='string', help="specify the start of the time range. 'format: yyyy-mm-dd hh:mm:ss'") + self.parser.add_option('--to', type='string', help="specify the end of the time range. 'format: yyyy-mm-dd hh:mm:ss'") + self.parser.add_option('--since', type='string', help="Specify time range that from 'n' [d]ays, 'n' [h]ours or 'n' [m]inutes. before to now. format: . example: 1h.",default='30m') + self.parser.add_option('--env', type='string', help='env, eg: "{env1=xxx, env2=xxx}"') + self.parser.add_option('--store_dir', type='string', help='the dir to store gather result, current dir by default.', default='./') + self.parser.add_option('-c', type='string', help='obdiag custom config', default=os.path.expanduser('~/.obdiag/config.yml')) + + def init(self, cmd, args): + super(ObdiagGatherSceneRunCommand, self).init(cmd, args) + return self + + def _do_command(self, obdiag): + return obdiag.gather_function('gather_scenes_run', self.opts) + + +class ObdiagAnalyzeLogCommand(ObdiagOriginCommand): + + def __init__(self): + super(ObdiagAnalyzeLogCommand, self).__init__('log', 'Analyze oceanbase log from online observer machines or offline oceanbase log files') + self.parser.add_option('--from', type='string', help="specify the start of the time range. 'format: yyyy-mm-dd hh:mm:ss'") + self.parser.add_option('--to', type='string', help="specify the end of the time range. 'format: yyyy-mm-dd hh:mm:ss'") + self.parser.add_option('--since', type='string', help="Specify time range that from 'n' [d]ays, 'n' [h]ours or 'n' [m]inutes. before to now. format: . example: 1h.", default='30m') + self.parser.add_option('--scope', type='string', help="log type constrains, choices=[observer, election, rootservice, all]", default='all') + self.parser.add_option('--grep', action="append", type='string', help="specify keywords constrain") + self.parser.add_option('--log_level', type='string', help="oceanbase logs greater than or equal to this level will be analyze, choices=[DEBUG, TRACE, INFO, WDIAG, WARN, EDIAG, ERROR]") + self.parser.add_option('--files', action="append", type='string', help="specify files") + self.parser.add_option('--store_dir', type='string', help='the dir to store gather result, current dir by default.', default='./') + self.parser.add_option('-c', type='string', help='obdiag custom config', default=os.path.expanduser('~/.obdiag/config.yml')) + + def init(self, cmd, args): + super(ObdiagAnalyzeLogCommand, self).init(cmd, args) + self.parser.set_usage('%s [options]' % self.prev_cmd) + return self + + def _do_command(self, obdiag): + offline_args_sign = '--files' + if self.args and (offline_args_sign in self.args): + return obdiag.analyze_fuction('analyze_log_offline', self.opts) + else: + return obdiag.analyze_fuction('analyze_log', self.opts) + + +class ObdiagAnalyzeFltTraceCommand(ObdiagOriginCommand): + + def __init__(self): + super(ObdiagAnalyzeFltTraceCommand, self).__init__('flt_trace', 'Analyze oceanbase trace.log from online observer machines or offline oceanbase trace.log files') + self.parser.add_option('--flt_trace_id', type='string', help="flt trace id, . format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") + self.parser.add_option('--files', action="append", help="specify files") + self.parser.add_option('--top', type='string', help="top leaf span", default=5) + self.parser.add_option('--recursion', type='string', help="Maximum number of recursion", default=8) + self.parser.add_option('--output', type='string', help="Print the result to the maximum output line on the screen", default=60) + self.parser.add_option('--store_dir', type='string', help='the dir to store gather result, current dir by default.', default='./') + self.parser.add_option('-c', type='string', help='obdiag custom config', default=os.path.expanduser('~/.obdiag/config.yml')) + + def init(self, cmd, args): + super(ObdiagAnalyzeFltTraceCommand, self).init(cmd, args) + self.parser.set_usage('%s [options]' % self.prev_cmd) + return self + + def _do_command(self, obdiag): + return obdiag.analyze_fuction('analyze_flt_trace', self.opts) + + +class ObdiagCheckCommand(ObdiagOriginCommand): + + def __init__(self): + super(ObdiagCheckCommand, self).__init__('check', 'check oceanbase cluster') + self.parser.add_option('--cases', type='string', help="check observer's cases on package_file") + self.parser.add_option('--obproxy_cases', type='string', help="check obproxy's cases on package_file") + self.parser.add_option('--store_dir', type='string', help='the dir to store check result, current dir by default.', default='./check_report/') + self.parser.add_option('-c', type='string', help='obdiag custom config', default=os.path.expanduser('~/.obdiag/config.yml')) + + + def init(self, cmd, args): + super(ObdiagCheckCommand, self).init(cmd, args) + self.parser.set_usage('%s [options]' % self.prev_cmd) + return self + + def _do_command(self, obdiag): + if 'list' in self.args: + obdiag.check_list(self.opts) + return + return obdiag.check(self.opts) + + +class ObdiagRCARunCommand(ObdiagOriginCommand): + + def __init__(self): + super(ObdiagRCARunCommand, self).__init__('run', 'root cause analysis') + self.parser.add_option('--scene', type='string', help="rca scene name. The argument is required.") + self.parser.add_option('--store_dir', type='string', help='the dir to store rca result, current dir by default.', default='./rca/') + self.parser.add_option('--input_parameters', type='string', help='input parameters of scene') + self.parser.add_option('-c', type='string', help='obdiag custom config', default=os.path.expanduser('~/.obdiag/config.yml')) + + def init(self, cmd, args): + super(ObdiagRCARunCommand, self).init(cmd, args) + self.parser.set_usage('%s [options]' % self.prev_cmd) + return self + + def _do_command(self, obdiag): + return obdiag.rca_run(self.opts) + + +class ObdiagRCAListCommand(ObdiagOriginCommand): + + def __init__(self): + super(ObdiagRCAListCommand, self).__init__('list', 'show list of rca list') + + def init(self, cmd, args): + super(ObdiagRCAListCommand, self).init(cmd, args) + self.parser.set_usage('%s [options]' % self.prev_cmd) + return self + + def _do_command(self, obdiag): + return obdiag.rca_list(self.opts) + + +class ObdiagConfigCommand(ObdiagOriginCommand): + + def __init__(self): + super(ObdiagConfigCommand, self).__init__('config', 'Quick build config') + self.parser.add_option('-h', type='string', help="database host") + self.parser.add_option('-u', type='string', help='sys_user', default='root@sys') + self.parser.add_option('-p', type='string', help="password", default='') + self.parser.add_option('-P', type='string', help="port") + + def init(self, cmd, args): + super(ObdiagConfigCommand, self).init(cmd, args) + self.parser.set_usage('%s [options]' % self.prev_cmd) + return self + + def _do_command(self, obdiag): + return obdiag.config(self.opts) + +class ObdiagUpdateCommand(ObdiagOriginCommand): + + def __init__(self): + super(ObdiagUpdateCommand, self).__init__('update', 'Update cheat files') + self.parser.add_option('--file', type='string', help="obdiag update cheat file path. Please note that you need to ensure the reliability of the files on your own.") + self.parser.add_option('--force', action='store_true', help='You can force online upgrades by adding --force in the command',) + + + def init(self, cmd, args): + super(ObdiagUpdateCommand, self).init(cmd, args) + self.parser.set_usage('%s [options]' % self.prev_cmd) + return self + + def _do_command(self, obdiag): + return obdiag.update(self.opts) + + +class ObdiagGatherCommand(MajorCommand): + + def __init__(self): + super(ObdiagGatherCommand, self).__init__('gather', 'Gather oceanbase diagnostic info') + self.register_command(ObdiagGatherAllCommand()) + self.register_command(ObdiagGatherLogCommand()) + self.register_command(ObdiagGatherSysStatCommand()) + self.register_command(ObdiagGatherStackCommand()) + self.register_command(ObdiagGatherPerfCommand()) + self.register_command(ObdiagGatherSlogCommand()) + self.register_command(ObdiagGatherClogCommand()) + self.register_command(ObdiagGatherPlanMonitorCommand()) + self.register_command(ObdiagGatherObproxyLogCommand()) + self.register_command(ObdiagGatherSceneCommand()) + + +class ObdiagGatherSceneCommand(MajorCommand): + + def __init__(self): + super(ObdiagGatherSceneCommand, self).__init__('scene', 'Gather scene diagnostic info') + self.register_command(ObdiagGatherSceneListCommand()) + self.register_command(ObdiagGatherSceneRunCommand()) + + +class ObdiagAnalyzeCommand(MajorCommand): + + def __init__(self): + super(ObdiagAnalyzeCommand, self).__init__('analyze', 'Analyze oceanbase diagnostic info') + self.register_command(ObdiagAnalyzeLogCommand()) + self.register_command(ObdiagAnalyzeFltTraceCommand()) + + +class ObdiagRCACommand(MajorCommand): + + def __init__(self): + super(ObdiagRCACommand, self).__init__('rca', 'root cause analysis') + self.register_command(ObdiagRCARunCommand()) + self.register_command(ObdiagRCAListCommand()) + + +class MainCommand(MajorCommand): + + def __init__(self): + super(MainCommand, self).__init__('obdiag', '') + self.register_command(DisplayTraceCommand()) + self.register_command(ObdiagGatherCommand()) + self.register_command(ObdiagAnalyzeCommand()) + self.register_command(ObdiagCheckCommand()) + self.register_command(ObdiagRCACommand()) + self.register_command(ObdiagConfigCommand()) + self.register_command(ObdiagUpdateCommand()) + self.parser.version = get_obdiag_version() + self.parser._add_version_option() diff --git a/common/base_rpc.py b/common/base_rpc.py deleted file mode 100644 index cdcf53ec..00000000 --- a/common/base_rpc.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2022/6/20 -@file: base_rpc.py -@desc: -""" -import random -from common.snowflake import SnowFlake -from utils.time_utils import get_current_us_timestamp - - -class Request(object): - """ - All class inheriting Request must implement to_dict() - """ - DUMP_KEY_LIST = ["request_timestamp", "request_id"] - - def __init__(self, timestamp=None): - self.id_generator = SnowFlake(random.randint(100,199)) - self.request_timestamp = get_current_us_timestamp() if timestamp is None else timestamp - self.request_id = self.generate_request_id() - - def generate_request_id(self): - return self.id_generator.get_id() - - def to_dict(self): - ret_dict = {} - for key in self.DUMP_KEY_LIST: - ret_dict[key] = getattr(self, key) - return ret_dict - - -class Response(object): - """ - All class inheriting Response class must implement to_dict() and from_dict() - """ - DUMP_KEY_LIST = ["response_id", "response_timestamp", "error_code", "error_msg", - "unready_logs"] - - def __init__(self, request_id, timestamp=None): - self.id_generator = SnowFlake(random.randint(100,199)) - self.response_timestamp = get_current_us_timestamp() if timestamp is None else timestamp - self.request_id = request_id - self.response_id = self.generate_request_id() - self.error_code = 0 - self.error_msg = "" - self.unready_logs = [] - - def generate_request_id(self): - return self.id_generator.get_id() - - def to_dict(self): - ret_dict = {} - for key in self.DUMP_KEY_LIST: - ret_dict[key] = getattr(self, key) - return ret_dict - - def from_dict(self, content_dict): - for key in self.DUMP_KEY_LIST: - setattr(self, key, content_dict[key]) - - -class RawDictRequest(Request): - def __init__(self, raw_dict, timestamp=None): - super(RawDictRequest, self).__init__(timestamp=timestamp) - - self.payload = raw_dict - - def to_dict(self): - parent_dict = super(RawDictRequest, self).to_dict() - parent_dict["payload"] = self.payload - return parent_dict - - -class RawDictResponse(Response): - def __init__(self, request, raw_dict, request_id, timestamp=None, err_code=0, err_msg=""): - super(RawDictResponse, self).__init__(request_id=request_id, - timestamp=timestamp) - self.payload = raw_dict - self.error_code = err_code - self.error_msg = err_msg - - def to_dict(self): - parent_dict = super(RawDictResponse, self).to_dict() - parent_dict["payload"] = self.payload - return parent_dict - - def from_dict(self, content_dict): - super(RawDictResponse, self).from_dict(content_dict) - self.payload = content_dict["payload"] diff --git a/common/command.py b/common/command.py index e03959b8..4ce4eac5 100644 --- a/common/command.py +++ b/common/command.py @@ -19,116 +19,102 @@ import subprocess from paramiko import SSHException - -from common.logger import logger -from utils.time_utils import extract_time_from_log_file_text, filename_time_to_datetime, \ - extract_filename_time_from_log_name from common.ob_connector import OBConnector +from common.tool import TimeUtils + +class LocalClient(object): + def __init__(self, stdio=None): + self.stdio = stdio -class LocalClient: def run(self, cmd): try: - logger.info("[local host] run cmd = [{0}] on localhost".format(cmd)) + self.stdio.verbose("[local host] run cmd = [{0}] on localhost".format(cmd)) out = subprocess.Popen(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True) stdout, stderr = out.communicate() if stderr: - logger.error("run cmd = [{0}] on localhost, stderr=[{1}]".format(cmd, stderr)) + self.stdio.error("run cmd = [{0}] on localhost, stderr=[{1}]".format(cmd, stderr)) return stdout except: - logger.error("run cmd = [{0}] on localhost".format(cmd)) + self.stdio.error("run cmd = [{0}] on localhost".format(cmd)) def run_get_stderr(self, cmd): try: - logger.info("run cmd = [{0}] on localhost".format(cmd)) + self.stdio.verbose("run cmd = [{0}] on localhost".format(cmd)) out = subprocess.Popen(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True) stdout, stderr = out.communicate() return stderr except: - logger.error("run cmd = [{0}] on localhost".format(cmd)) + self.stdio.error("run cmd = [{0}] on localhost".format(cmd)) + +class SshClient(object): + def __init__(self, stdio=None): + self.stdio = stdio -class SshClient: def run(self, ssh_helper, cmd): try: - logger.info("[remote host {0}] excute cmd = [{1}]".format(ssh_helper.get_name(), cmd)) + self.stdio.verbose("[remote host {0}] excute cmd = [{1}]".format(ssh_helper.get_name(), cmd)) stdout = ssh_helper.ssh_exec_cmd(cmd) - logger.debug( - "[remote host {0}] excute cmd = [{1}] complete, stdout=[{2}]".format(ssh_helper.get_name(), cmd, stdout)) + self.stdio.verbose("[remote host {0}] excute cmd = [{1}] complete, stdout=[{2}]".format(ssh_helper.get_name(), cmd, stdout)) return stdout except Exception as e: - logger.error("[remote host {0}] excute cmd = [{1}] except: [{2}]".format(ssh_helper.get_name(), cmd, e)) + self.stdio.error("[remote host {0}] excute cmd = [{1}] except: [{2}]".format(ssh_helper.get_name(), cmd, e)) def run_get_stderr(self, ssh_helper, cmd): try: - logger.info("[remote host {0}] run cmd = [{1}] start ...".format(ssh_helper.get_name(), cmd)) + self.stdio.verbose("[remote host {0}] run cmd = [{1}] start ...".format(ssh_helper.get_name(), cmd)) std = ssh_helper.ssh_exec_cmd_get_stderr(cmd) return std except Exception as e: - logger.error("[remote host {0}] run ssh cmd = [{1}] except: {2}".format(ssh_helper.get_name(), cmd, e)) + self.stdio.error("[remote host {0}] run ssh cmd = [{1}] except: {2}".format(ssh_helper.get_name(), cmd, e)) def run_ignore_err(self, ssh_helper, cmd): try: - logger.info("[remote host {0}] run cmd = [{1}] start ...".format(ssh_helper.get_name(), cmd)) + self.stdio.verbose("[remote host {0}] run cmd = [{1}] start ...".format(ssh_helper.get_name(), cmd)) std = ssh_helper.ssh_exec_cmd_ignore_err(cmd) return std except SSHException as e: - logger.error("[remote host {0}] run ssh cmd = [{1}] except: {2}".format(ssh_helper.get_name(), cmd, e)) + self.stdio.error("[remote host {0}] run ssh cmd = [{1}] except: {2}".format(ssh_helper.get_name(), cmd, e)) -def get_file_size(is_ssh, ssh_helper, dir): - """ - get the size of the file - :param: is_ssh, ssh helper, dir - :return: file size - """ - cmd = "ls -nl %s | awk '{print $5}'" % dir - if is_ssh: - file_size = SshClient().run(ssh_helper, cmd) - else: - file_size = LocalClient().run(cmd) - return file_size - - -def download_file(is_ssh, ssh_helper, remote_path, local_path): +def download_file(is_ssh, ssh_helper, remote_path, local_path, stdio=None): """ download file :param args: is_ssh, ssh helper, file path :return: local path """ - logger.info( - "Please wait a moment, download file [{0}] from server {1} to [{2}]".format(remote_path, ssh_helper.get_name(), - local_path)) try: if is_ssh: + stdio.verbose("Please wait a moment, download file [{0}] from server {1} to [{2}]".format(remote_path, ssh_helper.get_name(),local_path)) ssh_helper.download(remote_path, local_path) else: cmd = "cp -r {0} {1}".format(remote_path, local_path) - LocalClient().run(cmd) + LocalClient(stdio).run(cmd) except Exception as e: - logger.error("Download File Failed error: {0}".format(e)) + stdio.error("Download File Failed error: {0}".format(e)) return local_path -def upload_file(is_ssh, ssh_helper, local_path, remote_path): +def upload_file(is_ssh, ssh_helper, local_path, remote_path, stdio=None): """ upload file :param args: is_ssh, ssh helper, local file path, remote file path :return: local path """ - logger.info("Please wait a moment, upload file to server {0}, local file path {1}, remote file path {2}".format( + stdio.verbose("Please wait a moment, upload file to server {0}, local file path {1}, remote file path {2}".format( ssh_helper.get_name(), local_path, remote_path)) try: if is_ssh: ssh_helper.upload(local_path, remote_path) else: cmd = "cp -r {0} {1}".format(local_path, remote_path) - LocalClient().run(cmd) + LocalClient(stdio).run(cmd) except Exception as e: - logger.error("Upload File Failed error: {0}".format(e)) + stdio.error("Upload File Failed error: {0}".format(e)) -def rm_rf_file(is_ssh, ssh_helper, dir): +def rm_rf_file(is_ssh, ssh_helper, dir, stdio=None): """ delete file :param args: is_ssh, ssh helper, gather log full path @@ -136,12 +122,26 @@ def rm_rf_file(is_ssh, ssh_helper, dir): """ cmd = "rm -rf {0}".format(dir) if is_ssh: - SshClient().run(ssh_helper, cmd) + SshClient(stdio).run(ssh_helper, cmd) + else: + LocalClient(stdio).run(cmd) + +def delete_file_in_folder(is_ssh, ssh_helper, file_path, stdio): + """ + delete file + :param args: is_ssh, ssh helper, file_name + :return: + """ + if (file_path is None) or (not file_path.startswith('gather_pack')): + raise Exception("Please check file path, {0}".format(file_path)) + cmd = "rm -rf {file_path}/*".format(file_path=file_path) + if is_ssh: + SshClient(stdio).run(ssh_helper, cmd) else: - LocalClient().run(cmd) + LocalClient(stdio).run(cmd) -def is_empty_dir(is_ssh, ssh_helper, dir): +def is_empty_dir(is_ssh, ssh_helper, dir, stdio=None): """ determine whether it is an empty folder :param args: is_ssh, ssh helper, gather log full path @@ -149,16 +149,16 @@ def is_empty_dir(is_ssh, ssh_helper, dir): """ cmd = "ls -A {gather_path}|wc -w".format(gather_path=dir) if is_ssh: - file_num = SshClient().run(ssh_helper, cmd) + file_num = SshClient(stdio).run(ssh_helper, cmd) else: - file_num = LocalClient().run(cmd) + file_num = LocalClient(stdio).run(cmd) if int(file_num) == 0: return True else: return False -def get_file_start_time(is_ssh, ssh_helper, file_name, dir): +def get_file_start_time(is_ssh, ssh_helper, file_name, dir, stdio=None): """ get log file start time :param args: is_ssh, ssh helper, gather log full path @@ -166,35 +166,36 @@ def get_file_start_time(is_ssh, ssh_helper, file_name, dir): """ cmd = "head -n 1 {0}/{1}".format(dir, file_name) if is_ssh: - first_line_text = SshClient().run(ssh_helper, cmd) + first_line_text = SshClient(stdio).run(ssh_helper, cmd) else: - first_line_text = LocalClient().run(cmd) - return extract_time_from_log_file_text(str(first_line_text)) + first_line_text = LocalClient(stdio).run(cmd) + return TimeUtils.extract_time_from_log_file_text(str(first_line_text)) -def get_logfile_name_list(is_ssh, ssh_helper, from_time_str, to_time_str, log_dir, log_files): +def get_logfile_name_list(is_ssh, ssh_helper, from_time_str, to_time_str, log_dir, log_files, stdio=None): """ get log name list :param args: is_ssh, ssh helper, from time, to time, log dir, log file list :return: true or false """ + stdio.verbose("get log file name list, from time {0}, to time {1}, log dir {2}, log files {3}".format(from_time_str, to_time_str, log_dir, log_files)) log_name_list = [] last_file_dict = {"prefix_file_name": "", "file_name": "", "file_end_time": ""} for file_name in log_files.split('\n'): if file_name == "": - logger.warn("existing file name is empty") + stdio.verbose("existing file name is empty") continue if not file_name.endswith("log") and not file_name.endswith("wf"): file_start_time_str = "" prefix_name = file_name[:-14] if len(file_name) > 24 else "" - file_end_time_str = filename_time_to_datetime(extract_filename_time_from_log_name(file_name)) + file_end_time_str = TimeUtils.filename_time_to_datetime(TimeUtils.extract_filename_time_from_log_name(file_name, stdio), stdio) if last_file_dict["prefix_file_name"] != "" and last_file_dict["prefix_file_name"] == prefix_name: file_start_time_str = last_file_dict["file_end_time"] elif last_file_dict["prefix_file_name"] != "" and last_file_dict["prefix_file_name"] != prefix_name: file_start_time_str = "" file_end_time_str = "" elif last_file_dict["prefix_file_name"] == "": - file_start_time_str = get_file_start_time(is_ssh, ssh_helper, file_name, log_dir) + file_start_time_str = get_file_start_time(is_ssh, ssh_helper, file_name, log_dir, stdio) # When two time intervals overlap, need to add the file if (file_end_time_str != "") and (file_start_time_str != "") and (file_start_time_str <= to_time_str) and ( file_end_time_str >= from_time_str): @@ -202,13 +203,12 @@ def get_logfile_name_list(is_ssh, ssh_helper, from_time_str, to_time_str, log_di last_file_dict = {"prefix_file_name": prefix_name, "file_name": file_name, "file_end_time": file_end_time_str} elif file_name.endswith("log") or file_name.endswith("wf"): - logger.info("Filter online file %s on server", file_name) # Get the first and last lines of text of the file. Here, use a command get_first_line_cmd = "head -n 1 {0}/{1} && tail -n 1 {0}/{1}".format(log_dir, file_name) if is_ssh: - first_and_last_line_text = SshClient().run(ssh_helper, get_first_line_cmd) + first_and_last_line_text = SshClient(stdio).run(ssh_helper, get_first_line_cmd) else: - first_and_last_line_text = LocalClient().run(get_first_line_cmd) + first_and_last_line_text = LocalClient(stdio).run(get_first_line_cmd) # Split the first and last lines of text first_and_last_line_text_list = str(first_and_last_line_text).splitlines() @@ -217,21 +217,22 @@ def get_logfile_name_list(is_ssh, ssh_helper, from_time_str, to_time_str, log_di last_line_text = first_and_last_line_text_list[-1] # Time to parse the first and last lines of text - file_start_time_str = extract_time_from_log_file_text(first_line_text) - file_end_time = extract_time_from_log_file_text(last_line_text) + file_start_time_str = TimeUtils.extract_time_from_log_file_text(first_line_text, stdio) + file_end_time = TimeUtils.extract_time_from_log_file_text(last_line_text, stdio) + stdio.verbose("The log file {0} starts at {1} ends at {2}".format(file_name, file_start_time_str,file_end_time)) + stdio.verbose("to_time_str {0} from_time_str {1}".format(to_time_str, from_time_str)) if (file_start_time_str <= to_time_str) and (file_end_time >= from_time_str): log_name_list.append(file_name) if len(log_name_list) > 0: - logger.info("Find the qualified log file {0} on Server [{1}], " + stdio.verbose("Find the qualified log file {0} on Server [{1}], " "wait for the next step".format(log_name_list, "localhost" if not is_ssh else ssh_helper.get_name())) else: - logger.warn("Failed to find the qualified log file on Server [{0}], " - "please check whether the input parameters are correct. ".format( + stdio.warn("No found the qualified log file on Server [{0}]".format( "localhost" if not is_ssh else ssh_helper.get_name())) return log_name_list -def mkdir(is_ssh, ssh_helper, dir): +def mkdir(is_ssh, ssh_helper, dir, stdio=None): """ Create a folder when it does not exist :param args: is_ssh, ssh helper, folder path @@ -239,12 +240,12 @@ def mkdir(is_ssh, ssh_helper, dir): """ cmd = "mkdir -p {0}".format(dir) if is_ssh: - SshClient().run(ssh_helper, cmd) + SshClient(stdio).run(ssh_helper, cmd) else: - LocalClient().run(cmd) + LocalClient(stdio).run(cmd) -def delete_empty_file(is_ssh, ssh_helper, dir): +def delete_empty_file(is_ssh, ssh_helper, dir, stdio=None): """ delete empty folder :param args: is_ssh, ssh helper, gather log full path @@ -252,12 +253,12 @@ def delete_empty_file(is_ssh, ssh_helper, dir): """ cmd = "find {dir} -name '*' -type f -size 0c | xargs -n 1 rm -f".format(dir=dir) if is_ssh: - SshClient().run(ssh_helper, cmd) + SshClient(stdio).run(ssh_helper, cmd) else: - LocalClient().run(cmd) + LocalClient(stdio).run(cmd) -def zip_dir(is_ssh, ssh_helper, father_dir, zip_dir): +def zip_dir(is_ssh, ssh_helper, father_dir, zip_dir, stdio=None): """ Compress files through zip :param args: is_ssh, ssh helper, father dir, zip dir @@ -266,14 +267,14 @@ def zip_dir(is_ssh, ssh_helper, father_dir, zip_dir): cmd = "cd {father_dir} && zip {zip_dir}.zip -rm {zip_dir}".format( father_dir=father_dir, zip_dir=zip_dir) - logger.info("Please wait a moment ...") + stdio.verbose("Please wait a moment ...") if is_ssh: - SshClient().run(ssh_helper, cmd) + SshClient(stdio).run(ssh_helper, cmd) else: - LocalClient().run(cmd) + LocalClient(stdio).run(cmd) -def zip_encrypt_dir(is_ssh, ssh_helper, zip_password, father_dir, zip_dir): +def zip_encrypt_dir(is_ssh, ssh_helper, zip_password, father_dir, zip_dir, stdio=None): """ Compress files by encryption :param args: is_ssh, ssh helper, password, raw_log_dir, gather dir name @@ -283,14 +284,13 @@ def zip_encrypt_dir(is_ssh, ssh_helper, zip_password, father_dir, zip_dir): zip_password=zip_password, father_dir=father_dir, zip_dir=zip_dir) - logger.info("Please wait a moment ...") + stdio.verbose("Please wait a moment ...") if is_ssh: - SshClient().run(ssh_helper, cmd) + SshClient(stdio).run(ssh_helper, cmd) else: - LocalClient().run(cmd) - + LocalClient(stdio).run(cmd) -def is_support_arch(is_ssh, ssh_helper): +def is_support_arch(is_ssh, ssh_helper, stdio=None): """ Determine if it is a supported operating system :param args: is_ssh, ssh helper @@ -300,19 +300,19 @@ def is_support_arch(is_ssh, ssh_helper): cmd = "arch" try: if is_ssh: - arch_info = SshClient().run(ssh_helper, cmd) + arch_info = SshClient(stdio).run(ssh_helper, cmd) else: - arch_info = LocalClient().run(cmd) + arch_info = LocalClient(stdio).run(cmd) if arch_info.replace("\n", "") in support_arch_list: return True else: return False except: - logger.error("get server arch info failed") + stdio.error("get server arch info failed") return False -def get_observer_version(is_ssh, ssh_helper, ob_install_dir): +def get_observer_version(is_ssh, ssh_helper, ob_install_dir, stdio): """ get observer version :param args: is_ssh, ssh helper, ob install dir @@ -321,10 +321,10 @@ def get_observer_version(is_ssh, ssh_helper, ob_install_dir): ob_version = "" cmd = "{ob_install_dir}/bin/observer --version".format(ob_install_dir=ob_install_dir) if is_ssh: - ob_version_info = SshClient().run_get_stderr(ssh_helper, cmd) + ob_version_info = SshClient(stdio).run_get_stderr(ssh_helper, cmd) else: - ob_version_info = LocalClient().run_get_stderr(cmd) - logger.info("get observer version, run cmd = [{0}] ".format(cmd)) + ob_version_info = LocalClient(stdio).run_get_stderr(cmd) + stdio.verbose("get observer version, run cmd = [{0}] ".format(cmd)) if ob_version_info is not None: ob_version = re.findall(r'[(]OceanBase.(.+?)[)]', ob_version_info) if len(ob_version) > 0: @@ -334,10 +334,10 @@ def get_observer_version(is_ssh, ssh_helper, ob_install_dir): cmd = "export LD_LIBRARY_PATH={ob_install_dir}/lib && {ob_install_dir}/bin/observer --version".format( ob_install_dir=ob_install_dir) if is_ssh: - ob_version_info = SshClient().run_get_stderr(ssh_helper, cmd) + ob_version_info = SshClient(stdio).run_get_stderr(ssh_helper, cmd) else: - ob_version_info = LocalClient().run_get_stderr(cmd) - logger.info("get observer version with LD_LIBRARY_PATH,cmd:{0}".format(cmd)) + ob_version_info = LocalClient(stdio).run_get_stderr(cmd) + stdio.verbose("get observer version with LD_LIBRARY_PATH,cmd:{0}".format(cmd)) if "REVISION" not in ob_version_info: raise Exception("Please check conf about observer,{0}".format(ob_version_info)) ob_version = re.findall(r'[(]OceanBase.*\s(.+?)[)]', ob_version_info) @@ -345,7 +345,7 @@ def get_observer_version(is_ssh, ssh_helper, ob_install_dir): return result.strip() -def get_obproxy_version(is_ssh, ssh_helper, obproxy_install_dir): +def get_obproxy_version(is_ssh, ssh_helper, obproxy_install_dir, stdio): """ get obproxy version :param args: is_ssh, ssh helper, ob install dir @@ -354,10 +354,10 @@ def get_obproxy_version(is_ssh, ssh_helper, obproxy_install_dir): obproxy_version = "" cmd = "{obproxy_install_dir}/bin/obproxy --version".format(obproxy_install_dir=obproxy_install_dir) if is_ssh: - obproxy_version_info = SshClient().run_get_stderr(ssh_helper, cmd) + obproxy_version_info = SshClient(stdio).run_get_stderr(ssh_helper, cmd) else: - obproxy_version_info = LocalClient().run_get_stderr(cmd) - logger.debug("get obproxy version, run cmd = [{0}] ".format(cmd)) + obproxy_version_info = LocalClient(stdio).run_get_stderr(cmd) + stdio.verbose("get obproxy version, run cmd = [{0}] ".format(cmd)) if obproxy_version_info is not None: ob_version = re.findall(r'[(]OceanBase.(.+? +?)[)]', obproxy_version_info) if len(ob_version) > 0: @@ -366,10 +366,10 @@ def get_obproxy_version(is_ssh, ssh_helper, obproxy_install_dir): cmd = "export LD_LIBRARY_PATH={obproxy_install_dir}/lib && {obproxy_install_dir}/bin/obproxy --version".format( obproxy_install_dir=obproxy_install_dir) if is_ssh: - obproxy_version_info = SshClient().run_get_stderr(ssh_helper, cmd) + obproxy_version_info = SshClient(stdio).run_get_stderr(ssh_helper, cmd) else: - obproxy_version_info = LocalClient().run_get_stderr(cmd) - logger.debug("get obproxy version with LD_LIBRARY_PATH,cmd:{0}, result:{1}".format(cmd,obproxy_version_info)) + obproxy_version_info = LocalClient(stdio).run_get_stderr(cmd) + stdio.verbose("get obproxy version with LD_LIBRARY_PATH,cmd:{0}, result:{1}".format(cmd,obproxy_version_info)) if "REVISION" not in obproxy_version_info: raise Exception("Please check conf about proxy,{0}".format(obproxy_version_info)) pattern = r"(\d+\.\d+\.\d+\.\d+)" @@ -385,28 +385,30 @@ def get_obproxy_version(is_ssh, ssh_helper, obproxy_install_dir): return obproxy_version_info # Only applicable to the community version -def get_observer_version_by_sql(ob_cluster): - logger.debug("start get_observer_version_by_sql . input: {0}".format(ob_cluster)) +def get_observer_version_by_sql(ob_cluster, stdio=None): + stdio.verbose("start get_observer_version_by_sql . input: {0}".format(ob_cluster)) try: - ob_connector = OBConnector(ip=ob_cluster["host"], - port=ob_cluster["port"], - username=ob_cluster["user"], - password=ob_cluster["password"], + ob_connector = OBConnector(ip=ob_cluster.get("db_host"), + port=ob_cluster.get("db_port"), + username=ob_cluster.get("tenant_sys").get("user"), + password=ob_cluster.get("tenant_sys").get("password"), + stdio=stdio, timeout=100) ob_version_info = ob_connector.execute_sql("select version();") except Exception as e: raise Exception("get_observer_version_by_sql Exception. Maybe cluster'info is error: " + e.__str__()) ob_version = ob_version_info[0] - logger.debug("get_observer_version_by_sql ob_version_info is {0}".format(ob_version)) + stdio.verbose("get_observer_version_by_sql ob_version_info is {0}".format(ob_version)) version = re.findall(r'OceanBase(_)?(.CE)?-v(.+)', ob_version[0]) if len(version) > 0: return version[0][2] else: - return None + version = re.findall(r'(.+)', ob_version[0]) + return version[0] -def get_observer_pid(is_ssh, ssh_helper, ob_install_dir): +def get_observer_pid(is_ssh, ssh_helper, ob_install_dir, stdio=None): """ get observer pid :param args: is_ssh, ssh helper, ob install dir @@ -415,18 +417,18 @@ def get_observer_pid(is_ssh, ssh_helper, ob_install_dir): try: cmd = "cat {ob_install_dir}/run/observer.pid".format(ob_install_dir=ob_install_dir) if is_ssh: - pids = SshClient().run(ssh_helper, cmd) + pids = SshClient(stdio).run(ssh_helper, cmd) else: - pids = LocalClient().run(cmd) + pids = LocalClient(stdio).run(cmd) pid_list = pids.split() - logger.info("get observer pid, run cmd = [{0}], result:{1} ".format(cmd, pid_list)) + stdio.verbose("get observer pid, run cmd = [{0}], result:{1} ".format(cmd, pid_list)) except: - logger.info("get observer pid failed") + stdio.verbose("get observer pid failed") return [] return pid_list -def delete_file_force(is_ssh, ssh_helper, file_name): +def delete_file_force(is_ssh, ssh_helper, file_name, stdio=None): """ delete file force :param args: is_ssh, ssh helper, file_name @@ -434,12 +436,12 @@ def delete_file_force(is_ssh, ssh_helper, file_name): """ cmd = "rm -rf {0}".format(file_name) if is_ssh: - SshClient().run(ssh_helper, cmd) + SshClient(stdio).run(ssh_helper, cmd) else: - LocalClient().run(cmd) + LocalClient(stdio).run(cmd) -def delete_empty_file(is_ssh, ssh_helper, file_path): +def delete_empty_file(is_ssh, ssh_helper, file_path, stdio=None): """ delete empty file :param args: is_ssh, ssh helper, file_name @@ -447,12 +449,12 @@ def delete_empty_file(is_ssh, ssh_helper, file_path): """ cmd = "find {file_path} -name '*' -type f -size 0c | xargs -n 1 rm -f".format(file_path=file_path) if is_ssh: - SshClient().run(ssh_helper, cmd) + SshClient(stdio).run(ssh_helper, cmd) else: - LocalClient().run(cmd) + LocalClient(stdio).run(cmd) -def delete_file(is_ssh, ssh_helper, file_path): +def delete_file(is_ssh, ssh_helper, file_path, stdio=None): """ delete file :param args: is_ssh, ssh helper, file_name @@ -460,12 +462,12 @@ def delete_file(is_ssh, ssh_helper, file_path): """ cmd = "rm -rf {file_path}".format(file_path=file_path) if is_ssh: - SshClient().run(ssh_helper, cmd) + SshClient(stdio).run(ssh_helper, cmd) else: - LocalClient().run(cmd) + LocalClient(stdio).run(cmd) -def get_file_size(is_ssh, ssh_helper, file_path): +def get_file_size(is_ssh, ssh_helper, file_path, stdio=None): """ get file size :param args: is_ssh, ssh helper, file_path @@ -473,13 +475,13 @@ def get_file_size(is_ssh, ssh_helper, file_path): """ cmd = "ls -nl %s | awk '{print $5}'" % file_path if is_ssh: - file_size = SshClient().run(ssh_helper, cmd) + file_size = SshClient(stdio).run(ssh_helper, cmd) else: - file_size = LocalClient().run(cmd) + file_size = LocalClient(stdio).run(cmd) return file_size -def is_empty_dir(is_ssh, ssh_helper, dir_path): +def is_empty_dir(is_ssh, ssh_helper, dir_path, stdio=None): """ is empty dir :param args: is_ssh, ssh helper, dir_path @@ -487,31 +489,78 @@ def is_empty_dir(is_ssh, ssh_helper, dir_path): """ cmd = "ls -A {dir_path}|wc -w".format(dir_path=dir_path) if is_ssh: - file_num = SshClient().run(ssh_helper, cmd) + file_num = SshClient(stdio).run(ssh_helper, cmd) else: - file_num = LocalClient().run(cmd) + file_num = LocalClient(stdio).run(cmd) if int(file_num) == 0: return True else: return False -def is_empty_file(is_ssh, ssh_helper, file_path): +def is_empty_file(is_ssh, ssh_helper, file_path, stdio=None): """ is empty file :param args: is_ssh, ssh helper, file_path :return: True or False """ - file_size = get_file_size(is_ssh, ssh_helper, file_path) + file_size = get_file_size(is_ssh, ssh_helper, file_path, stdio) if int(file_size) == 0: return True else: return False -def get_obdiag_display(log_dir, trace_id): +def get_obdiag_display(log_dir, trace_id, stdio=None): cmd = 'grep -h "\[{}\]" {}* | sed "s/\[{}\] //g" '.format(trace_id, log_dir, trace_id) - stdout = LocalClient().run(cmd) + stdout = LocalClient(stdio).run(cmd) print_stdout = str(stdout).replace('\\n', '\n').replace('\\t', '\t') if len(print_stdout) > 0: print(print_stdout) + + +def uzip_dir_local(uzip_dir, stdio=None): + """ + Uncompress files through zip + :param args: father dir, zip dir + :return: + """ + cmd = f"cd {uzip_dir} && unzip *.zip && rm -rf *.zip" + LocalClient(stdio).run(cmd) + +def analyze_log_get_sqc_addr(uzip_dir, stdio): + """ + analyze files + :param args: father dir, uzip dir + :return: ip_port + """ + cmd = "cd {uzip_dir} && cd ob_log* && grep {key_words} * | grep -oP '{key_words}=\"\\K[^\"]+' | sort | uniq".format( + uzip_dir=uzip_dir, + key_words = "px_obdiag_sqc_addr") + stdout = LocalClient(stdio).run(cmd) + sqc_addrs = stdout.decode().strip().split('\n') + if len(sqc_addrs) > 0: + if not re.match(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', sqc_addrs[0]): + return None + else: + return sqc_addrs[0] + else: + return None + +def find_home_path_by_port(is_ssh, ssh_helper, internal_port_str, stdio): + cmd = "ps aux | grep observer | grep 'P {internal_port_str}' | grep -oP '/[^\s]*/bin/observer' ".format( + internal_port_str = internal_port_str) + if is_ssh: + stdout = SshClient(stdio).run(ssh_helper, cmd) + else: + stdout = LocalClient(stdio).run(cmd) + + str_list = stdout.strip().split('\n') + home_path = "" + for original_str in str_list: + original_str = str(original_str) + if original_str.endswith("/bin/observer") and not original_str.startswith('/[^\s]*'): + home_path = original_str.rstrip("/bin/observer") + break + stdio.verbose("home_path:{0}".format(home_path)) + return home_path diff --git a/common/config_helper.py b/common/config_helper.py index 858a209d..dacce797 100644 --- a/common/config_helper.py +++ b/common/config_helper.py @@ -22,48 +22,56 @@ from common.command import get_observer_version_by_sql from common.constant import const -from common.logger import logger from common.ob_connector import OBConnector -from utils.file_utils import mkdir_if_not_exist -from utils.time_utils import timestamp_to_filename_time -from utils.utils import split_ip -from utils.yaml_utils import write_yaml_data, read_yaml_data, write_yaml_data_append +from common.tool import DirectoryUtil +from common.tool import TimeUtils +from common.tool import StringUtils +from common.tool import YamlUtils +from common.tool import Util class ConfigHelper(object): - def __init__(self, sys_tenant_user, sys_tenant_password, db_host, db_port): - self.sys_tenant_user = sys_tenant_user - self.sys_tenant_password = sys_tenant_password - self.db_host = db_host - self.db_port = db_port + def __init__(self, context): + self.context = context + self.stdio = context.stdio + options = self.context.options + self.sys_tenant_user = Util.get_option(options, 'u') + self.sys_tenant_password = Util.get_option(options, 'p') + self.db_host = Util.get_option(options, 'h') + self.db_port = Util.get_option(options, 'P') + self.config_path = os.path.expanduser('~/.obdiag/config.yml') + self.inner_config = self.context.inner_config + self.ob_cluster = {"db_host": self.db_host, "db_port": self.db_port, "tenant_sys": {"password": self.sys_tenant_password, "user": self.sys_tenant_user, }} def get_cluster_name(self): - ob_cluster = {"host": self.db_host, "port": self.db_port, "user": self.sys_tenant_user, - "password": self.sys_tenant_password} - ob_version = get_observer_version_by_sql(ob_cluster) - obConnetcor = OBConnector(ip=self.db_host, port=self.db_port, username=self.sys_tenant_user, - password=self.sys_tenant_password, timeout=100) + ob_version = get_observer_version_by_sql(self.ob_cluster, self.stdio) + obConnetcor = OBConnector( + ip=self.db_host, + port=self.db_port, + username=self.sys_tenant_user, + password=self.sys_tenant_password, + stdio=self.stdio, + timeout=100) if ob_version.startswith("3") or ob_version.startswith("2"): sql = "select cluster_name from oceanbase.v$ob_cluster" res = obConnetcor.execute_sql(sql) if len(res) == 0: - logger.error("Failed to get cluster name, please check whether the cluster config correct!!!") + self.stdio.error("Failed to get cluster name, please check whether the cluster config correct!!!") else: return res[0][0] else: return "obcluster" - def get_host_info_list_by_cluster(self, args): - ob_cluster = {"host": self.db_host, "port": self.db_port, "user": self.sys_tenant_user, - "password": self.sys_tenant_password} - ob_version = get_observer_version_by_sql(ob_cluster) + def get_host_info_list_by_cluster(self): + ob_version = get_observer_version_by_sql(self.ob_cluster, self.stdio) obConnetcor = OBConnector(ip=self.db_host, port=self.db_port, username=self.sys_tenant_user, password=self.sys_tenant_password, + stdio=self.stdio, timeout=100) sql = "select SVR_IP, SVR_PORT, ZONE, BUILD_VERSION from oceanbase.DBA_OB_SERVERS" - if ob_version.startswith("3"): + if ob_version.startswith("3") or ob_version.startswith("2") or ob_version.startswith("1"): sql = "select SVR_IP, SVR_PORT, ZONE, BUILD_VERSION from oceanbase.__all_server" res = obConnetcor.execute_sql(sql) if len(res) == 0: @@ -73,14 +81,14 @@ def get_host_info_list_by_cluster(self, args): for row in res: host_info = OrderedDict() host_info["ip"] = row[0] - logger.debug("get host info: %s", host_info) + self.stdio.verbose("get host info: %s", host_info) host_info_list.append(host_info) return host_info_list - def build_configuration(self, args, path, inner_path): - logger.info("Getting all the node information of the cluster, please wait a moment ...") - all_host_info_list = self.get_host_info_list_by_cluster(args) - logger.debug("get node list %s", all_host_info_list) + def build_configuration(self): + self.stdio.verbose("Getting all the node information of the cluster, please wait a moment ...") + all_host_info_list = self.get_host_info_list_by_cluster() + self.stdio.verbose("get node list %s", all_host_info_list) all_host_ip_list = [] for host in all_host_info_list: all_host_ip_list.append(host["ip"]) @@ -89,10 +97,9 @@ def build_configuration(self, args, path, inner_path): nodes_config = [] for i in all_host_ip_list: nodes_config.append({"ip": i}) - old_config = self.get_old_configuration(path) - inner_config = self.get_old_configuration(inner_path) + old_config = self.get_old_configuration(self.config_path) # backup old config - self.save_old_configuration(old_config, inner_config) + self.save_old_configuration(old_config) # rewrite config ob_cluster_name = self.get_cluster_name() print("\033[33mPlease enter the following configuration !!!\033[0m") @@ -123,17 +130,15 @@ def build_configuration(self, args, path, inner_path): "nodes": nodes_config, "global": global_config }}} - write_yaml_data(new_config, path) + YamlUtils.write_yaml_data(new_config, self.config_path) need_config_obproxy = self.input_choice_default("need config obproxy [y/N]", "N") if need_config_obproxy: - self.build_obproxy_configuration(path) - logger.info( - "Node information has been rewritten to the configuration file {0}, and you can enjoy the journey !".format( - path)) + self.build_obproxy_configuration(self.config_path) + self.stdio.verbose("Node information has been rewritten to the configuration file {0}, and you can enjoy the journey !".format(self.config_path)) def build_obproxy_configuration(self, path): obproxy_servers = self.input_with_default("obproxy server eg:'192.168.1.1;192.168.1.2;192.168.1.3'", "") - obproxy_server_list = split_ip(obproxy_servers) + obproxy_server_list = StringUtils.split_ip(obproxy_servers) if len(obproxy_server_list) > 0: nodes_config = [] for server in obproxy_server_list: @@ -156,21 +161,21 @@ def build_obproxy_configuration(self, path): "nodes": nodes_config, "global": global_config }}} - write_yaml_data_append(new_config, path) + YamlUtils.write_yaml_data_append(new_config, path) def get_old_configuration(self, path): try: - data = read_yaml_data(path) + data = YamlUtils.read_yaml_data(path) return data except: pass - def save_old_configuration(self, config, inner_config): - backup_config_dir = os.path.expanduser(inner_config["obdiag"]["basic"]["config_backup_dir"]) - filename = "config_backup_{0}.yml".format(timestamp_to_filename_time(int(round(time.time() * 1000000)))) + def save_old_configuration(self, config): + backup_config_dir = os.path.expanduser(self.inner_config["obdiag"]["basic"]["config_backup_dir"]) + filename = "config_backup_{0}.yml".format(TimeUtils.timestamp_to_filename_time(int(round(time.time() * 1000000)))) backup_config_path = os.path.join(backup_config_dir, filename) - mkdir_if_not_exist(backup_config_dir) - write_yaml_data(config, backup_config_path) + DirectoryUtil.mkdir(path=backup_config_dir) + YamlUtils.write_yaml_data(config, backup_config_path) def input_with_default(self, prompt, default): value = input("\033[32mEnter your {0} (default:'{1}'): \033[0m".format(prompt, default)).strip() diff --git a/common/constant.py b/common/constant.py index 74364dad..4f7b9755 100644 --- a/common/constant.py +++ b/common/constant.py @@ -109,4 +109,5 @@ def __setattr__(self, name, value): const.TELEMETRY_PATH = "/api/web/oceanbase/report" const.UPDATE_REMOTE_SERVER = 'https://obbusiness-private.oss-cn-shanghai.aliyuncs.com' const.UPDATE_REMOTE_VERSION_FILE_NAME = 'https://obbusiness-private.oss-cn-shanghai.aliyuncs.com/download-center/opensource/obdiag/version.yaml' -const.UPDATE_REMOTE_UPDATE_FILE_NAME = 'https://obbusiness-private.oss-cn-shanghai.aliyuncs.com/download-center/opensource/obdiag/data.tar' \ No newline at end of file +const.UPDATE_REMOTE_UPDATE_FILE_NAME = 'https://obbusiness-private.oss-cn-shanghai.aliyuncs.com/download-center/opensource/obdiag/data.tar' +const.RCA_WORK_PATH= '~/.obdiag/rca' \ No newline at end of file diff --git a/handler/base_sql_handler.py b/common/log.py similarity index 58% rename from handler/base_sql_handler.py rename to common/log.py index 7e5fb070..4a98b80e 100644 --- a/handler/base_sql_handler.py +++ b/common/log.py @@ -11,15 +11,20 @@ # See the Mulan PSL v2 for more details. """ -@time: 2022/11/29 -@file: base_sql_handler.py +@file: log.py @desc: """ -from common.ob_connector import OBConnector +from __future__ import absolute_import, division, print_function +import logging -# 通过sql连接到集群中去获取sql性能数据的都继承自该handler -class BaseSQLHandler(object): - def __init__(self): - pass +class Logger(logging.Logger): + + def __init__(self, name, level=logging.DEBUG): + super(Logger, self).__init__(name, level) + self.buffer = [] + self.buffer_size = 0 + + def _log(self, level, msg, args, end='\n', **kwargs): + return super(Logger, self)._log(level, msg, args, **kwargs) \ No newline at end of file diff --git a/common/logger.py b/common/logger.py deleted file mode 100644 index 39cd57c1..00000000 --- a/common/logger.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2022/6/20 -@file: logger.py -@desc: -""" -import logging -import os -import sys -import uuid -from logging.handlers import TimedRotatingFileHandler - -from common.constant import const -from utils.file_utils import mkdir_if_not_exist -from utils.yaml_utils import read_yaml_data - -if getattr(sys, 'frozen', False): - absPath = os.path.dirname(os.path.abspath(sys.executable)) -else: - absPath = os.path.dirname(os.path.abspath(__file__)) -INNER_CONF_FILE = os.path.join(absPath, "conf/inner_config.yml") - -class Logger(object): - def __init__(self, log_config_dict): - self.logger = logging.getLogger() - try: - self.logger.setLevel(log_config_dict["obdiag"]["logger"]["log_level"].upper()) - except Exception as e: - raise ValueError("Invalid log level setting, error:{0} only supported set ['DEBUG','INFO','WARN','ERROR'], " - "Please modify conf/config.yml".format(e)) - log_dir = os.path.expanduser(log_config_dict["obdiag"]["logger"]["log_dir"]) - mkdir_if_not_exist(log_dir) - log_filename = log_config_dict["obdiag"]["logger"]["log_filename"] - log_format = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s') - file_format = logging.Formatter( - '[%%(asctime)s] [%%(levelname)s] [%s] [%%(filename)s->line:%%(lineno)d] %%(message)s' % uuid.uuid3( - uuid.NAMESPACE_DNS, str(os.getpid()))) - self.file_handler = TimedRotatingFileHandler(os.path.join(log_dir, log_filename), - when='D', interval=1, backupCount=30, encoding='utf-8') - self.file_handler.setFormatter(file_format) - try: - self.file_handler.setLevel(log_config_dict["obdiag"]["logger"]["file_handler_log_level"].upper()) - except Exception as e: - raise ValueError("Invalid log level setting, error:{0} only supported set ['DEBUG','INFO','WARN','ERROR'], " - "Please modify conf/config.yml".format(e)) - self.stdout_handler = logging.StreamHandler(sys.stdout) - try: - self.stdout_handler.setLevel(log_config_dict["obdiag"]["logger"]["stdout_handler_log_level"].upper()) - except Exception as e: - raise ValueError("Invalid log level setting, error:{0} only supported set ['DEBUG','INFO','WARN','ERROR'], " - "Please modify conf/config.yml".format(e)) - self.stdout_handler.setFormatter(log_format) - self.logger.addHandler(self.file_handler) - self.logger.addHandler(self.stdout_handler) - - def get_logger(self): - return self.logger - - -inner_config = const.OBDIAG_BASE_DEFAULT_CONFIG -if os.path.exists(INNER_CONF_FILE): - inner_config = read_yaml_data(INNER_CONF_FILE) -logger = Logger(inner_config).get_logger() - -if __name__ == "__main__": - logger.debug("tests debug") - logger.info("tests 1") - logger.error("test2") - logger.warning("tests 3") diff --git a/common/ob_connector.py b/common/ob_connector.py index b2279651..a1590ee0 100644 --- a/common/ob_connector.py +++ b/common/ob_connector.py @@ -17,28 +17,27 @@ """ from prettytable import from_db_cursor import pymysql as mysql -from common.logger import logger class OBConnector(object): - def __init__(self, ip, port, username, password=None, timeout=10): + def __init__(self, ip, port, username, password=None, stdio=None, timeout=10,): self.ip = str(ip) self.port = int(port) self.username = str(username) self.password = str(password) self.timeout = timeout self.conn = None + self.stdio = stdio self.init() def init(self): try: self._connect_db() except Exception as e: - logger.exception(e) + self.stdio.verbose(e) def _connect_db(self): try: - logger.debug("connect OB: {0}:{1} with user {2}".format(self.ip, self.port, self.username)) self.conn = mysql.connect( host=self.ip, port=self.port, @@ -46,9 +45,9 @@ def _connect_db(self): passwd=self.password, connect_timeout=30, ) - logger.debug("connect databse ...") + self.stdio.verbose("connect databse ...") except mysql.Error as e: - logger.error("connect OB: {0}:{1} with user {2} failed, error:{3}".format(self.ip, self.port, self.username, e)) + self.stdio.error("connect OB: {0}:{1} with user {2} failed, error:{3}".format(self.ip, self.port, self.username, e)) def execute_sql(self, sql): if self.conn is None: diff --git a/ocp/__init__.py b/common/ocp/__init__.py similarity index 100% rename from ocp/__init__.py rename to common/ocp/__init__.py diff --git a/ocp/ocp_api.py b/common/ocp/ocp_api.py similarity index 100% rename from ocp/ocp_api.py rename to common/ocp/ocp_api.py diff --git a/ocp/ocp_task.py b/common/ocp/ocp_task.py similarity index 99% rename from ocp/ocp_task.py rename to common/ocp/ocp_task.py index 3da4dc70..96c33ef6 100644 --- a/ocp/ocp_task.py +++ b/common/ocp/ocp_task.py @@ -20,7 +20,7 @@ import time import requests -from ocp import ocp_api +from common.ocp import ocp_api logger = logging.getLogger("run") diff --git a/common/scene.py b/common/scene.py index 7e433ca8..044445a0 100644 --- a/common/scene.py +++ b/common/scene.py @@ -15,14 +15,11 @@ @file: scene.py @desc: """ - - -from common.logger import logger -from utils.shell_utils import SshHelper -from utils.version_utils import compare_versions_greater +from common.ssh import SshHelper +from common.tool import StringUtils from common.command import get_observer_version, get_obproxy_version -def filter_by_version(scene, cluster): +def filter_by_version(scene, cluster, stdio=None): try: steps = scene steps_nu = 0 @@ -33,10 +30,10 @@ def filter_by_version(scene, cluster): if "version" in now_steps: steps_versions = now_steps["version"] if not isinstance(steps_versions, str): - logger.error("filter_by_version steps_version Exception : {0}".format("the type of version is not string")) + stdio.exception("filter_by_version steps_version Exception : {0}".format("the type of version is not string")) raise Exception("filter_by_version steps_version Exception : {0}".format("the type of version is not string")) version_real = cluster["version"] - logger.info("version_int is {0} steps_versions is {1}".format(version_real, steps_versions)) + stdio.verbose("version_int is {0} steps_versions is {1}".format(version_real, steps_versions)) steps_versions = steps_versions.replace(" ", "") steps_versions = steps_versions[1:-1] @@ -48,53 +45,53 @@ def filter_by_version(scene, cluster): minVersion = "-1" if maxVersion == "*": maxVersion = "999" - if compare_versions_greater(version_real, minVersion) and compare_versions_greater(maxVersion, version_real): + if StringUtils.compare_versions_greater(version_real, minVersion) and StringUtils.compare_versions_greater(maxVersion, version_real): break else: - logger.info("not version in now_steps") + stdio.verbose("not version in now_steps") break steps_nu = steps_nu + 1 if steps_nu > len(steps) - 1: - logger.warning("not version in this scene") + stdio.warn("not version in this scene") return -1 return steps_nu except Exception as e: - logger.error("filter_by_version Exception : {0}".format(e)) + stdio.exception("filter_by_version Exception : {0}".format(e)) raise Exception("filter_by_version Exception : {0}".format(e)) -def get_version(nodes, type): +def get_version(nodes, type, stdio=None): try: if len(nodes) < 1: raise Exception("input nodes is empty, please check your config") node = nodes[0] - ssh = SshHelper(True, node.get("ip"), node.get("user"), node.get("password"), node.get("port"), node.get("private_key"), node) + ssh = SshHelper(True, node.get("ip"), node.get("ssh_username"), node.get("ssh_password"), node.get("ssh_port"), node.get("ssh_key_file"), node) if type == "observer": - version = get_observer_version(True, ssh, nodes[0]["home_path"]) + version = get_observer_version(True, ssh, nodes[0]["home_path"], stdio) elif type == "obproxy": - version = get_obproxy_version(True, ssh, nodes[0]["home_path"]) + version = get_obproxy_version(True, ssh, nodes[0]["home_path"], stdio) return version except Exception as e: - logger.error("can't get version, Exception: {0}".format(e)) + stdio.exception("can't get version, Exception: {0}".format(e)) raise Exception("can't get version, Exception: {0}".format(e)) -def get_obproxy_and_ob_version(obproxy_nodes, nodes, type): +def get_obproxy_and_ob_version(obproxy_nodes, nodes, type, stdio=None): try: if type == "observer" or type == "other": if len(nodes) < 1: raise Exception("input nodes is empty, please check your config") node = nodes[0] - ssh = SshHelper(True, node.get("ip"), node.get("user"), node.get("password"), node.get("port"), node.get("private_key"), node) - version = get_observer_version(True, ssh, nodes[0]["home_path"]) + ssh = SshHelper(True, node.get("ip"), node.get("ssh_username"), node.get("ssh_password"), node.get("ssh_port"), node.get("ssh_key_file"), node) + version = get_observer_version(True, ssh, nodes[0]["home_path"], stdio) elif type == "obproxy": if len(nodes) < 1: raise Exception("input obproxy nodes is empty, please check your config") node = obproxy_nodes[0] - ssh = SshHelper(True, node.get("ip"), node.get("user"), node.get("password"), node.get("port"), node.get("private_key"), node) - version = get_obproxy_version(True, ssh, nodes[0]["home_path"]) + ssh = SshHelper(True, node.get("ip"), node.get("ssh_username"), node.get("ssh_password"), node.get("ssh_port"), node.get("ssh_key_file"), node) + version = get_obproxy_version(True, ssh, nodes[0]["home_path"], stdio) else: raise Exception( "type is {0} . No func to get the version".format(type)) return version except Exception as e: - logger.error("can't get version, Exception: {0}".format(e)) + stdio.exception("can't get version, Exception: {0}".format(e)) raise Exception("can't get version, Exception: {0}".format(e)) \ No newline at end of file diff --git a/common/snowflake.py b/common/snowflake.py deleted file mode 100644 index c9c6c531..00000000 --- a/common/snowflake.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2022/6/20 -@file: snowflake.py -@desc: -""" -import threading -import time - -strptime_lock = threading.RLock() - - -class SnowFlake(object): - def __new__(cls, *args, **kwargs): - if not hasattr(cls, '_inst'): - cls._inst = super(SnowFlake, cls).__new__(cls) - return cls._inst - - def __init__(self, source_id): - # thread safe - with strptime_lock: - self.start = int(time.mktime(time.strptime('2018-01-01 00:00:00', "%Y-%m-%d %H:%M:%S"))) - self.last = int(time.time()) - self.count_id = 0 - self.source_id = source_id - - def get_id(self): - # 时间差部分 - now = int(time.time()) - temp = now-self.start - if len(str(temp)) < 9: - length = len(str(temp)) - s = "0" * (9-length) - temp = s + str(temp) - if now == self.last: - self.count_id += 1 - else: - self.count_id = 0 - self.last = now - # 标识ID部分 - if len(str(self.source_id)) < 2: - length = len(str(self.source_id)) - s = "0" * (2-length) - self.source_id = s + str(self.source_id) - # 自增序列号部分 - if self.count_id == 99999: - time.sleep(1) - count_id_date = str(self.count_id) - if len(count_id_date) < 5: - length = len(count_id_date) - s = "0"*(5-length) - count_id_date = s + count_id_date - _id = str(temp) + str(self.source_id) + count_id_date - return _id - - -def sleep(): - time.sleep(2) diff --git a/common/ssh.py b/common/ssh.py new file mode 100644 index 00000000..9c18b6b2 --- /dev/null +++ b/common/ssh.py @@ -0,0 +1,1080 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -* +# Copyright (c) 2022 OceanBase +# OceanBase Diagnostic Tool 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. + +""" +@file: ssh.py +@desc: +""" + +from __future__ import absolute_import, division, print_function + +import enum +import getpass +import os +import tempfile +import warnings +import sys + +import paramiko +import time +import docker +from glob import glob +from paramiko import AuthenticationException, SFTPClient +from paramiko.client import SSHClient, AutoAddPolicy +from paramiko.ssh_exception import NoValidConnectionsError, SSHException +from multiprocessing.queues import Empty +from multiprocessing import Queue +from multiprocessing.pool import ThreadPool +from common.tool import COMMAND_ENV, DirectoryUtil, FileUtil, Timeout +from common.obdiag_exception import OBDIAGSSHConnException +from common.obdiag_exception import OBDIAGShellCmdException +from common.tool import StringUtils +from common.tool import TimeUtils +from stdio import SafeStdio +from err import EC_SSH_CONNECT +from subprocess32 import Popen, PIPE +warnings.filterwarnings("ignore") + + +__all__ = ("SshClient", "SshConfig", "LocalClient", "ConcurrentExecutor") + + +class SshConfig(object): + + def __init__(self, host, username='root', password=None, key_filename=None, port=22, timeout=30): + self.host = host + self.username = username + self.password = password if password is None else str(password) + self.key_filename = key_filename + self.port = int(port) + self.timeout = int(timeout) + + def __str__(self): + return '%s@%s' % (self.username ,self.host) + + +class SshReturn(object): + + def __init__(self, code, stdout, stderr): + self.code = code + self.stdout = stdout + self.stderr = stderr + + def __bool__(self): + return self.code == 0 + + def __nonzero__(self): + return self.__bool__() + + +class FeatureSshReturn(SshReturn, SafeStdio): + + def __init__(self, popen, timeout, stdio): + self.popen = popen + self.timeout = timeout + self.stdio = stdio + self._code = None + self._stdout = None + self._stderr = None + + def _get_return(self): + if self._code is None: + try: + p = self.popen + output, error = p.communicate(timeout=self.timeout) + self._stdout = output.decode(errors='replace') + self._stderr = error.decode(errors='replace') + self._code = p.returncode + verbose_msg = 'exited code %s' % self._code + if self._code: + verbose_msg += ', error output:\n%s' % self._stderr + self.stdio.verbose(verbose_msg) + except Exception as e: + self._stdout = '' + self._stderr = str(e) + self._code = 255 + verbose_msg = 'exited code 255, error output:\n%s' % self._stderr + self.stdio.verbose(verbose_msg) + self.stdio.exception('') + + @property + def code(self): + self._get_return() + return self._code + + @property + def stdout(self): + self._get_return() + return self._stdout + + @property + def stderr(self): + self._get_return() + return self._stderr + + +class FutureSshReturn(SshReturn): + + def __init__(self, client, command, timeout=None, stdio=None): + self.client = client + self.command = command + self.timeout = timeout + self.stdio = stdio if stdio else client.stdio + if self.stdio: + self.stdio = self.stdio.sub_io() + self.finsh = False + super(FutureSshReturn, self).__init__(127, '', '') + + def set_return(self, ssh_return): + self.code = ssh_return.code + self.stdout = ssh_return.stdout + self.stderr = ssh_return.stderr + self.finsh = True + + +class ConcurrentExecutor(object): + + def __init__(self, workers=None): + self.workers = workers + self.futures = [] + + def add_task(self, client, command, timeout=None, stdio=None): + ret = FutureSshReturn(client, command, timeout, stdio=stdio) + self.futures.append(ret) + return ret + + def size(self): + return len(self.futures) + + @staticmethod + def execute(future): + client = SshClient(future.client.config, future.stdio) + future.set_return(client.execute_command(future.command, timeout=future.timeout)) + return future + + def submit(self): + rets = [] + pool = ThreadPool(processes=self.workers) + try: + results = pool.map(ConcurrentExecutor.execute, tuple(self.futures)) + for r in results: + rets.append(r) + finally: + pool.close() + self.futures = [] + return rets + + +class LocalClient(SafeStdio): + + @staticmethod + def init_env(env=None): + if env is None: + return None + env_t = COMMAND_ENV.copy() + env_t.update(env) + return env_t + + @staticmethod + def execute_command_background(command, env=None, timeout=None, stdio=None): + stdio.verbose('local background execute: %s ' % command, end='') + try: + p = Popen(command, env=LocalClient.init_env(env), shell=True, stdout=PIPE, stderr=PIPE) + return FeatureSshReturn(p, timeout, stdio) + except Exception as e: + output = '' + error = str(e) + code = 255 + verbose_msg = 'exited code 255, error output:\n%s' % error + stdio.verbose(verbose_msg) + stdio.exception('') + return SshReturn(code, output, error) + + + @staticmethod + def execute_command(command, env=None, timeout=None, stdio=None): + stdio.verbose('local execute: %s ' % command, end='') + try: + p = Popen(command, env=LocalClient.init_env(env), shell=True, stdout=PIPE, stderr=PIPE) + output, error = p.communicate(timeout=timeout) + code = p.returncode + output = output.decode(errors='replace') + error = error.decode(errors='replace') + verbose_msg = 'exited code %s' % code + if code: + verbose_msg += ', error output:\n%s' % error + stdio.verbose(verbose_msg) + except Exception as e: + output = '' + error = str(e) + code = 255 + verbose_msg = 'exited code 255, error output:\n%s' % error + stdio.verbose(verbose_msg) + stdio.exception('') + return SshReturn(code, output, error) + + @staticmethod + def put_file(local_path, remote_path, stdio=None): + if LocalClient.execute_command('mkdir -p %s && cp -f %s %s' % (os.path.dirname(remote_path), local_path, remote_path), stdio=stdio): + return True + return False + + @staticmethod + def put_dir(local_dir, remote_dir, stdio=None): + if os.path.isdir(local_dir): + local_dir = os.path.join(local_dir, '*') + if os.path.exists(os.path.dirname(local_dir)) and not glob(local_dir): + stdio.verbose("%s is empty" % local_dir) + return True + if LocalClient.execute_command('mkdir -p %s && cp -frL %s %s' % (remote_dir, local_dir, remote_dir), stdio=stdio): + return True + return False + + @staticmethod + def write_file(content, file_path, mode='w', stdio=None): + stdio.verbose('write {} to {}'.format(content, file_path)) + try: + with FileUtil.open(file_path, mode, stdio=stdio) as f: + f.write(content) + f.flush() + return True + except: + stdio.exception('') + return False + + @staticmethod + def get_file(local_path, remote_path, stdio=None): + return LocalClient.put_file(remote_path, local_path, stdio=stdio) + + @staticmethod + def get_dir(local_path, remote_path, stdio=None): + return LocalClient.put_dir(remote_path, local_path, stdio=stdio) + + @staticmethod + def run_command(command, env=None, timeout=None, print_stderr=True, elimit=0, olimit=0, stdio=None): + stdio.verbose('local execute: %s ' % command) + stdout = "" + process = None + try: + with Timeout(timeout): + process = Popen(command, env=LocalClient.init_env(env), shell=True, stdout=PIPE, stderr=PIPE) + while process.poll() is None: + lines = process.stdout.readline() + line = lines.strip() + if line: + stdio.print(line.decode("utf8", 'ignore')) + stderr = process.stderr.read().decode("utf8", 'ignore') + code = process.returncode + verbose_msg = 'exit code {}'.format(code) + if code != 0 and stderr: + verbose_msg += ', error output:\n' + stdio.verbose(verbose_msg) + if print_stderr: + stdio.print(stderr) + if elimit == 0: + stderr = "" + elif elimit > 0: + stderr = stderr[-elimit:] + except Exception as e: + if process: + process.terminate() + stdout = '' + stderr = str(e) + code = 255 + verbose_msg = 'exited code 255, error output:\n%s' % stderr + stdio.verbose(verbose_msg) + stdio.exception('') + finally: + if process: + process.terminate() + return SshReturn(code, stdout, stderr) + +class RemoteTransporter(enum.Enum): + CLIENT = 0 + RSYNC = 1 + + def __lt__(self, other): + return self.value < other.value + + def __gt__(self, other): + return self.value > other.value + + +class SshClient(SafeStdio): + + DEFAULT_PATH = '/sbin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:' + LOCAL_HOST = ['127.0.0.1', 'localhost', '127.1', '127.0.1'] + DISABLED_ALGORITHMS = dict(pubkeys=["rsa-sha2-512", "rsa-sha2-256"]) + + def __init__(self, config, stdio=None): + self.config = config + self.stdio = stdio + self.sftp = None + self.is_connected = False + self.ssh_client = SSHClient() + self.env_str = '' + self._remote_transporter = None + self.task_queue = None + self.result_queue = None + self._is_local = self.is_local() + if self._is_local: + self.env = {} + else: + self.env = {'PATH': self.DEFAULT_PATH} + self._update_env() + + self._disabled_rsa_algorithms = None + super(SshClient, self).__init__() + + def _init_queue(self): + self.task_queue = Queue() + self.result_queue = Queue() + + def _update_env(self): + env = [] + for key in self.env: + if self.env[key]: + env.append('export %s=%s$%s;' % (key, self.env[key], key)) + self.env_str = ''.join(env) + + def add_env(self, key, value, rewrite=False, stdio=None): + if key not in self.env or not self.env[key] or rewrite: + stdio.verbose('%s@%s set env %s to \'%s\'' % (self.config.username, self.config.host, key, value)) + if self._is_local: + self._add_env_for_local(key, value, rewrite) + else: + self.env[key] = value + else: + stdio.verbose('%s@%s append \'%s\' to %s' % (self.config.username, self.config.host, value, key)) + if self._is_local: + self._add_env_for_local(key, value, rewrite) + else: + self.env[key] += value + self._update_env() + + def _add_env_for_local(self, key, value, rewrite=False): + if rewrite: + self.env[key] = value + else: + if key not in self.env: + self.env[key] = COMMAND_ENV.get(key, '') + self.env[key] += value + + def get_env(self, key, stdio=None): + return self.env[key] if key in self.env else None + + def del_env(self, key, stdio=None): + if key in self.env: + stdio.verbose('%s@%s delete env %s' % (self.config.username, self.config.host, key)) + del self.env[key] + self._update_env() + + def __str__(self): + return '%s@%s:%d' % (self.config.username, self.config.host, self.config.port) + + def is_localhost(self, stdio=None): + return self.config.host in self.LOCAL_HOST + + def _login(self, stdio=None, exit=True): + if self.is_connected: + return True + err = None + try: + self.ssh_client.set_missing_host_key_policy(AutoAddPolicy()) + stdio.verbose('host: %s, port: %s, user: %s, password: %s' % (self.config.host, self.config.port, self.config.username, self.config.password)) + self.ssh_client.connect( + self.config.host, + port=self.config.port, + username=self.config.username, + password=self.config.password, + key_filename=self.config.key_filename, + timeout=self.config.timeout, + disabled_algorithms=self._disabled_rsa_algorithms + ) + self.is_connected = True + except AuthenticationException: + stdio.exception('') + err = EC_SSH_CONNECT.format(user=self.config.username, ip=self.config.host, message='username or password error') + except NoValidConnectionsError: + stdio.exception('') + err = EC_SSH_CONNECT.format(user=self.config.username, ip=self.config.host, message='time out') + except BaseException as e: + stdio.exception('') + err = EC_SSH_CONNECT.format(user=self.config.username, ip=self.config.host, message=e) + if err: + if exit: + stdio.critical(err) + return err + stdio.error(err) + return err + return self.is_connected + + def _open_sftp(self, stdio=None): + if self.sftp: + return True + if self._login(stdio=stdio): + SFTPClient.from_transport(self.ssh_client.get_transport()) + self.sftp = self.ssh_client.open_sftp() + return True + return False + + def is_local(self): + return self.is_localhost() and self.config.username == getpass.getuser() + + def connect(self, stdio=None, exit=True): + if self._is_local: + return True + return self._login(stdio=stdio, exit=exit) + + def reconnect(self, stdio=None): + self.close(stdio=stdio) + return self.connect(stdio=stdio) + + def close(self, stdio=None): + if self._is_local: + return True + if self.is_connected: + self.ssh_client.close() + if self.sftp: + self.sftp = None + + def __del__(self): + self.close() + + def _execute_command(self, command, timeout=None, retry=3, stdio=None): + if not self._login(stdio): + return SshReturn(255, '', 'connect failed') + try: + stdin, stdout, stderr = self.ssh_client.exec_command(command, timeout=timeout) + output = stdout.read().decode(errors='replace') + error = stderr.read().decode(errors='replace') + if output: + idx = output.rindex('\n') + code = int(output[idx:]) + stdout = output[:idx] + verbose_msg = 'exited code %s' % code + else: + code, stdout = 1, '' + if code: + verbose_msg = 'exited code %s, error output:\n%s' % (code, error) + stdio.verbose(verbose_msg) + except SSHException as e: + if retry: + self.close() + return self._execute_command(command, retry-1, stdio) + else: + stdio.exception('') + stdio.critical('%s@%s connect failed: %s' % (self.config.username, self.config.host, e)) + raise e + except Exception as e: + stdio.exception('') + code = 255 + stdout = '' + error = str(e) + return SshReturn(code, stdout, error) + + def execute_command(self, command, timeout=None, stdio=None): + if timeout is None: + timeout = self.config.timeout + elif timeout <= 0: + timeout = None + + if self._is_local: + return LocalClient.execute_command(command, self.env if self.env else None, timeout, stdio=stdio) + + verbose_msg = '%s execute: %s ' % (self.config, command) + stdio.verbose(verbose_msg, end='') + command = '(%s %s);echo -e "\n$?\c"' % (self.env_str, command.strip(';').lstrip('\n')) + return self._execute_command(command, retry=3, timeout=timeout, stdio=stdio) + + @property + def remote_transporter(self): + if self._remote_transporter is not None: + return self._remote_transporter + _transporter = RemoteTransporter.CLIENT + if not self._is_local and self._remote_transporter is None: + if not self.config.password and not self.disable_rsync: + ret = LocalClient.execute_command('rsync -h', stdio=self.stdio) and self.execute_command('rsync -h', stdio=self.stdio) + if ret: + _transporter = RemoteTransporter.RSYNC + self._remote_transporter = _transporter + self.stdio.verbose("current remote_transporter {}".format(self._remote_transporter)) + return self._remote_transporter + + def put_file(self, local_path, remote_path, stdio=None): + if not os.path.isfile(local_path): + stdio.error('path: %s is not file' % local_path) + return False + if self._is_local: + return LocalClient.put_file(local_path, remote_path, stdio=stdio) + if not self._open_sftp(stdio=stdio): + return False + return self._put_file(local_path, remote_path, stdio=stdio) + + def write_file(self, content, file_path, mode='w', stdio=None): + if self._is_local: + return LocalClient.write_file(content, file_path, mode, stdio) + return self._write_file(content, file_path, mode, stdio) + + def _write_file(self, content, file_path, mode='w', stdio=None): + stdio.verbose('write {} to {}: {}'.format(content, self, file_path)) + try: + with tempfile.NamedTemporaryFile(mode=mode) as f: + f.write(content) + f.flush() + return self.put_file(f.name, file_path, stdio=stdio) + except: + stdio.exception('') + return False + + @property + def _put_file(self): + if self.remote_transporter == RemoteTransporter.RSYNC: + return self._rsync_put_file + else: + return self._client_put_file + + def _client_put_file(self, local_path, remote_path, stdio=None): + if self.execute_command('mkdir -p %s && rm -fr %s' % (os.path.dirname(remote_path), remote_path), stdio=stdio): + stdio.verbose('send %s to %s' % (local_path, remote_path)) + if self.sftp.put(local_path.replace('~', os.getenv('HOME')), remote_path.replace('~', os.getenv('HOME'))): + return self.execute_command('chmod %s %s' % (oct(os.stat(local_path).st_mode)[-3:], remote_path)) + return False + + def _rsync(self, source, target, stdio=None): + identity_option = "" + if self.config.key_filename: + identity_option += '-i {key_filename} '.format(key_filename=self.config.key_filename) + if self.config.port: + identity_option += '-p {}'.format(self.config.port) + cmd = 'yes | rsync -a -W -e "ssh {identity_option}" {source} {target}'.format( + identity_option=identity_option, + source=source, + target=target + ) + ret = LocalClient.execute_command(cmd, stdio=stdio) + return bool(ret) + + def _rsync_put_dir(self, local_path, remote_path, stdio=None): + stdio.verbose('send %s to %s by rsync' % (local_path, remote_path)) + source = os.path.join(local_path, '*') + if os.path.exists(os.path.dirname(source)) and not glob(source): + stdio.verbose("%s is empty" % source) + return True + target = "{user}@{host}:{remote_path}".format(user=self.config.username, host=self.config.host, remote_path=remote_path) + if self._rsync(source, target, stdio=stdio): + return True + else: + return False + + def _rsync_put_file(self, local_path, remote_path, stdio=None): + if not self.execute_command('mkdir -p %s' % os.path.dirname(remote_path), stdio=stdio): + return False + stdio.verbose('send %s to %s by rsync' % (local_path, remote_path)) + target = "{user}@{host}:{remote_path}".format(user=self.config.username, host=self.config.host, remote_path=remote_path) + if self._rsync(local_path, target, stdio=stdio): + return True + else: + return False + + def put_dir(self, local_dir, remote_dir, stdio=None): + if self._is_local: + return LocalClient.put_dir(local_dir, remote_dir, stdio=stdio) + if not self._open_sftp(stdio=stdio): + return False + if not self.execute_command('mkdir -p %s' % remote_dir, stdio=stdio): + return False + stdio.start_loading('Send %s to %s' % (local_dir, remote_dir)) + ret = self._put_dir(local_dir, remote_dir, stdio=stdio) + stdio.stop_loading('succeed' if ret else 'fail') + return ret + + @property + def _put_dir(self): + if self.remote_transporter == RemoteTransporter.RSYNC: + return self._rsync_put_dir + else: + return self._client_put_dir + + def _client_put_dir(self, local_dir, remote_dir, stdio=None): + has_failed = False + ret = LocalClient.execute_command('find -L %s -type f' % local_dir) + if not ret: + has_failed = True + all_files = ret.stdout.strip().split('\n') if ret.stdout else [] + ret = LocalClient.execute_command('find %s -type d' % local_dir) + if not ret: + has_failed = True + all_dirs = ret.stdout.strip().split('\n') if ret.stdout else [] + self._filter_dir_in_file_path(all_files, all_dirs) + for local_path in all_files: + remote_path = os.path.join(remote_dir, os.path.relpath(local_path, local_dir)) + if not self._client_put_file(local_path, remote_path, stdio=stdio): + stdio.error('Fail to get %s' % remote_path) + has_failed = True + for local_path in all_dirs: + remote_path = os.path.join(remote_dir, os.path.relpath(local_path, local_dir)) + stat = oct(os.stat(local_path).st_mode)[-3:] + cmd = '[ -d "{remote_path}" ] || (mkdir -p {remote_path}; chmod {stat} {remote_path})'.format(remote_path=remote_path, stat=stat) + if not self.execute_command(cmd): + has_failed = True + return not has_failed + + def get_file(self, local_path, remote_path, stdio=None): + dirname, _ = os.path.split(local_path) + if not dirname: + dirname = os.getcwd() + local_path = os.path.join(dirname, local_path) + if os.path.exists(dirname): + if not os.path.isdir(dirname): + stdio.error('%s is not directory' % dirname) + return False + elif not DirectoryUtil.mkdir(dirname, stdio=stdio): + return False + if os.path.exists(local_path) and not os.path.isfile(local_path): + stdio.error('path: %s is not file' % local_path) + return False + if self._is_local: + return LocalClient.get_file(local_path, remote_path, stdio=stdio) + if not self._open_sftp(stdio=stdio): + return False + return self._get_file(local_path, remote_path, stdio=stdio) + + @property + def _get_file(self): + if self.remote_transporter == RemoteTransporter.RSYNC: + return self._rsync_get_file + else: + return self._client_get_file + + def _rsync_get_dir(self, local_path, remote_path, stdio=None): + source = "{user}@{host}:{remote_path}".format(user=self.config.username, host=self.config.host, remote_path=remote_path) + if "*" not in remote_path: + source = os.path.join(source, "*") + target = local_path + stdio.verbose('get %s from %s by rsync' % (local_path, remote_path)) + if LocalClient.execute_command('mkdir -p {}'.format(local_path), stdio=stdio) and self._rsync(source, target, stdio=stdio): + return True + else: + return False + + def _rsync_get_file(self, local_path, remote_path, stdio=None): + source = "{user}@{host}:{remote_path}".format(user=self.config.username, host=self.config.host, remote_path=remote_path) + target = local_path + stdio.verbose('get %s from %s by rsync' % (local_path, remote_path)) + if self._rsync(source, target, stdio=stdio): + return True + else: + return False + + def _client_get_file(self, local_path, remote_path, stdio=None): + try: + self.sftp.get(remote_path, local_path) + stat = self.sftp.stat(remote_path) + os.chmod(local_path, stat.st_mode) + return True + except Exception as e: + stdio.exception('get %s from %s@%s:%s failed: %s' % (local_path, self.config.username, self.config.host, remote_path, e)) + return False + + def get_dir(self, local_dir, remote_dir, stdio=None): + dirname, _ = os.path.split(local_dir) + if not dirname: + dirname = os.getcwd() + local_dir = os.path.join(dirname, local_dir) + if "*" in dirname: + stdio.error('Invalid directory {}'.format(dirname)) + return False + if os.path.exists(dirname): + if not os.path.isdir(dirname): + stdio.error('%s is not directory' % dirname) + return False + elif not DirectoryUtil.mkdir(dirname, stdio=stdio): + return False + if os.path.exists(local_dir) and not os.path.isdir(local_dir): + stdio.error('%s is not directory' % local_dir) + return False + if self._is_local: + return LocalClient.get_dir(local_dir, remote_dir, stdio=stdio) + if not self._open_sftp(stdio=stdio): + return False + stdio.start_loading('Get %s from %s' % (local_dir, remote_dir)) + ret = self._get_dir(local_dir, remote_dir, stdio=stdio) + stdio.stop_loading('succeed' if ret else 'fail') + return ret + + @property + def _get_dir(self): + if self.remote_transporter == RemoteTransporter.RSYNC: + return self._rsync_get_dir + else: + return self._client_get_dir + + def _client_get_dir(self, local_dir, remote_dir, stdio=None): + task_queue = [] + has_failed = False + if DirectoryUtil.mkdir(local_dir, stdio=stdio): + try: + ret = self.execute_command('find %s -type f' % remote_dir) + if not ret: + stdio.verbose(ret.stderr) + has_failed = True + all_files = ret.stdout.strip().split('\n') if ret.stdout else [] + ret = self.execute_command('find %s -type d' % remote_dir) + if not ret: + has_failed = True + all_dirs = ret.stdout.strip().split('\n') if ret.stdout else [] + self._filter_dir_in_file_path(all_files, all_dirs) + for f in all_files: + task_queue.append(f) + if "*" in remote_dir: + remote_base_dir = os.path.dirname(remote_dir) + else: + remote_base_dir = remote_dir + for remote_path in task_queue: + local_path = os.path.join(local_dir, os.path.relpath(remote_path, remote_dir)) + if not self._client_get_file(local_path, remote_path, stdio=stdio): + stdio.error('Fail to get %s' % remote_path) + has_failed = True + for remote_path in all_dirs: + try: + local_path = os.path.join(local_dir, os.path.relpath(remote_path, remote_base_dir)) + if not os.path.exists(local_path): + stat = self.sftp.stat(remote_path) + os.makedirs(local_path, mode=stat.st_mode) + except Exception as e: + stdio.exception('Fail to make directory %s in local: %s' % (remote_path, e)) + has_failed = True + return not has_failed + except Exception as e: + stdio.exception('Fail to get %s: %s' % (remote_dir, e)) + + @staticmethod + def _filter_dir_in_file_path(files, directories): + skip_directories = [] + for path in files: + dir_name = os.path.dirname(path) + while dir_name not in ["/", ".", ""]: + if dir_name in skip_directories: + break + if dir_name in directories: + directories.remove(dir_name) + skip_directories.append(dir_name) + dir_name = os.path.dirname(dir_name) + + def file_downloader(self, local_dir, remote_dir, stdio=None): + try: + client = SshClient(config=self.config, stdio=None) + client._open_sftp(stdio=stdio) + client._remote_transporter = self.remote_transporter + while True: + remote_path = self.task_queue.get(block=False) + local_path = os.path.join(local_dir, os.path.relpath(remote_path, remote_dir)) + if client.get_file(local_path, remote_path, stdio=stdio): + self.result_queue.put(remote_path) + else: + stdio.error('Fail to get %s' % remote_path) + except Empty: + return + except: + stdio.exception("") + stdio.exception('Failed to get %s' % remote_dir) + + def file_uploader(self, local_dir, remote_dir, stdio=None): + try: + client = SshClient(config=self.config, stdio=None) + client._remote_transporter = self.remote_transporter + while True: + local_path, is_dir = self.task_queue.get(block=False) + remote_path = os.path.join(remote_dir, os.path.relpath(local_path, local_dir)) + if is_dir: + stat = oct(os.stat(local_path).st_mode)[-3:] + cmd = '[ -d "{remote_path}" ] || (mkdir -p {remote_path}; chmod {stat} {remote_path})'.format(remote_path=remote_path, stat=stat) + if client.execute_command(cmd): + self.result_queue.put(remote_path) + else: + if client.put_file(local_path, remote_path, stdio=stdio): + self.result_queue.put(remote_path) + else: + stdio.error('Fail to get %s' % remote_path) + except Empty: + return + except: + stdio.exception("") + stdio.verbose('Failed to get %s' % remote_dir) + +class SshHelper(object): + def __init__(self, is_ssh=None, host_ip=None, username=None, password=None, ssh_port=None, key_file=None, + node=None, stdio=None): + if node is None: + node={} + self.is_ssh = is_ssh + self.stdio = stdio + self.host_ip = host_ip + self.username = username + self.ssh_port = node.get("ssh_port") or ssh_port + self.need_password = True + self.password = node.get("ssh_password") or password + self.key_file = node.get("ssh_key_file") or key_file + self.key_file=os.path.expanduser(self.key_file) + self.ssh_type = node.get("ssh_type") or "remote" + self._ssh_fd = None + self._sftp_client = None + if "ssh_type" in node and node.get("ssh_type") == "docker": + try: + self.ssh_type = node["ssh_type"] + self.stdio.verbose("use ssh_type:{0} , node info : {1}".format(self.ssh_type, StringUtils.node_cut_passwd_for_log(node))) + self.node = node + # docker_permissions_check + if self.ssh_type == "docker": + self.client = docker.from_env() + if "container_name" not in node: + self.stdio.error("SshHelper init docker Exception: 'container_name' not in node") + raise Exception("SshHelper init docker Exception: 'container_name' not in node") + else: + self.stdio.error("SshHelper init not support the ssh_type : {0}".format(self.ssh_type)) + raise Exception("SshHelper init not support the ssh_type : {0}".format(self.ssh_type)) + + except Exception as e: + self.stdio.error("SshHelper init docker Exception: {0}".format(e)) + raise Exception("SshHelper init docker Exception: {0}".format(e)) + + return + + if self.is_ssh: + self.ssh_type = "remote" + if len(self.key_file) > 0: + try: + self._ssh_fd = paramiko.SSHClient() + self._ssh_fd.set_missing_host_key_policy(paramiko.client.AutoAddPolicy()) + self._ssh_fd.load_system_host_keys() + self._ssh_fd.connect(hostname=host_ip, username=username, key_filename=self.key_file, port=ssh_port) + except AuthenticationException: + self.password = input("Authentication failed, Input {0}@{1} password:\n".format(username, host_ip)) + self.need_password = True + self._ssh_fd.connect(hostname=host_ip, username=username, password=password, port=ssh_port) + except Exception as e: + raise OBDIAGSSHConnException("ssh {0}@{1}: failed, exception:{2}".format(username, host_ip, e)) + else: + self._ssh_fd = paramiko.SSHClient() + self._ssh_fd.set_missing_host_key_policy(paramiko.client.AutoAddPolicy()) + self._ssh_fd.load_system_host_keys() + self.need_password = True + self._ssh_fd.connect(hostname=host_ip, username=username, password=password, port=ssh_port) + + def ssh_exec_cmd(self, cmd): + if self.ssh_type == "docker": + try: + self.stdio.verbose("ssh_exec_cmd docker {0} cmd: {1}".format(self.node.get("container_name"), cmd)) + client_result = self.client.containers.get(self.node["container_name"]) + result = client_result.exec_run( + cmd=["bash", "-c", cmd], + detach=False, + stdout=True, + stderr=True, + ) + if result.exit_code != 0: + raise OBDIAGShellCmdException("Execute Shell command on server {0} failed, " + "command=[{1}], exception:{2}".format(self.node["container_name"], cmd, + result.output.decode('utf-8'))) + + except Exception as e: + self.stdio.error("sshHelper ssh_exec_cmd docker Exception: {0}".format(e)) + raise Exception("sshHelper ssh_exec_cmd docker Exception: {0}".format(e)) + + return result.output.decode('utf-8') + try: + stdin, stdout, stderr = self._ssh_fd.exec_command(cmd) + err_text = stderr.read() + if len(err_text): + raise OBDIAGShellCmdException("Execute Shell command on server {0} failed, " + "command=[{1}], exception:{2}".format(self.host_ip, cmd, err_text)) + except SSHException as e: + raise OBDIAGShellCmdException("Execute Shell command on server {0} failed, " + "command=[{1}], exception:{2}".format(self.host_ip, cmd, e)) + return stdout.read().decode('utf-8') + + def ssh_exec_cmd_ignore_err(self, cmd): + if self.ssh_type == "docker": + try: + client_result = self.client.containers.get(self.node["container_name"]) + result = client_result.exec_run( + cmd=["bash", "-c", cmd], + detach=False, + stdout=True, + stderr=True, + ) + except Exception as e: + self.stdio.error("sshHelper ssh_exec_cmd docker Exception: {0}".format(e)) + raise Exception("sshHelper ssh_exec_cmd docker Exception: {0}".format(e)) + + return result.output.decode('utf-8') + + try: + stdin, stdout, stderr = self._ssh_fd.exec_command(cmd) + return stdout.read().decode('utf-8') + except SSHException as e: + print("Execute Shell command on server {0} failed,command=[{1}], exception:{2}".format(self.node, cmd, e)) + + def ssh_exec_cmd_ignore_exception(self, cmd): + if self.ssh_type == "docker": + try: + client_result = self.client.containers.get(self.node["container_name"]) + result = client_result.exec_run( + cmd=["bash", "-c", cmd], + detach=False, + stdout=True, + stderr=True, + ) + return result.output.decode('utf-8') + except Exception as e: + self.stdio.error("sshHelper ssh_exec_cmd_ignore_exception docker Exception: {0}".format(e)) + pass + # raise Exception("sshHelper ssh_exec_cmd docker Exception: {0}".format(e)) + return + + try: + stdin, stdout, stderr = self._ssh_fd.exec_command(cmd) + return stderr.read().decode('utf-8') + except SSHException as e: + pass + + def ssh_exec_cmd_get_stderr(self, cmd): + if self.ssh_type == "docker": + try: + client_result = self.client.containers.get(self.node["container_name"]) + result = client_result.exec_run( + cmd=["bash", "-c", cmd], + detach=False, + stdout=True, + stderr=True, + ) + return result.output.decode('utf-8') + except Exception as e: + self.stdio.error("sshHelper ssh_exec_cmd_ignore_exception docker Exception: {0}".format(e)) + pass + # raise Exception("sshHelper ssh_exec_cmd docker Exception: {0}".format(e)) + return + try: + stdin, stdout, stderr = self._ssh_fd.exec_command(cmd) + return stderr.read().decode('utf-8') + except SSHException as e: + pass + + def progress_bar(self, transferred, to_be_transferred, suffix=''): + bar_len = 20 + filled_len = int(round(bar_len * transferred / float(to_be_transferred))) + percents = round(20.0 * transferred / float(to_be_transferred), 1) + bar = '\033[32;1m%s\033[0m' % '=' * filled_len + '-' * (bar_len - filled_len) + print_percents = round((percents * 5), 1) + sys.stdout.flush() + sys.stdout.write('Downloading [%s] %s%s%s %s %s\r' % (bar, '\033[32;1m%s\033[0m' % print_percents, '% [', self.translate_byte(transferred), ']', suffix)) + if transferred == to_be_transferred: + sys.stdout.write('Downloading [%s] %s%s%s %s %s\r' % ( + bar, '\033[32;1m%s\033[0m' % print_percents, '% [', self.translate_byte(transferred), ']', suffix)) + print() + + def download(self, remote_path, local_path): + if self.ssh_type == "docker": + try: + self.stdio.verbose("remote_path: {0}:{1} to local_path:{2}".format(self.node["container_name"], remote_path, local_path)) + client_result = self.client.containers.get(self.node["container_name"]) + data, stat = client_result.get_archive(remote_path) + with open(local_path, "wb") as f: + for chunk in data: + f.write(chunk) + return + except Exception as e: + self.stdio.error("sshHelper download docker Exception: {0}".format(e)) + raise Exception("sshHelper download docker Exception: {0}".format(e)) + return + + transport = self._ssh_fd.get_transport() + self._sftp_client = paramiko.SFTPClient.from_transport(transport) + print('Download {0}:{1}'.format(self.host_ip,remote_path)) + self._sftp_client.get(remote_path, local_path, callback=self.progress_bar) + self._sftp_client.close() + + def translate_byte(self, B): + B = float(B) + KB = float(1024) + MB = float(KB ** 2) + GB = float(MB ** 2) + TB = float(GB ** 2) + if B < KB: + return '{} {}'.format(B, 'bytes' if B > 1 else "byte") + elif KB < B < MB: + return '{:.2f} KB'.format(B / KB) + elif MB < B < GB: + return '{:.2f} MB'.format(B / MB) + elif GB < B < TB: + return '{:.2f} GB'.format(B / GB) + else: + return '{:.2f} TB'.format(B / TB) + + def upload(self, remote_path, local_path): + if self.ssh_type == "docker": + try: + self.stdio.verbose(" local_path:{0} to remote_path:{1}:{2}".format(local_path, self.node["container_name"], remote_path)) + + self.client.containers.get(self.node["container_name"]).put_archive(remote_path, local_path) + + return + except Exception as e: + self.stdio.error("sshHelper upload docker Exception: {0}".format(e)) + raise Exception("sshHelper upload docker Exception: {0}".format(e)) + return + transport = self._ssh_fd.get_transport() + self._sftp_client = paramiko.SFTPClient.from_transport(transport) + self._sftp_client.put(remote_path, local_path) + self._sftp_client.close() + + def ssh_close(self): + if self.ssh_type == "docker": + self.client.close() + return + if self._sftp_client is not None: + self._sftp_client.close() + self._sftp_client = None + + def __del__(self): + if self._sftp_client is not None: + self._sftp_client.close() + self._sftp_client = None + + def ssh_invoke_shell_switch_user(self, new_user, cmd, time_out): + if self.ssh_type == "docker": + try: + exec_id = self.client.exec_create(container=self.node["container_name"], command=['su', '- ' + new_user]) + response = self.client.exec_start(exec_id) + + return response + except Exception as e: + self.stdio.error("sshHelper ssh_invoke_shell_switch_user docker Exception: {0}".format(e)) + raise Exception("sshHelper ssh_invoke_shell_switch_user docker Exception: {0}".format(e)) + return + try: + ssh = self._ssh_fd.invoke_shell() + ssh.send('su {0}\n'.format(new_user)) + ssh.send('{}\n'.format(cmd)) + time.sleep(time_out) + self._ssh_fd.close() + result = ssh.recv(65535) + except SSHException as e: + raise OBDIAGShellCmdException("Execute Shell command on server {0} failed, " + "command=[{1}], exception:{2}".format(self.host_ip, cmd, e)) + return result + + def get_name(self): + if self.ssh_type == "docker": + return "(docker)"+self.node.get("container_name") + return self.host_ip \ No newline at end of file diff --git a/common/status_rpc.py b/common/status_rpc.py deleted file mode 100644 index 938c7268..00000000 --- a/common/status_rpc.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2022/6/20 -@file: status_rpc.py -@desc: -""" -from common.base_rpc import Request, Response - - -class StatusRequest(Request): - def __init__(self, timestamp=None, request_id=None): - super(StatusRequest, self).__init__(timestamp=timestamp) - if request_id is not None: - self.request_id = request_id - - def to_dict(self): - parent_dict = super(StatusRequest, self).to_dict() - return parent_dict - - -class StatusResponse(Response): - def __init__(self, request, status_result_dict, timestamp=None): - super(StatusResponse, self).__init__(request.request_id, - timestamp=timestamp) - self.status_result_dict = status_result_dict - - def to_dict(self): - parent_dict = super(StatusResponse, self).to_dict() - parent_dict["status_dict"] = self.status_result_dict - return parent_dict - - def from_dict(self, content_dict): - super(StatusResponse, self).from_dict(content_dict) - self.status_result_dict = content_dict["status_dict"] diff --git a/common/tool.py b/common/tool.py new file mode 100644 index 00000000..d1309933 --- /dev/null +++ b/common/tool.py @@ -0,0 +1,1390 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -* +# Copyright (c) 2022 OceanBase +# OceanBase Diagnostic Tool 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. + +""" +@file: tool.py +@desc: +""" + +from __future__ import absolute_import, division, print_function + +import os +import io +import bz2 +import random +import sys +import stat +import gzip +import fcntl +import signal +import shutil +import re +import json +import hashlib +import datetime +import tabulate +import tarfile +import socket +import requests +import decimal +import time +import json +import time +import traceback +import datetime +import string +import oyaml as yaml +import ast +import lzma +import pymysql as mysql +from datetime import timedelta +from random import choice +from io import BytesIO +from copy import copy +from colorama import Fore, Style +from ruamel.yaml import YAML +from err import EC_SQL_EXECUTE_FAILED +from stdio import SafeStdio +_open = open +encoding_open = open + + +__all__ = ( +"Timeout", +"DynamicLoading", +"ConfigUtil", +"DirectoryUtil", +"FileUtil", +"YamlLoader", +"OrderedDict", +"COMMAND_ENV", +"TimeUtils", +"NetUtils", +"StringUtils", +"YamlUtils", +"Util" +) + +_WINDOWS = os.name == 'nt' + + +class Timeout(object): + + def __init__(self, seconds=1, error_message='Timeout'): + self.seconds = seconds + self.error_message = error_message + + def handle_timeout(self, signum, frame): + raise TimeoutError(self.error_message) + + def _is_timeout(self): + return self.seconds and self.seconds > 0 + + def __enter__(self): + if self._is_timeout(): + signal.signal(signal.SIGALRM, self.handle_timeout) + signal.alarm(self.seconds) + + def __exit__(self, type, value, traceback): + if self._is_timeout(): + signal.alarm(0) + + +timeout = Timeout + + +class DynamicLoading(object): + + class Module(object): + + def __init__(self, module): + self.module = module + self.count = 0 + + LIBS_PATH = {} + MODULES = {} + + @staticmethod + def add_lib_path(lib): + if lib not in DynamicLoading.LIBS_PATH: + DynamicLoading.LIBS_PATH[lib] = 0 + if DynamicLoading.LIBS_PATH[lib] == 0: + sys.path.insert(0, lib) + DynamicLoading.LIBS_PATH[lib] += 1 + + @staticmethod + def add_libs_path(libs): + for lib in libs: + DynamicLoading.add_lib_path(lib) + + @staticmethod + def remove_lib_path(lib): + if lib not in DynamicLoading.LIBS_PATH: + return + if DynamicLoading.LIBS_PATH[lib] < 1: + return + try: + DynamicLoading.LIBS_PATH[lib] -= 1 + if DynamicLoading.LIBS_PATH[lib] == 0: + idx = sys.path.index(lib) + del sys.path[idx] + except: + pass + + @staticmethod + def remove_libs_path(libs): + for lib in libs: + DynamicLoading.remove_lib_path(lib) + + @staticmethod + def import_module(name, stdio=None): + if name not in DynamicLoading.MODULES: + try: + stdio and getattr(stdio, 'verbose', print)('import %s' % name) + module = __import__(name) + DynamicLoading.MODULES[name] = DynamicLoading.Module(module) + except: + stdio and getattr(stdio, 'exception', print)('import %s failed' % name) + stdio and getattr(stdio, 'verbose', print)('sys.path: %s' % sys.path) + return None + DynamicLoading.MODULES[name].count += 1 + stdio and getattr(stdio, 'verbose', print)('add %s ref count to %s' % (name, DynamicLoading.MODULES[name].count)) + return DynamicLoading.MODULES[name].module + + @staticmethod + def export_module(name, stdio=None): + if name not in DynamicLoading.MODULES: + return + if DynamicLoading.MODULES[name].count < 1: + return + try: + DynamicLoading.MODULES[name].count -= 1 + stdio and getattr(stdio, 'verbose', print)('sub %s ref count to %s' % (name, DynamicLoading.MODULES[name].count)) + if DynamicLoading.MODULES[name].count == 0: + stdio and getattr(stdio, 'verbose', print)('export %s' % name) + del sys.modules[name] + del DynamicLoading.MODULES[name] + except: + stdio and getattr(stdio, 'exception', print)('export %s failed' % name) + + +class ConfigUtil(object): + + @staticmethod + def get_value_from_dict(conf, key, default=None, transform_func=None): + try: + # 不要使用 conf.get(key, default)来替换,这里还有类型转换的需求 + value = conf[key] + return transform_func(value) if value is not None and transform_func else value + except: + return default + + @staticmethod + def get_list_from_dict(conf, key, transform_func=None): + try: + return_list = conf[key] + if transform_func: + return [transform_func(value) for value in return_list] + else: + return return_list + except: + return [] + + @staticmethod + def get_random_pwd_by_total_length(pwd_length=10): + char = string.ascii_letters + string.digits + pwd = "" + for i in range(pwd_length): + pwd = pwd + random.choice(char) + return pwd + + @staticmethod + def get_random_pwd_by_rule(lowercase_length=2, uppercase_length=2, digits_length=2, punctuation_length=2): + pwd = "" + for i in range(lowercase_length): + pwd += random.choice(string.ascii_lowercase) + for i in range(uppercase_length): + pwd += random.choice(string.ascii_uppercase) + for i in range(digits_length): + pwd += random.choice(string.digits) + for i in range(punctuation_length): + pwd += random.choice('(._+@#%)') + pwd_list = list(pwd) + random.shuffle(pwd_list) + return ''.join(pwd_list) + + @staticmethod + def passwd_format(passwd): + return "'{}'".format(passwd.replace("'", "'\"'\"'")) + + +class DirectoryUtil(object): + + @staticmethod + def get_owner(path): + return os.stat(path)[stat.ST_UID] + + @staticmethod + def list_dir(path, stdio=None): + files = [] + if os.path.isdir(path): + for fn in os.listdir(path): + fp = os.path.join(path, fn) + if os.path.isdir(fp): + files += DirectoryUtil.list_dir(fp) + else: + files.append(fp) + return files + + @staticmethod + def copy(src, dst, stdio=None): + if not os.path.isdir(src): + stdio and getattr(stdio, 'error', print)("cannot copy tree '%s': not a directory" % src) + return False + try: + names = os.listdir(src) + except: + stdio and getattr(stdio, 'exception', print)("error listing files in '%s':" % (src)) + return False + + if DirectoryUtil.mkdir(dst, stdio): + return False + + ret = True + links = [] + for n in names: + src_name = os.path.join(src, n) + dst_name = os.path.join(dst, n) + if os.path.islink(src_name): + link_dest = os.readlink(src_name) + links.append((link_dest, dst_name)) + + elif os.path.isdir(src_name): + ret = DirectoryUtil.copy(src_name, dst_name, stdio) and ret + else: + FileUtil.copy(src_name, dst_name, stdio) + for link_dest, dst_name in links: + FileUtil.symlink(link_dest, dst_name, stdio) + return ret + + @staticmethod + def mkdir(path, mode=0o755, stdio=None): + stdio and getattr(stdio, 'verbose', print)('mkdir %s' % path) + try: + os.makedirs(path, mode=mode) + return True + except OSError as e: + if e.errno == 17: + return True + elif e.errno == 20: + stdio and getattr(stdio, 'error', print)('%s is not a directory', path) + else: + stdio and getattr(stdio, 'error', print)('failed to create directory %s', path) + stdio and getattr(stdio, 'exception', print)('') + except: + stdio and getattr(stdio, 'exception', print)('') + stdio and getattr(stdio, 'error', print)('failed to create directory %s', path) + return False + + @staticmethod + def rm(path, stdio=None): + stdio and getattr(stdio, 'verbose', print)('rm %s' % path) + try: + if os.path.exists(path): + if os.path.islink(path): + os.remove(path) + else: + shutil.rmtree(path) + return True + except Exception as e: + stdio and getattr(stdio, 'exception', print)('') + stdio and getattr(stdio, 'error', print)('failed to remove %s', path) + return False + + +class FileUtil(object): + + COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 64 * 1024 + + @staticmethod + def checksum(target_path, stdio=None): + from common.ssh import LocalClient + if not os.path.isfile(target_path): + info = 'No such file: ' + target_path + if stdio: + getattr(stdio, 'error', print)(info) + return False + else: + raise IOError(info) + ret = LocalClient.execute_command('md5sum {}'.format(target_path), stdio=stdio) + if ret: + return ret.stdout.strip().split(' ')[0].encode('utf-8') + else: + m = hashlib.md5() + with open(target_path, 'rb') as f: + m.update(f.read()) + return m.hexdigest().encode(sys.getdefaultencoding()) + + @staticmethod + def copy_fileobj(fsrc, fdst): + fsrc_read = fsrc.read + fdst_write = fdst.write + while True: + buf = fsrc_read(FileUtil.COPY_BUFSIZE) + if not buf: + break + fdst_write(buf) + + @staticmethod + def copy(src, dst, stdio=None): + stdio and getattr(stdio, 'verbose', print)('copy %s %s' % (src, dst)) + if os.path.exists(src) and os.path.exists(dst) and os.path.samefile(src, dst): + info = "`%s` and `%s` are the same file" % (src, dst) + if stdio: + getattr(stdio, 'error', print)(info) + return False + else: + raise IOError(info) + + for fn in [src, dst]: + try: + st = os.stat(fn) + except OSError: + pass + else: + if stat.S_ISFIFO(st.st_mode): + info = "`%s` is a named pipe" % fn + if stdio: + getattr(stdio, 'error', print)(info) + return False + else: + raise IOError(info) + + try: + if os.path.islink(src): + FileUtil.symlink(os.readlink(src), dst) + return True + with FileUtil.open(src, 'rb') as fsrc, FileUtil.open(dst, 'wb') as fdst: + FileUtil.copy_fileobj(fsrc, fdst) + os.chmod(dst, os.stat(src).st_mode) + return True + except Exception as e: + if int(getattr(e, 'errno', -1)) == 26: + from common.ssh import LocalClient + if LocalClient.execute_command('/usr/bin/cp -f %s %s' % (src, dst), stdio=stdio): + return True + elif stdio: + getattr(stdio, 'exception', print)('copy error: %s' % e) + else: + raise e + return False + + @staticmethod + def symlink(src, dst, stdio=None): + stdio and getattr(stdio, 'verbose', print)('link %s %s' % (src, dst)) + try: + if DirectoryUtil.rm(dst, stdio): + os.symlink(src, dst) + return True + except Exception as e: + if stdio: + getattr(stdio, 'exception', print)('link error: %s' % e) + else: + raise e + return False + + @staticmethod + def open(path, _type='r', encoding=None, stdio=None): + stdio and getattr(stdio, 'verbose', print)('open %s for %s' % (path, _type)) + if os.path.exists(path): + if os.path.isfile(path): + return encoding_open(path, _type, encoding=encoding) + info = '%s is not file' % path + if stdio: + getattr(stdio, 'error', print)(info) + return None + else: + raise IOError(info) + dir_path, file_name = os.path.split(path) + if not dir_path or DirectoryUtil.mkdir(dir_path, stdio=stdio): + return encoding_open(path, _type, encoding=encoding) + info = '%s is not file' % path + if stdio: + getattr(stdio, 'error', print)(info) + return None + else: + raise IOError(info) + + @staticmethod + def unzip(source, ztype=None, stdio=None): + stdio and getattr(stdio, 'verbose', print)('unzip %s' % source) + if not ztype: + ztype = source.split('.')[-1] + try: + if ztype == 'bz2': + s_fn = bz2.BZ2File(source, 'r') + elif ztype == 'xz': + s_fn = lzma.LZMAFile(source, 'r') + elif ztype == 'gz': + s_fn = gzip.GzipFile(source, 'r') + else: + s_fn = open(source, 'r') + return s_fn + except: + stdio and getattr(stdio, 'exception', print)('failed to unzip %s' % source) + return None + + def extract_tar(tar_path, output_path, stdio=None): + if not os.path.exists(output_path): + os.makedirs(output_path) + try: + with tarfile.open(tar_path, 'r') as tar: + tar.extractall(path=output_path) + except: + stdio and getattr(stdio, 'exception', print)('failed to extract tar file %s' % tar_path) + return None + + @staticmethod + def rm(path, stdio=None): + stdio and getattr(stdio, 'verbose', print)('rm %s' % path) + if not os.path.exists(path): + return True + try: + os.remove(path) + return True + except: + stdio and getattr(stdio, 'exception', print)('failed to remove %s' % path) + return False + + @staticmethod + def move(src, dst, stdio=None): + return shutil.move(src, dst) + + @staticmethod + def share_lock_obj(obj, stdio=None): + stdio and getattr(stdio, 'verbose', print)('try to get share lock %s' % obj.name) + fcntl.flock(obj, fcntl.LOCK_SH | fcntl.LOCK_NB) + return obj + + @classmethod + def share_lock(cls, path, _type='w', stdio=None): + return cls.share_lock_obj(cls.open(path, _type=_type, stdio=stdio)) + + @staticmethod + def exclusive_lock_obj(obj, stdio=None): + stdio and getattr(stdio, 'verbose', print)('try to get exclusive lock %s' % obj.name) + fcntl.flock(obj, fcntl.LOCK_EX | fcntl.LOCK_NB) + return obj + + @classmethod + def exclusive_lock(cls, path, _type='w', stdio=None): + return cls.exclusive_lock_obj(cls.open(path, _type=_type, stdio=stdio)) + + @staticmethod + def unlock(obj, stdio=None): + stdio and getattr(stdio, 'verbose', print)('unlock %s' % obj.name) + fcntl.flock(obj, fcntl.LOCK_UN) + return obj + + def size_format(num, unit="B", output_str=False, stdio=None): + if num < 0: + raise ValueError("num cannot be negative!") + units = ["B", "K", "M", "G", "T"] + try: + unit_idx = units.index(unit) + except KeyError: + raise ValueError("unit {0} is illegal!".format(unit)) + new_num = float(num) * (1024 ** unit_idx) + unit_idx = 0 + while new_num > 1024: + new_num = float(new_num) / 1024 + unit_idx += 1 + if unit_idx >= len(units): + raise ValueError("size exceed 1023TB!") + if output_str: + return "".join(["%.3f" % new_num, units[unit_idx]]) + return new_num, units[unit_idx] + + @staticmethod + def show_file_size_tabulate(ip, file_size, stdio=None): + format_file_size = FileUtil.size_format(int(file_size), output_str=True, stdio=stdio) + summary_tab = [] + field_names = ["Node", "LogSize"] + summary_tab.append((ip, format_file_size)) + return "\nZipFileInfo:\n" + \ + tabulate.tabulate(summary_tab, headers=field_names, tablefmt="grid", showindex=False) + + @staticmethod + def show_file_list_tabulate(ip, file_list, stdio=None): + summary_tab = [] + field_names = ["Node", "LogList"] + summary_tab.append((ip, file_list)) + return "\nFileListInfo:\n" + \ + tabulate.tabulate(summary_tab, headers=field_names, tablefmt="grid", showindex=False) + + @staticmethod + def find_all_file(base, stdio=None): + file_list = [] + for root, ds, fs in os.walk(base): + for f in fs: + fullname = os.path.join(root, f) + file_list.append(fullname) + return file_list + + @staticmethod + def calculate_sha256(filepath, stdio=None): + + sha256 = hashlib.sha256() + try: + filepath = os.path.expanduser(filepath) + with open(filepath, 'rb') as file: + while True: + data = file.read(8192) + if not data: + break + sha256.update(data) + return sha256.hexdigest() + except Exception as e: + return "" + + def size(size_str, unit='B', stdio=None): + unit_size_dict = { + "b": 1, + "B": 1, + "k": 1024, + "K": 1024, + "m": 1024 * 1024, + "M": 1024 * 1024, + "g": 1024 * 1024 * 1024, + "G": 1024 * 1024 * 1024, + "t": 1024 * 1024 * 1024 * 1024, + "T": 1024 * 1024 * 1024 * 1024, + } + unit_str = size_str.strip()[-1] + if unit_str not in unit_size_dict: + raise ValueError('unit {0} not in {1}'.format(unit_str, unit_size_dict.keys())) + real_size = float(size_str.strip()[:-1]) * unit_size_dict[unit_str] + if real_size < 0: + raise ValueError('size cannot be negative!') + return real_size / unit_size_dict[unit] + + def write_append(filename, result, stdio=None): + with io.open(filename, 'a', encoding='utf-8') as fileobj: + fileobj.write(u'{}'.format(result)) + + +class YamlLoader(YAML): + + def __init__(self, stdio=None, typ=None, pure=False, output=None, plug_ins=None): + super(YamlLoader, self).__init__(typ=typ, pure=pure, output=output, plug_ins=plug_ins) + self.stdio = stdio + if not self.Representer.yaml_multi_representers and self.Representer.yaml_representers: + self.Representer.yaml_multi_representers = self.Representer.yaml_representers + + def load(self, stream): + try: + return super(YamlLoader, self).load(stream) + except Exception as e: + if getattr(self.stdio, 'exception', False): + self.stdio.exception('Parsing error:\n%s' % e) + raise e + + def loads(self, yaml_content): + try: + stream = BytesIO() + yaml_content = str(yaml_content).encode() + stream.write(yaml_content) + stream.seek(0) + return self.load(stream) + except Exception as e: + if getattr(self.stdio, 'exception', False): + self.stdio.exception('Parsing error:\n%s' % e) + raise e + + def dump(self, data, stream=None, transform=None): + try: + return super(YamlLoader, self).dump(data, stream=stream, transform=transform) + except Exception as e: + if getattr(self.stdio, 'exception', False): + self.stdio.exception('dump error:\n%s' % e) + raise e + + def dumps(self, data, transform=None): + try: + stream = BytesIO() + self.dump(data, stream=stream, transform=transform) + stream.seek(0) + content = stream.read() + if sys.version_info.major == 2: + return content + return content.decode() + except Exception as e: + if getattr(self.stdio, 'exception', False): + self.stdio.exception('dumps error:\n%s' % e) + raise e + +class YamlUtils(object): + + @staticmethod + def is_yaml_file(path, stdio=None): + if not os.path.isfile(path): + return False + if path.endswith(('.yaml', '.yml')): + return True + else: + return False + + @staticmethod + def read_yaml_data(file_path, stdio=None): + if YamlUtils.is_yaml_file(file_path): + try: + with open(file_path, 'r') as f: + data = yaml.load(f, Loader=yaml.FullLoader) + return data + except yaml.YAMLError as exc: + raise Exception("Error loading YAML from file, error: {0}".format(exc)) + + @staticmethod + def write_yaml_data(data, file_path, stdio=None): + with open(file_path, 'w') as f: + yaml.safe_dump(data, f, allow_unicode=True, sort_keys=False) + + @staticmethod + def write_yaml_data_append(data, file_path, stdio=None): + with open(file_path, 'a+') as f: + yaml.safe_dump(data, f, allow_unicode=True, sort_keys=False) + + +class CommandEnv(SafeStdio): + + def __init__(self): + self.source_path = None + self._env = os.environ.copy() + self._cmd_env = {} + + def load(self, source_path, stdio=None): + if self.source_path: + stdio.error("Source path of env already set.") + return False + self.source_path = source_path + try: + if os.path.exists(source_path): + with FileUtil.open(source_path, 'r') as f: + self._cmd_env = json.load(f) + except: + stdio.exception("Failed to load environments from {}".format(source_path)) + return False + return True + + def save(self, stdio=None): + if self.source_path is None: + stdio.error("Command environments need to load at first.") + return False + stdio.verbose("save environment variables {}".format(self._cmd_env)) + try: + with FileUtil.open(self.source_path, 'w', stdio=stdio) as f: + json.dump(self._cmd_env, f) + except: + stdio.exception('Failed to save environment variables') + return False + return True + + def get(self, key, default=""): + try: + return self.__getitem__(key) + except KeyError: + return default + + def set(self, key, value, save=False, stdio=None): + stdio.verbose("set environment variable {} value {}".format(key, value)) + self._cmd_env[key] = str(value) + if save: + return self.save(stdio=stdio) + return True + + def delete(self, key, save=False, stdio=None): + stdio.verbose("delete environment variable {}".format(key)) + if key in self._cmd_env: + del self._cmd_env[key] + if save: + return self.save(stdio=stdio) + return True + + def clear(self, save=True, stdio=None): + self._cmd_env = {} + if save: + return self.save(stdio=stdio) + return True + + def __getitem__(self, item): + value = self._cmd_env.get(item) + if value is None: + value = self._env.get(item) + if value is None: + raise KeyError(item) + return value + + def __contains__(self, item): + if item in self._cmd_env: + return True + elif item in self._env: + return True + else: + return False + + def copy(self): + result = dict(self._env) + result.update(self._cmd_env) + return result + + def show_env(self): + return self._cmd_env + + +class NetUtils(object): + + @staticmethod + def get_inner_ip(stdio=None): + localhost_ip = "127.0.0.1" + try: + localhost_ip = socket.gethostbyname(socket.gethostname()) + return localhost_ip + except Exception as e: + return localhost_ip + + @staticmethod + def network_connectivity(url="", stdio=None): + try: + socket.setdefaulttimeout(3) + response = requests.get(url, timeout=(3)) + if response.status_code is not None: + return True + else: + return False + except Exception as e: + return False + + @staticmethod + def download_file(url, local_filename, stdio=None): + with requests.get(url, stream=True) as r: + r.raise_for_status() + with open(local_filename, 'wb') as f: + for chunk in r.iter_content(chunk_size=8192): + f.write(chunk) + return local_filename + +COMMAND_ENV=CommandEnv() + + +class DateTimeEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, datetime.datetime): + # 将datetime对象转换为字符串 + return obj.strftime('%Y-%m-%d %H:%M:%S') + # 其他类型按默认处理 + return super().default(obj) + +class TimeUtils(object): + + @staticmethod + def parse_time_sec(time_str): + unit = time_str[-1] + value = int(time_str[:-1]) + if unit == "s": + value *= 1 + elif unit == "m": + value *= 60 + elif unit == "h": + value *= 3600 + elif unit == "d": + value *= 3600 * 24 + else: + raise Exception('%s parse time to second fialed:' % (time_str)) + return value + + @staticmethod + def get_format_time(time_str, stdio=None): + try: + return datetime.datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S") + except Exception as e: + stdio.exception('%s parse time fialed, error:\n%s, time format need to be %s' % (time_str, e, '%Y-%m-%d %H:%M:%S')) + + + @staticmethod + def sub_minutes(t, delta, stdio=None): + try: + return (t - datetime.timedelta(minutes=delta)).strftime('%Y-%m-%d %H:%M:%S') + except Exception as e: + stdio.exception('%s get time fialed, error:\n%s' % (t, e)) + + + @staticmethod + def add_minutes(t, delta, stdio=None): + try: + return (t + datetime.timedelta(minutes=delta)).strftime('%Y-%m-%d %H:%M:%S') + except Exception as e: + stdio.exception('%s get time fialed, error:\n%s' % (t, e)) + + @staticmethod + def parse_time_from_to(from_time=None, to_time=None, stdio=None): + format_from_time = None + format_to_time = None + sucess = False + if from_time: + format_from_time = TimeUtils.get_format_time(from_time, stdio) + format_to_time = TimeUtils.get_format_time(to_time, stdio) if to_time else TimeUtils.add_minutes(format_from_time, 30) + else: + if to_time: + format_to_time = TimeUtils.get_format_time(to_time, stdio) + format_from_time = TimeUtils.sub_minutes(format_to_time, 30) + if format_from_time and format_to_time: + sucess = True + return format_from_time, format_to_time, sucess + + @staticmethod + def parse_time_since(since=None, stdio=None): + now_time = datetime.datetime.now() + format_to_time = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') + try: + format_from_time = (now_time - datetime.timedelta(seconds=TimeUtils.parse_time_sec(since))).strftime('%Y-%m-%d %H:%M:%S') + except Exception as e: + stdio.exception('%s parse time fialed, error:\n%s' % (since, e)) + format_from_time = TimeUtils.sub_minutes(format_to_time, 30) + return format_from_time, format_to_time + + @staticmethod + def get_current_us_timestamp(stdio=None): + time_second = time.time() + return int(time_second * 1000000) + + @staticmethod + def parse_time_length_to_sec(time_length_str, stdio=None): + unit = time_length_str[-1] + if unit != "m" and unit != "h" and unit != "d": + raise Exception("time length must be format 'n'") + value = int(time_length_str[:-1]) + if unit == "m": + value *= 60 + elif unit == "h": + value *= 3600 + elif unit == "d": + value *= 3600 * 24 + else: + raise Exception("time length must be format 'n'") + return int(value) + + @staticmethod + def datetime_to_timestamp(datetime_str, stdio=None): + # yyyy-mm-dd hh:mm:ss.uuuuus or yyyy-mm-dd hh:mm:ss + try: + if len(datetime_str) > 19: + dt = datetime.datetime.strptime(datetime_str, '%Y-%m-%d %H:%M:%S.%f') + else: + dt = datetime.datetime.strptime(datetime_str, '%Y-%m-%d %H:%M:%S') + return int(dt.timestamp() * 1000000) + except Exception as e: + return 0 + + @staticmethod + def trans_datetime_utc_to_local(datetime_str, stdio=None): + utct_date = datetime.datetime.strptime(datetime_str, "%Y-%m-%dT%H:%M:%S") # 2020-12-01 03:21:57 + local_date = utct_date + datetime.timedelta(hours=8) # 加上时区 + local_date_srt = datetime.datetime.strftime(local_date, "%Y-%m-%d %H:%M:%S") # 2020-12-01 11:21:57 + trans_res = datetime.datetime.strptime(local_date_srt, "%Y-%m-%d %H:%M:%S") + return str(trans_res) + + @staticmethod + def timestamp_to_filename_time(timestamp, stdio=None): + second_timestamp = timestamp / 1000000 + time_obj = time.localtime(int(second_timestamp)) + filename_time_str = time.strftime('%Y%m%d%H%M%S', time_obj) + return filename_time_str + + @staticmethod + def parse_time_str(arg_time, stdio=None): + format_time = '' + try: + format_time = datetime.datetime.strptime(arg_time, "%Y-%m-%d %H:%M:%S") + except ValueError as e: + raise ValueError("time option {0} must be formatted as {1}".format(arg_time, '"%Y-%m-%d %H:%M:%S"')) + return format_time + + @staticmethod + def filename_time_to_datetime(filename_time, stdio=None): + """ transform yyyymmddhhmmss to yyyy-mm-dd hh:mm:ss""" + if filename_time != "": + return "{0}-{1}-{2} {3}:{4}:{5}".format(filename_time[0:4], filename_time[4:6], filename_time[6:8], filename_time[8:10], filename_time[10:12], filename_time[12:14]) + else: + return "" + + @staticmethod + def extract_filename_time_from_log_name(log_name, stdio=None): + """ eg: xxx.20221226231617 """ + log_name_fields = log_name.split(".") + if bytes.isdigit(log_name_fields[-1].encode("utf-8")) and len(log_name_fields[-1]) >= 14: + return log_name_fields[-1] + return "" + + @staticmethod + def extract_time_from_log_file_text(log_text, stdio=None): + # 因为 yyyy-mm-dd hh:mm:ss.000000 的格式已经占了27个字符,所以如果传进来的字符串包含时间信息,那长度一定大于27 + if len(log_text) > 27: + if log_text.startswith("["): + time_str = log_text[1: log_text.find(']')] + else: + time_str = log_text[0: log_text.find(',')] + time_without_us = time_str[0: time_str.find('.')] + try: + format_time = datetime.datetime.strptime(time_without_us, "%Y-%m-%d %H:%M:%S") + format_time_str = time.strftime("%Y-%m-%d %H:%M:%S", format_time.timetuple()) + except Exception as e: + format_time_str = "" + else: + format_time_str = "" + return format_time_str + + @staticmethod + def get_time_rounding(dt, step=0, rounding_level="s", stdio=None): + """ + 计算整分钟,整小时,整天的时间 + :param step: 往前或往后跳跃取整值,默认为0,即当前所在的时间,正数为往后,负数往前。 + 例如: + step = 0 时 2022-07-26 17:38:21.869993 取整秒后为 2022-07-26 17:38:21 + step = 1 时 2022-07-26 17:38:21.869993 取整秒后为 2022-07-26 17:38:22 + step = -1 时 2022-07-26 17:38:21.869993 取整秒后为 2022-07-26 17:38:20 + :param rounding_level: 字符串格式。 + "s": 按秒取整;"min": 按分钟取整;"hour": 按小时取整;"days": 按天取整 + :return: 处理后的时间 + """ + if rounding_level == "days": + td = timedelta(days=-step, seconds=dt.second, microseconds=dt.microsecond, milliseconds=0, minutes=dt.minute, hours=dt.hour, weeks=0) + new_dt = dt - td + elif rounding_level == "hour": + td = timedelta(days=0, seconds=dt.second, microseconds=dt.microsecond, milliseconds=0, minutes=dt.minute, hours=-step, weeks=0) + new_dt = dt - td + elif rounding_level == "min": + td = timedelta(days=0, seconds=dt.second, microseconds=dt.microsecond, milliseconds=0, minutes=-step, hours=0, weeks=0) + new_dt = dt - td + elif rounding_level == "s": + td = timedelta(days=0, seconds=-step, microseconds=dt.microsecond, milliseconds=0, minutes=0, hours=0, weeks=0) + new_dt = dt - td + else: + new_dt = dt + return str(new_dt) + + @staticmethod + def trans_time(size: int): + """ + 将时间单位转化为字符串 + :param size: 时间单位,单位为微秒 + :return: 转化后的字符串 + """ + if size < 0: + return 'NO_END' + mapping = [ + (86400000000, 'd'), + (3600000000, 'h'), + (60000000, 'm'), + (1000000, 's'), + (1000, 'ms'), + (1, 'μs'), + ] + for unit, unit_str in mapping: + if size >= unit: + if unit == 1: + return '{} {}'.format(size, unit_str) + else: + return '{:.3f} {}'.format(size / unit, unit_str) + return '0' + + @staticmethod + def str_2_timestamp(t, stdio=None): + if isinstance(t, int): + return t + temp = datetime.datetime.strptime(t, '%Y-%m-%d %H:%M:%S.%f') + return int(datetime.datetime.timestamp(temp) * 10 ** 6) + +class StringUtils(object): + + @staticmethod + def parse_mysql_conn(cli_conn_str, stdio=None): + db_info = {} + # 处理密码选项,注意区分短选项和长选项的密码 + password_pattern = re.compile(r'(-p\s*|--password=)([^ ]*)') + password_match = password_pattern.search(cli_conn_str) + if password_match: + password = password_match.group(2) + db_info['password'] = password + # 去除密码部分,避免后续解析出错 + cli_conn_str = cli_conn_str[:password_match.start()] + cli_conn_str[password_match.end():] + + # 模式匹配短选项 + short_opt_pattern = re.compile(r'-(\w)\s*(\S*)') + matches = short_opt_pattern.finditer(cli_conn_str) + for match in matches: + opt = match.group(1) + value = match.group(2) + if opt == 'h': + db_info['host'] = value + elif opt == 'u': + db_info['user'] = value + elif opt == 'P': + db_info['port'] = int(value) + elif opt == 'D': + db_info['database'] = value + + # 模式匹配长选项 + long_opt_pattern = re.compile(r'--(\w+)=([^ ]+)') + long_matches = long_opt_pattern.finditer(cli_conn_str) + for match in long_matches: + opt = match.group(1) + value = match.group(2) + if opt == 'host': + db_info['host'] = value + elif opt == 'user': + db_info['user'] = value + elif opt == 'port': + db_info['port'] = int(value) + elif opt in ['dbname', 'database']: + db_info['database'] = value + + # 如果存在命令行最后的参数,且不是一个选项,则认为是数据库名 + last_param = cli_conn_str.split()[-1] + if last_param[0] != '-' and 'database' not in db_info: + db_info['database'] = last_param + return db_info + + @staticmethod + def validate_db_info(db_info, stdio=None): + required_keys = {'database', 'host', 'user', 'port'} + if not required_keys.issubset(db_info.keys()) or any(not value for value in db_info.values()): + return False + if not isinstance(db_info['port'], int): + return False + for key, value in db_info.items(): + if key != 'port' and not isinstance(value, str): + return False + return True + + @staticmethod + def parse_env(env_string, stdio=None): + env_dict = {} + inner_str = env_string[1:-1] + pairs = inner_str.split(',') + for pair in pairs: + key_value = pair.strip().split('=') + if len(key_value) == 2: + key, value = key_value + if value.startswith('"') and value.endswith('"'): + value = value[1:-1] + elif value.startswith("'") and value.endswith("'"): + value = value[1:-1] + env_dict[key.strip()] = value.strip() + return env_dict + + @staticmethod + def get_observer_ip_from_trace_id(content, stdio=None): + if content[0] == 'Y' and len(content) >= 12: + sep = content.find('-') + uval = int(content[1:sep], 16) + ip = uval & 0xffffffff + port = (uval >> 32) & 0xffff + return "%d.%d.%d.%d:%d" % ((ip >> 24 & 0xff), (ip >> 16 & 0xff), (ip >> 8 & 0xff), (ip >> 0 & 0xff), port) + else: + return "" + + @staticmethod + def parse_range_string(range_str, nu, stdio=None): + # parse_range_string: Determine whether variable 'nu' is within the range of 'range_str' + # 提取范围字符串中的数字 + nu = int(nu) + range_str = range_str.replace(" ", "") + # range_str = range_str.replace(".", "") + start, end = range_str[1:-1].split(',') + need_less = True + need_than = True + # 将数字转换为整数 + if start.strip() == "*": + need_less = False + else: + start = float(start.strip()) + if end.strip() == "*": + need_than = False + else: + end = float(end.strip()) + stdio and getattr(stdio, 'verbose', print)('range_str is %s' % range_str) + + if need_less: + if range_str[0] == "(": + if nu <= start: + return False + elif range_str[0] == "[": + if nu < start: + return False + if need_than: + if range_str[-1] == ")": + if nu >= end: + return False + elif range_str[-1] == "]": + if nu > end: + return False + return True + + @staticmethod + def build_str_on_expr_by_dict(expr, variable_dict, stdio=None): + s = expr + d = variable_dict + def replacer(match): + key = match.group(1) + return str(d.get(key, match.group(0))) + return re.sub(r'#\{(\w+)\}', replacer, s) + + @staticmethod + def build_str_on_expr_by_dict_2(expr, variable_dict, stdio=None): + s = expr + d = variable_dict + def replacer(match): + key = match.group(1) + value = str(d.get(key, match.group(0))) + return f"{value}" + return re.sub(r'\$\{(\w+)\}', replacer, s) + + @staticmethod + def node_cut_passwd_for_log(obj, stdio=None): + if isinstance(obj, dict): + new_obj = {} + for key, value in obj.items(): + if key == "password" or key == "ssh_password": + continue + new_obj[key] = StringUtils.node_cut_passwd_for_log(value) + return new_obj + elif isinstance(obj, list): + return [StringUtils.node_cut_passwd_for_log(item) for item in obj] + else: + return obj + + @staticmethod + def split_ip(ip_str, stdio=None): + pattern = r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' + result = re.findall(pattern, ip_str) + return result + + @staticmethod + def is_chinese(s, stdio=None): + try: + s.encode('ascii') + except UnicodeEncodeError: + return True + else: + return False + + @staticmethod + def compare_versions_greater(v1, v2, stdio=None): + for i, j in zip(map(int, v1.split(".")), map(int, v2.split("."))): + if i == j: + continue + return i > j + return len(v1.split(".")) > len(v2.split(".")) + + @staticmethod + def compare_versions_lower(v1, v2, stdio=None): + for i, j in zip(map(int, v1.split(".")), map(int, v2.split("."))): + if i == j: + continue + return i < j + return len(v1.split(".")) < len(v2.split(".")) + + + +class Cursor(SafeStdio): + + def __init__(self, ip, port, user='root', tenant='sys', password='', stdio=None): + self.stdio = stdio + self.ip = ip + self.port = port + self._user = user + self.tenant = tenant + self.password = password + self.cursor = None + self.db = None + self._connect() + self._raise_exception = False + self._raise_cursor = None + + @property + def user(self): + if "@" in self._user: + return self._user + if self.tenant: + return "{}@{}".format(self._user, self.tenant) + else: + return self._user + + @property + def raise_cursor(self): + if self._raise_cursor: + return self._raise_cursor + raise_cursor = copy(self) + raise_cursor._raise_exception = True + self._raise_cursor = raise_cursor + return raise_cursor + + if sys.version_info.major == 2: + def _connect(self): + self.stdio.verbose('connect %s -P%s -u%s -p%s' % (self.ip, self.port, self.user, self.password)) + self.db = mysql.connect(host=self.ip, user=self.user, port=int(self.port), passwd=str(self.password)) + self.cursor = self.db.cursor(cursorclass=mysql.cursors.DictCursor) + else: + def _connect(self): + self.stdio.verbose('connect %s -P%s -u%s -p%s' % (self.ip, self.port, self.user, self.password)) + self.db = mysql.connect(host=self.ip, user=self.user, port=int(self.port), password=str(self.password), + cursorclass=mysql.cursors.DictCursor) + self.cursor = self.db.cursor() + + def new_cursor(self, tenant='sys', user='root', password='', ip='', port='', print_exception=True): + try: + ip = ip if ip else self.ip + port = port if port else self.port + return Cursor(ip=ip, port=port, user=user, tenant=tenant, password=password, stdio=self.stdio) + except: + print_exception and self.stdio.exception('') + self.stdio.verbose('fail to connect %s -P%s -u%s@%s -p%s' % (ip, port, user, tenant, password)) + return None + + def execute(self, sql, args=None, execute_func=None, raise_exception=False, exc_level='error', stdio=None): + try: + stdio.verbose('execute sql: %s. args: %s' % (sql, args)) + self.cursor.execute(sql, args) + if not execute_func: + return self.cursor + return getattr(self.cursor, execute_func)() + except Exception as e: + getattr(stdio, exc_level)(EC_SQL_EXECUTE_FAILED.format(sql=sql)) + if raise_exception is None: + raise_exception = self._raise_exception + if raise_exception: + stdio.exception('') + raise e + return False + + def fetchone(self, sql, args=None, raise_exception=False, exc_level='error', stdio=None): + return self.execute(sql, args=args, execute_func='fetchone', raise_exception=raise_exception, exc_level=exc_level, stdio=stdio) + + def fetchall(self, sql, args=None, raise_exception=False, exc_level='error', stdio=None): + return self.execute(sql, args=args, execute_func='fetchall', raise_exception=raise_exception, exc_level=exc_level, stdio=stdio) + + def close(self): + if self.cursor: + self.cursor.close() + self.cursor = None + if self.db: + self.db.close() + self.db = None + + +class Util(object): + + @staticmethod + def get_option(options, key, default=None): + if not hasattr(options, key) : + return default + value = getattr(options, key) + if value is None: + value = default + return value + + @staticmethod + def set_option(options, key, value): + setattr(options, key, value) + + @staticmethod + def convert_to_number(s, stdio=None): + if isinstance(s, (int, float)): + return s + if isinstance(s,decimal.Decimal): + try: + return float(s) + except: + return s + + if isinstance(s, str): + if s.startswith("-"): + if s[1:].isdigit(): + return int(s) + elif s[1:].isdecimal(): # 判断字符串是否全为数字或小数点 + return float(s) # 如果是,转换为浮点数 + if s.isdigit(): # 判断字符串是否全为数字 + return int(s) # 如果是,转换为整数 + elif s.isdecimal(): # 判断字符串是否全为数字或小数点 + return float(s) # 如果是,转换为浮点数 + try: + return float(s) + except Exception: + pass + + return s + + @staticmethod + def print_scene(scene_dict, stdio=None): + columns_to_print = ['command', 'info_en', 'info_cn'] + keys = columns_to_print + table_data = [[value[key] for key in keys] for value in scene_dict.values()] + column_widths = [max(len(str(item)) * (StringUtils.is_chinese(item) or 1) for item in column) for column in zip(*table_data)] + table_data.insert(0, keys) + Util.print_line(length= sum(column_widths) + 5) + for i in range(len(table_data)): + print(Fore.GREEN + " ".join(f"{item:<{width}}" for item, width in zip(table_data[i], column_widths)) + Style.RESET_ALL) + if i == 0: + Util.print_line(length= sum(column_widths) + 5) + Util.print_line(length= sum(column_widths) + 5) + + @staticmethod + def print_line(char='-', length=50, stdio=None): + print(char * length) + + @staticmethod + def print_title(name, stdio=None): + print("\n[{0}]:".format(name)) + + @staticmethod + def gen_password(length=8, chars=string.ascii_letters + string.digits, stdio=None): + return ''.join([choice(chars) for i in range(length)]) + + def retry(retry_count=3, retry_interval=2, stdio=None): + def real_decorator(decor_method): + def wrapper(*args, **kwargs): + for count in range(retry_count): + try: + return_values = decor_method(*args, **kwargs) + return return_values + except Exception as e: + if getattr(stdio, "Function execution %s retry: %s " %(decor_method.__name__, count + 1), False): + stdio.exception('dumps error:\n%s' % e) + time.sleep(retry_interval) + if count == retry_count - 1: + raise e + return wrapper + return real_decorator + + @staticmethod + def get_nodes_list(context, nodes, stdio=None): + ctx_nodes = context.get_variable("filter_nodes_list",None) + if ctx_nodes is not None and len(ctx_nodes)>0: + new_nodes = [] + for node in nodes: + if node in ctx_nodes: + new_nodes.append(node) + if len(new_nodes) != len(ctx_nodes) or len(new_nodes) == 0: + stdio.warn("Warn: no nodes found in context.") + return None + return new_nodes + return None diff --git a/common/types.py b/common/types.py new file mode 100644 index 00000000..2baef5f0 --- /dev/null +++ b/common/types.py @@ -0,0 +1,427 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -* +# Copyright (c) 2022 OceanBase +# OceanBase Diagnostic Tool 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. + +""" +@time: 2024/4/2 +@file: types.py +@desc: +""" + +from __future__ import absolute_import, division, print_function + +import os +import re +import uuid +import traceback + +__all__ = ( +"Moment", "Time", "Capacity", "CapacityWithB", "CapacityMB", "StringList", "Dict", "List", "StringOrKvList", "Double", +"Boolean", "Integer", "String", "Path", "SafeString", "PathList", "SafeStringList", "DBUrl", "WebUrl", "OBUser") + + +class Null(object): + + def __init__(self): + pass + + +class ConfigItemType(object): + TYPE_STR = None + NULL = Null() + + def __init__(self, s): + try: + self._origin = s + self._value = 0 + self.value = self.NULL + self._format() + if self.value == self.NULL: + self.value = self._origin + except: + raise Exception("'%s' is not %s" % (self._origin, self._type_str)) + + @property + def _type_str(self): + if self.TYPE_STR is None: + self.TYPE_STR = str(self.__class__.__name__).split('.')[-1] + return self.TYPE_STR + + def _format(self): + raise NotImplementedError + + def __str__(self): + return str(self._origin) + + def __hash__(self): + return self._origin.__hash__() + + @property + def __cmp_value__(self): + return self._value + + def __eq__(self, value): + if value is None: + return False + return self.__cmp_value__ == value.__cmp_value__ + + def __gt__(self, value): + if value is None: + return True + return self.__cmp_value__ > value.__cmp_value__ + + def __ge__(self, value): + if value is None: + return True + return self.__eq__(value) or self.__gt__(value) + + def __lt__(self, value): + if value is None: + return False + return self.__cmp_value__ < value.__cmp_value__ + + def __le__(self, value): + if value is None: + return False + return self.__eq__(value) or self.__lt__(value) + + +class Moment(ConfigItemType): + + def _format(self): + if self._origin: + if self._origin.upper() == 'DISABLE': + self._value = 0 + else: + r = re.match('^(\d{1,2}):(\d{1,2})$', self._origin) + h, m = r.groups() + h, m = int(h), int(m) + if 0 <= h <= 23 and 0 <= m <= 60: + self._value = h * 60 + m + else: + raise Exception('Invalid Value') + else: + self._value = 0 + + +class Time(ConfigItemType): + UNITS = { + 'ns': 0.000000001, + 'us': 0.000001, + 'ms': 0.001, + 's': 1, + 'm': 60, + 'h': 3600, + 'd': 86400 + } + + def _format(self): + if self._origin: + self._origin = str(self._origin).strip() + if self._origin.isdigit(): + n = self._origin + unit = self.UNITS['s'] + else: + r = re.match('^(\d+)(\w+)$', self._origin.lower()) + n, u = r.groups() + unit = self.UNITS.get(u.lower()) + if unit: + self._value = int(n) * unit + else: + raise Exception('Invalid Value') + else: + self._value = 0 + + +class DecimalValue: + + def __init__(self, value, precision=None): + if isinstance(value, str): + self.value = float(value) + else: + self.value = value + self.precision = precision + + def __repr__(self): + if self.precision is not None: + return "%.*f" % (self.precision, self.value) + return str(self.value) + + def __add__(self, other): + if isinstance(other, DecimalValue): + return DecimalValue(self.value + other.value, + self.precision if self.precision is not None else other.precision) + return DecimalValue(self.value + other, self.precision) + + def __sub__(self, other): + if isinstance(other, DecimalValue): + return DecimalValue(self.value - other.value, + self.precision if self.precision is not None else other.precision) + return DecimalValue(self.value - other, self.precision) + + def __mul__(self, other): + if isinstance(other, DecimalValue): + return DecimalValue(self.value * other.value, + self.precision if self.precision is not None else other.precision) + return DecimalValue(self.value * other, self.precision) + + def __truediv__(self, other): + if isinstance(other, DecimalValue): + return DecimalValue(self.value / other.value, + self.precision if self.precision is not None else other.precision) + return DecimalValue(self.value / other, self.precision) + + +class Capacity(ConfigItemType): + UNITS = {"B": 1, "K": 1 << 10, "M": 1 << 20, "G": 1 << 30, "T": 1 << 40, "P": 1 << 50} + + LENGTHS = {"B": 4, "K": 8, "M": 12, "G": 16, "T": 20, "P": 24} + + def __init__(self, s, precision=0): + self.precision = precision + super(Capacity, self).__init__(s) + + def __str__(self): + return str(self.value) + + @property + def btyes(self): + return self._value + + def _format(self): + if self._origin: + if not isinstance(self._origin, str) or self._origin.strip().isdigit(): + self._origin = int(float(self._origin)) + n = self._origin + unit = self.UNITS['B'] + for u in self.LENGTHS: + if len(str(self._origin)) < self.LENGTHS[u]: + break + else: + u = 'P' + else: + groups = re.match("^(\d+)\s*([BKMGTP])((IB)|B)?\s*$", self._origin.upper()) + if not groups: + raise ValueError("Invalid capacity string: %s" % self._origin) + n, u, _, _ = groups.groups() + unit = self.UNITS.get(u.upper()) + if unit: + self._value = int(n) * unit + self.value = str(DecimalValue(self._value, self.precision) / self.UNITS[u]) + u + else: + raise Exception('Invalid Value') + else: + self._value = 0 + self.value = str(DecimalValue(0, self.precision)) + + +class CapacityWithB(Capacity): + + def __init__(self, s): + super(CapacityWithB, self).__init__(s, precision=0) + + def _format(self): + super(CapacityWithB, self)._format() + self.value = self.value + 'B' + + +class CapacityMB(Capacity): + + def _format(self): + super(CapacityMB, self)._format() + if isinstance(self._origin, str) and self._origin.isdigit(): + self.value = self._origin + 'M' + self._value *= self.UNITS['M'] + if not self._origin: + self.value = '0M' + + +class StringList(ConfigItemType): + + def _format(self): + if self._origin: + self._origin = str(self._origin).strip() + self._value = self._origin.split(';') + else: + self._value = [] + + +class Dict(ConfigItemType): + + def _format(self): + if self._origin: + if not isinstance(self._origin, dict): + raise Exception("Invalid Value") + self._value = self._origin + else: + self._value = self.value = {} + + +class List(ConfigItemType): + + def _format(self): + if self._origin: + if not isinstance(self._origin, list): + raise Exception("Invalid value: {} is not a list.".format(self._origin)) + self._value = self._origin + else: + self._value = self.value = [] + + +class StringOrKvList(ConfigItemType): + + def _format(self): + if self._origin: + if not isinstance(self._origin, list): + raise Exception("Invalid value: {} is not a list.".format(self._origin)) + for item in self._origin: + if not item: + continue + if not isinstance(item, (str, dict)): + raise Exception("Invalid value: {} should be string or key-value format.".format(item)) + if isinstance(item, dict): + if len(item.keys()) != 1: + raise Exception("Invalid value: {} should be single key-value format".format(item)) + self._value = self._origin + else: + self._value = self.value = [] + + +class Double(ConfigItemType): + + def _format(self): + self.value = self._value = float(self._origin) if self._origin else 0 + + +class Boolean(ConfigItemType): + + def _format(self): + if isinstance(self._origin, bool): + self._value = self._origin + else: + _origin = str(self._origin).lower() + if _origin == 'true': + self._value = True + elif _origin == 'false': + self._value = False + elif _origin.isdigit(): + self._value = bool(self._origin) + else: + raise Exception('%s is not Boolean' % _origin) + self.value = self._value + + +class Integer(ConfigItemType): + + def _format(self): + if self._origin is None: + self._value = 0 + self._origin = 0 + else: + _origin = str(self._origin) + try: + self.value = self._value = int(_origin) + except: + raise Exception('%s is not Integer' % _origin) + + +class String(ConfigItemType): + + def _format(self): + self.value = self._value = str(self._origin) if self._origin else '' + + +# this type is used to ensure the parameter is a valid oceanbase user +class OBUser(ConfigItemType): + OB_USER_PATTERN = re.compile("^[a-zA-Z0-9_\.-]+(@[a-zA-Z0-9_\.-]+)?(#[a-zA-Z0-9_\.-]+)?$") + + def _format(self): + if not self.OB_USER_PATTERN.match(str(self._origin)): + raise Exception("%s is not a valid config" % self._origin) + self.value = self._value = str(self._origin) if self._origin else '' + + +# this type is used to ensure the parameter not containing special characters to inject command +class SafeString(ConfigItemType): + SAFE_STRING_PATTERN = re.compile("^[a-zA-Z0-9\u4e00-\u9fa5\-_:@/\.]*$") + + def _format(self): + if not self.SAFE_STRING_PATTERN.match(str(self._origin)): + raise Exception("%s is not a valid config" % self._origin) + self.value = self._value = str(self._origin) if self._origin else '' + + +# this type is used to ensure the parameter not containing special characters to inject command +class SafeStringList(ConfigItemType): + SAFE_STRING_PATTERN = re.compile("^[a-zA-Z0-9\u4e00-\u9fa5\-_:@/\.]*$") + + def _format(self): + if self._origin: + self._origin = str(self._origin).strip() + self._value = self._origin.split(';') + for v in self._value: + if not self.SAFE_STRING_PATTERN.match(v): + raise Exception("%s is not a valid config" % v) + else: + self._value = [] + + +# this type is used to ensure the parameter is a valid path by checking it's only certaining certain characters and not crossing path +class Path(ConfigItemType): + PATH_PATTERN = re.compile("^[a-zA-Z0-9\u4e00-\u9fa5\-_:@/\.]*$") + + def _format(self): + parent_path = "/{0}".format(uuid.uuid4().hex) + absolute_path = "/".join([parent_path, str(self._origin)]) + normalized_path = os.path.normpath(absolute_path) + + if not (self.PATH_PATTERN.match(str(self._origin)) and normalized_path.startswith(parent_path)): + raise Exception("%s is not a valid path" % self._origin) + self.value = self._value = str(self._origin) if self._origin else '' + + +# this type is used to ensure the parameter is a valid path by checking it's only certaining certain characters and not crossing path +class PathList(ConfigItemType): + PATH_PATTERN = re.compile("^[a-zA-Z0-9\u4e00-\u9fa5\-_:@/\.]*$") + + def _format(self): + parent_path = "/{0}".format(uuid.uuid4().hex) + if self._origin: + self._origin = str(self._origin).strip() + self._value = self._origin.split(';') + for v in self._value: + absolute_path = "/".join([parent_path, v]) + normalized_path = os.path.normpath(absolute_path) + if not (self.PATH_PATTERN.match(v) and normalized_path.startswith(parent_path)): + raise Exception("%s is not a valid path" % v) + else: + self._value = [] + + +# this type is used to ensure the parameter is a valid database connection url +class DBUrl(ConfigItemType): + DBURL_PATTERN = re.compile( + "^jdbc:(mysql|oceanbase):(\/\/)([a-zA-Z0-9_.-]+)(:[0-9]{1,5})?\/([a-zA-Z0-9_\-]+)(\?[a-zA-Z0-9_&;=.-]*)?$") + + def _format(self): + if not self.DBURL_PATTERN.match(str(self._origin)): + raise Exception("%s is not a valid config" % self._origin) + self.value = self._value = str(self._origin) if self._origin else '' + + +# this type is used to ensure the parameter is a valid web url +class WebUrl(ConfigItemType): + WEBURL_PATTERN = re.compile("^(https?:\/\/)?([\da-z_.-]+)(:[0-9]{1,5})?([\/\w \.-]*)*\/?(?:\?[\w&=_.-]*)?$") + + def _format(self): + if not self.WEBURL_PATTERN.match(str(self._origin)): + raise Exception("%s is not a valid config" % self._origin) + self.value = self._value = str(self._origin) if self._origin else '' diff --git a/utils/version_utils.py b/common/version.py similarity index 67% rename from utils/version_utils.py rename to common/version.py index f7f7f1c0..74af20df 100644 --- a/utils/version_utils.py +++ b/common/version.py @@ -12,7 +12,7 @@ """ @time: 2022/6/22 -@file: version_utils.py +@file: version.py @desc: """ @@ -22,22 +22,6 @@ OBDIAG_BUILD_TIME = '' -def compare_versions_greater(v1, v2): - for i, j in zip(map(int, v1.split(".")), map(int, v2.split("."))): - if i == j: - continue - return i > j - return len(v1.split(".")) > len(v2.split(".")) - - -def compare_versions_lower(v1, v2): - for i, j in zip(map(int, v1.split(".")), map(int, v2.split("."))): - if i == j: - continue - return i < j - return len(v1.split(".")) < len(v2.split(".")) - - def get_obdiag_version(): version = '''OceanBase Diagnostic Tool: %s BUILD_TIME: %s @@ -47,6 +31,3 @@ def get_obdiag_version(): There is NO WARRANTY, to the extent permitted by law.''' % (OBDIAG_VERSION, OBDIAG_BUILD_TIME) return version -def print_obdiag_version(): - print(get_obdiag_version()) - diff --git a/config.py b/config.py new file mode 100644 index 00000000..fb01eb8d --- /dev/null +++ b/config.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -* +# Copyright (c) 2022 OceanBase +# OceanBase Diagnostic Tool 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. + +""" +@file: config.py +@desc: +""" + +from __future__ import absolute_import, division, print_function +import os +from common.tool import DirectoryUtil +from stdio import SafeStdio +import oyaml as yaml +import pathlib +import sys +from collections import defaultdict + + +if getattr(sys, 'frozen', False): + absPath = os.path.dirname(os.path.abspath(sys.executable)) +else: + absPath = os.path.dirname(os.path.abspath(__file__)) +INNER_CONFIG_FILE = os.path.join(absPath, "conf/inner_config.yml") + +DEFAULT_CONFIG_DATA = ''' +obcluster: + ob_cluster_name: obcluster + db_host: 127.0.0.1 + db_port: 2881 + tenant_sys: + user: root@sys + password: "" + servers: + nodes: + - ip: 127.0.0.1 + global: + ssh_username: '' + ssh_password: '' + home_path: /root/observer +obproxy: + obproxy_cluster_name: obproxy + servers: + nodes: + - ip: 127.0.0.1 + global: + ssh_username: '' + ssh_password: '' + home_path: /root/obproxy +''' + +DEFAULT_INNER_CONFIG = { + 'obdiag': { + 'basic': { + 'config_path': '~/.obdiag/config.yml', + 'config_backup_dir': '~/.obdiag/backup_conf', + 'file_number_limit': 20, + 'file_size_limit': '2G', + }, + 'logger': { + 'log_dir': '~/.obdiag/log', + 'log_filename': 'obdiag.log', + 'file_handler_log_level': 'DEBUG', + 'log_level': 'INFO', + 'mode': 'obdiag', + 'stdout_handler_log_level': 'INFO', + }, + }, + 'check': { + 'ignore_version': False, + 'work_path': '~/.obdiag/check', + 'report': { + 'report_path': './check_report/', + 'export_type': 'table', + }, + 'package_file': '~/.obdiag/check/check_package.yaml', + 'tasks_base_path': '~/.obdiag/check/tasks/', + }, + 'gather': { + 'scenes_base_path': '~/.obdiag/gather/tasks', + }, + 'rca': { + 'result_path': './rca/', + }, + } + + +class Manager(SafeStdio): + + RELATIVE_PATH = '' + + def __init__(self, home_path, stdio=None): + self.stdio = stdio + self.path = home_path + self.is_init = self._mkdir(self.path) + + def _mkdir(self, path): + return DirectoryUtil.mkdir(path, stdio=self.stdio) + + def _rm(self, path): + return DirectoryUtil.rm(path, self.stdio) + + def load_config(self): + try: + with open(self.path, 'r') as file: + return yaml.safe_load(file) + except FileNotFoundError: + self.stdio.exception(f"Configuration file '{self.path}' not found.") + except yaml.YAMLError as exc: + self.stdio.exception(f"Error parsing YAML file: {exc}") + + def load_config_with_defaults(self, defaults_dict): + default_config = defaultdict(lambda: None, defaults_dict) + try: + with open(self.path, 'r') as stream: + loaded_config = yaml.safe_load(stream) + except FileNotFoundError: + self.stdio.exception(f"Configuration file '{self.path}' not found.") + return default_config + except yaml.YAMLError as exc: + self.stdio.exception(f"Error parsing YAML file: {exc}") + return default_config + combined_config = defaultdict(lambda: None, loaded_config) + for section, values in default_config.items(): + if isinstance(values, dict): + for key, default_value in values.items(): + if section not in combined_config or key not in combined_config[section]: + combined_config[section][key] = default_value + else: + if section not in combined_config: + combined_config[section] = values + return dict(combined_config) + + +class ConfigManager(Manager): + + def __init__(self, config_file=None, stdio=None): + default_config_path = os.path.join(os.path.expanduser("~"), ".obdiag", "config.yml") + + if config_file is None or not os.path.exists(config_file): + config_file = default_config_path + pathlib.Path(os.path.dirname(default_config_path)).mkdir(parents=True, exist_ok=True) + with open(default_config_path, 'w') as f: + f.write(DEFAULT_CONFIG_DATA) + super(ConfigManager, self).__init__(config_file, stdio) + self.config_file = config_file + self.config_data = self.load_config() + + def _safe_get(self, dictionary, *keys, default=None): + """Safe way to retrieve nested values from dictionaries""" + current = dictionary + for key in keys: + try: + current = current[key] + except KeyError: + return default + return current + + @property + def get_ocp_config(self): + ocp = self._safe_get(self.config_data, 'ocp', 'login', default={}) + return { + 'url': ocp.get('url'), + 'user': ocp.get('user'), + 'password': ocp.get('password'), + } + + @property + def get_ob_cluster_config(self): + ob_cluster = self.config_data.get('obcluster', {}) + nodes = ob_cluster.get('servers', {}).get('nodes', []) + + def create_ob_cluster_node(node_config, global_config): + return { + 'ip': node_config.get('ip'), + 'ssh_username': node_config.get('ssh_username', global_config.get('ssh_username')), + 'ssh_password': node_config.get('ssh_password', global_config.get('ssh_password')), + 'ssh_port': node_config.get('ssh_port', global_config.get('ssh_port', 22)), + 'home_path': node_config.get('home_path', global_config.get('home_path', '/root/observer')), + 'data_dir': node_config.get('data_dir', global_config.get('data_dir', '/root/observer/store')), + 'redo_dir': node_config.get('redo_dir', global_config.get('redo_dir', '/root/observer/store')), + 'ssh_key_file': node_config.get('ssh_key_file', global_config.get('ssh_key_file', '')), + 'ssh_type': node_config.get('ssh_type', global_config.get('ssh_type', 'remote')), + 'container_name': node_config.get('container_name', global_config.get('container_name')), + 'host_type': 'OBSERVER', + } + + global_config = ob_cluster.get('servers', {}).get('global', {}) + ob_cluster_nodes = [create_ob_cluster_node(node, global_config) for node in nodes] + + return { + 'ob_cluster_name': ob_cluster.get('ob_cluster_name'), + 'db_host': ob_cluster.get('db_host'), + 'db_port': ob_cluster.get('db_port'), + 'tenant_sys': { + 'user': ob_cluster.get('tenant_sys', {}).get('user'), + 'password': ob_cluster.get('tenant_sys', {}).get('password'), + }, + 'servers': ob_cluster_nodes, + } + + @property + def get_obproxy_config(self): + ob_proxy = self.config_data.get('obproxy', {}) + nodes = ob_proxy.get('servers', {}).get('nodes', []) + + def create_ob_proxy_node(node_config, global_config): + return { + 'ip': node_config.get('ip'), + 'ssh_username': node_config.get('ssh_username', global_config.get('ssh_username', '')), + 'ssh_password': node_config.get('ssh_password', global_config.get('ssh_password', '')), + 'ssh_port': node_config.get('ssh_port', global_config.get('ssh_port', 22)), + 'home_path': node_config.get('home_path', global_config.get('home_path', '/root/obproxy')), + 'ssh_key_file': node_config.get('ssh_key_file', global_config.get('ssh_key_file', '')), + 'ssh_type': node_config.get('ssh_type', global_config.get('ssh_type', 'ssh')), + 'container_name': node_config.get('container_name', global_config.get('container_name')), + 'host_type': 'OBPROXY', + } + + global_config = ob_proxy.get('servers', {}).get('global', {}) + ob_proxy_nodes = [create_ob_proxy_node(node, global_config) for node in nodes] + + return { + 'obproxy_cluster_name': ob_proxy.get('obproxy_cluster_name'), + 'servers': ob_proxy_nodes, + } + + @property + def get_node_config(self, type, node_ip, config_item): + if type == 'ob_cluster': + nodes = self.get_ob_cluster_config()['servers'] + elif type == 'ob_proxy': + nodes = self.get_obproxy_config()['servers'] + else: + self.stdio.exception(f"Unsupported cluster type: {type}") + + for node in nodes: + if node['ip'] == node_ip: + return node.get(config_item) + return None + +class InnerConfigManager(Manager): + + def __init__(self, stdio=None): + inner_config_abs_path = os.path.abspath(INNER_CONFIG_FILE) + super().__init__(inner_config_abs_path, stdio=stdio) + self.config = self.load_config_with_defaults(DEFAULT_INNER_CONFIG) \ No newline at end of file diff --git a/context.py b/context.py new file mode 100644 index 00000000..2e4e7b59 --- /dev/null +++ b/context.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -* +# Copyright (c) 2022 OceanBase +# OceanBase Diagnostic Tool 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. + +""" +@file: context.py +@desc: +""" +from __future__ import absolute_import, division, print_function +from optparse import Values + +class HandlerContextNamespace: + + def __init__(self, spacename): + self.spacename = spacename + self._variables = {} + self._return = {} + self._options = Values() + + @property + def variables(self): + return self._variables + + def get_variable(self, name, default=None): + return self._variables.get(name, default) + + def set_variable(self, name, value): + self._variables[name] = value + + def get_option(self, name, default=None): + return self._variables.get(name, default) + + def set_option(self, name, value): + self._options[name] = value + + def get_return(self, handler_name): + ret = self._return.get(handler_name) + if isinstance(ret, HandlerReturn): + return ret + return None + + def set_return(self, handler_name, handler_return): + self._return[handler_name] = handler_return + + +class HandlerReturn(object): + + def __init__(self, value=False, *arg, **kwargs): + self._return_value = value + self._return_args = arg + self._return_kwargs = kwargs + + def __nonzero__(self): + return self.__bool__() + + def __bool__(self): + return True if self._return_value else False + + @property + def value(self): + return self._return_value + + @property + def args(self): + return self._return_args + + @property + def kwargs(self): + return self._return_kwargs + + def get_return(self, key, default=None): + return self.kwargs.get(key, default) + + def set_args(self, *args): + self._return_args = args + + def set_kwargs(self, **kwargs): + self._return_kwargs = kwargs + + def set_return(self, value): + self._return_value = value + + def return_true(self, *args, **kwargs): + self.set_return(True) + self.set_args(*args) + self.set_kwargs(**kwargs) + + def return_false(self, *args, **kwargs): + self.set_return(False) + self.set_args(*args) + self.set_kwargs(**kwargs) + + +class HandlerContext(object): + + def __init__(self, handler_name=None, namespace=None, namespaces=None, cluster_config=None, obproxy_config=None, ocp_config=None, inner_config=None, cmd=None, options=None, stdio=None): + self.namespace = HandlerContextNamespace(namespace) + self.namespaces = namespaces + self.handler_name = handler_name + self.cluster_config = cluster_config + self.obproxy_config = obproxy_config + self.ocp_config = ocp_config + self.inner_config = inner_config + self.cmds = cmd + self.options = options + self.stdio = stdio + self._return = HandlerReturn() + + def get_return(self, handler_name=None, spacename=None): + if spacename: + namespace = self.namespaces.get(spacename) + else: + namespace = self.namespace + if handler_name is None: + handler_name = self.handler_name + return namespace.get_return(handler_name) if namespace else None + + def return_true(self, *args, **kwargs): + self._return.return_true(*args, **kwargs) + self.namespace.set_return(self.handler_name, self._return) + + def return_false(self, *args, **kwargs): + self._return.return_false(*args, **kwargs) + self.namespace.set_return(self.handler_name, self._return) + + def get_variable(self, name, spacename=None, default=None): + if spacename: + namespace = self.namespaces.get(spacename) + else: + namespace = self.namespace + return namespace.get_variable(name, default) if namespace else None + + def set_variable(self, name, value): + self.namespace.set_variable(name, value) + + def get_option(self, name, spacename=None, default=None): + if spacename: + namespace = self.namespaces.get(spacename) + else: + namespace = self.namespace + return namespace.get_option(name, default) if namespace else None + + def set_option(self, name, value): + self.namespace.set_option(name, value) \ No newline at end of file diff --git a/core.py b/core.py new file mode 100644 index 00000000..b5d7dcf3 --- /dev/null +++ b/core.py @@ -0,0 +1,353 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -* +# Copyright (c) 2022 OceanBase +# OceanBase Diagnostic Tool 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. + +""" +@file: core.py +@desc: +""" + +from __future__ import absolute_import, division, print_function + +import os +from optparse import Values +from copy import copy + +from handler.rca.rca_handler import RCAHandler +from handler.rca.rca_list import RcaScenesListHandler +from common.ssh import SshClient, SshConfig +from context import HandlerContextNamespace, HandlerContext +from config import ConfigManager, InnerConfigManager +from err import CheckStatus, SUG_SSH_FAILED +from handler.analyzer.analyze_flt_trace import AnalyzeFltTraceHandler +from handler.analyzer.analyze_log import AnalyzeLogHandler +from handler.checker.check_handler import CheckHandler +from handler.checker.check_list import CheckListHandler +from handler.gather.gather_log import GatherLogHandler +from handler.gather.gather_awr import GatherAwrHandler +from handler.gather.gather_obproxy_log import GatherObProxyLogHandler +from handler.gather.gather_sysstat import GatherOsInfoHandler +from handler.gather.gather_obstack2 import GatherObstack2Handler +from handler.gather.gather_obadmin import GatherObAdminHandler +from handler.gather.gather_perf import GatherPerfHandler +from handler.gather.gather_plan_monitor import GatherPlanMonitorHandler +from handler.gather.gather_scenes import GatherSceneHandler +from handler.gather.scenes.list import GatherScenesListHandler +from telemetry.telemetry import telemetry +from update.update import UpdateHandler +from colorama import Fore, Style +from common.config_helper import ConfigHelper + +from common.tool import Util +from common.tool import TimeUtils + + +class ObdiagHome(object): + + def __init__(self, stdio=None, config_path=os.path.expanduser('~/.obdiag/config.yml')): + self._optimize_manager = None + self.stdio = None + self._stdio_func = None + self.cmds = [] + self.options = Values() + self.namespaces = {} + self.set_stdio(stdio) + self.context = None + self.inner_config_manager = InnerConfigManager(stdio) + self.config_manager = ConfigManager(config_path, stdio) + if self.inner_config_manager.config.get("obdiag") is not None and self.inner_config_manager.config.get("obdiag").get( + "basic") is not None and self.inner_config_manager.config.get("obdiag").get("basic").get( + "telemetry") is not None and self.inner_config_manager.config.get("obdiag").get("basic").get("telemetry") is False: + telemetry.work_tag = False + + def fork(self, cmds=None, options=None, stdio=None): + new_obdiag = copy(self) + if cmds: + new_obdiag.set_cmds(cmds) + if options: + new_obdiag.set_options(options) + if stdio: + new_obdiag.set_stdio(stdio) + return new_obdiag + + def set_cmds(self, cmds): + self.cmds = cmds + + def set_options(self, options): + self.options = options + + def set_stdio(self, stdio): + def _print(msg, *arg, **kwarg): + sep = kwarg['sep'] if 'sep' in kwarg else None + end = kwarg['end'] if 'end' in kwarg else None + return print(msg, sep='' if sep is None else sep, end='\n' if end is None else end) + + self.stdio = stdio + self._stdio_func = {} + if not self.stdio: + return + for func in ['start_loading', 'stop_loading', 'print', 'confirm', 'verbose', 'warn', 'exception', 'error', + 'critical', 'print_list', 'read']: + self._stdio_func[func] = getattr(self.stdio, func, _print) + + def set_context(self, handler_name, namespace, config): + self.context = HandlerContext( + handler_name=handler_name, + namespace=namespace, + cluster_config=config.get_ob_cluster_config, + obproxy_config=config.get_obproxy_config, + ocp_config=config.get_ocp_config, + cmd=self.cmds, + options=self.options, + stdio=self.stdio, + inner_config=self.inner_config_manager.config + ) + telemetry.set_cluster_conn(config.get_ob_cluster_config) + + def set_offline_context(self, handler_name, namespace): + self.context = HandlerContext( + handler_name=handler_name, + namespace=namespace, + cmd=self.cmds, + options=self.options, + stdio=self.stdio, + inner_config=self.inner_config_manager.config + ) + + def get_namespace(self, spacename): + if spacename in self.namespaces: + namespace = self.namespaces[spacename] + else: + namespace = HandlerContextNamespace(spacename=spacename) + self.namespaces[spacename] = namespace + return namespace + + def call_plugin(self, plugin, spacename=None, target_servers=None, **kwargs): + args = { + 'namespace': spacename, + 'namespaces': self.namespaces, + 'cluster_config': None, + 'obproxy_config': None, + 'ocp_config': None, + 'cmd': self.cmds, + 'options': self.options, + 'stdio': self.stdio, + 'target_servers': target_servers + } + args.update(kwargs) + self._call_stdio('verbose', 'Call %s ' % (plugin)) + return plugin(**args) + + def _call_stdio(self, func, msg, *arg, **kwarg): + if func not in self._stdio_func: + return None + return self._stdio_func[func](msg, *arg, **kwarg) + + def ssh_clients_connect(self, servers, ssh_clients, user_config, fail_exit=False): + self._call_stdio('start_loading', 'Open ssh connection') + connect_io = self.stdio if fail_exit else self.stdio.sub_io() + connect_status = {} + success = True + for server in servers: + if server not in ssh_clients: + client = SshClient( + SshConfig( + server.ip, + user_config.username, + user_config.password, + user_config.key_file, + user_config.port, + user_config.timeout + ), + self.stdio + ) + error = client.connect(stdio=connect_io) + connect_status[server] = status = CheckStatus() + if error is not True: + success = False + status.status = CheckStatus.FAIL + status.error = error + status.suggests.append(SUG_SSH_FAILED.format()) + else: + status.status = CheckStatus.PASS + ssh_clients[server] = client + self._call_stdio('stop_loading', 'succeed' if success else 'fail') + return connect_status + + def gather_function(self, function_type, opt): + config = self.config_manager + if not config: + self._call_stdio('error', 'No such custum config') + return False + else: + self.stdio.print("{0} start ...".format(function_type)) + self.set_context(function_type, 'gather', config) + timestamp = TimeUtils.get_current_us_timestamp() + self.context.set_variable('gather_timestamp', timestamp) + if function_type == 'gather_log': + handler = GatherLogHandler(self.context) + return handler.handle() + elif function_type == 'gather_awr': + handler = GatherAwrHandler(self.context) + return handler.handle() + elif function_type == 'gather_clog': + self.context.set_variable('gather_obadmin_mode', 'clog') + handler = GatherObAdminHandler(self.context) + return handler.handle() + elif function_type == 'gather_slog': + self.context.set_variable('gather_obadmin_mode', 'slog') + handler = GatherObAdminHandler(self.context) + return handler.handle() + elif function_type == 'gather_obproxy_log': + handler = GatherObProxyLogHandler(self.context) + return handler.handle() + elif function_type == 'gather_obstack': + handler = GatherObstack2Handler(self.context) + return handler.handle() + elif function_type == 'gather_perf': + handler = GatherPerfHandler(self.context) + return handler.handle() + elif function_type == 'gather_plan_monitor': + handler = GatherPlanMonitorHandler(self.context) + return handler.handle() + elif function_type == 'gather_all': + handler_sysstat = GatherOsInfoHandler(self.context) + handler_sysstat.handle() + handler_stack = GatherObstack2Handler(self.context) + handler_stack.handle() + handler_perf = GatherPerfHandler(self.context) + handler_perf.handle() + handler_log = GatherLogHandler(self.context) + handler_log.handle() + handler_obproxy = GatherObProxyLogHandler(self.context) + handler_obproxy.handle() + return True + elif function_type == 'gather_sysstat': + handler = GatherOsInfoHandler(self.context) + return handler.handle() + elif function_type == 'gather_scenes_run': + handler = GatherSceneHandler(self.context) + return handler.handle() + else: + self._call_stdio('error', 'Not support gather function: {0}'.format(function_type)) + return False + + def gather_scenes_list(self, opt): + self.set_offline_context('gather_scenes_list', 'gather') + handler = GatherScenesListHandler(self.context) + return handler.handle() + + def analyze_fuction(self, function_type, opt): + config = self.config_manager + if not config: + self._call_stdio('error', 'No such custum config') + return False + else: + self.stdio.print("{0} start ...".format(function_type)) + self.set_context(function_type, 'analyze', config) + if function_type == 'analyze_log': + handler = AnalyzeLogHandler(self.context) + handler.handle() + elif function_type == 'analyze_log_offline': + handler = AnalyzeLogHandler(self.context) + handler.handle() + elif function_type == 'analyze_flt_trace': + handler = AnalyzeFltTraceHandler(self.context) + handler.handle() + else: + self._call_stdio('error', 'Not support analyze function: {0}'.format(function_type)) + return False + + def check(self, opts): + config = self.config_manager + if not config: + self._call_stdio('error', 'No such custum config') + return False + else: + self.stdio.print("check start ...") + self.set_context('check', 'check', config) + obproxy_check_handler = None + observer_check_handler = None + if self.context.obproxy_config.get("servers") is not None and len(self.context.obproxy_config.get("servers"))>0: + obproxy_check_handler = CheckHandler(self.context,check_target_type="obproxy") + obproxy_check_handler.handle() + obproxy_check_handler.execute() + if self.context.cluster_config.get("servers") is not None and len(self.context.cluster_config.get("servers"))>0: + observer_check_handler = CheckHandler(self.context,check_target_type="observer") + observer_check_handler.handle() + observer_check_handler.execute() + if obproxy_check_handler is not None: + obproxy_report_path = os.path.expanduser(obproxy_check_handler.report.get_report_path()) + if os.path.exists(obproxy_report_path): + self.stdio.print( + "Check obproxy finished. For more details, please run cmd '" + Fore.YELLOW + " cat {0} ".format( + obproxy_check_handler.report.get_report_path()) + Style.RESET_ALL + "'") + if observer_check_handler is not None: + observer_report_path = os.path.expanduser(observer_check_handler.report.get_report_path()) + if os.path.exists(observer_report_path): + self.stdio.print( + "Check observer finished. For more details, please run cmd'" + Fore.YELLOW + " cat {0} ".format( + observer_check_handler.report.get_report_path()) + Style.RESET_ALL + "'") + + def check_list(self, opts): + config = self.config_manager + if not config: + self._call_stdio('error', 'No such custum config') + return False + else: + self.set_context('check_list', 'check_list', config) + handler = CheckListHandler(self.context) + handler.handle() + + def rca_run(self, opts): + config = self.config_manager + if not config: + self._call_stdio('error', 'No such custum config') + return False + else: + self.set_context('rca_run', 'rca_run', config) + try: + handler = RCAHandler(self.context) + handler.handle() + handler.execute() + except Exception as e: + self.stdio.error(e) + + def rca_list(self, opts): + config = self.config_manager + if not config: + self._call_stdio('error', 'No such custum config') + return False + else: + self.set_context('rca_list', 'rca_list', config) + handler = RcaScenesListHandler(context=self.context) + handler.handle() + + def update(self, opts): + config = self.config_manager + if not config: + self._call_stdio('error', 'No such custum config') + return False + else: + self.stdio.print("update start ...") + self.set_context('update', 'update', config) + handler = UpdateHandler(self.context) + handler.execute() + + def config(self, opt): + config = self.config_manager + if not config: + self._call_stdio('error', 'No such custum config') + return False + else: + self.set_context('config', 'config', config) + config_helper = ConfigHelper(context=self.context) + config_helper.build_configuration() diff --git a/dev_init.sh b/dev_init.sh new file mode 100644 index 00000000..b9cb8692 --- /dev/null +++ b/dev_init.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +PROJECT_PATH=$(cd "$(dirname "$0")"; pwd) +WORK_DIR=$(readlink -f "$(dirname ${BASH_SOURCE[0]})") + +get_python_version() { + python3 -c "import sys; print(sys.version_info[0], sys.version_info[1])" +} + +check_python_version() { + local version_output=$(python3 -c "import sys; print(sys.version_info.major, sys.version_info.minor)") + IFS=' ' read -ra version <<< "$version_output" + + major=${version[0]} + minor=${version[1]} + + if (( major < 3 || (major == 3 && minor < 8) )); then + echo "Your Python3 version is less than 3.8. Please updating Python3..." + exit 1 + fi +} + +install_requirements() { + REQ_FILE="${PROJECT_PATH}/requirements3.txt" + if [[ -f "$REQ_FILE" ]]; then + echo "Installing packages listed in $REQ_FILE..." + pip3 install -r "$REQ_FILE" + else + echo "No requirements3.txt file found at the expected location." + fi +} + +copy_file(){ + if [ ${OBDIAG_HOME} ]; then + OBDIAG_HOME=${OBDIAG_HOME} + else + OBDIAG_HOME="${HOME}/.obdiag" + fi + + mkdir -p ${OBDIAG_HOME} && cd ${OBDIAG_HOME} + mkdir -p ${OBDIAG_HOME}/check + mkdir -p ${OBDIAG_HOME}/gather + if [ -d "${WORK_DIR}/handler/checker/tasks" ]; then + cp -rf ${WORK_DIR}/handler/checker/tasks ${OBDIAG_HOME}/check/ + fi + if [ -d "${WORK_DIR}/handler/gather/tasks" ]; then + cp -rf ${WORK_DIR}/handler/gather/tasks ${OBDIAG_HOME}/gather/ + fi + + if [ -d "${WORK_DIR}/example" ]; then + cp -rf ${WORK_DIR}/example ${OBDIAG_HOME}/ + fi + + if [ -d "${WORK_DIR}/rca" ]; then + cp -rf ${WORK_DIR}/rca ${OBDIAG_HOME}/ + fi + +} +copy_file +echo "File initialization completed" + +check_python_version + +source ${WORK_DIR}/init_obdiag_cmd.sh + +echo "Creating or updating alias 'obdiag' to run 'python3 ${PROJECT_PATH}/main.py'" +echo "alias obdiag='python3 ${PROJECT_PATH}/main.py'" >> ~/.bashrc +source ~/.bashrc +echo "Initialization completed successfully!" \ No newline at end of file diff --git a/docs/analyze_flt_trace.md b/docs/analyze_flt_trace.md index 5811ccd0..eb6e1a91 100644 --- a/docs/analyze_flt_trace.md +++ b/docs/analyze_flt_trace.md @@ -22,24 +22,23 @@ obdiag config -h192.168.1.1 -uroot@sys -p***** -P2881 ### Step 3: 执行全链路诊断命令 ```shell script -$obdiag analyze flt_trace -h -usage: obdiag analyze flt_trace [-h] [--store_dir store_dir] [-c config] [--files files [files ...]] --flt_trace_id flt_trace_id [--top top] [--recursion recursion] [--output output] +$ obdiag analyze flt_trace -h +Usage: obdiag analyze flt_trace [options] -According to the input parameters, analyze observer logs - -optional arguments: - -h, --help show this help message and exit - --store_dir store_dir - the dir to store gather result, current dir by default. - -c config obdiag custom config - --files files [files ...] - specify file - --flt_trace_id flt_trace_id - flt trace id - --top top top leaf span - --recursion recursion +Options: + --flt_trace_id=FLT_TRACE_ID + flt trace id, . format: xxxxxxxx-xxxx-xxxx-xxxx- + xxxxxxxxxxxx + --files=FILES specify files + --top=TOP top leaf span + --recursion=RECURSION Maximum number of recursion - --output output Print the result to the maximum output line on the screen - -Example1: obdiag analyze flt_trace --flt_trace_id + --output=OUTPUT Print the result to the maximum output line on the + screen + --store_dir=STORE_DIR + the dir to store gather result, current dir by + default. + -c C obdiag custom config + -h, --help Show help and exit. + -v, --verbose Activate verbose output. ``` diff --git a/docs/analyze_ob_log.md b/docs/analyze_ob_log.md index f33888d0..93fb33c4 100644 --- a/docs/analyze_ob_log.md +++ b/docs/analyze_ob_log.md @@ -2,39 +2,35 @@ 'obdiag analyze log' can specify a time range to analyze the OceanBase logs on the target host and the log files passing OceanBase for analysis. ``` $ obdiag analyze log -h -usage: obdiag analyze log [-h] [--from datetime datetime] [--to datetime datetime] [--since 'n'] [--store_dir store_dir] - [-c config] [--scope scope] [--log_level log_level] [--files files [files ...]] [--grep grep [grep ...]] +Usage: obdiag analyze log [options] -According to the input parameters, analyze observer logs - -optional arguments: - -h, --help show this help message and exit - --from datetime datetime - specify the start of the time range. format: yyyy-mm-dd hh:mm:ss. - --to datetime datetime - specify the end of the time range. format: yyyy-mm-dd hh:mm:ss. - --since 'n' Specify time range that from 'n' [d]ays, 'n' [h]ours or 'n' [m]inutes. before to now. format: . +Options: + --from=FROM specify the start of the time range. 'format: yyyy-mm- + dd hh:mm:ss' + --to=TO specify the end of the time range. 'format: yyyy-mm-dd + hh:mm:ss' + --since=SINCE Specify time range that from 'n' [d]ays, 'n' [h]ours + or 'n' [m]inutes. before to now. format: . example: 1h. - --store_dir store_dir - the dir to store gather result, current dir by default. - -c config obdiag custom config - --scope scope log type constrains, choices=[observer, election, rootservice, all], default=all - --log_level log_level - log level constrains, choices=[DEBUG, TRACE, INFO, WDIAG, WARN, EDIAG, ERROR], default=WARN - --files files [files ...] - specify file - --grep grep [grep ...] - specify keywords constrain - -Example1: obdiag analyze log --scope observer --from 2022-06-16 18:25:00 --to 2022-06-16 18:30:00 -Example2: obdiag analyze log --scope observer --since 1h --grep STORAGE -Example3: obdiag analyze log --files observer.log.20230831142211247 -Example4: obdiag analyze log --files ./log/ + --scope=SCOPE log type constrains, choices=[observer, election, + rootservice, all] + --grep=GREP specify keywords constrain + --log_level=LOG_LEVEL + oceanbase logs greater than or equal to this level + will be analyze, choices=[DEBUG, TRACE, INFO, WDIAG, + WARN, EDIAG, ERROR] + --files=FILES specify files + --store_dir=STORE_DIR + the dir to store gather result, current dir by + default. + -c C obdiag custom config + -h, --help Show help and exit. + -v, --verbose Activate verbose output. ``` Example: ```shell script -$ obdiag analyze log --scope observer --from 2023-10-08 10:25:00 --to 2023-10-08 11:30:00 +$ obdiag analyze log --scope observer --from "2023-10-08 10:25:00" --to "2023-10-08 11:30:00" ... FileListInfo: diff --git a/docs/check.md b/docs/check.md index 34d5723a..f4c12ead 100644 --- a/docs/check.md +++ b/docs/check.md @@ -39,20 +39,22 @@ obdiag check --cases=ad --obproxy_cases=proxy check功能所关联的配置项在"CHECK"下,基本上的参数均无需变更或更改频率较低 ```yaml script -CHECK: +check: ignore_version: false + work_path: "~/.obdiag/check" report: report_path: "./check_report/" export_type: table package_file: "~/.obdiag/check/check_package.yaml" tasks_base_path: "~/.obdiag/check/tasks/" ``` -ignore_version表示是否需要在执行巡检项时跳过版本匹配 -report下主要是对报告的参数进行配置 -- report_path表示输出报告的路径 -- export_type表示输出报告的类型,目前支持table 、json 、xml后续需要支持的可以提交issue -package_file表示巡检项集合的保存路径 -tasks_base_path表示巡检项所保存的头路径,下面存储了不同check_target的巡检项目文件 +ignore_version: 表示是否需要在执行巡检项时跳过版本匹配 +work_path: 巡检场景的存储目录 +report: 下主要是对报告的参数进行配置 +- report_path: 表示输出报告的路径 +- export_type: 表示输出报告的类型,目前支持table 、json 、xml后续需要支持的可以提交issue +package_file: 表示巡检项集合的保存路径 +tasks_base_path: 表示巡检项所保存的头路径,下面存储了不同check_target的巡检项目文件 diff --git a/docs/gather_admin.md b/docs/gather_admin.md index 2a3cd210..1c639bd6 100644 --- a/docs/gather_admin.md +++ b/docs/gather_admin.md @@ -3,32 +3,30 @@ 通过ob_admin工具能解析clog和slog文件,并对所选时间范围内的clog和slog进行一键收集。 ``` $ obdiag gather clog -h -usage: obdiag gather clog [-h] [--from datetime datetime] [--to datetime datetime] [--since 'n'] [--store_dir store_dir] - [-c config] [--encrypt encrypt] +Usage: obdiag gather clog [options] -According to the input parameters, gather the clog of the specified range (whether it is time range), compress and pack, and transmit to -the specified path of the obdiag machine. - -optional arguments: - -h, --help show this help message and exit - --from datetime datetime - specify the start of the time range. format: yyyy-mm-dd hh:mm:ss. - --to datetime datetime - specify the end of the time range. format: yyyy-mm-dd hh:mm:ss. - --since 'n' Specify time range that from 'n' [d]ays, 'n' [h]ours or 'n' [m]inutes. before to now. format: . +Options: + --from=FROM specify the start of the time range. 'format: yyyy-mm- + dd hh:mm:ss' + --to=TO specify the end of the time range. 'format: yyyy-mm-dd + hh:mm:ss' + --since=SINCE Specify time range that from 'n' [d]ays, 'n' [h]ours + or 'n' [m]inutes. before to now. format: . example: 1h. - --store_dir store_dir - the dir to store gather result, current dir by default. - -c config obdiag custom config - --encrypt encrypt Whether the returned results need to be encrypted, choices=[true, false], default=false - -Example: obdiag gather clog --from 2022-06-16 18:25:00 --to 2022-06-16 18:30:00 + --encrypt=ENCRYPT Whether the returned results need to be encrypted, + choices=[true, false] + --store_dir=STORE_DIR + the dir to store gather result, current dir by + default. + -c C obdiag custom config + -h, --help Show help and exit. + -v, --verbose Activate verbose output. ``` 执行结果: ```shell script -$ obdiag gather clog --from 2023-01-16 18:25:00 --to 2023-01-17 01:30:00 +$ obdiag gather clog --from "2023-01-16 18:25:00" --to "2023-01-17 01:30:00" Gather clog Summary: +----------------+-----------+---------+--------+----------------------------------------------------------------------+ @@ -40,32 +38,30 @@ Gather clog Summary: ``` $ obdiag gather slog -h -usage: obdiag gather slog [-h] [--from datetime datetime] [--to datetime datetime] [--since 'n'] [--store_dir store_dir] - [-c config] [--encrypt encrypt] +Usage: obdiag gather slog [options] -According to the input parameters, gather the slog of the specified range (whether it is time range), compress and pack, and transmit to -the specified path of the obdiag machine. - -optional arguments: - -h, --help show this help message and exit - --from datetime datetime - specify the start of the time range. format: yyyy-mm-dd hh:mm:ss. - --to datetime datetime - specify the end of the time range. format: yyyy-mm-dd hh:mm:ss. - --since 'n' Specify time range that from 'n' [d]ays, 'n' [h]ours or 'n' [m]inutes. before to now. format: . +Options: + --from=FROM specify the start of the time range. 'format: yyyy-mm- + dd hh:mm:ss' + --to=TO specify the end of the time range. 'format: yyyy-mm-dd + hh:mm:ss' + --since=SINCE Specify time range that from 'n' [d]ays, 'n' [h]ours + or 'n' [m]inutes. before to now. format: . example: 1h. - --store_dir store_dir - the dir to store gather result, current dir by default. - -c config obdiag custom config - --encrypt encrypt Whether the returned results need to be encrypted, choices=[true, false], default=false - -Example: obdiag gather slog --from 2022-06-16 18:25:00 --to 2022-06-16 18:30:00 + --encrypt=ENCRYPT Whether the returned results need to be encrypted, + choices=[true, false] + --store_dir=STORE_DIR + the dir to store gather result, current dir by + default. + -c C obdiag custom config + -h, --help Show help and exit. + -v, --verbose Activate verbose output. ``` 执行结果: ```shell script -$ obdiag gather slog --from 2023-01-16 18:25:00 --to 2023-01-17 01:30:00 +$ obdiag gather slog --from "2023-01-16 18:25:00" --to "2023-01-17 01:30:00" Gather slog Summary: +----------------+-----------+---------+--------+----------------------------------------------------------------------+ diff --git a/docs/gather_all.md b/docs/gather_all.md index a32a467d..68f56644 100644 --- a/docs/gather_all.md +++ b/docs/gather_all.md @@ -5,33 +5,33 @@ 例子: ```shell script $ obdiag gather all -h -usage: obdiag gather all [-h] [--from datetime datetime] [--to datetime datetime] [--since 'n'] [--store_dir store_dir] [-c config] [--scope scope] - [--encrypt encrypt] [--grep grep [grep ...]] - -According to the input parameters, gather all reports - -optional arguments: - -h, --help show this help message and exit - --from datetime datetime - specify the start of the time range. format: yyyy-mm-dd hh:mm:ss. - --to datetime datetime - specify the end of the time range. format: yyyy-mm-dd hh:mm:ss. - --since 'n' Specify time range that from 'n' [d]ays, 'n' [h]ours or 'n' [m]inutes. before to now. format: . example: 1h. - --store_dir store_dir - the dir to store gather result, current dir by default. - -c config obdiag custom config - --scope scope log type constrains, choices=[observer, election, rootservice, all], default=all - --encrypt encrypt Whether the returned results need to be encrypted, choices=[true, false], default=false - --grep grep [grep ...] - specify keywords constrain for log - -Example: obdiag gather all --from 2022-06-16 18:25:00 --to 2022-06-16 18:30:00 +Usage: obdiag gather all [options] + +Options: + --from=FROM specify the start of the time range. 'format: yyyy-mm- + dd hh:mm:ss' + --to=TO specify the end of the time range. 'format: yyyy-mm-dd + hh:mm:ss' + --since=SINCE Specify time range that from 'n' [d]ays, 'n' [h]ours + or 'n' [m]inutes. before to now. format: . + example: 1h. + --scope=SCOPE log type constrains, choices=[observer, election, + rootservice, all] + --grep=GREP specify keywords constrain + --encrypt=ENCRYPT Whether the returned results need to be encrypted, + choices=[true, false] + --store_dir=STORE_DIR + the dir to store gather result, current dir by + default. + -c C obdiag custom config + -h, --help Show help and exit. + -v, --verbose Activate verbose output. ``` 例子: ```shell script - $ obdiag gather all --from 2022-07-28 16:25:00 --to 2022-07-28 18:30:00 --cluster_name test --encrypt true + $ obdiag gather all --from "2022-07-28 16:25:00" --to "2022-07-28 18:30:00" --encrypt true ... ZipFileInfo: diff --git a/docs/gather_awr.md b/docs/gather_awr.md index ff5d59f3..7ba0931b 100644 --- a/docs/gather_awr.md +++ b/docs/gather_awr.md @@ -3,27 +3,34 @@ 该命令用户收集性能报告报告 ``` $ obdiag gather awr -h -usage: obdiag gather awr [-h] [--from datetime datetime] [--to datetime datetime] [--since 'n'] [--store_dir store_dir] - [-c config] --cluster_name cluster_name - -According to the input parameters, gather the awr of the specified range (whether it is time range), compress and pack, and transmit to -the specified path of the obdiag machine. - -optional arguments: - -h, --help show this help message and exit - --from datetime datetime - specify the start of the time range. format: yyyy-mm-dd hh:mm:ss. - --to datetime datetime - specify the end of the time range. format: yyyy-mm-dd hh:mm:ss. - --since 'n' Specify time range that from 'n' [d]ays, 'n' [h]ours or 'n' [m]inutes. before to now. format: . - example: 1h. - --store_dir store_dir - the dir to store gather result, current dir by default. - -c config obdiag custom config - --cluster_name cluster_name - cluster name. - -Example: obdiag gather awr --from 2022-06-16 18:25:00 --to 2022-06-16 18:30:00 +Usage: obdiag gather [options] + +Available commands: + +all Gather oceanbase diagnostic info + +clog Gather clog + +log Gather oceanbase logs from oceanbase machines + +obproxy_log Gather obproxy log from obproxy machines + +perf Gather perf + +plan_monitor Gather ParalleSQL information + +scene Gather scene diagnostic info + +slog Gather slog + +stack Gather stack + +sysstat Gather Host information + + +Options: + -h, --help Show help and exit. + -v, --verbose Activate verbose output. ``` diff --git a/docs/gather_ob_log.md b/docs/gather_ob_log.md index 36a04fd1..cd02f445 100644 --- a/docs/gather_ob_log.md +++ b/docs/gather_ob_log.md @@ -2,34 +2,32 @@ 通过 gather log命令,可以指定时间范围的来去搜集目标主机上的OceanBase日志(后续会陆续开放除OceanBase运行日志外其他信息的搜集)。 ``` $ obdiag gather log -h -usage: obdiag gather log [-h] [--from datetime datetime] [--to datetime datetime] [--since 'n'] [--store_dir store_dir] - [-c config] [--scope scope] [--grep grep [grep ...]] [--encrypt encrypt] - -According to the input parameters, gather the logs of the specified range (whether it is time range), compress and pack, and transmit to -the specified path of the obdiag machine. - -optional arguments: - -h, --help show this help message and exit - --from datetime datetime - specify the start of the time range. format: yyyy-mm-dd hh:mm:ss. - --to datetime datetime - specify the end of the time range. format: yyyy-mm-dd hh:mm:ss. - --since 'n' Specify time range that from 'n' [d]ays, 'n' [h]ours or 'n' [m]inutes. before to now. format: . +Usage: obdiag gather log [options] + +Options: + --from=FROM specify the start of the time range. 'format: yyyy-mm- + dd hh:mm:ss' + --to=TO specify the end of the time range. 'format: yyyy-mm-dd + hh:mm:ss' + --since=SINCE Specify time range that from 'n' [d]ays, 'n' [h]ours + or 'n' [m]inutes. before to now. format: . example: 1h. - --store_dir store_dir - the dir to store gather result, current dir by default. - -c config obdiag custom config - --scope scope log type constrains, choices=[observer, election, rootservice, all], default=all - --grep grep [grep ...] - specify keywords constrain - --encrypt encrypt Whether the returned results need to be encrypted, choices=[true, false], default=false - -Example: obdiag gather log --scope observer --from 2022-06-16 18:25:00 --to 2022-06-16 18:30:00 + --scope=SCOPE log type constrains, choices=[observer, election, + rootservice, all] + --grep=GREP specify keywords constrain + --encrypt=ENCRYPT Whether the returned results need to be encrypted, + choices=[true, false] + --store_dir=STORE_DIR + the dir to store gather result, current dir by + default. + -c C obdiag custom config + -h, --help Show help and exit. + -v, --verbose Activate verbose output. ``` 例子: ```shell script -$ obdiag gather log --scope observer --from 2022-06-25 10:25:00 --to 2022-06-25 18:30:00 --grep STORAGE --encrypt true +$ obdiag gather log --scope observer --from "2022-06-25 10:25:00" --to "2022-06-25 18:30:00" --grep STORAGE --encrypt true ... ZipFileInfo: diff --git a/docs/gather_obproxy_log.md b/docs/gather_obproxy_log.md index 8a535837..76ea23ec 100644 --- a/docs/gather_obproxy_log.md +++ b/docs/gather_obproxy_log.md @@ -2,35 +2,32 @@ 通过 gather obproxy_log命令,可以指定时间范围的来去搜集目标主机上的ObProxy日志。 ``` $ obdiag gather obproxy_log -h -usage: obdiag gather obproxy_log [-h] [--from datetime datetime] [--to datetime datetime] [--since 'n'] [--store_dir store_dir] - [-c config] [--scope scope] [--grep grep [grep ...]] [--encrypt encrypt] - -According to the input parameters, gather the logs of the specified range (whether it is time range), compress and pack, and transmit to -the specified path of the obdiag machine. - -optional arguments: - -h, --help show this help message and exit - --from datetime datetime - specify the start of the time range. format: yyyy-mm-dd hh:mm:ss. - --to datetime datetime - specify the end of the time range. format: yyyy-mm-dd hh:mm:ss. - --since 'n' Specify time range that from 'n' [d]ays, 'n' [h]ours or 'n' [m]inutes. before to now. format: . +Usage: obdiag gather obproxy_log [options] + +Options: + --from=FROM specify the start of the time range. 'format: yyyy-mm- + dd hh:mm:ss' + --to=TO specify the end of the time range. 'format: yyyy-mm-dd + hh:mm:ss' + --since=SINCE Specify time range that from 'n' [d]ays, 'n' [h]ours + or 'n' [m]inutes. before to now. format: . example: 1h. - --store_dir store_dir - the dir to store gather result, current dir by default. - -c config obdiag custom config - --scope scope log type constrains, choices=[obproxy, obproxy_digest, obproxy_stat, obproxy_slow, obproxy_limit, all], - default=all - --grep grep [grep ...] - specify keywords constrain - --encrypt encrypt Whether the returned results need to be encrypted, choices=[true, false], default=false - -Example: obdiag gather obproxy_log --scope obproxy --from 2022-06-16 18:25:00 --to 2022-06-16 18:30:00 + --scope=SCOPE log type constrains, choices=[observer, election, + rootservice, all] + --grep=GREP specify keywords constrain + --encrypt=ENCRYPT Whether the returned results need to be encrypted, + choices=[true, false] + --store_dir=STORE_DIR + the dir to store gather result, current dir by + default. + -c C obdiag custom config + -h, --help Show help and exit. + -v, --verbose Activate verbose output. ``` 例子: ```shell script -$ obdiag gather obproxy_log --scope obproxy --from 2022-06-25 10:25:00 --to 2022-06-25 18:30:00 --encrypt true +$ obdiag gather obproxy_log --scope obproxy --from "2022-06-25 10:25:00" --to "2022-06-25 18:30:00" --encrypt true ... ZipFileInfo: diff --git a/docs/gather_sql_plan_monitor.md b/docs/gather_sql_plan_monitor.md index ef68232d..1bed755a 100644 --- a/docs/gather_sql_plan_monitor.md +++ b/docs/gather_sql_plan_monitor.md @@ -1,18 +1,16 @@ ## gather plan_monitor命令 ```shell script $ obdiag gather plan_monitor -h -usage: obdiag gather plan_monitor [-h] [--store_dir store_dir] [-c config] --trace_id trace_id +Usage: obdiag gather plan_monitor [options] -According to the input parameters, gather the sql plan monitor of the specified trace_id compress and pack, and transmit to the specified -path of the obdiag machine. - -optional arguments: - -h, --help show this help message and exit - --store_dir store_dir - the dir to store gather result, current dir by default. - -c config obdiag custom config - --trace_id trace_id sql trace id - -Example: obdiag gather plan_monitor --trace_id xxxxx +Options: + --trace_id=TRACE_ID sql trace id + --store_dir=STORE_DIR + the dir to store gather result, current dir by + default. + --env=ENV env, eg: "{env1=xxx, env2=xxx}" + -c C obdiag custom config + -h, --help Show help and exit. + -v, --verbose Activate verbose output. ``` diff --git a/docs/gather_sysstat.md b/docs/gather_sysstat.md index 1b3fe2d6..9a831975 100644 --- a/docs/gather_sysstat.md +++ b/docs/gather_sysstat.md @@ -3,17 +3,15 @@ 收集主机dmesg信息、主机cpu\内存信息 ``` $ obdiag gather sysstat -h -usage: obdiag gather sysstat [-h] [--store_dir store_dir] [-c config] +Usage: obdiag gather sysstat [options] -According to the input parameters, gather the os info compress and pack, and transmit to the specified path of the obdiag machine. - -optional arguments: - -h, --help show this help message and exit - --store_dir store_dir - the dir to store gather result, current dir by default. - -c config obdiag custom config - -Example: obdiag gather sysstat +Options: + --store_dir=STORE_DIR + the dir to store gather result, current dir by + default. + -c C obdiag custom config + -h, --help Show help and exit. + -v, --verbose Activate verbose output. ``` 执行结果: diff --git a/err.py b/err.py new file mode 100644 index 00000000..0fc92745 --- /dev/null +++ b/err.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -* +# Copyright (c) 2022 OceanBase +# OceanBase Diagnostic Tool 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. + +""" +@file: err.py +@desc: +""" + +from __future__ import absolute_import, division, print_function + +class OBDIAGErrorCode(object): + + def __init__(self, code, msg): + self.code = code + self.msg = msg + + def __str__(self): + return self.msg + + +class OBDIAGErrorCodeTemplate(object): + + def __init__(self, code, msg): + self.code = code + self.msg = msg + self._str_ = ('OBDIAG-%04d: ' % code) + msg + + def format(self, *args, **kwargs): + return OBDIAGErrorCode( + self.code, + self._str_.format(*args, **kwargs), + ) + + def __str__(self): + return self.msg + + +class FixEval(object): + + DEL = 0 + SET = 1 + + def __init__(self, operation, key, value=None, is_global=False): + self.operation = operation + self.key = key + self.value = value + self.is_global = is_global + +class OBDIAGErrorSuggestion(object): + + def __init__(self, msg, auto_fix=False, fix_eval=[]): + self.msg = msg + self.auto_fix = auto_fix + self.fix_eval = fix_eval + + +class OBDIAGErrorSuggestionTemplate(object): + + def __init__(self, msg, auto_fix=False, fix_eval=[]): + self._msg = msg + self.auto_fix = auto_fix + self.fix_eval = fix_eval if isinstance(fix_eval, list) else [fix_eval] + + def format(self, *args, **kwargs): + return OBDIAGErrorSuggestion( + self._msg.format(*args, **kwargs), + auto_fix=kwargs.get('auto_fix', self.auto_fix), + fix_eval=kwargs.get('fix_eval', self.fix_eval) + ) + +class CheckStatus(object): + + FAIL = "FAIL" + PASS = "PASS" + WAIT = "WAIT" + + def __init__(self, status=WAIT, error=None, suggests=[]): + self.status = status + self.error = error + self.suggests = suggests + +SUG_SSH_FAILED = OBDIAGErrorSuggestionTemplate('Please check user config and network') +EC_SSH_CONNECT = OBDIAGErrorCodeTemplate(1013, '{user}@{ip} connect failed: {message}') +EC_SQL_EXECUTE_FAILED = OBDIAGErrorCodeTemplate(5000, "{sql} execute failed") \ No newline at end of file diff --git a/handler/analyzer/analyze_flt_trace.py b/handler/analyzer/analyze_flt_trace.py index 74efad50..3faf5b4f 100644 --- a/handler/analyzer/analyze_flt_trace.py +++ b/handler/analyzer/analyze_flt_trace.py @@ -18,45 +18,82 @@ import json import os import sys -import threading -import uuid from concurrent.futures import ProcessPoolExecutor, as_completed import traceback - -from common.logger import logger from common.constant import const from common.command import LocalClient, SshClient, delete_file from handler.analyzer.log_parser.tree import Tree -from utils.file_utils import mkdir_if_not_exist, find_all_file from common.command import download_file, mkdir -from utils.shell_utils import SshHelper -from utils.time_utils import str_2_timestamp -from utils.time_utils import timestamp_to_filename_time -from utils.time_utils import get_current_us_timestamp -from utils.utils import get_localhost_inner_ip, display_trace +from common.ssh import SshHelper +from common.tool import TimeUtils +from common.tool import Util +from common.tool import DirectoryUtil +from common.tool import FileUtil class AnalyzeFltTraceHandler(object): - def __init__(self, nodes, gather_pack_dir): + def __init__(self, context, gather_pack_dir=None): + self.context = context + self.stdio = context.stdio self.directly_analyze_files = False self.analyze_files_list = [] self.is_ssh = True self.gather_ob_log_temporary_dir = const.GATHER_LOG_TEMPORARY_DIR_DEFAULT self.gather_pack_dir = gather_pack_dir self.flt_trace_id = '' - self.nodes = nodes + self.nodes = [] self.workers = const.FLT_TRACE_WORKER self.max_recursion = const.FLT_TRACE_TREE_MAX_RECURSION self.config_path = const.DEFAULT_CONFIG_PATH self.top = const.FLT_TRACE_TREE_TOP_LEAF self.output = const.FLT_TRACE_OUTPUT - self.gather_timestamp = get_current_us_timestamp() + self.gather_timestamp = TimeUtils.get_current_us_timestamp() - def handle(self, args): - if not self.__check_valid_and_parse_args(args): - return - local_store_parent_dir = os.path.join(self.gather_pack_dir, "analyze_flt_result_{0}".format(timestamp_to_filename_time(self.gather_timestamp))) - logger.info("Use {0} as pack dir.".format(local_store_parent_dir)) + def init_config(self): + self.nodes = self.context.cluster_config['servers'] + self.inner_config = self.context.inner_config + return True + + def init_option(self): + options = self.context.options + files_option = Util.get_option(options, 'files') + store_dir_option = Util.get_option(options, 'store_dir') + flt_trace_id_option = Util.get_option(options, 'flt_trace_id') + top_option = Util.get_option(options, 'top') + recursion_option = Util.get_option(options, 'recursion') + output_option = Util.get_option(options, 'output') + if store_dir_option is not None: + if not os.path.exists(os.path.abspath(store_dir_option)): + self.stdio.warn('Warning: args --store_dir [{0}] incorrect: No such directory, Now create it'.format(os.path.abspath(store_dir_option))) + os.makedirs(os.path.abspath(store_dir_option)) + self.gather_pack_dir = os.path.abspath(store_dir_option) + if files_option: + self.directly_analyze_files = True + self.analyze_files_list = files_option + self.is_ssh = False + if flt_trace_id_option: + self.flt_trace_id = flt_trace_id_option + else: + self.stdio.error("option --flt_trace_id not found, please provide") + return False + if top_option: + self.top = int(top_option) + if recursion_option: + self.max_recursion = int(recursion_option) + if output_option: + self.output = int(output_option) + return True + + + def handle(self): + if not self.init_option(): + self.stdio.error('init option failed') + return False + if not self.init_config(): + self.stdio.error('init config failed') + return False + local_store_parent_dir = os.path.join(self.gather_pack_dir, "analyze_flt_result_{0}".format(TimeUtils.timestamp_to_filename_time(self.gather_timestamp))) + self.stdio.verbose("Use {0} as pack dir.".format(local_store_parent_dir)) analyze_tuples = [] node_files = [] old_files = [] @@ -85,7 +122,6 @@ def handle_from_node(node): tree.build(data) # output tree self.__output(local_store_parent_dir, tree, self.output) - display_trace(uuid.uuid3(uuid.NAMESPACE_DNS, str(os.getpid()))) return analyze_tuples def __handle_from_node(self, node, old_files, local_store_parent_dir): @@ -94,24 +130,24 @@ def __handle_from_node(self, node, old_files, local_store_parent_dir): "error": "" } remote_ip = node.get("ip") if self.is_ssh else '127.0.0.1' - remote_user = node.get("user") - remote_password = node.get("password") - remote_port = node.get("port") - remote_private_key = node.get("private_key") + remote_user = node.get("ssh_username") + remote_password = node.get("ssh_password") + remote_port = node.get("ssh_port") + remote_private_key = node.get("ssh_key_file") node_files = [] - logger.info("Sending Collect Shell Command to node {0} ...".format(remote_ip)) - mkdir_if_not_exist(local_store_parent_dir) + self.stdio.verbose("Sending Collect Shell Command to node {0} ...".format(remote_ip)) + DirectoryUtil.mkdir(path=local_store_parent_dir, stdio=self.stdio) if "ssh_type" in node and node["ssh_type"] == "docker": local_store_dir = "{0}/docker_{1}".format(local_store_parent_dir, node["container_name"]) else: local_store_dir = "{0}/{1}".format(local_store_parent_dir, remote_ip) - mkdir_if_not_exist(local_store_dir) + DirectoryUtil.mkdir(path=local_store_dir, stdio=self.stdio) ssh_failed = False try: ssh = SshHelper(self.is_ssh, remote_ip, remote_user, remote_password, remote_port, remote_private_key, node) except Exception as e: ssh = None - logger.error("ssh {0}@{1}: failed, Please check the {2}".format( + self.stdio.exception("ssh {0}@{1}: failed, Please check the {2}".format( remote_user, remote_ip, self.config_path)) @@ -121,14 +157,14 @@ def __handle_from_node(self, node, old_files, local_store_parent_dir): if not ssh_failed: gather_dir_name = "trace_merged_cache" gather_dir_full_path = "{0}/{1}".format("/tmp", gather_dir_name) - mkdir(self.is_ssh, ssh, gather_dir_full_path) + mkdir(self.is_ssh, ssh, gather_dir_full_path, self.stdio) if self.is_ssh: self.__get_online_log_file(ssh, node, gather_dir_full_path, local_store_dir) else: self.__get_offline_log_file(ssh, gather_dir_full_path, local_store_dir) - delete_file(self.is_ssh, ssh, os.path.join(gather_dir_full_path, str(node.get("host_type")) + '-' + str(self.flt_trace_id))) + delete_file(self.is_ssh, ssh, os.path.join(gather_dir_full_path, str(node.get("host_type")) + '-' + str(self.flt_trace_id)), self.stdio) ssh.ssh_close() - for file in find_all_file(local_store_dir): + for file in FileUtil.find_all_file(local_store_dir): if self.flt_trace_id in file and (file not in old_files): node_files.append(file) return resp, node_files @@ -161,13 +197,13 @@ def check_filename(filename): gather_path=gather_path, log_name=self.flt_trace_id, log_dir=log_path) - logger.debug("grep files, run cmd = [{0}]".format(grep_cmd)) - SshClient().run(ssh_helper, grep_cmd) + self.stdio.verbose("grep files, run cmd = [{0}]".format(grep_cmd)) + SshClient(self.stdio).run(ssh_helper, grep_cmd) log_full_path = "{gather_path}/{log_name}".format( log_name=self.flt_trace_id, gather_path=gather_path ) - download_file(True, ssh_helper, log_full_path, local_store_path) + download_file(True, ssh_helper, log_full_path, local_store_path, self.stdio) def __get_offline_log_file(self, ssh_helper, log_full_path, local_store_dir): """ @@ -181,8 +217,8 @@ def __get_offline_log_file(self, ssh_helper, log_full_path, local_store_dir): grep_args=self.flt_trace_id, log_file=' '.join(log_name_list), local_store_path=local_store_path) - LocalClient().run(grep_cmd) - download_file(False, ssh_helper, log_full_path, local_store_path) + LocalClient(self.stdio).run(grep_cmd) + download_file(False, ssh_helper, log_full_path, local_store_path, self.stdio) def __get_log_name_list_offline(self): """ @@ -196,10 +232,10 @@ def __get_log_name_list_offline(self): if os.path.isfile(path): log_name_list.append(path) else: - log_names = find_all_file(path) + log_names = FileUtil.find_all_file(path) if len(log_names) > 0: log_name_list.extend(log_names) - logger.info("get log list {}".format(log_name_list)) + self.stdio.verbose("get log list {}".format(log_name_list)) return log_name_list def __parse_log_file(self, node, file, trace): @@ -214,7 +250,7 @@ def __parse_log_file(self, node, file, trace): counter += 1 li.append(parsed) else: - logger.info('file:{} trace:{} total:{}'.format(file, trace, counter)) + self.stdio.verbose('file:{} trace:{} total:{}'.format(file, trace, counter)) break return li @@ -239,7 +275,7 @@ def remap_key(di): try: data = json.loads(content) except Exception: - logger.info(traceback.format_exc()) + self.stdio.verbose(traceback.format_exc()) sys.exit() if not isinstance(data, list): raise ValueError('json file is not a list') @@ -247,7 +283,7 @@ def remap_key(di): if trace == item['trace_id']: li.append(remap_key(item)) for key in time_keys: - item[key] = str_2_timestamp(item[key]) + item[key] = TimeUtils.str_2_timestamp(item[key]) return li def parse_line(self, node, line, trace): @@ -293,13 +329,13 @@ def __file_mapping(self, args): results = [] for log_dir in args.log_dirs: if not os.path.isdir(log_dir): - logger.info('Dir not exist: {}'.format(log_dir)) + self.stdio.verbose('Dir not exist: {}'.format(log_dir)) continue for file in self.__scan_trace_file(log_dir): results.append((file, trace_id)) for file in args.log_files: if not os.path.isfile(file): - logger.info('File not exist: {}'.format(file)) + self.stdio.verbose('File not exist: {}'.format(file)) continue if (file, trace_id) not in results: results.append((file, trace_id)) @@ -307,7 +343,7 @@ def __file_mapping(self, args): def __output(self, result_dir, tree, output_terminal=60): if not tree.nodes: - logger.warning("The analysis result is empty") + self.stdio.warn("The analysis result is empty") return filename = os.path.join(result_dir, '{}.txt'.format(self.flt_trace_id)) line_counter = 0 @@ -318,44 +354,18 @@ def __output(self, result_dir, tree, output_terminal=60): line_counter += 1 if line_counter < output_terminal: if len(line) > 100: - print(line[:97], '...') + self.stdio.print("{0} {1}".format(line[:97], '...')) else: - print(line) + self.stdio.print(line) elif line_counter == output_terminal: - print('Result too large, wait a moment ...\n') - logger.debug('Result saved: {}'.format(os.path.abspath(filename))) + self.stdio.print('Result too large, wait a moment ...\n') + self.stdio.verbose('Result saved: {}'.format(os.path.abspath(filename))) last_info = "For more details, please run cmd \033[32m' cat {0} '\033[0m\n".format(filename) - print(last_info) + self.stdio.print(last_info) def parse_file(self, file): - logger.info('parse file: {}'.format(file[1])) + self.stdio.verbose('parse file: {}'.format(file[1])) if file[1].endswith('.json'): return self.__parse_json_file(file[0], file[1], self.flt_trace_id) else: - return self.__parse_log_file(file[0], file[1], self.flt_trace_id) - - def __check_valid_and_parse_args(self, args): - """ - chech whether command args are valid. If invalid, stop processing and print the error to the user - :param args: command args - :return: boolean. True if valid, False if invalid. - """ - if getattr(args, "files") is not None: - self.directly_analyze_files = True - self.analyze_files_list = getattr(args, "files") - self.is_ssh = False - # 2: store_dir must exist, else create directory. - if getattr(args, "store_dir") is not None: - if not os.path.exists(os.path.abspath(getattr(args, "store_dir"))): - logger.warn("Error: args --store_dir [{0}] incorrect: No such directory, Now create it".format(os.path.abspath(getattr(args, "store_dir")))) - os.makedirs(os.path.abspath(getattr(args, "store_dir"))) - self.gather_pack_dir = os.path.abspath(getattr(args, "store_dir")) - if getattr(args, "flt_trace_id") is not None: - self.flt_trace_id = getattr(args, "flt_trace_id")[0] - if getattr(args, "top") is not None: - self.top = int(getattr(args, "top")[0]) - if getattr(args, "recursion") is not None: - self.max_recursion = int(getattr(args, "recursion")[0]) - if getattr(args, "output") is not None: - self.output = int(getattr(args, "output")[0]) - return True + return self.__parse_log_file(file[0], file[1], self.flt_trace_id) \ No newline at end of file diff --git a/handler/analyzer/analyze_log.py b/handler/analyzer/analyze_log.py index e0b87a49..517c1814 100644 --- a/handler/analyzer/analyze_log.py +++ b/handler/analyzer/analyze_log.py @@ -18,38 +18,33 @@ import datetime import os import re -import threading -import uuid import tabulate from handler.base_shell_handler import BaseShellHandler -from common.logger import logger from common.obdiag_exception import OBDIAGFormatException -from common.obdiag_exception import OBDIAGInvalidArgs from common.constant import const -from common.command import LocalClient, SshClient, delete_file +from common.command import LocalClient, SshClient from common.ob_log_level import OBLogLevel from handler.meta.ob_error import OB_RET_DICT -from utils.file_utils import mkdir_if_not_exist, parse_size, find_all_file -from utils.file_utils import write_result_append_to_file, show_file_list_tabulate -from common.command import download_file, get_logfile_name_list, mkdir -from utils.shell_utils import SshHelper -from utils.time_utils import parse_time_str -from utils.time_utils import parse_time_length_to_sec -from utils.time_utils import timestamp_to_filename_time -from utils.time_utils import datetime_to_timestamp -from utils.utils import display_trace, get_localhost_inner_ip +from common.command import download_file, get_logfile_name_list, mkdir, delete_file +from common.ssh import SshHelper +from common.tool import Util +from common.tool import DirectoryUtil +from common.tool import FileUtil +from common.tool import TimeUtils class AnalyzeLogHandler(BaseShellHandler): - def __init__(self, nodes, gather_pack_dir, gather_timestamp, common_config): - super(AnalyzeLogHandler, self).__init__(nodes) + def __init__(self, context): + super(AnalyzeLogHandler, self).__init__() + self.context = context + self.stdio = context.stdio self.directly_analyze_files = False self.analyze_files_list = [] self.is_ssh = True - self.gather_timestamp = gather_timestamp + self.gather_timestamp = None self.gather_ob_log_temporary_dir = const.GATHER_LOG_TEMPORARY_DIR_DEFAULT - self.gather_pack_dir = gather_pack_dir + self.gather_pack_dir = None self.ob_log_dir = None self.from_time_str = None self.to_time_str = None @@ -58,20 +53,85 @@ def __init__(self, nodes, gather_pack_dir, gather_timestamp, common_config): self.zip_encrypt = False self.log_level = OBLogLevel.WARN self.config_path = const.DEFAULT_CONFIG_PATH - if common_config is None: + + def init_config(self): + self.nodes = self.context.cluster_config['servers'] + self.inner_config = self.context.inner_config + if self.inner_config is None: self.file_number_limit = 20 - self.file_size_limit = 2 * 1024 * 1024 + self.file_size_limit = 2 * 1024 * 1024 * 1024 + else: + basic_config = self.inner_config['obdiag']['basic'] + self.file_number_limit = int(basic_config["file_number_limit"]) + self.file_size_limit = int(FileUtil.size(basic_config["file_size_limit"])) + self.config_path = basic_config['config_path'] + return True + + def init_option(self): + options = self.context.options + from_option = Util.get_option(options, 'from') + to_option = Util.get_option(options, 'to') + since_option = Util.get_option(options, 'since') + store_dir_option = Util.get_option(options, 'store_dir') + grep_option = Util.get_option(options, 'grep') + scope_option = Util.get_option(options, 'scope') + log_level_option = Util.get_option(options, 'log_level') + files_option = Util.get_option(options, 'files') + if files_option: + self.is_ssh = False + self.directly_analyze_files = True + self.analyze_files_list = files_option + if from_option is not None and to_option is not None: + try: + from_timestamp = TimeUtils.parse_time_str(from_option) + to_timestamp = TimeUtils.parse_time_str(to_option) + self.from_time_str = from_option + self.to_time_str = to_option + except OBDIAGFormatException: + self.stdio.exception('Error: Datetime is invalid. Must be in format yyyy-mm-dd hh:mm:ss. from_datetime={0}, to_datetime={1}'.format(from_option, to_option)) + return False + if to_timestamp <= from_timestamp: + self.stdio.exception('Error: from datetime is larger than to datetime, please check.') + return False + elif (from_option is None or to_option is None) and since_option is not None: + self.stdio.warn('No time option provided, default processing is based on the last 30 minutes') + now_time = datetime.datetime.now() + self.to_time_str = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') + self.from_time_str = (now_time - datetime.timedelta(seconds=TimeUtils.parse_time_length_to_sec(since_option))).strftime('%Y-%m-%d %H:%M:%S') + self.stdio.print('analyze log from_time: {0}, to_time: {1}'.format(self.from_time_str, self.to_time_str)) else: - self.file_number_limit = int(common_config["file_number_limit"]) - self.file_size_limit = int(parse_size(common_config["file_size_limit"])) + self.stdio.warn('No time option provided, default processing is based on the last 30 minutes') + now_time = datetime.datetime.now() + self.to_time_str = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') + if since_option is not None: + self.from_time_str = (now_time - datetime.timedelta(seconds=TimeUtils.parse_time_length_to_sec(since_option))).strftime('%Y-%m-%d %H:%M:%S') + else: + self.from_time_str = (now_time - datetime.timedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') + self.stdio.print('analyze log from_time: {0}, to_time: {1}'.format(self.from_time_str, self.to_time_str)) + if store_dir_option is not None: + if not os.path.exists(os.path.abspath(store_dir_option)): + self.stdio.warn('Error: args --store_dir [{0}] incorrect: No such directory, Now create it'.format(os.path.abspath(store_dir_option))) + os.makedirs(os.path.abspath(store_dir_option)) + self.gather_pack_dir = os.path.abspath(store_dir_option) + if grep_option is not None: + self.grep_args = grep_option + if scope_option: + self.scope = scope_option + if log_level_option: + self.log_level = OBLogLevel().get_log_level(scope_option) + return True - def handle(self, args): - if not self.__check_valid_and_parse_args(args): - return + def handle(self): + if not self.init_option(): + self.stdio.error('init option failed') + return False + if not self.init_config(): + self.stdio.error('init config failed') + return False local_store_parent_dir = os.path.join(self.gather_pack_dir, - "analyze_pack_{0}".format(timestamp_to_filename_time( - self.gather_timestamp))) - logger.info("Use {0} as pack dir.".format(local_store_parent_dir)) + "analyze_pack_{0}".format(TimeUtils.timestamp_to_filename_time( + TimeUtils.get_current_us_timestamp()))) + self.stdio.verbose("Use {0} as pack dir.".format(local_store_parent_dir)) analyze_tuples = [] def handle_from_node(node): @@ -87,19 +147,20 @@ def handle_from_node(node): node["ip"] = local_ip handle_from_node(node) + self.stdio.start_loading('analyze result start') title, field_names, summary_list, summary_details_list = self.__get_overall_summary(analyze_tuples, self.directly_analyze_files) table = tabulate.tabulate(summary_list, headers=field_names, tablefmt="grid", showindex=False) - print(title) - print(table) - write_result_append_to_file(os.path.join(local_store_parent_dir, "result_details.txt"), title + str(table) + "\n\nDetails:\n\n") + self.stdio.stop_loading('analyze result sucess') + self.stdio.print(title) + self.stdio.print(table) + FileUtil.write_append(os.path.join(local_store_parent_dir, "result_details.txt"), title + str(table) + "\n\nDetails:\n\n") for m in range(len(summary_details_list)): for n in range(len(field_names)): extend = "\n\n" if n == len(field_names) -1 else "\n" - write_result_append_to_file(os.path.join(local_store_parent_dir, "result_details.txt"), field_names[n] + ": " + str(summary_details_list[m][n]) + extend) + FileUtil.write_append(os.path.join(local_store_parent_dir, "result_details.txt"), field_names[n] + ": " + str(summary_details_list[m][n]) + extend) last_info = "For more details, please run cmd \033[32m' cat {0} '\033[0m\n".format(os.path.join(local_store_parent_dir, "result_details.txt")) - print(last_info) - display_trace(uuid.uuid3(uuid.NAMESPACE_DNS, str(os.getpid()))) + self.stdio.print(last_info) return analyze_tuples def __handle_from_node(self, node, local_store_parent_dir): @@ -109,24 +170,24 @@ def __handle_from_node(self, node, local_store_parent_dir): } node_results = [] remote_ip = node.get("ip") if self.is_ssh else '127.0.0.1' - remote_user = node.get("user") - remote_password = node.get("password") - remote_port = node.get("port") - remote_private_key = node.get("private_key") + remote_user = node.get("ssh_username") + remote_password = node.get("ssh_password") + remote_port = node.get("ssh_port") + remote_private_key = node.get("ssh_key_file") remote_home_path = node.get("home_path") - logger.info("Sending Collect Shell Command to node {0} ...".format(remote_ip)) - mkdir_if_not_exist(local_store_parent_dir) + self.stdio.verbose("Sending Collect Shell Command to node {0} ...".format(remote_ip)) + DirectoryUtil.mkdir(path=local_store_parent_dir, stdio=self.stdio) if "ssh_type" in node and node["ssh_type"]=="docker": local_store_dir= "{0}/docker_{1}".format(local_store_parent_dir, node["container_name"]) else: local_store_dir = "{0}/{1}".format(local_store_parent_dir, remote_ip.replace(".", "_")) - mkdir_if_not_exist(local_store_dir) + DirectoryUtil.mkdir(path=local_store_dir,stdio=self.stdio) ssh_failed = False ssh = None try: - ssh = SshHelper(self.is_ssh, remote_ip, remote_user, remote_password, remote_port, remote_private_key,node) + ssh = SshHelper(self.is_ssh, remote_ip, remote_user, remote_password, remote_port, remote_private_key,node, self.stdio) except Exception as e: - logger.error("ssh {0}@{1}: failed, Please check the {2}".format( + self.stdio.error("ssh {0}@{1}: failed, Please check the {2}".format( remote_user, remote_ip, self.config_path)) @@ -134,16 +195,16 @@ def __handle_from_node(self, node, local_store_parent_dir): resp["skip"] = True resp["error"] = "Please check the {0}".format(self.config_path) if not ssh_failed: - from_datetime_timestamp = timestamp_to_filename_time(datetime_to_timestamp(self.from_time_str)) - to_datetime_timestamp = timestamp_to_filename_time(datetime_to_timestamp(self.to_time_str)) + from_datetime_timestamp = TimeUtils.timestamp_to_filename_time(TimeUtils.datetime_to_timestamp(self.from_time_str)) + to_datetime_timestamp = TimeUtils.timestamp_to_filename_time(TimeUtils.datetime_to_timestamp(self.to_time_str)) gather_dir_name = "ob_log_{0}_{1}_{2}".format(ssh.host_ip, from_datetime_timestamp, to_datetime_timestamp) gather_dir_full_path = "{0}/{1}".format("/tmp", gather_dir_name) - mkdir(self.is_ssh, ssh, gather_dir_full_path) + mkdir(self.is_ssh, ssh, gather_dir_full_path, self.stdio) log_list, resp = self.__handle_log_list(ssh, node, resp) if resp["skip"]: return resp, node_results - print(show_file_list_tabulate(remote_ip, log_list)) + self.stdio.print(FileUtil.show_file_list_tabulate(remote_ip, log_list, self.stdio)) for log_name in log_list: if self.directly_analyze_files: self.__pharse_offline_log_file(ssh_helper=ssh, log_name=log_name, local_store_dir=local_store_dir) @@ -153,9 +214,11 @@ def __handle_from_node(self, node, local_store_parent_dir): gather_path=gather_dir_full_path, local_store_dir=local_store_dir) analyze_log_full_path = "{0}/{1}".format(local_store_dir, log_name) + self.stdio.start_loading('analyze log start') file_result = self.__parse_log_lines(analyze_log_full_path) + self.stdio.stop_loading('analyze log sucess') node_results.append(file_result) - delete_file(self.is_ssh, ssh, gather_dir_full_path) + delete_file(self.is_ssh, ssh, gather_dir_full_path, self.stdio) ssh.ssh_close() return resp, node_results @@ -165,7 +228,7 @@ def __handle_log_list(self, ssh, node, resp): else: log_list = self.__get_log_name_list(ssh, node) if len(log_list) > self.file_number_limit: - logger.warn("{0} The number of log files is {1}, out of range (0,{2}]".format(node.get("ip"), len(log_list), + self.stdio.warn("{0} The number of log files is {1}, out of range (0,{2}]".format(node.get("ip"), len(log_list), self.file_number_limit)) resp["skip"] = True, resp["error"] = "Too many files {0} > {1}, Please adjust the analyze time range".format(len(log_list), @@ -176,7 +239,7 @@ def __handle_log_list(self, ssh, node, resp): self.file_number_limit) return log_list, resp elif len(log_list) == 0: - logger.warn( + self.stdio.warn( "{0} The number of log files is {1}, No files found, " "Please adjust the query limit".format(node.get("ip"), len(log_list))) resp["skip"] = True, @@ -197,12 +260,12 @@ def __get_log_name_list(self, ssh_helper, node): get_oblog = "ls -1 -F %s/observer.log* %s/rootservice.log* %s/election.log* | awk -F '/' '{print $NF}'" % \ (log_path, log_path, log_path) log_name_list = [] - log_files = SshClient().run(ssh_helper, get_oblog) if self.is_ssh else LocalClient().run(get_oblog) + log_files = SshClient(self.stdio).run(ssh_helper, get_oblog) if self.is_ssh else LocalClient(self.stdio).run(get_oblog) if log_files: log_name_list = get_logfile_name_list(self.is_ssh, ssh_helper, self.from_time_str, self.to_time_str, - log_path, log_files) + log_path, log_files, self.stdio) else: - logger.error("Unable to find the log file. Please provide the correct --ob_install_dir, the default is [/home/admin/oceanbase]") + self.stdio.error("Unable to find the log file. Please provide the correct --ob_install_dir, the default is [/home/admin/oceanbase]") return log_name_list def __get_log_name_list_offline(self): @@ -217,10 +280,10 @@ def __get_log_name_list_offline(self): if os.path.isfile(path): log_name_list.append(path) else: - log_names = find_all_file(path) + log_names = FileUtil.find_all_file(path) if len(log_names) > 0: log_name_list.extend(log_names) - logger.info("get log list {}".format(log_name_list)) + self.stdio.verbose("get log list {}".format(log_name_list)) return log_name_list def __pharse_log_file(self, ssh_helper, node, log_name, gather_path, local_store_dir): @@ -237,13 +300,13 @@ def __pharse_log_file(self, ssh_helper, node, log_name, gather_path, local_store gather_path=gather_path, log_name=log_name, log_dir=log_path) - logger.debug("grep files, run cmd = [{0}]".format(grep_cmd)) - SshClient().run(ssh_helper, grep_cmd) if self.is_ssh else LocalClient().run(grep_cmd) + self.stdio.verbose("grep files, run cmd = [{0}]".format(grep_cmd)) + SshClient(self.stdio).run(ssh_helper, grep_cmd) if self.is_ssh else LocalClient(self.stdio).run(grep_cmd) log_full_path = "{gather_path}/{log_name}".format( log_name=log_name, gather_path=gather_path ) - download_file(self.is_ssh, ssh_helper, log_full_path, local_store_path) + download_file(self.is_ssh, ssh_helper, log_full_path, local_store_path, self.stdio) else: real_time_logs = ["observer.log", "rootservice.log", "election.log", "trace.log", "observer.log.wf", "rootservice.log.wf", "election.log.wf", "trace.log.wf"] if log_name in real_time_logs: @@ -251,13 +314,13 @@ def __pharse_log_file(self, ssh_helper, node, log_name, gather_path, local_store gather_path=gather_path, log_name=log_name, log_dir=log_path) - logger.debug("copy files, run cmd = [{0}]".format(cp_cmd)) - SshClient().run(ssh_helper, cp_cmd) if self.is_ssh else LocalClient().run(cp_cmd) + self.stdio.verbose("copy files, run cmd = [{0}]".format(cp_cmd)) + SshClient(self.stdio).run(ssh_helper, cp_cmd) if self.is_ssh else LocalClient(self.stdio).run(cp_cmd) log_full_path = "{gather_path}/{log_name}".format(log_name=log_name, gather_path=gather_path) - download_file(self.is_ssh, ssh_helper, log_full_path, local_store_path) + download_file(self.is_ssh, ssh_helper, log_full_path, local_store_path, self.stdio) else: log_full_path = "{log_dir}/{log_name}".format(log_name=log_name, log_dir=log_path) - download_file(self.is_ssh, ssh_helper, log_full_path, local_store_path) + download_file(self.is_ssh, ssh_helper, log_full_path, local_store_path, self.stdio) def __pharse_offline_log_file(self, ssh_helper, log_name, local_store_dir): """ @@ -270,10 +333,10 @@ def __pharse_offline_log_file(self, ssh_helper, log_name, local_store_dir): grep_args=self.grep_args, log_name=log_name, local_store_path=local_store_path) - logger.info("grep files, run cmd = [{0}]".format(grep_cmd)) - SshClient().run(ssh_helper, grep_cmd) if self.is_ssh else LocalClient().run(grep_cmd) + self.stdio.verbose("grep files, run cmd = [{0}]".format(grep_cmd)) + SshClient(self.stdio).run(ssh_helper, grep_cmd) if self.is_ssh else LocalClient(self.stdio).run(grep_cmd) else: - download_file(self.is_ssh, ssh_helper, log_name, local_store_path) + download_file(self.is_ssh, ssh_helper, log_name, local_store_path, self.stdio) def __get_observer_ret_code(self, log_line): """ @@ -303,7 +366,7 @@ def __parse_log_lines(self, file_full_path): :return: error_dict """ error_dict = {} - logger.info("start parse log {0}".format(file_full_path)) + self.stdio.verbose("start parse log {0}".format(file_full_path)) with open(file_full_path, 'r', encoding='utf8', errors='ignore') as file: line_num = 0 for line in file: @@ -343,7 +406,7 @@ def __parse_log_lines(self, file_full_path): "last_found_time": last_found_time, "trace_id_list": trace_id_list } - logger.info("complete parse log {0}".format(file_full_path)) + self.stdio.verbose("complete parse log {0}".format(file_full_path)) return error_dict def __get_time_from_ob_log_line(self, log_line): @@ -385,54 +448,6 @@ def __get_log_level(self, log_line): return OBLogLevel().get_log_level(level.rstrip()) return 0 - - def __check_valid_and_parse_args(self, args): - """ - chech whether command args are valid. If invalid, stop processing and print the error to the user - :param args: command args - :return: boolean. True if valid, False if invalid. - """ - if getattr(args, "files") is not None: - self.directly_analyze_files = True - self.analyze_files_list = getattr(args, "files") - self.is_ssh = False - # to timestamp must be larger than from timestamp, and be valid - if getattr(args, "from") is not None and getattr(args, "to") is not None: - try: - from_timestamp = parse_time_str(getattr(args, "from")) - to_timestamp = parse_time_str(getattr(args, "to")) - self.from_time_str = getattr(args, "from") - self.to_time_str = getattr(args, "to") - except OBDIAGFormatException: - logger.error("Error: Datetime is invalid. Must be in format yyyy-mm-dd hh:mm:ss. " \ - "from_datetime={0}, to_datetime={1}".format(getattr(args, "from"), getattr(args, "to"))) - return False - if to_timestamp <= from_timestamp: - logger.error("Error: from datetime is larger than to datetime, please check.") - return False - else: - now_time = datetime.datetime.now() - self.to_time_str = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') - if args.since is not None: - self.from_time_str = (now_time - datetime.timedelta( - seconds=parse_time_length_to_sec(args.since))).strftime('%Y-%m-%d %H:%M:%S') - else: - self.from_time_str = (now_time - datetime.timedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') - # 2: store_dir must exist, else create directory. - if getattr(args, "store_dir") is not None: - if not os.path.exists(os.path.abspath(getattr(args, "store_dir"))): - logger.warn("Error: args --store_dir [{0}] incorrect: No such directory, Now create it".format(os.path.abspath(getattr(args, "store_dir")))) - os.makedirs(os.path.abspath(getattr(args, "store_dir"))) - self.gather_pack_dir = os.path.abspath(getattr(args, "store_dir")) - - if getattr(args, "grep") is not None: - self.grep_args = ' '.join(getattr(args, "grep")) - if getattr(args, "scope") is not None: - self.scope = getattr(args, "scope")[0] - if getattr(args, "log_level") is not None: - self.log_level = OBLogLevel().get_log_level(getattr(args, "log_level")[0]) - return True - @staticmethod def __get_overall_summary(node_summary_tuples, is_files=False): """ diff --git a/handler/analyzer/log_parser/log_entry.py b/handler/analyzer/log_parser/log_entry.py index c1a934d0..405192e0 100644 --- a/handler/analyzer/log_parser/log_entry.py +++ b/handler/analyzer/log_parser/log_entry.py @@ -16,8 +16,7 @@ @desc: """ import time - -from utils.time_utils import datetime_to_timestamp +from common.tool import TimeUtils def find_field_end(data, end_chs=",)}({|][", start=0, end=-1): @@ -129,7 +128,7 @@ def parse_from_data(self, data, time_slice): raise Exception() time_slice[1] += (time.time()-st) st = time.time() - self.timestamp_us = datetime_to_timestamp(in_brac_elems[0]) + self.timestamp_us = TimeUtils.datetime_to_timestamp(in_brac_elems[0]) self.log_level = bare_elems[0] self.component = in_brac_elems[1] location = bare_elems[1].strip() diff --git a/handler/analyzer/log_parser/tree.py b/handler/analyzer/log_parser/tree.py index bf51ac92..89692745 100644 --- a/handler/analyzer/log_parser/tree.py +++ b/handler/analyzer/log_parser/tree.py @@ -17,7 +17,7 @@ """ import heapq from typing import List, Dict, Union -from utils.time_utils import trans_time +from common.tool import TimeUtils from prettytable import PrettyTable @@ -61,7 +61,7 @@ def _get_key_str(self, key): @property def elapsed_time(self): if self.value: - return trans_time(self.value['trace_data']['end_ts'] - self.value['trace_data']['start_ts']) + return TimeUtils.trans_time(self.value['trace_data']['end_ts'] - self.value['trace_data']['start_ts']) return '-' @property @@ -203,7 +203,7 @@ def detail(self, index, node: Node): st = node.value['trace_data'].get('start_ts') if node.value else None et = node.value['trace_data'].get('end_ts') if node.value else None if st and et: - time_str = 'Elapsed: {}'.format(trans_time(et - st)) + time_str = 'Elapsed: {}'.format(TimeUtils.trans_time(et - st)) else: time_str = '' return '{} - {} {} {}' \ diff --git a/handler/base_http_handler.py b/handler/base_http_handler.py deleted file mode 100644 index 5c1e38a9..00000000 --- a/handler/base_http_handler.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2022/6/21 -@file: base_handler.py -@desc: -""" -import requests - -from common.logger import logger - - -# 所有需要使用http的handler继承自该handler -class BaseHttpHandler(object): - def __init__(self, ocp): - self.ocp = ocp - self.ocp_user = ocp["login"]["user"] - self.ocp_password = ocp["login"]["password"] - self.ocp_url = ocp["login"]["url"] - self.auth = (self.ocp_user, self.ocp_password) - - @staticmethod - def http_framework(func, retry=3): - resp = { - "error": False, - } - request_exception_msg = None - http_resp = None - for retry in range(retry, 0, -1): - try: - http_resp = func() - except Exception as e: - logger.warning("Oceanbase Diagnostic Tool Request exception: {0}. retry={1}".format(e, retry)) - request_exception_msg = "{0}".format(e) - continue - if http_resp is None: - request_exception_msg = "resp obj is None" - continue - request_exception_msg = None - if http_resp.status_code != 200: - logger.warning("Oceanbase Diagnostic Tool Request get {0} status code. retry={1}".format( - http_resp.status_code, retry)) - else: - break - # handle error in http request - if request_exception_msg is not None: - resp["error"] = True - resp["error_msg"] = "Oceanbase Diagnostic Tool Request exception: {0}.".format(request_exception_msg) - return resp - if http_resp.status_code != 200: - resp["error"] = True - logger.error("Oceanbase Diagnostic Tool Request get {0} status code.".format(http_resp.status_code)) - resp["error_msg"] = "Oceanbase Diagnostic Tool Request get {0} status code.".format(http_resp.status_code) - return resp - resp_dict = http_resp.json() - resp["raw_resp"] = resp_dict - # handle error - if resp_dict["error_code"] != 0: - resp["error"] = True - resp["error_msg"] = resp_dict["error_msg"] - return resp - - @classmethod - def http_post_json(cls, url, data_dict, param, retry=3, timeout=300): - return cls.http_framework( - lambda: requests.post(url, json=data_dict, param=param, timeout=timeout), retry=retry) - - @classmethod - def http_get(cls, url, data_dict, param, retry=3, timeout=300): - return cls.http_framework( - lambda: requests.get(url, json=data_dict, param=param, timeout=timeout), retry=retry) - - @staticmethod - def download(url, as_file_path, auth, timeout=300): - with open(as_file_path, "wb") as write_fd: - write_fd.write(requests.get(url, auth=auth, timeout=timeout).content) - return as_file_path diff --git a/handler/base_shell_handler.py b/handler/base_shell_handler.py index c2edc403..e8105bea 100644 --- a/handler/base_shell_handler.py +++ b/handler/base_shell_handler.py @@ -21,5 +21,10 @@ class BaseShellHandler(object): - def __init__(self, nodes): - self.nodes = nodes + def __init__(self) -> None: + self._stdio_func = None + + def _call_stdio(self, func, msg, *arg, **kwarg): + if func not in self._stdio_func: + return None + return self._stdio_func[func](msg, *arg, **kwarg) diff --git a/handler/checker/check_handler.py b/handler/checker/check_handler.py index e6ea810f..5d4152b8 100644 --- a/handler/checker/check_handler.py +++ b/handler/checker/check_handler.py @@ -17,51 +17,53 @@ """ import os -import uuid import yaml -from common.logger import logger from handler.checker.check_exception import CheckException from handler.checker.check_report import TaskReport, CheckReport, CheckrReportException from handler.checker.check_task import TaskBase from common.scene import get_version import re -from utils.utils import display_trace, node_cut_passwd_for_log -from utils.yaml_utils import read_yaml_data +from common.tool import Util +from common.tool import YamlUtils +from common.tool import StringUtils class CheckHandler: - def __init__(self, ignore_version, cluster, nodes, export_report_path, export_report_type, - check_target_type="observer", - tasks_base_path="~/.obdiag/check/tasks/", - work_path="~/.obdiag/check"): + def __init__(self, context, check_target_type="observer"): + self.context = context + self.stdio = context.stdio # init input parameters self.report = None self.tasks = None - self.export_report_path = export_report_path - self.export_report_type = export_report_type - self.ignore_version = ignore_version - self.cluster = cluster - self.nodes = nodes - self.tasks_base_path = tasks_base_path + self.work_path = os.path.expanduser(self.context.inner_config["check"]["work_path"] or "~/.obdiag/check") + self.export_report_path=os.path.expanduser(self.context.inner_config["check"]["report"]["report_path"] or "./check_report/") + self.export_report_type = self.context.inner_config["check"]["report"]["export_type"] or "table" + self.ignore_version = self.context.inner_config["check"]["ignore_version"] or False + self.cluster = self.context.cluster_config + if check_target_type=="observer": + self.nodes =self.context.cluster_config.get("servers") + if check_target_type == "obproxy": + self.nodes = self.context.obproxy_config.get("servers") + self.tasks_base_path = os.path.expanduser(self.work_path + "/tasks/") self.check_target_type = check_target_type - logger.debug("CheckHandler input. ignore_version is {0} , cluster is {1} , nodes is {2}, " - "export_report_path is {3}, export_report_type is {4} , check_target_type is {5}, " - " tasks_base_path is {6}.".format(ignore_version, - cluster.get( - "ob_cluster_name") or cluster.get( - "obproxy_cluster_name"), - node_cut_passwd_for_log(nodes), - export_report_path, - export_report_type, - check_target_type, - tasks_base_path)) + self.stdio.verbose("CheckHandler input. ignore_version is {0} , cluster is {1} , nodes is {2}, " + "export_report_path is {3}, export_report_type is {4} , check_target_type is {5}, " + " tasks_base_path is {6}.".format(self.ignore_version, + self.cluster.get( + "ob_cluster_name") or self.cluster.get( + "obproxy_cluster_name"), + StringUtils.node_cut_passwd_for_log(self.nodes), + self.export_report_path, + self.export_report_type, + self.check_target_type, + self.tasks_base_path)) # case_package_file # build case_package_file if check_target_type is not None: - case_package_file = work_path + "/" + check_target_type + "_check_package.yaml" + case_package_file = self.work_path + "/" + check_target_type + "_check_package.yaml" else: raise CheckException("check_target_type is null. Please check the conf") case_package_file = os.path.expanduser(case_package_file) @@ -69,11 +71,11 @@ def __init__(self, ignore_version, cluster, nodes, export_report_path, export_re self.package_file_name = case_package_file else: raise CheckException("case_package_file {0} is not exist".format(case_package_file)) - logger.info("case_package_file is " + self.package_file_name) + self.stdio.verbose("case_package_file is " + self.package_file_name) # checker tasks_base_path # build tasks_base_path if check_target_type is not None: - tasks_base_path = tasks_base_path + "/" + check_target_type + tasks_base_path = self.tasks_base_path + "/" + check_target_type else: raise CheckException("check_target_type is null. Please check the conf") tasks_base_path = os.path.expanduser(tasks_base_path) @@ -81,40 +83,31 @@ def __init__(self, ignore_version, cluster, nodes, export_report_path, export_re self.tasks_base_path = tasks_base_path else: raise CheckException("tasks_base_path {0} is not exist".format(tasks_base_path)) - logger.info("tasks_base_path is " + self.tasks_base_path) + self.stdio.verbose("tasks_base_path is " + self.tasks_base_path) + # input_param + self.options=self.context.options - # checker export_report_path - self.export_report_path = export_report_path - - def handle(self, args): + def handle(self): try: package_name = None - if self.check_target_type == "obproxy" and getattr(args, "obproxy_cases"): - obproxy_cases = getattr(args, "obproxy_cases") - if isinstance(obproxy_cases, list): - package_name = obproxy_cases[0] - else: - package_name = getattr(args, "obproxy_cases") + if self.check_target_type == "obproxy" and Util.get_option(self.options, 'obproxy_cases'): + package_name = Util.get_option(self.options, 'obproxy_cases') - if self.check_target_type == "observer" and getattr(args, "cases"): - package_name = getattr(args, "cases") - if isinstance(package_name, list): - package_name = package_name[0] - else: - package_name = getattr(args, "cases") - if getattr(args, "store_dir"): - self.export_report_path = getattr(args, "store_dir")[0] - logger.info("export_report_path change to " + self.export_report_path) + if self.check_target_type == "observer" and Util.get_option(self.options, 'cases'): + package_name = Util.get_option(self.options, 'cases') + if Util.get_option(self.options, 'store_dir'): + self.export_report_path = Util.get_option(self.options, 'store_dir') + self.stdio.verbose("export_report_path change to " + self.export_report_path) self.export_report_path = os.path.expanduser(self.export_report_path) if not os.path.exists(self.export_report_path): - logger.warning("{0} not exists. mkdir it!".format(self.export_report_path)) + self.stdio.warn("{0} not exists. mkdir it!".format(self.export_report_path)) os.mkdir(self.export_report_path) - logger.info("export_report_path is " + self.export_report_path) + self.stdio.verbose("export_report_path is " + self.export_report_path) - logger.info("package_name is {0}".format(package_name)) # get package's by package_name self.tasks = {} if package_name: + self.stdio.verbose("package_name is {0}".format(package_name)) package_tasks_by_name = self.get_package_tasks(package_name) self.get_all_tasks() end_tasks = {} @@ -126,7 +119,7 @@ def handle(self, args): end_tasks[package_task] = self.tasks[task_name] self.tasks = end_tasks else: - logger.debug("tasks_package is all") + self.stdio.verbose("tasks_package is all") self.get_all_tasks() filter_tasks = self.get_package_tasks("filter") if len(filter_tasks) > 0: @@ -138,9 +131,9 @@ def handle(self, args): if re.match(filter_task.strip(), task_name.strip()) is None: new_tasks[task_name] = task_value self.tasks = new_tasks - logger.info("tasks is {0}".format(self.tasks.keys())) + self.stdio.verbose("tasks is {0}".format(self.tasks.keys())) except Exception as e: - logger.error(e) + self.stdio.error(e) # get all tasks def get_all_tasks(self): @@ -151,7 +144,7 @@ def get_all_tasks(self): if file.endswith('.yaml'): folder_name = os.path.basename(root) task_name = "{}.{}".format(folder_name, file.split('.')[0]) - task_data = read_yaml_data(os.path.join(root, file)) + task_data = YamlUtils.read_yaml_data(os.path.join(root, file)) tasks[task_name] = task_data if len(tasks) == 0: raise Exception("the len of tasks is 0") @@ -168,7 +161,7 @@ def get_package_tasks(self, package_name): return [] else: raise CheckException("no cases name is {0}".format(package_name)) - logger.debug("by cases name: {0} , get cases: {1}".format(package_name, packege_tasks[package_name])) + self.stdio.verbose("by cases name: {0} , get cases: {1}".format(package_name, packege_tasks[package_name])) if packege_tasks[package_name].get("tasks") is None: return [] return packege_tasks[package_name].get("tasks") @@ -176,32 +169,33 @@ def get_package_tasks(self, package_name): # execute task def execute_one(self, task_name): try: - logger.info("execute tasks is {0}".format(task_name)) + self.stdio.verbose("execute tasks is {0}".format(task_name)) # Verify if the version is within a reasonable range - report = TaskReport(task_name) + report = TaskReport(self.context,task_name) if not self.ignore_version: - version = get_version(self.nodes, self.check_target_type) + version = get_version(self.nodes, self.check_target_type, self.stdio) if version: self.cluster["version"] = version - logger.info("cluster.version is {0}".format(self.cluster["version"])) - task = TaskBase(self.tasks[task_name]["task"], self.nodes, self.cluster, report) - logger.info("{0} execute!".format(task_name)) + self.stdio.verbose("cluster.version is {0}".format(self.cluster["version"])) + task = TaskBase(self.context, self.tasks[task_name]["task"], self.nodes, self.cluster, report) + self.stdio.verbose("{0} execute!".format(task_name)) task.execute() - logger.info("execute tasks end : {0}".format(task_name)) + self.stdio.verbose("execute tasks end : {0}".format(task_name)) return report else: - logger.error("can't get version") + self.stdio.error("can't get version") else: - logger.info("ignore version") + self.stdio.verbose("ignore version") except Exception as e: - logger.error("execute_one Exception : {0}".format(e)) + self.stdio.error("execute_one Exception : {0}".format(e)) raise CheckException("execute_one Exception : {0}".format(e)) def execute(self): try: - logger.info("execute_all_tasks. the number of tasks is {0} ,tasks is {1}".format(len(self.tasks.keys()), - self.tasks.keys())) - self.report = CheckReport(export_report_path=self.export_report_path, + self.stdio.verbose( + "execute_all_tasks. the number of tasks is {0} ,tasks is {1}".format(len(self.tasks.keys()), + self.tasks.keys())) + self.report = CheckReport(self.context, export_report_path=self.export_report_path, export_report_type=self.export_report_type, report_target=self.check_target_type) # one of tasks to execute @@ -210,8 +204,6 @@ def execute(self): self.report.add_task_report(t_report) self.report.export_report() except CheckrReportException as e: - logger.error("Report error :{0}".format(e)) + self.stdio.error("Report error :{0}".format(e)) except Exception as e: - logger.error("Internal error :{0}".format(e)) - finally: - display_trace(uuid.uuid3(uuid.NAMESPACE_DNS, str(os.getpid()))) + self.stdio.error("Internal error :{0}".format(e)) \ No newline at end of file diff --git a/handler/checker/check_list.py b/handler/checker/check_list.py index 895d0365..f885fe74 100644 --- a/handler/checker/check_list.py +++ b/handler/checker/check_list.py @@ -19,16 +19,17 @@ import yaml -from common.logger import logger -from utils.print_utils import print_title, print_scene +from common.tool import Util class CheckListHandler: - def __init__(self, work_path): - self.work_path = os.path.expanduser(work_path) + def __init__(self, context): + self.context = context + self.stdio = context.stdio + self.work_path = os.path.expanduser(self.context.inner_config["check"]["work_path"] or "~/.obdiag/check") def handle(self): - logger.debug("list check cases") + self.stdio.verbose("list check cases") entries = os.listdir(self.work_path) files = [f for f in entries if os.path.isfile(os.path.join(self.work_path, f))] for file in files: @@ -36,21 +37,22 @@ def handle(self): cases_map = {"all": {"name": "all", "command": "obdiag check", "info_en": "default check all task without filter", "info_cn": "默认执行除filter组里的所有巡检项"}} - # 获取有哪些文件符合并获取对应的头文件 - # 使用字符串分割方法 + # Obtain which files match and corresponding header files + # Using string segmentation methods parts = file.split('_') if len(parts) < 1: - logger.warning( + self.stdio.warn( "invalid check package name :{0} , Please don't add file, which 'check_package' in the name".format( file)) continue target = parts[0] + file = "{0}/{1}".format(self.work_path, file) package_file_data = None # read yaml file with open(file, 'r') as f: package_file_data = yaml.safe_load(f) if not package_file_data or len(package_file_data) == 0: - logger.warning("No data check package data :{0} ".format(file)) + self.stdio.warn("No data check package data :{0} ".format(file)) continue for package_data in package_file_data: if package_data == "filter": @@ -59,12 +61,12 @@ def handle(self): if target == "observer": package_target = "cases" else: - package_target = "{0}-cases".format(target) + package_target = "{0}_cases".format(target) cases_map[package_data] = {"name": package_data, "command": "obdiag check --{0}={1}".format(package_target, package_data), "info_en": package_file_data[package_data].get("info_en") or "", "info_cn": package_file_data[package_data].get("info_cn") or ""} - print_title("check cases about {0}".format(target)) - print_scene(cases_map) + Util.print_title("check cases about {0}".format(target)) + Util.print_scene(cases_map) diff --git a/handler/checker/check_report.py b/handler/checker/check_report.py index 1f51cf49..c0790061 100644 --- a/handler/checker/check_report.py +++ b/handler/checker/check_report.py @@ -21,7 +21,6 @@ # first level is task_name. If all tasks are passed, it will be marked as 'pass'. If any task is not passed, # it will generate separate reports for each step at the second level. The dimension of the second level is step, # and generally, only the steps with exceptions will be summarized, but it can also handle them as needed. -from common.logger import logger from prettytable import PrettyTable import datetime import os @@ -35,14 +34,17 @@ class CheckReport: - def __init__(self, report_target="observer", export_report_path="./check_report/", export_report_type="table"): + def __init__(self, context, report_target="observer", export_report_path="./check_report/", + export_report_type="table"): + self.context = context + self.stdio = context.stdio self.tasks = [] self.export_report_path = export_report_path try: if not os.path.exists(export_report_path): os.makedirs(export_report_path) except Exception as e: - logger.error("init check_report {0}".format(e)) + self.stdio.error("init check_report {0}".format(e)) raise CheckrReportException("int check_report {0}".format(e)) self.export_report_type = export_report_type @@ -54,13 +56,14 @@ def __init__(self, report_target="observer", export_report_path="./check_report/ report_path = self.export_report_path + file_name self.report_path = report_path - logger.info("export report to {0}".format(report_path)) + self.stdio.verbose("export report to {0}".format(report_path)) def add_task_report(self, task_report): self.tasks.append(task_report) def export_report(self): - logger.info("export report to {0}.{1}, export type is {1}".format(self.report_path, self.export_report_type)) + self.stdio.verbose( + "export report to {0}.{1}, export type is {1}".format(self.report_path, self.export_report_type)) try: if self.export_report_type == "table": self.export_report_table() @@ -74,7 +77,7 @@ def export_report(self): raise CheckrReportException("export_report_type: {0} is not support".format(self.export_report_type)) self.export_report_path = self.export_report_path + "." + self.export_report_type except Exception as e: - logger.error("export_report Exception : {0}".format(e)) + self.stdio.error("export_report Exception : {0}".format(e)) raise CheckrReportException(e) def get_report_path(self): @@ -97,7 +100,7 @@ def export_report_yaml(self): def export_report_json(self): allMap = self.report_tobeMap() - logger.debug("export_report_json allMap: {0}".format(allMap)) + self.stdio.verbose("export_report_json allMap: {0}".format(allMap)) with open(self.report_path + ".json", 'w', encoding="utf8") as f: # for python2 and python3 try: @@ -126,7 +129,8 @@ def report_tobeMap(self): allMap["warning"] = warningMap allMap["all"] = allInfoMap telemetry.push_check_info(self.report_target, - {"fail_cases": list(failMap), "critical_cases": list(criticalMap), "warning_cases": list(warningMap)}) + {"fail_cases": list(failMap), "critical_cases": list(criticalMap), + "warning_cases": list(warningMap)}) return allMap def export_report_table(self): @@ -146,10 +150,10 @@ def export_report_table(self): report_all_tb = PrettyTable(["task", "task_report"]) report_all_tb.align["task_report"] = "l" report_all_tb.title = "all-tasks-report" - logger.debug("export report start") - failMap=[] - criticalMap=[] - warningMap=[] + self.stdio.verbose("export report start") + failMap = [] + criticalMap = [] + warningMap = [] for task in self.tasks: if len(task.all_fail()) != 0: @@ -166,30 +170,33 @@ def export_report_table(self): if len(task.all_fail()) == 0 and len(task.all_critical()) == 0 and len(task.all_warning()) == 0: report_all_tb.add_row([task.name, "all pass"]) telemetry.push_check_info(self.report_target, - {"fail_cases": list(set(failMap)), "critical_cases": list(set(criticalMap)), "warning_cases": list(set(warningMap))}) + {"fail_cases": list(set(failMap)), "critical_cases": list(set(criticalMap)), + "warning_cases": list(set(warningMap))}) fp = open(self.report_path + ".table", 'a+', encoding="utf8") if len(report_fail_tb._rows) != 0: - logger.debug(report_fail_tb) + self.stdio.verbose(report_fail_tb) fp.write(report_fail_tb.get_string() + "\n") if len(report_critical_tb._rows) != 0: - logger.debug(report_critical_tb) + self.stdio.verbose(report_critical_tb) fp.write(report_critical_tb.get_string() + "\n") if len(report_warning_tb._rows) != 0: - logger.debug(report_warning_tb) + self.stdio.verbose(report_warning_tb) fp.write(report_warning_tb.get_string() + "\n") if len(report_all_tb._rows) != 0: - logger.debug(report_all_tb) + self.stdio.verbose(report_all_tb) fp.write(report_all_tb.get_string() + "\n") fp.close() - logger.debug("export report end") + self.stdio.verbose("export report end") except Exception as e: raise CheckrReportException("export report {0}".format(e)) class TaskReport: - def __init__(self, task_name, level="normal"): + def __init__(self, context, task_name, level="normal"): + self.context = context + self.stdio = context.stdio self.steps = [] self.name = task_name self.level = level @@ -204,7 +211,7 @@ def __init__(self, task_name, level="normal"): self.fail = [] def add(self, info, level="normal"): - logger.debug("add task_report {0} ,{1}".format(info, level)) + self.stdio.verbose("add task_report {0} ,{1}".format(info, level)) if level == "normal": self.add_normal(info) elif level == "warning": @@ -214,7 +221,7 @@ def add(self, info, level="normal"): elif level == "fail": self.add_fail(info) else: - logger.warning("report level is not support: " + str(level)) + self.stdio.warn("report level is not support: " + str(level)) self.add_normal(info) def add_normal(self, normal): diff --git a/handler/checker/check_task.py b/handler/checker/check_task.py index 77a873dc..bcd357b4 100644 --- a/handler/checker/check_task.py +++ b/handler/checker/check_task.py @@ -16,16 +16,17 @@ @desc: """ -from common.logger import logger from handler.checker.check_exception import StepResultFailException, \ StepExecuteFailException, StepResultFalseException, TaskException from handler.checker.step.stepbase import StepBase -from utils.utils import node_cut_passwd_for_log +from common.tool import StringUtils from common.scene import filter_by_version class TaskBase(object): - def __init__(self, task, nodes, cluster, report, task_variable_dict=None): + def __init__(self, context, task, nodes, cluster, report, task_variable_dict=None): + self.context = context + self.stdio = context.stdio if task_variable_dict is None: self.task_variable_dict = {} else: @@ -36,44 +37,44 @@ def __init__(self, task, nodes, cluster, report, task_variable_dict=None): self.report = report def execute(self): - logger.info("task_base execute") - steps_nu = filter_by_version(self.task, self.cluster) + self.stdio.verbose("task_base execute") + steps_nu = filter_by_version(self.task, self.cluster, self.stdio) if steps_nu < 0: - logger.warning("Unadapted by version. SKIP") + self.stdio.warn("Unadapted by version. SKIP") self.report.add("Unadapted by version. SKIP", "warning") return "Unadapted by version.SKIP" - logger.info("filter_by_version is return {0}".format(steps_nu)) - if len(self.nodes)==0: + self.stdio.verbose("filter_by_version is return {0}".format(steps_nu)) + if len(self.nodes) == 0: raise Exception("node is not exist") for node in self.nodes: - logger.info("run task in node: {0}".format(node_cut_passwd_for_log(node))) + self.stdio.verbose("run task in node: {0}".format(StringUtils.node_cut_passwd_for_log(node))) steps = self.task[steps_nu] nu = 1 for step in steps["steps"]: try: - logger.debug("step nu: {0}".format(nu)) - if len(self.cluster)==0: + self.stdio.verbose("step nu: {0}".format(nu)) + if len(self.cluster) == 0: raise Exception("cluster is not exist") - step_run = StepBase(step, node, self.cluster, self.task_variable_dict) - logger.debug("step nu: {0} initted, to execute".format(nu)) + step_run = StepBase(self.context, step, node, self.cluster, self.task_variable_dict) + self.stdio.verbose("step nu: {0} initted, to execute".format(nu)) step_run.execute(self.report) self.task_variable_dict = step_run.update_task_variable_dict() if "report_type" in step["result"] and step["result"]["report_type"] == "execution": - logger.info("report_type stop this step") + self.stdio.verbose("report_type stop this step") return except StepExecuteFailException as e: - logger.error("TaskBase execute CheckStepFailException: {0} . Do Next Task".format(e)) + self.stdio.error("TaskBase execute CheckStepFailException: {0} . Do Next Task".format(e)) return except StepResultFalseException as e: - logger.warning("TaskBase execute StepResultFalseException: {0} .".format(e)) + self.stdio.warn("TaskBase execute StepResultFalseException: {0} .".format(e)) continue except StepResultFailException as e: - logger.error("TaskBase execute StepResultFailException: {0}".format(e)) + self.stdio.warn("TaskBase execute StepResultFailException: {0}".format(e)) return except Exception as e: - logger.error("TaskBase execute Exception: {0}".format(e)) + self.stdio.error("TaskBase execute Exception: {0}".format(e)) raise TaskException("TaskBase execute Exception: {0}".format(e)) - logger.debug("step nu: {0} execute end ".format(nu)) + self.stdio.verbose("step nu: {0} execute end ".format(nu)) nu = nu + 1 - logger.info("task execute end") \ No newline at end of file + self.stdio.verbose("task execute end") diff --git a/handler/checker/result/result.py b/handler/checker/result/result.py index 06cc13f7..9e505699 100644 --- a/handler/checker/result/result.py +++ b/handler/checker/result/result.py @@ -17,7 +17,6 @@ """ from handler.checker.check_exception import ResultFalseException, ResultFailException, VerifyFailException from handler.checker.result.verify import VerifyResult -from common.logger import logger import re @@ -26,7 +25,9 @@ # report_type) class CheckResult: - def __init__(self, step_result_info, variable_dict): + def __init__(self,context, step_result_info, variable_dict): + self.context = context + self.stdio = context.stdio self.step_result_info = step_result_info self.variable_dict = variable_dict self.result = False @@ -36,22 +37,22 @@ def execute(self): self.result = False if "verify_type" in self.step_result_info: verify_type = self.step_result_info["verify_type"] - logger.info("verify_type is {0}".format(verify_type)) + self.stdio.verbose("verify_type is {0}".format(verify_type)) # if verify in step.result[] if "verify" in self.step_result_info: try: - verify = VerifyResult(self.step_result_info["verify"], + verify = VerifyResult(self.context,self.step_result_info["verify"], self.variable_dict, self.step_result_info["set_value"], verify_type) result = verify.execute() - logger.info("verify.execute end. and result is {0}".format(result)) + self.stdio.verbose("verify.execute end. and result is {0}".format(result)) except Exception as e: - logger.error("check_result execute VerifyFailException :{0}".format(e)) + self.stdio.error("check_result execute VerifyFailException :{0}".format(e)) raise ResultFailException(e) if not result: err_msg = self.build_msg() - logger.info( + self.stdio.verbose( "verify.execute end. and result is false return ResultFalseException err_msg:{0}".format(err_msg)) raise ResultFalseException(err_msg) diff --git a/handler/checker/result/verify.py b/handler/checker/result/verify.py index 468e31a7..d1d6531f 100644 --- a/handler/checker/result/verify.py +++ b/handler/checker/result/verify.py @@ -18,28 +18,29 @@ import decimal import re import subprocess32 as subprocess -from common.logger import logger from handler.checker.check_exception import VerifyFalseException, VerifyFailException from handler.meta.check_meta import GlobalCheckMeta -from utils.utils import parse_range_string +from common.tool import StringUtils class VerifyResult(object): # There are three types of validation results: pass; VerifyFailException (if an exception occurs during the # validation process, handle it as fail); VerifyException (verification failed, report needs to be combined with # report_type) - def __init__(self, expr, env_dict, now_step_set_value_name, verify_type="base"): + def __init__(self, context, expr, env_dict, now_step_set_value_name, verify_type="base"): + self.context = context + self.stdio = context.stdio self.expr = expr self.env_dict = env_dict self.verify_type = verify_type self.now_step_set_value_name = now_step_set_value_name def execute(self): - logger.debug("verify_result execute") - logger.info("verify_type input is {0}".format(self.verify_type)) + self.stdio.verbose("verify_result execute") + self.stdio.verbose("verify_type input is {0}".format(self.verify_type)) if self.verify_type is None or self.verify_type == "": self.verify_type = "base" - logger.info("verify_type input is {0}, to set base".format(self.verify_type)) + self.stdio.verbose("verify_type input is {0}, to set base".format(self.verify_type)) if self.verify_type == "base": return self._verify_base() elif self.verify_type == "between": @@ -55,74 +56,87 @@ def execute(self): def _verify_between(self): try: - result = parse_range_string(self.expr, self.env_dict[self.now_step_set_value_name]) + result = StringUtils.parse_range_string(self.expr, self.env_dict[self.now_step_set_value_name]) except Exception as e: - logger.error("parse_range_string error: " + self.expr + "->" + e.__str__()) + self.stdio.error("parse_range_string error: " + self.expr + "->" + e.__str__()) raise VerifyFailException(e) return result def _verify_base(self): check_verify_shell = GlobalCheckMeta().get_value(key="check_verify_shell") try: - logger.debug("the result verify is {0}".format(self.expr)) + self.stdio.verbose("the result verify is {0}".format(self.expr)) real_shell = re.sub(r'\$\{([^}]+)\}', self.expr, check_verify_shell) for env in self.env_dict: - logger.debug("add env: {0} ,the value:{1} , the type:{2}".format(env, self.env_dict[env], + self.stdio.verbose("add env: {0} ,the value:{1} , the type:{2}".format(env, self.env_dict[env], type(self.env_dict[env]))) if isinstance(self.env_dict[env], int): real_shell = env + '=' + str(self.env_dict[env]) + '\n' + real_shell else: real_shell = env + '="' + str(self.env_dict[env]) + '"\n' + real_shell - logger.debug("real_shell: {0}".format(real_shell)) + self.stdio.verbose("real_shell: {0}".format(real_shell)) process = subprocess.Popen(real_shell, shell=True, stdout=subprocess.PIPE) out, err = process.communicate() process.stdout.close() result = out[:-1].decode('utf-8') - logger.info("_verify_base result: {0}".format(result)) + self.stdio.verbose("_verify_base result: {0}".format(result)) return result == "true" except Exception as e: - logger.error("_verify_base error: {0} -> {1}".format(str(self.expr) , e)) + self.stdio.error("_verify_base error: {0} -> {1}".format(str(self.expr), e)) raise VerifyFailException("_verify_base error: " + self.expr + "->" + e.__str__()) def _verify_max(self): try: - if isinstance(self.env_dict[self.now_step_set_value_name],decimal.Decimal): - self.env_dict[self.now_step_set_value_name]=int(self.env_dict[self.now_step_set_value_name]) - if not isinstance(self.env_dict[self.now_step_set_value_name],(int,float,decimal.Decimal)): - raise Exception("{0} is {1} and the type is {2}, not int or float or decimal !".format(self.now_step_set_value_name, self.env_dict[self.now_step_set_value_name],type(self.env_dict[self.now_step_set_value_name]))) + if isinstance(self.env_dict[self.now_step_set_value_name], decimal.Decimal): + self.env_dict[self.now_step_set_value_name] = int(self.env_dict[self.now_step_set_value_name]) + if not isinstance(self.env_dict[self.now_step_set_value_name], (int, float, decimal.Decimal)): + raise Exception( + "{0} is {1} and the type is {2}, not int or float or decimal !".format(self.now_step_set_value_name, + self.env_dict[ + self.now_step_set_value_name], + type(self.env_dict[ + self.now_step_set_value_name]))) range_str = self.expr result = int(self.env_dict[self.now_step_set_value_name]) < int(range_str) return result except Exception as e: - logger.error("_verify_max error: {0} -> {1}".format(str(self.expr) , e)) + self.stdio.error("_verify_max error: {0} -> {1}".format(str(self.expr), e)) raise VerifyFalseException(e) def _verify_min(self): try: - if isinstance(self.env_dict[self.now_step_set_value_name],decimal.Decimal): - self.env_dict[self.now_step_set_value_name]=int(self.env_dict[self.now_step_set_value_name]) - if not isinstance(self.env_dict[self.now_step_set_value_name],(int,float,decimal.Decimal)): - raise Exception("{0} is {1} and the type is {2}, not int or float ordecimal !".format(self.now_step_set_value_name, self.env_dict[self.now_step_set_value_name],type(self.env_dict[self.now_step_set_value_name]))) + if isinstance(self.env_dict[self.now_step_set_value_name], decimal.Decimal): + self.env_dict[self.now_step_set_value_name] = int(self.env_dict[self.now_step_set_value_name]) + if not isinstance(self.env_dict[self.now_step_set_value_name], (int, float, decimal.Decimal)): + raise Exception( + "{0} is {1} and the type is {2}, not int or float ordecimal !".format(self.now_step_set_value_name, + self.env_dict[ + self.now_step_set_value_name], + type(self.env_dict[ + self.now_step_set_value_name]))) range_str = self.expr - result=int(self.env_dict[self.now_step_set_value_name]) > int(range_str) + result = int(self.env_dict[self.now_step_set_value_name]) > int(range_str) return result except Exception as e: - logger.error("_verify_min error: {0} -> {1}".format(str(self.expr),e)) + self.stdio.error("_verify_min error: {0} -> {1}".format(str(self.expr), e)) raise VerifyFalseException(e) def _verify_equal(self): try: - if isinstance(self.env_dict[self.now_step_set_value_name],decimal.Decimal): - self.env_dict[self.now_step_set_value_name]=int(self.env_dict[self.now_step_set_value_name]) - if not isinstance(self.env_dict[self.now_step_set_value_name],(int,float,decimal.Decimal)): - raise Exception("{0} is {1} and the type is {2}, not int or float or decimal !".format(self.now_step_set_value_name, self.env_dict[self.now_step_set_value_name],type(self.env_dict[self.now_step_set_value_name]))) + if isinstance(self.env_dict[self.now_step_set_value_name], decimal.Decimal): + self.env_dict[self.now_step_set_value_name] = int(self.env_dict[self.now_step_set_value_name]) + if not isinstance(self.env_dict[self.now_step_set_value_name], (int, float, decimal.Decimal)): + raise Exception( + "{0} is {1} and the type is {2}, not int or float or decimal !".format(self.now_step_set_value_name, + self.env_dict[ + self.now_step_set_value_name], + type(self.env_dict[ + self.now_step_set_value_name]))) result = False range_str = self.expr if int(self.env_dict[self.now_step_set_value_name]) == int(range_str): result = True except Exception as e: - logger.error("_verify_equal error: {0} -> {1}".format(str(self.expr),e)) + self.stdio.error("_verify_equal error: {0} -> {1}".format(str(self.expr), e)) raise VerifyFailException(e) return result - - diff --git a/handler/checker/step/data_size.py b/handler/checker/step/data_size.py new file mode 100644 index 00000000..c93a3e9d --- /dev/null +++ b/handler/checker/step/data_size.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -* +# Copyright (c) 2022 OceanBase +# OceanBase Diagnostic Tool 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. + +""" +@time: 2024/4/2 +@file: data_size.py +@desc: +""" +from common.types import Capacity +from handler.checker.check_exception import StepExecuteFailException +from common.ssh import SshHelper +from handler.checker.check_report import TaskReport +from common.tool import Util + + +class DataSizeHandler: + def __init__(self,context, step, node, task_variable_dict): + self.context = context + self.stdio = context.stdio + self.stdio.verbose("init DataSizeHandler") + self.ssh_helper = None + self.parameters = None + self.step = step + self.node = node + self.task_variable_dict = task_variable_dict + + try: + is_ssh = True + self.ssh_helper = SshHelper(is_ssh, node.get("ip"), + node.get("ssh_username"), + node.get("ssh_password"), + node.get("ssh_port"), + node.get("ssh_key_file"), + node) + except Exception as e: + self.stdio.error( + "GetSystemParameterHandler ssh init fail . Please check the NODES conf Exception : {0} .".format(e)) + raise Exception( + "GetSystemParameterHandler ssh init fail . Please check the NODES conf Exception : {0} .".format(e)) + + # step report + self.parameter = [] + self.report = TaskReport + + def execute(self): + + try: + if "key" not in self.step: + raise StepExecuteFailException("DataSizeHandler execute parameter's 'key' is not set") + self.stdio.verbose("DataSizeHandler execute: {0}".format(self.step["key"])) + s = self.step["key"] + value = self.task_variable_dict[s] + self.task_variable_dict[s]=Capacity(value).btyes() + self.stdio.verbose("DataSizeHandler set {0} = {1}".format(s,self.task_variable_dict[s])) + except Exception as e: + self.stdio.error("DataSizeHandler execute Exception: {0}".format(e).strip()) + raise StepExecuteFailException("DataSizeHandler execute Exception: {0}".format(e).strip()) + + def get_report(self): + return self.report + + def update_step_variable_dict(self): + return self.task_variable_dict diff --git a/handler/checker/step/get_system_parameter.py b/handler/checker/step/get_system_parameter.py index a5474f4d..8ecbc6d4 100644 --- a/handler/checker/step/get_system_parameter.py +++ b/handler/checker/step/get_system_parameter.py @@ -17,15 +17,16 @@ """ from handler.checker.check_exception import StepExecuteFailException -from utils.shell_utils import SshHelper +from common.ssh import SshHelper from handler.checker.check_report import TaskReport -from common.logger import logger -from utils.utils import convert_to_number, get_localhost_inner_ip +from common.tool import Util class GetSystemParameterHandler: - def __init__(self, step, node, task_variable_dict): - logger.debug("init GetSystemParameterHandler") + def __init__(self,context, step, node, task_variable_dict): + self.context = context + self.stdio = context.stdio + self.stdio.verbose("init GetSystemParameterHandler") self.ssh_helper = None self.parameters = None self.step = step @@ -35,13 +36,13 @@ def __init__(self, step, node, task_variable_dict): try: is_ssh = True self.ssh_helper = SshHelper(is_ssh, node.get("ip"), - node.get("user"), - node.get("password"), - node.get("port"), - node.get("private_key"), + node.get("ssh_username"), + node.get("ssh_password"), + node.get("ssh_port"), + node.get("ssh_key_file"), node) except Exception as e: - logger.error( + self.stdio.error( "GetSystemParameterHandler ssh init fail . Please check the NODES conf Exception : {0} .".format(e)) raise Exception( "GetSystemParameterHandler ssh init fail . Please check the NODES conf Exception : {0} .".format(e)) @@ -56,8 +57,8 @@ def get_parameter(self, parameter_name): parameter_value = self.ssh_helper.ssh_exec_cmd("cat /proc/sys/" + parameter_name).strip() self.ssh_helper.ssh_close() except Exception as e: - logger.warning( - "get {0} fail:{1} .please check,the parameter_value will be set -1".format(parameter_name, e)) + self.stdio.warn( + "get {0} fail:{1} .please check, the parameter_value will be set -1".format(parameter_name, e)) parameter_value = str("-1") return parameter_value @@ -66,7 +67,7 @@ def execute(self): try: if "parameter" not in self.step: raise StepExecuteFailException("GetSystemParameterHandler execute parameter is not set") - logger.info("GetSystemParameterHandler execute: {0}".format(self.step["parameter"])) + self.stdio.verbose("GetSystemParameterHandler execute: {0}".format(self.step["parameter"])) s = self.step["parameter"] if '.' in s: last_substring = s.rsplit('.', 1) @@ -75,7 +76,7 @@ def execute(self): s = self.step["parameter"] # SystemParameter exist? if self.ssh_helper.ssh_exec_cmd('find /proc/sys/ -name "{0}"'.format(s)) == "": - logger.warning("{0} is not exist".format(self.step["parameter"])) + self.stdio.warn("{0} is not exist".format(self.step["parameter"])) if "result" in self.step and "set_value" in self.step["result"]: self.task_variable_dict[self.step["result"]["set_value"]] = "" return @@ -84,10 +85,10 @@ def execute(self): if "result" in self.step and "set_value" in self.step["result"]: if len(parameter_value) > 0: parameter_value = parameter_value.strip() - logger.info("GetSystemParameterHandler get value : {0}".format(parameter_value)) - self.task_variable_dict[self.step["result"]["set_value"]] = convert_to_number(parameter_value) + self.stdio.verbose("GetSystemParameterHandler get value : {0}".format(parameter_value)) + self.task_variable_dict[self.step["result"]["set_value"]] = Util.convert_to_number(parameter_value) except Exception as e: - logger.error("get_parameter execute: {0}".format(e).strip()) + self.stdio.error("get_parameter execute: {0}".format(e).strip()) raise StepExecuteFailException("get_parameter execute: {0}".format(e).strip()) def get_report(self): diff --git a/handler/checker/step/sql.py b/handler/checker/step/sql.py index 98b29bfe..7b44d86d 100644 --- a/handler/checker/step/sql.py +++ b/handler/checker/step/sql.py @@ -17,14 +17,16 @@ """ from handler.checker.check_exception import StepExecuteFailException -from common.logger import logger from common.ob_connector import OBConnector -from utils.utils import build_str_on_expr_by_dict, convert_to_number +from common.tool import StringUtils +from common.tool import Util class StepSQLHandler: - def __init__(self, step, ob_cluster, task_variable_dict): + def __init__(self,context, step, ob_cluster, task_variable_dict): try: + self.context = context + self.stdio = context.stdio self.ob_cluster = ob_cluster self.ob_cluster_name = ob_cluster.get("cluster_name") self.tenant_mode = None @@ -34,9 +36,10 @@ def __init__(self, step, ob_cluster, task_variable_dict): port=ob_cluster.get("db_port"), username=ob_cluster.get("tenant_sys").get("user"), password=ob_cluster.get("tenant_sys").get("password"), + stdio=self.stdio, timeout=10000) except Exception as e: - logger.error("StepSQLHandler init fail. Please check the OBCLUSTER conf. OBCLUSTER: {0} Exception : {1} .".format(ob_cluster,e)) + self.stdio.error("StepSQLHandler init fail. Please check the OBCLUSTER conf. OBCLUSTER: {0} Exception : {1} .".format(ob_cluster,e)) raise Exception("StepSQLHandler init fail. Please check the OBCLUSTER conf. OBCLUSTER: {0} Exception : {1} .".format(ob_cluster,e)) self.task_variable_dict = task_variable_dict self.enable_dump_db = False @@ -53,24 +56,24 @@ def execute(self): try: if "sql" not in self.step: raise StepExecuteFailException("StepSQLHandler execute sql is not set") - sql = build_str_on_expr_by_dict(self.step["sql"], self.task_variable_dict) - logger.info("StepSQLHandler execute: {0}".format(sql)) + sql = StringUtils.build_str_on_expr_by_dict(self.step["sql"], self.task_variable_dict) + self.stdio.verbose("StepSQLHandler execute: {0}".format(sql)) data = self.ob_connector.execute_sql(sql) if data is None: - logger.warning("sql result is None: {0}".format(self.step["sql"])) - logger.info("execute_sql result:{0}".format(data)) + self.stdio.warn("sql result is None: {0}".format(self.step["sql"])) + self.stdio.verbose("execute_sql result:{0}".format(data)) if len(data) == 0: - logger.warning("sql result is None: {0}".format(self.step["sql"])) + self.stdio.warn("sql result is None: {0}".format(self.step["sql"])) else: data = data[0][0] if data is None: data = "" - logger.info("sql result:{0}".format(convert_to_number(str(data)))) + self.stdio.verbose("sql result:{0}".format(Util.convert_to_number(str(data)))) if "result" in self.step and "set_value" in self.step["result"]: - logger.info("sql execute update task_variable_dict: {0} = {1}".format(self.step["result"]["set_value"], convert_to_number(data))) - self.task_variable_dict[self.step["result"]["set_value"]] = convert_to_number(data) + self.stdio.verbose("sql execute update task_variable_dict: {0} = {1}".format(self.step["result"]["set_value"], Util.convert_to_number(data))) + self.task_variable_dict[self.step["result"]["set_value"]] = Util.convert_to_number(data) except Exception as e: - logger.error("StepSQLHandler execute Exception: {0}".format(e).strip()) + self.stdio.error("StepSQLHandler execute Exception: {0}".format(e).strip()) raise StepExecuteFailException("StepSQLHandler execute Exception: {0}".format(e).strip()) def update_step_variable_dict(self): diff --git a/handler/checker/step/ssh.py b/handler/checker/step/ssh.py index 0a1a9269..963cd19f 100644 --- a/handler/checker/step/ssh.py +++ b/handler/checker/step/ssh.py @@ -18,13 +18,15 @@ from handler.checker.check_exception import StepExecuteFailException from handler.checker.check_report import TaskReport -from utils.shell_utils import SshHelper -from common.logger import logger -from utils.utils import convert_to_number, build_str_on_expr_by_dict, get_localhost_inner_ip +from common.ssh import SshHelper +from common.tool import StringUtils +from common.tool import Util class SshHandler: - def __init__(self, step, node, task_variable_dict): + def __init__(self,context, step, node, task_variable_dict): + self.context = context + self.stdio = context.stdio self.ssh_report_value = None self.parameters = None self.step = step @@ -32,13 +34,13 @@ def __init__(self, step, node, task_variable_dict): try: is_ssh = True self.ssh_helper = SshHelper(is_ssh, node.get("ip"), - node.get("user"), - node.get("password"), - node.get("port"), - node.get("private_key"), + node.get("ssh_username"), + node.get("ssh_password"), + node.get("ssh_port"), + node.get("ssh_key_file"), node) except Exception as e: - logger.error( + self.stdio.error( "SshHandler init fail. Please check the NODES conf. node: {0}. Exception : {1} .".format(node, e)) raise Exception( "SshHandler init fail. Please check the NODES conf node: {0} Exception : {1} .".format(node, e)) @@ -50,24 +52,24 @@ def execute(self): try: if "ssh" not in self.step: raise StepExecuteFailException("SshHandler execute ssh is not set") - ssh_cmd = build_str_on_expr_by_dict(self.step["ssh"], self.task_variable_dict) - logger.info("step SshHandler execute :{0} ".format(ssh_cmd)) + ssh_cmd = StringUtils.build_str_on_expr_by_dict(self.step["ssh"], self.task_variable_dict) + self.stdio.verbose("step SshHandler execute :{0} ".format(ssh_cmd)) ssh_report_value = self.ssh_helper.ssh_exec_cmd(ssh_cmd) if ssh_report_value is None: ssh_report_value = "" if len(ssh_report_value) > 0: ssh_report_value = ssh_report_value.strip() - logger.info("ssh result:{0}".format(convert_to_number(ssh_report_value))) + self.stdio.verbose("ssh result:{0}".format(Util.convert_to_number(ssh_report_value))) if "result" in self.step and "set_value" in self.step["result"]: - logger.debug("ssh result set {0}".format(self.step["result"]["set_value"], - convert_to_number(ssh_report_value))) - self.task_variable_dict[self.step["result"]["set_value"]] = convert_to_number(ssh_report_value) + self.stdio.verbose("ssh result set {0}".format(self.step["result"]["set_value"], + Util.convert_to_number(ssh_report_value))) + self.task_variable_dict[self.step["result"]["set_value"]] = Util.convert_to_number(ssh_report_value) except Exception as e: - logger.error("ssh execute Exception:{0}".format(e).strip()) + self.stdio.error("ssh execute Exception:{0}".format(e).strip()) raise StepExecuteFailException("ssh execute Exception:{0}".format(e).strip()) finally: self.ssh_helper.ssh_close() - logger.info("step SshHandler ssh_report_value:{0}".format(ssh_report_value)) + self.stdio.verbose("step SshHandler ssh_report_value:{0}".format(ssh_report_value)) def update_step_variable_dict(self): return self.task_variable_dict diff --git a/handler/checker/step/stepbase.py b/handler/checker/step/stepbase.py index fa3eccc3..a3351211 100644 --- a/handler/checker/step/stepbase.py +++ b/handler/checker/step/stepbase.py @@ -18,16 +18,18 @@ from handler.checker.check_exception import StepResultFailException, StepExecuteFailException, \ ResultFalseException, ResultFailException, StepResultFalseException +from handler.checker.step.data_size import DataSizeHandler from handler.checker.step.get_system_parameter import GetSystemParameterHandler from handler.checker.result.result import CheckResult from handler.checker.step.ssh import SshHandler from handler.checker.step.sql import StepSQLHandler -from common.logger import logger import docker class StepBase(object): - def __init__(self, step, node, cluster, task_variable_dict): + def __init__(self, context, step, node, cluster, task_variable_dict): + self.context = context + self.stdio = context.stdio self.step = step self.node = node self.cluster = cluster @@ -43,27 +45,29 @@ def execute(self, report): if "ip" in self.node: self.task_variable_dict["remote_ip"] = self.node["ip"] elif "ssh_type" in self.node and self.node["ssh_type"] == "docker": - logger.debug("execute ssh_type is docker") + self.stdio.verbose("execute ssh_type is docker") self.task_variable_dict["remote_ip"] = \ - docker.from_env().containers.get(self.node["container_name"]).attrs['NetworkSettings']['Networks'][ - 'bridge']["IPAddress"] + docker.from_env().containers.get(self.node["container_name"]).attrs['NetworkSettings']['Networks'][ + 'bridge']["IPAddress"] for key in self.node: - self.task_variable_dict["remote_{0}".format(key)]=self.node[key] + self.task_variable_dict["remote_{0}".format(key)] = self.node[key] if "type" not in self.step: raise StepExecuteFailException("Missing field :type") if self.step["type"] == "get_system_parameter": - handler = GetSystemParameterHandler(self.step, self.node, self.task_variable_dict) + handler = GetSystemParameterHandler(self.context, self.step, self.node, self.task_variable_dict) elif self.step["type"] == "ssh": - handler = SshHandler(self.step, self.node, self.task_variable_dict) + handler = SshHandler(self.context, self.step, self.node, self.task_variable_dict) elif self.step["type"] == "sql": - handler = StepSQLHandler(self.step, self.cluster, self.task_variable_dict) + handler = StepSQLHandler(self.context, self.step, self.cluster, self.task_variable_dict) + elif self.step["type"] == "data_size": + handler = DataSizeHandler(self.context, self.step, self.cluster, self.task_variable_dict) else: raise StepExecuteFailException("the type not support: {0}".format(self.step["type"])) - logger.debug("task execute and result") + self.stdio.verbose("task execute and result") handler.execute() except Exception as e: - logger.error("StepBase handler.execute fail {0}".format(e)) + self.stdio.error("StepBase handler.execute fail {0}".format(e)) if self.step["type"] == "sql": report.add("[cluster:{0}] {1}".format(self.cluster.get("ob_cluster_name") or self.cluster.get( "obproxy_cluster_name") or no_cluster_name_msg, e), "fail") @@ -75,17 +79,17 @@ def execute(self, report): try: self.task_variable_dict = handler.update_step_variable_dict() - logger.debug("self.task_variable_dict: {0}".format(self.task_variable_dict)) + self.stdio.verbose("self.task_variable_dict: {0}".format(self.task_variable_dict)) if self.step["type"] == "get_system_parameter" and "result" in self.step and "set_value" in self.step[ "result"] and self.task_variable_dict[self.step["result"]["set_value"]] == "": return if "result" in self.step: - logger.debug("result execute ") - result = CheckResult(self.step["result"], self.task_variable_dict) + self.stdio.verbose("result execute ") + result = CheckResult(self.context, self.step["result"], self.task_variable_dict) result.execute() if "report_type" in self.step["result"] and self.step["result"]["report_type"] == "execution": - logger.debug("report_type stop this step") + self.stdio.verbose("report_type stop this step") return except ResultFalseException as resultException: @@ -94,12 +98,12 @@ def execute(self, report): # When result.type is execution, if this step is executed successfully, subsequent steps will not be # executed. - logger.warning("step_base ResultFalseException:{0}".format(resultException)) + self.stdio.warn("step_base ResultFalseException:{0}".format(resultException)) level = "critical" - logger.debug("step_base ResultFalseException self.step.result:{0}".format(self.step["result"])) + self.stdio.verbose("step_base ResultFalseException self.step.result:{0}".format(self.step["result"])) if "result" in self.step: if "report_type" in self.step["result"]: - logger.info("report_type use is {0}".format(self.step["result"]["report_type"])) + self.stdio.verbose("report_type use is {0}".format(self.step["result"]["report_type"])) level = self.step["result"]["report_type"] if level == "execution": @@ -117,7 +121,7 @@ def execute(self, report): except ResultFailException as resultFailException: # 验证失败,属于fail类型,一般是verify阶段出现异常,需要马上修正 - logger.error("step_base ResultFailException:{0}".format(resultFailException)) + self.stdio.error("step_base ResultFailException:{0}".format(resultFailException)) if self.step["type"] == "sql": report.add("[cluster:{0}] {1}".format(self.cluster.get("ob_cluster_name") or self.cluster.get( "obproxy_cluster_name") or no_cluster_name_msg, resultFailException), "fail") @@ -128,7 +132,7 @@ def execute(self, report): raise StepResultFailException(resultFailException) except Exception as e: - logger.error("step_base Exception {0}".format(e)) + self.stdio.error("step_base Exception {0}".format(e)) raise StepExecuteFailException(e) def update_task_variable_dict(self): diff --git a/obproxy_check_package.yaml b/handler/checker/tasks/obproxy_check_package.yaml similarity index 100% rename from obproxy_check_package.yaml rename to handler/checker/tasks/obproxy_check_package.yaml diff --git a/handler/checker/tasks/observer/cluster/data_path_settings.yaml b/handler/checker/tasks/observer/cluster/data_path_settings.yaml index a6010200..39dd7e4f 100644 --- a/handler/checker/tasks/observer/cluster/data_path_settings.yaml +++ b/handler/checker/tasks/observer/cluster/data_path_settings.yaml @@ -25,7 +25,7 @@ task: verify: '[ "${data_dir_disk}" != "${log_dir_disk}" ]' err_msg: "ip:#{remote_ip} ,data_dir and log_dir_disk are on the same disk." - type: ssh - ssh: "df -T #{data_dir_path} | awk 'NR==2 && $2==\"xfs\" {used=$3/(1024^4); exit !(used > 16)}' && echo 0 || echo 1" + ssh: "df -T #{data_dir_path} | grep \"/\"|awk '{if ($3 > 17179869184 && $2 != \"xfs\") print \"1\"; else print \"0\"}'" result: set_value: file_system verify_type: equal @@ -39,7 +39,7 @@ task: verify: '[ "${file_system}" == "xfs" ] || [ "${file_system}" == "ext4" ] || [ "${file_system}" == "xfs" ]' err_msg: "ip:#{remote_ip} ,log_dir_path: #{log_dir_path} file_system is not xfs or ext4." - type: ssh - ssh: "df -T #{log_dir_path} | awk 'NR==2 && $2==\"xfs\" {used=$3/(1024^4); exit !(used > 16)}' && echo 0 || echo 1" + ssh: "df -T #{log_dir_path} | grep \"/\"|awk '{if ($3 > 17179869184 && $2 != \"xfs\") print \"1\"; else print \"0\"}'" result: set_value: file_system verify_type: equal diff --git a/handler/checker/tasks/observer/cluster/part_trans_action_max.yaml b/handler/checker/tasks/observer/cluster/part_trans_action_max.yaml index 0aaa1fe6..6264c3fc 100644 --- a/handler/checker/tasks/observer/cluster/part_trans_action_max.yaml +++ b/handler/checker/tasks/observer/cluster/part_trans_action_max.yaml @@ -1,10 +1,19 @@ info: 'Check whether there are more than 200 transaction participants' task: - - steps: + - version: "[3.0.0.0,4.0.0.0)" + steps: - type: sql - sql: 'select count(0) from oceanbase.__all_virtual_trans_stat where part_trans_action > 2 ;' + sql: 'select count(0) from oceanbase.__all_virtual_trans_stat where part_trans_action > 2;' result: set_value: transaction_participants + verify_type: equal + report_type: execution + verify: 0 + err_msg: 'transaction_participants is #{transaction_participants} , need check transaction_participants_max' + - type: sql + sql: 'select count(0) from oceanbase.__all_virtual_trans_stat where part_trans_action > 2 group by trans_id;' + result: + set_value: transaction_participants_max verify_type: max verify: 200 - err_msg: 'part_trans_action_max is #{transaction_participants} , over 200' \ No newline at end of file + err_msg: 'part_trans_action_max is #{transaction_participants_max} , over 200' \ No newline at end of file diff --git a/handler/checker/tasks/observer/system/parameter.yaml b/handler/checker/tasks/observer/system/parameter.yaml index 34323636..6cb80c7e 100644 --- a/handler/checker/tasks/observer/system/parameter.yaml +++ b/handler/checker/tasks/observer/system/parameter.yaml @@ -154,8 +154,8 @@ task: result: set_value: parameter report_type: warning - verify: "[ ${parameter} -eq 1 ]" - err_msg: 'vm.nr_hugepages : #{parameter} , which is not recommended. Set it within the range of 1 ' + verify: "[ ${parameter} -eq 0 ]" + err_msg: 'vm.nr_hugepages : #{parameter} , which is not recommended. Set it within the range of 0 ' - type: get_system_parameter parameter: fs.aio-max-nr result: diff --git a/handler/checker/tasks/observer/system/ulimit_parameter.yaml b/handler/checker/tasks/observer/system/ulimit_parameter.yaml index 2506f0e1..08eb0ea3 100644 --- a/handler/checker/tasks/observer/system/ulimit_parameter.yaml +++ b/handler/checker/tasks/observer/system/ulimit_parameter.yaml @@ -14,8 +14,8 @@ task: result: set_value: parameter report_type: warning - verify: "[ 'unlimited' == ${parameter} ]" - err_msg: 'On ip : #{remote_ip}, ulimit -u is #{parameter} . It is a non recommended value, and the recommended value is unlimited. Please refer to the official website document for the configuration method' + verify: "[ '655360' == ${parameter} ]" + err_msg: 'On ip : #{remote_ip}, ulimit -u is #{parameter} . It is a non recommended value, and the recommended value is 655360. Please refer to the official website document for the configuration method' - type: ssh ssh: ulimit -s result: diff --git a/observer_check_package.yaml b/handler/checker/tasks/observer_check_package.yaml similarity index 98% rename from observer_check_package.yaml rename to handler/checker/tasks/observer_check_package.yaml index 41d5bc06..f0a6b356 100644 --- a/observer_check_package.yaml +++ b/handler/checker/tasks/observer_check_package.yaml @@ -39,4 +39,4 @@ filter: info_en: "Inspection that needs to be ignored" info_cn: "需要忽略的检查" tasks: - - sysbench.* + - sysbench.* \ No newline at end of file diff --git a/handler/gather/gather_awr.py b/handler/gather/gather_awr.py index 92b7edf6..5234a69c 100644 --- a/handler/gather/gather_awr.py +++ b/handler/gather/gather_awr.py @@ -21,46 +21,45 @@ import datetime import tabulate import requests - -from handler.base_http_handler import BaseHttpHandler -from common.obdiag_exception import OBDIAGInvalidArgs, OBDIAGArgsNotFoundException from common.obdiag_exception import OBDIAGFormatException -from common.logger import logger -from utils.file_utils import mkdir_if_not_exist, size_format, write_result_append_to_file -from utils.time_utils import datetime_to_timestamp -from utils.time_utils import trans_datetime_utc_to_local -from utils.time_utils import timestamp_to_filename_time -from utils.time_utils import parse_time_length_to_sec -from utils.time_utils import get_time_rounding -from utils.time_utils import parse_time_str -from ocp import ocp_api -from ocp import ocp_task -from ocp import ocp_cluster -from ocp import ocp_base +from common.tool import DirectoryUtil +from common.tool import FileUtil +from common.tool import Util +from common.tool import TimeUtils +from common.ocp import ocp_task, ocp_api -class GatherAwrHandler(BaseHttpHandler): - def __init__(self, ocp, gather_pack_dir, gather_timestamp): - super(GatherAwrHandler, self).__init__(ocp) - self.ocp = ocp +class GatherAwrHandler(object): + def __init__(self, context, gather_pack_dir='./'): + self.context = context + self.stdio = context.stdio self.gather_pack_dir = gather_pack_dir - self.gather_timestamp = gather_timestamp + if self.context.get_variable("gather_timestamp", None) : + self.gather_timestamp=self.context.get_variable("gather_timestamp") + else: + self.gather_timestamp = TimeUtils.get_current_us_timestamp() - def handle(self, args): - """ - the overall handler for the gather command - :param args: command args - :return: the summary should be displayed - """ - # check args first - if not self.__check_valid_and_parse_args(args): - return + def init_config(self): + ocp = self.context['ocp'] + self.ocp_user = ocp["login"]["user"] + self.ocp_password = ocp["login"]["password"] + self.ocp_url = ocp["login"]["url"] + self.auth = (self.ocp_user, self.ocp_password) + return True + + def handle(self): + if not self.init_option(): + self.stdio.error('init option failed') + return False + if not self.init_config(): + self.stdio.error('init config failed') + return False # example of the format of pack dir for this command: (gather_pack_dir)/gather_pack_20190610123344 pack_dir_this_command = os.path.join(self.gather_pack_dir, - "gather_pack_{0}".format(timestamp_to_filename_time( + "gather_pack_{0}".format(TimeUtils.timestamp_to_filename_time( self.gather_timestamp))) - logger.info("Use {0} as pack dir.".format(pack_dir_this_command)) - mkdir_if_not_exist(pack_dir_this_command) + self.stdio.verbose("Use {0} as pack dir.".format(pack_dir_this_command)) + DirectoryUtil.mkdir(path=pack_dir_this_command, stdio=self.stdio) gather_tuples = [] gather_pack_path_dict = {} @@ -95,9 +94,9 @@ def handle_awr_from_ocp(ocp_url, cluster_name, arg): list(map(lambda x: x.start(), ocp_threads)) list(map(lambda x: x.join(), ocp_threads)) summary_tuples = self.__get_overall_summary(gather_tuples) - print(summary_tuples) + self.stdio.print(summary_tuples) # 将汇总结果持久化记录到文件中 - write_result_append_to_file(os.path.join(pack_dir_this_command, "result_summary.txt"), summary_tuples) + FileUtil.write_append(os.path.join(pack_dir_this_command, "result_summary.txt"), summary_tuples) return gather_tuples, gather_pack_path_dict @@ -113,13 +112,13 @@ def __download_report(self, store_path, name, report_id): "error": False, } - logger.info( + self.stdio.verbose( "Sending Status Request to cluster {0} ...".format(self.cluster_name)) path = ocp_api.cluster + "/%s/performance/workload/reports/%s" % (self.cluster_id, report_id) save_path = os.path.join(store_path, name + ".html") pack_path = self.download(self.ocp_url + path, save_path, self.auth) - logger.info( + self.stdio.verbose( "cluster {0} response. analysing...".format(self.cluster_name)) resp["gather_pack_path"] = pack_path @@ -127,13 +126,13 @@ def __download_report(self, store_path, name, report_id): return resp return resp - def __generate_awr_report(self, args): + def __generate_awr_report(self): """ call OCP API to generate awr report :param args: command args :return: awr report name """ - snapshot_list = self.__get_snapshot_list(args) + snapshot_list = self.__get_snapshot_list() if len(snapshot_list) <= 1: raise Exception("AWR report at least need 2 snapshot, cluster now only have %s", len(snapshot_list)) else: @@ -142,9 +141,9 @@ def __generate_awr_report(self, args): path = ocp_api.cluster + "/%s/performance/workload/reports" % self.cluster_id - start_time = datetime.datetime.strptime(trans_datetime_utc_to_local(start_time.split(".")[0]), + start_time = datetime.datetime.strptime(TimeUtils.trans_datetime_utc_to_local(start_time.split(".")[0]), "%Y-%m-%d %H:%M:%S") - end_time = datetime.datetime.strptime(trans_datetime_utc_to_local(end_time.split(".")[0]), + end_time = datetime.datetime.strptime(TimeUtils.trans_datetime_utc_to_local(end_time.split(".")[0]), "%Y-%m-%d %H:%M:%S") params = { "name": "OBAWR_obcluster_%s_%s_%s" % ( @@ -161,7 +160,7 @@ def __generate_awr_report(self, args): ocp_task.Task.wait_done(task_instance) return response.json()["data"]["name"] - def __get_snapshot_list(self, args): + def __get_snapshot_list(self): """ get snapshot list from ocp :param args: command args @@ -170,23 +169,23 @@ def __get_snapshot_list(self, args): snapshot_id_list = [] path = ocp_api.cluster + "/%s/performance/workload/snapshots" % self.cluster_id response = requests.get(self.ocp_url + path, auth=self.auth) - from_datetime_timestamp = datetime_to_timestamp(self.from_time_str) - to_datetime_timestamp = datetime_to_timestamp(self.to_time_str) + from_datetime_timestamp = TimeUtils.datetime_to_timestamp(self.from_time_str) + to_datetime_timestamp = TimeUtils.datetime_to_timestamp(self.to_time_str) # 如果用户给定的时间间隔不足一个小时,为了能够获取到snapshot,需要将时间进行调整 if from_datetime_timestamp + 3600000000 >= to_datetime_timestamp: # 起始时间取整点 - from_datetime_timestamp = datetime_to_timestamp(get_time_rounding(dt=parse_time_str(self.from_time_str), step=0, rounding_level="hour")) + from_datetime_timestamp = TimeUtils.datetime_to_timestamp(TimeUtils.get_time_rounding(dt=TimeUtils.parse_time_str(self.from_time_str), step=0, rounding_level="hour")) # 结束时间在起始时间的基础上增加一个小时零三分钟(三分钟是给的偏移量,确保能够获取到快照) to_datetime_timestamp = from_datetime_timestamp + 3600000000 + 3*60000000 for info in response.json()["data"]["contents"]: try: - snapshot_time = datetime_to_timestamp( - trans_datetime_utc_to_local(str(info["snapshotTime"]).split(".")[0])) + snapshot_time = TimeUtils.datetime_to_timestamp( + TimeUtils.trans_datetime_utc_to_local(str(info["snapshotTime"]).split(".")[0])) if from_datetime_timestamp <= snapshot_time <= to_datetime_timestamp: snapshot_id_list.append((info["snapshotId"], info["snapshotTime"])) except: - logger.error("get snapshot failed, pass") - logger.info("get snapshot list {0}".format(snapshot_id_list)) + self.stdio.error("get snapshot failed, pass") + self.stdio.verbose("get snapshot list {0}".format(snapshot_id_list)) return snapshot_id_list def __get_awr_report_id(self, report_name): @@ -202,31 +201,32 @@ def __get_awr_report_id(self, report_name): return info["id"] return 0 - def __check_valid_and_parse_args(self, args): - """ - chech whether command args are valid. If invalid, stop processing and print the error to the user - :param args: command args - :return: boolean. True if valid, False if invalid. - """ - if getattr(args, "from") is not None and getattr(args, "to") is not None: + def init_option(self): + options = self.context.options + store_dir_option = Util.get_option(options, 'store_dir') + from_option = Util.get_option(options, 'from') + to_option = Util.get_option(options, 'to') + since_option = Util.get_option(options, 'since') + if from_option is not None and to_option is not None: try: - self.from_time_str = getattr(args, "from") - self.to_time_str = getattr(args, "to") - from_timestamp = datetime_to_timestamp(getattr(args, "from")) - to_timestamp = datetime_to_timestamp(getattr(args, "to")) + self.from_time_str = from_option + self.to_time_str = to_option + from_timestamp = TimeUtils.datetime_to_timestamp(from_option) + to_timestamp = TimeUtils.datetime_to_timestamp(to_option) except OBDIAGFormatException: - logger.error("Error: Datetime is invalid. Must be in format yyyy-mm-dd hh:mm:ss. " \ + self.stdio.error("Error: Datetime is invalid. Must be in format yyyy-mm-dd hh:mm:ss. " \ "from_datetime={0}, to_datetime={1}".format(getattr(args, "from"), args.to)) return False if to_timestamp <= from_timestamp: - logger.error("Error: from datetime is larger than to datetime, please check.") + self.stdio.error("Error: from datetime is larger than to datetime, please check.") return False - elif (getattr(args, "from") is None or getattr(args, "to") is None) and args.since is not None: + elif (from_option is None or to_option is None) and since_option is not None: + self.stdio.warn('No time option provided, default processing is based on the last 30 minutes') # the format of since must be 'n' try: - since_to_seconds = parse_time_length_to_sec(args.since) + since_to_seconds = TimeUtils.parse_time_length_to_sec(since_option) except ValueError: - logger.error("Error: the format of since must be 'n'") + self.stdio.error("Error: the format of since must be 'n'") return False now_time = datetime.datetime.now() self.to_time_str = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') @@ -234,13 +234,13 @@ def __check_valid_and_parse_args(self, args): since_to_seconds = 3600 self.from_time_str = (now_time - datetime.timedelta(seconds=since_to_seconds)).strftime('%Y-%m-%d %H:%M:%S') else: - logger.error("Invalid args, you need input since or from and to datetime, args={0}".format(args)) - # store_dir must exist, else create directory. - if getattr(args, "store_dir") is not None: - if not os.path.exists(os.path.abspath(getattr(args, "store_dir"))): - logger.warn("Error: args --store_dir [{0}] incorrect: No such directory, Now create it".format(os.path.abspath(getattr(args, "store_dir")))) - os.makedirs(os.path.abspath(getattr(args, "store_dir"))) - self.gather_pack_dir = os.path.abspath(getattr(args, "store_dir")) + self.stdio.error("Invalid args, you need input since or from and to datetime") + return False + if store_dir_option and store_dir_option != "./": + if not os.path.exists(os.path.abspath(store_dir_option)): + self.stdio.warn('warn: args --store_dir [{0}] incorrect: No such directory, Now create it'.format(os.path.abspath(store_dir_option))) + os.makedirs(os.path.abspath(store_dir_option)) + self.gather_pack_dir = os.path.abspath(store_dir_option) return True @staticmethod @@ -258,7 +258,7 @@ def __get_overall_summary(node_summary_tuple): file_size = tup[3] consume_time = tup[4] pack_path = tup[5] - format_file_size = size_format(file_size, output_str=True) + format_file_size = FileUtil.size_format(num=file_size, output_str=True) summary_tab.append((cluster, "Error" if is_err else "Completed", format_file_size, "{0} s".format(int(consume_time)), pack_path)) return "\nGather AWR Summary:\n" + \ diff --git a/handler/gather/gather_log.py b/handler/gather/gather_log.py index a2bb0b23..0f8b7ed6 100644 --- a/handler/gather/gather_log.py +++ b/handler/gather/gather_log.py @@ -17,68 +17,137 @@ """ import datetime import os -import threading import time -import uuid - import tabulate - from handler.base_shell_handler import BaseShellHandler -from common.logger import logger from common.obdiag_exception import OBDIAGFormatException -from common.obdiag_exception import OBDIAGInvalidArgs from common.constant import const -from common.command import LocalClient, SshClient -from utils.file_utils import mkdir_if_not_exist, size_format, write_result_append_to_file, parse_size, show_file_size_tabulate from common.command import get_file_size, download_file, is_empty_dir, rm_rf_file, get_logfile_name_list, mkdir, delete_empty_file, zip_encrypt_dir, zip_dir -from utils.shell_utils import SshHelper -from utils.password_util import gen_password -from utils.time_utils import parse_time_str -from utils.time_utils import parse_time_length_to_sec -from utils.time_utils import timestamp_to_filename_time -from utils.time_utils import datetime_to_timestamp -from utils.utils import get_localhost_inner_ip, display_trace +from common.ssh import SshHelper +from common.command import SshClient, LocalClient +from common.tool import TimeUtils +from common.tool import Util +from common.tool import DirectoryUtil +from common.tool import FileUtil +from common.tool import NetUtils class GatherLogHandler(BaseShellHandler): - def __init__(self, nodes, gather_pack_dir, gather_timestamp=None, common_config=None, is_scene=False): - super(GatherLogHandler, self).__init__(nodes) + def __init__(self, context, gather_pack_dir='./', is_scene=False): + super(GatherLogHandler, self).__init__() + self.context = context + self.stdio = context.stdio self.is_ssh = True - self.gather_timestamp = gather_timestamp self.gather_ob_log_temporary_dir = const.GATHER_LOG_TEMPORARY_DIR_DEFAULT self.gather_pack_dir = gather_pack_dir self.ob_log_dir = None self.from_time_str = None self.to_time_str = None - self.grep_args = None + self.grep_options = None self.scope = None self.zip_encrypt = False self.is_scene = is_scene self.config_path = const.DEFAULT_CONFIG_PATH - if common_config is None: + if self.context.get_variable("gather_timestamp", None) : + self.gather_timestamp=self.context.get_variable("gather_timestamp") + else: + self.gather_timestamp = TimeUtils.get_current_us_timestamp() + + def init_config(self): + self.nodes = self.context.cluster_config['servers'] + new_nodes = Util.get_nodes_list(self.context, self.nodes, self.stdio) + if new_nodes: + self.nodes = new_nodes + self.inner_config = self.context.inner_config + if self.inner_config is None: self.file_number_limit = 20 self.file_size_limit = 2 * 1024 * 1024 * 1024 else: - self.file_number_limit = int(common_config["file_number_limit"]) - self.file_size_limit = int(parse_size(common_config["file_size_limit"])) + basic_config = self.inner_config['obdiag']['basic'] + self.file_number_limit = int(basic_config["file_number_limit"]) + self.file_size_limit = int(FileUtil.size(basic_config["file_size_limit"])) + self.config_path = basic_config['config_path'] + return True + + def init_option(self): + options = self.context.options + from_option = Util.get_option(options, 'from') + to_option = Util.get_option(options, 'to') + since_option = Util.get_option(options, 'since') + store_dir_option = Util.get_option(options, 'store_dir') + grep_option = Util.get_option(options, 'grep') + scope_option = Util.get_option(options, 'scope') + encrypt_option = Util.get_option(options, 'encrypt') + if self.context.get_variable("gather_from",None) : + from_option=self.context.get_variable("gather_from") + if self.context.get_variable("gather_to",None) : + to_option=self.context.get_variable("gather_to") + if self.context.get_variable("gather_since",None) : + since_option=self.context.get_variable("gather_since") + if self.context.get_variable("store_dir",None) : + store_dir_option=self.context.get_variable("store_dir") + if self.context.get_variable("gather_scope",None) : + scope_option=self.context.get_variable("gather_scope") + if self.context.get_variable("gather_grep",None) : + grep_option=self.context.get_variable("gather_grep") + if from_option is not None and to_option is not None: + try: + from_timestamp = TimeUtils.parse_time_str(from_option) + to_timestamp = TimeUtils.parse_time_str(to_option) + self.from_time_str = from_option + self.to_time_str = to_option + except OBDIAGFormatException: + self.stdio.exception('Error: Datetime is invalid. Must be in format "yyyy-mm-dd hh:mm:ss". from_datetime={0}, to_datetime={1}'.format(from_option, to_option)) + return False + if to_timestamp <= from_timestamp: + self.stdio.exception('Error: from datetime is larger than to datetime, please check.') + return False + elif (from_option is None or to_option is None) and since_option is not None: + self.stdio.warn('No time option provided, default processing is based on the last 30 minutes') + now_time = datetime.datetime.now() + self.to_time_str = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') + self.from_time_str = (now_time - datetime.timedelta(seconds=TimeUtils.parse_time_length_to_sec(since_option))).strftime('%Y-%m-%d %H:%M:%S') + self.stdio.print('gather log from_time: {0}, to_time: {1}'.format(self.from_time_str, self.to_time_str)) + else: + self.stdio.warn('No time option provided, default processing is based on the last 30 minutes') + now_time = datetime.datetime.now() + self.to_time_str = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') + if since_option: + self.from_time_str = (now_time - datetime.timedelta(seconds=TimeUtils.parse_time_length_to_sec(since_option))).strftime('%Y-%m-%d %H:%M:%S') + else: + self.from_time_str = (now_time - datetime.timedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') + self.stdio.print('gather log from_time: {0}, to_time: {1}'.format(self.from_time_str, self.to_time_str)) + if store_dir_option is not None and store_dir_option != './': + if not os.path.exists(os.path.abspath(store_dir_option)): + self.stdio.warn('warn: args --store_dir [{0}] incorrect: No such directory, Now create it'.format(os.path.abspath(store_dir_option))) + os.makedirs(os.path.abspath(store_dir_option)) + self.gather_pack_dir = os.path.abspath(store_dir_option) + if scope_option: + self.scope = scope_option + if encrypt_option == "true": + self.zip_encrypt = True + if grep_option: + self.grep_options = grep_option + return True - def handle(self, args): - # check args first - if not self.__check_valid_and_parse_args(args): - return - # example of the format of pack dir for this command: {gather_pack_dir}/gather_pack_20190610123344 + def handle(self): + if not self.init_option(): + self.stdio.error('init option failed') + return False + if not self.init_config(): + self.stdio.error('init config failed') + return False if self.is_scene: pack_dir_this_command = self.gather_pack_dir else: - pack_dir_this_command = os.path.join(self.gather_pack_dir, "gather_pack_{0}".format(timestamp_to_filename_time(self.gather_timestamp))) - mkdir_if_not_exist(pack_dir_this_command) - logger.info("Use {0} as pack dir.".format(pack_dir_this_command)) + pack_dir_this_command = os.path.join(self.gather_pack_dir, "gather_pack_{0}".format(TimeUtils.timestamp_to_filename_time(self.gather_timestamp))) + DirectoryUtil.mkdir(path=pack_dir_this_command, stdio=self.stdio) + self.stdio.verbose('Use {0} as pack dir.'.format(pack_dir_this_command)) gather_tuples = [] - gather_pack_path_dict = {} def handle_from_node(node): st = time.time() - resp = self.__handle_from_node(args, pack_dir_this_command, node) + resp = self.__handle_from_node(pack_dir_this_command, node) file_size = "" if len(resp["error"]) == 0: file_size = os.path.getsize(resp["gather_pack_path"]) @@ -92,47 +161,42 @@ def handle_from_node(node): for node in self.nodes: handle_from_node(node) else: - local_ip = get_localhost_inner_ip() + local_ip = NetUtils.get_inner_ip() node = self.nodes[0] node["ip"] = local_ip for node in self.nodes: handle_from_node(node) summary_tuples = self.__get_overall_summary(gather_tuples, self.zip_encrypt) - print(summary_tuples) - display_trace(uuid.uuid3(uuid.NAMESPACE_DNS, str(os.getpid()))) + self.stdio.print(summary_tuples) # Persist the summary results to a file - write_result_append_to_file(os.path.join(pack_dir_this_command, "result_summary.txt"), summary_tuples) - + FileUtil.write_append(os.path.join(pack_dir_this_command, "result_summary.txt"), summary_tuples) last_info = "For result details, please run cmd \033[32m' cat {0} '\033[0m\n".format(os.path.join(pack_dir_this_command, "result_summary.txt")) - print(last_info) - - # When using encryption mode, record the account and password information into the file - return gather_tuples, gather_pack_path_dict + return True - def __handle_from_node(self, args, pack_dir_this_command, node): + def __handle_from_node(self, pack_dir_this_command, node): resp = { "skip": False, "error": "", "zip_password": "", "gather_pack_path": "" } - remote_ip = node.get("ip") if self.is_ssh else get_localhost_inner_ip() - remote_user = node.get("user") - remote_password = node.get("password") - remote_port = node.get("port") - remote_private_key = node.get("private_key") + remote_ip = node.get("ip") if self.is_ssh else NetUtils.get_inner_ip() + remote_user = node.get("ssh_username") + remote_password = node.get("ssh_password") + remote_port = node.get("ssh_port") + remote_private_key = node.get("ssh_key_file") remote_home_path = node.get("home_path") ssh_failed = False - logger.info("Sending Collect Shell Command to node {0} ...".format(remote_ip)) + self.stdio.verbose('Sending Collect Shell Command to node {0} ...'.format(remote_ip)) if "ssh_type" in node and node["ssh_type"] == "docker": local_store_dir = "{0}/docker_{1}".format(pack_dir_this_command, node["container_name"]) else: local_store_dir = "{0}/{1}".format(pack_dir_this_command, remote_ip) try: - ssh = SshHelper(self.is_ssh, remote_ip, remote_user, remote_password, remote_port, remote_private_key, node) + ssh = SshHelper(self.is_ssh, remote_ip, remote_user, remote_password, remote_port, remote_private_key, node, self.stdio) except Exception as e: - logger.error("ssh {0}@{1}: failed, Please check the {2}".format( + self.stdio.exception('ssh {0}@{1}: failed, Please check the {2}'.format( remote_user, remote_ip, self.config_path)) @@ -142,42 +206,99 @@ def __handle_from_node(self, args, pack_dir_this_command, node): if not ssh_failed: # transform timestamp(in us) to yyyymmddhhmmss (filename_time style) - from_datetime_timestamp = timestamp_to_filename_time(datetime_to_timestamp(self.from_time_str)) - to_datetime_timestamp = timestamp_to_filename_time(datetime_to_timestamp(self.to_time_str)) + from_datetime_timestamp = TimeUtils.timestamp_to_filename_time(TimeUtils.datetime_to_timestamp(self.from_time_str)) + to_datetime_timestamp = TimeUtils.timestamp_to_filename_time(TimeUtils.datetime_to_timestamp(self.to_time_str)) gather_dir_name = "ob_log_{0}_{1}_{2}".format(ssh.host_ip, from_datetime_timestamp, to_datetime_timestamp) gather_dir_full_path = "{0}/{1}".format("/tmp", gather_dir_name) - mkdir(self.is_ssh, ssh, gather_dir_full_path) + mkdir(self.is_ssh, ssh, gather_dir_full_path, self.stdio) log_list, resp = self.__handle_log_list(ssh, node, resp) if resp["skip"]: return resp - for log_name in log_list: - self.__pharse_log(ssh_helper=ssh, log_name=log_name, home_path=remote_home_path, gather_path=gather_dir_full_path) - delete_empty_file(self.is_ssh, ssh, gather_dir_full_path) + if self.context.get_variable("gather_mode") == "trace_id_log": + self.__grep_log_until_empty(ssh_helper=ssh, home_path=remote_home_path, log_list=log_list, gather_path=gather_dir_full_path) + else: + for log_name in log_list: + self.__pharse_log(ssh_helper=ssh, log_name=log_name, home_path=remote_home_path, gather_path=gather_dir_full_path) + delete_empty_file(self.is_ssh, ssh, gather_dir_full_path, self.stdio) - is_empty = is_empty_dir(self.is_ssh, ssh, gather_dir_full_path) + is_empty = is_empty_dir(self.is_ssh, ssh, gather_dir_full_path, self.stdio) if is_empty: resp["error"] = "Empty file" resp["zip_password"] = "" - rm_rf_file(self.is_ssh, ssh, gather_dir_full_path) + rm_rf_file(self.is_ssh, ssh, gather_dir_full_path, self.stdio) else: self.__handle_zip_file(node.get("ip"), ssh, resp, gather_dir_name, pack_dir_this_command) ssh.ssh_close() return resp + + def __grep_log_until_empty(self, ssh_helper, home_path, log_list, gather_path): + """ + 按时间顺序排序日志,从最新的时间(或者从设置的时间)开始往前找日志,直到grep的结果不为空,再直到grep的结果为空,则停止 + :param ssh_helper, home_path, log_list, gather_path + :return: + """ + log_type_list = ['observer', 'election', 'rootservice'] + + # 理论上只有上述三种日志,other_log_list应该为空 + other_log_list = [log_name for log_name in log_list if not any(log_name.startswith(prefix) for prefix in log_type_list)] + for log_name in other_log_list: + self.__pharse_log(ssh_helper=ssh_helper, log_name=log_name, home_path=home_path, gather_path=gather_path) + + # wf结尾的日志非全量日志,不排查 + # 形如observer.log等日志不方便排序,暂时删除,在后续重新加上 + log_list = [log_name for log_name in log_list if (log_name not in other_log_list) and log_name[-1].isdigit()] + + for log_type in log_type_list: + cur_type_log_list = [log_name for log_name in log_list if log_name.startswith(log_type)] + # 按照时间从最新的到最旧的 + cur_type_log_list.sort(reverse=True) + # 没有时间后缀的是最新日志,插入到首部 + cur_type_log_list.insert(0, f'{log_type}.log') + has_res = False + for log_name in cur_type_log_list: + is_empty = self.__grep_log(ssh_helper=ssh_helper, log_name=log_name, home_path=home_path, gather_path=gather_path) + if not is_empty: + has_res = True + elif has_res: + # 已有结果且grep结果为空,说明相关日志已集齐 + break + + def __grep_log(self, ssh_helper, home_path, log_name, gather_path): + """ + 处理传入的日志文件,将满足条件的日志文件归集到一起,并返回grep结果是否为空 + :param ssh_helper, log_name, gather_path + :return is_empty: + """ + log_path = os.path.join(home_path, "log") + if self.grep_options is not None: + grep_cmd = "grep -e '{grep_args}' {log_dir}/{log_name} > {gather_path}/{log_name} ".format( + grep_args=self.grep_options, + gather_path=gather_path, + log_name=log_name, + log_dir=log_path) + find_file_cmd = "find {gather_path} -type f -name {log_name} ! -empty".format( + gather_path=gather_path, + log_name=log_name) + self.stdio.verbose("grep files, run cmd = [{0}]".format(grep_cmd)) + self.stdio.verbose("grep files, run cmd = [{0}]".format(find_file_cmd)) + SshClient(self.stdio).run(ssh_helper, grep_cmd) if self.is_ssh else LocalClient(self.stdio).run(grep_cmd) + find_file_res = SshClient(self.stdio).run(ssh_helper, find_file_cmd) if self.is_ssh else LocalClient(self.stdio).run(grep_cmd) + return find_file_res == "" + else: + raise Exception("grep arg is none.") def __handle_log_list(self, ssh, node, resp): log_list = self.__get_log_name(ssh, node) ip = node.get("ip") if len(log_list) > self.file_number_limit: - logger.warn( - "{0} The number of log files is {1}, out of range (0,{2}], " + self.stdio.warn('{0} The number of log files is {1}, out of range (0,{2}], ' "Please adjust the query limit".format(ip, len(log_list), self.file_number_limit)) resp["skip"] = True, resp["error"] = "Too many files {0} > {1}".format(len(log_list), self.file_number_limit) return log_list, resp elif len(log_list) <= 0: - logger.warn( - "{0} The number of log files is {1}, The time range for file gather from {2} to {3}, and no eligible files were found" + self.stdio.warn('{0} The number of log files is {1}, The time range for file gather from {2} to {3}, and no eligible files were found' "Please adjust the query time limit".format(ip, len(log_list), self.from_time_str, self.to_time_str)) resp["skip"] = True, resp["error"] = "No files found" @@ -198,11 +319,11 @@ def __get_log_name(self, ssh_helper, node): get_oblog = "ls -1 -F %s/observer.log* %s/rootservice.log* %s/election.log* | awk -F '/' '{print $NF}'" % \ (log_path, log_path, log_path) log_name_list = [] - log_files = SshClient().run(ssh_helper, get_oblog) if self.is_ssh else LocalClient().run(get_oblog) + log_files = SshClient(self.stdio).run(ssh_helper, get_oblog) if self.is_ssh else LocalClient(self.stdio).run(get_oblog) if log_files: - log_name_list = get_logfile_name_list(self.is_ssh, ssh_helper, self.from_time_str, self.to_time_str, log_path, log_files) + log_name_list = get_logfile_name_list(self.is_ssh, ssh_helper, self.from_time_str, self.to_time_str, log_path, log_files, self.stdio) else: - logger.error("Unable to find the log file. Please provide the correct home_path, the default is [/root/observer]") + self.stdio.error('Unable to find the log file. Please provide the correct home_path, the default is [/root/observer]') return log_name_list def __pharse_log(self, ssh_helper, home_path, log_name, gather_path): @@ -212,93 +333,66 @@ def __pharse_log(self, ssh_helper, home_path, log_name, gather_path): :return: """ log_path = os.path.join(home_path, "log") - if self.grep_args is not None: - grep_cmd = "grep -e '{grep_args}' {log_dir}/{log_name} >> {gather_path}/{log_name} ".format( - grep_args=self.grep_args, - gather_path=gather_path, - log_name=log_name, - log_dir=log_path) - logger.debug("grep files, run cmd = [{0}]".format(grep_cmd)) - SshClient().run(ssh_helper, grep_cmd) if self.is_ssh else LocalClient().run(grep_cmd) + if self.grep_options is not None: + if type(self.grep_options) == str: + grep_cmd = "grep -e '{grep_options}' {log_dir}/{log_name} >> {gather_path}/{log_name} ".format( + grep_options=self.grep_options, + gather_path=gather_path, + log_name=log_name, + log_dir=log_path) + elif type(self.grep_options) == list and len(self.grep_options)>0: + grep_litter_cmd="" + for grep_option in self.grep_options: + if type(grep_option)!=str: + self.stdio.error('The grep args must be string or list of strings, but got {0}'.format(type(grep_option))) + raise Exception('The grep args must be string or list of strings, but got {0}'.format(type(grep_option))) + elif grep_option == "": + self.stdio.warn('The grep args must be string or list of strings, but got ""') + continue + grep_litter_cmd += "| grep -e '{0}'".format(grep_option) + grep_cmd = "cat {log_dir}/{log_name} {grep_options} >> {gather_path}/{log_name} ".format( + grep_options=grep_litter_cmd, + gather_path=gather_path, + log_name=log_name, + log_dir=log_path) + self.stdio.verbose('grep files, run cmd = [{0}]'.format(grep_cmd)) + SshClient(self.stdio).run(ssh_helper, grep_cmd) if self.is_ssh else LocalClient(self.stdio).run(grep_cmd) else: cp_cmd = "cp {log_dir}/{log_name} {gather_path}/{log_name} ".format( gather_path=gather_path, log_name=log_name, log_dir=log_path) - logger.debug("copy files, run cmd = [{0}]".format(cp_cmd)) - SshClient().run(ssh_helper, cp_cmd) if self.is_ssh else LocalClient().run(cp_cmd) + self.stdio.verbose('copy files, run cmd = [{0}]'.format(cp_cmd)) + SshClient(self.stdio).run(ssh_helper, cp_cmd) if self.is_ssh else LocalClient(self.stdio).run(cp_cmd) def __handle_zip_file(self, ip, ssh, resp, gather_dir_name, pack_dir_this_command): zip_password = "" gather_dir_full_path = "{0}/{1}".format(self.gather_ob_log_temporary_dir, gather_dir_name) + self.stdio.start_loading('[ip: {0}] zip observer log start'.format(ip)) if self.zip_encrypt: - zip_password = gen_password(16) - zip_encrypt_dir(self.is_ssh, ssh, zip_password, self.gather_ob_log_temporary_dir, gather_dir_name) + zip_password = Util.gen_password(16) + zip_encrypt_dir(self.is_ssh, ssh, zip_password, self.gather_ob_log_temporary_dir, gather_dir_name, self.stdio) else: - zip_dir(self.is_ssh, ssh, self.gather_ob_log_temporary_dir, gather_dir_name) + zip_dir(self.is_ssh, ssh, self.gather_ob_log_temporary_dir, gather_dir_name, self.stdio) + self.stdio.stop_loading('[ip: {0}] zip observer log end'.format(ip)) gather_package_dir = "{0}.zip".format(gather_dir_full_path) - gather_log_file_size = get_file_size(self.is_ssh, ssh, gather_package_dir) - print(show_file_size_tabulate(ip, gather_log_file_size)) + gather_log_file_size = get_file_size(self.is_ssh, ssh, gather_package_dir, self.stdio) + self.stdio.print(FileUtil.show_file_size_tabulate(ip, gather_log_file_size)) local_store_path = "" if int(gather_log_file_size) < self.file_size_limit: local_store_path = pack_dir_this_command + "/{0}.zip".format(gather_dir_name) - download_file(self.is_ssh, ssh, gather_package_dir, local_store_path) + download_file(self.is_ssh, ssh, gather_package_dir, local_store_path, self.stdio) resp["error"] = "" resp["zip_password"] = zip_password else: resp["error"] = "File too large" resp["zip_password"] = "" - rm_rf_file(self.is_ssh, ssh, gather_package_dir) + rm_rf_file(self.is_ssh, ssh, gather_package_dir, self.stdio) resp["gather_pack_path"] = local_store_path - - logger.debug( - "Collect pack gathered from node {0}: stored in {1}".format(ip, gather_package_dir)) + self.stdio.verbose("Collect pack gathered from node {0}: stored in {1}".format(ip, gather_package_dir)) return resp - def __check_valid_and_parse_args(self, args): - """ - chech whether command args are valid. If invalid, stop processing and print the error to the user - :param args: command args - :return: boolean. True if valid, False if invalid. - """ - # 1: to timestamp must be larger than from timestamp, and be valid - if getattr(args, "from") is not None and getattr(args, "to") is not None: - try: - from_timestamp = parse_time_str(getattr(args, "from")) - to_timestamp = parse_time_str(getattr(args, "to")) - self.from_time_str = getattr(args, "from") - self.to_time_str = getattr(args, "to") - except OBDIAGFormatException: - logger.error("Error: Datetime is invalid. Must be in format yyyy-mm-dd hh:mm:ss. " \ - "from_datetime={0}, to_datetime={1}".format(getattr(args, "from"), getattr(args, "to"))) - return False - if to_timestamp <= from_timestamp: - logger.error("Error: from datetime is larger than to datetime, please check.") - return False - else: - now_time = datetime.datetime.now() - self.to_time_str = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') - if args.since is not None: - self.from_time_str = (now_time - datetime.timedelta( - seconds=parse_time_length_to_sec(args.since))).strftime('%Y-%m-%d %H:%M:%S') - else: - self.from_time_str = (now_time - datetime.timedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') - # 2: store_dir must exist, else create directory. - if getattr(args, "store_dir") is not None: - if not os.path.exists(os.path.abspath(getattr(args, "store_dir"))): - logger.warn("Error: args --store_dir [{0}] incorrect: No such directory, Now create it".format(os.path.abspath(getattr(args, "store_dir")))) - os.makedirs(os.path.abspath(getattr(args, "store_dir"))) - self.gather_pack_dir = os.path.abspath(getattr(args, "store_dir")) - - if hasattr(args, "grep") and args.grep is not None: - self.grep_args = getattr(args, "grep")[0] - if hasattr(args, "scope") and args.scope is not None: - self.scope = getattr(args, "scope")[0] - if hasattr(args, "encrypt") and args.encrypt[0] == "true": - self.zip_encrypt = True - return True - @staticmethod def __get_overall_summary(node_summary_tuple, is_zip_encrypt): """ @@ -319,9 +413,9 @@ def __get_overall_summary(node_summary_tuple, is_zip_encrypt): consume_time = tup[5] pack_path = tup[6] try: - format_file_size = size_format(file_size, output_str=True) + format_file_size = FileUtil.size_format(num=file_size, output_str=True) except: - format_file_size = size_format(0, output_str=True) + format_file_size = FileUtil.size_format(num=0, output_str=True) if is_zip_encrypt: summary_tab.append((node, "Error:" + tup[2] if is_err else "Completed", format_file_size, tup[4], "{0} s".format(int(consume_time)), pack_path)) diff --git a/handler/gather/gather_obadmin.py b/handler/gather/gather_obadmin.py index 16d42401..fd5161fc 100644 --- a/handler/gather/gather_obadmin.py +++ b/handler/gather/gather_obadmin.py @@ -16,60 +16,119 @@ @desc: """ import os -import threading import time import datetime -import uuid import tabulate -from common.logger import logger from common.obdiag_exception import OBDIAGFormatException from common.constant import const from common.command import LocalClient, SshClient, is_empty_dir from handler.base_shell_handler import BaseShellHandler -from utils.file_utils import mkdir_if_not_exist, size_format, write_result_append_to_file, parse_size, \ - show_file_size_tabulate from common.command import download_file, rm_rf_file, get_file_size, zip_encrypt_dir, zip_dir, get_observer_version -from utils.password_util import gen_password -from utils.shell_utils import SshHelper -from utils.time_utils import parse_time_str, parse_time_length_to_sec -from utils.time_utils import timestamp_to_filename_time -from utils.version_utils import compare_versions_lower -from utils.utils import get_localhost_inner_ip, display_trace +from common.ssh import SshHelper +from common.tool import TimeUtils +from common.tool import StringUtils +from common.tool import Util +from common.tool import DirectoryUtil +from common.tool import FileUtil +from common.tool import NetUtils class GatherObAdminHandler(BaseShellHandler): - def __init__(self, nodes, gather_pack_dir, gather_timestamp, mode, common_config): - super(GatherObAdminHandler, self).__init__(nodes) + def __init__(self, context, gather_pack_dir='./', is_scene=False): + super(GatherObAdminHandler, self).__init__() + self.context = context + self.stdio = context.stdio self.is_ssh = True - self.gather_timestamp = gather_timestamp self.gather_ob_log_temporary_dir = const.GATHER_LOG_TEMPORARY_DIR_DEFAULT self.local_stored_path = gather_pack_dir self.remote_stored_path = None - self.ob_admin_mode = mode self.from_time_str = None self.to_time_str = None self.grep_args = None self.zip_encrypt = False self.config_path = const.DEFAULT_CONFIG_PATH - if common_config is None: - self.file_size_limit = 2 * 1024 * 1024 + if self.context.get_variable("gather_timestamp", None) : + self.gather_timestamp=self.context.get_variable("gather_timestamp") else: - self.file_size_limit = int(parse_size(common_config["file_size_limit"])) + self.gather_timestamp = TimeUtils.get_current_us_timestamp() - def handle(self, args): - if not self.__check_valid_args(args): - return + def init_config(self): + self.nodes = self.context.cluster_config['servers'] + new_nodes = Util.get_nodes_list(self.context, self.nodes, self.stdio) + if new_nodes: + self.nodes = new_nodes + self.inner_config = self.context.inner_config + self.ob_admin_mode = 'clog' + if self.context.get_variable("gather_obadmin_mode", None) : + self.ob_admin_mode = self.context.get_variable("gather_obadmin_mode") + if self.inner_config is None: + self.file_number_limit = 20 + self.file_size_limit = 2 * 1024 * 1024 * 1024 + else: + basic_config = self.inner_config['obdiag']['basic'] + self.file_number_limit = int(basic_config["file_number_limit"]) + self.file_size_limit = int(FileUtil.size(basic_config["file_size_limit"])) + self.config_path = basic_config['config_path'] + return True - pack_dir_this_command = os.path.join(self.local_stored_path, - "gather_pack_{0}".format(timestamp_to_filename_time( - self.gather_timestamp))) - logger.info("Use {0} as pack dir.".format(pack_dir_this_command)) + def init_option(self): + options = self.context.options + from_option = Util.get_option(options, 'from') + to_option = Util.get_option(options, 'to') + since_option = Util.get_option(options, 'since') + store_dir_option = Util.get_option(options, 'store_dir') + encrypt_option = Util.get_option(options, 'encrypt') + if from_option is not None and to_option is not None: + try: + from_timestamp = TimeUtils.parse_time_str(from_option) + to_timestamp = TimeUtils.parse_time_str(to_option) + self.from_time_str = from_option + self.to_time_str = to_option + except OBDIAGFormatException: + self.stdio.exception('Error: Datetime is invalid. Must be in format yyyy-mm-dd hh:mm:ss. from_datetime={0}, to_datetime={1}'.format(from_option, to_option)) + return False + if to_timestamp <= from_timestamp: + self.stdio.exception('Error: from datetime is larger than to datetime, please check.') + return False + elif (from_option is None or to_option is None) and since_option is not None: + self.stdio.warn('No time option provided, default processing is based on the last 30 minutes') + now_time = datetime.datetime.now() + self.to_time_str = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') + self.from_time_str = (now_time - datetime.timedelta(seconds=TimeUtils.parse_time_length_to_sec(since_option, self.stdio))).strftime('%Y-%m-%d %H:%M:%S') + self.stdio.print('gather from_time: {0}, to_time: {1}'.format(self.from_time_str, self.to_time_str)) + else: + self.stdio.warn('No time option provided, default processing is based on the last 30 minutes') + now_time = datetime.datetime.now() + self.to_time_str = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') + if since_option is not None: + self.from_time_str = (now_time - datetime.timedelta(seconds=TimeUtils.parse_time_length_to_sec(since_option, self.stdio))).strftime('%Y-%m-%d %H:%M:%S') + else: + self.from_time_str = (now_time - datetime.timedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') + self.stdio.print('gather from_time: {0}, to_time: {1}'.format(self.from_time_str, self.to_time_str)) + if store_dir_option and store_dir_option != './': + if not os.path.exists(os.path.abspath(store_dir_option)): + self.stdio.warn('Error: args --store_dir [{0}] incorrect: No such directory, Now create it'.format(os.path.abspath(store_dir_option))) + os.makedirs(os.path.abspath(store_dir_option)) + self.local_stored_path = os.path.abspath(store_dir_option) + if encrypt_option == "true": + self.zip_encrypt = True + return True + + def handle(self): + if not self.init_option(): + self.stdio.error('init option failed') + return False + if not self.init_config(): + self.stdio.error('init config failed') + return False + pack_dir_this_command = os.path.join(self.local_stored_path, "gather_pack_{0}".format(TimeUtils.timestamp_to_filename_time(self.gather_timestamp))) + self.stdio.verbose("Use {0} as pack dir.".format(pack_dir_this_command)) gather_tuples = [] def handle_from_node(node): st = time.time() - resp = self.__handle_from_node(args, pack_dir_this_command, node) + resp = self.__handle_from_node(pack_dir_this_command, node) file_size = "" if len(resp["error"]) == 0: file_size = os.path.getsize(resp["gather_pack_path"]) @@ -83,7 +142,7 @@ def handle_from_node(node): for node in self.nodes: handle_from_node(node) else: - local_ip = get_localhost_inner_ip() + local_ip = NetUtils.get_inner_ip(self.stdio) node = self.nodes[0] node["ip"] = local_ip for node in self.nodes: @@ -94,28 +153,26 @@ def handle_from_node(node): else: mode = "clog" summary_tuples = self.__get_overall_summary(gather_tuples, mode, self.zip_encrypt) - print(summary_tuples) - display_trace(uuid.uuid3(uuid.NAMESPACE_DNS, str(os.getpid()))) + self.stdio.print(summary_tuples) # Persist the summary results to a file - write_result_append_to_file(os.path.join(pack_dir_this_command, "result_summary.txt"), summary_tuples) + FileUtil.write_append(os.path.join(pack_dir_this_command, "result_summary.txt"), summary_tuples) last_info = "For result details, please run cmd \033[32m' cat {0} '\033[0m\n".format(os.path.join(pack_dir_this_command, "result_summary.txt")) - print(last_info) - def __handle_from_node(self, args, local_stored_path, node): + def __handle_from_node(self, local_stored_path, node): resp = { "skip": False, "error": "", "gather_pack_path": "" } - remote_ip = node.get("ip") if self.is_ssh else get_localhost_inner_ip() - remote_user = node.get("user") - remote_password = node.get("password") - remote_port = node.get("port") - remote_private_key = node.get("private_key") - logger.info( + remote_ip = node.get("ip") if self.is_ssh else NetUtils.get_inner_ip() + remote_user = node.get("ssh_username") + remote_password = node.get("ssh_password") + remote_port = node.get("ssh_port") + remote_private_key = node.get("ssh_key_file") + self.stdio.verbose( "Sending Collect Shell Command to node {0} ...".format(remote_ip)) - mkdir_if_not_exist(local_stored_path) + DirectoryUtil.mkdir(path=local_stored_path, stdio=self.stdio) now_time = datetime.datetime.now().strftime('%Y%m%d%H%M%S') if self.ob_admin_mode == "slog": remote_dir_name = "slog_{0}_{1}".format(remote_ip, now_time) @@ -124,9 +181,9 @@ def __handle_from_node(self, args, local_stored_path, node): remote_dir_full_path = "/tmp/{0}".format(remote_dir_name) ssh_failed = False try: - ssh_helper = SshHelper(self.is_ssh, remote_ip, remote_user, remote_password, remote_port, remote_private_key, node) + ssh_helper = SshHelper(self.is_ssh, remote_ip, remote_user, remote_password, remote_port, remote_private_key, node, self.stdio) except Exception as e: - logger.error("ssh {0}@{1}: failed, Please check the {2}".format( + self.stdio.error("ssh {0}@{1}: failed, Please check the {2}".format( remote_user, remote_ip, self.config_path)) @@ -135,10 +192,10 @@ def __handle_from_node(self, args, local_stored_path, node): resp["error"] = "Please check the {0}".format(self.config_path) if not ssh_failed: mkdir_cmd = "mkdir -p {0}".format(remote_dir_full_path) - SshClient().run(ssh_helper, mkdir_cmd) if self.is_ssh else LocalClient().run(mkdir_cmd) - ob_version = get_observer_version(self.is_ssh, ssh_helper, node.get("home_path")) - if (ob_version != "" and not compare_versions_lower(ob_version, const.MAX_OB_VERSION_SUPPORT_GATHER_OBADMIN)) or ob_version == "": - logger.info("This version {0} does not support gather clog/slog . The max supported version less than {1}". + SshClient(self.stdio).run(ssh_helper, mkdir_cmd) if self.is_ssh else LocalClient(self.stdio).run(mkdir_cmd) + ob_version = get_observer_version(self.is_ssh, ssh_helper, node.get("home_path"), self.stdio) + if (ob_version != "" and not StringUtils.compare_versions_lower(ob_version, const.MAX_OB_VERSION_SUPPORT_GATHER_OBADMIN, self.stdio)) or ob_version == "": + self.stdio.verbose("This version {0} does not support gather clog/slog . The max supported version less than {1}". format(ob_version, const.MAX_OB_VERSION_SUPPORT_GATHER_OBADMIN)) resp["error"] = "{0} not support gather clog/slog".format(ob_version) resp["gather_pack_path"] = "{0}".format(local_stored_path) @@ -149,25 +206,25 @@ def __handle_from_node(self, args, local_stored_path, node): self.__gather_log_info(ssh_helper, node, slog, remote_dir_full_path) self.__mv_log(ssh_helper, remote_dir_full_path) - if is_empty_dir(self.is_ssh, ssh_helper, "/tmp/{0}".format(remote_dir_name)): + if is_empty_dir(self.is_ssh, ssh_helper, "/tmp/{0}".format(remote_dir_name), self.stdio): resp["error"] = "gather failed, folder is empty" resp["zip_password"] = "" else: resp = self.__handle_zip_file(remote_ip, ssh_helper, resp, remote_dir_name, local_stored_path) - rm_rf_file(self.is_ssh, ssh_helper, remote_dir_full_path) + rm_rf_file(self.is_ssh, ssh_helper, remote_dir_full_path, self.stdio) return resp def __handle_log_list(self, ssh, node, resp): log_list = self.__get_log_name(ssh, node) if len(log_list) > 20: - logger.warn( + self.stdio.warn( "{0} The number of log files is {1}, out of range (0,20], " "Please adjust the query limit".format(node.get("ip"), len(log_list))) resp["skip"] = True, resp["error"] = "Too many files {0} > 20".format(len(log_list)) return log_list, resp elif len(log_list) <= 0: - logger.warn( + self.stdio.warn( "{0} The number of log files is {1}, out of range (0,20], " "Please adjust the query limit".format(node.get("ip"), len(log_list))) resp["skip"] = True, @@ -179,25 +236,25 @@ def __handle_zip_file(self, ip, ssh, resp, gather_dir_name, pack_dir_this_comman zip_password = "" gather_dir_full_path = "{0}/{1}".format(self.gather_ob_log_temporary_dir, gather_dir_name) if self.zip_encrypt: - zip_password = gen_password(16) - zip_encrypt_dir(self.is_ssh, ssh, zip_password, self.gather_ob_log_temporary_dir, gather_dir_name) + zip_password = Util.gen_password(16) + zip_encrypt_dir(self.is_ssh, ssh, zip_password, self.gather_ob_log_temporary_dir, gather_dir_name, self.stdio) else: - zip_dir(self.is_ssh, ssh, self.gather_ob_log_temporary_dir, gather_dir_name) + zip_dir(self.is_ssh, ssh, self.gather_ob_log_temporary_dir, gather_dir_name, self.stdio) gather_package_dir = "{0}.zip".format(gather_dir_full_path) - gather_log_file_size = get_file_size(self.is_ssh, ssh, gather_package_dir) - print(show_file_size_tabulate(ip, gather_log_file_size)) + gather_log_file_size = get_file_size(self.is_ssh, ssh, gather_package_dir, self.stdio) + self.stdio.print(FileUtil.show_file_size_tabulate(ip, gather_log_file_size, self.stdio)) local_path = "" if int(gather_log_file_size) < self.file_size_limit: - local_path = download_file(self.is_ssh, ssh, gather_package_dir, pack_dir_this_command) + local_path = download_file(self.is_ssh, ssh, gather_package_dir, pack_dir_this_command, self.stdio) resp["error"] = "" resp["zip_password"] = zip_password else: resp["error"] = "File too large" resp["zip_password"] = "" - rm_rf_file(self.is_ssh, ssh, gather_package_dir) + rm_rf_file(self.is_ssh, ssh, gather_package_dir, self.stdio) resp["gather_pack_path"] = local_path - logger.debug( + self.stdio.verbose( "Collect pack gathered from node {0}: stored in {1}".format(ip, gather_package_dir)) return resp @@ -213,11 +270,11 @@ def __get_log_name(self, ssh_helper, node): get_log = "ls -l SLOG_DIR --time-style '+.%Y%m%d%H%M%S' | awk '{print $7,$6}'".replace("SLOG_DIR", slog_dir) else: get_log = "ls -l CLOG_DIR --time-style '+.%Y%m%d%H%M%S' | awk '{print $7,$6}'".replace("CLOG_DIR", clog_dir) - log_files = SshClient().run(ssh_helper, get_log) if self.is_ssh else LocalClient().run(get_log) + log_files = SshClient(self.stdio).run(ssh_helper, get_log) if self.is_ssh else LocalClient(self.stdio).run(get_log) log_name_list = [] for file_name in log_files.split('\n'): if file_name == "": - logger.warn("existing file name is empty") + self.stdio.verbose("existing file name is empty") continue log_name_fields = file_name.split(".") if bytes.isdigit(log_name_fields[-1].encode("utf-8")) and len(log_name_fields[-1]) == 14: @@ -227,11 +284,10 @@ def __get_log_name(self, ssh_helper, node): if (log_time > from_time) and (log_time < to_time): log_name_list.append(str(log_name_fields[0]).rstrip()) if len(log_name_list): - logger.info("Find the qualified log file {0} on Server [{1}], " + self.stdio.verbose("Find the qualified log file {0} on Server [{1}], " "wait for the next step".format(log_name_list, ssh_helper.get_name())) else: - logger.warn("Failed to find the qualified log file on Server [{0}], " - "please check whether the input parameters are correct. ".format(ssh_helper.get_name())) + self.stdio.warn("No found the qualified log file on Server [{0}]".format(ssh_helper.get_name())) return log_name_list def __gather_log_info(self, ssh_helper, node, log_name, remote_dir): @@ -250,52 +306,16 @@ def __gather_log_info(self, ssh_helper, node, log_name, remote_dir): obadmin_dir=obadmin_install_dir, clog_name=log_name, ) - logger.info("gather obadmin info, run cmd = [{0}]".format(cmd)) - SshClient().run(ssh_helper, cmd) if self.is_ssh else LocalClient().run(cmd) + self.stdio.verbose("gather obadmin info, run cmd = [{0}]".format(cmd)) + SshClient(self.stdio).run(ssh_helper, cmd) if self.is_ssh else LocalClient(self.stdio).run(cmd) def __mv_log(self, ssh_helper, remote_dir): if self.ob_admin_mode == "slog": cmd = "cd {remote_dir} && mv ob_admin.log ob_admin_slog.log".format(remote_dir=remote_dir) else: cmd = "cd {remote_dir} && mv ob_admin.log ob_admin_clog.log".format(remote_dir=remote_dir) - logger.info("mv log info, run cmd = [{0}]".format(cmd)) - SshClient().run(ssh_helper, cmd) if self.is_ssh else LocalClient().run(cmd) - - def __check_valid_args(self, args): - """ - chech whether command args are valid. If invalid, stop processing and print the error to the user - :param args: command args - :return: boolean. True if valid, False if invalid. - """ - # 1: store_dir must exist, else create directory. - if getattr(args, "store_dir") is not None: - if not os.path.exists(os.path.abspath(getattr(args, "store_dir"))): - logger.warn("Error: args --store_dir [{0}] incorrect: No such directory, Now create it".format(os.path.abspath(getattr(args, "store_dir")))) - os.makedirs(os.path.abspath(getattr(args, "store_dir"))) - self.local_stored_path = os.path.abspath(getattr(args, "store_dir")) - - if getattr(args, "encrypt")[0] == "true": - self.zip_encrypt = True - # 3: to timestamp must be larger than from timestamp, and be valid - if getattr(args, "from") is not None and getattr(args, "to") is not None: - try: - from_timestamp = parse_time_str(getattr(args, "from")) - to_timestamp = parse_time_str(getattr(args, "to")) - self.from_time_str = getattr(args, "from") - self.to_time_str = getattr(args, "to") - except OBDIAGFormatException: - logger.error("Error: Datetime is invalid. Must be in format yyyy-mm-dd hh:mm:ss. " \ - "from_datetime={0}, to_datetime={1}".format(getattr(args, "from"), getattr(args, "to"))) - return False - if to_timestamp <= from_timestamp: - logger.error("Error: from datetime is larger than to datetime, please check.") - return False - elif (getattr(args, "from") is None or getattr(args, "to") is None) and args.since is not None: - now_time = datetime.datetime.now() - self.to_time_str = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') - self.from_time_str = (now_time - datetime.timedelta( - seconds=parse_time_length_to_sec(args.since))).strftime('%Y-%m-%d %H:%M:%S') - return True + self.stdio.verbose("mv log info, run cmd = [{0}]".format(cmd)) + SshClient(self.stdio).run(ssh_helper, cmd) if self.is_ssh else LocalClient(self.stdio).run(cmd) @staticmethod def __get_overall_summary(node_summary_tuple, mode, is_zip_encrypt): @@ -312,9 +332,9 @@ def __get_overall_summary(node_summary_tuple, mode, is_zip_encrypt): consume_time = tup[5] pack_path = tup[6] try: - format_file_size = size_format(file_size, output_str=True) + format_file_size = FileUtil.size_format(num=file_size, output_str=True) except: - format_file_size = size_format(0, output_str=True) + format_file_size = FileUtil.size_format(num=0, output_str=True) if is_zip_encrypt: summary_tab.append((node, "Error:" + tup[2] if is_err else "Completed", format_file_size, tup[4], "{0} s".format(int(consume_time)), pack_path)) diff --git a/handler/gather/gather_obproxy_log.py b/handler/gather/gather_obproxy_log.py index f05bd050..9eaf72ac 100644 --- a/handler/gather/gather_obproxy_log.py +++ b/handler/gather/gather_obproxy_log.py @@ -18,32 +18,30 @@ import datetime import os import time -import uuid import tabulate from handler.base_shell_handler import BaseShellHandler -from common.logger import logger from common.obdiag_exception import OBDIAGFormatException from common.command import LocalClient, SshClient from common.constant import const -from utils.file_utils import mkdir_if_not_exist, size_format, write_result_append_to_file, parse_size, show_file_size_tabulate from common.command import get_file_size, download_file, is_empty_dir, get_logfile_name_list, mkdir, delete_empty_file, \ rm_rf_file, zip_encrypt_dir, zip_dir -from utils.shell_utils import SshHelper -from utils.password_util import gen_password -from utils.time_utils import parse_time_str -from utils.time_utils import parse_time_length_to_sec -from utils.time_utils import timestamp_to_filename_time -from utils.time_utils import datetime_to_timestamp -from utils.utils import get_localhost_inner_ip, display_trace +from common.ssh import SshHelper +from common.tool import Util +from common.tool import DirectoryUtil +from common.tool import FileUtil +from common.tool import NetUtils +from common.tool import TimeUtils class GatherObProxyLogHandler(BaseShellHandler): - def __init__(self, nodes, gather_pack_dir, gather_timestamp=None, common_config=None, is_scene=False): - super(GatherObProxyLogHandler, self).__init__(nodes) + def __init__(self, context, gather_pack_dir='./', is_scene=False): + super(GatherObProxyLogHandler, self).__init__() + self.pack_dir_this_command = "" + self.context = context + self.stdio = context.stdio self.is_ssh = True - self.gather_timestamp = gather_timestamp self.gather_log_temporary_dir = const.GATHER_LOG_TEMPORARY_DIR_DEFAULT self.gather_pack_dir = gather_pack_dir self.log_dir = None @@ -54,76 +52,150 @@ def __init__(self, nodes, gather_pack_dir, gather_timestamp=None, common_config= self.zip_encrypt = False self.is_scene = is_scene self.config_path = const.DEFAULT_CONFIG_PATH - if common_config is None: + if self.context.get_variable("gather_timestamp", None) : + self.gather_timestamp=self.context.get_variable("gather_timestamp") + else: + self.gather_timestamp = TimeUtils.get_current_us_timestamp() + + def init_config(self): + self.nodes = self.context.obproxy_config['servers'] + new_nodes = Util.get_nodes_list(self.context, self.nodes, self.stdio) + if new_nodes: + self.nodes = new_nodes + self.inner_config = self.context.inner_config + if self.inner_config is None: self.file_number_limit = 20 self.file_size_limit = 2 * 1024 * 1024 * 1024 else: - self.file_number_limit = int(common_config["file_number_limit"]) - self.file_size_limit = int(parse_size(common_config["file_size_limit"])) + basic_config = self.inner_config['obdiag']['basic'] + self.file_number_limit = int(basic_config["file_number_limit"]) + self.file_size_limit = int(FileUtil.size(basic_config["file_size_limit"])) + self.config_path = basic_config['config_path'] + return True + + def init_option(self): + options = self.context.options + from_option = Util.get_option(options, 'from') + to_option = Util.get_option(options, 'to') + since_option = Util.get_option(options, 'since') + store_dir_option = Util.get_option(options, 'store_dir') + grep_option = Util.get_option(options, 'grep') + encrypt_option = Util.get_option(options, 'encrypt') + scope_option = Util.get_option(options, 'scope') + if self.context.get_variable("gather_from",None) : + from_option=self.context.get_variable("gather_from") + if self.context.get_variable("gather_to",None) : + to_option=self.context.get_variable("gather_to") + if self.context.get_variable("gather_since",None) : + since_option=self.context.get_variable("gather_since") + if self.context.get_variable("store_dir",None) : + store_dir_option=self.context.get_variable("store_dir") + if self.context.get_variable("gather_scope",None) : + scope_option = self.context.get_variable("gather_scope") + if self.context.get_variable("gather_grep", None): + grep_option = self.context.get_variable("gather_grep") + + if from_option is not None and to_option is not None: + try: + from_timestamp = TimeUtils.parse_time_str(from_option) + to_timestamp = TimeUtils.parse_time_str(to_option) + self.from_time_str = from_option + self.to_time_str = to_option + except OBDIAGFormatException: + self.stdio.exception('Error: Datetime is invalid. Must be in format yyyy-mm-dd hh:mm:ss. from_datetime={0}, to_datetime={1}'.format(from_option, to_option)) + return False + if to_timestamp <= from_timestamp: + self.stdio.exception('Error: from datetime is larger than to datetime, please check.') + return False + elif (from_option is None or to_option is None) and since_option is not None: + self.stdio.warn('No time option provided, default processing is based on the last 30 minutes') + now_time = datetime.datetime.now() + self.to_time_str = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') + self.from_time_str = (now_time - datetime.timedelta(seconds=TimeUtils.parse_time_length_to_sec(since_option))).strftime('%Y-%m-%d %H:%M:%S') + self.stdio.print('gather from_time: {0}, to_time: {1}'.format(self.from_time_str, self.to_time_str)) + else: + self.stdio.warn('No time option provided, default processing is based on the last 30 minutes') + now_time = datetime.datetime.now() + self.to_time_str = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') + if since_option is not None: + self.from_time_str = (now_time - datetime.timedelta(seconds=TimeUtils.parse_time_length_to_sec(since_option))).strftime('%Y-%m-%d %H:%M:%S') + else: + self.from_time_str = (now_time - datetime.timedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') + self.stdio.print('gather from_time: {0}, to_time: {1}'.format(self.from_time_str, self.to_time_str)) + if store_dir_option and store_dir_option != './': + if not os.path.exists(os.path.abspath(store_dir_option)): + self.stdio.warn('warn: args --store_dir [{0}] incorrect: No such directory, Now create it'.format(os.path.abspath(store_dir_option))) + os.makedirs(os.path.abspath(store_dir_option)) + self.gather_pack_dir = os.path.abspath(store_dir_option) + if scope_option: + self.scope = scope_option + if encrypt_option == "true": + self.zip_encrypt = True + if grep_option: + self.grep_args = grep_option + self.stdio.verbose("grep_args:{0}".format(grep_option)) + return True - def handle(self, args): - if not self.__check_valid_and_parse_args(args): - return + def handle(self): + if not self.init_option(): + self.stdio.error('init option failed') + return False + if not self.init_config(): + self.stdio.error('init config failed') + return False if self.is_scene: pack_dir_this_command = self.gather_pack_dir else: - pack_dir_this_command = os.path.join(self.gather_pack_dir, "gather_pack_{0}".format(timestamp_to_filename_time(self.gather_timestamp))) - logger.info("Use {0} as pack dir.".format(pack_dir_this_command)) + pack_dir_this_command = os.path.join(self.gather_pack_dir, "gather_pack_{0}".format(TimeUtils.timestamp_to_filename_time(self.gather_timestamp))) + DirectoryUtil.mkdir(path=pack_dir_this_command, stdio=self.stdio) + self.stdio.verbose("Use {0} as pack dir.".format(pack_dir_this_command)) gather_tuples = [] - gather_pack_path_dict = {} def handle_from_node(node): st = time.time() - resp = self.__handle_from_node(args, node, pack_dir_this_command) + resp = self.__handle_from_node(node, pack_dir_this_command) file_size = "" if len(resp["error"]) == 0: file_size = os.path.getsize(resp["gather_pack_path"]) - gather_tuples.append((node.get("ip"), False, resp["error"], - file_size, - resp["zip_password"], - int(time.time() - st), - resp["gather_pack_path"])) + gather_tuples.append((node.get("ip"), False, resp["error"], file_size, resp["zip_password"], int(time.time() - st), resp["gather_pack_path"])) - if self.is_ssh: for node in self.nodes: handle_from_node(node) else: - local_ip = get_localhost_inner_ip() + local_ip = NetUtils.get_inner_ip() node = self.nodes[0] node["ip"] = local_ip for node in self.nodes: handle_from_node(node) summary_tuples = self.__get_overall_summary(gather_tuples, self.zip_encrypt) - print(summary_tuples) - display_trace(uuid.uuid3(uuid.NAMESPACE_DNS, str(os.getpid()))) - write_result_append_to_file(os.path.join(pack_dir_this_command, "result_summary.txt"), summary_tuples) + self.stdio.print(summary_tuples) + self.pack_dir_this_command=pack_dir_this_command + FileUtil.write_append(os.path.join(pack_dir_this_command, "result_summary.txt"), summary_tuples) last_info = "For result details, please run cmd \033[32m' cat {0} '\033[0m\n".format(os.path.join(pack_dir_this_command, "result_summary.txt")) - print(last_info) - return gather_tuples, gather_pack_path_dict + return True - def __handle_from_node(self, args, node, pack_dir_this_command): + def __handle_from_node(self, node, pack_dir_this_command): resp = { "skip": False, "error": "", "zip_password": "", "gather_pack_path": "" } - remote_ip = node.get("ip") if self.is_ssh else get_localhost_inner_ip() - remote_user = node.get("user") - remote_password = node.get("password") - remote_port = node.get("port") - remote_private_key = node.get("private_key") + remote_ip = node.get("ip") if self.is_ssh else NetUtils.get_inner_ip() + remote_user = node.get("ssh_username") + remote_password = node.get("ssh_password") + remote_port = node.get("ssh_port") + remote_private_key = node.get("ssh_key_file") remote_home_path = node.get("home_path") ssh_failed = False - logger.info( - "Sending Collect Shell Command to node {0} ...".format(remote_ip)) - mkdir_if_not_exist(pack_dir_this_command) + self.stdio.verbose("Sending Collect Shell Command to node {0} ...".format(remote_ip)) + DirectoryUtil.mkdir(path=pack_dir_this_command, stdio=self.stdio) try: - ssh = SshHelper(self.is_ssh, remote_ip, remote_user, remote_password, remote_port, remote_private_key,node) + ssh = SshHelper(self.is_ssh, remote_ip, remote_user, remote_password, remote_port, remote_private_key,node, self.stdio) except Exception as e: - logger.error("ssh {0}@{1}: failed, Please check the {2}".format( + self.stdio.exception("ssh {0}@{1}: failed, Please check the {2}".format( remote_user, remote_ip, self.config_path)) @@ -131,24 +203,24 @@ def __handle_from_node(self, args, node, pack_dir_this_command): resp["skip"] = True resp["error"] = "Please check the {0}".format(self.config_path) if not ssh_failed: - from_datetime_timestamp = timestamp_to_filename_time(datetime_to_timestamp(self.from_time_str)) - to_datetime_timestamp = timestamp_to_filename_time(datetime_to_timestamp(self.to_time_str)) + from_datetime_timestamp = TimeUtils.timestamp_to_filename_time(TimeUtils.datetime_to_timestamp(self.from_time_str)) + to_datetime_timestamp = TimeUtils.timestamp_to_filename_time(TimeUtils.datetime_to_timestamp(self.to_time_str)) gather_dir_name = "obproxy_log_{0}_{1}_{2}".format(ssh.host_ip, from_datetime_timestamp, to_datetime_timestamp) gather_dir_full_path = "{0}/{1}".format("/tmp", gather_dir_name) - mkdir(self.is_ssh, ssh, gather_dir_full_path) + mkdir(self.is_ssh, ssh, gather_dir_full_path, self.stdio) log_list, resp = self.__handle_log_list(ssh, node, resp) if resp["skip"]: return resp for log_name in log_list: self.__pharse_log(ssh_helper=ssh, log_name=log_name, home_path=remote_home_path, gather_path=gather_dir_full_path) - delete_empty_file(self.is_ssh, ssh, gather_dir_full_path) + delete_empty_file(self.is_ssh, ssh, gather_dir_full_path, self.stdio) - is_empty = is_empty_dir(self.is_ssh, ssh, gather_dir_full_path) + is_empty = is_empty_dir(self.is_ssh, ssh, gather_dir_full_path, self.stdio) if is_empty: resp["error"] = "Empty file" resp["zip_password"] = "" - rm_rf_file(self.is_ssh, ssh, gather_dir_full_path) + rm_rf_file(self.is_ssh, ssh, gather_dir_full_path, self.stdio) else: self.__handle_zip_file(remote_ip, ssh, resp, gather_dir_name, pack_dir_this_command) ssh.ssh_close() @@ -158,15 +230,13 @@ def __handle_log_list(self, ssh, node, resp): log_list = self.__get_log_name(ssh, node) ip = node.get("ip") if len(log_list) > self.file_number_limit: - logger.warn( - "{0} The number of log files is {1}, out of range (0,{2}], " + self.stdio.warn("{0} The number of log files is {1}, out of range (0,{2}], " "Please adjust the query limit".format(ip, len(log_list), self.file_number_limit)) resp["skip"] = True, resp["error"] = "Too many files {0} > {1}".format(len(log_list), self.file_number_limit) return log_list, resp elif len(log_list) <= 0: - logger.warn( - "{0} The number of log files is {1}, The time range for file gather from {2} to {3}, and no eligible files were found" + self.stdio.warn("{0} The number of log files is {1}, The time range for file gather from {2} to {3}, and no eligible files were found" "Please adjust the query time limit".format(ip, len(log_list), self.from_time_str, self.to_time_str)) resp["skip"] = True, resp["error"] = "No files found" @@ -177,20 +247,19 @@ def __get_log_name(self, ssh_helper, node): home_path = node.get("home_path") log_path = os.path.join(home_path, "log") if self.scope == "obproxy" or self.scope == "obproxy_stat" or self.scope == "obproxy_digest" or \ - self.scope == "obproxy_limit" or self.scope == "obproxy_slow": + self.scope == "obproxy_limit" or self.scope == "obproxy_slow" or self.scope == "obproxy_diagnosis" or self.scope == "obproxy_error": get_obproxy_log = "ls -1 -F %s/*%s.*log* | awk -F '/' '{print $NF}'" % (log_path, self.scope) else: - get_obproxy_log = "ls -1 -F %s/obproxy.*log* %s/obproxy_stat.*log* %s/obproxy_digest.*log* %s/obproxy_limit.*log* %s/obproxy_slow.*log* | awk -F '/' '{print $NF}'" % \ - (log_path, log_path, log_path, log_path, log_path) + get_obproxy_log = "ls -1 -F %s/obproxy.*log* %s/obproxy_error.*log* %s/obproxy_stat.*log* %s/obproxy_digest.*log* %s/obproxy_limit.*log* %s/obproxy_slow.*log* | awk -F '/' '{print $NF}'" % (log_path, log_path, log_path, log_path, log_path, log_path) if self.is_ssh: - log_files = SshClient().run(ssh_helper, get_obproxy_log) + log_files = SshClient(self.stdio).run(ssh_helper, get_obproxy_log).strip() else: - log_files = LocalClient().run(get_obproxy_log) + log_files = LocalClient(self.stdio).run(get_obproxy_log).strip() log_name_list = [] if log_files: - log_name_list = get_logfile_name_list(self.is_ssh, ssh_helper, self.from_time_str, self.to_time_str, log_path, log_files) + log_name_list = get_logfile_name_list(self.is_ssh, ssh_helper, self.from_time_str, self.to_time_str, log_path, log_files, self.stdio) else: - logger.error("Unable to find the log file. Please provide the correct home_path config and check obproxy {0} log exist".format(log_path)) + self.stdio.error("Unable to find the log file. Please provide the correct home_path config and check obproxy {0} log exist".format(log_path)) return log_name_list def __pharse_log(self, ssh_helper, home_path, log_name, gather_path): @@ -201,97 +270,65 @@ def __pharse_log(self, ssh_helper, home_path, log_name, gather_path): """ log_path = os.path.join(home_path, "log") if self.grep_args is not None: - grep_cmd = "grep -e '{grep_args}' {log_dir}/{log_name} >> {gather_path}/{log_name} ".format( - grep_args=self.grep_args, - gather_path=gather_path, - log_name=log_name, - log_dir=log_path) - logger.info("grep files, run cmd = [{0}]".format(grep_cmd)) - SshClient().run(ssh_helper, grep_cmd) if self.is_ssh else LocalClient().run(grep_cmd) + grep_cmd="" + if type(self.grep_args) == str: + grep_cmd = "grep -e '{grep_args}' {log_dir}/{log_name} >> {gather_path}/{log_name} ".format( + grep_args=self.grep_args, + gather_path=gather_path, + log_name=log_name, + log_dir=log_path) + elif type(self.grep_args) == list and len(self.grep_args)>0: + grep_litter_cmd="" + for grep_arg in self.grep_args: + if type(grep_arg)!=str: + self.stdio.error('The grep args must be string or list of strings, but got {0}'.format(type(grep_arg))) + raise Exception('The grep args must be string or list of strings, but got {0}'.format(type(grep_arg))) + elif grep_arg == "": + self.stdio.warn('The grep args must be string or list of strings, but got ""') + continue + grep_litter_cmd += "| grep -e '{0}'".format(grep_arg) + + grep_cmd = "cat {log_dir}/{log_name} {grep_args} >> {gather_path}/{log_name} ".format( + grep_args=grep_litter_cmd, + gather_path=gather_path, + log_name=log_name, + log_dir=log_path) + self.stdio.verbose("grep files, run cmd = [{0}]".format(grep_cmd)) + SshClient(self.stdio).run(ssh_helper, grep_cmd) if self.is_ssh else LocalClient(self.stdio).run(grep_cmd) else: cp_cmd = "cp {log_dir}/{log_name} {gather_path}/{log_name} ".format( gather_path=gather_path, log_name=log_name, log_dir=log_path) - logger.info("copy files, run cmd = [{0}]".format(cp_cmd)) - SshClient().run(ssh_helper, cp_cmd) if self.is_ssh else LocalClient().run(cp_cmd) + self.stdio.verbose("copy files, run cmd = [{0}]".format(cp_cmd)) + SshClient(self.stdio).run(ssh_helper, cp_cmd) if self.is_ssh else LocalClient(self.stdio).run(cp_cmd) def __handle_zip_file(self, ip, ssh, resp, gather_dir_name, pack_dir_this_command): zip_password = "" gather_dir_full_path = "{0}/{1}".format(self.gather_log_temporary_dir, gather_dir_name) if self.zip_encrypt: - zip_password = gen_password(16) - zip_encrypt_dir(self.is_ssh, ssh, zip_password, self.gather_log_temporary_dir, gather_dir_name) + zip_password = Util.gen_password(16) + zip_encrypt_dir(self.is_ssh, ssh, zip_password, self.gather_log_temporary_dir, gather_dir_name, self.stdio) else: - zip_dir(self.is_ssh, ssh, self.gather_log_temporary_dir, gather_dir_name) + zip_dir(self.is_ssh, ssh, self.gather_log_temporary_dir, gather_dir_name, self.stdio) gather_package_dir = "{0}.zip".format(gather_dir_full_path) - gather_log_file_size = get_file_size(self.is_ssh, ssh, gather_package_dir) - print(show_file_size_tabulate(ip, gather_log_file_size)) + gather_log_file_size = get_file_size(self.is_ssh, ssh, gather_package_dir, self.stdio) + self.stdio.print(FileUtil.show_file_size_tabulate(ip, gather_log_file_size)) local_path = "" if int(gather_log_file_size) < self.file_size_limit: local_store_path = pack_dir_this_command + "/{0}.zip".format(gather_dir_name) - local_path = download_file(self.is_ssh, ssh, gather_package_dir, local_store_path) + local_path = download_file(self.is_ssh, ssh, gather_package_dir, local_store_path, self.stdio) resp["error"] = "" resp["zip_password"] = zip_password else: resp["error"] = "File too large" resp["zip_password"] = "" - rm_rf_file(self.is_ssh, ssh, gather_package_dir) + rm_rf_file(self.is_ssh, ssh, gather_package_dir, self.stdio) resp["gather_pack_path"] = local_path - - logger.debug( - "Collect pack gathered from node {0}: stored in {1}".format(ip, gather_package_dir)) + self.stdio.verbose("Collect pack gathered from node {0}: stored in {1}".format(ip, gather_package_dir)) return resp - def __check_valid_and_parse_args(self, args): - """ - chech whether command args are valid. If invalid, stop processing and print the error to the user - :param args: command args - :return: boolean. True if valid, False if invalid. - """ - # 1: to timestamp must be larger than from timestamp, and be valid - if getattr(args, "from") is not None and getattr(args, "to") is not None: - try: - from_timestamp = parse_time_str(getattr(args, "from")) - to_timestamp = parse_time_str(getattr(args, "to")) - self.from_time_str = getattr(args, "from") - self.to_time_str = getattr(args, "to") - except OBDIAGFormatException: - logger.error("Error: Datetime is invalid. Must be in format yyyy-mm-dd hh:mm:ss. " \ - "from_datetime={0}, to_datetime={1}".format(getattr(args, "from"), getattr(args, "to"))) - return False - if to_timestamp <= from_timestamp: - logger.error("Error: from datetime is larger than to datetime, please check.") - return False - elif (getattr(args, "from") is None or getattr(args, "to") is None) and args.since is not None: - now_time = datetime.datetime.now() - self.to_time_str = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') - self.from_time_str = (now_time - datetime.timedelta( - seconds=parse_time_length_to_sec(args.since))).strftime('%Y-%m-%d %H:%M:%S') - else: - now_time = datetime.datetime.now() - self.to_time_str = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') - if args.since is not None: - self.from_time_str = (now_time - datetime.timedelta( - seconds=parse_time_length_to_sec(args.since))).strftime('%Y-%m-%d %H:%M:%S') - else: - self.from_time_str = (now_time - datetime.timedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') - # 2: store_dir must exist, else create directory. - if getattr(args, "store_dir") is not None: - if not os.path.exists(os.path.abspath(getattr(args, "store_dir"))): - logger.warn("Error: args --store_dir [{0}] incorrect: No such directory, Now create it".format(os.path.abspath(getattr(args, "store_dir")))) - os.makedirs(os.path.abspath(getattr(args, "store_dir"))) - self.gather_pack_dir = os.path.abspath(getattr(args, "store_dir")) - - if getattr(args, "grep") is not None: - self.grep_args = ' '.join(getattr(args, "grep")) - if getattr(args, "scope") is not None: - self.scope = getattr(args, "scope")[0] - if getattr(args, "encrypt")[0] == "true": - self.zip_encrypt = True - return True - @staticmethod def __get_overall_summary(node_summary_tuple, is_zip_encrypt): """ @@ -312,9 +349,9 @@ def __get_overall_summary(node_summary_tuple, is_zip_encrypt): consume_time = tup[5] pack_path = tup[6] try: - format_file_size = size_format(file_size, output_str=True) + format_file_size = FileUtil.size_format(num=file_size, output_str=True) except: - format_file_size = size_format(0, output_str=True) + format_file_size = FileUtil.size_format(num=0, output_str=True) if is_zip_encrypt: summary_tab.append((node, "Error:" + tup[2] if is_err else "Completed", format_file_size, tup[4], "{0} s".format(int(consume_time)), pack_path)) diff --git a/handler/gather/gather_obstack2.py b/handler/gather/gather_obstack2.py index 7be4c863..a8804ad7 100644 --- a/handler/gather/gather_obstack2.py +++ b/handler/gather/gather_obstack2.py @@ -16,55 +16,83 @@ @desc: """ import os -import re import sys -import threading import time import datetime import tabulate -import uuid from common.command import download_file, is_empty_dir, is_support_arch, get_observer_version, get_observer_pid, mkdir, zip_dir, get_file_size, delete_file_force, is_empty_file, upload_file -from common.logger import logger -from common.obdiag_exception import OBDIAGInvalidArgs from common.constant import const from common.command import LocalClient, SshClient from handler.base_shell_handler import BaseShellHandler -from utils.version_utils import compare_versions_greater -from utils.retry_utils import retry -from utils.file_utils import mkdir_if_not_exist, size_format, write_result_append_to_file, parse_size -from utils.shell_utils import SshHelper -from utils.time_utils import timestamp_to_filename_time -from utils.utils import get_localhost_inner_ip, display_trace +from common.tool import Util +from common.ssh import SshHelper +from common.tool import TimeUtils +from common.tool import Util +from common.tool import DirectoryUtil +from common.tool import FileUtil +from common.tool import NetUtils +from common.tool import StringUtils class GatherObstack2Handler(BaseShellHandler): - def __init__(self, nodes, gather_pack_dir, gather_timestamp=None, common_config=None, is_scene=False): - super(GatherObstack2Handler, self).__init__(nodes) + def __init__(self, context, gather_pack_dir='./', is_scene=False): + super(GatherObstack2Handler, self).__init__() + self.context = context + self.stdio = context.stdio self.is_ssh = True - self.gather_timestamp = gather_timestamp self.local_stored_path = gather_pack_dir self.remote_stored_path = None self.is_scene = is_scene self.config_path = const.DEFAULT_CONFIG_PATH - if common_config is None: + if self.context.get_variable("gather_timestamp", None) : + self.gather_timestamp=self.context.get_variable("gather_timestamp") + else: + self.gather_timestamp = TimeUtils.get_current_us_timestamp() + + def init_config(self): + self.nodes = self.context.cluster_config['servers'] + new_nodes = Util.get_nodes_list(self.context, self.nodes, self.stdio) + if new_nodes: + self.nodes = new_nodes + self.inner_config = self.context.inner_config + if self.inner_config is None: + self.file_number_limit = 20 self.file_size_limit = 2 * 1024 * 1024 * 1024 else: - self.file_size_limit = int(parse_size(common_config["file_size_limit"])) + basic_config = self.inner_config['obdiag']['basic'] + self.file_number_limit = int(basic_config["file_number_limit"]) + self.file_size_limit = int(FileUtil.size(basic_config["file_size_limit"])) + self.config_path = basic_config['config_path'] + return True - def handle(self, args): - if not self.__check_valid_args(args): - return + def init_option(self): + options = self.context.options + store_dir_option = Util.get_option(options, 'store_dir') + if store_dir_option and store_dir_option != './': + if not os.path.exists(os.path.abspath(store_dir_option)): + self.stdio.warn('warn: args --store_dir [{0}] incorrect: No such directory, Now create it'.format(os.path.abspath(store_dir_option))) + os.makedirs(os.path.abspath(store_dir_option)) + self.local_stored_path = os.path.abspath(store_dir_option) + return True + + def handle(self): + if not self.init_option(): + self.stdio.error('init option failed') + return False + if not self.init_config(): + self.stdio.error('init config failed') + return False if self.is_scene: pack_dir_this_command = self.local_stored_path else: - pack_dir_this_command = os.path.join(self.local_stored_path, "gather_pack_{0}".format(timestamp_to_filename_time(self.gather_timestamp))) - logger.info("Use {0} as pack dir.".format(pack_dir_this_command)) + pack_dir_this_command = os.path.join(self.local_stored_path, "gather_pack_{0}".format(TimeUtils.timestamp_to_filename_time(self.gather_timestamp))) + self.stdio.verbose("Use {0} as pack dir.".format(pack_dir_this_command)) gather_tuples = [] def handle_from_node(node): st = time.time() - resp = self.__handle_from_node(args, pack_dir_this_command, node) + resp = self.__handle_from_node(pack_dir_this_command, node) file_size = "" if len(resp["error"]) == 0: file_size = os.path.getsize(resp["gather_pack_path"]) @@ -77,43 +105,40 @@ def handle_from_node(node): for node in self.nodes: handle_from_node(node) else: - local_ip = get_localhost_inner_ip() + local_ip = NetUtils.get_inner_ip() node = self.nodes[0] node["ip"] = local_ip for node in self.nodes: handle_from_node(node) summary_tuples = self.__get_overall_summary(gather_tuples) - print(summary_tuples) - display_trace(uuid.uuid3(uuid.NAMESPACE_DNS, str(os.getpid()))) + self.stdio.print(summary_tuples) # Persist the summary results to a file - write_result_append_to_file(os.path.join(pack_dir_this_command, "result_summary.txt"), summary_tuples) + FileUtil.write_append(os.path.join(pack_dir_this_command, "result_summary.txt"), summary_tuples) last_info = "For result details, please run cmd \033[32m' cat {0} '\033[0m\n".format(os.path.join(pack_dir_this_command, "result_summary.txt")) - print(last_info) - def __handle_from_node(self, args, local_stored_path, node): + def __handle_from_node(self, local_stored_path, node): resp = { "skip": False, "error": "", "gather_pack_path": "" } - remote_ip = node.get("ip") if self.is_ssh else get_localhost_inner_ip() - remote_user = node.get("user") - remote_password = node.get("password") - remote_port = node.get("port") - remote_private_key = node.get("private_key") - remote_home_path = node.get("home_path") - logger.info( + remote_ip = node.get("ip") if self.is_ssh else NetUtils.get_inner_ip() + remote_user = node.get("ssh_username") + remote_password = node.get("ssh_password") + remote_port = node.get("ssh_port") + remote_private_key = node.get("ssh_key_file") + self.stdio.verbose( "Sending Collect Shell Command to node {0} ...".format(remote_ip)) - mkdir_if_not_exist(local_stored_path) + DirectoryUtil.mkdir(path=local_stored_path, stdio=self.stdio) now_time = datetime.datetime.now().strftime('%Y%m%d%H%M%S') remote_dir_name = "obstack2_{0}_{1}".format(remote_ip, now_time) remote_dir_full_path = "/tmp/{0}".format(remote_dir_name) ssh_failed = False try: - ssh_helper = SshHelper(self.is_ssh, remote_ip, remote_user, remote_password, remote_port, remote_private_key,node) + ssh_helper = SshHelper(self.is_ssh, remote_ip, remote_user, remote_password, remote_port, remote_private_key,node, self.stdio) except Exception as e: - logger.error("ssh {0}@{1}: failed, Please check the {2}".format( + self.stdio.exception("ssh {0}@{1}: failed, Please check the {2}".format( remote_user, remote_ip, self.config_path)) @@ -122,92 +147,93 @@ def __handle_from_node(self, args, local_stored_path, node): resp["error"] = "Please check the {0}".format(self.config_path) if not ssh_failed: - if not is_support_arch(self.is_ssh, ssh_helper): + if not is_support_arch(self.is_ssh, ssh_helper, self.stdio): resp["error"] = "remote server {0} arch not support gather obstack".format(ssh_helper.get_name()) return resp - mkdir(self.is_ssh, ssh_helper, remote_dir_full_path) + mkdir(self.is_ssh, ssh_helper, remote_dir_full_path, self.stdio) # install and chmod obstack2 - ob_version = get_observer_version(self.is_ssh, ssh_helper, node.get("home_path")) - if not compare_versions_greater(ob_version, const.MIN_OB_VERSION_SUPPORT_GATHER_OBSTACK): - logger.info("This version {0} does not support gather obstack . The minimum supported version is {1}". + ob_version = get_observer_version(self.is_ssh, ssh_helper, node.get("home_path"), self.stdio) + if not StringUtils.compare_versions_greater(ob_version, const.MIN_OB_VERSION_SUPPORT_GATHER_OBSTACK): + self.stdio.verbose("This version {0} does not support gather obstack . The minimum supported version is {1}". format(ob_version, const.MIN_OB_VERSION_SUPPORT_GATHER_OBSTACK)) resp["error"] = "{0} not support gather obstack".format(ob_version) resp["gather_pack_path"] = "{0}".format(local_stored_path) return resp is_need_install_obstack = self.__is_obstack_exists(self.is_ssh, ssh_helper) if is_need_install_obstack: - logger.info("There is no obstack2 on the host {0}. It needs to be installed. " + self.stdio.verbose("There is no obstack2 on the host {0}. It needs to be installed. " "Please wait a moment ...".format(remote_ip)) if getattr(sys, 'frozen', False): absPath = os.path.dirname(sys.executable) else: absPath = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) obstack2_local_stored_full_path = os.path.join(absPath, const.OBSTACK2_LOCAL_STORED_PATH) - upload_file(self.is_ssh, ssh_helper, obstack2_local_stored_full_path, const.OBSTACK2_DEFAULT_INSTALL_PATH) - logger.info("Installation of obstack2 is completed and gather begins ...") + upload_file(self.is_ssh, ssh_helper, obstack2_local_stored_full_path, const.OBSTACK2_DEFAULT_INSTALL_PATH, self.stdio) + self.stdio.verbose("Installation of obstack2 is completed and gather begins ...") self.__chmod_obstack2(self.is_ssh, ssh_helper) # get observer_pid - observer_pid_list = get_observer_pid(self.is_ssh, ssh_helper, node.get("home_path")) + observer_pid_list = get_observer_pid(self.is_ssh, ssh_helper, node.get("home_path"), self.stdio) # gather obstack2 info for observer_pid in observer_pid_list: user = self.__get_observer_execute_user(ssh_helper, observer_pid) self.__gather_obstack2_info(self.is_ssh, ssh_helper, user, observer_pid, remote_dir_name,node) try: + self.stdio.start_loading('gather obstack info') self.is_ready(ssh_helper, observer_pid, remote_dir_name) + self.stdio.stop_loading('gather obstack info sucess') except: - logger.error("Gather obstack info on the host {0} observer pid {1}".format(remote_ip, observer_pid)) + self.stdio.stop_loading('gather info failed') + self.stdio.error("Gather obstack info on the host {0} observer pid {1}".format(remote_ip, observer_pid)) delete_file_force(self.is_ssh, ssh_helper, "/tmp/{dir_name}/observer_{pid}_obstack.txt" - .format(dir_name=remote_dir_name, pid=observer_pid)) + .format(dir_name=remote_dir_name, pid=observer_pid), self.stdio) pass - if is_empty_dir(self.is_ssh, ssh_helper, "/tmp/{0}".format(remote_dir_name)): + if is_empty_dir(self.is_ssh, ssh_helper, "/tmp/{0}".format(remote_dir_name), self.stdio): resp["error"] = "gather failed, folder is empty" return resp - zip_dir(self.is_ssh, ssh_helper, "/tmp", remote_dir_name) + zip_dir(self.is_ssh, ssh_helper, "/tmp", remote_dir_name, self.stdio) remote_zip_file_path = "{0}.zip".format(remote_dir_full_path) - file_size = get_file_size(self.is_ssh, ssh_helper, remote_zip_file_path) + file_size = get_file_size(self.is_ssh, ssh_helper, remote_zip_file_path, self.stdio) remote_file_full_path = "{0}.zip".format(remote_dir_full_path) if int(file_size) < self.file_size_limit: local_file_path = "{0}/{1}.zip".format(local_stored_path, remote_dir_name) - download_file(self.is_ssh, ssh_helper, remote_file_full_path, local_file_path) + download_file(self.is_ssh, ssh_helper, remote_file_full_path, local_file_path, self.stdio) resp["error"] = "" else: resp["error"] = "File too large" - delete_file_force(self.is_ssh, ssh_helper, remote_file_full_path) + delete_file_force(self.is_ssh, ssh_helper, remote_file_full_path, self.stdio) ssh_helper.ssh_close() resp["gather_pack_path"] = "{0}/{1}.zip".format(local_stored_path, remote_dir_name) return resp - @retry(5, 2) + @Util.retry(5, 2) def is_ready(self, ssh_helper, pid, remote_dir_name): try: - logger.info("Check whether the directory /tmp/{dir_name} or " + self.stdio.verbose("Check whether the directory /tmp/{dir_name} or " "file /tmp/{dir_name}/observer_{pid}_obstack.txt is empty" .format(dir_name=remote_dir_name, pid=pid)) - is_empty_dir_res = is_empty_dir(self.is_ssh, ssh_helper, "/tmp/{0}".format(remote_dir_name)) + is_empty_dir_res = is_empty_dir(self.is_ssh, ssh_helper, "/tmp/{0}".format(remote_dir_name), self.stdio) is_empty_file_res = is_empty_file(self.is_ssh, ssh_helper, "/tmp/{dir_name}/observer_{pid}_obstack.txt" - .format(dir_name=remote_dir_name, pid=pid)) + .format(dir_name=remote_dir_name, pid=pid), self.stdio) if is_empty_dir_res or is_empty_file_res: - logger.info( + self.stdio.verbose( "The server {host_ip} directory /tmp/{dir_name} or file /tmp/{dir_name}/observer_{pid}_obstack.txt" " is empty, waiting for the collection to complete" - .format(host_ip=ssh_helper.get_name() if self.is_ssh else get_localhost_inner_ip(), dir_name=remote_dir_name, pid=pid)) + .format(host_ip=ssh_helper.get_name() if self.is_ssh else NetUtils.get_inner_ip(self.stdio), dir_name=remote_dir_name, pid=pid)) raise except Exception as e: raise e - @staticmethod - def __chmod_obstack2(is_ssh, ssh_helper): + def __chmod_obstack2(self, is_ssh, ssh_helper): cmd = "chmod a+x {file}".format(file=const.OBSTACK2_DEFAULT_INSTALL_PATH) - SshClient().run(ssh_helper, cmd) if is_ssh else LocalClient().run(cmd) + SshClient(self.stdio).run(ssh_helper, cmd) if is_ssh else LocalClient(self.stdio).run(cmd) - @staticmethod - def __is_obstack_exists(is_ssh, ssh_helper): + def __is_obstack_exists(self, is_ssh, ssh_helper): cmd = "test -e {file} && echo exists".format(file=const.OBSTACK2_DEFAULT_INSTALL_PATH) - stdout = SshClient().run(ssh_helper, cmd) if is_ssh else LocalClient().run(cmd) + stdout = SshClient(self.stdio).run(ssh_helper, cmd) if is_ssh else LocalClient(self.stdio).run(cmd) if stdout == 'exists': return False else: @@ -215,43 +241,28 @@ def __is_obstack_exists(is_ssh, ssh_helper): def __get_observer_execute_user(self, ssh_helper, pid): cmd = "ps -o ruser=userForLongName -e -o pid,ppid,c,stime,tty,time,cmd | grep observer | grep {0} | awk {1}".format(pid, "'{print $1}'") - stdout = SshClient().run(ssh_helper, cmd) if self.is_ssh else LocalClient().run(cmd) + stdout = SshClient(self.stdio).run(ssh_helper, cmd) if self.is_ssh else LocalClient(self.stdio).run(cmd) user = stdout.splitlines()[0] - logger.info("get observer execute user, run cmd = [{0}], result:{1} ".format(cmd, user)) + self.stdio.verbose("get observer execute user, run cmd = [{0}], result:{1} ".format(cmd, user)) return user - @staticmethod - def __gather_obstack2_info(is_ssh, ssh_helper, user, observer_pid, remote_gather_dir,node): + def __gather_obstack2_info(self, is_ssh, ssh_helper, user, observer_pid, remote_gather_dir,node): cmd = "{obstack} {pid} > /tmp/{gather_dir}/observer_{pid}_obstack.txt".format( obstack=const.OBSTACK2_DEFAULT_INSTALL_PATH, pid=observer_pid, gather_dir=remote_gather_dir) if is_ssh: if user == ssh_helper.username: - logger.debug("gather obstack info on server {0}, run cmd = [{1}]".format(ssh_helper.get_name(), cmd)) - SshClient().run_ignore_err(ssh_helper, cmd) + self.stdio.verbose("gather obstack info on server {0}, run cmd = [{1}]".format(ssh_helper.get_name(), cmd)) + SshClient(self.stdio).run_ignore_err(ssh_helper, cmd) else: ssh_helper_new = SshHelper(ssh_helper.host_ip, ssh_helper.username, ssh_helper.password, ssh_helper.ssh_port, ssh_helper.key_file,node) chown_cmd = "chown {user} /tmp/{gather_dir}/".format(user=user,gather_dir=remote_gather_dir) - SshClient().run(ssh_helper_new, chown_cmd) - logger.info("gather obstack info on server {0}, run cmd = [su {1}, {2}]".format(ssh_helper.get_name(), user, cmd)) + SshClient(self.stdio).run(ssh_helper_new, chown_cmd) + self.stdio.verbose("gather obstack info on server {0}, run cmd = [su {1}, {2}]".format(ssh_helper.get_name(), user, cmd)) ssh_helper_new.ssh_invoke_shell_switch_user(user, cmd, 10) else: - LocalClient().run(cmd) - - def __check_valid_args(self, args): - """ - chech whether command args are valid. If invalid, stop processing and print the error to the user - :param args: command args - :return: boolean. True if valid, False if invalid. - """ - # 1: store_dir must exist, else create directory. - if getattr(args, "store_dir") is not None: - if not os.path.exists(os.path.abspath(getattr(args, "store_dir"))): - logger.warn("Error: args --store_dir [{0}] incorrect: No such directory, Now create it".format(os.path.abspath(getattr(args, "store_dir")))) - os.makedirs(os.path.abspath(getattr(args, "store_dir"))) - self.local_stored_path = os.path.abspath(getattr(args, "store_dir")) - return True + LocalClient(self.stdio).run(cmd) @staticmethod def __get_overall_summary(node_summary_tuple): @@ -264,9 +275,9 @@ def __get_overall_summary(node_summary_tuple): consume_time = tup[4] pack_path = tup[5] try: - format_file_size = size_format(file_size, output_str=True) + format_file_size = FileUtil.size_format(num=file_size, output_str=True) except: - format_file_size = size_format(0, output_str=True) + format_file_size = FileUtil.size_format(num=0, output_str=True) summary_tab.append((node, "Error:" + tup[2] if is_err else "Completed", format_file_size, "{0} s".format(int(consume_time)), pack_path)) return "\nGather Ob stack Summary:\n" + \ diff --git a/handler/gather/gather_perf.py b/handler/gather/gather_perf.py index 4db8604c..79e7a996 100644 --- a/handler/gather/gather_perf.py +++ b/handler/gather/gather_perf.py @@ -16,48 +16,79 @@ @desc: """ import os -import threading import time import datetime import tabulate -import uuid from common.command import get_observer_pid, mkdir, zip_dir, get_file_size, download_file, delete_file_force -from common.logger import logger from common.command import LocalClient, SshClient from common.constant import const from handler.base_shell_handler import BaseShellHandler -from utils.file_utils import mkdir_if_not_exist, size_format, write_result_append_to_file, parse_size -from utils.shell_utils import SshHelper -from utils.time_utils import timestamp_to_filename_time -from utils.utils import get_localhost_inner_ip, display_trace +from common.ssh import SshHelper +from common.tool import Util +from common.tool import DirectoryUtil +from common.tool import FileUtil +from common.tool import NetUtils +from common.tool import TimeUtils class GatherPerfHandler(BaseShellHandler): - def __init__(self, nodes, gather_pack_dir, gather_timestamp=None, common_config=None, is_scene=False): - super(GatherPerfHandler, self).__init__(nodes) + def __init__(self, context, gather_pack_dir='./', is_scene=False): + super(GatherPerfHandler, self).__init__() + self.context = context + self.stdio = context.stdio self.is_ssh = True - self.gather_timestamp = gather_timestamp self.local_stored_path = gather_pack_dir self.remote_stored_path = None self.ob_install_dir = None self.is_scene = is_scene self.scope = "all" self.config_path = const.DEFAULT_CONFIG_PATH - if common_config is None: - self.file_size_limit = 2 * 1024 * 1024 + if self.context.get_variable("gather_timestamp", None) : + self.gather_timestamp=self.context.get_variable("gather_timestamp") else: - self.file_size_limit = int(parse_size(common_config["file_size_limit"])) + self.gather_timestamp = TimeUtils.get_current_us_timestamp() + + def init_config(self): + self.nodes = self.context.cluster_config['servers'] + new_nodes = Util.get_nodes_list(self.context, self.nodes, self.stdio) + if new_nodes: + self.nodes = new_nodes + self.inner_config = self.context.inner_config + if self.inner_config is None: + self.file_number_limit = 20 + self.file_size_limit = 2 * 1024 * 1024 * 1024 + else: + basic_config = self.inner_config['obdiag']['basic'] + self.file_number_limit = int(basic_config["file_number_limit"]) + self.file_size_limit = int(FileUtil.size(basic_config["file_size_limit"])) + self.config_path = basic_config['config_path'] + return True - def handle(self, args): - if not self.__check_valid_args(args): - return + def init_option(self): + options = self.context.options + store_dir_option = Util.get_option(options, 'store_dir') + if store_dir_option and store_dir_option != './': + if not os.path.exists(os.path.abspath(store_dir_option)): + self.stdio.warn('warn: args --store_dir [{0}] incorrect: No such directory, Now create it'.format(os.path.abspath(store_dir_option))) + os.makedirs(os.path.abspath(store_dir_option)) + self.local_stored_path = os.path.abspath(store_dir_option) + self.scope_option = Util.get_option(options, 'scope') + return True + + def handle(self): + if not self.init_option(): + self.stdio.error('init option failed') + return False + if not self.init_config(): + self.stdio.error('init config failed') + return False if self.is_scene: pack_dir_this_command = self.local_stored_path else: - pack_dir_this_command = os.path.join(self.local_stored_path,"gather_pack_{0}".format(timestamp_to_filename_time(self.gather_timestamp))) - logger.info("Use {0} as pack dir.".format(pack_dir_this_command)) + pack_dir_this_command = os.path.join(self.local_stored_path,"gather_pack_{0}".format(TimeUtils.timestamp_to_filename_time(self.gather_timestamp))) + self.stdio.verbose("Use {0} as pack dir.".format(pack_dir_this_command)) gather_tuples = [] def handle_from_node(node): @@ -75,19 +106,17 @@ def handle_from_node(node): for node in self.nodes: handle_from_node(node) else: - local_ip = get_localhost_inner_ip() + local_ip = NetUtils.get_inner_ip(self.stdio) node = self.nodes[0] node["ip"] = local_ip for node in self.nodes: handle_from_node(node) summary_tuples = self.__get_overall_summary(gather_tuples) - print(summary_tuples) - display_trace(uuid.uuid3(uuid.NAMESPACE_DNS, str(os.getpid()))) + self.stdio.print(summary_tuples) # Persist the summary results to a file - write_result_append_to_file(os.path.join(pack_dir_this_command, "result_summary.txt"), summary_tuples) + FileUtil.write_append(os.path.join(pack_dir_this_command, "result_summary.txt"), summary_tuples) last_info = "For result details, please run cmd \033[32m' cat {0} '\033[0m\n".format(os.path.join(pack_dir_this_command, "result_summary.txt")) - print(last_info) def __handle_from_node(self, node, local_stored_path): resp = { @@ -95,24 +124,22 @@ def __handle_from_node(self, node, local_stored_path): "error": "", "gather_pack_path": "" } - remote_ip = node.get("ip") if self.is_ssh else get_localhost_inner_ip() - remote_user = node.get("user") - remote_password = node.get("password") - remote_port = node.get("port") - remote_private_key = node.get("private_key") - remote_home_path = node.get("home_path") - logger.info( - "Sending Collect Shell Command to node {0} ...".format(remote_ip)) - mkdir_if_not_exist(local_stored_path) + remote_ip = node.get("ip") if self.is_ssh else NetUtils.get_inner_ip(self.stdio) + remote_user = node.get("ssh_username") + remote_password = node.get("ssh_password") + remote_port = node.get("ssh_port") + remote_private_key = node.get("ssh_key_file") + self.stdio.verbose("Sending Collect Shell Command to node {0} ...".format(remote_ip)) + DirectoryUtil.mkdir(path=local_stored_path, stdio=self.stdio) now_time = datetime.datetime.now().strftime('%Y%m%d%H%M%S') remote_dir_name = "perf_{0}_{1}".format(node.get("ip"), now_time) remote_dir_full_path = "/tmp/{0}".format(remote_dir_name) ssh_failed = False try: ssh_helper = SshHelper(self.is_ssh, remote_ip, remote_user, remote_password, remote_port, - remote_private_key, node) + remote_private_key, node, self.stdio) except Exception as e: - logger.error("ssh {0}@{1}: failed, Please check the {2}".format( + self.stdio.exception("ssh {0}@{1}: failed, Please check the {2}".format( remote_user, remote_ip, self.config_path)) @@ -120,9 +147,9 @@ def __handle_from_node(self, node, local_stored_path): resp["skip"] = True resp["error"] = "Please check the {0}".format(self.config_path) if not ssh_failed: - mkdir(self.is_ssh, ssh_helper, remote_dir_full_path) + mkdir(self.is_ssh, ssh_helper, remote_dir_full_path, self.stdio) - pid_observer_list = get_observer_pid(self.is_ssh, ssh_helper, node.get("home_path")) + pid_observer_list = get_observer_pid(self.is_ssh, ssh_helper, node.get("home_path"), self.stdio) if len(pid_observer_list) == 0: resp["error"] = "can't find observer" return resp @@ -136,16 +163,16 @@ def __handle_from_node(self, node, local_stored_path): self.__gather_perf_flame(ssh_helper, remote_dir_full_path, pid_observer) self.__gather_top(ssh_helper, remote_dir_full_path, pid_observer) - zip_dir(self.is_ssh, ssh_helper, "/tmp", remote_dir_name) + zip_dir(self.is_ssh, ssh_helper, "/tmp", remote_dir_name, self.stdio) remote_file_full_path = "{0}.zip".format(remote_dir_full_path) - file_size = get_file_size(self.is_ssh, ssh_helper, remote_file_full_path) + file_size = get_file_size(self.is_ssh, ssh_helper, remote_file_full_path, self.stdio) if int(file_size) < self.file_size_limit: local_file_path = "{0}/{1}.zip".format(local_stored_path, remote_dir_name) - download_file(self.is_ssh,ssh_helper, remote_file_full_path, local_file_path) + download_file(self.is_ssh,ssh_helper, remote_file_full_path, local_file_path, self.stdio) resp["error"] = "" else: resp["error"] = "File too large" - delete_file_force(self.is_ssh, ssh_helper, remote_file_full_path) + delete_file_force(self.is_ssh, ssh_helper, remote_file_full_path, self.stdio) ssh_helper.ssh_close() resp["gather_pack_path"] = "{0}/{1}.zip".format(local_stored_path, remote_dir_name) return resp @@ -154,55 +181,38 @@ def __gather_perf_sample(self, ssh_helper, gather_path, pid_observer): try: cmd = "cd {gather_path} && perf record -o sample.data -e cycles -c 100000000 -p {pid} -g -- sleep 20".format( gather_path=gather_path, pid=pid_observer) - logger.info("gather perf sample, run cmd = [{0}]".format(cmd)) - SshClient().run_ignore_err(ssh_helper, cmd) if self.is_ssh else LocalClient().run(cmd) + self.stdio.verbose("gather perf sample, run cmd = [{0}]".format(cmd)) + SshClient(self.stdio).run_ignore_err(ssh_helper, cmd) if self.is_ssh else LocalClient(self.stdio).run(cmd) generate_data = "cd {gather_path} && perf script -i sample.data -F ip,sym -f > sample.viz".format( gather_path=gather_path) - logger.info("generate perf sample data, run cmd = [{0}]".format(generate_data)) - SshClient().run_ignore_err(ssh_helper, generate_data) if self.is_ssh else LocalClient().run(generate_data) + self.stdio.verbose("generate perf sample data, run cmd = [{0}]".format(generate_data)) + SshClient(self.stdio).run_ignore_err(ssh_helper, generate_data) if self.is_ssh else LocalClient(self.stdio).run(generate_data) except: - logger.error("generate perf sample data on server [{0}] failed".format(ssh_helper.get_name())) + self.stdio.error("generate perf sample data on server [{0}] failed".format(ssh_helper.get_name())) def __gather_perf_flame(self, ssh_helper, gather_path, pid_observer): try: perf_cmd = "cd {gather_path} && perf record -o flame.data -F 99 -p {pid} -g -- sleep 20".format( gather_path=gather_path, pid=pid_observer) - logger.info("gather perf, run cmd = [{0}]".format(perf_cmd)) - SshClient().run_ignore_err(ssh_helper, perf_cmd) if self.is_ssh else LocalClient().run(perf_cmd) + self.stdio.verbose("gather perf, run cmd = [{0}]".format(perf_cmd)) + SshClient(self.stdio).run_ignore_err(ssh_helper, perf_cmd) if self.is_ssh else LocalClient(self.stdio).run(perf_cmd) generate_data = "cd {gather_path} && perf script -i flame.data > flame.viz".format( gather_path=gather_path) - logger.info("generate perf data, run cmd = [{0}]".format(generate_data)) - SshClient().run_ignore_err(ssh_helper, generate_data) if self.is_ssh else LocalClient().run(generate_data) + self.stdio.verbose("generate perf data, run cmd = [{0}]".format(generate_data)) + SshClient(self.stdio).run_ignore_err(ssh_helper, generate_data) if self.is_ssh else LocalClient(self.stdio).run(generate_data) except: - logger.error("generate perf data on server [{0}] failed".format(ssh_helper.get_name())) + self.stdio.error("generate perf data on server [{0}] failed".format(ssh_helper.get_name())) def __gather_top(self, ssh_helper, gather_path, pid_observer): try: cmd = "cd {gather_path} && top -Hp {pid} -b -n 1 > top.txt".format( gather_path=gather_path, pid=pid_observer) - logger.info("gather top, run cmd = [{0}]".format(cmd)) - SshClient().run(ssh_helper, cmd) if self.is_ssh else LocalClient().run(cmd) + self.stdio.verbose("gather top, run cmd = [{0}]".format(cmd)) + SshClient(self.stdio).run(ssh_helper, cmd) if self.is_ssh else LocalClient(self.stdio).run(cmd) except: - logger.error("gather top on server failed [{0}]".format(ssh_helper.get_name())) - - - def __check_valid_args(self, args): - """ - chech whether command args are valid. If invalid, stop processing and print the error to the user - :param args: command args - :return: boolean. True if valid, False if invalid. - """ - # 1: store_dir must exist, else create directory. - if getattr(args, "store_dir") is not None: - if not os.path.exists(os.path.abspath(getattr(args, "store_dir"))): - logger.warn("Error: args --store_dir [{0}] incorrect: No such directory, Now create it".format(os.path.abspath(getattr(args, "store_dir")))) - os.makedirs(os.path.abspath(getattr(args, "store_dir"))) - self.local_stored_path = os.path.abspath(getattr(args, "store_dir")) - if getattr(args, "scope") is not None: - self.scope = getattr(args, "scope")[0] - return True + self.stdio.error("gather top on server failed [{0}]".format(ssh_helper.get_name())) @staticmethod def __get_overall_summary(node_summary_tuple): @@ -215,9 +225,9 @@ def __get_overall_summary(node_summary_tuple): consume_time = tup[4] pack_path = tup[5] try: - format_file_size = size_format(file_size, output_str=True) + format_file_size = FileUtil.size_format(num=file_size, output_str=True) except: - format_file_size = size_format(0, output_str=True) + format_file_size = FileUtil.size_format(num=0, output_str=True) summary_tab.append((node, "Error:" + tup[2] if is_err else "Completed", format_file_size, "{0} s".format(int(consume_time)), pack_path)) return "\nGather Perf Summary:\n" + \ diff --git a/handler/gather/gather_plan_monitor.py b/handler/gather/gather_plan_monitor.py index 1d631e2e..f26f25b2 100644 --- a/handler/gather/gather_plan_monitor.py +++ b/handler/gather/gather_plan_monitor.py @@ -21,39 +21,28 @@ import shutil import time from decimal import Decimal -import uuid import pymysql as mysql import tabulate from prettytable import from_db_cursor - -from common.logger import logger from common.ob_connector import OBConnector -from common.obdiag_exception import OBDIAGArgsNotFoundException -from handler.base_sql_handler import BaseSQLHandler from handler.meta.html_meta import GlobalHtmlMeta from handler.meta.sql_meta import GlobalSqlMeta -from utils.file_utils import mkdir_if_not_exist, write_result_append_to_file -from utils.time_utils import timestamp_to_filename_time -from utils.string_utils import parse_custom_env_string -from utils.utils import display_trace -from utils.string_utils import parse_mysql_cli_connection_string, validate_db_info - - -class GatherPlanMonitorHandler(BaseSQLHandler): - def __init__(self, ob_cluster, gather_pack_dir, gather_timestamp=None, db_conn={}, is_scene=False): - super(GatherPlanMonitorHandler, self).__init__() - self.ob_cluster = ob_cluster +from common.tool import Util +from common.tool import DirectoryUtil +from common.tool import StringUtils +from common.tool import FileUtil +from common.tool import TimeUtils + + +class GatherPlanMonitorHandler(object): + def __init__(self, context, gather_pack_dir='./', is_scene=False): + self.context = context + self.stdio = context.stdio + self.ob_cluster = None self.local_stored_path = gather_pack_dir - self.gather_timestamp = gather_timestamp - self.ob_cluster_name = ob_cluster.get("ob_cluster_name") self.tenant_mode = None self.sys_database = None self.database = None - self.ob_connector = OBConnector(ip=ob_cluster.get("db_host"), - port=ob_cluster.get("db_port"), - username=ob_cluster.get("tenant_sys").get("user"), - password=ob_cluster.get("tenant_sys").get("password"), - timeout=100) self.enable_dump_db = False self.trace_id = None self.env = {} @@ -63,36 +52,66 @@ def __init__(self, ob_cluster, gather_pack_dir, gather_timestamp=None, db_conn={ self.ob_major_version = None self.sql_audit_name = "gv$sql_audit" self.plan_explain_name = "gv$plan_cache_plan_explain" - self.db_conn = db_conn self.is_scene = is_scene - self.gather_pack_dir = gather_pack_dir + if self.context.get_variable("gather_timestamp", None) : + self.gather_timestamp=self.context.get_variable("gather_timestamp") + else: + self.gather_timestamp = TimeUtils.get_current_us_timestamp() + + def init_config(self): + ob_cluster = self.context.cluster_config + self.ob_connector = OBConnector(ip=ob_cluster.get("db_host"), port=ob_cluster.get("db_port"), username=ob_cluster.get("tenant_sys").get("user"), password=ob_cluster.get("tenant_sys").get("password"), stdio=self.stdio, timeout=100) + self.ob_cluster_name = ob_cluster.get("ob_cluster_name") + return True + + def init_option(self): + options = self.context.options + trace_id_option = Util.get_option(options, 'trace_id') + store_dir_option = Util.get_option(options, 'store_dir') + env_option = Util.get_option(options, 'env') + if self.context.get_variable("gather_plan_monitor_trace_id", None) : + trace_id_option=self.context.get_variable("gather_plan_monitor_trace_id") + if trace_id_option is not None: + self.trace_id = trace_id_option + else: + self.stdio.error("option --trace_id not found, please provide") + return False + if store_dir_option and store_dir_option != './': + if not os.path.exists(os.path.abspath(store_dir_option)): + self.stdio.warn('warn: option --store_dir [{0}] incorrect: No such directory, Now create it'.format(os.path.abspath(store_dir_option))) + os.makedirs(os.path.abspath(store_dir_option)) + self.local_stored_path = os.path.abspath(store_dir_option) + if env_option is not None: + self.__init_db_conn(env_option) + else: + self.db_connector = self.ob_connector + self.tenant_mode_detected() + return True def __init_db_connector(self): - self.db_connector = OBConnector(ip=self.db_conn.get("host"), port=self.db_conn.get("port"), username=self.db_conn.get("user"), password=self.db_conn.get("password"), timeout=100) + self.db_connector = OBConnector(ip=self.db_conn.get("host"), port=self.db_conn.get("port"), username=self.db_conn.get("user"), password=self.db_conn.get("password"), stdio=self.stdio, timeout=100) - def handle(self, args): - """ - the overall handler for the gather command - :param args: command args - :return: the summary should be displayed - """ - if not self.__check_valid_and_parse_args(args): - return + def handle(self): + if not self.init_config(): + self.stdio.error('init config failed') + return False + if not self.init_option(): + self.stdio.error('init option failed') + return False if self.is_scene: - pack_dir_this_command = self.gather_pack_dir + pack_dir_this_command = self.local_stored_path else: pack_dir_this_command = os.path.join(self.local_stored_path, "gather_pack_{0}".format( - timestamp_to_filename_time(self.gather_timestamp))) + TimeUtils.timestamp_to_filename_time(self.gather_timestamp))) self.report_file_path = os.path.join(pack_dir_this_command, "sql_plan_monitor_report.html") - logger.info("Use {0} as pack dir.".format(pack_dir_this_command)) - mkdir_if_not_exist(pack_dir_this_command) + self.stdio.verbose("Use {0} as pack dir.".format(pack_dir_this_command)) + DirectoryUtil.mkdir(path=pack_dir_this_command, stdio=self.stdio) gather_tuples = [] gather_pack_path_dict = {} - def handle_plan_monitor_from_ob(cluster_name, args): + def handle_plan_monitor_from_ob(cluster_name): """ handler sql plan monitor from ob - :param args: cluster_name, command args :return: """ st = time.time() @@ -108,13 +127,13 @@ def handle_plan_monitor_from_ob(cluster_name, args): tenant_id = trace[10] svr_ip = trace[12] svr_port = trace[13] - logger.info("TraceID : %s " % trace_id) - logger.info("SQL : %s " % sql) - logger.info("SVR_IP : %s " % svr_ip) - logger.info("SVR_PORT : %s " % svr_port) - logger.info("DB: %s " % db_name) - logger.info("PLAN_ID: %s " % plan_id) - logger.info("TENANT_ID: %s " % tenant_id) + self.stdio.verbose("TraceID : %s " % trace_id) + self.stdio.verbose("SQL : %s " % sql) + self.stdio.verbose("SVR_IP : %s " % svr_ip) + self.stdio.verbose("SVR_PORT : %s " % svr_port) + self.stdio.verbose("DB: %s " % db_name) + self.stdio.verbose("PLAN_ID: %s " % plan_id) + self.stdio.verbose("TENANT_ID: %s " % tenant_id) sql_plan_monitor_svr_agg_template = self.sql_plan_monitor_svr_agg_template_sql() sql_plan_monitor_svr_agg_v1 = str(sql_plan_monitor_svr_agg_template) \ @@ -137,36 +156,36 @@ def handle_plan_monitor_from_ob(cluster_name, args): plan_explain_sql = self.plan_explain_sql(tenant_id, plan_id, svr_ip, svr_port) # 输出报告头 - logger.info("[sql plan monitor report task] report header") + self.stdio.verbose("[sql plan monitor report task] report header") self.report_header() # 输出sql_audit的概要信息 - logger.info("[sql plan monitor report task] report sql_audit") + self.stdio.verbose("[sql plan monitor report task] report sql_audit") self.report_sql_audit() # 输出sql explain的信息 - logger.info("[sql plan monitor report task] report plan explain") + self.stdio.verbose("[sql plan monitor report task] report plan explain") self.report_plan_explain(db_name, sql) # 输出plan cache的信息 - logger.info("[sql plan monitor report task] report plan cache") + self.stdio.verbose("[sql plan monitor report task] report plan cache") self.report_plan_cache(plan_explain_sql) # 输出表结构的信息 - logger.info("[sql plan monitor report task] report table schema") + self.stdio.verbose("[sql plan monitor report task] report table schema") self.report_schema(user_sql) self.init_monitor_stat() # 输出sql_audit的详细信息 - logger.info("[sql plan monitor report task] report sql_audit details") + self.stdio.verbose("[sql plan monitor report task] report sql_audit details") self.report_sql_audit_details(full_audit_sql_by_trace_id_sql) # 输出算子信息 表+图 - logger.info("[sql plan monitor report task] report sql plan monitor dfo") + self.stdio.verbose("[sql plan monitor report task] report sql plan monitor dfo") self.report_sql_plan_monitor_dfo_op(sql_plan_monitor_dfo_op) # 输出算子信息按 svr 级汇总 表+图 - logger.info("[sql plan monitor report task] report sql plan monitor group by server") + self.stdio.verbose("[sql plan monitor report task] report sql plan monitor group by server") self.report_sql_plan_monitor_svr_agg(sql_plan_monitor_svr_agg_v1, sql_plan_monitor_svr_agg_v2) self.report_fast_preview() # 输出算子信息按算子维度聚集 - logger.info("[sql plan monitor report task] sql plan monitor detail operator") + self.stdio.verbose("[sql plan monitor report task] sql plan monitor detail operator") self.report_sql_plan_monitor_detail_operator_priority(sql_plan_monitor_detail_v1) # 输出算子信息按线程维度聚集 - logger.info("[sql plan monitor report task] sql plan monitor group by priority") + self.stdio.verbose("[sql plan monitor report task] sql plan monitor group by priority") self.reportsql_plan_monitor_detail_svr_priority(sql_plan_monitor_detail_v2) # 输出本报告在租户下使用的 SQL @@ -181,7 +200,7 @@ def handle_plan_monitor_from_ob(cluster_name, args): t = time.localtime(time.time()) self.__report("报告生成时间: %s" % (time.strftime("%Y-%m-%d %H:%M:%S", t))) self.report_footer() - logger.info("report footer complete") + self.stdio.verbose("report footer complete") if resp["skip"]: return @@ -197,61 +216,33 @@ def handle_plan_monitor_from_ob(cluster_name, args): else: absPath = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) cs_resources_path = os.path.join(absPath, "resources") - logger.info("[cs resource path] : {0}".format(cs_resources_path)) + self.stdio.verbose("[cs resource path] : {0}".format(cs_resources_path)) target_resources_path = os.path.join(pack_dir_this_command, "resources") self.copy_cs_resource(cs_resources_path, target_resources_path) - logger.info("[sql plan monitor report task] start") - handle_plan_monitor_from_ob(self.ob_cluster_name, args) - logger.info("[sql plan monitor report task] end") + self.stdio.verbose("[sql plan monitor report task] start") + handle_plan_monitor_from_ob(self.ob_cluster_name) + self.stdio.verbose("[sql plan monitor report task] end") summary_tuples = self.__get_overall_summary(gather_tuples) - print(summary_tuples) - display_trace(uuid.uuid3(uuid.NAMESPACE_DNS, str(os.getpid()))) + self.stdio.print(summary_tuples) # 将汇总结果持久化记录到文件中 - write_result_append_to_file(os.path.join(pack_dir_this_command, "result_summary.txt"), summary_tuples) + FileUtil.write_append(os.path.join(pack_dir_this_command, "result_summary.txt"), summary_tuples) return gather_tuples, gather_pack_path_dict - def __check_valid_and_parse_args(self, args): - """ - chech whether command args are valid. If invalid, stop processing and print the error to the user - :param args: command args - :return: boolean. True if valid, False if invalid. - """ - if getattr(args, "trace_id") is not None: - # 1: trace_id must be must be provided, if not be valid - try: - self.trace_id = getattr(args, "trace_id") - except OBDIAGArgsNotFoundException: - logger.error("Error: trace_id must be must be provided") - return False - else: - return False - # 2: store_dir must exist, else create directory. - if getattr(args, "store_dir") is not None: - if not os.path.exists(os.path.abspath(getattr(args, "store_dir"))): - logger.warn("Error: args --store_dir [{0}] incorrect: No such directory, Now create it".format(os.path.abspath(getattr(args, "store_dir")))) - os.makedirs(os.path.abspath(getattr(args, "store_dir"))) - self.local_stored_path = os.path.abspath(getattr(args, "store_dir")) - if getattr(args, "env") is not None: - self.__init_db_conn(args) - else: - self.db_connector = self.ob_connector - self.tenant_mode_detected() - return True - def __init_db_conn(self, args): + def __init_db_conn(self, env): try: - env_dict = parse_custom_env_string(getattr(args, "env")) + env_dict = StringUtils.parse_env(env) self.env = env_dict cli_connection_string = self.env.get("db_connect") - self.db_conn = parse_mysql_cli_connection_string(cli_connection_string) - if validate_db_info(self.db_conn): + self.db_conn = StringUtils.parse_mysql_conn(cli_connection_string) + if StringUtils.validate_db_info(self.db_conn): self.__init_db_connector() else: - logger.error("db connection information requird [db_connect = '-hxx -Pxx -uxx -pxx -Dxx'] but provided {0}, please check the --env {0}".format(env_dict)) + self.stdio.error("db connection information requird [db_connect = '-hxx -Pxx -uxx -pxx -Dxx'] but provided {0}, please check the --env {0}".format(env_dict)) self.db_connector = self.ob_connector except Exception as e: self.db_connector = self.ob_connector - logger.error("init db connector, error: {0}, please check --env args {1}".format(e, parse_custom_env_string(getattr(args, "env")))) + self.stdio.exception("init db connector, error: {0}, please check --env option ") @staticmethod def __get_overall_summary(node_summary_tuple): @@ -287,7 +278,7 @@ def report_schema(self, sql): try: data = self.db_connector.execute_sql("show create table %s" % t) schemas = schemas + "
%s
" % (data[1]) - logger.debug("table schema: {0}".format(schemas)) + self.stdio.verbose("table schema: {0}".format(schemas)) except Exception as e: pass cursor = self.ob_connector.execute_sql_return_cursor("show variables like '%parallel%'") @@ -308,8 +299,8 @@ def report_schema(self, sql): "

SCHEMA 信息

") cursor.close() except Exception as e: - logger.error("report table schema failed %s" % sql) - logger.error(repr(e)) + self.stdio.exception("report table schema failed %s" % sql) + self.stdio.exception(repr(e)) pass def report_pre(self, s): @@ -320,7 +311,7 @@ def report_header(self): header = GlobalHtmlMeta().get_value(key="sql_plan_monitor_report_header") with open(self.report_file_path, 'w') as f: f.write(header) - logger.info("report header complete") + self.stdio.verbose("report header complete") def init_monitor_stat(self): sql = "select ID,NAME,TYPE from " + ( @@ -328,7 +319,7 @@ def init_monitor_stat(self): data = self.ob_connector.execute_sql(sql) for item in data: self.STAT_NAME[item[0]] = {"type": item[2], "name": item[1]} - logger.info("init sql plan monitor stat complete") + self.stdio.verbose("init sql plan monitor stat complete") def otherstat_detail_explain_item(self, item, n, v): try: @@ -516,7 +507,7 @@ def report_svr_agg_graph_data(self, ident, cursor, title=''): item['PLAN_DEPTH']) data = data + "{start:0}];" data = data + "

%s

" % (title, ident) - logger.debug("report SQL_PLAN_MONITOR SQC operator priority start, DATA: %s", data) + self.stdio.verbose("report SQL_PLAN_MONITOR SQC operator priority start, DATA: %s", data) self.__report(data) def report_svr_agg_graph_data_obversion4(self, ident, cursor, title=''): @@ -534,7 +525,7 @@ def report_svr_agg_graph_data_obversion4(self, ident, cursor, title=''): item['PLAN_DEPTH'], skewness) data = data + "{start:0}];" data = data + "

%s

" % (title, ident) - logger.debug("report SQL_PLAN_MONITOR SQC operator priority start, DATA: %s", data) + self.stdio.verbose("report SQL_PLAN_MONITOR SQC operator priority start, DATA: %s", data) self.__report(data) def report_fast_preview(self): @@ -547,7 +538,7 @@ def report_fast_preview(self): ''' self.__report(content) - logger.info("report SQL_PLAN_MONITOR fast preview complete") + self.stdio.verbose("report SQL_PLAN_MONITOR fast preview complete") def report_footer(self): footer = GlobalHtmlMeta().get_value(key="sql_plan_monitor_report_footer") @@ -563,7 +554,7 @@ def tenant_mode_detected(self): ob_version = "3.0.0.0" for row in data: ob_version = row[1] - logger.info("Detected mySQL mode successful, Database version :{0} ".format(ob_version)) + self.stdio.verbose("Detected mySQL mode successful, Database version :{0} ".format(ob_version)) version_pattern = r'(?:OceanBase(_CE)?\s+)?(\d+\.\d+\.\d+\.\d+)' matched_version = re.search(version_pattern, ob_version) if matched_version: @@ -578,10 +569,10 @@ def tenant_mode_detected(self): self.tenant_mode = "mysql" self.sys_database = "oceanbase" else: - logger.warn("Failed to match ob version") + self.stdio.warn("Failed to match ob version") except: data = self.ob_connector.execute_sql("select SUBSTR(BANNER, 11, 100) from V$VERSION;") - logger.info("Detectedo oracle mode successful, Database version : %s " % ("%s" % data[0])) + self.stdio.verbose("Detectedo oracle mode successful, Database version : %s " % ("%s" % data[0])) version = ("%s" % data[0]) if int(version[0]) >= 4: self.sql_audit_name = "gv$ob_sql_audit" @@ -594,7 +585,6 @@ def tenant_mode_detected(self): def init_resp(self): """ the handler for one ob cluster - :param args: command args :param target_ob: the agent object :return: a resp dict, indicating the information of the response """ @@ -722,38 +712,38 @@ def report_sql_audit_details(self, sql): full_audit_sql_result = self.ob_connector.execute_sql_pretty(sql) self.__report( "

SQL_AUDIT 信息

") - logger.info("report full sql audit complete") + self.stdio.verbose("report full sql audit complete") # plan cache def report_plan_cache(self, sql): try: cursor_plan_explain = self.ob_connector.execute_sql_return_cursor(sql) - logger.info("select plan_explain from ob complete") + self.stdio.verbose("select plan_explain from ob complete") self.report_pre(sql) - logger.info("report plan_explain_sql complete") + self.stdio.verbose("report plan_explain_sql complete") data_plan_explain = from_db_cursor(cursor_plan_explain) data_plan_explain.align = 'l' self.report_pre(data_plan_explain) - logger.info("report plan_explain complete") + self.stdio.verbose("report plan_explain complete") except Exception as e: - logger.error("plan cache> %s" % sql) - logger.error(repr(e)) + self.stdio.exception("plan cache> %s" % sql) + self.stdio.exception(repr(e)) pass # sql_audit 概要 def report_sql_audit(self): sql = self.sql_audit_by_trace_id_limit1_sql() - logger.debug("select sql_audit from ob with SQL: %s", sql) + self.stdio.verbose("select sql_audit from ob with SQL: %s", sql) try: sql_audit_result = self.ob_connector.execute_sql_pretty(sql) - logger.debug("sql_audit_result: %s", sql_audit_result) - logger.info("report sql_audit_result to file start ...") + self.stdio.verbose("sql_audit_result: %s", sql_audit_result) + self.stdio.verbose("report sql_audit_result to file start ...") self.__report(sql_audit_result.get_html_string()) - logger.info("report sql_audit_result end") + self.stdio.verbose("report sql_audit_result end") except Exception as e: - logger.error("sql_audit> %s" % sql) - logger.error(repr(e)) + self.stdio.exception("sql_audit> %s" % sql) + self.stdio.exception(repr(e)) def report_plan_explain(self, db_name, raw_sql): explain_sql = "explain %s" % raw_sql @@ -761,60 +751,60 @@ def report_plan_explain(self, db_name, raw_sql): db = mysql.connect(host=self.ob_cluster["db_host"], port=int(self.ob_cluster["db_port"]), user=self.ob_cluster["tenant_sys"]["user"], passwd=self.ob_cluster["tenant_sys"]["password"], db=db_name) cursor = db.cursor() - logger.debug("execute SQL: %s", explain_sql) + self.stdio.verbose("execute SQL: %s", explain_sql) cursor.execute(explain_sql) sql_explain_result_sql = "%s" % explain_sql sql_explain_result = cursor.fetchone() # output explain result - logger.info("report sql_explain_result_sql complete") + self.stdio.verbose("report sql_explain_result_sql complete") self.report_pre(sql_explain_result_sql) - logger.info("report sql_explain_result_sql complete") + self.stdio.verbose("report sql_explain_result_sql complete") self.report_pre(sql_explain_result) - logger.info("report sql_explain_result complete") + self.stdio.verbose("report sql_explain_result complete") except Exception as e: - logger.error("plan explain> %s" % explain_sql) - logger.error(repr(e)) + self.stdio.exception("plan explain> %s" % explain_sql) + self.stdio.exception(repr(e)) pass def report_sql_plan_monitor_dfo_op(self, sql): data_sql_plan_monitor_dfo_op = self.ob_connector.execute_sql_pretty(sql) self.__report( "

SQL_PLAN_MONITOR DFO 级调度时序汇总

") - logger.info("report SQL_PLAN_MONITOR DFO complete") + self.stdio.verbose("report SQL_PLAN_MONITOR DFO complete") cursor_sql_plan_monitor_dfo_op = self.ob_connector.execute_sql_return_cursor_dictionary(sql) if self.ob_major_version >= 4: self.report_dfo_sched_agg_graph_data_obversion4(cursor_sql_plan_monitor_dfo_op, '调度时序图') else: self.report_dfo_sched_agg_graph_data(cursor_sql_plan_monitor_dfo_op, '调度时序图') - logger.info("report SQL_PLAN_MONITOR DFO SCHED complete") + self.stdio.verbose("report SQL_PLAN_MONITOR DFO SCHED complete") cursor_sql_plan_monitor_dfo_op = self.ob_connector.execute_sql_return_cursor_dictionary(sql) if self.ob_major_version >= 4: self.report_dfo_agg_graph_data_obversion4(cursor_sql_plan_monitor_dfo_op, '数据时序图') else: self.report_dfo_agg_graph_data(cursor_sql_plan_monitor_dfo_op, '数据时序图') - logger.info("report SQL_PLAN_MONITOR DFO graph data complete") + self.stdio.verbose("report SQL_PLAN_MONITOR DFO graph data complete") def report_sql_plan_monitor_svr_agg(self, sql_plan_monitor_svr_agg_v1, sql_plan_monitor_svr_agg_v2): cursor_sql_plan_monitor_svr_agg = self.ob_connector.execute_sql_return_cursor(sql_plan_monitor_svr_agg_v1) self.__report( "

SQL_PLAN_MONITOR SQC 级汇总

") - logger.info("report SQL_PLAN_MONITOR SQC complete") + self.stdio.verbose("report SQL_PLAN_MONITOR SQC complete") cursor_sql_plan_monitor_svr_agg_v1 = self.ob_connector.execute_sql_return_cursor_dictionary( sql_plan_monitor_svr_agg_v2) if self.ob_major_version >= 4: self.report_svr_agg_graph_data_obversion4('svr_agg_serial_v1', cursor_sql_plan_monitor_svr_agg_v1, '算子优先视图') else: self.report_svr_agg_graph_data('svr_agg_serial_v1', cursor_sql_plan_monitor_svr_agg_v1, '算子优先视图') - logger.info("report SQL_PLAN_MONITOR SQC operator priority complete") + self.stdio.verbose("report SQL_PLAN_MONITOR SQC operator priority complete") cursor_data_sql_plan_monitor_svr_agg_v2 = self.ob_connector.execute_sql_return_cursor_dictionary( sql_plan_monitor_svr_agg_v2) if self.ob_major_version >= 4: self.report_svr_agg_graph_data('svr_agg_serial_v2', cursor_data_sql_plan_monitor_svr_agg_v2, '机器优先视图') else: self.report_svr_agg_graph_data('svr_agg_serial_v2', cursor_data_sql_plan_monitor_svr_agg_v2, '机器优先视图') - logger.info("report SQL_PLAN_MONITOR SQC server priority complete") + self.stdio.verbose("report SQL_PLAN_MONITOR SQC server priority complete") def report_sql_plan_monitor_detail_operator_priority(self, sql): cursor_sql_plan_monitor_detail = self.ob_connector.execute_sql_return_cursor(sql) @@ -822,7 +812,7 @@ def report_sql_plan_monitor_detail_operator_priority(self, sql): "

SQL_PLAN_MONITOR 详情

") - logger.info("report SQL_PLAN_MONITOR details complete") + self.stdio.verbose("report SQL_PLAN_MONITOR details complete") cursor_sql_plan_monitor_detail_v1 = self.ob_connector.execute_sql_return_cursor_dictionary(sql) if self.ob_major_version >= 4: self.report_detail_graph_data_obversion4("detail_serial_v1", @@ -830,7 +820,7 @@ def report_sql_plan_monitor_detail_operator_priority(self, sql): '算子优先视图') else: self.report_detail_graph_data("detail_serial_v1", cursor_sql_plan_monitor_detail_v1, '算子优先视图') - logger.info("report SQL_PLAN_MONITOR details operator priority complete") + self.stdio.verbose("report SQL_PLAN_MONITOR details operator priority complete") def reportsql_plan_monitor_detail_svr_priority(self, sql): cursor_sql_plan_monitor_detail_v2 = self.ob_connector.execute_sql_return_cursor_dictionary(sql) @@ -840,4 +830,4 @@ def reportsql_plan_monitor_detail_svr_priority(self, sql): '线程优先视图') else: self.report_detail_graph_data("detail_serial_v2", cursor_sql_plan_monitor_detail_v2, '线程优先视图') - logger.info("report SQL_PLAN_MONITOR details server priority complete") + self.stdio.verbose("report SQL_PLAN_MONITOR details server priority complete") diff --git a/handler/gather/gather_scenes.py b/handler/gather/gather_scenes.py index a350502f..a0b7056b 100644 --- a/handler/gather/gather_scenes.py +++ b/handler/gather/gather_scenes.py @@ -18,46 +18,56 @@ import os import re -import uuid +from stdio import SafeStdio import datetime -from common.logger import logger from handler.gather.scenes.base import SceneBase -from utils.utils import display_trace from common.obdiag_exception import OBDIAGFormatException -from utils.time_utils import parse_time_str -from utils.time_utils import parse_time_length_to_sec -from utils.time_utils import timestamp_to_filename_time -from utils.utils import display_trace from handler.gather.scenes.list import GatherScenesListHandler -from utils.file_utils import mkdir_if_not_exist -from utils.string_utils import parse_custom_env_string +from common.tool import DirectoryUtil +from common.tool import StringUtils from common.scene import get_obproxy_and_ob_version from colorama import Fore, Style +from common.tool import Util +from common.tool import TimeUtils -class GatherSceneHandler: +class GatherSceneHandler(SafeStdio): - def __init__(self, obproxy_cluster, obproxy_nodes, ob_cluster, ob_nodes, gather_pack_dir, gather_timestamp, tasks_base_path="./handler/gather/tasks/", task_type="observer"): + def __init__(self, context, gather_pack_dir='./', tasks_base_path="~/.obdiag/gather/tasks/", task_type="observer"): + self.context = context + self.stdio = context.stdio self.is_ssh = True self.report = None - self.gather_timestamp = gather_timestamp self.gather_pack_dir = gather_pack_dir self.report_path = None self.yaml_tasks = {} self.code_tasks = [] self.env = {} self.scene = None - self.obproxy_cluster = obproxy_cluster - self.obproxy_nodes = obproxy_nodes - self.cluster = ob_cluster - self.ob_nodes = ob_nodes self.tasks_base_path = tasks_base_path self.task_type = task_type self.variables = {} + if self.context.get_variable("gather_timestamp", None) : + self.gather_timestamp=self.context.get_variable("gather_timestamp") + else: + self.gather_timestamp = TimeUtils.get_current_us_timestamp() + + def init_config(self): + self.cluster = self.context.cluster_config + self.obproxy_nodes = self.context.obproxy_config['servers'] + self.ob_nodes = self.context.cluster_config['servers'] + new_nodes = Util.get_nodes_list(self.context, self.ob_nodes, self.stdio) + if new_nodes: + self.nodes = new_nodes + return True - def handle(self, args): - if not self.__check_valid_and_parse_args(args): - return + def handle(self): + if not self.init_option(): + self.stdio.error('init option failed') + return False + if not self.init_config(): + self.stdio.error('init config failed') + return False self.__init_variables() self.__init_report_path() self.__init_task_names() @@ -66,51 +76,49 @@ def handle(self, args): def execute(self): try: - logger.info("execute_tasks. the number of tasks is {0} ,tasks is {1}".format(len(self.yaml_tasks.keys()), self.yaml_tasks.keys())) + self.stdio.verbose("execute_tasks. the number of tasks is {0} ,tasks is {1}".format(len(self.yaml_tasks.keys()), self.yaml_tasks.keys())) for key, value in zip(self.yaml_tasks.keys(), self.yaml_tasks.values()): self.__execute_yaml_task_one(key, value) for task in self.code_tasks: self.__execute_code_task_one(task) except Exception as e: - logger.error("Internal error :{0}".format(e)) - finally: - display_trace(uuid.uuid3(uuid.NAMESPACE_DNS, str(os.getpid()))) + self.stdio.error("Internal error :{0}".format(e)) # execute yaml task def __execute_yaml_task_one(self, task_name, task_data): try: - logger.info("execute tasks is {0}".format(task_name)) + self.stdio.print("execute tasks: {0}".format(task_name)) task_type = self.__get_task_type(task_name) - version = get_obproxy_and_ob_version(self.obproxy_nodes, self.ob_nodes, self.task_type) + version = get_obproxy_and_ob_version(self.obproxy_nodes, self.ob_nodes, self.task_type, self.stdio) if version: self.cluster["version"] = re.findall(r'\d+\.\d+\.\d+\.\d+', version)[0] - logger.info("cluster.version is {0}".format(self.cluster["version"])) - task = SceneBase(scene=task_data["task"], obproxy_nodes=self.obproxy_nodes, ob_nodes=self.ob_nodes, cluster=self.cluster, report_dir=self.report_path, args=self.args, env=self.env, scene_variable_dict=self.variables, task_type=task_type) - logger.info("{0} execute!".format(task_name)) + self.stdio.verbose("cluster.version is {0}".format(self.cluster["version"])) + task = SceneBase(context=self.context, scene=task_data["task"], report_dir=self.report_path, env=self.env, scene_variable_dict=self.variables, task_type=task_type) + self.stdio.verbose("{0} execute!".format(task_name)) task.execute() - logger.info("execute tasks end : {0}".format(task_name)) + self.stdio.verbose("execute tasks end : {0}".format(task_name)) else: - logger.error("can't get version") + self.stdio.error("can't get version") except Exception as e: - logger.error("__execute_yaml_task_one Exception : {0}".format(e)) + self.stdio.error("__execute_yaml_task_one Exception : {0}".format(e)) # execute code task def __execute_code_task_one(self, task_name): try: - logger.info("execute tasks is {0}".format(task_name)) + self.stdio.verbose("execute tasks is {0}".format(task_name)) scene = {"name": task_name} - task = SceneBase(scene=scene, obproxy_nodes=self.obproxy_nodes, ob_nodes=self.ob_nodes, cluster=self.cluster, report_dir=self.report_path, args=self.args, env=self.env, mode='code', task_type=task_name) - logger.info("{0} execute!".format(task_name)) + task = SceneBase(context=self.context, scene=scene, report_dir=self.report_path, env=self.env, mode='code', task_type=task_name) + self.stdio.verbose("{0} execute!".format(task_name)) task.execute() - logger.info("execute tasks end : {0}".format(task_name)) + self.stdio.verbose("execute tasks end : {0}".format(task_name)) except Exception as e: - logger.error("__execute_code_task_one Exception : {0}".format(e)) + self.stdio.error("__execute_code_task_one Exception : {0}".format(e)) def __init_task_names(self): if self.scene: new = re.sub(r'\{|\}', '', self.scene) items = re.split(r'[;,]', new) - scene = GatherScenesListHandler(self.tasks_base_path) + scene = GatherScenesListHandler(self.context) for item in items: yaml_task_data = scene.get_one_yaml_task(item) is_code_task = scene.is_code_task(item) @@ -120,30 +128,29 @@ def __init_task_names(self): if yaml_task_data: self.yaml_tasks[item] = yaml_task_data else: - logger.error("Invalid Task :{0}".format(item)) + self.stdio.error("Invalid Task :{0}".format(item)) else: - logger.error("get task name failed") + self.stdio.error("get task name failed") def __init_report_path(self): try: - self.report_path = os.path.join(self.gather_pack_dir, "gather_pack_{0}".format(timestamp_to_filename_time(self.gather_timestamp))) - logger.info("Use {0} as pack dir.".format(self.report_path)) - mkdir_if_not_exist(self.report_path) + self.report_path = os.path.join(self.gather_pack_dir, "gather_pack_{0}".format(TimeUtils.timestamp_to_filename_time(self.gather_timestamp), self.stdio)) + self.stdio.verbose("Use {0} as pack dir.".format(self.report_path)) + DirectoryUtil.mkdir(path=self.report_path, stdio=self.stdio) except Exception as e: - logger.error("init_report_path failed, error:{0}".format(e)) + self.stdio.error("init_report_path failed, error:{0}".format(e)) def __init_variables(self): try: - self.variables = { "observer_data_dir": self.ob_nodes[0].get("home_path") if self.ob_nodes and self.ob_nodes[0].get("home_path") else "", "obproxy_data_dir": self.obproxy_nodes[0].get("home_path") if self.obproxy_nodes and self.obproxy_nodes[0].get("home_path") else "", "from_time": self.from_time_str, "to_time": self.to_time_str } - logger.info("gather scene variables: {0}".format(self.variables)) + self.stdio.verbose("gather scene variables: {0}".format(self.variables)) except Exception as e: - logger.error("init gather scene variables failed, error: {0}".format(e)) + self.stdio.error("init gather scene variables failed, error: {0}".format(e)) def __get_task_type(self, s): trimmed_str = s.strip() @@ -153,48 +160,54 @@ def __get_task_type(self, s): else: return None - def __check_valid_and_parse_args(self, args): - """ - chech whether command args are valid. If invalid, stop processing and print the error to the user - :param args: command args - :return: boolean. True if valid, False if invalid. - """ - self.args = args - # 1: to timestamp must be larger than from timestamp, and be valid - if getattr(args, "from") is not None and getattr(args, "to") is not None: + def init_option(self): + options = self.context.options + from_option = Util.get_option(options, 'from') + to_option = Util.get_option(options, 'to') + since_option = Util.get_option(options, 'since') + store_dir_option = Util.get_option(options, 'store_dir') + env_option = Util.get_option(options, 'env') + scene_option = Util.get_option(options, 'scene') + if from_option is not None and to_option is not None: try: - from_timestamp = parse_time_str(getattr(args, "from")) - to_timestamp = parse_time_str(getattr(args, "to")) - self.from_time_str = getattr(args, "from") - self.to_time_str = getattr(args, "to") + from_timestamp = TimeUtils.parse_time_str(from_option) + to_timestamp = TimeUtils.parse_time_str(to_option) + self.from_time_str = from_option + self.to_time_str = to_option except OBDIAGFormatException: - logger.error("Error: Datetime is invalid. Must be in format yyyy-mm-dd hh:mm:ss. from_datetime={0}, to_datetime={1}".format(getattr(args, "from"), getattr(args, "to"))) + self.stdio.exception('Error: Datetime is invalid. Must be in format yyyy-mm-dd hh:mm:ss. from_datetime={0}, to_datetime={1}'.format(from_option, to_option)) return False if to_timestamp <= from_timestamp: - logger.error("Error: from datetime is larger than to datetime, please check.") + self.stdio.exception('Error: from datetime is larger than to datetime, please check.') return False + elif (from_option is None or to_option is None) and since_option is not None: + self.stdio.warn('No time option provided, default processing is based on the last 30 minutes') + now_time = datetime.datetime.now() + self.to_time_str = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') + self.from_time_str = (now_time - datetime.timedelta(seconds=TimeUtils.parse_time_length_to_sec(since_option))).strftime('%Y-%m-%d %H:%M:%S') + self.stdio.print('gather from_time: {0}, to_time: {1}'.format(self.from_time_str, self.to_time_str)) else: + self.stdio.warn('No time option provided, default processing is based on the last 30 minutes') now_time = datetime.datetime.now() self.to_time_str = (now_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') - if args.since is not None: - self.from_time_str = (now_time - datetime.timedelta( - seconds=parse_time_length_to_sec(args.since))).strftime('%Y-%m-%d %H:%M:%S') + if since_option: + self.from_time_str = (now_time - datetime.timedelta(seconds=TimeUtils.parse_time_length_to_sec(since_option))).strftime('%Y-%m-%d %H:%M:%S') else: self.from_time_str = (now_time - datetime.timedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') - # 2: store_dir must exist, else create directory. - if getattr(args, "store_dir") is not None: - if not os.path.exists(os.path.abspath(getattr(args, "store_dir"))): - logger.warn("Error: args --store_dir [{0}] incorrect: No such directory, Now create it".format(os.path.abspath(getattr(args, "store_dir")))) - os.makedirs(os.path.abspath(getattr(args, "store_dir"))) - self.gather_pack_dir = os.path.abspath(getattr(args, "store_dir")) - if getattr(args, "scene") is not None: - self.scene = ' '.join(getattr(args, "scene")) + self.stdio.print('gather from_time: {0}, to_time: {1}'.format(self.from_time_str, self.to_time_str)) + if store_dir_option: + if not os.path.exists(os.path.abspath(store_dir_option)): + self.stdio.warn('warn: args --store_dir [{0}] incorrect: No such directory, Now create it'.format(os.path.abspath(store_dir_option))) + os.makedirs(os.path.abspath(store_dir_option)) + self.gather_pack_dir = os.path.abspath(store_dir_option) + if scene_option: + self.scene = scene_option else: return False - if getattr(args, "env") is not None: - env_dict = parse_custom_env_string(getattr(args, "env")) + if env_option: + env_dict = StringUtils.parse_env(env_option) self.env = env_dict return True def __print_result(self): - print(Fore.YELLOW + "\nGather scene results stored in this directory: {0}\n".format(self.report_path) + Style.RESET_ALL) + self.stdio.print(Fore.YELLOW + "\nGather scene results stored in this directory: {0}\n".format(self.report_path) + Style.RESET_ALL) diff --git a/handler/gather/gather_sysstat.py b/handler/gather/gather_sysstat.py index 39c6167d..759b17db 100644 --- a/handler/gather/gather_sysstat.py +++ b/handler/gather/gather_sysstat.py @@ -20,52 +20,82 @@ import datetime import tabulate -import uuid - -from common.logger import logger from common.constant import const from common.command import LocalClient, SshClient from common.command import get_file_size, download_file, mkdir, zip_dir from handler.base_shell_handler import BaseShellHandler -from utils.file_utils import mkdir_if_not_exist, size_format, write_result_append_to_file, parse_size -from utils.shell_utils import SshHelper -from utils.time_utils import timestamp_to_filename_time -from utils.utils import get_localhost_inner_ip, display_trace +from common.ssh import SshHelper +from common.tool import Util +from common.tool import DirectoryUtil +from common.tool import FileUtil +from common.tool import NetUtils +from common.tool import TimeUtils class GatherOsInfoHandler(BaseShellHandler): - def __init__(self, nodes, gather_pack_dir, gather_timestamp=None, common_config=None, is_scene=False): - super(GatherOsInfoHandler, self).__init__(nodes) - for node in nodes: - if node.get("ssh_type") == "docker": - logger.error("the ssh_type is docker not support sysstat") - raise Exception("the ssh_type is docker not support sysstat") + def __init__(self, context, gather_pack_dir='./', is_scene=False): + super(GatherOsInfoHandler, self).__init__() + self.context = context + self.stdio = context.stdio self.is_ssh = True - self.gather_timestamp = gather_timestamp self.local_stored_path = gather_pack_dir self.remote_stored_path = None self.is_scene = is_scene self.config_path = const.DEFAULT_CONFIG_PATH - if common_config is None: + if self.context.get_variable("gather_timestamp", None) : + self.gather_timestamp=self.context.get_variable("gather_timestamp") + else: + self.gather_timestamp = TimeUtils.get_current_us_timestamp() + + def init_config(self): + self.nodes = self.context.cluster_config['servers'] + new_nodes = Util.get_nodes_list(self.context, self.nodes, self.stdio) + if new_nodes: + self.nodes = new_nodes + self.inner_config = self.context.inner_config + if self.inner_config is None: + self.file_number_limit = 20 self.file_size_limit = 2 * 1024 * 1024 * 1024 else: - self.file_size_limit = int(parse_size(common_config["file_size_limit"])) + basic_config = self.inner_config['obdiag']['basic'] + self.file_number_limit = int(basic_config["file_number_limit"]) + self.file_size_limit = int(FileUtil.size(basic_config["file_size_limit"])) + self.config_path = basic_config['config_path'] + for node in self.nodes: + if node.get("ssh_type") == "docker": + self.stdio.warn("the ssh_type is docker not support sysstat") + return False + return True + + def init_option(self): + options = self.context.options + store_dir_option = Util.get_option(options, 'store_dir') + if store_dir_option and store_dir_option != './': + if not os.path.exists(os.path.abspath(store_dir_option)): + self.stdio.warn('warn: args --store_dir [{0}] incorrect: No such directory, Now create it'.format(os.path.abspath(store_dir_option))) + os.makedirs(os.path.abspath(store_dir_option)) + self.local_stored_path = os.path.abspath(store_dir_option) + self.scope_option = Util.get_option(options, 'scope') + return True - def handle(self, args): - # check args first - if not self.__check_valid_args(args): - return + def handle(self): + if not self.init_option(): + self.stdio.error('init option failed') + return False + if not self.init_config(): + self.stdio.error('init config failed') + return False if self.is_scene: pack_dir_this_command = self.local_stored_path else: - pack_dir_this_command = os.path.join(self.local_stored_path,"gather_pack_{0}".format(timestamp_to_filename_time(self.gather_timestamp))) - logger.info("Use {0} as pack dir.".format(pack_dir_this_command)) + pack_dir_this_command = os.path.join(self.local_stored_path,"gather_pack_{0}".format(TimeUtils.timestamp_to_filename_time(self.gather_timestamp))) + self.stdio.verbose("Use {0} as pack dir.".format(pack_dir_this_command)) gather_tuples = [] def handle_from_node(node): st = time.time() - resp = self.__handle_from_node(args, node, pack_dir_this_command) + resp = self.__handle_from_node(node, pack_dir_this_command) file_size = "" if len(resp["error"]) == 0: file_size = os.path.getsize(resp["gather_pack_path"]) @@ -79,43 +109,38 @@ def handle_from_node(node): for node in self.nodes: handle_from_node(node) else: - local_ip = get_localhost_inner_ip() + local_ip = NetUtils.get_inner_ip() node = self.nodes[0] node["ip"] = local_ip for node in self.nodes: handle_from_node(node) summary_tuples = self.__get_overall_summary(gather_tuples) - print(summary_tuples) - display_trace(uuid.uuid3(uuid.NAMESPACE_DNS, str(os.getpid()))) + self.stdio.print(summary_tuples) # Persist the summary results to a file - write_result_append_to_file(os.path.join(pack_dir_this_command, "result_summary.txt"), summary_tuples) - last_info = "For result details, please run cmd \033[32m' cat {0} '\033[0m\n".format(os.path.join(pack_dir_this_command, "result_summary.txt")) - print(last_info) + FileUtil.write_append(os.path.join(pack_dir_this_command, "result_summary.txt"), summary_tuples) - def __handle_from_node(self, args, node, local_stored_path): + def __handle_from_node(self, node, local_stored_path): resp = { "skip": False, "error": "", "gather_pack_path": "" } - remote_ip = node.get("ip") if self.is_ssh else get_localhost_inner_ip() - remote_user = node.get("user") - remote_password = node.get("password") - remote_port = node.get("port") - remote_private_key = node.get("private_key") - remote_home_path = node.get("home_path") - logger.info( - "Sending Collect Shell Command to node {0} ...".format(remote_ip)) - mkdir_if_not_exist(local_stored_path) + remote_ip = node.get("ip") if self.is_ssh else NetUtils.get_inner_ip() + remote_user = node.get("ssh_username") + remote_password = node.get("ssh_password") + remote_port = node.get("ssh_port") + remote_private_key = node.get("ssh_key_file") + self.stdio.verbose("Sending Collect Shell Command to node {0} ...".format(remote_ip)) + DirectoryUtil.mkdir(path=local_stored_path, stdio=self.stdio) now_time = datetime.datetime.now().strftime('%Y%m%d%H%M%S') remote_dir_name = "sysstat_{0}_{1}".format(remote_ip, now_time) remote_dir_full_path = "/tmp/{0}".format(remote_dir_name) ssh_failed = False try: - ssh_helper = SshHelper(self.is_ssh, remote_ip, remote_user, remote_password, remote_port, remote_private_key,node) + ssh_helper = SshHelper(self.is_ssh, remote_ip, remote_user, remote_password, remote_port, remote_private_key,node, self.stdio) except Exception as e: - logger.error("ssh {0}@{1}: failed, Please check the {2}".format( + self.stdio.exception("ssh {0}@{1}: failed, Please check the {2}".format( remote_user, remote_ip, self.config_path)) @@ -123,20 +148,20 @@ def __handle_from_node(self, args, node, local_stored_path): resp["skip"] = True resp["error"] = "Please check the {0}".format(self.config_path) if not ssh_failed: - mkdir(self.is_ssh, ssh_helper, remote_dir_full_path) + mkdir(self.is_ssh, ssh_helper, remote_dir_full_path, self.stdio) self.__gather_dmesg_boot_info(ssh_helper, remote_dir_full_path) self.__gather_dmesg_current_info(ssh_helper, remote_dir_full_path) self.__gather_cpu_info(ssh_helper, remote_dir_full_path) self.__gather_mem_info(ssh_helper, remote_dir_full_path) - zip_dir(self.is_ssh, ssh_helper, "/tmp", remote_dir_name) + zip_dir(self.is_ssh, ssh_helper, "/tmp", remote_dir_name, self.stdio) remote_file_full_path = "{0}.zip".format(remote_dir_full_path) - file_size = get_file_size(self.is_ssh, ssh_helper, remote_file_full_path) + file_size = get_file_size(self.is_ssh, ssh_helper, remote_file_full_path, self.stdio) if int(file_size) < self.file_size_limit: local_file_path = "{0}/{1}.zip".format(local_stored_path, remote_dir_name) - logger.info( + self.stdio.verbose( "local file path {0}...".format(local_file_path)) - download_file(self.is_ssh, ssh_helper, remote_file_full_path, local_file_path) + download_file(self.is_ssh, ssh_helper, remote_file_full_path, local_file_path, self.stdio) resp["error"] = "" else: resp["error"] = "File too large" @@ -149,56 +174,41 @@ def __gather_dmesg_current_info(self, ssh_helper, gather_path): try: dmesg_cmd = "dmesg --ctime > {gather_path}/dmesg.human.current".format( gather_path=gather_path) - logger.info("gather dmesg current info, run cmd = [{0}]".format(dmesg_cmd)) - SshClient().run(ssh_helper, dmesg_cmd) if self.is_ssh else LocalClient().run(dmesg_cmd) + self.stdio.verbose("gather dmesg current info, run cmd = [{0}]".format(dmesg_cmd)) + SshClient(self.stdio).run(ssh_helper, dmesg_cmd) if self.is_ssh else LocalClient(self.stdio).run(dmesg_cmd) except: - logger.error("Failed to gather dmesg current info on server {0}".format(ssh_helper.get_name())) + self.stdio.error("Failed to gather dmesg current info on server {0}".format(ssh_helper.get_name())) def __gather_dmesg_boot_info(self, ssh_helper, dir_path): try: file_exit_cmd = "ls -l {file_path} 2>/dev/null".format(file_path="/var/log/dmesg") - file_exit = SshClient().run(ssh_helper, file_exit_cmd) if self.is_ssh else LocalClient().run(file_exit_cmd) + file_exit = SshClient(self.stdio).run(ssh_helper, file_exit_cmd) if self.is_ssh else LocalClient(self.stdio).run(file_exit_cmd) if file_exit: dmesg_cmd = 'cp --force /var/log/dmesg {dir_path}/dmesg.boot'.format(dir_path=dir_path) - logger.info("gather dmesg boot info on server {0}, run cmd = [{1}]".format(ssh_helper.get_name(), dmesg_cmd)) - SshClient().run(ssh_helper, dmesg_cmd) if self.is_ssh else LocalClient().run(dmesg_cmd) + self.stdio.verbose("gather dmesg boot info on server {0}, run cmd = [{1}]".format(ssh_helper.get_name(), dmesg_cmd)) + SshClient(self.stdio).run(ssh_helper, dmesg_cmd) if self.is_ssh else LocalClient(self.stdio).run(dmesg_cmd) else: - logger.warn("the file /var/log/dmesg on server {0} not found ".format(ssh_helper.get_name())) + self.stdio.warn("the file /var/log/dmesg on server {0} not found ".format(ssh_helper.get_name())) except: - logger.error("Failed to gather the /var/log/dmesg on server {0}".format(ssh_helper.get_name())) + self.stdio.error("Failed to gather the /var/log/dmesg on server {0}".format(ssh_helper.get_name())) def __gather_cpu_info(self, ssh_helper, gather_path): try: tsar_cmd = "tsar --cpu -i 1 > {gather_path}/one_day_cpu_data.txt".format( gather_path=gather_path) - logger.info("gather cpu info on server {0}, run cmd = [{1}]".format(ssh_helper.get_name(), tsar_cmd)) - SshClient().run(ssh_helper, tsar_cmd) if self.is_ssh else LocalClient().run(tsar_cmd) + self.stdio.verbose("gather cpu info on server {0}, run cmd = [{1}]".format(ssh_helper.get_name(), tsar_cmd)) + SshClient(self.stdio).run(ssh_helper, tsar_cmd) if self.is_ssh else LocalClient(self.stdio).run(tsar_cmd) except: - logger.error("Failed to gather cpu info use tsar on server {0}".format(ssh_helper.get_name())) + self.stdio.error("Failed to gather cpu info use tsar on server {0}".format(ssh_helper.get_name())) def __gather_mem_info(self, ssh_helper, gather_path): try: tsar_cmd = "tsar --mem -i 1 > {gather_path}/one_day_mem_data.txt".format( gather_path=gather_path) - logger.info("gather memory info on server {0}, run cmd = [{1}]".format(ssh_helper.get_name(), tsar_cmd)) - SshClient().run(ssh_helper, tsar_cmd) if self.is_ssh else LocalClient().run(tsar_cmd) + self.stdio.verbose("gather memory info on server {0}, run cmd = [{1}]".format(ssh_helper.get_name(), tsar_cmd)) + SshClient(self.stdio).run(ssh_helper, tsar_cmd) if self.is_ssh else LocalClient(self.stdio).run(tsar_cmd) except: - logger.error("Failed to gather memory info use tsar on server {0}".format(ssh_helper.get_name())) - - - def __check_valid_args(self, args): - """ - chech whether command args are valid. If invalid, stop processing and print the error to the user - :param args: command args - :return: boolean. True if valid, False if invalid. - """ - # 1: store_dir must exist, else create directory. - if getattr(args, "store_dir") is not None: - if not os.path.exists(os.path.abspath(getattr(args, "store_dir"))): - logger.warn("Error: args --store_dir [{0}] incorrect: No such directory, Now create it".format(os.path.abspath(getattr(args, "store_dir")))) - os.makedirs(os.path.abspath(getattr(args, "store_dir"))) - self.local_stored_path = os.path.abspath(getattr(args, "store_dir")) - return True + self.stdio.error("Failed to gather memory info use tsar on server {0}".format(ssh_helper.get_name())) @staticmethod def __get_overall_summary(node_summary_tuple): @@ -211,9 +221,9 @@ def __get_overall_summary(node_summary_tuple): consume_time = tup[4] pack_path = tup[5] try: - format_file_size = size_format(file_size, output_str=True) + format_file_size = FileUtil.size_format(num=file_size, output_str=True) except: - format_file_size = size_format(0, output_str=True) + format_file_size = FileUtil.size_format(num=0, output_str=True) summary_tab.append((node, "Error:" + tup[2] if is_err else "Completed", format_file_size, "{0} s".format(int(consume_time)), pack_path)) return "\nGather Sysstat Summary:\n" + \ diff --git a/handler/gather/scenes/base.py b/handler/gather/scenes/base.py index 37f26a15..286d6302 100644 --- a/handler/gather/scenes/base.py +++ b/handler/gather/scenes/base.py @@ -15,24 +15,25 @@ @file: base.py @desc: """ - -from common.logger import logger +from stdio import SafeStdio from common.scene import filter_by_version from handler.gather.step.base import Base -from utils.utils import node_cut_passwd_for_log +from common.tool import StringUtils from handler.gather.scenes.sql_problem import SQLProblemScene from handler.gather.scenes.cpu_high import CPUHighScene +from handler.gather.scenes.px_collect_log import SQLPXCollectLogScene -class SceneBase(object): - def __init__(self, scene, obproxy_nodes, ob_nodes, cluster, report_dir=None, scene_variable_dict={}, args=None, env={}, mode="yaml", task_type="observer"): +class SceneBase(SafeStdio): + def __init__(self,context, scene, report_dir=None, scene_variable_dict={}, env={}, mode="yaml", task_type="observer"): + self.context = context + self.stdio = context.stdio self.scene_variable_dict = scene_variable_dict self.scene = scene - self.cluster = cluster - self.ob_nodes = ob_nodes - self.obproxy_nodes = obproxy_nodes + self.cluster = context.cluster_config + self.ob_nodes = context.cluster_config['servers'] + self.obproxy_nodes = context.obproxy_config['servers'] self.report_dir = report_dir - self.args = args self.mode = mode self.env = env self.task_type = task_type @@ -50,55 +51,58 @@ def execute(self): elif self.mode == "code": self.__execute_code_mode() else: - logger.error("Unsupported mode. SKIP") + self.stdio.error("Unsupported mode. SKIP") raise Exception("Unsupported mode. SKIP") except Exception as e: raise Exception("execute failed, error: {0}".format(e)) def __execute_yaml_mode(self, nodes): - steps_nu = filter_by_version(self.scene, self.cluster) + steps_nu = filter_by_version(self.scene, self.cluster, self.stdio) if steps_nu < 0: - logger.warning("Unadapted by version. SKIP") + self.stdio.warn("Unadapted by version. SKIP") return "Unadapted by version.SKIP" - logger.info("filter_by_version is return {0}".format(steps_nu)) + self.stdio.verbose("filter_by_version is return {0}".format(steps_nu)) if len(nodes)==0: - logger.warn("node is not exist") + self.stdio.warn("node is not exist") return node_number = 0 for node in nodes: - logger.info("run scene in node: {0}".format(node_cut_passwd_for_log(node))) + self.stdio.print("run scene excute yaml mode in node: {0} start".format(StringUtils.node_cut_passwd_for_log(node['ip'], self.stdio))) steps = self.scene[steps_nu] nu = 1 node_number = node_number + 1 for step in steps["steps"]: try: - logger.debug("step nu: {0}".format(nu)) + self.stdio.verbose("step nu: {0}".format(nu)) if len(self.cluster)==0: - logger.error("cluster is not exist") + self.stdio.error("cluster is not exist") return - step_run = Base(step, node, self.cluster, self.report_dir, self.scene_variable_dict, self.args, self.env, node_number) - logger.info("step nu: {0} initted, to execute".format(nu)) + step_run = Base(self.context, step, node, self.cluster, self.report_dir, self.scene_variable_dict, self.env, node_number) + self.stdio.verbose("step nu: {0} initted, to execute".format(nu)) step_run.execute() self.scene_variable_dict = step_run.update_task_variable_dict() except Exception as e: - logger.error("SceneBase execute Exception: {0}".format(e)) + self.stdio.error("SceneBase execute Exception: {0}".format(e)) return - logger.info("step nu: {0} execute end ".format(nu)) + self.stdio.verbose("step nu: {0} execute end ".format(nu)) nu = nu + 1 - logger.info("scene execute end") + self.stdio.print("run scene excute yaml mode in node: {0} end".format(StringUtils.node_cut_passwd_for_log(node['ip'], self.stdio))) + self.stdio.verbose("run scene excute yaml mode in node") def __execute_code_mode(self): if self.scene["name"] == "observer.perf_sql" or self.scene["name"] == "observer.sql_err": - scene = SQLProblemScene(self.scene["name"], self.ob_nodes, self.obproxy_nodes, self.cluster, self.report_dir, self.scene_variable_dict, self.args, self.env) + scene = SQLProblemScene(self.context, self.scene["name"], self.report_dir, self.scene_variable_dict, self.env) elif self.scene["name"] == "observer.cpu_high": - scene = CPUHighScene(self.ob_nodes, self.cluster, self.report_dir, self.scene_variable_dict, self.args, self.env) + scene = CPUHighScene(self.context, self.report_dir, self.scene_variable_dict, self.env) + elif self.scene["name"] == "observer.px_collect_log": + scene = SQLPXCollectLogScene(self.context, self.scene["name"], self.report_dir, self.scene_variable_dict, self.env) else: - logger.error("unsupported hard code scene {0}".format(self.scene["name"])) + self.stdio.error("unsupported hard code scene {0}".format(self.scene["name"])) return try: - logger.info("hard code scene {0} execute start".format(self.scene["name"])) + self.stdio.verbose("hard code scene {0} execute start".format(self.scene["name"])) scene.execute() - logger.info("hard code scene {0} execute end".format(self.scene["name"])) + self.stdio.verbose("hard code scene {0} execute end".format(self.scene["name"])) except Exception as e: - logger.error("hard code scene execute failed, error :{0}".format(e)) + self.stdio.error("hard code scene execute failed, error :{0}".format(e)) diff --git a/handler/gather/scenes/cpu_high.py b/handler/gather/scenes/cpu_high.py index dba402a8..87923419 100644 --- a/handler/gather/scenes/cpu_high.py +++ b/handler/gather/scenes/cpu_high.py @@ -16,26 +16,26 @@ @desc: """ import os -from utils.shell_utils import SshHelper -from common.logger import logger +from stdio import SafeStdio +from common.ssh import SshHelper from handler.gather.gather_obstack2 import GatherObstack2Handler from handler.gather.gather_perf import GatherPerfHandler -from utils.parser_utils import ParserAction from handler.gather.gather_log import GatherLogHandler -class CPUHighScene(object): - def __init__(self, nodes, cluster, report_path, task_variable_dict=None, args=None, env={}): +class CPUHighScene(SafeStdio): + def __init__(self, context, report_path, task_variable_dict=None, env={}): + self.context = context + self.stdio = context.stdio if task_variable_dict is None: self.task_variable_dict = {} else: self.task_variable_dict = task_variable_dict - self.nodes = nodes - self.cluster = cluster self.report_path = report_path - self.args = args self.env = env self.is_ssh = True - self.ob_nodes = nodes + self.nodes = self.context.cluster_config['servers'] + self.cluster = self.context.cluster_config + self.ob_nodes = self.context.cluster_config['servers'] def execute(self): self.__gather_obstack() @@ -44,41 +44,39 @@ def execute(self): self.__gather_log() def __gather_obstack(self): - logger.info("gather obstack start") - obstack = GatherObstack2Handler(nodes=self.nodes, gather_pack_dir=self.report_path, is_scene=True) - obstack.handle(self.args) - logger.info("gather obstack end") + self.stdio.print("gather obstack start") + obstack = GatherObstack2Handler(self.context, self.report_path, is_scene=True) + obstack.handle() + self.stdio.print("gather obstack end") def __gather_perf(self): - logger.info("gather perf start") - perf = GatherPerfHandler(nodes=self.nodes, gather_pack_dir=self.report_path, is_scene=True) - self.args = ParserAction.add_attribute_to_namespace(self.args, 'scope', "all") - perf.handle(self.args) - logger.info("gather perf end") + self.stdio.print("gather perf start") + perf = GatherPerfHandler(self.context, self.report_path, is_scene=True) + perf.handle() + self.stdio.print("gather perf end") def __gather_current_clocksource(self): try: - logger.info("gather current_clocksource start") + self.stdio.print("gather current_clocksource start") for node in self.nodes: - ssh_helper = SshHelper(self.is_ssh, node.get("ip"), node.get("user"), node.get("password"), node.get("port"), node.get("private_key"), node) + ssh_helper = SshHelper(self.is_ssh, node.get("ip"), node.get("ssh_username"), node.get("ssh_password"), node.get("ssh_port"), node.get("ssh_key_file"), node) cmd = 'cat /sys/devices/system/clocksource/clocksource0/current_clocksource' - logger.info("gather current_clocksource, run cmd = [{0}]".format(cmd)) + self.stdio.verbose("gather current_clocksource, run cmd = [{0}]".format(cmd)) result = ssh_helper.ssh_exec_cmd(cmd) file_path = os.path.join(self.report_path, "current_clocksource_{ip}_result.txt".format(ip=str(node.get("ip")).replace('.', '_'))) self.report(file_path, cmd, result) - logger.info("gather current_clocksource end") + self.stdio.print("gather current_clocksource end") except Exception as e: - logger.error("SshHandler init fail. Please check the node conf. Exception : {0} .".format(e)) + self.stdio.error("SshHandler init fail. Please check the node conf. Exception : {0} .".format(e)) def __gather_log(self): try: - logger.info("gather observer log start") - handler = GatherLogHandler(nodes=self.ob_nodes, gather_pack_dir=self.report_path, is_scene=True) - self.args = ParserAction.add_attribute_to_namespace(self.args, 'grep', None) - handler.handle(self.args) - logger.info("gather observer log end") + self.stdio.print("gather observer log start") + handler = GatherLogHandler(self.context, self.report_path, is_scene=True) + handler.handle() + self.stdio.print("gather observer log end") except Exception as e: - logger.error("gather observer log failed, error: {0}".format(e)) + self.stdio.error("gather observer log failed, error: {0}".format(e)) raise Exception("gather observer log failed, error: {0}".format(e)) def report(self, file_path, command, data): @@ -87,4 +85,4 @@ def report(self, file_path, command, data): f.write('\n\n' + 'shell > ' + command + '\n') f.write(data + '\n') except Exception as e: - logger.error("report sql result to file: {0} failed, error: ".format(file_path)) + self.stdio.error("report sql result to file: {0} failed, error: ".format(file_path)) diff --git a/handler/gather/scenes/list.py b/handler/gather/scenes/list.py index 86dbf2d4..a83bde28 100644 --- a/handler/gather/scenes/list.py +++ b/handler/gather/scenes/list.py @@ -17,13 +17,15 @@ """ import os -from common.logger import logger -from utils.yaml_utils import read_yaml_data +from stdio import SafeStdio +from common.tool import YamlUtils from handler.gather.scenes.register import hardcode_scene_list -from utils.print_utils import print_scene, print_title +from common.tool import Util -class GatherScenesListHandler: - def __init__(self, yaml_tasks_base_path="./handler/gather/tasks/"): +class GatherScenesListHandler(SafeStdio): + def __init__(self, context, yaml_tasks_base_path="~/.obdiag/gather/tasks/"): + self.context = context + self.stdio = context.stdio self.observer_tasks = {} self.obproxy_tasks = {} self.other_tasks = {} @@ -32,15 +34,15 @@ def __init__(self, yaml_tasks_base_path="./handler/gather/tasks/"): if os.path.exists(base_path): self.yaml_tasks_base_path = base_path else: - logger.error("Failed to find yaml task path: {0}".format(base_path)) + self.stdio.error("Failed to find yaml task path: {0}".format(base_path)) - def handle(self, args): - logger.debug("list gather scene") + def handle(self): + self.stdio.verbose("list gather scene") self.get_all_yaml_tasks() self.get_all_code_tasks() - logger.debug("len of observer_tasks: {0}; len of observer_tasks: {1}; len of observer_tasks: {2};".format(len(self.observer_tasks), len(self.obproxy_tasks), len(self.other_tasks))) + self.stdio.verbose("len of observer_tasks: {0}; len of observer_tasks: {1}; len of observer_tasks: {2};".format(len(self.observer_tasks), len(self.obproxy_tasks), len(self.other_tasks))) if (len(self.observer_tasks) + len(self.obproxy_tasks) + len(self.other_tasks)) == 0: - logger.error("Failed to find any tasks") + self.stdio.error("Failed to find any tasks") else: self.print_scene_data() @@ -52,7 +54,7 @@ def get_all_yaml_tasks(self): if file.endswith('.yaml'): folder_name = os.path.basename(root) task_name = "{}.{}".format(folder_name, file.split('.')[0]) - task_data = read_yaml_data(os.path.join(root, file)) + task_data = YamlUtils.read_yaml_data(os.path.join(root, file)) task_data["name"] = task_name if folder_name == "observer": self.observer_tasks[task_name] = task_data @@ -61,7 +63,7 @@ def get_all_yaml_tasks(self): else: self.other_tasks[task_name] = task_data except Exception as e: - logger.error("get all yaml task failed, error: ", e) + self.stdio.error("get all yaml task failed, error: ", e) def get_all_code_tasks(self): try: @@ -73,7 +75,7 @@ def get_all_code_tasks(self): else: self.other_tasks[scene.name] = self.__get_hardcode_task(scene) except Exception as e: - logger.error("get all hard code task failed, error: ", e) + self.stdio.error("get all hard code task failed, error: ", e) def __get_hardcode_task(self, scene): return {"name": scene.name, "command": scene.command, "info_en": scene.info_en, "info_cn": scene.info_cn,} @@ -88,11 +90,11 @@ def get_one_yaml_task(self, name): folder_name = os.path.basename(root) task_name = "{}.{}".format(folder_name, file.split('.')[0]) if name == task_name: - task_data = read_yaml_data(os.path.join(root, file)) + task_data = YamlUtils.read_yaml_data(os.path.join(root, file)) task_data["name"] = task_name return task_data except Exception as e: - logger.error("get one yaml task failed, error: ", e) + self.stdio.error("get one yaml task failed, error: ", e) def is_code_task(self, name): try: @@ -101,7 +103,7 @@ def is_code_task(self, name): return True return False except Exception as e: - logger.error("get one code task failed, error: ", e) + self.stdio.error("get one code task failed, error: ", e) return False def print_scene_data(self): @@ -111,17 +113,17 @@ def print_scene_data(self): if self.other_tasks: sorted_other_tasks = sorted(self.other_tasks.items(), key=lambda x: x[0]) sorted_other_tasks_dict = {k: v for k, v in sorted_other_tasks} - print_title("Other Problem Gather Scenes") - print_scene(sorted_other_tasks_dict) + Util.print_title("Other Problem Gather Scenes") + Util.print_scene(sorted_other_tasks_dict) if self.obproxy_tasks: sorted_obproxy_tasks = sorted(self.obproxy_tasks.items(), key=lambda x: x[0]) sorted_obproxy_tasks_dict = {k: v for k, v in sorted_obproxy_tasks} - print_title("Obproxy Problem Gather Scenes") - print_scene(sorted_obproxy_tasks_dict) + Util.print_title("Obproxy Problem Gather Scenes") + Util.print_scene(sorted_obproxy_tasks_dict) if self.observer_tasks: sorted_observer_tasks = sorted(self.observer_tasks.items(), key=lambda x: x[0]) sorted_observer_tasks_dict = {k: v for k, v in sorted_observer_tasks} - print_title("Observer Problem Gather Scenes") - print_scene(sorted_observer_tasks_dict) + Util.print_title("Observer Problem Gather Scenes") + Util.print_scene(sorted_observer_tasks_dict) \ No newline at end of file diff --git a/handler/gather/scenes/px_collect_log.py b/handler/gather/scenes/px_collect_log.py new file mode 100644 index 00000000..e6234a8b --- /dev/null +++ b/handler/gather/scenes/px_collect_log.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -* +# Copyright (c) 2022 OceanBase +# OceanBase Diagnostic Tool 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. + +""" +@time: 2024/03/13 +@file: px_collect_log.py +@desc: +""" +from handler.gather.gather_log import GatherLogHandler +from common.command import uzip_dir_local, analyze_log_get_sqc_addr, delete_file_in_folder, find_home_path_by_port +from common.ssh import SshHelper +import datetime + +class SQLPXCollectLogScene(object): + def __init__(self, context, scene_name, report_path, task_variable_dict=None, env={}): + self.context = context + self.stdio = context.stdio + if task_variable_dict is None: + self.task_variable_dict = {} + else: + self.task_variable_dict = task_variable_dict + self.report_path = report_path + self.env = env + self.is_ssh = True + self.scene_name = scene_name + self.db_conn = {} + self.trace_id = "FAKE_TRACE_ID" + self.sql_task_node = [] + self.ob_nodes = self.context.cluster_config['servers'] + self.obproxy_nodes = self.context.obproxy_config['servers'] + self.cluster = self.context.cluster_config + self.search_time = None + + # 考虑到时间较多,有必要指定时间区间段,便于快速拿到日志 + def execute(self): + # 1. 获取trace id对应的机器地址 + if self.__parse_env(): + # 2. 收集对应机器的日志信息 + self.__gather_log() + # 3. 分析日志,提取SQC地址 + self.__analyze_log() + # 解压日志到一个新的目录 + # 分析日志,提取关键字地址 + # 4. 收集SQC机器的日志 + # 如果存在有效地址,则删除本地被解压的日志和压缩包,重新收集并存储于当前地址 + # 否则不存在,则删除被解压的目录 + if len(self.sql_task_node) != 0: + self.stdio.verbose("delete file start") + delete_file_in_folder(False, None, self.report_path, self.stdio) + self.stdio.verbose("delete file end") + self.__gather_log() + + def __gather_log(self): + try: + self.stdio.verbose("gather observer log start, trace id: {0}".format(self.trace_id)) + handler = GatherLogHandler(self.context, gather_pack_dir=self.report_path, is_scene=True) + self.context.set_variable('filter_nodes_list', self.sql_task_node) + self.context.set_variable('gather_grep', self.trace_id) + self.context.set_variable('gather_mode', 'trace_id_log') + from_time_str = (self.search_time - datetime.timedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S') + to_time_str = (self.search_time + datetime.timedelta(minutes=1)).strftime('%Y-%m-%d %H:%M:%S') + self.context.set_variable("gather_from", from_time_str) + self.context.set_variable("gather_to", to_time_str) + handler.handle() + self.stdio.verbose("gather observer log end") + except Exception as e: + self.stdio.error("gather observer log failed, error: {0}".format(e)) + raise Exception("gather observer log failed, error: {0}".format(e)) + + def __analyze_log(self): + try: + self.stdio.verbose("analyze observer log start") + uzip_dir = self.report_path + uzip_dir_local(uzip_dir, self.stdio) + ip_port_str = analyze_log_get_sqc_addr(uzip_dir, self.stdio) + if ip_port_str is None or len(ip_port_str) == 0: + self.stdio.warn("No logs were found indicating that the SQC interrupted the QC; the error occurred locally in the QC.") + self.sql_task_node = [] + return + self.stdio.verbose("find sqc ip_port_str: {0}".format(ip_port_str)) + ip_str, internal_port_str = ip_port_str.split(":") + home_path_str = self.__find_home_path_by_port(ip_str, internal_port_str) + sqc_sql_task_node = [] + for node in self.ob_nodes: + if node["ip"] == ip_str and node["home_path"] == home_path_str: + sqc_sql_task_node.append(node) + break + if self.sql_task_node == sqc_sql_task_node: + self.stdio.verbose("The SQC with an error occurred on the same observer as the QC.") + self.sql_task_node = [] + else: + self.stdio.verbose("The SQC with an error info: {0}".format(sqc_sql_task_node)) + self.sql_task_node = sqc_sql_task_node + self.stdio.verbose("analyze observer log end") + except Exception as e: + self.stdio.exception("analyze observer log failed, error: {0}".format(e)) + raise Exception("analyze observer log failed, error: {0}".format(e)) + + def __find_home_path_by_port(self, ip_str, internal_port_str): + for node in self.ob_nodes: + if node["ip"] == ip_str: + remote_ip = node.get("ip") + remote_user = node.get("ssh_username") + remote_password = node.get("ssh_password") + remote_port = node.get("ssh_port") + remote_private_key = node.get("ssh_key_file") + try: + ssh = SshHelper(self.is_ssh, remote_ip, remote_user, remote_password, remote_port, remote_private_key, node, self.stdio) + except Exception as e: + self.stdio.error("ssh {0}@{1}: failed, Please check the config".format( + remote_user, + remote_ip)) + return find_home_path_by_port(True, ssh, internal_port_str, self.stdio) + + def parse_trace_id(self, trace_id): + id_ = trace_id.split('-')[0].split('Y')[1] + uval = int(id_, 16) + ip = uval & 0xffffffff + port = (uval >> 32) & 0xffff + ip_str = "{}.{}.{}.{}".format((ip >> 24) & 0xff, (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff) + origin_ip_port = "{}:{}".format(ip_str, port) + return origin_ip_port + + def parse_trace_id2(self, trace_id): + parts = trace_id.split('-') + processed_parts = [] + for idx, t in enumerate(parts): + v = int(t) + n = hex(v)[2:] + if idx == 1: + n = n.zfill(16) + processed_parts.append(n.upper()) + s = 'Y' + '-'.join(processed_parts) + origin_ip_port2 = self.parse_trace_id(s) + return origin_ip_port2 + + def analyze_traceid(self, trace_id): + if (len(trace_id) < 50): + if (trace_id[0] == 'Y'): + return self.parse_trace_id(trace_id) + else: + return self.parse_trace_id2(trace_id) + else: + raise Exception("Trace_id({0}) error!".format(trace_id)) + + def __parse_env(self): + try: + trace_id = self.env.get("trace_id") + if trace_id: + self.trace_id = self.env.get("trace_id") + else: + self.stdio.error("option env [--trace_id] not found, please run 'obdiag gather scene list' to check usage") + return False + search_time = self.env.get("estimated_time") + if search_time is None or len(search_time) == 0: + search_time = datetime.datetime.now() + else: + try: + search_time = datetime.datetime.strptime(search_time, "%Y-%m-%d %H:%M:%S") + except Exception as e: + self.stdio.error("option env [--estimated_time] format error, please use '%Y-%m-%d %H:%M:%S', run 'obdiag gather scene list' to check usage") + return False + self.search_time = search_time + self.stdio.verbose("QC addr analyze begin {0}".format(trace_id)) + ip_port_str = self.analyze_traceid(trace_id) + self.stdio.verbose("analyze text: {0}".format(ip_port_str)) + ip_str, internal_port_str = ip_port_str.split(':') + home_path_str = self.__find_home_path_by_port(ip_str, internal_port_str) + for node in self.ob_nodes: + if node["ip"] == ip_str and node["home_path"] == home_path_str: + self.sql_task_node.append(node) + break + self.stdio.verbose("QC addr analyze end {0}".format(self.sql_task_node)) + return True + except Exception as e: + self.stdio.error("Parse env fail. Exception : {0} .".format(e)) \ No newline at end of file diff --git a/handler/gather/scenes/register.py b/handler/gather/scenes/register.py index 9ae68fc7..9586cfd7 100644 --- a/handler/gather/scenes/register.py +++ b/handler/gather/scenes/register.py @@ -17,6 +17,7 @@ """ from dataclasses import dataclass +import datetime @dataclass class RegisteredHardCodeScene: @@ -28,6 +29,7 @@ class RegisteredHardCodeScene: # 对于不适合通过yaml编排的复杂场景可以用这个类注册,注册后通过代码实现采集逻辑 db_connect = '-hxx -Pxx -uxx -pxx -Dxx' trace_id = 'xx' +estimated_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') hardcode_scene_list = [ RegisteredHardCodeScene( @@ -43,4 +45,10 @@ class RegisteredHardCodeScene: '[SQL 执行出错]' ), RegisteredHardCodeScene('observer.cpu_high', 'obdiag gather scene run --scene=observer.cpu_high', '[High CPU]', '[CPU高]'), + RegisteredHardCodeScene( + 'observer.px_collect_log', + f'''obdiag gather scene run --scene=observer.px_collect_log --env "{{trace_id='{trace_id}', estimated_time='{estimated_time}'}}"''', + '[Collect error source node logs for SQL PX]', + '[SQL PX 收集报错源节点日志]' + ), ] diff --git a/handler/gather/scenes/sql_problem.py b/handler/gather/scenes/sql_problem.py index 743739e3..bb1ba1c0 100644 --- a/handler/gather/scenes/sql_problem.py +++ b/handler/gather/scenes/sql_problem.py @@ -16,25 +16,25 @@ @desc: """ -from common.logger import logger -from utils.parser_utils import ParserAction +from stdio import SafeStdio from handler.gather.gather_log import GatherLogHandler from handler.gather.gather_obproxy_log import GatherObProxyLogHandler from handler.gather.gather_plan_monitor import GatherPlanMonitorHandler -from utils.string_utils import parse_mysql_cli_connection_string +from common.tool import StringUtils -class SQLProblemScene(object): - def __init__(self, scene_name, ob_nodes, obproxy_nodes, cluster, report_path, task_variable_dict=None, args=None, env={}): +class SQLProblemScene(SafeStdio): + def __init__(self, context, scene_name, report_path, task_variable_dict=None, env={}): + self.context = context + self.stdio=context.stdio if task_variable_dict is None: self.task_variable_dict = {} else: self.task_variable_dict = task_variable_dict - self.ob_nodes = ob_nodes - self.obproxy_nodes = obproxy_nodes - self.cluster = cluster + self.ob_nodes = self.context.cluster_config['servers'] + self.obproxy_nodes = self.context.obproxy_config['servers'] + self.cluster = self.context.cluster_config self.report_path = report_path - self.args = args self.env = env self.is_ssh = True self.scene_name = scene_name @@ -42,62 +42,67 @@ def __init__(self, scene_name, ob_nodes, obproxy_nodes, cluster, report_path, ta self.trace_id = "FAKE_TRACE_ID" def execute(self): - self.__parse_env() - self.__gather_log() - self.__gather_obproxy_log() - self.__gather_sql_info() + if self.__parse_env(): + self.__gather_log() + self.__gather_obproxy_log() + self.__gather_sql_info() def __gather_log(self): try: - logger.info("gather observer log start") - handler = GatherLogHandler(nodes=self.ob_nodes, gather_pack_dir=self.report_path, is_scene=True) - self.args = ParserAction.add_attribute_to_namespace(self.args, 'grep', None) - handler.handle(self.args) - logger.info("gather observer log end") + self.stdio.verbose("gather observer log start") + handler = GatherLogHandler(self.context, self.report_path, is_scene=True) + handler.handle() + self.stdio.verbose("gather observer log end") except Exception as e: - logger.error("gather observer log failed, error: {0}".format(e)) + self.stdio.error("gather observer log failed, error: {0}".format(e)) raise Exception("gather observer log failed, error: {0}".format(e)) + def __gather_obproxy_log(self): try: - logger.info("gather obproxy log start") - handler = GatherObProxyLogHandler(nodes=self.obproxy_nodes, gather_pack_dir=self.report_path, is_scene=True) + self.stdio.verbose("gather obproxy log start") + handler = GatherObProxyLogHandler(self.context, gather_pack_dir=self.report_path, is_scene=True) if self.scene_name: if self.scene_name == "observer.sql_err": - self.args = ParserAction.add_attribute_to_namespace(self.args, 'grep', None) + pass elif self.scene_name == "observer.perf_sql": - self.args = ParserAction.add_attribute_to_namespace(self.args, 'grep', self.trace_id) + self.context.set_variable('gather_scope', self.trace_id) else: - logger.warn("unsupported scene {0}".format(self.scene_name)) + self.stdio.warn("unsupported scene {0}".format(self.scene_name)) return - self.args = ParserAction.add_attribute_to_namespace(self.args, 'scope', "all") - self.args = ParserAction.add_attribute_to_namespace(self.args, 'encrypt', "false") - handler.handle(self.args) - logger.info("gather obproxy log end") + handler.handle() + self.stdio.verbose("gather obproxy log end") else: - logger.warn("scene is None") + self.stdio.warn("scene is None") return except Exception as e: - logger.error("gather obproxy log failed, error: {0}".format(e)) + self.stdio.error("gather obproxy log failed, error: {0}".format(e)) raise Exception("gather obproxy log failed, error: {0}".format(e)) def __gather_sql_info(self): try: - logger.info("gather sql info start") - handler = GatherPlanMonitorHandler(ob_cluster=self.cluster, gather_pack_dir=self.report_path, db_conn=self.db_conn, is_scene=True) - self.args = ParserAction.add_attribute_to_namespace(self.args, 'trace_id', self.trace_id) - handler.handle(self.args) - logger.info("gather sql info end") + self.stdio.verbose("gather sql info start") + handler = GatherPlanMonitorHandler(self.context, gather_pack_dir=self.report_path, is_scene=True) + self.context.set_variable('gather_plan_monitor_trace_id', self.trace_id) + handler.handle() + self.stdio.verbose("gather sql info end") except Exception as e: - logger.error("gather sql info failed, error: {0}".format(e)) + self.stdio.error("gather sql info failed, error: {0}".format(e)) raise Exception("gather sql info failed, error: {0}".format(e)) def report(self): pass def __parse_env(self): - cli_connection_string = self.env.get("db_connect") - self.db_conn = parse_mysql_cli_connection_string(cli_connection_string) - trace_id = self.env.get("trace_id") - if trace_id: - self.trace_id = self.env.get("trace_id") + if self.env: + cli_connection_string = self.env.get("db_connect") + self.db_conn = StringUtils.parse_mysql_conn(cli_connection_string) + trace_id = self.env.get("trace_id") + if trace_id: + self.trace_id = self.env.get("trace_id") + else: + self.stdio.error("option env [--trace_id] not found, please run 'obdiag gather scene list' to check usage") + return False + else: + self.stdio.error("option env not found, please run 'obdiag gather scene list' to check usage") + return False diff --git a/handler/gather/step/base.py b/handler/gather/step/base.py index 0a2a9f10..73623416 100644 --- a/handler/gather/step/base.py +++ b/handler/gather/step/base.py @@ -15,17 +15,19 @@ @file: base.py @desc: """ +from stdio import SafeStdio import docker from handler.gather.step.ssh import SshHandler from handler.gather.step.sql import StepSQLHandler -from common.logger import logger from handler.gather.gather_log import GatherLogHandler from handler.gather.gather_obproxy_log import GatherObProxyLogHandler from handler.gather.gather_sysstat import GatherOsInfoHandler -from utils.parser_utils import ParserAction -class Base(object): - def __init__(self, step, node, cluster, report_path, task_variable_dict=None, args=None, env={}, node_number = 1): + +class Base(SafeStdio): + def __init__(self, context, step, node, cluster, report_path, task_variable_dict=None, env={}, node_number = 1): + self.context = context + self.stdio=context.stdio if task_variable_dict is None: self.task_variable_dict = {} else: @@ -34,66 +36,60 @@ def __init__(self, step, node, cluster, report_path, task_variable_dict=None, ar self.node = node self.cluster = cluster self.report_path = report_path - self.args = args self.env = env self.node_number = node_number def execute(self): - logger.debug("step: {0}".format(self.step)) + self.stdio.verbose("step: {0}".format(self.step)) no_cluster_name_msg="(Please set ob_cluster_name or obproxy_cluster_name)" try: if "ip" in self.node: self.task_variable_dict["remote_ip"] = self.node["ip"] elif "ssh_type" in self.node and self.node["ssh_type"]=="docker": - logger.debug("execute ssh_type is docker") + self.stdio.verbose("execute ssh_type is docker") self.task_variable_dict["remote_ip"] = docker.from_env().containers.get(self.node["container_name"]).attrs['NetworkSettings']['Networks']['bridge']["IPAddress"] self.task_variable_dict["remote_home_path"] = self.node["home_path"] if "type" not in self.step: - logger.error("Missing field :type") + self.stdio.error("Missing field :type") if (self.node_number > 1) and self.step.get("global") and (self.step.get("global") == "true"): - logger.info("step sets the value of the global is true and it is processing the {0} node, skipping gather".format(self.node_number)) + self.stdio.verbose("step sets the value of the global is true and it is processing the {0} node, skipping gather".format(self.node_number)) else: if self.step["type"] == "ssh": - handler = SshHandler(self.step, self.node, self.report_path, self.task_variable_dict) + handler = SshHandler(self.context, self.step, self.node, self.report_path, self.task_variable_dict) handler.execute() elif self.step["type"] == "sql": - handler = StepSQLHandler(self.step, self.cluster, self.report_path, self.task_variable_dict) + handler = StepSQLHandler(self.context, self.step, self.cluster, self.report_path, self.task_variable_dict) handler.execute() elif self.step["type"] == "log": if self.node.get("host_type") and self.node.get("host_type") == "OBSERVER": - handler = GatherLogHandler(nodes=[self.node], gather_pack_dir=self.report_path, is_scene=True) - if self.step.get("grep") is None or len(self.step.get("grep")) == 0: - self.args = ParserAction.add_attribute_to_namespace(self.args, 'grep', None) - else: - self.args = ParserAction.add_attribute_to_namespace(self.args, 'grep', self.step.get("grep")) - handler.handle(self.args) + handler = GatherLogHandler(self.context, gather_pack_dir=self.report_path, is_scene=True) + self.context.set_variable('filter_nodes_list', [self.node]) + self.context.set_variable('gather_grep', self.step.get("grep")) + handler.handle() else: - logger.info("node host_type is {0} not OBSERVER, skipping gather log".format(self.node.get("host_type"))) + self.stdio.verbose("node host_type is {0} not OBSERVER, skipping gather log".format(self.node.get("host_type"))) elif self.step["type"] == "obproxy_log": if self.node.get("host_type") and self.node.get("host_type") == "OBPROXY": - handler = GatherObProxyLogHandler(nodes=[self.node], gather_pack_dir=self.report_path, is_scene=True) - if self.step.get("grep") is None or len(self.step.get("grep")) == 0: - self.args = ParserAction.add_attribute_to_namespace(self.args, 'grep', None) - else: - self.args = ParserAction.add_attribute_to_namespace(self.args, 'grep', self.step.get("grep")) - self.args = ParserAction.add_attribute_to_namespace(self.args, 'scope', 'all') - self.args = ParserAction.add_attribute_to_namespace(self.args, 'encrypt', 'false') - handler.handle(self.args) + handler = GatherObProxyLogHandler(self.context, gather_pack_dir=self.report_path, is_scene=True) + self.context.set_variable('filter_nodes_list', [self.node]) + self.context.set_variable('gather_grep', self.step.get("grep")) + handler.handle() else: - logger.info("node host_type is {0} not OBPROXY, skipping gather log".format(self.node.get("host_type"))) + self.stdio.verbose("node host_type is {0} not OBPROXY, skipping gather log".format(self.node.get("host_type"))) elif self.step["type"] == "sysstat": - handler = GatherOsInfoHandler(nodes=[self.node], gather_pack_dir=self.report_path, is_scene=True) - handler.handle(self.args) + handler = GatherOsInfoHandler(self.context, gather_pack_dir=self.report_path, is_scene=True) + self.context.set_variable('filter_nodes_list', [self.node]) + handler.handle() else: - logger.error("the type not support: {0}" .format(self.step["type"])) + self.stdio.error("the type not support: {0}" .format(self.step["type"])) except Exception as e: - logger.error("StepBase handler.execute fail, error: {0}".format(e)) + self.stdio.error("StepBase handler.execute fail, error: {0}".format(e)) if self.step["type"] == "sql": - logger.error("[cluster:{0}] {1}]".format(self.cluster.get("ob_cluster_name") or self.cluster.get("obproxy_cluster_name") or no_cluster_name_msg, e)) + self.stdio.error("[cluster:{0}] {1}]".format(self.cluster.get("ob_cluster_name") or self.cluster.get("obproxy_cluster_name") or no_cluster_name_msg, e)) else: - logger.error("[{0}:{1}] {2}]".format(self.node.get("ssh_type") or "", self.node.get("container_name") or self.task_variable_dict.get("remote_ip") or "", e)) - logger.error("StepBase handler.execute fail, error: {0}".format(e)) + self.stdio.error("[{0}:{1}] {2}]".format(self.node.get("ssh_type") or "", self.node.get("container_name") or self.task_variable_dict.get("remote_ip") or "", e)) + self.stdio.error("StepBase handler.execute fail, error: {0}".format(e)) def update_task_variable_dict(self): return self.task_variable_dict diff --git a/handler/gather/step/sql.py b/handler/gather/step/sql.py index db12335a..68de95c9 100644 --- a/handler/gather/step/sql.py +++ b/handler/gather/step/sql.py @@ -16,14 +16,16 @@ @desc: """ import os -from common.logger import logger +from stdio import SafeStdio from common.ob_connector import OBConnector from tabulate import tabulate -from utils.utils import build_str_on_expr_by_dict_2, convert_to_number +from common.tool import StringUtils -class StepSQLHandler: - def __init__(self, step, ob_cluster, report_path, task_variable_dict): +class StepSQLHandler(SafeStdio): + def __init__(self, context, step, ob_cluster, report_path, task_variable_dict): + self.context = context + self.stdio=context.stdio try: self.ob_cluster = ob_cluster self.ob_cluster_name = ob_cluster.get("cluster_name") @@ -34,9 +36,10 @@ def __init__(self, step, ob_cluster, report_path, task_variable_dict): port=ob_cluster.get("db_port"), username=ob_cluster.get("tenant_sys").get("user"), password=ob_cluster.get("tenant_sys").get("password"), + stdio=self.stdio, timeout=10000) except Exception as e: - logger.error("StepSQLHandler init fail. Please check the OBCLUSTER conf. OBCLUSTER: {0} Exception : {1} .".format(ob_cluster,e)) + self.stdio.error("StepSQLHandler init fail. Please check the OBCLUSTER conf. OBCLUSTER: {0} Exception : {1} .".format(ob_cluster,e)) self.task_variable_dict = task_variable_dict self.enable_dump_db = False self.enable_fast_dump = False @@ -48,16 +51,16 @@ def __init__(self, step, ob_cluster, report_path, task_variable_dict): def execute(self): try: if "sql" not in self.step: - logger.error("StepSQLHandler execute sql is not set") + self.stdio.error("StepSQLHandler execute sql is not set") return - sql = build_str_on_expr_by_dict_2(self.step["sql"], self.task_variable_dict) - logger.info("StepSQLHandler execute: {0}".format(sql)) + sql = StringUtils.build_str_on_expr_by_dict_2(self.step["sql"], self.task_variable_dict) + self.stdio.verbose("StepSQLHandler execute: {0}".format(sql)) columns, data = self.ob_connector.execute_sql_return_columns_and_data(sql) if data is None or len(data) == 0: - logger.warning("excute sql: {0}, result is None".format(sql)) + self.stdio.verbose("excute sql: {0}, result is None".format(sql)) self.report(sql, columns, data) except Exception as e: - logger.error("StepSQLHandler execute Exception: {0}".format(e).strip()) + self.stdio.error("StepSQLHandler execute Exception: {0}".format(e).strip()) def update_step_variable_dict(self): return self.task_variable_dict @@ -70,4 +73,4 @@ def report(self, sql, column_names, data): f.write('\n\n' + 'obclient > ' + sql + '\n') f.write(formatted_table) except Exception as e: - logger.error("report sql result to file: {0} failed, error: ".format(self.report_file_path)) \ No newline at end of file + self.stdio.error("report sql result to file: {0} failed, error: ".format(self.report_file_path)) \ No newline at end of file diff --git a/handler/gather/step/ssh.py b/handler/gather/step/ssh.py index 41ec26f2..ced94117 100644 --- a/handler/gather/step/ssh.py +++ b/handler/gather/step/ssh.py @@ -16,13 +16,15 @@ @desc: """ import os -from utils.shell_utils import SshHelper -from common.logger import logger -from utils.utils import build_str_on_expr_by_dict_2 +from stdio import SafeStdio +from common.ssh import SshHelper +from common.tool import StringUtils -class SshHandler: - def __init__(self, step, node, report_path, task_variable_dict): +class SshHandler(SafeStdio): + def __init__(self, context, step, node, report_path, task_variable_dict): + self.context = context + self.stdio=context.stdio self.ssh_report_value = None self.parameters = None self.step = step @@ -30,9 +32,9 @@ def __init__(self, step, node, report_path, task_variable_dict): self.report_path = report_path try: is_ssh = True - self.ssh_helper = SshHelper(is_ssh, node.get("ip"), node.get("user"), node.get("password"), node.get("port"), node.get("private_key"), node) + self.ssh_helper = SshHelper(is_ssh, node.get("ip"), node.get("ssh_username"), node.get("ssh_password"), node.get("ssh_port"), node.get("ssh_key_file"), node) except Exception as e: - logger.error("SshHandler init fail. Please check the NODES conf. node: {0}. Exception : {1} .".format(node, e)) + self.stdio.error("SshHandler init fail. Please check the NODES conf. node: {0}. Exception : {1} .".format(node, e)) self.task_variable_dict = task_variable_dict self.parameter = [] self.report_file_path = os.path.join(self.report_path, "shell_result.txt") @@ -40,10 +42,10 @@ def __init__(self, step, node, report_path, task_variable_dict): def execute(self): try: if "ssh" not in self.step: - logger.error("SshHandler execute ssh is not set") + self.stdio.error("SshHandler execute ssh is not set") return - ssh_cmd = build_str_on_expr_by_dict_2(self.step["ssh"], self.task_variable_dict) - logger.info("step SshHandler execute :{0} ".format(ssh_cmd)) + ssh_cmd = StringUtils.build_str_on_expr_by_dict_2(self.step["ssh"], self.task_variable_dict) + self.stdio.verbose("step SshHandler execute :{0} ".format(ssh_cmd)) ssh_report_value = self.ssh_helper.ssh_exec_cmd(ssh_cmd) if ssh_report_value is None: ssh_report_value = "" @@ -51,10 +53,10 @@ def execute(self): ssh_report_value = ssh_report_value.strip() self.report(ssh_cmd, ssh_report_value) except Exception as e: - logger.error("ssh execute Exception:{0}".format(e).strip()) + self.stdio.error("ssh execute Exception:{0}".format(e).strip()) finally: self.ssh_helper.ssh_close() - logger.debug("gather step SshHandler ssh_report_value:{0}".format(ssh_report_value)) + self.stdio.verbose("gather step SshHandler ssh_report_value:{0}".format(self.ssh_report_value)) def update_step_variable_dict(self): return self.task_variable_dict @@ -65,4 +67,4 @@ def report(self, command, data): f.write('\n\n' + 'shell > ' + command + '\n') f.write(data + '\n') except Exception as e: - logger.error("report sql result to file: {0} failed, error: ".format(self.report_file_path)) + self.stdio.error("report sql result to file: {0} failed, error: ".format(self.report_file_path)) diff --git a/handler/gather/tasks/observer/compaction.yaml b/handler/gather/tasks/observer/compaction.yaml index d268b428..3e50ec65 100644 --- a/handler/gather/tasks/observer/compaction.yaml +++ b/handler/gather/tasks/observer/compaction.yaml @@ -148,7 +148,7 @@ task: sql: "show parameters like 'freeze_trigger_percentage';" global: true - type: sql - sql: "select t.tenant_name, t1.database_name, round(sum(t2.data_size)/1024/1024/1024,2) as data_size_gb, round(sum(t2.required_size)/1024/1024/1024,2) as required_size_gb from oceanbase.dba_ob_tenants t,cdb_ob_table_locations t1,cdb_ob_tablet_replicas t2 where t.tenant_id=t1.tenant_id and t1.svr_ip=t2.svr_ip and t1.tenant_id=t2.tenant_id and t1.ls_id=t2.ls_id and t1.tablet_id=t2.tablet_id and t1.role='leader' group by t.tenant_name, t1.database_name order by data_size_gb desc;" + sql: "select t.tenant_name, t1.database_name, round(sum(t2.data_size)/1024/1024/1024,2) as data_size_gb, round(sum(t2.required_size)/1024/1024/1024,2) as required_size_gb from oceanbase.dba_ob_tenants t, oceanbase.cdb_ob_table_locations t1, oceanbase.cdb_ob_tablet_replicas t2 where t.tenant_id=t1.tenant_id and t1.svr_ip=t2.svr_ip and t1.tenant_id=t2.tenant_id and t1.ls_id=t2.ls_id and t1.tablet_id=t2.tablet_id and t1.role='leader' group by t.tenant_name, t1.database_name order by data_size_gb desc;" global: true - type: log global: false diff --git a/handler/gather/tasks/observer/delay_of_primary_and_backup.yaml b/handler/gather/tasks/observer/delay_of_primary_and_backup.yaml index a814359e..0693831d 100644 --- a/handler/gather/tasks/observer/delay_of_primary_and_backup.yaml +++ b/handler/gather/tasks/observer/delay_of_primary_and_backup.yaml @@ -151,7 +151,7 @@ task: sql: "show parameters like '%backup%';" global: true - type: sql - sql: "SELECT TENANT_NAME, TENANT_ID, TENANT_ROLE, SCN_TO_TIMESTAMP(SYNC_SCN) FROM oceanbase.DBA_OB_TENANTS WHERE TENANT_NAME = 'standby_tenant';;" + sql: "SELECT TENANT_NAME, TENANT_ID, TENANT_ROLE, SCN_TO_TIMESTAMP(SYNC_SCN) FROM oceanbase.DBA_OB_TENANTS WHERE TENANT_NAME = 'standby_tenant';" global: true - type: sql sql: "SELECT LS_ID, SCN_TO_TIMESTAMP(END_SCN) FROM oceanbase.GV$OB_LOG_STAT WHERE ROLE = 'LEADER';" diff --git a/handler/gather/tasks/observer/io.yaml b/handler/gather/tasks/observer/io.yaml index 0cabe87f..23bab615 100644 --- a/handler/gather/tasks/observer/io.yaml +++ b/handler/gather/tasks/observer/io.yaml @@ -82,7 +82,7 @@ task: sql: "show parameters like '%syslog_io_bandwidth_limit%';" global: true - type: sql - sql: "select * from __all_virtual_io_quota limit 20" + sql: "select * from oceanbase.__all_virtual_io_quota limit 20" global: true - type: ssh ssh: "df -h" diff --git a/handler/gather/tasks/observer/memory.yaml b/handler/gather/tasks/observer/memory.yaml index 374349d3..140aca02 100644 --- a/handler/gather/tasks/observer/memory.yaml +++ b/handler/gather/tasks/observer/memory.yaml @@ -97,7 +97,7 @@ task: sql: "select * from oceanbase.GV$OB_MEMSTORE limit 20" global: true - type: ssh # 可看到租户的规格、线程、队列及请求统计等信息,且这条日志每个租户每 30s 打印一次 - ssh: "grep 'dump tenant info.tenant=' ${observer_data_dir}/log/observer.log | sed 's/,/,\n/g'" + ssh: "grep 'dump tenant info.tenant=' ${observer_data_dir}/log/observer.log | sed 's/,/,/g'" global: false - type: log grep: "" diff --git a/handler/rca/__init__.py b/handler/rca/__init__.py index d85f698e..eea6dbce 100644 --- a/handler/rca/__init__.py +++ b/handler/rca/__init__.py @@ -11,7 +11,7 @@ # See the Mulan PSL v2 for more details. """ -@time: 2023/12/22 +@time: 2024/03/08 @file: __init__.py @desc: """ diff --git a/utils/__init__.py b/handler/rca/plugins/__init__.py similarity index 96% rename from utils/__init__.py rename to handler/rca/plugins/__init__.py index 2b33595e..eea6dbce 100644 --- a/utils/__init__.py +++ b/handler/rca/plugins/__init__.py @@ -11,7 +11,8 @@ # See the Mulan PSL v2 for more details. """ -@time: 2022/6/20 +@time: 2024/03/08 @file: __init__.py @desc: -""" \ No newline at end of file +""" + diff --git a/handler/rca/plugins/gather.py b/handler/rca/plugins/gather.py new file mode 100644 index 00000000..0bf1caf1 --- /dev/null +++ b/handler/rca/plugins/gather.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -* +# Copyright (c) 2022 OceanBase +# OceanBase Diagnostic Tool 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. + +""" +@time: 2024/03/08 +@file: gather.py +@desc: +""" +import os.path +import zipfile + +from handler.gather.gather_log import GatherLogHandler +from common.tool import Util +from handler.gather.gather_obproxy_log import GatherObProxyLogHandler + + +class Gather_log(): + def __init__(self, context): + self.conf_map = {} + self.context = context + self.stdio = context.stdio + + self.work_path = context.get_variable("store_dir") + "/gather_log" + self.options = self.context.options + self.greps_key = [] + self.nodes = [] + self.init_parameters() + + def init_parameters(self): + self.conf_map["filter_nodes_list"] = [] + self.conf_map["gather_from"] = "" + self.conf_map["gather_to"] = "" + self.conf_map["gather_since"] = "" + self.conf_map["gather_scope"] = "" + self.conf_map["store_dir"] = self.work_path + self.conf_map["gather_target"] = "observer" + + def grep(self, key): + if key is None or len(key) < 1 or type(key) != str: + raise Exception("The keyword cannot be empty!") + self.greps_key.append(key) + + def execute(self, save_path=""): + try: + self.stdio.verbose("Gather_log execute,the greps_key: {0}".format(self.greps_key)) + if save_path is None or save_path == '': + save_path = self.work_path + save_path = os.path.expanduser(save_path) + if os.path.exists(save_path): + self.work_path = save_path + else: + os.mkdir(save_path) + self.stdio.verbose("{0} is not exist, create it.".format(save_path)) + self.work_path = save_path + self.conf_map["store_dir"] = self.work_path + self.stdio.verbose("Gather_log execute,the conf_map: {0}".format(self.conf_map)) + if len(self.greps_key) == 0: + self.stdio.error("The keyword cannot be empty!") + raise Exception("The keyword cannot be empty!") + self.context.set_variable("gather_grep", self.greps_key) + self.stdio.verbose("gather_grep is {0}".format(self.greps_key)) + nodes_list = [] + if not self.conf_map["filter_nodes_list"] or len(self.conf_map["filter_nodes_list"]) == 0: + self.context.set_variable("filter_nodes_list", self.conf_map["filter_nodes_list"]) + # execute on all nodes_list + handle=None + for conf in self.conf_map: + self.context.set_variable(conf, self.conf_map[conf]) + if self.conf_map["gather_target"] == 'observer': + all_node = self.context.cluster_config.get("servers") + if self.conf_map["filter_nodes_list"] and len(self.conf_map["filter_nodes_list"]>0): + # execute on specific nodes_list + for gather_node in self.conf_map["filter_nodes_list"]: + for node in all_node: + if node["ip"] in gather_node["ip"] and node["port"] in gather_node["port"]: + nodes_list.append(node) + self.stdio.verbose("{0} is in the nodes list".format(node.get("ip"))) + self.conf_map["filter_nodes_list"] = nodes_list + handle=GatherLogHandler(self.context) + elif self.conf_map["gather_target"] == 'obproxy': + all_node = self.context.get_variable('obproxy_nodes') + if self.conf_map["filter_nodes_list"]: + # execute on specific nodes_list + for node in all_node: + if node not in self.conf_map["filter_nodes_list"]: + self.stdio.warn("{0} is not in the nodes list".format(node.get("ip"))) + continue + else: + nodes_list.append(node) + self.conf_map["filter_nodes_list"] = nodes_list + handle=GatherObProxyLogHandler(self.context) + + if handle is None: + self.stdio.error("rca gather handle the target cannot be empty!") + raise Exception("rca gather handle the target cannot be empty!") + else: + handle.handle() + gather_result=handle.pack_dir_this_command + zip_files = os.listdir(gather_result) + result_log_files=[] + for zip_file in zip_files: + if "zip" not in zip_file: + continue + + # open zip file + self.stdio.verbose("open zip file: {0}".format(os.path.join(gather_result,zip_file))) + with zipfile.ZipFile(os.path.join(gather_result,zip_file), 'r') as zip_ref: + # Extract all files to the current directory + zip_ref.extractall(gather_result) + for file_name in os.listdir(gather_result): + if "zip" not in file_name and "result_summary.txt" not in file_name: + log_dir=os.path.join(gather_result,file_name) + for log_file in os.listdir(log_dir): + result_log_files.append(os.path.join(log_dir,log_file)) + self.stdio.verbose("result_log_files add {0}".format(os.path.join(log_dir,log_file))) + + self.reset() + + return result_log_files + except Exception as e: + raise Exception("rca plugins Gather_log execute error: {0}".format(e)) + + def set_parameters(self, parameter, value): + parameter = "gather_{0}".format(parameter) + if parameter in self.conf_map: + self.conf_map[parameter] = value + return True + return False + + def reset(self): + self.init_parameters() diff --git a/handler/rca/rca_handler.py b/handler/rca/rca_handler.py index 33873036..92f423b3 100644 --- a/handler/rca/rca_handler.py +++ b/handler/rca/rca_handler.py @@ -16,71 +16,293 @@ @desc: """ import datetime - -from common.logger import logger +import json +import os +from textwrap import fill +from common.command import get_obproxy_version, get_observer_version_by_sql, get_observer_version +from prettytable import PrettyTable +from common.ob_connector import OBConnector +from handler.rca.plugins.gather import Gather_log from handler.rca.rca_exception import RCANotNeedExecuteException -from handler.rca.rca_scene import rca_map -from utils.utils import node_cut_passwd_for_log +from handler.rca.rca_list import RcaScenesListHandler +from common.ssh import SshHelper +from common.tool import Util +from common.tool import StringUtils +from colorama import Fore, Style -def scene_exist(scene_name): - if scene_name in rca_map: - return True - else: - return False +class RCAHandler: + def __init__(self, context): + self.context = context + self.stdio = context.stdio + self.ob_cluster = self.context.cluster_config + self.options = self.context.options + observer_nodes = self.context.cluster_config.get("servers") + # build observer_nodes ,add ssher + context_observer_nodes = [] + if observer_nodes is not None: + for node in observer_nodes: + ssh = SshHelper(True, node.get("ip"), + node.get("ssh_username"), + node.get("ssh_password"), + node.get("ssh_port"), + node.get("ssh_key_file"), + node) + node["ssher"] = ssh + context_observer_nodes.append(node) + self.context.set_variable("observer_nodes", context_observer_nodes) + obproxy_nodes = self.context.obproxy_config.get("servers") + # build obproxy_nodes + context_obproxy_nodes = [] + if obproxy_nodes is not None: + for node in obproxy_nodes: + ssh = SshHelper(True, node.get("ip"), + node.get("ssh_username"), + node.get("ssh_password"), + node.get("ssh_port"), + node.get("ssh_key_file"), + node) + node["ssher"] = ssh + context_obproxy_nodes.append(node) + self.context.set_variable("obproxy_nodes", context_obproxy_nodes) -class RCAHandler: + # build ob_connector + try: + if self.ob_cluster is not None: + ob_connector = OBConnector(ip=self.ob_cluster.get("db_host"), + port=self.ob_cluster.get("db_port"), + username=self.ob_cluster.get("tenant_sys").get("user"), + password=self.ob_cluster.get("tenant_sys").get("password"), + stdio=self.stdio, + timeout=10000) + self.context.set_variable("ob_connector", ob_connector) + except Exception as e: + self.stdio.warn("RCAHandler init ob_connector failed: {0}. If the scene need it, please check the conf.yaml".format(str(e))) + # build report + store_dir = Util.get_option(self.options, 'store_dir') + if store_dir is None: + store_dir = "./rca/" + self.stdio.verbose("RCAHandler.init store dir: {0}".format(store_dir)) + report = Result(self.context) + report.set_save_path(store_dir) + self.context.set_variable("report", report) + + # build observer_version by sql or ssher. If using SSHer, the observer_version is set to node[0]. + observer_version = "" + try: + observer_version = get_observer_version_by_sql(self.ob_cluster, self.stdio) + except Exception as e: + if len(context_observer_nodes) > 0: + observer_version = get_observer_version(True, context_observer_nodes[0]["ssher"], + context_observer_nodes[0]["home_path"],self.stdio) + else: + self.stdio.warn("RCAHandler Failed to get observer version:{0}".format(e)) + self.stdio.verbose("RCAHandler.init get observer version: {0}".format(observer_version)) + + if observer_version != "": + self.stdio.verbose("RCAHandler.init get observer version: {0}".format(observer_version)) + self.context.set_variable("observer_version", observer_version) + else: + self.stdio.warn("RCAHandler.init Failed to get observer version.") + + # build obproxy_version. just by ssh + if self.context.get_variable("obproxy_version", default="") == "": + if len(obproxy_nodes) > 0: + obproxy_version = "" + try: + if len(context_obproxy_nodes) > 0: + obproxy_version = get_obproxy_version(True, context_obproxy_nodes[0]["ssher"], + context_obproxy_nodes[0]["home_path"],self.stdio) + except Exception as e: + self.stdio.warn("RCAHandler.init Failed to get obproxy version. Error:{0}".format(e)) + if obproxy_version != "": + self.stdio.verbose("RCAHandler.init get obproxy version: {0}".format(obproxy_version)) + else: + self.stdio.warn("RCAHandler.init Failed to get obproxy version.") + self.stdio.verbose("RCAHandler.init get obproxy version: {0}".format(obproxy_version)) + self.context.set_variable("obproxy_version", obproxy_version) + + self.context.set_variable("ob_cluster", self.ob_cluster) + + # set rca_deep_limit + rca_list = RcaScenesListHandler(self.context) + all_scenes_info, all_scenes_item = rca_list.get_all_scenes() + self.context.set_variable("rca_deep_limit", len(all_scenes_info)) + self.all_scenes = all_scenes_item - def __init__(self, cluster, nodes, obproxy_nodes, - result_path="./rca/"): self.rca_scene_parameters = None self.rca_scene = None - self.cluster = cluster - self.nodes = nodes - self.obproxy_nodes = obproxy_nodes - self.result_path = result_path + self.cluster = self.context.get_variable("ob_cluster") + self.nodes = self.context.get_variable("observer_nodes") + self.obproxy_nodes = self.context.get_variable("obproxy_nodes") + self.store_dir = store_dir # init input parameters self.report = None self.tasks = None - logger.debug("RCAHandler init.cluster:{0}, init.nodes:{1}, init.obproxy_nodes:{2}, init.result_path:{3}".format( - self.cluster.get( - "ob_cluster_name") or self.cluster.get( - "obproxy_cluster_name"), node_cut_passwd_for_log(self.nodes), node_cut_passwd_for_log(self.obproxy_nodes), self.result_path)) + rca_scene_parameters = Util.get_option(self.options, 'input_parameters', "") + if rca_scene_parameters != "": + try: + rca_scene_parameters=json.loads(rca_scene_parameters) + except Exception as e: + raise Exception("Failed to parse input_parameters. Please check the option is json:{0}".format(rca_scene_parameters)) + else: + rca_scene_parameters= {} + self.context.set_variable("input_parameters", rca_scene_parameters) + self.store_dir = Util.get_option(self.options, 'store_dir', "./rca/") + self.context.set_variable("store_dir", self.store_dir) + self.stdio.verbose( + "RCAHandler init.cluster:{0}, init.nodes:{1}, init.obproxy_nodes:{2}, init.store_dir:{3}".format( + self.cluster.get( + "ob_cluster_name") or self.cluster.get( + "obproxy_cluster_name"), StringUtils.node_cut_passwd_for_log(self.nodes), + StringUtils.node_cut_passwd_for_log(self.obproxy_nodes), self.store_dir)) def get_result_path(self): - return self.result_path + return self.store_dir + + def handle(self): - def handle(self, args): - if getattr(args, "parameters"): - self.rca_scene_parameters = getattr(args, "parameters", "")[0].strip() - if getattr(args, "store_dir"): - self.result_path = getattr(args, "store_dir", "./rca/")[0].strip() + scene_name = Util.get_option(self.options, 'scene', None) + if scene_name: + scene_name = scene_name.strip() + if scene_name in self.all_scenes: + self.rca_scene = self.all_scenes[scene_name] + if self.rca_scene is None: + raise Exception("rca_scene :{0} is not exist".format(scene_name)) - if getattr(args, "scene") and scene_exist(getattr(args, "scene")[0]): - self.rca_scene = rca_map[getattr(args, "scene")[0]] - self.result_path = "{0}/{1}_{2}".format(self.result_path, getattr(args, "scene")[0].strip(), - datetime.datetime.now().strftime('%Y%m%d%H%M%S')) - self.rca_scene.init(self.cluster, self.nodes, self.obproxy_nodes, - env=self.rca_scene_parameters, result_path=self.result_path) + self.store_dir = os.path.expanduser("{0}/{1}_{2}".format(self.store_dir, scene_name, + datetime.datetime.now().strftime( + '%Y%m%d%H%M%S'))) + if not os.path.exists(self.store_dir): + os.mkdir(self.store_dir) + self.context.set_variable("store_dir", self.store_dir) + self.stdio.verbose("{1} store_dir:{0}".format(self.store_dir, scene_name)) + # build gather_log + self.context.set_variable("gather_log", Gather_log(self.context)) + try: + if self.rca_scene.init(self.context) is False: + return + except Exception as e: + raise Exception("rca_scene.init err: {0}".format(e)) + self.stdio.verbose("{0} init success".format(scene_name)) else: - raise Exception("rca_scene :{0} is not exist or not input".format(getattr(args, "scene", ""))) + raise Exception("rca_scene :{0} is not exist or not input".format(scene_name)) # get all tasks def execute(self): try: self.rca_scene.execute() except RCANotNeedExecuteException as e: - logger.warning("rca_scene.execute not need execute: {0}".format(e)) + self.stdio.warn("rca_scene.execute not need execute: {0}".format(e)) pass except Exception as e: - logger.error("rca_scene.execute err: {0}".format(e)) raise Exception("rca_scene.execute err: {0}".format(e)) try: self.rca_scene.export_result() except Exception as e: - logger.error("rca_scene.export_result err: {0}".format(e)) raise Exception("rca_scene.export_result err: {0}".format(e)) + self.stdio.print( "rca finished. For more details, the result on '" + Fore.YELLOW + self.get_result_path() + Style.RESET_ALL + "' \nYou can get the suggest by '" + Fore.YELLOW + "cat " + self.get_result_path() + "/record" + Style.RESET_ALL + "'") + + + +class RcaScene: + def __init__(self): + self.gather_log = None + self.stdio = None + self.input_parameters = None + self.ob_cluster = None + self.ob_connector = None + self.store_dir = None + self.obproxy_version = None + self.observer_version = None + self.report = None + self.obproxy_nodes = None + self.observer_nodes = None + self.context = None + self.name = type(self).__name__ + self.Result = None + + def init(self, context): + self.context = context + self.stdio = context.stdio + self.Result = Result(self.context) + self.observer_nodes = context.get_variable('observer_nodes') + self.obproxy_nodes = context.get_variable('obproxy_nodes') + self.report = context.get_variable('report') + self.obproxy_version = context.get_variable('obproxy_version', default="") + self.observer_version = context.get_variable('observer_version', default="") + self.ob_connector = context.get_variable('ob_connector',default=None) + self.store_dir = context.get_variable('store_dir') + self.ob_cluster = context.get_variable('ob_cluster') + self.input_parameters = context.get_variable('input_parameters') or {} + self.gather_log = context.get_variable('gather_log') + + def execute(self): + # 获取获取根因分析结果(包括运维建议),返回RCA_ResultRecord格式 + raise Exception("rca ({0}) scene.execute() undefined".format(type(self).__name__)) + + def get_result(self): + # 设定场景分析的返回场景使用说明,需要的参数等等 + raise Exception("rca ({0}) scene.get_result() undefined".format(type(self).__name__)) + + def get_scene_info(self): + raise Exception("rca ({0}) scene.get_scene_info() undefined".format(type(self).__name__)) + def export_result(self): + return self.Result.export() + +class Result: + + def __init__(self, context): + # self.suggest = "" + self.records = [] + self.context = context + self.stdio = context.stdio + self.save_path = self.context.get_variable('store_dir') + + def set_save_path(self, save_path): + self.save_path = os.path.expanduser(save_path) + if os.path.exists(save_path): + self.save_path = save_path + else: + os.makedirs(save_path) + self.save_path = save_path + self.stdio.verbose("rca result save_path is :{0}".format(self.save_path)) + + def export(self): + record_file_name = os.path.expanduser("{0}/{1}".format(self.save_path, "record")) + self.stdio.verbose("save record to {0}".format(record_file_name)) + with open(record_file_name, 'w') as f: + for record in self.records: + record_data = record.export_record() + f.write(record_data.get_string()) + f.write("\n") + f.write(record.export_suggest()) + f.write("\n") + + +class RCA_ResultRecord: + def __init__(self): + self.records = [] + self.suggest = "The suggest: " + + def add_record(self, record): + self.records.append(record) + + def add_suggest(self, suggest): + self.suggest += suggest + + def export_suggest(self): + return self.suggest + def export_record(self): + record_tb = PrettyTable(["step", "info"]) + record_tb.align["info"] = "l" + record_tb.title = "record" + i = 0 + while i < len(self.records): + record_tb.add_row([i + 1, fill(self.records[i], width=100)]) + i += 1 + return record_tb diff --git a/handler/rca/rca_list.py b/handler/rca/rca_list.py index 4d7ec6ad..c03ef872 100644 --- a/handler/rca/rca_list.py +++ b/handler/rca/rca_list.py @@ -15,47 +15,68 @@ @file: rca_list.py @desc: """ -from common.logger import logger -from dataclasses import dataclass -from utils.print_utils import print_scene, print_title - -@dataclass -class RegisteredScene: - name: str - command: str - info_en: str - info_cn: str - - -scene_list = [ - RegisteredScene( - 'major_hold', - 'obdiag rca run --scene=major_hold', - '[root cause analysis of major hold]', - '[针对卡合并场景的根因分析]' - ), - RegisteredScene( - 'disconnection', - 'obdiag rca run --scene=disconnection', - '[root cause analysis of disconnection]', - '[针对断链接场景的根因分析]' - ), - RegisteredScene('lock_conflict', 'obdiag rca run --scene=lock_conflict', '[root cause analysis of lock conflict]', '[针对锁冲突的根因分析]'), -] +import os.path +from common.constant import const +from common.tool import DynamicLoading +from common.tool import Util class RcaScenesListHandler: - def handle(self, args): - logger.debug("list rca scenes") - scenes_map = self.__get_scenes() - self.__print_scenes_data(scenes_map) - - def __print_scenes_data(self,scenes): - print_title("Rca Scenes") - print_scene(scenes) - - def __get_scenes(self): - scenes_map = {} - for scene in scene_list: - scenes_map[scene.name]={"name": scene.name, "command": scene.command, "info_en": scene.info_en, "info_cn": scene.info_cn} - return scenes_map \ No newline at end of file + def __init__(self, context, work_path=const.RCA_WORK_PATH): + self.context = context + self.stdio = context.stdio + + if not work_path: + work_path = const.RCA_WORK_PATH + if os.path.exists(os.path.expanduser(work_path)): + self.work_path = os.path.expanduser(work_path) + else: + self.stdio.warn( + "input rca work_path not exists: {0}, use default path {1}".format(work_path, const.RCA_WORK_PATH)) + self.work_path = const.RCA_WORK_PATH + + def get_all_scenes(self): + # find all rca file + scenes_files = self.__find_rca_files() + # get all info + scene_list = {} + scene_info_list = {} + if not scenes_files or len(scenes_files) == 0: + self.stdio.error("no rca scene found! Please check RCA_WORK_PATH: {0}".format(self.work_path)) + return + for scene_file in scenes_files: + lib_path = self.work_path + module_name = os.path.basename(scene_file)[:-9] + DynamicLoading.add_lib_path(lib_path) + module = DynamicLoading.import_module(os.path.basename(scene_file)[:-3], None) + if not hasattr(module, module_name): + self.stdio.error("{0} import_module failed".format(module_name)) + continue + scene_list[module_name] = getattr(module, module_name) + + for scene_name, scene in scene_list.items(): + scene_info = scene.get_scene_info() + scene_info_list[scene_name] = {"name": scene_name, + "command": "obdiag rca run --scene={0}".format(scene_name), + "info_en": scene_info["info_en"], + "info_cn": scene_info["info_cn"] + } + return scene_info_list,scene_list + + def handle(self): + try: + self.stdio.verbose("list rca scenes") + scene_info_list,scene_itme_list = self.get_all_scenes() + Util.print_scene(scene_info_list) + except Exception as e: + self.stdio.error("RcaScenesListHandler Exception: {0}".format(e)) + raise e + + def __find_rca_files(self): + files = [] + for file_or_folder in os.listdir(self.work_path): + full_path = os.path.join(self.work_path, file_or_folder) + if os.path.isfile(full_path): + if full_path.endswith('_scene.py') and len(os.path.basename(full_path)) > 7: + files.append(full_path) + return files diff --git a/handler/rca/rca_scene/__init__.py b/handler/rca/rca_scene/__init__.py deleted file mode 100644 index 90641263..00000000 --- a/handler/rca/rca_scene/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2023/12/22 -@file: __init__.py -@desc: -""" -from handler.rca.rca_scene.disconnection_scene import DisconnectionScene -from handler.rca.rca_scene.lock_conflict_scene import LockConflictScene -from handler.rca.rca_scene.major_hold_scene import MajorHoldScene - -rca_map = {} -rca_map["major_hold"] = MajorHoldScene() -rca_map["lock_conflict"] = LockConflictScene() -rca_map["disconnection"] = DisconnectionScene() - diff --git a/handler/rca/rca_scene/scene_base.py b/handler/rca/rca_scene/scene_base.py deleted file mode 100644 index f4c3aa6e..00000000 --- a/handler/rca/rca_scene/scene_base.py +++ /dev/null @@ -1,89 +0,0 @@ -import os - -from prettytable import PrettyTable -from textwrap import fill - -from common.logger import logger - - -class scene_base: - def __init__(self): - self.env = None - self.observer_nodes = None - self.ob_cluster = None - self.result_path = None - self.cluster = None - self.obproxy_nodes = None - self.Result = Result() - - def init(self, cluster, nodes, obproxy_nodes, env, result_path): - self.cluster = cluster - self.obproxy_nodes = obproxy_nodes - self.observer_nodes = nodes - self.env = env - self.ob_cluster = cluster - self.Result.set_save_path(result_path) - pass - - def info(self): - pass - - def execute(self): - pass - - -class Result: - - def __init__(self): - # self.suggest = "" - self.procedure = None - self.records = [] - self.save_path = "./" - - def set_save_path(self, save_path): - self.save_path = os.path.expanduser(save_path) - if os.path.exists(save_path): - self.save_path = save_path - else: - os.makedirs(save_path) - self.save_path = save_path - logger.info("rca result save_path is :{0}".format(self.save_path)) - - def export(self): - record_file_name = "{0}/{1}".format(self.save_path, "record") - logger.info("save record to {0}".format(record_file_name)) - with open(record_file_name, 'w') as f: - for record in self.records: - record_data = record.export_record() - f.write(record_data.get_string()) - f.write("\n") - f.write(record.export_suggest()) - f.write("\n") - - -class RCA_ResultRecord: - def __init__(self): - self.records = [] - self.suggest = "The suggest: " - - def add_record(self, record): - logger.info("add_record:{0}".format(record)) - self.records.append(record) - - def add_suggest(self, suggest): - logger.info("add_suggest:{0}".format(suggest)) - self.suggest += suggest - - def export_suggest(self): - return self.suggest - - def export_record(self): - record_tb = PrettyTable(["step", "info"]) - record_tb.align["info"] = "l" - record_tb.title = "record" - i = 0 - while i < len(self.records): - record_tb.add_row([i + 1, fill(self.records[i], width=100)]) - i += 1 - logger.debug(record_tb) - return record_tb diff --git a/handler/rca/scene/ddl_disk_full_scene.py b/handler/rca/scene/ddl_disk_full_scene.py new file mode 100644 index 00000000..89095f80 --- /dev/null +++ b/handler/rca/scene/ddl_disk_full_scene.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -* +# Copyright (c) 2022 OceanBase +# OceanBase Diagnostic Tool 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. + +""" +@time: 2024/04/01 +@file: ddl_disk_full.py +@desc: +""" +import re + +from handler.rca.rca_exception import RCAInitException, RCAExecuteException +from handler.rca.rca_handler import RcaScene, RCA_ResultRecord +from common.tool import StringUtils + + +class DDlDiskFullScene(RcaScene): + def __init__(self): + super().__init__() + self.index_table_id = None + self.estimated_size = None + self.estimated_data_size = None + self.index_name = None + self.action_type = None + self.table_id = None + self.tenant_id = None + + def init(self, context): + super().init(context) + + ## observer version>4.2.1.0 + observer_version = self.observer_version + if observer_version is None or len(observer_version.strip()) == 0: + raise RCAInitException("observer version is None. Please check the NODES conf.") + if not (observer_version == "4.2.1.0" or StringUtils.compare_versions_greater(observer_version, "4.2.1.0")): + self.stdio.error("observer version is {0}, which is less than 4.2.1.0.".format(observer_version)) + raise RCAInitException("observer version is {0}, which is less than 4.2.1.0.".format(observer_version)) + if self.ob_connector is None: + raise RCAInitException("ob_connector is None. Please check the NODES conf.") + self.verbose("observer version is {0}.".format(observer_version)) + # check table_name and tenant_name + + table_name = self.input_parameters.get("table_name") + tenant_name = self.input_parameters.get("tenant_name") + action_type = self.input_parameters.get("action_type") + index_name = self.input_parameters.get("index_name") + if table_name is None or table_name == "" or tenant_name is None or tenant_name == "": + raise RCAInitException("table_name or tenant_name is None. Please check the input parameters.") + if action_type is not None: + if action_type=="add_index": + self.action_type = action_type + self.verbose("action type is {0}.".format(action_type)) + if index_name is not None and index_name.strip() != "": + self.verbose("index name is {0}.".format(index_name)) + self.index_name = index_name.strip() + else: + self.action_type = None + self.stdio.error("action type is {0}. but index_name is None. Please input it.".format(action_type)) + else: + self.stdio.error("action type is {0}, but only support add_index now.".format(action_type)) + + tenant_data = self.ob_connector.execute_sql( + "select tenant_id from oceanbase.__all_tenant where tenant_name = '{0}';".format(tenant_name)) + self.tenant_id = tenant_data[0][0] + if self.tenant_id is None: + raise RCAInitException( + "can not find tenant id by tenant name: {0}. Please check the tenant name.".format(tenant_name)) + + table_id_data = self.ob_connector.execute_sql( + "select table_id from oceanbase.__all_virtual_table where table_name = '{0}';".format(table_name)) + self.table_id = table_id_data[0][0] + if self.table_id is None: + raise RCAInitException( + "can not find table id by table name: {0}. Please check the table name.".format(table_name)) + self.verbose("table_id is {0}, tenant_id is {1}.".format(self.table_id, self.tenant_id)) + + def verbose(self, info): + self.stdio.verbose("[DDlDiskFullScene] {0}".format(info)) + + def execute(self): + try: + record = RCA_ResultRecord() + record.add_record("table_id is {0}".format(self.table_id)) + record.add_record("tenant_id is {0}".format(self.tenant_id)) + # get estimated_data_size + self.verbose("start to get estimated_data_size...") + ## if the action is not add_index + sql = "select svr_ip, svr_port, sum(original_size) as estimated_data_size from oceanbase.__all_virtual_tablet_sstable_macro_info where tablet_id in (select tablet_id from oceanbase.__all_virtual_tablet_to_table_history where table_id = {0}) and (svr_ip, svr_port) in (select svr_ip, svr_port from oceanbase.__all_virtual_ls_meta_table where role = 1) group by svr_ip, svr_port;".format( + self.table_id) + self.verbose("execute_sql is {0}".format(sql)) + tablet_size_data = self.ob_connector.execute_sql(sql) + self.verbose("tablet_size_data is {0}".format(tablet_size_data)) + record.add_record("tablet_size_data is {0}".format(tablet_size_data)) + if len(tablet_size_data) <= 0 or tablet_size_data[0][2] is None: + raise RCAExecuteException( + "can not find tablet size info or estimated_data_size. please check the data:{0}.".format( + tablet_size_data)) + self.estimated_size = tablet_size_data + self.verbose("estimated_size is {0}".format(self.estimated_size)) + record.add_record("estimated_size is {0}".format(self.estimated_size)) + + # get estimated_size to self.estimated_size + if self.action_type is not None and self.action_type == "add_index": + self.verbose("start add_index_action") + record.add_record("index_name is {0}".format(self.index_name)) + record.add_record("action_type is {0}".format(self.action_type)) + ## if the action is add_index + sql = "select table_id from oceanbase.__all_virtual_table_history where tenant_id = '{0}' and data_table_id = '{1}' and table_name like '%{2}%';".format( + self.tenant_id, self.table_id, self.index_name) + self.verbose("execute_sql is {0}".format(sql)) + self.index_table_id = self.ob_connector.execute_sql(sql)[0][0] + self.verbose("index_table_id is {0}".format(self.index_table_id)) + record.add_record("index_table_id is {0}".format(self.index_table_id)) + + # Query the sum of the lengths of all columns in the main table + sql = "select table_id, sum(data_length) from oceanbase.__all_virtual_column_history where tenant_id = '{0}' and table_id = '{1}';".format( + self.tenant_id, self.table_id) + self.verbose("execute_sql is {0}".format(sql)) + main_table_sum_of_data_length = int(self.ob_connector.execute_sql(sql)[0][1]) + self.verbose("main_table_sum_of_data_length is {0}".format(main_table_sum_of_data_length)) + record.add_record("main_table_sum_of_data_length is {0}".format(main_table_sum_of_data_length)) + + # The sum of the lengths of all columns in the query index + sql = "select table_id, sum(data_length) from oceanbase.__all_virtual_column_history where tenant_id = '{0}' and table_id = '{1}';".format( + self.tenant_id, self.index_table_id) + self.verbose("execute_sql is {0}".format(sql)) + index_table_sum_of_data_length = int(self.ob_connector.execute_sql(sql)[0][1]) + self.verbose("index_table_sum_of_data_length is {0}".format(index_table_sum_of_data_length)) + record.add_record("index_table_sum_of_data_length is {0}".format(index_table_sum_of_data_length)) + + # + new_estimated_size = [] + for node_estimated_size in self.estimated_size: + new_node_estimated_size = [node_estimated_size[0], node_estimated_size[1]] + estimiated_index_size = int(index_table_sum_of_data_length / main_table_sum_of_data_length / 1024 / 1024 * int( + node_estimated_size[2])) + + new_node_estimated_size.append(estimiated_index_size) + new_estimated_size.append(new_node_estimated_size) + self.estimated_size = new_estimated_size + self.verbose("estimated_size is {0}".format(self.estimated_size)) + + for estimated_size in self.estimated_size: + target_server_ip = estimated_size[0] + target_server_port = estimated_size[1] + target_server_estimated_size = int(estimated_size[2]) + self.verbose("On target_server_ip is {0}, target_server_port is {1}, target_server_estimated_size is {2}".format(target_server_ip, target_server_port, target_server_estimated_size)) + record.add_record("On target_server_ip is {0}, target_server_port is {1}, target_server_estimated_size is {2}".format(target_server_ip, target_server_port, target_server_estimated_size)) + + # get target_server_total_size and target_server_used_size + target_server_data = self.ob_connector.execute_sql( + "select total_size, used_size from oceanbase.__all_virtual_disk_stat where svr_ip = '{0}' and svr_port = {1};".format( + target_server_ip, target_server_port)) + target_server_total_size = int(target_server_data[0][0]) + self.verbose("target_server_total_size is {0}".format(target_server_total_size)) + record.add_record("target_server_total_size is {0}".format(target_server_total_size)) + + target_server_used_size = int(target_server_data[0][1]) + self.verbose("target_server_used_size is {0}".format(target_server_used_size)) + record.add_record("target_server_used_size is {0}".format(target_server_used_size)) + + # get data_disk_usage_limit_percentage + sql = "SELECT VALUE FROM oceanbase.GV$OB_PARAMETERS WHERE SVR_IP='{0}' and SVR_PORT='{1}' and NAME LIKE \"data_disk_usage_limit_percentage\"".format( + target_server_ip, target_server_port) + self.verbose("execute_sql is {0}".format(sql)) + data_disk_usage_limit_percentage = int(self.ob_connector.execute_sql(sql)[0][0]) + # data_disk_usage_limit_percentage is a Cluster level configuration items + self.verbose("data_disk_usage_limit_percentage is {0}".format(data_disk_usage_limit_percentage)) + record.add_record("data_disk_usage_limit_percentage is {0}".format(data_disk_usage_limit_percentage)) + if self.observer_version == "4.3.0.0" or StringUtils.compare_versions_greater(self.observer_version, + "4.3.0.0"): + target_server_estimated_size =int(target_server_estimated_size * 15/10) + else: + target_server_estimated_size =int(target_server_estimated_size * 55/10) + self.verbose("target_server_estimated_size is {0}".format(target_server_estimated_size)) + record.add_record("target_server_estimated_size is {0}".format(target_server_estimated_size)) + + + available_disk_space=int(target_server_total_size/100*data_disk_usage_limit_percentage-target_server_used_size) + self.verbose("available_disk_space is {0}".format(available_disk_space)) + record.add_record("available_disk_space is {0}".format(available_disk_space)) + + if target_server_estimated_size - available_disk_space > 0: + record.add_record("target_server_estimated_size - available_disk_space is {0}".format(target_server_estimated_size - available_disk_space)) + record.add_suggest( + "the disk space of server({0}:{1}) disk is not enough. please add the server disk".format( + target_server_ip, target_server_port)) + else: + record.add_record("target_server_estimated_size - available_disk_space is {0}".format(target_server_estimated_size - available_disk_space)) + record.add_suggest( + "the disk space of server({0}:{1}) is enough. Don't warn ".format(target_server_ip, + target_server_port)) + self.Result.records.append(record) + except Exception as e: + raise RCAExecuteException("DDlDiskFullScene execute error: {0}".format(e)) + finally: + self.stdio.verbose("end DDlDiskFullScene execute") + + def export_result(self): + super().export_result() + + def get_scene_info(self): + + return {"name": "ddl_disk_full", + "info_en": "Insufficient disk space reported during DDL process. ", + "info_cn": 'DDL过程中报磁盘空间不足的问题', + } + + +ddl_disk_full = DDlDiskFullScene() \ No newline at end of file diff --git a/handler/rca/rca_scene/disconnection_scene.py b/handler/rca/scene/disconnection_scene.py similarity index 76% rename from handler/rca/rca_scene/disconnection_scene.py rename to handler/rca/scene/disconnection_scene.py index f7f72c6f..1ac6d6a2 100644 --- a/handler/rca/rca_scene/disconnection_scene.py +++ b/handler/rca/scene/disconnection_scene.py @@ -11,95 +11,94 @@ # See the Mulan PSL v2 for more details. """ -@time: 2023/12/22 -@file: disconnection_scene.py +@time: 2024/03/11 +@file: disconnectionScene.py @desc: """ import re -import time -import datetime +from handler.rca.rca_handler import RcaScene, RCA_ResultRecord +from common.tool import StringUtils -from common.command import get_obproxy_version -from common.logger import logger -from handler.rca.rca_scene.scene_base import scene_base, Result, RCA_ResultRecord -from utils.shell_utils import SshHelper -from utils.version_utils import compare_versions_greater - - -class DisconnectionScene(scene_base): +class DisconnectionScene(RcaScene): def __init__(self): super().__init__() - def init(self, cluster, nodes, obproxy_nodes, env, result_path): - super().init(cluster, nodes, obproxy_nodes, env, result_path) + def init(self, context): + super().init(context) + if self.obproxy_nodes is None or len(self.obproxy_nodes) == 0: + raise Exception("obproxy_nodes is empty") - for node in obproxy_nodes: + for node in self.obproxy_nodes: if "home_path" not in node or len(node["home_path"].strip()) == 0: + self.stdio.warn("obproxy_node home_path is empty") raise Exception("obproxy_node home_path is empty") - try: - is_ssh = True - ssh_helper = SshHelper(is_ssh, node.get("ip"), - node.get("user"), - node.get("password"), - node.get("port"), - node.get("private_key"), - node) - except Exception as e: - logger.error( - "SshHandler init fail. Please check the NODES conf. node: {0}. Exception : {1} .".format(node, e)) - raise Exception( - "SshHandler init fail. Please check the NODES conf node: {0} Exception : {1} .".format(node, e)) - obproxy_version = get_obproxy_version(True, ssh_helper, node.get("home_path")) - if obproxy_version is None: + ssh_helper = node["ssher"] + if ssh_helper is None: + raise Exception("obproxy_node:{0} ssher is None".format(node["ip"])) + obproxy_version = self.obproxy_version + if obproxy_version is None or len(obproxy_version.strip()) == 0: raise Exception("obproxy version is None. Please check the NODES conf.") - if not (obproxy_version == "4.2.2.0" or compare_versions_greater(obproxy_version, "4.2.2.0")): - raise Exception("obproxy version must be greater than 4.2.2.0. Please check the NODES conf.") + if not (obproxy_version == "4.2.2.0" or StringUtils.compare_versions_greater(obproxy_version, "4.2.2.0")): + raise Exception("DisconnectionScene's obproxy version must be greater than 4.2.2.0. Please check the NODES conf.") def execute(self): for node in self.obproxy_nodes: self.__execute_obproxy_one_node(node) - logger.info("end disconnectionScene execute all nodes") + self.stdio.verbose("end disconnectionScene execute all nodes") def export_result(self): return self.Result.export() - def __execute_obproxy_one_node(self, node): - ssh = SshHelper(True, node.get("ip"), - node.get("user"), - node.get("password"), - node.get("port"), - node.get("private_key"), - node) - all_log = ssh.ssh_exec_cmd( - 'grep "CONNECTION](trace_type" -m 100 $(ls {0}/log/obproxy_diagnosis.log* | head -10 ) '.format( - node['home_path']) - ) - - log_list = all_log.strip().split('\n') - for line in log_list: - try: - record = RCA_ResultRecord() - record.add_record( - "node:{1} obproxy_diagnosis_log:{0}".format(line, node.get("ip"))) - log_check = DisconnectionLog(line, record) - suggest = log_check.execute() - record.add_suggest(suggest) - logger.debug("suggest:{0}".format(suggest)) + def get_scene_info(self): + # 设定场景分析的返回场景使用说明,需要的参数等等 + return {"name": "disconnection", + "info_en": "root cause analysis of disconnection", + "info_cn": "针对断链接场景的根因分析", + } - # self.Result.suggest += "obproxy_diagnosis_log:{0}\nsuggest:{1}\n\n".format(line, suggest) - self.Result.records.append(record) - except Exception as e: - logger.warning("line in log_list is error, log: {0} ,err:{1}".format(line, e)) - continue + def __execute_obproxy_one_node(self, node): + self.gather_log.grep("CONNECTION](trace_type") + self.gather_log.set_parameters("nodes_list", [node]) + self.gather_log.set_parameters("target", "obproxy") + self.gather_log.set_parameters("scope", "obproxy_diagnosis") + if self.input_parameters.get("since") is not None: + since=self.input_parameters.get("since") + self.gather_log.set_parameters("since", since) + self.work_path = self.store_dir + logs_name=self.gather_log.execute() + if len(logs_name)==0: + self.stdio.warn("not found log about disconnection. On node: {0}".format(node["ip"])) + return + self.stdio.verbose("logs_name:{0}".format(logs_name)) + # read the log file + for name in logs_name: + self.stdio.verbose("read the log file: {0}".format(name)) + with open(name, 'r') as f: + log_list = f.read().strip().split('\n') + for line in log_list: + try: + record = RCA_ResultRecord() + record.add_record( + "node:{1} obproxy_diagnosis_log:{0}".format(line, node.get("ip"))) + log_check = DisconnectionLog(self.context,line, record) + suggest = log_check.execute() + record.add_suggest(suggest) + self.stdio.verbose("suggest:{0}".format(suggest)) + self.Result.records.append(record) + except Exception as e: + self.stdio.warn("line in log_list is error, log: {0} ,err:{1}".format(line, e)) + continue class DisconnectionLog: - def __init__(self, log, record): + def __init__(self,context, log, record): + self.context = context + self.stdio = context.stdio self.record = record - logger.debug("DisconnectionLog base:{0}".format(log)) + self.stdio.verbose("DisconnectionLog base:{0}".format(log)) if log is None or len(log.strip()) == 0: - logger.debug("log is None or len(log.strip()) == 0") + self.stdio.verbose("log is None or len(log.strip()) == 0") raise Exception("log is None or len(log.strip()) == 0") self.timeout_event = "" @@ -139,12 +138,12 @@ def __init__(self, log, record): record.add_record("cs_id:{0}, server_session_id:{1}".format(cs_id, server_session_id)) except Exception as e: - logger.error("DisconnectionLog err: {0}".format(e)) + self.stdio.error("DisconnectionLog err: {0}".format(e)) def execute(self): # self.get_suggest() try: - suggest = get_disconnectionSuggest(self.trace_type, self.error_code, self.error_msg, self.record) + suggest = get_disconnectionSuggest(self.context,self.trace_type, self.error_code, self.error_msg, self.record) return suggest except Exception as e: raise Exception("DisconnectionLog execute err: {0}".format(e)) @@ -255,7 +254,8 @@ def execute(self): } -def get_disconnectionSuggest(trace_type, error_code, error_msg, record): +def get_disconnectionSuggest(context,trace_type, error_code, error_msg, record): + stdio=context.stdio if trace_type == "" or error_code == "" or error_msg == "": raise Exception( "not find the suggest. Please contact the community and upload the exception information.. trace_type:{0}, error_code:{1}, error_msg:{2}".format( @@ -271,13 +271,13 @@ def get_disconnectionSuggest(trace_type, error_code, error_msg, record): for suggest_error_msg in error_msgs: # 子串 if suggest_error_msg in error_msg: - logger.info( + stdio.verbose( "find the suggest. trace_type:{0}, error_code:{1}, error_msg:{2}".format(trace_type, error_code, error_msg)) suggest += "\n" suggest += Suggest_error_code.get(suggest_error_msg) if suggest.strip() != "": - logger.info( + stdio.verbose( "find the suggest. trace_type:{0}, error_code:{1}, error_msg:{2}, suggest:{3}".format(trace_type, error_code, error_msg, @@ -287,12 +287,13 @@ def get_disconnectionSuggest(trace_type, error_code, error_msg, record): suggest = "not find the suggest. Please contact the community and upload the exception information.. trace_type:{0}, error_code:{1}, error_msg:{2}. The suggestions are as follows. You can try using the following suggestions or submit the logs to the Oceanbase community.".format( trace_type, error_code, error_msg) - suggest +="\n" + suggest += "\n" for error_msg_by_Suggest_error_code in Suggest_error_code: - suggest += Suggest_error_code.get(error_msg_by_Suggest_error_code)+"\n" + suggest += Suggest_error_code.get(error_msg_by_Suggest_error_code) + "\n" return suggest else: raise Exception("the disconnection error_code :{0} ,not support.".format(error_code)) else: raise Exception("the disconnection trace_type :{0} ,not support.".format(trace_type)) +disconnection=DisconnectionScene() \ No newline at end of file diff --git a/handler/rca/rca_scene/lock_conflict_scene.py b/handler/rca/scene/lock_conflict_scene.py similarity index 64% rename from handler/rca/rca_scene/lock_conflict_scene.py rename to handler/rca/scene/lock_conflict_scene.py index dab88102..6e0ab71a 100644 --- a/handler/rca/rca_scene/lock_conflict_scene.py +++ b/handler/rca/scene/lock_conflict_scene.py @@ -15,52 +15,27 @@ @file: lock_conflict_scene.py @desc: """ -from common.command import get_observer_version -from common.logger import logger -from common.ob_connector import OBConnector from handler.rca.rca_exception import RCAInitException, RCANotNeedExecuteException -from handler.rca.rca_scene.scene_base import scene_base, Result, RCA_ResultRecord -from utils.shell_utils import SshHelper -from utils.version_utils import compare_versions_greater +from handler.rca.rca_handler import RcaScene, RCA_ResultRecord +from common.tool import StringUtils -class LockConflictScene(scene_base): +class LockConflictScene(RcaScene): def __init__(self): super().__init__() - self.ob_connector = None - self.observer_nodes = None - self.ob_cluster = None - self.observer_version = None - self.default_node = None - def init(self, cluster, nodes, obproxy_nodes, env, result_path): + def init(self, context): try: - super().init(cluster, nodes, obproxy_nodes, env, result_path) - self.default_node = self.observer_nodes[0] - - ssh = SshHelper(True, self.default_node.get("ip"), - self.default_node.get("user"), - self.default_node.get("password"), - self.default_node.get("port"), - self.default_node.get("private_key"), - self.default_node) - self.observer_version = get_observer_version(True, ssh, self.default_node["home_path"]) - - self.ob_connector = OBConnector(ip=self.ob_cluster.get("db_host"), - port=self.ob_cluster.get("db_port"), - username=self.ob_cluster.get("tenant_sys").get("user"), - password=self.ob_cluster.get("tenant_sys").get("password"), - timeout=10000) - + super().init(context) + if self.observer_version is None or len(self.observer_version.strip()) == 0 or self.observer_version == "": + raise Exception("observer version is None. Please check the NODES conf.") except Exception as e: raise RCAInitException("LockConflictScene RCAInitException: ", e) def execute(self): - if self.observer_version is None or len(self.observer_version) == 0: - raise Exception("observer version is None. Please check the NODES conf.") - if self.observer_version == "4.2.0.0" or compare_versions_greater(self.observer_version, "4.2.0.0"): + if self.observer_version == "4.2.0.0" or StringUtils.compare_versions_greater(self.observer_version, "4.2.0.0"): self.__execute_4_2() - elif compare_versions_greater("4.2.2.0", self.observer_version): + elif StringUtils.compare_versions_greater("4.2.2.0", self.observer_version): self.__execute_old() else: raise Exception("observer version is {0}. Not support".format(self.observer_version)) @@ -76,36 +51,41 @@ def __execute_4_2(self): first_record.add_suggest("No block lock found. Not Need Execute") self.Result.records.append(first_record) raise RCANotNeedExecuteException("No block lock found.") - first_record.add_record("by select * from oceanbase.GV$OB_LOCKS where BLOCK=1; the len is {0}".format(len(data))) + first_record.add_record( + "by select * from oceanbase.GV$OB_LOCKS where BLOCK=1; the len is {0}".format(len(data))) for OB_LOCKS_data in data: trans_record = RCA_ResultRecord() first_record_records = first_record.records.copy() trans_record.records.extend(first_record_records) self.Result.records.append(trans_record) try: - if OB_LOCKS_data.get('TRANS_ID') is None: - trans_record.add_record("trans_id is null") - trans_record.add_suggest("trans_id is null. can not do next") + if OB_LOCKS_data.get('ID1') is None:# Holding lock session id + trans_record.add_record("Holding lock trans_id is null") + trans_record.add_suggest("Holding lock trans_id is null. can not do next") continue else: - trans_id = OB_LOCKS_data['TRANS_ID'] - trans_record.add_record("trans_id is {0}".format(trans_id)) + trans_id = OB_LOCKS_data['ID1'] + trans_record.add_record("holding lock trans_id is {0}".format(trans_id)) + wait_lock_trans_id=OB_LOCKS_data['TRANS_ID'] cursor_by_trans_id = self.ob_connector.execute_sql_return_cursor_dictionary( - 'select * from oceanbase.V$OB_TRANSACTION_PARTICIPANTS where TX_ID="{0}";'.format(trans_id)) + 'select * from oceanbase.V$OB_TRANSACTION_PARTICIPANTS where TX_ID="{0}";'.format(wait_lock_trans_id)) + self.stdio.verbose("get SESSION_ID by trans_id:{0}".format(trans_id)) + trans_record.add_record("wait_lock_trans_id is {0}".format(wait_lock_trans_id)) session_datas = cursor_by_trans_id.fetchall() trans_record.add_record( - "get SESSION_ID by trans_id:{0}. get data:{0}".format(trans_id, session_datas)) + "get SESSION_ID by wait_lock_trans_id:{0}. get data:{0}".format(trans_id, session_datas)) if len(session_datas) != 1: - trans_record.add_suggest( - "get SESSION_ID by trans_id:{0}. Maybe the lock is not exist".format(trans_id)) + trans_record.add_suggest("wait_lock_session_id is not get. The holding lock trans_id is {0}. You can resolve lock conflicts by killing this locked session, but this may cause business exceptions. Please use with caution.".format(trans_id)) continue if session_datas[0].get("SESSION_ID") is not None: trans_record.add_record("get SESSION_ID:{0}".format(session_datas[0].get("SESSION_ID"))) trans_record.add_suggest("Sessions corresponding to lock transactions. The ID is {0}, " "which may be a lock conflict issue.You can be accessed through kill " - "session_ Roll back the corresponding transaction with ID. Please " + "session to rollback the corresponding transaction with ID. Please " "note that this will result in corresponding transaction regression! " "".format(session_datas[0].get("SESSION_ID"))) + else: + trans_record.add_record("wait_lock_session_id is not get. The holding lock trans_id is {0}. You can resolve lock conflicts by killing this locked session, but this may cause business exceptions. Please use with caution.".format(trans_id)) except Exception as e: trans_record.add_record("get SESSION_ID panic. OB_LOCKS_data:{0} error: {1}".format(OB_LOCKS_data, e)) @@ -128,7 +108,6 @@ def __execute_old(self): len(virtual_lock_wait_stat_datas))) for trans_lock_data in virtual_lock_wait_stat_datas: - trans_id = trans_lock_data["block_session_id"] trans_record = RCA_ResultRecord() first_record_records = first_record.records.copy() @@ -144,5 +123,14 @@ def __execute_old(self): return + def get_scene_info(self): + return {"name": "lock_conflict", + "info_en": "root cause analysis of lock conflict", + "info_cn": "针对锁冲突的根因分析", + } + def export_result(self): - return self.Result.export() \ No newline at end of file + return self.Result.export() + + +lock_conflict = LockConflictScene() diff --git a/handler/rca/rca_scene/major_hold_scene.py b/handler/rca/scene/major_hold_scene.py similarity index 84% rename from handler/rca/rca_scene/major_hold_scene.py rename to handler/rca/scene/major_hold_scene.py index 293c216c..dee2ad5a 100644 --- a/handler/rca/rca_scene/major_hold_scene.py +++ b/handler/rca/scene/major_hold_scene.py @@ -17,53 +17,28 @@ """ import json import re - -from common.command import get_observer_version -from common.logger import logger -from common.ob_connector import OBConnector from handler.rca.rca_exception import RCAInitException, RCAExecuteException, RCANotNeedExecuteException -from handler.rca.rca_scene.scene_base import scene_base, Result, RCA_ResultRecord -from utils.shell_utils import SshHelper -from utils.time_utils import DateTimeEncoder -from utils.version_utils import compare_versions_greater +from handler.rca.rca_handler import RcaScene, RCA_ResultRecord +from common.tool import DateTimeEncoder +from common.tool import StringUtils -class MajorHoldScene(scene_base): +class MajorHoldScene(RcaScene): def __init__(self): super().__init__() - self.local_path = None - self.ob_cluster = None - self.observer_nodes = [] - self.observer_version = "" - self.ob_connector = None - self.Result = Result() - - def init(self, cluster, nodes, obproxy_nodes, env, result_path): + self.local_path = "" + + def init(self, context): try: - super().__init__() - self.Result.set_save_path(result_path) - self.ob_cluster = cluster - self.observer_nodes = nodes - self.local_path = result_path - node = self.observer_nodes[0] - ssh = SshHelper(True, node.get("ip"), - node.get("user"), - node.get("password"), - node.get("port"), - node.get("private_key"), - node) - self.observer_version = get_observer_version(True, ssh, node["home_path"]) + super().init(context) + self.local_path = context.get_variable('result_path') + if self.observer_version is None: raise Exception("obproxy version is None. Please check the NODES conf.") - if not (self.observer_version == "4.0.0.0" or compare_versions_greater(self.observer_version, "4.0.0.0")): + if not (self.observer_version == "4.0.0.0" or StringUtils.compare_versions_greater(self.observer_version, "4.0.0.0")): raise Exception("observer version must be greater than 4.0.0.0. Please check the NODES conf.") - self.ob_connector = OBConnector(ip=self.ob_cluster.get("db_host"), - port=self.ob_cluster.get("db_port"), - username=self.ob_cluster.get("tenant_sys").get("user"), - password=self.ob_cluster.get("tenant_sys").get("password"), - timeout=10000) except Exception as e: raise RCAInitException("MajorHoldScene RCAInitException: {0}".format(e)) @@ -89,7 +64,7 @@ def execute(self): err_tenant_ids.extend(CDB_OB_MAJOR_COMPACTION_err_tenant_ids) except Exception as e: - logger.warning("MajorHoldScene execute CDB_OB_MAJOR_COMPACTION panic: {0}".format(e)) + self.stdio.warn("MajorHoldScene execute CDB_OB_MAJOR_COMPACTION panic: {0}".format(e)) raise RCAExecuteException("MajorHoldScene execute CDB_OB_MAJOR_COMPACTION panic: {0}".format(e)) # __all_virtual_compaction_diagnose_info里存在status=FAILED的记录 try: @@ -108,7 +83,7 @@ def execute(self): __all_virtual_compaction_diagnose_info_err_tenant_ids)) err_tenant_ids.extend(__all_virtual_compaction_diagnose_info_err_tenant_ids) except Exception as e: - logger.error("MajorHoldScene execute CDB_OB_MAJOR_COMPACTION panic: {0}".format(e)) + self.stdio.error("MajorHoldScene execute CDB_OB_MAJOR_COMPACTION panic: {0}".format(e)) raise RCAExecuteException("MajorHoldScene execute CDB_OB_MAJOR_COMPACTION panic: {0}".format(e)) # GV$OB_COMPACTION_PROGRESS表中,根据上一次合并记录中的data_size/(estimated_finish_time-start_time)与当前合并版本记录中(data_size-unfinished_data_size)/(当前时间-start_time)相比,如果差距过大(当前合并比上一次合并慢很多,以5倍为指标) try: @@ -125,11 +100,12 @@ def execute(self): first_record.add_record( "merge tasks that have not ended beyond the expected time,the tenant_id is {0}".format( time_out_merge_err_tenant_ids)) - logger.info("merge tasks that have not ended beyond the expected time,the tenant_id is {0}".format( - time_out_merge_err_tenant_ids)) + self.stdio.verbose( + "merge tasks that have not ended beyond the expected time,the tenant_id is {0}".format( + time_out_merge_err_tenant_ids)) err_tenant_ids.extend(time_out_merge_err_tenant_ids) except Exception as e: - logger.error("MajorHoldScene execute GV$OB_COMPACTION_PROGRESS panic: {0}".format(e)) + self.stdio.error("MajorHoldScene execute GV$OB_COMPACTION_PROGRESS panic: {0}".format(e)) raise RCAExecuteException("MajorHoldScene execute GV$OB_COMPACTION_PROGRESS panic: {0}".format(e)) if not need_tag: first_record.add_suggest("major merge abnormal situation not need execute") @@ -138,14 +114,14 @@ def execute(self): else: err_tenant_ids = list(set(err_tenant_ids)) first_record.add_suggest("some tenants need execute MajorHoldScene. :{0}".format(err_tenant_ids)) - logger.info("On CDB_OB_MAJOR_COMPACTION") + self.stdio.verbose("On CDB_OB_MAJOR_COMPACTION") # execute record need more for err_tenant_id in err_tenant_ids: tenant_record = RCA_ResultRecord() - first_record_records=first_record.records.copy() + first_record_records = first_record.records.copy() tenant_record.records.extend(first_record_records) - logger.info("tenant_id is {0}".format(err_tenant_id)) + self.stdio.verbose("tenant_id is {0}".format(err_tenant_id)) tenant_record.add_record("tenant_id is {0}".format(err_tenant_id)) # 1 try: @@ -165,7 +141,7 @@ def execute(self): except Exception as e: tenant_record.add_record("#1 on CDB_OB_MAJOR_COMPACTION get data failed") - logger.warning("MajorHoldScene execute exception: {0}".format(e)) + self.stdio.warn("MajorHoldScene execute exception: {0}".format(e)) pass # 2 try: @@ -185,7 +161,7 @@ def execute(self): except Exception as e: tenant_record.add_record("#2&3 on __all_virtual_compaction_diagnose_info get data failed") - logger.warning("#2&3 MajorHoldScene execute exception: {0}".format(e)) + self.stdio.warn("#2&3 MajorHoldScene execute exception: {0}".format(e)) pass # 4 @@ -231,20 +207,17 @@ def execute(self): svr_ip = svrs[0][4] svr_port = svrs[0][5] node = None + ssh_helper = None for observer_node in self.observer_nodes: if observer_node["ip"] == svr_ip and observer_node["port"] == svr_port: node = observer_node + ssh_helper = observer_node["ssher"] if node == None: - logger.error( + self.stdio.error( "can not find ls_svr by TENANT_ID:{2} ip:{0},port:{1}".format(svr_ip, svr_port, err_tenant_id)) break - ssh_helper = SshHelper(True, node.get("ip"), - node.get("user"), - node.get("password"), - node.get("port"), - node.get("private_key"), - node) + log_name = "/tmp/major_hold_scene_4_major_merge_progress_checker_{0}.log".format(err_tenant_id) ssh_helper.ssh_exec_cmd( 'grep "major_merge_progress_checker" {0}/log/rootservice.log* | grep T{1} -m500 >{2}'.format( @@ -253,7 +226,7 @@ def execute(self): tenant_record.add_record("download {0} to {1}".format(log_name, self.local_path)) ssh_helper.ssh_exec_cmd("rm -rf {0}".format(log_name)) except Exception as e: - logger.error("MajorHoldScene execute 4 exception: {0}".format(e)) + self.stdio.error("MajorHoldScene execute 4 exception: {0}".format(e)) raise RCAExecuteException("MajorHoldScene execute 4 exception: {0}".format(e)) # 5 @@ -272,7 +245,7 @@ def execute(self): file_name)) except Exception as e: - logger.warning("MajorHoldScene execute 5 exception: {0}".format(e)) + self.stdio.warn("MajorHoldScene execute 5 exception: {0}".format(e)) tenant_record.add_suggest("send the {0} to the oceanbase community".format(self.local_path)) self.Result.records.append(tenant_record) @@ -304,17 +277,14 @@ def diagnose_info_switch(self, sql_data, tenant_record): diagnose_info = sql_data[8] if "schedule medium failed" in diagnose_info: node = None + ssh_helper = None for observer_node in self.observer_nodes: if svr_ip == observer_node.get("ip"): node = observer_node + ssh_helper = observer_node["ssher"] if node is None: raise RCAExecuteException("can not find observer node by ip:{0}, port:{1}".format(svr_ip, svr_port)) - ssh_helper = SshHelper(True, node.get("ip"), - node.get("user"), - node.get("password"), - node.get("port"), - node.get("private_key"), - node) + log_name = "/tmp/rca_major_hold_schedule_medium_failed_{1}_{2}_{0}.txt".format(tenant_id, svr_ip, svr_port) tenant_record.add_record( @@ -347,18 +317,15 @@ def diagnose_info_switch(self, sql_data, tenant_record): "diagnose_info type is error_no. error_no: {0}, err_trace:{1}, table_id:{2}, tenant_id:{3}, compaction_scn: {4}, global_broadcast_scn: {5}. compaction_scn&1|awk '{print $2}'|awk -F '.' '{print $1}'` - -if (( $PY_VERSION == 3 )) -then - export LD_LIBRARY_PATH=$(ld --verbose | grep SEARCH_DIR | tr -s ' ;' \\012 | awk -F\" '{print $2}' | sed 's/=//' | tr '\n' ':')$OBDIAG_DIR/dependencies/python3/libs/ - export PYTHONPATH=$OBDIAG_DIR/dependencies/python3/site-packages:$PYTHONPATH - $OBDIAG_PYTHON $OBDIAG_DIR/dependencies/check_dependencies.py -else - echo "Please make sure Python3 exists in your environment" - exit 1 -fi - -for i in "$@" -do - if [[ $i =~ $whitespace ]] - then - i="\"$i\"" - fi - final_str=${final_str}${blank_str}${i} -done -PROG="$0" $OBDIAG_PYTHON $OBDIAG_DIR/obdiag_main.py $final_str - diff --git a/obdiag_client.py b/obdiag_client.py deleted file mode 100644 index e6d45f8c..00000000 --- a/obdiag_client.py +++ /dev/null @@ -1,561 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@file: obdiag_client.py -@desc: -""" -import uuid - -from prettytable import PrettyTable - -from common.command import get_obdiag_display -from common.constant import const -from handler.analyzer.analyze_flt_trace import AnalyzeFltTraceHandler -from handler.analyzer.analyze_log import AnalyzeLogHandler -from handler.checker.check_handler import CheckHandler -from handler.checker.check_list import CheckListHandler -from handler.gather.gather_log import GatherLogHandler -from handler.gather.gather_awr import GatherAwrHandler -from handler.gather.gather_obproxy_log import GatherObProxyLogHandler -from handler.gather.gather_sysstat import GatherOsInfoHandler -from handler.gather.gather_obstack2 import GatherObstack2Handler -from handler.gather.gather_obadmin import GatherObAdminHandler -from handler.gather.gather_perf import GatherPerfHandler -from handler.gather.gather_plan_monitor import GatherPlanMonitorHandler -from handler.gather.gather_scenes import GatherSceneHandler -from handler.gather.scenes.list import GatherScenesListHandler -from handler.rca.rca_list import RcaScenesListHandler -from common.config_helper import ConfigHelper -import base64 -import os -import sys -from common.logger import logger -from handler.rca.rca_handler import RCAHandler -from telemetry.telemetry import telemetry -from update.update import UpdateHandler -from utils.time_utils import get_current_us_timestamp -from utils.utils import display_trace -from utils.yaml_utils import read_yaml_data -from utils.version_utils import print_obdiag_version -from colorama import Fore, Style - -if getattr(sys, 'frozen', False): - absPath = os.path.dirname(os.path.abspath(sys.executable)) -else: - absPath = os.path.dirname(os.path.abspath(__file__)) -INNER_CONFIG_FILE = os.path.join(absPath, "conf/inner_config.yml") - -DEFAULT_CONFIG_FILE = os.path.join(os.path.expanduser('~'), ".obdiag/config.yml") - - -class OBDIAGClient(object): - def __new__(cls, *args, **kwargs): - if not hasattr(cls, '_inst'): - cls._inst = super(OBDIAGClient, cls).__new__(cls, *args, **kwargs) - cls._inited = False - return cls._inst - - def __init__(self): - - if not self._inited: - self.config_file = const.DEFAULT_CONFIG_PATH - self.inner_config_file = INNER_CONFIG_FILE - self.gather_timestamp = get_current_us_timestamp() - self.config = None - self.inner_config = None - self.observer_nodes = [] - self.obproxy_nodes = [] - self.other_nodes = [] - self.ocp = None - self._inited = True - self.ocp_url = None - self.ocp_user = None - self.ocp_password = None - self.ocp_is_exits = None - # ocp metadb - self.ocp_metadb_user = None - self.ocp_metadb_password = None - self.ocp_metadb_ip = None - self.ocp_metadb_port = None - self.ocp_metadb_name = None - # gather handler - self.gather_awr_handler = None - self.gather_log_handler = None - self.gather_sysstat_handler = None - self.gather_obstack_handler = None - self.gather_perf_handler = None - self.gather_clog_handler = None - self.gather_slog_handler = None - self.gather_plan_monitor_handler = None - self.gather_obproxy_log_handler = None - self.handle_gather_scene_handler = None - self.handle_gather_scene_list_handler = None - self.handle_rca_scenes_list_handler = None - self.handle_check_list_handler = None - # analyze handler - self.analyze_log_handler = None - self.analyze_flt_trace_handler = None - # params - self.default_collect_pack_dir = "" - # obdiag basic config - self.basic_config = None - # obdiag check - self.check_handler = None - self.obproxy_cluster = None - self.ob_cluster = None - self.obdiag_log_file = os.path.join( - os.path.expanduser(const.OBDIAG_BASE_DEFAULT_CONFIG["obdiag"]["logger"]["log_dir"]), - const.OBDIAG_BASE_DEFAULT_CONFIG["obdiag"]["logger"]["log_filename"]) - # obdiag rca - self.rca_result_path = None - self.handle_update_handler = None - - def init(self, args): - if "c" in args and (getattr(args, "c") is not None): - self.config_file = os.path.abspath(getattr(args, "c")) - self.read_config(self.config_file) - self.read_inner_config(self.inner_config_file) - self.init_basic_config() - if self.inner_config.get("obdiag") is not None and self.inner_config.get("obdiag").get( - "basic") is not None and self.inner_config.get("obdiag").get("basic").get( - "telemetry") is not None and self.inner_config.get("obdiag").get("basic").get("telemetry") is False: - telemetry.work_tag = False - if ("gather_log" in args) or ("gather_obstack" in args) or ("gather_perf" in args) or ( - "gather_clog" in args) or ("gather_slog" in args) or ("analyze_log" in args): - self.init_obcluster_config() - return self.init_observer_node_config() - elif "gather_awr" in args: - return self.init_ocp_config() - elif "gather_obproxy_log" in args: - self.init_obproxy_config() - return self.init_obproxy_node_config() - elif ("analyze_flt_trace" in args) or ("gather_sysstat" in args): - self.init_obcluster_config() - sucess_1 = self.init_observer_node_config() - self.init_obproxy_config() - sucess_2 = self.init_obproxy_node_config() - return sucess_1 or sucess_2 - elif "check" in args: - self.init_obcluster_config() - sucess_1 = self.init_observer_node_config() - self.init_obproxy_config() - sucess_2 = self.init_obproxy_node_config() - sucess_3 = self.init_checker_config() - return sucess_3 and (sucess_1 or sucess_2) - elif "gather_plan_monitor" in args: - return self.init_obcluster_config() - elif ("gather_scene" in args) or ("gather_scene_list" in args): - self.init_obcluster_config() - sucess_1 = self.init_observer_node_config() - self.init_obproxy_config() - sucess_2 = self.init_obproxy_node_config() - sucess_3 = self.init_gather_scene_config() - return sucess_3 and (sucess_1 or sucess_2) - elif "rca_run" in args: - self.init_obcluster_config() - sucess_1 = self.init_observer_node_config() - self.init_obproxy_config() - sucess_2 = self.init_obproxy_node_config() - sucess_3 = self.init_rca_config() - return sucess_3 and (sucess_1 or sucess_2) - - def init_observer_node_config(self): - try: - observer_nodes = [] - if self.ob_cluster is not None: - ob_cluster = self.ob_cluster - cluster_name = ob_cluster.get("ob_cluster_name") - db_host = ob_cluster.get("db_host") - - db_port = get_conf_data_str(ob_cluster.get("db_port"), 2881) - - ob_servers = ob_cluster.get("servers") - global_values = ob_servers.get("global") - - global_ssh_user_name = get_conf_data_str(global_values.get("ssh_username"), "root") - - global_ssh_password = get_conf_data_str(global_values.get("ssh_password"), "") - - global_ssh_port = get_conf_data_str(global_values.get("ssh_port"), 22) - - global_ssh_type = get_conf_data_str(global_values.get("ssh_type"), "remote") - - global_container_name = get_conf_data_str(global_values.get("container_name"), "") - - global_home_path = get_conf_data_str(global_values.get("home_path"), const.OB_INSTALL_DIR_DEFAULT) - - global_ssh_key_file = get_conf_data_str(global_values.get("ssh_key_file"), "") - - global_data_dir = get_conf_data_str(global_values.get("data_dir"), - os.path.join(global_home_path, "store").strip()) - - global_redo_dir = get_conf_data_str(global_values.get("redo_dir"), global_data_dir) - - global_node_ip = global_values.get("ip") - nodes = ob_servers.get("nodes") - for node in nodes: - node_config = {} - node_config["cluster_name"] = cluster_name - node_config["host_type"] = "OBSERVER" - node_config["db_host"] = db_host - node_config["db_port"] = db_port - node_config["ip"] = get_conf_data_str(node.get("ip"), global_node_ip) - node_config["port"] = get_conf_data_str(node.get("port"), global_ssh_port) - node_config["home_path"] = get_conf_data_str(node.get("home_path"), global_home_path) - node_config["user"] = get_conf_data_str(node.get("ssh_username"), global_ssh_user_name) - node_config["password"] = get_conf_data_str(node.get("ssh_password"), global_ssh_password) - node_config["private_key"] = get_conf_data_str(node.get("ssh_key_file"), global_ssh_key_file) - node_config["data_dir"] = get_conf_data_str(node.get("data_dir"), global_data_dir) - node_config["redo_dir"] = get_conf_data_str(node.get("redo_dir"), global_redo_dir) - node_config["ssh_type"] = get_conf_data_str(node.get("ssh_type"), global_ssh_type) - node_config["container_name"] = get_conf_data_str(node.get("container_name"), global_container_name) - observer_nodes.append(node_config) - else: - return False - self.observer_nodes = observer_nodes - return True - except Exception as e: - logger.error("observer node config init Failed, error:{0}".format(e)) - return False - - def init_obproxy_node_config(self): - try: - obproxy_nodes = [] - if self.obproxy_cluster is not None: - obproxy_cluster = self.obproxy_cluster - cluster_name = obproxy_cluster.get("obproxy_cluster_name") - obproxy_servers = obproxy_cluster.get("servers") - global_values = obproxy_servers.get("global") - global_ssh_user_name = get_conf_data_str(global_values.get("ssh_username"), "root") - global_ssh_password = get_conf_data_str(global_values.get("ssh_password"), "") - global_ssh_port = get_conf_data_str(global_values.get("ssh_port"), 22) - global_ssh_type = get_conf_data_str(global_values.get("ssh_type"), "remote") - global_container_name = get_conf_data_str(global_values.get("container_name"), "") - global_home_path = get_conf_data_str(global_values.get("home_path"), const.OBPROXY_INSTALL_DIR_DEFAULT) - global_ssh_key_file = get_conf_data_str(global_values.get("ssh_key_file"), "") - global_data_dir = get_conf_data_str(global_values.get("data_dir"), - os.path.join(global_home_path, "store").strip()) - global_redo_dir = get_conf_data_str(global_values.get("redo_dir"), global_data_dir) - global_node_ip = global_values.get("ip") - - nodes = obproxy_servers.get("nodes") - for node in nodes: - node_config = {} - node_ip = node.get("ip") - if node_ip: - node_config["host_type"] = "OBPROXY" - node_config["cluster_name"] = cluster_name - node_config["ip"] = get_conf_data_str(node.get("ip"), global_node_ip) - node_config["port"] = get_conf_data_str(node.get("port"), global_ssh_port) - node_config["home_path"] = get_conf_data_str(node.get("home_path"), global_home_path) - node_config["user"] = get_conf_data_str(node.get("ssh_username"), global_ssh_user_name) - node_config["password"] = get_conf_data_str(node.get("ssh_password"), global_ssh_password) - node_config["private_key"] = get_conf_data_str(node.get("ssh_key_file"), global_ssh_key_file) - node_config["data_dir"] = get_conf_data_str(node.get("data_dir"), global_data_dir) - node_config["redo_dir"] = get_conf_data_str(node.get("redo_dir"), global_redo_dir) - node_config["ssh_type"] = get_conf_data_str(node.get("ssh_type"), global_ssh_type) - node_config["container_name"] = get_conf_data_str(node.get("container_name"), - global_container_name) - obproxy_nodes.append(node_config) - self.obproxy_nodes = obproxy_nodes - return True - else: - return False - except Exception as e: - logger.error("obproxy node config init Failed, error:{0}".format(e)) - return False - - def init_basic_config(self): - try: - if self.inner_config.get("obdiag"): - self.basic_config = self.inner_config.get("obdiag").get("basic") - self.obdiag_log_file = os.path.join( - os.path.expanduser(self.inner_config.get("obdiag").get("logger").get("log_dir")), - self.inner_config.get("obdiag").get("logger").get("log_filename")) - self.config_file = os.path.expanduser(self.basic_config.get("config_path")) - except: - self.basic_config = const.OBDIAG_BASE_DEFAULT_CONFIG["obdiag"]["basic"] - self.obdiag_log_file = os.path.join( - os.path.expanduser(const.OBDIAG_BASE_DEFAULT_CONFIG["obdiag"]["logger"]["log_dir"]), - const.OBDIAG_BASE_DEFAULT_CONFIG["obdiag"]["logger"]["log_filename"]) - - def init_ocp_config(self): - try: - ocp = self.config.get("ocp") - if ocp is not None: - self.ocp = ocp - self.ocp_url = self.ocp.get("login").get("url") - self.ocp_user = self.ocp.get("login").get("user") - self.ocp_password = self.ocp.get("login").get("password") - return True - else: - return False - except Exception as e: - logger.warning("ocp config init Failed, error:{0}".format(e)) - return False - - def init_checker_config(self): - try: - check_config = self.inner_config.get("check") - if check_config is None: - check_config = const.OBDIAG_CHECK_DEFAULT_CONFIG - self.check_report_path = check_config["report"]["report_path"] - self.check_report_type = check_config["report"]["export_type"] - self.check_ignore_version = check_config["ignore_version"] - self.check_work_path = check_config["work_path"] - return True - except Exception as e: - logger.error("checker config init Failed, error:{0}".format(e)) - return False - - def init_gather_scene_config(self): - try: - gather_scene_config = self.inner_config.get("gather") - if gather_scene_config is None: - gather_scene_config = const.OBDIAG_GATHER_DEFAULT_CONFIG - self.gather_scene_base_path = gather_scene_config["scenes_base_path"] - return True - except Exception as e: - logger.error("gather scene config init Failed, error:{0}".format(e)) - return False - - def init_rca_config(self): - try: - rca_config = self.inner_config.get("rca") - if rca_config is None: - rca_config = const.OBDIAG_RCA_DEFAULT_CONFIG - self.rca_result_path = rca_config["result_path"] - self.rca_result_path = os.path.dirname(self.rca_result_path) - if not os.path.isdir(self.rca_result_path): - logger.warning("rca_result_path is not exist ,mkdir it: {0}".format(self.rca_result_path)) - os.makedirs(self.rca_result_path) - return True - except Exception as e: - logger.error("rca config init Failed, error:{0}".format(e)) - return False - - def init_obproxy_config(self): - if self.config is None: - logger.error("obproxy config file not found") - return False - try: - config = self.config.get("obproxy") - if config: - self.obproxy_cluster = self.config.get("obproxy") - return True - except Exception as e: - logger.error("obproxy config init Failed, error:{0}".format(e)) - return False - - def init_obcluster_config(self): - if self.config is None: - logger.error("obcluster config file not found") - return False - try: - config = self.config.get("obcluster") - if config: - self.ob_cluster = self.config.get("obcluster") - return True - except Exception as e: - logger.error("obcluster config init Failed, error:{0}".format(e)) - return False - - def read_config(self, config_file): - if not os.path.exists(config_file): - os.system(r"touch {}".format(config_file)) - self.config = read_yaml_data(config_file) - - def read_inner_config(self, config_file): - self.inner_config = read_yaml_data(config_file) - - def obdiag_version(self, args): - return print_obdiag_version() - - def obdiag_display(self, args): - trace_id = "" - if getattr(args, "trace_id") is not None: - trace_id = getattr(args, "trace_id")[0] - return get_obdiag_display(self.obdiag_log_file, trace_id) - - def quick_build_configuration(self, args): - try: - user = getattr(args, "u")[0] - if getattr(args, "p"): - password = getattr(args, "p")[0] - else: - password = "" - host = getattr(args, "h")[0] - port = getattr(args, "P")[0] - config_helper = ConfigHelper(user, password, host, port) - config_helper.build_configuration(args, self.config_file, INNER_CONFIG_FILE) - except Exception as e: - logger.error("Configuration generation failed, error:{0}".format(e)) - - def handle_gather_log_command(self, args): - self.gather_log_handler = GatherLogHandler(self.observer_nodes, self.default_collect_pack_dir, - self.gather_timestamp, - self.basic_config) - return self.gather_log_handler.handle(args) - - def handle_gather_sysstat_command(self, args): - self.gather_sysstat_handler = GatherOsInfoHandler(self.observer_nodes, self.default_collect_pack_dir, - self.gather_timestamp, self.basic_config) - return self.gather_sysstat_handler.handle(args) - - def handle_gather_obstack_command(self, args): - self.gather_obstack_handler = GatherObstack2Handler(self.observer_nodes, self.default_collect_pack_dir, - self.gather_timestamp, self.basic_config) - return self.gather_obstack_handler.handle(args) - - def handle_gather_perf_command(self, args): - self.gather_perf_handler = GatherPerfHandler(self.observer_nodes, self.default_collect_pack_dir, - self.gather_timestamp, - self.basic_config) - return self.gather_perf_handler.handle(args) - - def handle_gather_clog_command(self, args): - self.gather_clog_handler = GatherObAdminHandler(self.observer_nodes, self.default_collect_pack_dir, - self.gather_timestamp, "clog", self.basic_config) - return self.gather_clog_handler.handle(args) - - def handle_gather_slog_command(self, args): - self.gather_slog_handler = GatherObAdminHandler(self.observer_nodes, self.default_collect_pack_dir, - self.gather_timestamp, "slog", self.basic_config) - return self.gather_slog_handler.handle(args) - - def handle_gather_awr_command(self, args): - self.gather_awr_handler = GatherAwrHandler(self.ocp, self.default_collect_pack_dir, self.gather_timestamp) - return self.gather_awr_handler.handle(args) - - def handle_gather_plan_monitor(self, args): - self.gather_plan_monitor_handler = GatherPlanMonitorHandler(self.ob_cluster, self.default_collect_pack_dir, - self.gather_timestamp) - return self.gather_plan_monitor_handler.handle(args) - - def handle_gather_scene_command(self, args): - if getattr(args,"dis_update") is not None and getattr(args,"dis_update")[0] == True: - logger.info("You can update the latest file by not adding --dis_update=Ture") - else: - self.handle_update_handler = UpdateHandler() - self.handle_update_handler.execute("") - logger.info("If you do not want automatic updates. You can adding --dis_update=Ture") - self.handle_gather_scene_handler = GatherSceneHandler(self.obproxy_cluster, self.obproxy_nodes, self.ob_cluster, - self.observer_nodes, self.default_collect_pack_dir, - self.gather_timestamp, self.gather_scene_base_path) - return self.handle_gather_scene_handler.handle(args) - - def handle_gather_scene_list_command(self, args): - self.handle_gather_scene_list_handler = GatherScenesListHandler(self.gather_scene_base_path) - return self.handle_gather_scene_list_handler.handle(args) - - def handle_gather_obproxy_log_command(self, args): - self.gather_obproxy_log_handler = GatherObProxyLogHandler(self.obproxy_nodes, self.default_collect_pack_dir, - self.gather_timestamp, self.basic_config) - return self.gather_obproxy_log_handler.handle(args) - - @staticmethod - def handle_password_encrypt(args): - logger.info("Input password=[{0}], encrypted password=[{1}]".format(args.password[0], - base64.b64encode( - args.password[0].encode()))) - return - - def handle_analyze_log_command(self, args): - self.analyze_log_handler = AnalyzeLogHandler(self.observer_nodes, self.default_collect_pack_dir, - self.gather_timestamp, - self.basic_config) - return self.analyze_log_handler.handle(args) - - def handle_analyze_flt_trace_command(self, args): - nodes = [] - nodes.extend(self.observer_nodes) - nodes.extend(self.obproxy_nodes) - nodes.extend(self.other_nodes) - self.analyze_flt_trace_handler = AnalyzeFltTraceHandler(nodes, self.default_collect_pack_dir) - return self.analyze_flt_trace_handler.handle(args) - - def handle_check_command(self, args): - if getattr(args,"dis_update") is not None and getattr(args,"dis_update")[0] == True: - logger.info("You can update the latest file by not adding --dis_update=Ture") - else: - self.handle_update_handler = UpdateHandler() - self.handle_update_handler.execute("") - logger.info("If you do not want automatic updates. You can adding --dis_update=Ture") - obproxy_check_handler = None - observer_check_handler = None - if self.obproxy_cluster is not None: - obproxy_check_handler = CheckHandler(ignore_version=self.check_ignore_version, cluster=self.obproxy_cluster, - nodes=self.obproxy_nodes, - export_report_path=self.check_report_path, - export_report_type=self.check_report_type, - work_path=self.check_work_path, - check_target_type="obproxy") - obproxy_check_handler.handle(args) - obproxy_check_handler.execute() - if self.ob_cluster is not None: - observer_check_handler = CheckHandler(ignore_version=self.check_ignore_version, cluster=self.ob_cluster, - nodes=self.observer_nodes, - export_report_path=self.check_report_path, - export_report_type=self.check_report_type, - work_path=self.check_work_path, - check_target_type="observer") - observer_check_handler.handle(args) - observer_check_handler.execute() - if obproxy_check_handler is not None: - print("Check obproxy finished. For more details, please run cmd '" + Fore.YELLOW + " cat {0} ".format( - obproxy_check_handler.report.get_report_path()) + Style.RESET_ALL + "'") - if observer_check_handler is not None: - print("Check observer finished. For more details, please run cmd'" + Fore.YELLOW + " cat {0} ".format( - observer_check_handler.report.get_report_path()) + Style.RESET_ALL + "'") - - return - - def handle_check_list_command(self, args): - self.handle_check_list_handler = CheckListHandler(self.check_work_path) - return self.handle_check_list_handler.handle() - - def handle_rca_run_command(self, args): - try: - rca_handler = RCAHandler(cluster=self.ob_cluster, - nodes=self.observer_nodes, - obproxy_nodes=self.obproxy_nodes, - result_path=self.rca_result_path or "./rca") - rca_handler.handle(args) - rca_handler.execute() - logger.info( - "rca finished. For more details, the result on '" + Fore.YELLOW + rca_handler.get_result_path() + Style.RESET_ALL + "' \nYou can get the suggest by '" + Fore.YELLOW + "cat " + rca_handler.get_result_path() + "/record" + Style.RESET_ALL + "'") - except Exception as e: - logger.error("rca failed! error msg:" + str(e)) - finally: - display_trace(uuid.uuid3(uuid.NAMESPACE_DNS, str(os.getpid()))) - - return - - def handle_rca_list_command(self, args): - self.handle_rca_scenes_list_handler = RcaScenesListHandler() - return self.handle_rca_scenes_list_handler.handle(args) - - def handle_update_command(self, args): - self.handle_update_handler = UpdateHandler() - if getattr(args, 'force'): - return self.handle_update_handler.execute(getattr(args, 'file'),force=True) - return self.handle_update_handler.execute(getattr(args, 'file'),force=False) - - -def get_conf_data_str(value, dafult_value): - if value is None: - return dafult_value - elif isinstance(value, str): - return value.strip() - - return value diff --git a/obdiag_main.py b/obdiag_main.py deleted file mode 100644 index 6bf6d279..00000000 --- a/obdiag_main.py +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@file: obdiag_main.py -@desc: -""" - -from common.logger import logger - -from obdiag_client import OBDIAGClient -from telemetry.telemetry import telemetry -from utils.parser_utils import ArgParser - -CONFIG_PARSE_IGNORE_ATTR = ["start_date", "end_date"] -DEFAULT_SINCE_HOURS = 12 - - -def pharse_config(args): - try: - if args.config: - args.config(args) - except AttributeError: - logger.debug("object has no attribute 'config' pass quick config\n") - - -def gather_log(args): - try: - if args.gather_log: - args.gather_log(args) - except AttributeError: - logger.debug("object has no attribute 'gather_log' pass gather log\n") - - -def gather_awr(args): - try: - if args.gather_awr: - args.gather_awr(args) - except AttributeError: - logger.debug("object has no attribute 'gather_awr' pass gather awr\n") - - -def gather_sysstat(args): - try: - if args.gather_sysstat: - args.gather_sysstat(args) - except AttributeError: - logger.debug("object has no attribute 'gather_sysstat' pass gather sysstat info\n") - - -def gather_perf(args): - try: - if args.gather_perf: - args.gather_perf(args) - except AttributeError: - logger.debug("object has no attribute 'gather_perf' pass gather perf info\n") - - -def gather_obstack(args): - try: - if args.gather_obstack: - args.gather_obstack(args) - except AttributeError: - logger.debug("object has no attribute 'gather_obstack' pass gather ob stack\n") - - -def gather_plan_monitor(args): - try: - if args.gather_plan_monitor: - args.gather_plan_monitor(args) - except AttributeError: - logger.debug("object has no attribute 'gather_plan_monitor' pass gather ob sql plan monitor\n") - - -def gather_clog(args): - try: - if args.gather_clog: - args.gather_clog(args) - except AttributeError: - logger.debug("object has no attribute 'gather_clog' pass gather clog\n") - - -def gather_slog(args): - try: - if args.gather_slog: - args.gather_slog(args) - except AttributeError: - logger.debug("object has no attribute 'gather_slog' pass gather slog\n") - - -def gather_obproxy_log(args): - try: - if args.gather_obproxy_log: - args.gather_obproxy_log(args) - except AttributeError: - logger.debug("object has no attribute 'gather_obproxy_log' pass gather obproxy log\n") - - -def gather_scene(args): - try: - if args.gather_scene: - args.gather_scene(args) - except AttributeError: - logger.debug("object has no attribute 'gather_scene' pass gather scene\n") - -def gather_scene_list(args): - try: - if args.gather_scene_list: - args.gather_scene_list(args) - except AttributeError: - logger.debug("object has no attribute 'gather_scene_list' pass gather scene list\n") - - -def get_version(args): - try: - if args.version: - args.version(args) - except AttributeError: - logger.debug("object has no attribute 'version'\n") - - -def get_obdiag_trace_log(args): - try: - if args.display: - args.display(args) - except AttributeError: - logger.debug("object has no attribute 'display'\n") - - -def analyze_log(args): - try: - if args.analyze_log: - args.analyze_log(args) - except AttributeError: - logger.debug("object has no attribute 'analyze_log' pass analyze log\n") - - -def analyze_flt_trace(args): - try: - if args.analyze_flt_trace: - args.analyze_flt_trace(args) - except AttributeError: - logger.debug("object has no attribute 'analyze_flt_trace' pass analyze trace log\n") - - -def check(args): - try: - if args.check: - args.check(args) - except AttributeError as e: - logger.debug("object has no attribute 'check' pass check\n") - - -def rca_run(args): - try: - if args.rca_run: - args.rca_run(args) - except AttributeError as e: - logger.debug("object has no attribute 'rca_run' pass rca run\n") - - -def rca_list(args): - try: - if args.rca_list: - args.rca_list(args) - except AttributeError as e: - logger.debug("object has no attribute 'rca_list' pass rca list\n") -def update(args): - try: - if args.rca_list: - args.rca_list(args) - except AttributeError as e: - logger.debug("object has no attribute 'rca_list' pass rca list\n") - -if __name__ == '__main__': - obdiag = OBDIAGClient() - arg_parser = ArgParser(obdiag) - obdiag_args = arg_parser.parse_argv() - get_version(obdiag_args) - get_obdiag_trace_log(obdiag_args) - pharse_config(obdiag_args) - telemetry.push_cmd_info(obdiag_args) - rca_list(obdiag_args) - if obdiag.init(obdiag_args): - telemetry.set_cluster_conn(obdiag.ob_cluster) - gather_log(obdiag_args) - gather_awr(obdiag_args) - gather_sysstat(obdiag_args) - gather_perf(obdiag_args) - gather_obstack(obdiag_args) - gather_plan_monitor(obdiag_args) - gather_clog(obdiag_args) - gather_slog(obdiag_args) - gather_obproxy_log(obdiag_args) - gather_scene(obdiag_args) - gather_scene_list(obdiag_args) - analyze_log(obdiag_args) - analyze_flt_trace(obdiag_args) - check(obdiag_args) - rca_run(obdiag_args) - telemetry.put_data() diff --git a/ocp/ocp_base.py b/ocp/ocp_base.py deleted file mode 100644 index ff12c5ba..00000000 --- a/ocp/ocp_base.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - - -""" -@time: 2022/6/24 -# File : ocp_api.py -# Description: -""" -import json -from common.logger import logger -from utils import utils - - -class OcpBase: - def __init__(self, url, username, password): - self.url = url - self.username = username - self.password = password - - def check_ocp_site(self): - logger.info("Checking if OCP is accessible...") - output = utils.execute_command( - "curl -s --user {}:{} -X GET '{}/api/v2/info'" - .format(self.username, self.password, self.url)) - response = json.loads(output) - logger.info("check ocp response: {0}".format(response)) - try: - response["buildTime"] - except KeyError: - raise Exception("OCP is inaccessible because: {}".format(response["error"])) diff --git a/ocp/ocp_cluster.py b/ocp/ocp_cluster.py deleted file mode 100644 index a59faf3d..00000000 --- a/ocp/ocp_cluster.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - - -""" -@time: 2022/6/24 -# File : ocp_cluster.py -# Description: -""" -import requests -from ocp import ocp_api - - -class ObCluster(): - def __init__(self, url, auth, id=None): - self.url = url - self.auth = auth - - self.architecture = "" - self.compactionStatus = "" - self.configUrl = "" - self.createTime = "" - self.creator = "" - self.id = id - self.dataDiskPath = "" - self.installPath = "" - self.logDiskPath = "" - self.minObBuildVersion = "" - self.name = "" - self.obClusterId = "" - self.obVersion = "" - self.oraclePrivilegeManagementSupported = True - self.partitionCount = 0 - self.performanceStats = {} - self.protectionMode = "" - self.redoTransportMode = "" - self.redoTransportStatus = "" - self.regionCount = 0 - self.rootServers = [] - self.serverCount = 0 - self.standbyClusters = [] - self.status = "" - self.syncStatus = "" - self.tenantCount = 0 - self.tenants = [] - self.type = "" - self.updateTime = "" - self.vpcId = 1 - self.zoneTopo = "" - self.zones = [] - self.standbyClusterList = [] - - def _seri_get(self, data): - for k, v in data.items(): - setattr(self, k, v) - - def get(self): - path = ocp_api.cluster + "/%s" % self.id - response = requests.get(self.url + path, auth=self.auth) - self._seri_get(response.json()["data"]) - # 备集群 - if len(self.standbyClusters) > 0: - for info in self.standbyClusters: - standby_cluster = ObCluster(self.url, self.auth, id=info["id"]) - standby_cluster.get() - self.standbyClusterList.append(standby_cluster) - - def get_by_name(self, name): - path = ocp_api.cluster - response = requests.get(self.url + path, auth=self.auth) - cluster_info = {} - for content in response.json()["data"]["contents"]: - if content["name"] == name[0]: - cluster_info = content - break - if not cluster_info: - raise Exception("can't find cluster by name:%s" % name) - - if cluster_info: - self._seri_get(cluster_info) - - def get_cluster_id_by_name(self, name): - cluster_info = {} - path = ocp_api.cluster - response = requests.get(self.url + path, auth=self.auth) - for content in response.json()["data"]["contents"]: - if content["name"] == name[0]: - cluster_info = content - break - if not cluster_info: - raise Exception("can't find cluster by name:%s" % name) - - if cluster_info: - return cluster_info["id"] - else: - raise Exception("can't find clusterId by name:%s" % name) \ No newline at end of file diff --git a/ocp/ocp_host.py b/ocp/ocp_host.py deleted file mode 100644 index edf1697e..00000000 --- a/ocp/ocp_host.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - - -""" -@time: 2022/6/24 -# File : ocp_host.py -# Description: -""" -import requests -from ocp import ocp_api - - -class Host(): - def __init__(self, url, auth, id=None, ip=None): - self.url = url - self.auth = auth - self.id = id - self.ip = ip - - # remote status - self.clockDiffMillis = "" - self.currentTime = "" - self.diskUsage = "" - self.timezone = "" - - # basic info - self.alias = "" - self.architecture = "" - self.createTime = "" - self.description = "" - self.hostAgentId = "" - self.hostAgentStatus = "" - self.hostAgentVersion = "" - self.idcDescription = "" - self.idcId = "" - self.idcName = "" - self.innerIpAddress = ip - self.kind = "" - self.name = "" - self.operatingSystem = "" - self.operatingSystemRelease = "" - self.publishPorts = "" - self.regionDescription = "" - self.regionId = "" - self.regionName = "" - self.serialNumber = "" - self.services = [] - self.sshPort = "" - self.status = "" - self.typeDescription = "" - self.typeId = "" - self.typeName = "" - self.updateTime = "" - self.vpcId = "" - self.vpcName = "" - - self.agent_list = [] - self.installHome = "" - self.lastAvailableTime = "" - self.logHome = "" - self.agent_status = "" - self.agent_version = "" - - def _seri_info(self, data): - for k, v in data.items(): - setattr(self, k, v) - - self.ip = self.innerIpAddress - - def get_host_list(self): - path = ocp_api.host - response = requests.get(self.url + path, auth=self.auth) - host_list = [] - host_data = response.json()["data"]["contents"] - for data in host_data: - h = Host(self) - h._seri_info(data) - host_list.append(h) - return host_list - - def get_all_host(self): - return self.get_host_list() diff --git a/requirements3.txt b/requirements3.txt index 6b812597..ef1ff40d 100644 --- a/requirements3.txt +++ b/requirements3.txt @@ -30,3 +30,7 @@ pyinstaller>=4.3 colorama==0.4.6 PyMySQL==1.0.2 sqlparse==0.4.4 +ruamel.yaml==0.17.4 +progressbar==2.5 +halo==0.0.31 +inspect2==0.1.2 \ No newline at end of file diff --git a/rpm/build.sh b/rpm/build.sh index 8b12da03..9c103636 100755 --- a/rpm/build.sh +++ b/rpm/build.sh @@ -2,7 +2,7 @@ python_bin='python' W_DIR=`pwd` -VERSION=${VERSION:-'1.6.2'} +VERSION=${VERSION:-'2.0.0'} function python_version() diff --git a/rpm/oceanbase-diagnostic-tool.spec b/rpm/oceanbase-diagnostic-tool.spec index 2ba1bb5d..b6da9a50 100644 --- a/rpm/oceanbase-diagnostic-tool.spec +++ b/rpm/oceanbase-diagnostic-tool.spec @@ -1,5 +1,5 @@ Name: oceanbase-diagnostic-tool -Version:1.6.2 +Version:2.0.0 Release: %(echo $RELEASE)%{?dist} Summary: oceanbase diagnostic tool program Group: Development/Tools @@ -33,13 +33,14 @@ VERSION="$RPM_PACKAGE_VERSION" source py-env-activate py38 cd $SRC_DIR pip install -r requirements3.txt -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com -cp -f obdiag_main.py obdiag.py -sed -i "s//$DATE/" ./utils/version_utils.py && sed -i "s//$VERSION/" ./utils/version_utils.py +cp -f main.py obdiag.py +sed -i "s//$DATE/" ./common/version.py && sed -i "s//$VERSION/" ./common/version.py mkdir -p $BUILD_DIR/SOURCES ${RPM_BUILD_ROOT} mkdir -p $BUILD_DIR/SOURCES/site-packages mkdir -p $BUILD_DIR/SOURCES/resources -mkdir -p $BUILD_DIR/SOURCES/handler/checker/tasks +mkdir -p $BUILD_DIR/SOURCES/check/tasks mkdir -p $BUILD_DIR/SOURCES/gather/tasks +mkdir -p $BUILD_DIR/SOURCES/rca mkdir -p $BUILD_DIR/SOURCES/dependencies/bin mkdir -p ${RPM_BUILD_ROOT}/usr/bin mkdir -p ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool @@ -49,26 +50,30 @@ rm -f obdiag.py oceanbase-diagnostic-tool.spec \cp -rf $SRC_DIR/example $BUILD_DIR/SOURCES/example \cp -rf $SRC_DIR/resources $BUILD_DIR/SOURCES/ \cp -rf $SRC_DIR/dependencies/bin $BUILD_DIR/SOURCES/dependencies -\cp -rf $SRC_DIR/handler/checker/tasks $BUILD_DIR/SOURCES/tasks +\cp -rf $SRC_DIR/handler/checker/tasks $BUILD_DIR/SOURCES/check \cp -rf $SRC_DIR/handler/gather/tasks $BUILD_DIR/SOURCES/gather -\cp -rf $SRC_DIR/*check_package.yaml $BUILD_DIR/SOURCES/ +\cp -rf $SRC_DIR/handler/rca/scene/* $BUILD_DIR/SOURCES/rca \cp -rf $SRC_DIR/init.sh $BUILD_DIR/SOURCES/init.sh \cp -rf $SRC_DIR/init_obdiag_cmd.sh $BUILD_DIR/SOURCES/init_obdiag_cmd.sh \cp -rf $SRC_DIR/conf $BUILD_DIR/SOURCES/conf mkdir -p ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/lib/ mkdir -p ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/dependencies/bin mkdir -p ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/gather +mkdir -p ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/check +mkdir -p ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/rca + \cp -rf $SRC_DIR/dist/obdiag ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/obdiag \cp -rf $BUILD_DIR/SOURCES/site-packages ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/lib/site-packages \cp -rf $BUILD_DIR/SOURCES/resources ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/resources \cp -rf $BUILD_DIR/SOURCES/dependencies/bin ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/dependencies \cp -rf $BUILD_DIR/SOURCES/example ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/ \cp -rf $BUILD_DIR/SOURCES/conf ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/ -\cp -rf $BUILD_DIR/SOURCES/*check_package.yaml ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/ \cp -rf $BUILD_DIR/SOURCES/init.sh ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/ \cp -rf $BUILD_DIR/SOURCES/init_obdiag_cmd.sh ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/ -\cp -rf $BUILD_DIR/SOURCES/tasks ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/tasks +\cp -rf $BUILD_DIR/SOURCES/check/* ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/check +mv ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/check/tasks/*.yaml ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/check/ \cp -rf $BUILD_DIR/SOURCES/gather/tasks ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/gather +\cp -rf $BUILD_DIR/SOURCES/rca/* ${RPM_BUILD_ROOT}/usr/local/oceanbase-diagnostic-tool/rca %files diff --git a/stdio.py b/stdio.py new file mode 100644 index 00000000..5131df51 --- /dev/null +++ b/stdio.py @@ -0,0 +1,954 @@ +# coding: utf-8 +# OceanBase Deploy. +# Copyright (C) 2021 OceanBase +# +# This file is part of OceanBase Deploy. +# +# OceanBase Deploy is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# OceanBase Deploy is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with OceanBase Deploy. If not, see . + + +from __future__ import absolute_import, division, print_function + +import os +import signal +import sys +import fcntl +import traceback +import inspect2 +import six +import logging +from logging import handlers + +from enum import Enum +from halo import Halo, cursor +from colorama import Fore +from prettytable import PrettyTable +from progressbar import AdaptiveETA, Bar, SimpleProgress, ETA, FileTransferSpeed, Percentage, ProgressBar +from types import MethodType +from inspect2 import Parameter + +from common.log import Logger + + +if sys.version_info.major == 3: + raw_input = input + input = lambda msg: int(raw_input(msg)) + + +class BufferIO(object): + + def __init__(self, auto_clear=True): + self._buffer = [] + self.auto_clear = auto_clear + self.closed = False + + def isatty(self): + return False + + def writable(self): + return not self.closed + + def close(self): + self.closed = True + return self + + def open(self): + self.closed = False + self._buffer = [] + return self + + def __enter__(self): + return self.open() + + def __exit__(self, *args, **kwargs): + return self.close() + + def write(self, s): + self._buffer.append(s) + + def read(self, *args, **kwargs): + s = ''.join(self._buffer) + self.auto_clear and self.clear() + return s + + def clear(self): + self._buffer = [] + + def flush(self): + self.auto_clear and self.clear() + return True + + +class SysStdin(object): + + NONBLOCK = False + STATS = None + FD = None + IS_TTY = None + + @classmethod + def isatty(cls): + if cls.IS_TTY is None: + cls.IS_TTY = sys.stdin.isatty() + return cls.IS_TTY + + @classmethod + def fileno(cls): + if cls.FD is None: + cls.FD = sys.stdin.fileno() + return cls.FD + + @classmethod + def stats(cls): + if cls.STATS is None: + cls.STATS = fcntl.fcntl(cls.fileno(), fcntl.F_GETFL) + return cls.STATS + + @classmethod + def nonblock(cls): + if cls.NONBLOCK is False: + fcntl.fcntl(cls.fileno(), fcntl.F_SETFL, cls.stats() | os.O_NONBLOCK) + cls.NONBLOCK = True + + @classmethod + def block(cls): + if cls.NONBLOCK: + fcntl.fcntl(cls.fileno(), fcntl.F_SETFL, cls.stats()) + cls.NONBLOCK = True + + @classmethod + def readline(cls, blocked=False): + if blocked: + cls.block() + else: + cls.nonblock() + return cls._readline() + + @classmethod + def read(cls, blocked=False): + return ''.join(cls.readlines(blocked=blocked)) + + @classmethod + def readlines(cls, blocked=False): + if blocked: + cls.block() + else: + cls.nonblock() + return cls._readlines() + + @classmethod + def _readline(cls): + if cls.NONBLOCK: + try: + for line in sys.stdin: + return line + except IOError: + return '' + finally: + cls.block() + else: + return sys.stdin.readline() + + @classmethod + def _readlines(cls): + if cls.NONBLOCK: + lines = [] + try: + for line in sys.stdin: + lines.append(line) + except IOError: + pass + finally: + cls.block() + return lines + else: + return sys.stdin.readlines() + + +class FormtatText(object): + + def __init__(self, text, color): + self.text = text + self.color_text = color + text + Fore.RESET + + def format(self, istty=True): + return self.color_text if istty else self.text + + def __str__(self): + return self.format() + + @staticmethod + def info(text): + return FormtatText(text, Fore.BLUE) + + @staticmethod + def success(text): + return FormtatText(text, Fore.GREEN) + + @staticmethod + def warning(text): + return FormtatText(text, Fore.YELLOW) + + @staticmethod + def error(text): + return FormtatText(text, Fore.RED) + + +class LogSymbols(Enum): + + INFO = FormtatText.info('!') + SUCCESS = FormtatText.success('ok') + WARNING = FormtatText.warning('!!') + ERROR = FormtatText.error('x') + + +class IOTable(PrettyTable): + + @property + def align(self): + """Controls alignment of fields + Arguments: + + align - alignment, one of "l", "c", or "r" """ + return self._align + + @align.setter + def align(self, val): + if not self._field_names: + self._align = {} + elif isinstance(val, dict): + val_map = val + for field in self._field_names: + if field in val_map: + val = val_map[field] + self._validate_align(val) + else: + val = 'l' + self._align[field] = val + else: + if val: + self._validate_align(val) + else: + val = 'l' + for field in self._field_names: + self._align[field] = val + + +class IOHalo(Halo): + + def __init__(self, text='', color='cyan', text_color=None, spinner='line', animation=None, placement='right', interval=-1, enabled=True, stream=sys.stdout): + super(IOHalo, self).__init__(text=text, color=color, text_color=text_color, spinner=spinner, animation=animation, placement=placement, interval=interval, enabled=enabled, stream=stream) + + def start(self, text=None): + if getattr(self._stream, 'isatty', lambda : False)(): + return super(IOHalo, self).start(text=text) + else: + text and self._stream.write(text) + + def stop_and_persist(self, symbol=' ', text=None): + if getattr(self._stream, 'isatty', lambda : False)(): + return super(IOHalo, self).stop_and_persist(symbol=symbol, text=text) + else: + self._stream.write(' %s\n' % symbol.format(istty=False)) + + def succeed(self, text=None): + return self.stop_and_persist(symbol=LogSymbols.SUCCESS.value, text=text) + + def fail(self, text=None): + return self.stop_and_persist(symbol=LogSymbols.ERROR.value, text=text) + + def warn(self, text=None): + return self.stop_and_persist(symbol=LogSymbols.WARNING.value, text=text) + + def info(self, text=None): + return self.stop_and_persist(symbol=LogSymbols.INFO.value, text=text) + + +class IOProgressBar(ProgressBar): + + @staticmethod + def _get_widgets(widget_type, text, istty=True): + if istty is False: + return [text] + elif widget_type == 'download': + return ['%s: ' % text, Percentage(), ' ', Bar(marker='#', left='[', right=']'), ' ', ETA(), ' ', FileTransferSpeed()] + elif widget_type == 'timer': + return ['%s: ' % text, Percentage(), ' ', Bar(marker='#', left='[', right=']'), ' ', AdaptiveETA()] + elif widget_type == 'simple_progress': + return ['%s: (' % text, SimpleProgress(sep='/'), ') ', Bar(marker='#', left='[', right=']')] + else: + return ['%s: ' % text, Percentage(), ' ', Bar(marker='#', left='[', right=']')] + + def __init__(self, maxval=None, text='', term_width=None, poll=1, left_justify=True, stream=None, widget_type='download'): + self.stream_isatty = getattr(stream, 'isatty', lambda : False)() + super(IOProgressBar, self).__init__(maxval=maxval, widgets=self._get_widgets(widget_type, text, self.stream_isatty), term_width=term_width, poll=poll, left_justify=left_justify, fd=stream) + + def start(self): + self._hide_cursor() + return super(IOProgressBar, self).start() + + def update(self, value=None): + return super(IOProgressBar, self).update(value=value) + + def finish(self): + if self.finished: + return + self.finished = True + self.update(self.maxval) + self._finish() + + def interrupt(self): + if self.finished: + return + self._finish() + + def _finish(self): + self.finished = True + self.fd.write('\n') + self._show_cursor() + if self.signal_set: + signal.signal(signal.SIGWINCH, signal.SIG_DFL) + + def _need_update(self): + return (self.currval == self.maxval or self.currval == 0 or self.stream_isatty) and super(IOProgressBar, self)._need_update() + + def _check_stream(self): + if self.fd.closed: + return False + try: + check_stream_writable = self.fd.writable + except AttributeError: + pass + else: + return check_stream_writable() + return True + + def _hide_cursor(self): + """Disable the user's blinking cursor + """ + if self._check_stream() and self.stream_isatty: + cursor.hide(stream=self.fd) + + def _show_cursor(self): + """Re-enable the user's blinking cursor + """ + if self._check_stream() and self.stream_isatty: + cursor.show(stream=self.fd) + + +class MsgLevel(object): + + CRITICAL = 50 + FATAL = CRITICAL + ERROR = 40 + WARNING = 30 + WARN = WARNING + INFO = 20 + DEBUG = 10 + VERBOSE = DEBUG + NOTSET = 0 + + +class IO(object): + + WIDTH = 64 + VERBOSE_LEVEL = 0 + WARNING_PREV = FormtatText.warning('[WARN]') + ERROR_PREV = FormtatText.error('[ERROR]') + + def __init__(self, + level, + msg_lv=MsgLevel.DEBUG, + use_cache=False, + track_limit=0, + root_io=None, + input_stream=SysStdin, + output_stream=sys.stdout + ): + self.level = level + self.msg_lv = msg_lv + self.default_confirm = False + self._log_path = None + self._trace_id = None + self._log_name = 'default' + self._trace_logger = None + self._log_cache = [] if use_cache else None + self._root_io = root_io + self.track_limit = track_limit + self._verbose_prefix = '-' * self.level + self.sync_obj = None + self.input_stream = None + self._out_obj = None + self._cur_out_obj = None + self._before_critical = None + self._output_is_tty = False + self._input_is_tty = False + self.set_input_stream(input_stream) + self.set_output_stream(output_stream) + + def isatty(self): + if self._root_io: + return self._root_io.isatty() + return self._output_is_tty and self._input_is_tty + + def set_input_stream(self, input_stream): + if self._root_io: + return False + self.input_stream = input_stream + self._input_is_tty = input_stream.isatty() + + def set_output_stream(self, output_stream): + if self._root_io: + return False + if self._cur_out_obj == self._out_obj: + self._cur_out_obj = output_stream + self._out_obj = output_stream + self._output_is_tty = output_stream.isatty() + return True + + def init_trace_logger(self, log_path, log_name=None, trace_id=None, recreate=False): + if self._root_io: + return False + if self._trace_logger is None or recreate: + self._log_path = log_path + if log_name: + self._log_name = log_name + if trace_id: + self._trace_id = trace_id + self._trace_logger = None + return True + return False + + def __getstate__(self): + state = {} + for key in self.__dict__: + state[key] = self.__dict__[key] + for key in ['_trace_logger', 'input_stream', 'sync_obj', '_out_obj', '_cur_out_obj', '_before_critical']: + state[key] = None + return state + + @property + def trace_logger(self): + if self._root_io: + return self._root_io.trace_logger + if self.log_path and self._trace_logger is None: + self._trace_logger = Logger(self.log_name) + handler = handlers.TimedRotatingFileHandler(self.log_path, when='midnight', interval=1, backupCount=30) + if self.trace_id: + handler.setFormatter(logging.Formatter("[%%(asctime)s.%%(msecs)03d] [%s] [%%(levelname)s] %%(message)s" % self.trace_id, "%Y-%m-%d %H:%M:%S")) + else: + handler.setFormatter(logging.Formatter("[%%(asctime)s.%%(msecs)03d] [%%(levelname)s] %%(message)s", "%Y-%m-%d %H:%M:%S")) + self._trace_logger.addHandler(handler) + return self._trace_logger + + @property + def trace_id(self): + if self._root_io: + return self._root_io.trace_id + return self._trace_id + + @property + def log_path(self): + if self._root_io: + return self._root_io.log_path + return self._log_path + + @property + def log_name(self): + if self._root_io: + return self._root_io.log_name + return self._log_name + + @property + def log_cache(self): + if self._root_io: + self._root_io.log_cache + return self._log_cache + + def before_close(self): + if self._before_critical: + try: + self._before_critical(self) + except: + pass + + def _close(self): + self.before_close() + self._flush_log() + + def __del__(self): + self._close() + + def exit(self, code): + self._close() + sys.exit(code) + + def set_cache(self, status): + if status: + self._cache_on() + + def _cache_on(self): + if self._root_io: + return False + if self.log_cache is None: + self._log_cache = [] + return True + + def _cache_off(self): + if self._root_io: + return False + if self.log_cache is not None: + self._flush_log() + self._log_cache = None + return True + + def get_input_stream(self): + if self._root_io: + return self._root_io.get_input_stream() + return self.input_stream + + def get_cur_out_obj(self): + if self._root_io: + return self._root_io.get_cur_out_obj() + return self._cur_out_obj + + def _start_buffer_io(self): + if self._root_io: + return False + if self._cur_out_obj != self._out_obj: + return False + self._cur_out_obj = BufferIO() + return True + + def _stop_buffer_io(self): + if self._root_io: + return False + if self._cur_out_obj == self._out_obj: + return False + text = self._cur_out_obj.read() + self._cur_out_obj = self._out_obj + if text: + self.print(text) + return True + + @staticmethod + def set_verbose_level(level): + IO.VERBOSE_LEVEL = level + + @property + def syncing(self): + if self._root_io: + return self._root_io.syncing + return self.sync_obj is not None + + def _start_sync_obj(self, sync_clz, before_critical, *arg, **kwargs): + if self._root_io: + return self._root_io._start_sync_obj(sync_clz, before_critical, *arg, **kwargs) + if self.sync_obj: + return None + if not self._start_buffer_io(): + return None + kwargs['stream'] = self._out_obj + try: + self.sync_obj = sync_clz(*arg, **kwargs) + self._before_critical = before_critical + except Exception as e: + self._stop_buffer_io() + raise e + return self.sync_obj + + def _clear_sync_ctx(self): + self._stop_buffer_io() + self.sync_obj = None + self._before_critical = None + + def _stop_sync_obj(self, sync_clz, stop_type, *arg, **kwargs): + if self._root_io: + ret = self._root_io._stop_sync_obj(sync_clz, stop_type, *arg, **kwargs) + self._clear_sync_ctx() + else: + if not isinstance(self.sync_obj, sync_clz): + return False + try: + ret = getattr(self.sync_obj, stop_type)(*arg, **kwargs) + except Exception as e: + raise e + finally: + self._clear_sync_ctx() + return ret + + def start_loading(self, text, *arg, **kwargs): + if self.sync_obj: + return False + self.sync_obj = self._start_sync_obj(IOHalo, lambda x: x.stop_loading('fail'), *arg, **kwargs) + if self.sync_obj: + self.log(MsgLevel.INFO, text) + return self.sync_obj.start(text) + + def stop_loading(self, stop_type, *arg, **kwargs): + if not isinstance(self.sync_obj, IOHalo): + return False + if getattr(self.sync_obj, stop_type, False): + return self._stop_sync_obj(IOHalo, stop_type, *arg, **kwargs) + else: + return self._stop_sync_obj(IOHalo, 'stop') + + def update_loading_text(self, text): + if not isinstance(self.sync_obj, IOHalo): + return False + self.log(MsgLevel.VERBOSE, text) + self.sync_obj.text = text + return self.sync_obj + + def start_progressbar(self, text, maxval, widget_type='download'): + if self.sync_obj: + return False + self.sync_obj = self._start_sync_obj(IOProgressBar, lambda x: x.finish_progressbar(), text=text, maxval=maxval, widget_type=widget_type) + if self.sync_obj: + self.log(MsgLevel.INFO, text) + return self.sync_obj.start() + + def update_progressbar(self, value): + if not isinstance(self.sync_obj, IOProgressBar): + return False + return self.sync_obj.update(value) + + def finish_progressbar(self): + if not isinstance(self.sync_obj, IOProgressBar): + return False + return self._stop_sync_obj(IOProgressBar, 'finish') + + def interrupt_progressbar(self): + if not isinstance(self.sync_obj, IOProgressBar): + return False + return self._stop_sync_obj(IOProgressBar, 'interrupt') + + def sub_io(self, msg_lv=None): + if msg_lv is None: + msg_lv = self.msg_lv + return self.__class__( + self.level + 1, + msg_lv=msg_lv, + track_limit=self.track_limit, + root_io=self._root_io if self._root_io else self + ) + + def print_list(self, ary, field_names=None, exp=lambda x: x if isinstance(x, (list, tuple)) else [x], show_index=False, start=0, **kwargs): + if not ary: + title = kwargs.get("title", "") + empty_msg = kwargs.get("empty_msg", "{} is empty.".format(title)) + if empty_msg: + self.print(empty_msg) + return + show_index = field_names is not None and show_index + if show_index: + show_index.insert(0, 'idx') + table = IOTable(field_names, **kwargs) + for row in ary: + row = exp(row) + if show_index: + row.insert(start) + start += 1 + table.add_row(row) + self.print(table) + + def read(self, msg='', blocked=False): + if msg: + self._print(MsgLevel.INFO, msg) + return self.get_input_stream().read(blocked) + + def confirm(self, msg): + msg = '%s [y/n]: ' % msg + self.print(msg, end='') + if self.default_confirm: + self.verbose("default confirm: True") + return True + if self.isatty() and not self.syncing: + while True: + try: + ans = self.get_input_stream().readline(blocked=True).strip().lower() + if ans == 'y': + return True + if ans == 'n': + return False + except Exception as e: + if not e: + return False + else: + self.verbose("isatty: %s, syncing: %s, auto confirm: False" % (self.isatty(), self.syncing)) + return False + + def _format(self, msg, *args): + if args: + msg = msg % args + return msg + + def _print(self, msg_lv, msg, *args, **kwargs): + if msg_lv < self.msg_lv: + return + if 'prev_msg' in kwargs: + print_msg = '%s %s' % (kwargs['prev_msg'], msg) + del kwargs['prev_msg'] + else: + print_msg = msg + kwargs['file'] = self.get_cur_out_obj() + kwargs['file'] and print(self._format(print_msg, *args), **kwargs) + del kwargs['file'] + self.log(msg_lv, msg, *args, **kwargs) + + def log(self, levelno, msg, *args, **kwargs): + self._cache_log(levelno, msg, *args, **kwargs) + + def _cache_log(self, levelno, msg, *args, **kwargs): + if self.trace_logger: + log_cache = self.log_cache + lines = str(msg).split('\n') + for line in lines: + if log_cache is None: + self._log(levelno, line, *args, **kwargs) + else: + log_cache.append((levelno, line, args, kwargs)) + + def _flush_log(self): + if not self._root_io and self.trace_logger and self._log_cache: + for levelno, line, args, kwargs in self._log_cache: + self.trace_logger.log(levelno, line, *args, **kwargs) + self._log_cache = [] + + def _log(self, levelno, msg, *args, **kwargs): + if self.trace_logger: + self.trace_logger.log(levelno, msg, *args, **kwargs) + + def print(self, msg, *args, **kwargs): + self._print(MsgLevel.INFO, msg, *args, **kwargs) + + def warn(self, msg, *args, **kwargs): + self._print(MsgLevel.WARN, msg, prev_msg=self.WARNING_PREV.format(self.isatty()), *args, **kwargs) + + def error(self, msg, *args, **kwargs): + self._print(MsgLevel.ERROR, msg, prev_msg=self.ERROR_PREV.format(self.isatty()), *args, **kwargs) + + def critical(self, msg, *args, **kwargs): + if 'code' in kwargs: + code = kwargs['code'] + del kwargs['code'] + else: + code = 255 + self._print(MsgLevel.CRITICAL, '%s %s' % (self.ERROR_PREV, msg), *args, **kwargs) + if not self._root_io: + self.exit(code) + + def verbose(self, msg, *args, **kwargs): + if self.level > self.VERBOSE_LEVEL: + self.log(MsgLevel.VERBOSE, '%s %s' % (self._verbose_prefix, msg), *args, **kwargs) + return + self._print(MsgLevel.VERBOSE, '%s %s' % (self._verbose_prefix, msg), *args, **kwargs) + + if sys.version_info.major == 2: + def exception(self, msg='', *args, **kwargs): + import linecache + exception_msg = [] + ei = sys.exc_info() + exception_msg.append('Traceback (most recent call last):') + stack = traceback.extract_stack()[self.track_limit:-2] + tb = ei[2] + while tb is not None: + f = tb.tb_frame + lineno = tb.tb_lineno + co = f.f_code + filename = co.co_filename + name = co.co_name + linecache.checkcache(filename) + line = linecache.getline(filename, lineno, f.f_globals) + tb = tb.tb_next + stack.append((filename, lineno, name, line)) + for line in stack: + exception_msg.append(' File "%s", line %d, in %s' % line[:3]) + if line[3]: exception_msg.append(' ' + line[3].strip()) + lines = [] + for line in traceback.format_exception_only(ei[0], ei[1]): + lines.append(line) + if lines: + exception_msg.append(''.join(lines)) + if self.level <= self.VERBOSE_LEVEL: + print_stack = lambda m: self._print(MsgLevel.ERROR, m) + else: + print_stack = lambda m: self.log(MsgLevel.ERROR, m) + msg and self.error(msg) + print_stack('\n'.join(exception_msg)) + else: + def exception(self, msg='', *args, **kwargs): + ei = sys.exc_info() + traceback_e = traceback.TracebackException(type(ei[1]), ei[1], ei[2], limit=None) + pre_stach = traceback.extract_stack()[self.track_limit:-2] + pre_stach.reverse() + for summary in pre_stach: + traceback_e.stack.insert(0, summary) + lines = [] + for line in traceback_e.format(chain=True): + lines.append(line) + if self.level <= self.VERBOSE_LEVEL: + print_stack = lambda m: self._print(MsgLevel.ERROR, m) + else: + print_stack = lambda m: self.log(MsgLevel.ERROR, m) + msg and self.error(msg) + print_stack(''.join(lines)) + + +class _Empty(object): + pass + + +EMPTY = _Empty() +del _Empty + + +class FakeReturn(object): + + def __call__(self, *args, **kwargs): + return None + + def __len__(self): + return 0 + + +FAKE_RETURN = FakeReturn() + + +class StdIO(object): + + def __init__(self, io=None): + self.io = io + self._attrs = {} + self._warn_func = getattr(self.io, "warn", print) + + def __getattr__(self, item): + if item.startswith('__'): + return super(StdIO, self).__getattribute__(item) + if self.io is None: + if item == 'sub_io': + return self + else: + return FAKE_RETURN + if item not in self._attrs: + attr = getattr(self.io, item, EMPTY) + if attr is not EMPTY: + self._attrs[item] = attr + else: + is_tty = getattr(self._stream, 'isatty', lambda : False)() + self._warn_func(FormtatText.warning("WARNING: {} has no attribute '{}'".format(self.io, item)).format(is_tty)) + self._attrs[item] = FAKE_RETURN + return self._attrs[item] + + +FAKE_IO = StdIO() + + +def get_stdio(io_obj): + if io_obj is None: + return FAKE_IO + elif isinstance(io_obj, StdIO): + return io_obj + else: + return StdIO(io_obj) + + +def safe_stdio_decorator(default_stdio=None): + + def decorated(func): + is_bond_method = False + _type = None + if isinstance(func, (staticmethod, classmethod)): + is_bond_method = True + _type = type(func) + func = func.__func__ + all_parameters = inspect2.signature(func).parameters + if "stdio" in all_parameters: + default_stdio_in_params = all_parameters["stdio"].default + if not isinstance(default_stdio_in_params, Parameter.empty): + _default_stdio = default_stdio_in_params or default_stdio + + def func_wrapper(*args, **kwargs): + _params_keys = list(all_parameters.keys()) + _index = _params_keys.index("stdio") + if "stdio" not in kwargs and len(args) > _index: + stdio = get_stdio(args[_index]) + tmp_args = list(args) + tmp_args[_index] = stdio + args = tuple(tmp_args) + else: + stdio = get_stdio(kwargs.get("stdio", _default_stdio)) + kwargs["stdio"] = stdio + return func(*args, **kwargs) + return _type(func_wrapper) if is_bond_method else func_wrapper + else: + return _type(func) if is_bond_method else func + return decorated + + +class SafeStdioMeta(type): + + @staticmethod + def _init_wrapper_func(func): + def wrapper(*args, **kwargs): + setattr(args[0], "_wrapper_func", {}) + func(*args, **kwargs) + if "stdio" in args[0].__dict__: + args[0].__dict__["stdio"] = get_stdio(args[0].__dict__["stdio"]) + + if func.__name__ != wrapper.__name__: + return wrapper + else: + return func + + def __new__(mcs, name, bases, attrs): + + for key, attr in attrs.items(): + if key.startswith("__") and key.endswith("__"): + continue + if isinstance(attr, (staticmethod, classmethod)): + attrs[key] = safe_stdio_decorator()(attr) + cls = type.__new__(mcs, name, bases, attrs) + cls.__init__ = mcs._init_wrapper_func(cls.__init__) + return cls + + +class _StayTheSame(object): + pass + + +STAY_THE_SAME = _StayTheSame() + + +class SafeStdio(six.with_metaclass(SafeStdioMeta)): + _wrapper_func = {} + + def __getattribute__(self, item): + _wrapper_func = super(SafeStdio, self).__getattribute__("_wrapper_func") + if item not in _wrapper_func: + attr = super(SafeStdio, self).__getattribute__(item) + if (not item.startswith("__") or not item.endswith("__")) and isinstance(attr, MethodType): + if "stdio" in inspect2.signature(attr).parameters: + _wrapper_func[item] = safe_stdio_decorator(default_stdio=getattr(self, "stdio", None))(attr) + return _wrapper_func[item] + _wrapper_func[item] = STAY_THE_SAME + return attr + if _wrapper_func[item] is STAY_THE_SAME: + return super(SafeStdio, self).__getattribute__(item) + return _wrapper_func[item] + + def __setattr__(self, key, value): + if key in self._wrapper_func: + del self._wrapper_func[key] + return super(SafeStdio, self).__setattr__(key, value) diff --git a/telemetry/telemetry.py b/telemetry/telemetry.py index f4a8cf0b..7137a63c 100644 --- a/telemetry/telemetry.py +++ b/telemetry/telemetry.py @@ -24,9 +24,9 @@ from io import open from common.constant import const from common.ob_connector import OBConnector -from utils.network_utils import network_connectivity -from utils.time_utils import DateTimeEncoder -from utils.version_utils import get_obdiag_version +from common.tool import NetUtils +from common.tool import DateTimeEncoder +from common.version import get_obdiag_version import ssl ssl._create_default_https_context = ssl._create_unverified_context class Telemetry(): @@ -43,28 +43,32 @@ def __init__(self): self.version = get_obdiag_version() def set_cluster_conn(self, obcluster): - if not self.work_tag: - return - if self.work_tag: - self.work_tag = network_connectivity("https://" + const.TELEMETRY_URL + const.TELEMETRY_PATH) - if not self.work_tag: - return - - if obcluster is not None: - try: + try: + if not self.work_tag: + return + if self.work_tag: + self.work_tag = NetUtils.network_connectivity("https://" + const.TELEMETRY_URL + const.TELEMETRY_PATH) + if not self.work_tag: + return + + if obcluster is not None: + try: + + self.cluster_conn = OBConnector(ip=obcluster.get("db_host"), + port=obcluster.get("db_port"), + username=obcluster.get("tenant_sys").get("user"), + password=obcluster.get("tenant_sys").get("password"), + stdio=self.stdio, + timeout=10000) + self.threads.append(threading.Thread(None, self.get_cluster_info())) + # self.threads.append(threading.Thread(None, self.get_tenant_info())) + for thread in self.threads: + thread.start() + except Exception as e: + pass + except Exception as e: + pass - self.cluster_conn = OBConnector(ip=obcluster.get("db_host"), - port=obcluster.get("db_port"), - username=obcluster.get("tenant_sys").get("user"), - password=obcluster.get("tenant_sys").get("password"), - timeout=10000) - self.threads.append(threading.Thread(None, self.get_cluster_info())) - # self.threads.append(threading.Thread(None, self.get_tenant_info())) - for thread in self.threads: - thread.start() - except Exception as e: - pass - return def get_cluster_info(self): if self.cluster_conn is not None: @@ -181,6 +185,11 @@ def put_info_to_oceanbase(self): def ip_mix_by_sha256(ip): ip = ip.encode('utf-8') return hmac.new(key.encode('utf-8'), ip, digestmod=hashlib.sha256).hexdigest().upper() +def ip_mix_by_sha1(ip=""): + sha1 = hashlib.sha1() + sha1.update(ip.encode()) + return sha1.hexdigest() + telemetry = Telemetry() diff --git a/update/update.py b/update/update.py index 8871d18f..f9dd97bf 100644 --- a/update/update.py +++ b/update/update.py @@ -19,23 +19,38 @@ import shutil import time from common.constant import const -from common.logger import logger -from utils.file_utils import calculate_sha256, extract_tar -from utils.network_utils import download_file, network_connectivity -from utils.version_utils import OBDIAG_VERSION, compare_versions_greater +from common.tool import FileUtil +from common.tool import NetUtils +from common.tool import StringUtils +from common.tool import Util +from common.version import OBDIAG_VERSION import yaml # for update obdiag files without obdiag class UpdateHandler: - def __init__(self): + def __init__(self,context): + self.context = context + self.stdio = context.stdio self.local_update_file_sha = "" self.local_obdiag_version = OBDIAG_VERSION self.remote_obdiag_version = "" self.remote_tar_sha = "" + self.options=self.context.options + self.file_path="" + self.force=False + # on obdiag update command + if context.namespace.spacename =="update": + self.file_path=Util.get_option(self.options, 'file',default="") + self.force=Util.get_option(self.options, 'force',default=False) - def execute(self, file_path, force=False): + + + + def execute(self): try: + file_path=self.file_path + force=self.force remote_server = const.UPDATE_REMOTE_SERVER remote_version_file_name = const.UPDATE_REMOTE_VERSION_FILE_NAME local_version_file_name = os.path.expanduser('~/.obdiag/remote_version.yaml') @@ -45,26 +60,26 @@ def execute(self, file_path, force=False): if file_path and file_path != "": self.handle_update_offline(file_path) return - if network_connectivity(remote_server) is False: - logger.warning("[update] network connectivity failed. Please check your network connection.") + if NetUtils.network_connectivity(remote_server) is False: + self.stdio.warn("[update] network connectivity failed. Please check your network connection.") return - download_file(remote_version_file_name, os.path.expanduser(local_version_file_name)) + NetUtils.download_file(remote_version_file_name, os.path.expanduser(local_version_file_name)) with open(local_version_file_name, 'r') as file: remote_data = yaml.safe_load(file) if remote_data.get("obdiag_version") is None: - logger.warning("obdiag_version is None. Do not perform the upgrade process.") + self.stdio.warn("obdiag_version is None. Do not perform the upgrade process.") return else: self.remote_obdiag_version = remote_data["obdiag_version"].strip() - if compare_versions_greater(self.remote_obdiag_version, self.local_obdiag_version): - logger.warning( + if StringUtils.compare_versions_greater(self.remote_obdiag_version, self.local_obdiag_version): + self.stdio.warn( "remote_obdiag_version is {0}. local_obdiag_version is {1}. " "remote_obdiag_version>local_obdiag_version. Unable to update dependency files, please upgrade " "obdiag. Do not perform the upgrade process.".format( self.remote_obdiag_version, self.local_obdiag_version)) return if remote_data.get("remote_tar_sha") is None: - logger.warning("remote_tar_sha is None. Do not perform the upgrade process.") + self.stdio.warn("remote_tar_sha is None. Do not perform the upgrade process.") return else: self.remote_tar_sha = remote_data["remote_tar_sha"] @@ -76,19 +91,19 @@ def execute(self, file_path, force=False): local_data = yaml.safe_load(file) if local_data.get("remote_tar_sha") is not None and local_data.get( "remote_tar_sha") == self.remote_tar_sha: - logger.info("[update] remote_tar_sha as local_tar_sha. No need to update.") + self.stdio.warn("[update] remote_tar_sha as local_tar_sha. No need to update.") return # get data_update_time if local_data.get("data_update_time") is not None and time.time() - local_data[ "data_update_time"] < 3600 * 24 * 7: - logger.info("[update] data_update_time No need to update.") + self.stdio.warn("[update] data_update_time No need to update.") return # download_update_files - download_file(remote_update_file_name, local_update_file_name) + NetUtils.download_file(remote_update_file_name, local_update_file_name) # check_sha - self.local_update_file_sha = calculate_sha256(local_update_file_name) + self.local_update_file_sha = FileUtil.calculate_sha256(local_update_file_name) if self.remote_tar_sha != self.local_update_file_sha: - logger.warning( + self.stdio.warn( "remote_tar_sha is {0}, but local_tar_sha is {1}. Unable to update dependency files. Do not perform the upgrade process.".format( self.remote_tar_sha, self.local_update_file_sha)) return @@ -103,21 +118,31 @@ def execute(self, file_path, force=False): shutil.rmtree(os.path.expanduser("~/.obdiag/gather.d")) if os.path.exists(os.path.expanduser("~/.obdiag/gather")): os.rename(os.path.expanduser("~/.obdiag/gather"), os.path.expanduser("~/.obdiag/gather.d")) + + ## rca + if os.path.exists(os.path.expanduser("~/.obdiag/rca.d")): + shutil.rmtree(os.path.expanduser("~/.obdiag/rca.d")) + if os.path.exists(os.path.expanduser("~/.obdiag/rca")): + os.rename(os.path.expanduser("~/.obdiag/rca"), os.path.expanduser("~/.obdiag/rca.d")) # decompression remote files - extract_tar(os.path.expanduser(local_update_file_name), os.path.expanduser("~/.obdiag")) + FileUtil.extract_tar(os.path.expanduser(local_update_file_name), os.path.expanduser("~/.obdiag")) # update data save with open(os.path.expanduser("~/.obdiag/data_version.yaml"), 'w') as f: yaml.dump({"data_update_time": int(time.time()), "remote_tar_sha": self.remote_tar_sha}, f) - logger.info("[update] Successfully updated. The original data is stored in the *. d folder.") + self.stdio.print("[update] Successfully updated. The original data is stored in the *. d folder.") + return except Exception as e: - logger.warning('[update] Failed to update. Error message: {0}'.format(e)) + self.stdio.warn('[update] Failed to update. Error message: {0}'.format(e)) def handle_update_offline(self, file): file = os.path.expanduser(file) - self.local_update_file_sha = calculate_sha256(file) + self.local_update_file_sha = FileUtil.calculate_sha256(file) if os.path.exists(file) is False: - logger.error('{0} does not exist.'.format(file)) + self.stdio.error('{0} does not exist.'.format(file)) + return + if not file.endswith('.tar'): + self.stdio.error('{0} is not a tar file.'.format(file)) return ## check_old_files if os.path.exists(os.path.expanduser("~/.obdiag/check.d")): @@ -129,9 +154,15 @@ def handle_update_offline(self, file): shutil.rmtree(os.path.expanduser("~/.obdiag/gather.d")) if os.path.exists(os.path.expanduser("~/.obdiag/gather")): os.rename(os.path.expanduser("~/.obdiag/gather"), os.path.expanduser("~/.obdiag/gather.d")) + + ## rca + if os.path.exists(os.path.expanduser("~/.obdiag/rca.d")): + shutil.rmtree(os.path.expanduser("~/.obdiag/rca.d")) + if os.path.exists(os.path.expanduser("~/.obdiag/rca")): + os.rename(os.path.expanduser("~/.obdiag/rca"), os.path.expanduser("~/.obdiag/rca.d")) # decompression remote files - extract_tar(os.path.expanduser(file), os.path.expanduser("~/.obdiag")) + FileUtil.extract_tar(os.path.expanduser(file), os.path.expanduser("~/.obdiag")) # update data save with open(os.path.expanduser("~/.obdiag/data_version.yaml"), 'w') as f: yaml.dump({"data_update_time": int(time.time()), "remote_tar_sha": self.remote_tar_sha}, f) - logger.info("[update] Successfully updated. The original data is stored in the *. d folder.") + self.stdio.print("[update] Successfully updated. The original data is stored in the *. d folder.") diff --git a/utils/file_utils.py b/utils/file_utils.py deleted file mode 100644 index eebab9d2..00000000 --- a/utils/file_utils.py +++ /dev/null @@ -1,247 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2022/6/21 -@file: file_utils.py -@desc: -""" -import functools -import hashlib -import os -import io -import shutil -import tarfile -import threading -import tabulate - - -def synchronized(wrapped): - lock = threading.Lock() - - @functools.wraps(wrapped) - def _wrap(*args, **kwargs): - with lock: - return wrapped(*args, **kwargs) - - return _wrap - - -@synchronized -def mkdir_if_not_exist(target_dir): - if not os.path.exists(target_dir): - os.makedirs(target_dir) - - -def delete_file_if_exist(file_path): - if os.path.exists(file_path): - if os.path.isdir(file_path): - shutil.rmtree(file_path) - else: - os.remove(file_path) - - -def size_format(num, unit="B", output_str=False): - """ - transform a large number bytes to unit that the number is smaller 1024 - example: 1048576B -> 1MB - input: size_format(1025, "KB") if you want to transform 1025KB to 1MB - :param num: the number of bytes/K/M/... - :param unit: the unit of your input - :param output_str: if output_str is true, directly output a string such as "12KB", otherwise output (12, "KB") - :return: if output_str is True: a tuple(12, 'KB') else a string "12KB" - """ - if num < 0: - raise ValueError("num cannot be negative!") - units = ["B", "K", "M", "G", "T"] - try: - unit_idx = units.index(unit) - except KeyError: - raise ValueError("unit {0} is illegal!".format(unit)) - new_num = float(num) * (1024 ** unit_idx) - unit_idx = 0 - while new_num > 1024: - new_num = float(new_num) / 1024 - unit_idx += 1 - if unit_idx >= len(units): - raise ValueError("size exceed 1023TB!") - if output_str: - return "".join(["%.3f" % new_num, units[unit_idx]]) - return new_num, units[unit_idx] - - -# if not found, return the idx nearest to the val -def binary_search(val_list, val, key=lambda x: x): - li_size = len(val_list) - if li_size == 0: - return -1 - st, et = 0, li_size - 1 - while et >= st: - if et == st: - return et - mid = (st + et) // 2 - if key(val_list[mid]) == val: - return mid - elif key(val_list[mid]) > val: - et = mid - else: - st = mid + 1 - return et - - -# closed interval, return the range as two sides of the closed interval -# if none, return (x, -1), where x is the last val smaller than min_val, and x in [-1, len(val_list)-1] -# x == -1 means the interval is all smaller than the first element -def binary_range_search(val_list, min_val, max_val, key=lambda x: x): - """ - range search on a sorted val_list. - :param val_list: A sorted val_list. From small to large. - :param min_val: min_val in range constrain. - :param max_val: max_val in range constrain. - :param key: sorted key. - :return: a tuple (x,y). - if x>=0 and y>=0, indicates that val_list[x:y+1] are all in [min_val, max_val]. - if y==-1, indicates that no element is in the range, and x is the idx of last element that smaller than min_val. - If x==-1 and y==-1, all elements are larger than min_val and max_val, or val_list is empty. - """ - if len(val_list) == 0: - return -1, -1 - if min_val > key(val_list[-1]): - return len(val_list) - 1, -1 - if max_val < key(val_list[0]): - return -1, -1 - min_idx = binary_search(val_list, min_val, key=key) + 1 - max_idx = binary_search(val_list, max_val, key=key) - 1 - # the last idx that val[idx] <= max_val - while max_idx + 1 < len(val_list) and key(val_list[max_idx + 1]) <= max_val: - max_idx += 1 - # the first idx that val[idx] >= min_val - while min_idx - 1 >= 0 and key(val_list[min_idx - 1]) >= min_val: - min_idx -= 1 - if max_idx < min_idx: - return max_idx, -1 - return min_idx, max_idx - - -def get_log_dump_pkl_file_name(log_filename): - return "{0}_logObj.pkl".format(log_filename) - - -def parse_size(size_str, unit='B'): - unit_size_dict = { - "b": 1, - "B": 1, - "k": 1024, - "K": 1024, - "m": 1024 * 1024, - "M": 1024 * 1024, - "g": 1024 * 1024 * 1024, - "G": 1024 * 1024 * 1024, - "t": 1024 * 1024 * 1024 * 1024, - "T": 1024 * 1024 * 1024 * 1024, - } - unit_str = size_str.strip()[-1] - if unit_str not in unit_size_dict: - raise ValueError('unit {0} not in {1}'.format(unit_str, unit_size_dict.keys())) - real_size = float(size_str.strip()[:-1]) * unit_size_dict[unit_str] - if real_size < 0: - raise ValueError('size cannot be negative!') - return real_size / unit_size_dict[unit] - - -def write_result_append_to_file(filename, result): - with io.open(filename, 'a', encoding='utf-8') as fileobj: - fileobj.write(u'{}'.format(result)) - - -def show_file_size_tabulate(ip, file_size): - """ - show the size of the file - :param args: remote host ip, file size - :return: file info - """ - format_file_size = size_format(int(file_size), output_str=True) - summary_tab = [] - field_names = ["Node", "LogSize"] - summary_tab.append((ip, format_file_size)) - return "\nZipFileInfo:\n" + \ - tabulate.tabulate(summary_tab, headers=field_names, tablefmt="grid", showindex=False) - - -def show_file_list_tabulate(ip, file_list): - """ - Display the list of log files on the node - :param args: remote host ip, file list - :return: file list info - """ - summary_tab = [] - field_names = ["Node", "LogList"] - summary_tab.append((ip, file_list)) - return "\nFileListInfo:\n" + \ - tabulate.tabulate(summary_tab, headers=field_names, tablefmt="grid", showindex=False) - - -def find_all_file(base): - """ - Traverse and query all files in the specified directory - :param args: specified directory - :return: file list - """ - file_list = [] - for root, ds, fs in os.walk(base): - for f in fs: - fullname = os.path.join(root, f) - file_list.append(fullname) - return file_list - - -def write_data_append(output_file, results): - with open(output_file, 'a', encoding='utf-8') as f: - for row in results: - line_to_write = ','.join(str(item) for item in row) - f.write(line_to_write + '\n') - - -def calculate_sha256(filepath): # sha1sum - """ - Calculate sha256 - :param args: file path - :return: the hexadecimal hash value - """ - sha256 = hashlib.sha256() - try: - filepath = os.path.expanduser(filepath) - with open(filepath, 'rb') as file: - while True: - data = file.read(8192) # Read fixed large and small blocks of files - if not data: - break - sha256.update(data) # Update the SHA-1 hash object - return sha256.hexdigest() # Return the hexadecimal hash value - except Exception as e: - return "" - - -def extract_tar(tar_path, output_path): - """ - Extract all files from the tar file to the specified directory - - Args: - tar_path (str): Path of tar file - output_path (str): Output directory for extracting files - Returns: - None - """ - if not os.path.exists(output_path): - os.makedirs(output_path) - with tarfile.open(tar_path, 'r') as tar: - tar.extractall(path=output_path) diff --git a/utils/network_utils.py b/utils/network_utils.py deleted file mode 100644 index eba3b599..00000000 --- a/utils/network_utils.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2023/12/26 -@file: version_utils.py -@desc: -""" -import socket - -import requests - -from common.constant import const - - -def network_connectivity(url=""): - try: - socket.setdefaulttimeout(3) - response = requests.get(url, timeout=(3)) - if response.status_code is not None: - return True - else: - return False - except Exception as e: - return False - - -def download_file(url, local_filename): - # 发送HTTP GET请求并获取响应内容 - with requests.get(url, stream=True) as r: - r.raise_for_status() # 如果响应状态码不是200,则抛出异常 - # 打开本地文件并写入数据 - with open(local_filename, 'wb') as f: - for chunk in r.iter_content(chunk_size=8192): - f.write(chunk) # 将内容写入文件 - return local_filename \ No newline at end of file diff --git a/utils/parser_utils.py b/utils/parser_utils.py deleted file mode 100644 index 8783b54e..00000000 --- a/utils/parser_utils.py +++ /dev/null @@ -1,427 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2022/6/21 -@file: parser_utils.py -@desc: -""" -import argparse -import os - - -class StringMergeAction(argparse.Action): - def __init__(self, option_strings, dest, **kwargs): - super(StringMergeAction, self).__init__(option_strings, dest, **kwargs) - - def __call__(self, parser, namespace, values, option_string=None): - setattr(namespace, self.dest, " ".join(values)) - - -class ParserAction(object): - def add_attribute_to_namespace(args, attr_name, attr_value): - setattr(args, attr_name, attr_value) - return args - - -class ArgParser(object): - def __new__(cls, *args, **kwargs): - if not hasattr(cls, '_inst'): - cls._inst = super(ArgParser, cls).__new__(cls) - cls._inited = False - return cls._inst - - def __init__(self, client): - if not self._inited: - self.client = client - self._inited = True - - @staticmethod - def get_arg_parser(): - return ArgParser(None) - - def parse_argv(self, argv=None): - parser = argparse.ArgumentParser(description="Oceanbase Diagnostic Tool", prog=os.environ.get("PROG"), - add_help=True) - subparsers = parser.add_subparsers() - - # 定义一部分公共参数,可以被子命令复用 - parents_time_arguments = argparse.ArgumentParser(add_help=False) - parents_time_arguments.add_argument("--from", nargs=2, action=StringMergeAction, - help="specify the start of the time range. format: yyyy-mm-dd hh:mm:ss.", - metavar="datetime") - parents_time_arguments.add_argument("--to", nargs=2, action=StringMergeAction, - help="specify the end of the time range. format: yyyy-mm-dd hh:mm:ss.", - metavar="datetime") - parents_time_arguments.add_argument("--since", - help="Specify time range that from 'n' [d]ays, 'n' [h]ours or 'n' [" - "m]inutes. before to now. format: . example: 1h.", - metavar="'n'") - parents_common_arguments = argparse.ArgumentParser(add_help=False) - parents_common_arguments.add_argument("--store_dir", metavar="store_dir", - help="the dir to store gather result, current dir by default.") - parents_common_arguments.add_argument("-c", metavar="config", help="obdiag custom config") - - parser_version = subparsers.add_parser( - "version", help="Oceanbase Diagnostic Tool Version", - epilog="Example: obdiag version", - conflict_handler='resolve', - description="Oceanbase Diagnostic Tool Version" - ) - parser_version.set_defaults(version=self.client.obdiag_version) - - parser_obdiag_display = subparsers.add_parser( - "display-trace", help="Display obdiag trace log", - epilog="Example: obdiag display", - conflict_handler='resolve', - description="Display obdiag trace log" - ) - parser_obdiag_display.add_argument("--trace_id", metavar="trace_id", nargs=1, - help="obdiag-trace trace_id", required=True) - parser_obdiag_display.set_defaults(display=self.client.obdiag_display) - - # 通过sys租户快速生成配置文件 - parser_config = subparsers.add_parser( - "config", help="Quick build config", - epilog="Example: obdiag config -h127.0.0.1 -uroot@sys -ptest -P2881", - conflict_handler='resolve', - description="Quick build config" - ) - parser_config.add_argument("-h", metavar="db_host", nargs=1, - help="database host", required=True) - parser_config.add_argument("-u", metavar="sys_user", nargs=1, - help="sys tenant user", required=True) - parser_config.add_argument("-p", metavar="password", nargs=1, - help="password", required=False, default="") - parser_config.add_argument("-P", metavar="port", nargs=1, - help="db port", required=False, default=2881) - parser_config.set_defaults(config=self.client.quick_build_configuration) - - # gather命令 - parser_gather = subparsers.add_parser("gather", help="Gather logs and other information", ) - - # 定义gather命令的子命令 - subparsers_gather = parser_gather.add_subparsers() - # 定义gather命令的子命令: log - gather_log_arguments = subparsers_gather.add_parser( - "log", help="Filter and gather logs into a package", - epilog="Example: obdiag gather log --scope observer " - "--from 2022-06-16 18:25:00 --to 2022-06-16 18:30:00 ", - parents=[parents_time_arguments, parents_common_arguments], - conflict_handler='resolve', - description="According to the input parameters, gather the logs of the specified range " - "(whether it is time range), compress and pack, " - "and transmit to the specified path of the obdiag machine.") - - gather_log_arguments.set_defaults(gather_log=self.client.handle_gather_log_command) - gather_log_arguments.add_argument("--scope", metavar="scope", nargs=1, - choices=["observer", "election", "rootservice", "all"], - default="all", - help="log type constrains, " - "choices=[observer, election, rootservice, all], " - "default=all") - gather_log_arguments.add_argument("--grep", metavar="grep", nargs='+', - help="specify keywords constrain") - gather_log_arguments.add_argument("--encrypt", metavar="encrypt", nargs=1, - choices=["true", "false"], - default="false", - help="Whether the returned results need to be encrypted, " - "choices=[true, false], " - "default=false") - - # 定义gather命令的子命令: sysstat, 收集主机层面的信息 - gather_sysstat_arguments = subparsers_gather.add_parser( - "sysstat", help="Gather sysstat info", - epilog="Example: obdiag gather sysstat", - parents=[parents_common_arguments], - conflict_handler='resolve', - description="According to the input parameters, gather the os info " - "compress and pack, and transmit to the specified path of the obdiag machine.") - - gather_sysstat_arguments.set_defaults(gather_sysstat=self.client.handle_gather_sysstat_command) - - # 定义gather命令的子命令: stack - gather_obstack_arguments = subparsers_gather.add_parser( - "stack", help="Gather ob stack", - epilog="Example: obdiag gather stack", - parents=[parents_common_arguments], - conflict_handler='resolve', - description="According to the input parameters, gather the ob stack " - "compress and pack, and transmit to the specified path of the obdiag machine.") - - gather_obstack_arguments.set_defaults(gather_obstack=self.client.handle_gather_obstack_command) - - # gather 子命令 awr - gather_awr_arguments = subparsers_gather.add_parser( - "awr", help="Filter and gather awr reports", - epilog="Example: obdiag gather awr --cluster_name demo1 " - "--from 2022-06-16 18:25:00 --to 2022-06-16 18:30:00 ", - parents=[parents_time_arguments, parents_common_arguments], - conflict_handler='resolve', - description="According to the input parameters, gather the awr of the specified range " - "(whether it is time range), compress and pack, " - "and transmit to the specified path of the obdiag machine.") - - gather_awr_arguments.set_defaults(gather_awr=self.client.handle_gather_awr_command) - gather_awr_arguments.add_argument("--cluster_name", metavar="cluster_name", required=True, - nargs=1, help="cluster name.") - - # 定义gather命令的子命令: perf - gather_perf_arguments = subparsers_gather.add_parser( - "perf", help="Gather perf info", - epilog="Example: obdiag gather perf", - parents=[parents_common_arguments], - conflict_handler='resolve', - description="According to the input parameters, gather the perf info " - "compress and pack, and transmit to the specified path of the obdiag machine.") - gather_perf_arguments.set_defaults(gather_sysstat=self.client.handle_gather_perf_command) - gather_perf_arguments.add_argument("--scope", metavar="scope", nargs=1, - choices=["sample", "flame", "all"], - default="all", - help="perf type constrains, " - "choices=[sample, flame, all], " - "default=all") - - # gather 子命令 plan_monitor - gather_plan_monitor_arguments = subparsers_gather.add_parser( - "plan_monitor", help="Filter and gather sql plan monitor reports", - epilog="Example: obdiag gather plan_monitor --trace_id xxxxx", - parents=[parents_common_arguments], - conflict_handler='resolve', - description="According to the input parameters, gather the sql plan monitor of the specified trace_id " - "compress and pack, and transmit to the specified path of the obdiag machine.") - - gather_plan_monitor_arguments.set_defaults(gather_plan_monitor=self.client.handle_gather_plan_monitor) - gather_plan_monitor_arguments.add_argument("--trace_id", metavar="trace_id", required=True, - nargs=1, help=" sql trace id") - gather_plan_monitor_arguments.add_argument("--env", metavar="env", type=str, - help='env, eg: "{env1=xxx, env2=xxx}"') - - # gather 子命令 clog - gather_clog_arguments = subparsers_gather.add_parser( - "clog", help="Filter and gather clog", - epilog="Example: obdiag gather clog --from 2022-06-16 18:25:00 --to 2022-06-16 18:30:00 ", - parents=[parents_time_arguments, parents_common_arguments], - conflict_handler='resolve', - description="According to the input parameters, gather the clog of the specified range " - "(whether it is time range), compress and pack, " - "and transmit to the specified path of the obdiag machine.") - gather_clog_arguments.set_defaults(gather_clog=self.client.handle_gather_clog_command) - gather_clog_arguments.add_argument("--encrypt", metavar="encrypt", nargs=1, - choices=["true", "false"], - default="false", - help="Whether the returned results need to be encrypted, " - "choices=[true, false], " - "default=false") - - # gather 子命令 slog - gather_slog_arguments = subparsers_gather.add_parser( - "slog", help="Filter and gather slog", - epilog="Example: obdiag gather slog --from 2022-06-16 18:25:00 --to 2022-06-16 18:30:00 ", - parents=[parents_time_arguments, parents_common_arguments], - conflict_handler='resolve', - description="According to the input parameters, gather the slog of the specified range " - "(whether it is time range), compress and pack, " - "and transmit to the specified path of the obdiag machine.") - gather_slog_arguments.set_defaults(gather_slog=self.client.handle_gather_slog_command) - gather_slog_arguments.add_argument("--encrypt", metavar="encrypt", nargs=1, - choices=["true", "false"], - default="false", - help="Whether the returned results need to be encrypted, " - "choices=[true, false], " - "default=false") - - # 定义gather命令的子命令: obproxy_log - gather_obproxy_log_arguments = subparsers_gather.add_parser( - "obproxy_log", help="Filter and gather obproxy logs into a package", - epilog="Example: obdiag gather obproxy_log --scope obproxy " - "--from 2022-06-16 18:25:00 --to 2022-06-16 18:30:00 ", - parents=[parents_time_arguments, parents_common_arguments], - conflict_handler='resolve', - description="According to the input parameters, gather the logs of the specified range " - "(whether it is time range), compress and pack, " - "and transmit to the specified path of the obdiag machine.") - - gather_obproxy_log_arguments.set_defaults(gather_obproxy_log=self.client.handle_gather_obproxy_log_command) - gather_obproxy_log_arguments.add_argument("--scope", metavar="scope", nargs=1, - choices=["obproxy", "obproxy_digest", "obproxy_stat", "obproxy_slow", - "obproxy_limit", "all"], - default="all", - help="log type constrains, " - "choices=[obproxy, obproxy_digest, obproxy_stat, obproxy_slow, obproxy_limit, all], " - "default=all") - gather_obproxy_log_arguments.add_argument("--grep", metavar="grep", nargs='+', - help="specify keywords constrain") - gather_obproxy_log_arguments.add_argument("--encrypt", metavar="encrypt", nargs=1, - choices=["true", "false"], - default="false", - help="Whether the returned results need to be encrypted, " - "choices=[true, false], " - "default=false") - - # gather all - gather_all_arguments = subparsers_gather.add_parser( - "all", help="Gather all", - epilog="Example: obdiag gather all --from 2022-06-16 18:25:00 --to 2022-06-16 18:30:00 ", - parents=[parents_time_arguments, parents_common_arguments], - conflict_handler='resolve', - description="According to the input parameters, gather all reports") - - gather_all_arguments.set_defaults( - gather_log=self.client.handle_gather_log_command, - gather_sysstat=self.client.handle_gather_sysstat_command, - gather_obstack=self.client.handle_gather_obstack_command, - gather_perf=self.client.handle_gather_perf_command - ) - gather_all_arguments.add_argument("--scope", metavar="scope", nargs=1, - choices=["observer", "election", "rootservice", "all"], - default="all", - help="log type constrains, " - "choices=[observer, election, rootservice, all], " - "default=all") - gather_all_arguments.add_argument("--encrypt", metavar="encrypt", nargs=1, - choices=["true", "false"], - default="false", - help="Whether the returned results need to be encrypted, " - "choices=[true, false], " - "default=false") - gather_all_arguments.add_argument("--grep", metavar="grep", nargs='+', - help="specify keywords constrain for log") - - gather_scene = subparsers_gather.add_parser( - "scene", help="Gather scene info", - conflict_handler='resolve', - description="gather scene") - # gather scene list - subparsers_gather_scene = gather_scene.add_subparsers() - gather_scene_arguments = subparsers_gather_scene.add_parser( - "run", - help="Gather scene run", - parents=[parents_time_arguments, parents_common_arguments], - epilog="Example: obdiag gather scene run --scene=xxx", - conflict_handler='resolve', - description="gather scene run") - gather_scene_arguments.set_defaults(gather_scene=self.client.handle_gather_scene_command) - gather_scene_arguments.add_argument("--scene", metavar="scene", nargs=1, required=True, help="specify scene") - gather_scene_arguments.add_argument("--env", metavar="env", type=str, help='env, eg: "{env1=xxx, env2=xxx}"') - gather_scene_arguments.add_argument("--dis_update",type=bool, metavar="dis_update", nargs=1,help="The type is bool. --dis_update is assigned any value representing true",required=False) - - # gather scene list - gather_scene_list_arguments = subparsers_gather_scene.add_parser( - "list", - help="Gather scene list", - epilog="Example: obdiag gather scene list", - conflict_handler='resolve', - description="gather scene list") - gather_scene_list_arguments.set_defaults(gather_scene_list=self.client.handle_gather_scene_list_command) - - # analyze - parser_analyze = subparsers.add_parser("analyze", help="analyze logs and other information", ) - subparsers_analyze = parser_analyze.add_subparsers() - analyze_log_arguments = subparsers_analyze.add_parser( - "log", help="Filter and analyze observer logs", - epilog="Example1: obdiag analyze log --scope observer --from 2022-06-16 18:25:00 --to 2022-06-16 18:30:00\n\n" - "Example2: obdiag analyze log --scope observer --since 1h --grep STORAGE\n\n" - "Example3: obdiag analyze log --files observer.log.20230831142211247\n\n" - "Example4: obdiag analyze log --files ./log/", - parents=[parents_time_arguments, parents_common_arguments], - conflict_handler='resolve', - description="According to the input parameters, analyze observer logs") - - analyze_log_arguments.set_defaults(analyze_log=self.client.handle_analyze_log_command) - analyze_log_arguments.add_argument("--scope", metavar="scope", nargs=1, - choices=["observer", "election", "rootservice", "all"], - default="all", - help="log type constrains, " - "choices=[observer, election, rootservice, all], " - "default=all") - analyze_log_arguments.add_argument("--log_level", metavar="log_level", nargs=1, - choices=["DEBUG", "TRACE", "INFO", "WDIAG", "WARN", "EDIAG", "ERROR"], - help="log level constrains, " - "choices=[DEBUG, TRACE, INFO, WDIAG, WARN, EDIAG, ERROR], " - "default=WARN") - analyze_log_arguments.add_argument("--files", metavar="files", nargs='+', - help="specify file") - analyze_log_arguments.add_argument("--grep", metavar="grep", nargs='+', - help="specify keywords constrain") - - analyze_flt_trace_arguments = subparsers_analyze.add_parser( - "flt_trace", help="Filter and analyze observer trace log", - epilog="Example1: obdiag analyze flt_trace --flt_trace_id \n\n", - parents=[parents_common_arguments], - conflict_handler='resolve', - description="According to the input parameters, analyze observer logs") - analyze_flt_trace_arguments.set_defaults(analyze_flt_trace=self.client.handle_analyze_flt_trace_command) - analyze_flt_trace_arguments.add_argument("--files", metavar="files", nargs='+', - help="specify file") - analyze_flt_trace_arguments.add_argument("--flt_trace_id", metavar="flt_trace_id", nargs=1, required=True, - help="flt trace id") - analyze_flt_trace_arguments.add_argument("--top", metavar="top", nargs=1, help="top leaf span") - analyze_flt_trace_arguments.add_argument("--recursion", metavar="recursion", nargs=1, - help="Maximum number of recursion") - analyze_flt_trace_arguments.add_argument("--output", metavar="output", nargs=1, - help="Print the result to the maximum output line on the screen") - - # 定义巡检参数check arguments - check_arguments = subparsers.add_parser("check", help="do check", - epilog="Example: obdiag check list\n\n" - "Example: obdiag check --cases=ad\n\n", - conflict_handler='resolve', ) - check_arguments.set_defaults(check=self.client.handle_check_command) - - subparsers_check = check_arguments.add_subparsers() - check_arguments.set_defaults(check=self.client.handle_check_command) - check_arguments.add_argument("--cases", metavar="cases", nargs=1, - help="check observer's cases on package_file", required=False) - check_arguments.add_argument("--obproxy_cases", metavar="obproxy_cases", nargs=1, - help="check obproxy's cases on package_file", required=False) - check_arguments.add_argument("--store_dir", metavar="store_dir", nargs=1, - help="report path", required=False) - check_arguments.add_argument("-c", metavar="config", help="obdiag custom config") - check_arguments.add_argument("--dis_update",type=bool, metavar="dis_update", nargs=1,help="The type is bool. --dis_updata is assigned any value representing true",required=False) - check_list_arguments = subparsers_check.add_parser( - "list", help="show list of check list", - epilog="Example: obdiag check list\n\n", ) - check_list_arguments.set_defaults(check=self.client.handle_check_list_command) - - # rca arguments - rca_arguments = subparsers.add_parser("rca", help="root cause analysis", - epilog="Example: obdiag rca run --scene=disconnection\n\n" - "Example: obdiag rca list", - conflict_handler='resolve', ) - subparsers_rca = rca_arguments.add_subparsers() - rca_list_arguments = subparsers_rca.add_parser( - "list", help="show list of rca list", - epilog="Example: obdiag rca list\n\n",) - rca_list_arguments.set_defaults(rca_list=self.client.handle_rca_list_command) - - rca_run_arguments = subparsers_rca.add_parser( - "run", help="Filter and analyze observer trace log", - epilog="Example: obdiag rca run --scene=disconnection\n\n", - conflict_handler='resolve', - description="According to the input parameters, rca run") - rca_run_arguments.set_defaults(rca_run=self.client.handle_rca_run_command) - rca_run_arguments.add_argument("--scene", metavar="scene", nargs=1,help="scene name. The argument is required.", required=True) - rca_run_arguments.add_argument("--parameters", metavar="parameters", nargs=1,help="Other parameters required for the scene, input in JSON format.",required=False) - rca_run_arguments.add_argument("--store_dir", metavar="store_dir", nargs=1,help="result path",required=False) - rca_run_arguments.add_argument("-c", metavar="config", help="obdiag custom config") - - # 定义升级参数update arguments - update_arguments = subparsers.add_parser("update", help="Update cheat files", - epilog="Example: obdiag update\n\n", - conflict_handler='resolve', ) - update_arguments.set_defaults(check=self.client.handle_update_command) - update_arguments.add_argument("--file", metavar="file", help="obdiag update cheat file path. Please note that you need to ensure the reliability of the files on your own") - update_arguments.add_argument("--force",type=bool, metavar="force", nargs=1,help="Force Update",required=False) - # parse args - args = parser.parse_args(args=argv) - return args diff --git a/utils/print_spin.py b/utils/print_spin.py deleted file mode 100644 index 1572b160..00000000 --- a/utils/print_spin.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2024/2/1 -@file: print_spin.py -@desc: -""" -import threading -import time - - -class Spin(): - def __init__(self, info): - self.run_thread = None - self.run_tag = True - self.info = info - - def run(self): - self.run_thread = threading.Thread(target=self.__run) - self.run_thread.start() - - def __run(self): - while self.run_tag: - print('\r{0}|'.format(self.info), end='') - time.sleep(0.5) - print('\r{0}/'.format(self.info), end='') - time.sleep(0.5) - print('\r{0}-'.format(self.info), end='') - time.sleep(0.5) - print('\r{0}\\'.format(self.info), end='') - time.sleep(0.5) - print("\r", end='') - - def stop(self): - self.run_tag = False - self.run_thread.join() diff --git a/utils/print_utils.py b/utils/print_utils.py deleted file mode 100644 index 19d40ac7..00000000 --- a/utils/print_utils.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2024/01/23 -@file: print_utils.py -@desc: -""" -from colorama import Fore, Style -from utils.utils import is_chinese - -def print_scene(scene_dict): - columns_to_print = ['command', 'info_en', 'info_cn'] - keys = columns_to_print - table_data = [[value[key] for key in keys] for value in scene_dict.values()] - column_widths = [max(len(str(item)) * (is_chinese(item) or 1) for item in column) for column in zip(*table_data)] - table_data.insert(0, keys) - print_line(length= sum(column_widths) + 5) - for i in range(len(table_data)): - print(Fore.GREEN + " ".join(f"{item:<{width}}" for item, width in zip(table_data[i], column_widths)) + Style.RESET_ALL) - if i == 0: - print_line(length= sum(column_widths) + 5) - print_line(length= sum(column_widths) + 5) - -def print_line(char='-', length=50): - print(char * length) - -def print_title(name): - print("\n[{0}]:".format(name)) \ No newline at end of file diff --git a/utils/retry_utils.py b/utils/retry_utils.py deleted file mode 100644 index c21cf5fc..00000000 --- a/utils/retry_utils.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2020/7/20 -@file: retry_utils.py -@desc: -""" - -import time -from common.logger import logger - - -def retry(retry_count=3, retry_interval=2): - """ - retry decorator - Example: - @retry(3, 2) or @retry() - def test(): - pass - """ - - def real_decorator(decor_method): - def wrapper(*args, **kwargs): - for count in range(retry_count): - try: - return_values = decor_method(*args, **kwargs) - return return_values - except Exception as error: - logger.error("Function execution %s retry: %s " % - (decor_method.__name__, count + 1)) - time.sleep(retry_interval) - if count == retry_count - 1: - raise error - - return wrapper - - return real_decorator diff --git a/utils/shell_utils.py b/utils/shell_utils.py deleted file mode 100644 index 0a66b56d..00000000 --- a/utils/shell_utils.py +++ /dev/null @@ -1,308 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2022/6/22 -@file: shell_utils.py -@desc: -""" -import sys -import os - -import paramiko -import time - -from paramiko import AuthenticationException -from paramiko import SSHException - -from common.obdiag_exception import OBDIAGSSHConnException -from common.obdiag_exception import OBDIAGShellCmdException -import docker -from common.logger import logger - - -class SshHelper(object): - def __init__(self, is_ssh=None, host_ip=None, username=None, password=None, ssh_port=None, key_file=None, - node=None): - self.is_ssh = is_ssh - self.host_ip = host_ip - self.username = username - self.ssh_port = ssh_port - self.need_password = True - self.password = password - self.key_file = key_file - self.ssh_type = node.get("ssh_type") - self._ssh_fd = None - self._sftp_client = None - if "ssh_type" in node and node.get("ssh_type") == "docker": - try: - self.ssh_type = node["ssh_type"] - logger.debug("use ssh_type:{0} , node info : {1}".format(self.ssh_type, node_cut_passwd_for_log(node))) - self.node = node - # docker_permissions_check - if self.ssh_type == "docker": - self.client = docker.from_env() - if "container_name" not in node: - logger.error("SshHelper init docker Exception: 'container_name' not in node") - raise Exception("SshHelper init docker Exception: 'container_name' not in node") - else: - logger.error("SshHelper init not support the ssh_type : {0}".format(self.ssh_type)) - raise Exception("SshHelper init not support the ssh_type : {0}".format(self.ssh_type)) - - except Exception as e: - logger.Error("SshHelper init docker Exception: {0}".format(e)) - raise Exception("SshHelper init docker Exception: {0}".format(e)) - - return - - if self.is_ssh: - self.ssh_type = "remote" - if len(self.key_file) > 0: - try: - self._ssh_fd = paramiko.SSHClient() - self._ssh_fd.set_missing_host_key_policy(paramiko.client.AutoAddPolicy()) - self._ssh_fd.load_system_host_keys() - key = paramiko.RSAKey.from_private_key_file(key_file) - self._ssh_fd.connect(hostname=host_ip, username=username, pkey=key, port=ssh_port) - except AuthenticationException: - self.password = input("Authentication failed, Input {0}@{1} password:\n".format(username, host_ip)) - self.need_password = True - self._ssh_fd.connect(hostname=host_ip, username=username, password=password, port=ssh_port) - except Exception as e: - raise OBDIAGSSHConnException("ssh {0}@{1}: failed, exception:{2}".format(username, host_ip, e)) - else: - self._ssh_fd = paramiko.SSHClient() - self._ssh_fd.set_missing_host_key_policy(paramiko.client.AutoAddPolicy()) - self._ssh_fd.load_system_host_keys() - self.need_password = True - self._ssh_fd.connect(hostname=host_ip, username=username, password=password, port=ssh_port) - - def ssh_exec_cmd(self, cmd): - if self.ssh_type == "docker": - try: - logger.debug("ssh_exec_cmd docker {0} cmd: {1}".format(self.node.get("container_name"), cmd)) - client_result = self.client.containers.get(self.node["container_name"]) - result = client_result.exec_run( - cmd=["bash", "-c", cmd], - detach=False, - stdout=True, - stderr=True, - ) - if result.exit_code != 0: - raise OBDIAGShellCmdException("Execute Shell command on server {0} failed, " - "command=[{1}], exception:{2}".format(self.node["container_name"], cmd, - result.output.decode('utf-8'))) - - except Exception as e: - logger.error("sshHelper ssh_exec_cmd docker Exception: {0}".format(e)) - raise Exception("sshHelper ssh_exec_cmd docker Exception: {0}".format(e)) - - return result.output.decode('utf-8') - try: - stdin, stdout, stderr = self._ssh_fd.exec_command(cmd) - err_text = stderr.read() - if len(err_text): - raise OBDIAGShellCmdException("Execute Shell command on server {0} failed, " - "command=[{1}], exception:{2}".format(self.host_ip, cmd, err_text)) - except SSHException as e: - raise OBDIAGShellCmdException("Execute Shell command on server {0} failed, " - "command=[{1}], exception:{2}".format(self.host_ip, cmd, e)) - return stdout.read().decode('utf-8') - - def ssh_exec_cmd_ignore_err(self, cmd): - if self.ssh_type == "docker": - try: - client_result = self.client.containers.get(self.node["container_name"]) - result = client_result.exec_run( - cmd=["bash", "-c", cmd], - detach=False, - stdout=True, - stderr=True, - ) - except Exception as e: - logger.error("sshHelper ssh_exec_cmd docker Exception: {0}".format(e)) - raise Exception("sshHelper ssh_exec_cmd docker Exception: {0}".format(e)) - - return result.output.decode('utf-8') - - try: - stdin, stdout, stderr = self._ssh_fd.exec_command(cmd) - return stdout.read().decode('utf-8') - except SSHException as e: - print("Execute Shell command on server {0} failed,command=[{1}], exception:{2}".format(self.node, cmd, e)) - - def ssh_exec_cmd_ignore_exception(self, cmd): - if self.ssh_type == "docker": - try: - client_result = self.client.containers.get(self.node["container_name"]) - result = client_result.exec_run( - cmd=["bash", "-c", cmd], - detach=False, - stdout=True, - stderr=True, - ) - return result.output.decode('utf-8') - except Exception as e: - logger.error("sshHelper ssh_exec_cmd_ignore_exception docker Exception: {0}".format(e)) - pass - # raise Exception("sshHelper ssh_exec_cmd docker Exception: {0}".format(e)) - return - - try: - stdin, stdout, stderr = self._ssh_fd.exec_command(cmd) - return stderr.read().decode('utf-8') - except SSHException as e: - pass - - def ssh_exec_cmd_get_stderr(self, cmd): - if self.ssh_type == "docker": - try: - client_result = self.client.containers.get(self.node["container_name"]) - result = client_result.exec_run( - cmd=["bash", "-c", cmd], - detach=False, - stdout=True, - stderr=True, - ) - return result.output.decode('utf-8') - except Exception as e: - logger.error("sshHelper ssh_exec_cmd_ignore_exception docker Exception: {0}".format(e)) - pass - # raise Exception("sshHelper ssh_exec_cmd docker Exception: {0}".format(e)) - return - try: - stdin, stdout, stderr = self._ssh_fd.exec_command(cmd) - return stderr.read().decode('utf-8') - except SSHException as e: - pass - - def progress_bar(self, transferred, to_be_transferred, suffix=''): - bar_len = 20 - filled_len = int(round(bar_len * transferred / float(to_be_transferred))) - percents = round(20.0 * transferred / float(to_be_transferred), 1) - bar = '\033[32;1m%s\033[0m' % '=' * filled_len + '-' * (bar_len - filled_len) - print_percents = round((percents * 5), 1) - sys.stdout.flush() - sys.stdout.write('Downloading [%s] %s%s%s %s %s\r' % (bar, '\033[32;1m%s\033[0m' % print_percents, '% [', self.translate_byte(transferred), ']', suffix)) - if transferred == to_be_transferred: - sys.stdout.write('Downloading [%s] %s%s%s %s %s\r' % ( - bar, '\033[32;1m%s\033[0m' % print_percents, '% [', self.translate_byte(transferred), ']', suffix)) - print() - - def download(self, remote_path, local_path): - if self.ssh_type == "docker": - try: - logger.info("remote_path: {0}:{1} to local_path:{2}".format(self.node["container_name"], remote_path, local_path)) - client_result = self.client.containers.get(self.node["container_name"]) - data, stat = client_result.get_archive(remote_path) - with open(local_path, "wb") as f: - for chunk in data: - f.write(chunk) - return - except Exception as e: - logger.error("sshHelper download docker Exception: {0}".format(e)) - raise Exception("sshHelper download docker Exception: {0}".format(e)) - return - - transport = self._ssh_fd.get_transport() - self._sftp_client = paramiko.SFTPClient.from_transport(transport) - print('Download {0}:{1}'.format(self.host_ip,remote_path)) - self._sftp_client.get(remote_path, local_path, callback=self.progress_bar) - self._sftp_client.close() - - def translate_byte(self, B): - B = float(B) - KB = float(1024) - MB = float(KB ** 2) - GB = float(MB ** 2) - TB = float(GB ** 2) - if B < KB: - return '{} {}'.format(B, 'bytes' if B > 1 else "byte") - elif KB < B < MB: - return '{:.2f} KB'.format(B / KB) - elif MB < B < GB: - return '{:.2f} MB'.format(B / MB) - elif GB < B < TB: - return '{:.2f} GB'.format(B / GB) - else: - return '{:.2f} TB'.format(B / TB) - - def upload(self, remote_path, local_path): - if self.ssh_type == "docker": - try: - logger.info(" local_path:{0} to remote_path:{1}:{2}".format(local_path, self.node["container_name"], remote_path)) - - self.client.containers.get(self.node["container_name"]).put_archive(remote_path, local_path) - - return - except Exception as e: - logger.error("sshHelper upload docker Exception: {0}".format(e)) - raise Exception("sshHelper upload docker Exception: {0}".format(e)) - return - transport = self._ssh_fd.get_transport() - self._sftp_client = paramiko.SFTPClient.from_transport(transport) - self._sftp_client.put(remote_path, local_path) - self._sftp_client.close() - - def ssh_close(self): - if self.ssh_type == "docker": - self.client.close() - return - if self._sftp_client is not None: - self._sftp_client.close() - self._sftp_client = None - - def __del__(self): - if self._sftp_client is not None: - self._sftp_client.close() - self._sftp_client = None - - def ssh_invoke_shell_switch_user(self, new_user, cmd, time_out): - if self.ssh_type == "docker": - try: - exec_id = self.client.exec_create(container=self.node["container_name"], command=['su', '- ' + new_user]) - response = self.client.exec_start(exec_id) - - return response - except Exception as e: - logger.error("sshHelper ssh_invoke_shell_switch_user docker Exception: {0}".format(e)) - raise Exception("sshHelper ssh_invoke_shell_switch_user docker Exception: {0}".format(e)) - return - try: - ssh = self._ssh_fd.invoke_shell() - ssh.send('su {0}\n'.format(new_user)) - ssh.send('{}\n'.format(cmd)) - time.sleep(time_out) - self._ssh_fd.close() - result = ssh.recv(65535) - except SSHException as e: - raise OBDIAGShellCmdException("Execute Shell command on server {0} failed, " - "command=[{1}], exception:{2}".format(self.host_ip, cmd, e)) - return result - - def get_name(self): - if self.ssh_type == "docker": - return "(docker)"+self.node.get("container_name") - return self.host_ip - -def node_cut_passwd_for_log(obj): - if isinstance(obj, dict): - new_obj = {} - for key, value in obj.items(): - if key == "password" or key == "ssh_password": - continue - new_obj[key] = node_cut_passwd_for_log(value) - return new_obj - elif isinstance(obj, list): - return [node_cut_passwd_for_log(item) for item in obj] - else: - return obj \ No newline at end of file diff --git a/utils/sql_utils.py b/utils/sql_utils.py deleted file mode 100644 index 4999fde7..00000000 --- a/utils/sql_utils.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2024/01/17 -@file: sql_utils.py -@desc: -""" - -import sqlparse - -def extract_db_and_table(sql): - parsed_sql = sqlparse.parse(sql) - - db_tables_list = [] - - for statement in parsed_sql: - tokens = list(statement.tokens) - if statement.get_type() == 'SELECT': - from_index = next((i for i, token in enumerate(tokens) if - token.ttype == sqlparse.tokens.Keyword and token.value.lower() == 'from'), -1) - if from_index != -1: - after_from_tokens = tokens[from_index + 1:] - parse_db_table(after_from_tokens, db_tables_list) - - elif statement.get_type() == 'INSERT': - into_index = next((i for i, token in enumerate(tokens) if - token.ttype == sqlparse.tokens.Keyword and token.value.lower() == 'into'), -1) - if into_index != -1: - after_into_tokens = tokens[into_index + 1:] - parse_db_table(after_into_tokens, db_tables_list) - - return db_tables_list - - -def parse_db_table(tokens, db_tables_list): - for token in tokens: - if isinstance(token, sqlparse.sql.IdentifierList): - for sub_token in token.tokens: - parts = split_db_table(sub_token.value) - if len(parts) > 1: - db_tables_list.append(parts) - elif isinstance(token, sqlparse.sql.Identifier): - parts = split_db_table(token.value) - if len(parts) > 1: - db_tables_list.append(parts) - - -def split_db_table(table_name): - parts = table_name.replace('`', '').split('.') - return ('unknown' if len(parts) == 1 else parts[0], parts[-1]) \ No newline at end of file diff --git a/utils/string_utils.py b/utils/string_utils.py deleted file mode 100644 index 975a34db..00000000 --- a/utils/string_utils.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2024/01/17 -@file: string_utils.py -@desc: -""" - -import re - -def parse_mysql_cli_connection_string(cli_conn_str): - db_info = {} - # 处理密码选项,注意区分短选项和长选项的密码 - password_pattern = re.compile(r'(-p\s*|--password=)([^ ]*)') - password_match = password_pattern.search(cli_conn_str) - if password_match: - password = password_match.group(2) - db_info['password'] = password - # 去除密码部分,避免后续解析出错 - cli_conn_str = cli_conn_str[:password_match.start()] + cli_conn_str[password_match.end():] - - # 模式匹配短选项 - short_opt_pattern = re.compile(r'-(\w)\s*(\S*)') - matches = short_opt_pattern.finditer(cli_conn_str) - for match in matches: - opt = match.group(1) - value = match.group(2) - if opt == 'h': - db_info['host'] = value - elif opt == 'u': - db_info['user'] = value - elif opt == 'P': - db_info['port'] = int(value) - elif opt == 'D': - db_info['database'] = value - - # 模式匹配长选项 - long_opt_pattern = re.compile(r'--(\w+)=([^ ]+)') - long_matches = long_opt_pattern.finditer(cli_conn_str) - for match in long_matches: - opt = match.group(1) - value = match.group(2) - if opt == 'host': - db_info['host'] = value - elif opt == 'user': - db_info['user'] = value - elif opt == 'port': - db_info['port'] = int(value) - elif opt in ['dbname', 'database']: - db_info['database'] = value - - # 如果存在命令行最后的参数,且不是一个选项,则认为是数据库名 - last_param = cli_conn_str.split()[-1] - if last_param[0] != '-' and 'database' not in db_info: - db_info['database'] = last_param - return db_info - -def validate_db_info(db_info): - required_keys = {'database', 'host', 'user', 'port'} - if not required_keys.issubset(db_info.keys()) or any(not value for value in db_info.values()): - return False - if not isinstance(db_info['port'], int): - return False - for key, value in db_info.items(): - if key != 'port' and not isinstance(value, str): - return False - return True - -def parse_custom_env_string(env_string): - env_dict = {} - # 去除花括号 - inner_str = env_string[1:-1] - pairs = inner_str.split(',') - for pair in pairs: - key_value = pair.strip().split('=') - if len(key_value) == 2: - key, value = key_value - # 处理可能含有单引号或双引号的情况 - if value.startswith('"') and value.endswith('"'): - value = value[1:-1] - elif value.startswith("'") and value.endswith("'"): - value = value[1:-1] - env_dict[key.strip()] = value.strip() - - return env_dict \ No newline at end of file diff --git a/utils/time_utils.py b/utils/time_utils.py deleted file mode 100644 index 7a7eba24..00000000 --- a/utils/time_utils.py +++ /dev/null @@ -1,233 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2022/6/21 -@file: time_utils.py -@desc: -""" -import json -import time -import traceback -import datetime -from datetime import timedelta - -from common.obdiag_exception import OBDIAGFormatException - - -class Timer(object): - def __init__(self, timer_name): - self.timer = time.time() - self.timer_name = timer_name - - def __enter__(self): - self.timer = time.time() - return self.timer - - def __exit__(self, exc_type, exc_val, exc_tb): - self.end_timer = time.time() - # print("{0} consume time {1} s".format(self.timer_name, self.end_timer - self.timer)) - - -def _get_timestamp(year, mon, day, hour, minute, sec): - mon -= 2 - if 0 >= mon: - mon += 12 - year -= 1 - - return ((((year / 4 - year / 100 + year / 400 + 367 * mon / 12 + day) + - year * 365 - 719499) * 24 + hour) * 60 + minute) * 60 + sec + -8 * 60 * 60 - - -def get_current_us_timestamp(): - time_second = time.time() - return int(time_second * 1000000) - - -def get_invalid_us_timestamp(): - return int(0) - - -def parse_time_length_to_sec(time_length_str): - unit = time_length_str[-1] - value = int(time_length_str[:-1]) - if unit == "m": - value *= 60 - elif unit == "h": - value *= 3600 - elif unit == "d": - value *= 3600 * 24 - else: - raise OBDIAGFormatException("time length must be format 'n'") - return int(value) - -def datetime_to_timestamp(datetime_str): - # yyyy-mm-dd hh:mm:ss.uuuuus or yyyy-mm-dd hh:mm:ss - try: - if len(datetime_str) > 19: - dt = datetime.datetime.strptime(datetime_str, '%Y-%m-%d %H:%M:%S.%f') - else: - dt = datetime.datetime.strptime(datetime_str, '%Y-%m-%d %H:%M:%S') - return int(dt.timestamp() * 1000000) - except Exception as e: - return 0 - - -def trans_datetime_utc_to_local(datetime_str): - utct_date = datetime.datetime.strptime(datetime_str, "%Y-%m-%dT%H:%M:%S") # 2020-12-01 03:21:57 - local_date = utct_date + datetime.timedelta(hours=8) # 加上时区 - local_date_srt = datetime.datetime.strftime(local_date, "%Y-%m-%d %H:%M:%S") # 2020-12-01 11:21:57 - trans_res = datetime.datetime.strptime(local_date_srt, "%Y-%m-%d %H:%M:%S") - return str(trans_res) - - -def timestamp_to_datetime(date_timestamp): - second_timestamp, us = int(date_timestamp) // 1000000, int(date_timestamp) % 1000000 - time_obj = time.localtime(int(second_timestamp)) - datetime_str = time.strftime('%Y-%m-%d %H:%M:%S', time_obj) - datetime_str += '.{0}'.format(us) - return datetime_str - - -def timestamp_to_datetime_no_us(date_timestamp): - second_timestamp = int(date_timestamp) // 1000000 - time_obj = time.localtime(int(second_timestamp)) - datetime_str = time.strftime('%Y-%m-%d %H:%M:%S', time_obj) - return datetime_str - - -# transform timestamp(in us) to yyyymmdd hhmmss (filename_time style) -def timestamp_to_filename_time(timestamp): - second_timestamp = timestamp / 1000000 - time_obj = time.localtime(int(second_timestamp)) - filename_time_str = time.strftime('%Y%m%d%H%M%S', time_obj) - return filename_time_str - - -def parse_time_str(arg_time): - try: - format_time = datetime.datetime.strptime(arg_time, "%Y-%m-%d %H:%M:%S") - except Exception as e: - traceback.print_exc() - except_str = traceback.format_exc() - raise OBDIAGFormatException(except_str + "arg_time={0}".format(arg_time)) - return format_time - - -def filename_time_to_datetime(filename_time): - """ transform yyyymmddhhmmss to yyyy-mm-dd hh:mm:ss""" - if filename_time != "": - return "{0}-{1}-{2} {3}:{4}:{5}".format(filename_time[0:4], - filename_time[4:6], - filename_time[6:8], - filename_time[8:10], - filename_time[10:12], - filename_time[12:14]) - else: - return "" - - -def extract_filename_time_from_log_name(log_name): - """ eg: xxx.20221226231617 """ - log_name_fields = log_name.split(".") - if bytes.isdigit(log_name_fields[-1].encode("utf-8")) and len(log_name_fields[-1]) >= 14: - return log_name_fields[-1] - return "" - - -def extract_time_from_log_file_text(log_text): - # 因为 yyyy-mm-dd hh:mm:ss.000000 的格式已经占了27个字符,所以如果传进来的字符串包含时间信息,那长度一定大于27 - if len(log_text) > 27: - if log_text.startswith("["): - time_str = log_text[1: log_text.find(']')] - else: - time_str = log_text[0: log_text.find(',')] - time_without_us = time_str[0: time_str.find('.')] - try: - format_time = datetime.datetime.strptime(time_without_us, "%Y-%m-%d %H:%M:%S") - format_time_str = time.strftime("%Y-%m-%d %H:%M:%S", format_time.timetuple()) - except Exception as e: - format_time_str = "" - else: - format_time_str = "" - return format_time_str - - -def get_time_rounding(dt, step=0, rounding_level="s"): - """ - 计算整分钟,整小时,整天的时间 - :param step: 往前或往后跳跃取整值,默认为0,即当前所在的时间,正数为往后,负数往前。 - 例如: - step = 0 时 2022-07-26 17:38:21.869993 取整秒后为 2022-07-26 17:38:21 - step = 1 时 2022-07-26 17:38:21.869993 取整秒后为 2022-07-26 17:38:22 - step = -1 时 2022-07-26 17:38:21.869993 取整秒后为 2022-07-26 17:38:20 - :param rounding_level: 字符串格式。 - "s": 按秒取整;"min": 按分钟取整;"hour": 按小时取整;"days": 按天取整 - :return: 处理后的时间 - """ - if rounding_level == "days": - td = timedelta(days=-step, seconds=dt.second, microseconds=dt.microsecond, milliseconds=0, minutes=dt.minute, - hours=dt.hour, weeks=0) - new_dt = dt - td - elif rounding_level == "hour": - td = timedelta(days=0, seconds=dt.second, microseconds=dt.microsecond, milliseconds=0, minutes=dt.minute, - hours=-step, weeks=0) - new_dt = dt - td - elif rounding_level == "min": - td = timedelta(days=0, seconds=dt.second, microseconds=dt.microsecond, milliseconds=0, minutes=-step, hours=0, - weeks=0) - new_dt = dt - td - elif rounding_level == "s": - td = timedelta(days=0, seconds=-step, microseconds=dt.microsecond, milliseconds=0, minutes=0, hours=0, weeks=0) - new_dt = dt - td - else: - new_dt = dt - return str(new_dt) - - -def trans_time(size: int): - """ - 将时间单位转化为字符串 - :param size: 时间单位,单位为微秒 - :return: 转化后的字符串 - """ - if size < 0: - return 'NO_END' - mapping = [ - (86400000000, 'd'), - (3600000000, 'h'), - (60000000, 'm'), - (1000000, 's'), - (1000, 'ms'), - (1, 'μs'), - ] - for unit, unit_str in mapping: - if size >= unit: - if unit == 1: - return '{} {}'.format(size, unit_str) - else: - return '{:.3f} {}'.format(size / unit, unit_str) - return '0' - - -def str_2_timestamp(t): - if isinstance(t, int): - return t - temp = datetime.datetime.strptime(t, '%Y-%m-%d %H:%M:%S.%f') - return int(datetime.datetime.timestamp(temp) * 10 ** 6) -class DateTimeEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, datetime.datetime): - # 将datetime对象转换为字符串 - return obj.strftime('%Y-%m-%d %H:%M:%S') - # 其他类型按默认处理 - return super().default(obj) diff --git a/utils/utils.py b/utils/utils.py deleted file mode 100644 index 42ee2804..00000000 --- a/utils/utils.py +++ /dev/null @@ -1,220 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2022/6/22 -@file: utils.py -@desc: -""" -import decimal -import ast -import re -import sys -import subprocess -import socket -from common.logger import logger - - -def print_error(msg): - logger.error("\033[0;31m%s\033[0m" % msg) - - -def check_version_gt_36(): - if sys.version_info[0] >= 3 and sys.version_info[1] >= 6: - return True - return False - - -def containVarInString(containVar, stringVar): - try: - if isinstance(stringVar, str): - if stringVar.find(containVar): - return True - else: - return False - else: - return False - except Exception as e: - pass - - -def execute_command(cmd): - logger.debug("Executing: {}".format(cmd)) - output = None - try: - output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True) - except subprocess.CalledProcessError as err: - logger.error("Failed to execute cmd={}, returncode={}, output={}".format(cmd, err.returncode, err.output)) - raise - logger.debug("Executed cmd={}, output={}".format(cmd, output)) - return output - - -def get_localhost_inner_ip(): - localhost_ip = "127.0.0.1" - try: - localhost_ip = socket.gethostbyname(socket.gethostname()) - return localhost_ip - except Exception as e: - return localhost_ip - - -def get_observer_ip_from_trace_id(content): - if content[0] == 'Y' and len(content) >= 12: - sep = content.find('-') - uval = int(content[1:sep], 16) - ip = uval & 0xffffffff - port = (uval >> 32) & 0xffff - return "%d.%d.%d.%d:%d" % ((ip >> 24 & 0xff), (ip >> 16 & 0xff), (ip >> 8 & 0xff), (ip >> 0 & 0xff), port) - else: - return "" - - -def skip_char(sub, b): - if len(sub) <= 0: - return False - if sub[0] == b: - sub = sub[1:] - return True - return False - - -def convert_to_number(s): - if isinstance(s, (int, float)): - return s - if isinstance(s,decimal.Decimal): - try: - return float(s) - except: - return s - - if isinstance(s, str): - if s.startswith("-"): - if s[1:].isdigit(): - return int(s) - elif s[1:].isdecimal(): # 判断字符串是否全为数字或小数点 - return float(s) # 如果是,转换为浮点数 - if s.isdigit(): # 判断字符串是否全为数字 - return int(s) # 如果是,转换为整数 - elif s.isdecimal(): # 判断字符串是否全为数字或小数点 - return float(s) # 如果是,转换为浮点数 - try: - return float(s) - except Exception: - pass - - return s - - -def parse_range_string(range_str, nu): - # parse_range_string: Determine whether variable 'nu' is within the range of 'range_str' - # 提取范围字符串中的数字 - nu = int(nu) - range_str = range_str.replace(" ", "") - # range_str = range_str.replace(".", "") - start, end = range_str[1:-1].split(',') - need_less = True - need_than = True - # 将数字转换为整数 - if start.strip() == "*": - need_less = False - else: - start = float(start.strip()) - if end.strip() == "*": - need_than = False - else: - end = float(end.strip()) - logger.info("range_str is {0}".format(range_str)) - - if need_less: - if range_str[0] == "(": - if nu <= start: - return False - elif range_str[0] == "[": - if nu < start: - return False - if need_than: - if range_str[-1] == ")": - if nu >= end: - return False - elif range_str[-1] == "]": - if nu > end: - return False - return True - - -def build_str_on_expr_by_dict(expr, variable_dict): - s = expr - d = variable_dict - - def replacer(match): - key = match.group(1) - return str(d.get(key, match.group(0))) - - return re.sub(r'#\{(\w+)\}', replacer, s) - - -def build_str_on_expr_by_dict_2(expr, variable_dict): - s = expr - d = variable_dict - - def replacer(match): - key = match.group(1) - value = str(d.get(key, match.group(0))) - return f"'{value}'" - - return re.sub(r'\$\{(\w+)\}', replacer, s) - - -def display_trace(uuid): - print("If you want to view detailed obdiag logs, please run:' obdiag display-trace --trace_id {0} '".format(uuid)) - - -def node_cut_passwd_for_log(obj): - if isinstance(obj, dict): - new_obj = {} - for key, value in obj.items(): - if key == "password" or key == "ssh_password": - continue - new_obj[key] = node_cut_passwd_for_log(value) - return new_obj - elif isinstance(obj, list): - return [node_cut_passwd_for_log(item) for item in obj] - else: - return obj - - -def obcluster_cut_passwd_for_log(obcluster): - new_obj = obcluster.copy() - if "tenant_sys" in new_obj and "password" in new_obj["tenant_sys"]: - del new_obj["tenant_sys"]["password"] - return new_obj - - -def split_ip(ip_str): - pattern = r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' - result = re.findall(pattern, ip_str) - return result - - -def parse_env_string_to_dict(env_string): - env_string = env_string.replace(';', ',') - env_dict = ast.literal_eval(f"{{{env_string}}}") - return env_dict - -def is_chinese(s): - try: - s.encode('ascii') - except UnicodeEncodeError: - return True - else: - return False \ No newline at end of file diff --git a/utils/yaml_utils.py b/utils/yaml_utils.py deleted file mode 100644 index 5da34d1b..00000000 --- a/utils/yaml_utils.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python -# -*- coding: UTF-8 -* -# Copyright (c) 2022 OceanBase -# OceanBase Diagnostic Tool 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. - -""" -@time: 2023/1/17 -@file: yaml_utils.py -@desc: -""" - -import oyaml as yaml -import os - - -def is_yaml_file(path): - if not os.path.isfile(path): - return False - if path.endswith(('.yaml', '.yml')): - return True - else: - return False - -def read_yaml_data(file_path): - if is_yaml_file(file_path): - try: - with open(file_path, 'r') as f: - data = yaml.load(f, Loader=yaml.FullLoader) - return data - except yaml.YAMLError as exc: - raise Exception("Error loading YAML from file, error: {0}".format(exc)) - - -def write_yaml_data(data, file_path): - with open(file_path, 'w') as f: - yaml.safe_dump(data, f, allow_unicode=True, sort_keys=False) - - -def write_yaml_data_append(data, file_path): - with open(file_path, 'a+') as f: - yaml.safe_dump(data, f, allow_unicode=True, sort_keys=False) - - -def write_yaml_data_sorted(data, file_path): - with open(file_path, 'w') as f: - yaml.safe_dump(data, f, allow_unicode=True, sort_keys=True) - - -def write_yaml_data_append_sorted(data, file_path): - with open(file_path, 'a+') as f: - yaml.safe_dump(data, f, allow_unicode=True, sort_keys=True) From 39d93f231e79d1c9a384dc8dd911731da5c6a30a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A0=E7=A3=8A?= Date: Thu, 11 Apr 2024 19:46:04 +0800 Subject: [PATCH 2/2] release 2.0.0 --- LICENSE | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index d7248cbe..ea7b4745 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,68 @@ - Mulan Permissive Software License,Version 2 +木兰宽松许可证, 第2版 + +2020年1月 http://license.coscl.org.cn/MulanPSL2 + +您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: + +0. 定义 + +“软件” 是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 + +“贡献” 是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 + +“贡献者” 是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 + +“法人实体” 是指提交贡献的机构及其“关联实体”。 + +“关联实体” 是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 + +1. 授予版权许可 + +每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 + +2. 授予专利许可 + +每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 + +3. 无商标许可 + +“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 + +4. 分发限制 + +您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 + +5. 免责声明与责任限制 + +“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 + +6. 语言 + +“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 + +条款结束 + +如何将木兰宽松许可证,第2版,应用到您的软件 + +如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: + +1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; + +2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; + +3, 请将如下声明文本放入每个源文件的头部注释中。 + +``` +Copyright (c) [Year] [name of copyright holder] +[Software Name] 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. +``` Mulan Permissive Software License,Version 2 (Mulan PSL v2) @@ -42,4 +106,26 @@ THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. -END OF THE TERMS AND CONDITIONS \ No newline at end of file +END OF THE TERMS AND CONDITIONS + +How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software + +To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps: + +Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; + +Create a file named "LICENSE" which contains the whole context of this License in the first directory of your software package; + +Attach the statement to the appropriate annotated syntax at the beginning of each source file. + +``` +Copyright (c) [Year] [name of copyright holder] +[Software Name] 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. +``` \ No newline at end of file