|
| 1 | +#!/usr/bin/env python |
| 2 | +# -*- coding: UTF-8 -*- |
| 3 | +# Copyright (c) 2022 OceanBase |
| 4 | +# OceanBase Diagnostic Tool is licensed under Mulan PSL v2. |
| 5 | +# You can use this software according to the terms and conditions of the Mulan PSL v2. |
| 6 | +# You may obtain a copy of Mulan PSL v2 at: |
| 7 | +# http://license.coscl.org.cn/MulanPSL2 |
| 8 | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, |
| 9 | +# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, |
| 10 | +# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. |
| 11 | +# See the Mulan PSL v2 for more details. |
| 12 | + |
| 13 | +""" |
| 14 | +@time: 2026/6/8 |
| 15 | +@file: major_freeze_not_allow.py |
| 16 | +@desc: RCA scene for manual major freeze rejected by OB_MAJOR_FREEZE_NOT_ALLOW. |
| 17 | +""" |
| 18 | + |
| 19 | +import os |
| 20 | + |
| 21 | +from src.handler.rca.rca_exception import RCAExecuteException, RCAInitException |
| 22 | +from src.handler.rca.rca_handler import RcaScene |
| 23 | +from src.common.tool import StringUtils |
| 24 | + |
| 25 | + |
| 26 | +class MajorFreezeNotAllowScene(RcaScene): |
| 27 | + ERROR_PATTERNS = ("OB_MAJOR_FREEZE_NOT_ALLOW", "ERROR 4217", "-4217", "MAJOR FREEZE NOT ALLOWED NOW") |
| 28 | + |
| 29 | + def __init__(self): |
| 30 | + super().__init__() |
| 31 | + self.tenant_name = None |
| 32 | + self.error_msg = None |
| 33 | + self.collect_logs = True |
| 34 | + |
| 35 | + def verbose(self, info): |
| 36 | + self.stdio.verbose("[MajorFreezeNotAllowScene] {0}".format(info)) |
| 37 | + |
| 38 | + def get_scene_info(self): |
| 39 | + return { |
| 40 | + "name": "major_freeze_not_allow", |
| 41 | + "info_en": "Diagnose OB_MAJOR_FREEZE_NOT_ALLOW when manually executing major freeze.", |
| 42 | + "info_cn": "诊断手动执行合并时出现 OB_MAJOR_FREEZE_NOT_ALLOW 的原因", |
| 43 | + "example": "obdiag rca run --scene=major_freeze_not_allow --env tenant_name=xxx [--env error_msg='ERROR 4217 (HY000): Major freeze not allowed now']", |
| 44 | + } |
| 45 | + |
| 46 | + def init(self, context): |
| 47 | + super().init(context) |
| 48 | + min_supported_version = "4.0.0.0" |
| 49 | + if self.observer_version is None or len(self.observer_version.strip()) == 0: |
| 50 | + raise RCAInitException("observer version is None. Please check the NODES conf.") |
| 51 | + if not (self.observer_version == min_supported_version or StringUtils.compare_versions_greater(self.observer_version, min_supported_version)): |
| 52 | + raise RCAInitException("observer version is {0}, which is less than {1}.".format(self.observer_version, min_supported_version)) |
| 53 | + if self.ob_connector is None: |
| 54 | + raise RCAInitException("ob_connector is None. Please check the NODES conf.") |
| 55 | + |
| 56 | + self.tenant_name = self.input_parameters.get("tenant_name") |
| 57 | + if not self.tenant_name: |
| 58 | + raise RCAInitException("tenant_name is required. Please use --env tenant_name=xxx.") |
| 59 | + |
| 60 | + self.error_msg = self.input_parameters.get("error_msg") |
| 61 | + collect_logs = str(self.input_parameters.get("collect_logs", "true")).lower() |
| 62 | + self.collect_logs = collect_logs not in ("0", "false", "no") |
| 63 | + self.work_path = context.get_variable("store_dir") |
| 64 | + if not os.path.exists(self.work_path): |
| 65 | + os.makedirs(self.work_path) |
| 66 | + self.record.add_record("major_freeze_not_allow init: tenant_name={0}, collect_logs={1}".format(self.tenant_name, self.collect_logs)) |
| 67 | + |
| 68 | + @classmethod |
| 69 | + def is_major_freeze_not_allow_error(cls, error_msg): |
| 70 | + if not error_msg: |
| 71 | + return True |
| 72 | + normalized_error_msg = str(error_msg).upper() |
| 73 | + return any(pattern in normalized_error_msg for pattern in cls.ERROR_PATTERNS) |
| 74 | + |
| 75 | + @staticmethod |
| 76 | + def _escape_sql_literal(value): |
| 77 | + return str(value).replace("'", "''") |
| 78 | + |
| 79 | + def _query_tenant_role(self, tenant_name): |
| 80 | + tenant_name_escaped = self._escape_sql_literal(tenant_name) |
| 81 | + sql = "SELECT tenant_name, tenant_role FROM oceanbase.DBA_OB_TENANTS WHERE tenant_name = '{0}'".format(tenant_name_escaped) |
| 82 | + self.record.add_record("Query tenant role: {0}".format(sql)) |
| 83 | + cursor = self.ob_connector.execute_sql_return_cursor_dictionary(sql) |
| 84 | + rows = cursor.fetchall() |
| 85 | + if not rows: |
| 86 | + return None |
| 87 | + return rows[0] |
| 88 | + |
| 89 | + @staticmethod |
| 90 | + def _row_get(row, *keys): |
| 91 | + for key in keys: |
| 92 | + if key in row: |
| 93 | + return row.get(key) |
| 94 | + return None |
| 95 | + |
| 96 | + def _check_tenant_role(self): |
| 97 | + row = self._query_tenant_role(self.tenant_name) |
| 98 | + if row is None: |
| 99 | + self.record.add_record("Tenant '{0}' was not found in oceanbase.DBA_OB_TENANTS.".format(self.tenant_name)) |
| 100 | + self.record.add_suggest("Check whether tenant_name is correct, then rerun this RCA scene.") |
| 101 | + return |
| 102 | + |
| 103 | + tenant_role = self._row_get(row, "tenant_role", "TENANT_ROLE") |
| 104 | + tenant_name = self._row_get(row, "tenant_name", "TENANT_NAME") or self.tenant_name |
| 105 | + self.record.add_record("Tenant role: tenant_name={0}, tenant_role={1}".format(tenant_name, tenant_role)) |
| 106 | + if str(tenant_role).upper() == "STANDBY": |
| 107 | + self.record.add_suggest("Tenant '{0}' is a standby tenant. Manual `ALTER SYSTEM MAJOR FREEZE tenant = {0}` is not allowed on standby tenants; execute manual major freeze on the primary tenant or wait for synchronization.".format(tenant_name)) |
| 108 | + else: |
| 109 | + self.record.add_suggest("Tenant '{0}' role is {1}, not STANDBY. OB_MAJOR_FREEZE_NOT_ALLOW is not explained by standby tenant role; continue checking observer/rootservice logs around the failed major freeze.".format(tenant_name, tenant_role)) |
| 110 | + |
| 111 | + def _gather_related_logs(self): |
| 112 | + if not self.collect_logs or self.gather_log is None: |
| 113 | + self.record.add_record("Skip collecting logs for OB_MAJOR_FREEZE_NOT_ALLOW.") |
| 114 | + return |
| 115 | + log_path = os.path.join(self.work_path, "major_freeze_not_allow_logs") |
| 116 | + self.gather_log.set_parameters("scope", "observer") |
| 117 | + self.gather_log.grep("OB_MAJOR_FREEZE_NOT_ALLOW") |
| 118 | + logs_name = self.gather_log.execute(save_path=log_path) |
| 119 | + if logs_name: |
| 120 | + self.record.add_record("Collected OB_MAJOR_FREEZE_NOT_ALLOW logs: {0}".format(logs_name)) |
| 121 | + else: |
| 122 | + self.record.add_record("No OB_MAJOR_FREEZE_NOT_ALLOW logs were collected.") |
| 123 | + |
| 124 | + def execute(self): |
| 125 | + try: |
| 126 | + if not self.is_major_freeze_not_allow_error(self.error_msg): |
| 127 | + self.record.add_record("error_msg does not contain OB_MAJOR_FREEZE_NOT_ALLOW patterns: {0}".format(self.error_msg)) |
| 128 | + self.record.add_suggest("This scene is intended for ERROR 4217 / OB_MAJOR_FREEZE_NOT_ALLOW. Please check the error message or choose another RCA scene.") |
| 129 | + return |
| 130 | + self.record.add_record("Start diagnosing OB_MAJOR_FREEZE_NOT_ALLOW for tenant '{0}'.".format(self.tenant_name)) |
| 131 | + self._gather_related_logs() |
| 132 | + self._check_tenant_role() |
| 133 | + except Exception as e: |
| 134 | + raise RCAExecuteException("MajorFreezeNotAllowScene execute error: {0}".format(e)) |
| 135 | + finally: |
| 136 | + self.stdio.verbose("end MajorFreezeNotAllowScene execute") |
| 137 | + |
| 138 | + |
| 139 | +major_freeze_not_allow = MajorFreezeNotAllowScene() |
0 commit comments