Skip to content

Commit

Permalink
fix(backend): 修复spider异常无法提单问题 #8709
Browse files Browse the repository at this point in the history
  • Loading branch information
iSecloud committed Dec 18, 2024
1 parent 999a977 commit b790e8d
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 46 deletions.
12 changes: 2 additions & 10 deletions dbm-ui/backend/db_meta/api/cluster/tendbha/detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,11 @@ def scan_cluster(cluster: Cluster) -> Graphic:
dummy_slave_be_node, slave_be_group = graph.add_node(slave_be, to_group=slave_be_group)
graph.add_line(source=slave_be_group, target=receiver_instance_group, label=LineLabel.Bind)

for otr in (
StorageInstanceTuple.objects.filter(ejector=ejector_instance)
.prefetch_related("cluster")
.exclude(receiver__cluster=cluster)
):
for otr in StorageInstanceTuple.objects.filter(ejector=ejector_instance).exclude(receiver__cluster=cluster):
foreign_receiver_cluster = otr.receiver.cluster.get()
graph.add_foreign_cluster(ForeignRelationType.RepTo, foreign_receiver_cluster)

for otr in (
StorageInstanceTuple.objects.filter(receiver=ejector_instance)
.prefetch_related("cluster")
.exclude(ejector__cluster=cluster)
):
for otr in StorageInstanceTuple.objects.filter(receiver=ejector_instance).exclude(ejector__cluster=cluster):
foreign_ejector_cluster = otr.ejector.cluster.get()
graph.add_foreign_cluster(ForeignRelationType.RepFrom, foreign_ejector_cluster)

Expand Down
17 changes: 16 additions & 1 deletion dbm-ui/backend/tests/mock_data/components/itsm.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ def update_service(cls, *args, **kwargs):
def create_ticket(cls, *args, **kwargs):
response_data = copy.deepcopy(cls.base_info)
response_data["data"] = {"sn": ticket_flow.SN}

return response_data["data"]

@classmethod
Expand All @@ -99,6 +98,22 @@ def ticket_approval_result(cls, *args, **kwargs):

return response_data["data"]

@classmethod
def get_ticket_logs(cls, *args, **kwargs):
response_data = copy.deepcopy(cls.base_info)
response_data["data"] = {
"sn": "REQ20200831000005",
"title": "测试内置审批日志",
"create_at": "2020-08-31 20:57:22",
"creator": "xxx(xxx)",
"logs": [
{"operator": "xxx", "message": "流程开始"},
{"operator": "xxx", "message": "xxx 处理节点【提单】(提交)"},
{"operator": "admin", "message": "admin 处理节点【审批】(通过)"},
],
}
return response_data["data"]

@classmethod
def get_ticket_info(cls, *args, **kwargs):
response_data = copy.deepcopy(cls.base_info)
Expand Down
4 changes: 2 additions & 2 deletions dbm-ui/backend/tests/mock_data/ticket/sqlserver_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,12 @@
"bk_cloud_id": 0,
"city_code": "深圳",
"db_module_id": DB_MODULE_ID + 1,
"cluster_count": 1,
"cluster_count": 2,
"inst_num": 1,
"ip_source": "resource_pool",
"nodes": {"backend": []},
"resource_spec": {
"sqlserver_ha": {
"backend_group": {
"spec_id": 102,
"spec_name": "2核_4G_10G",
"spec_cluster_type": "sqlserver_ha",
Expand Down
8 changes: 3 additions & 5 deletions dbm-ui/backend/tests/mock_data/ticket/ticket_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,9 @@
MYSQL_FULL_BACKUP_TICKET_DATA = {
"bk_biz_id": constant.BK_BIZ_ID,
"details": {
"infos": {
"backup_type": "logical",
"file_tag": "DBFILE1M",
"clusters": [{"cluster_id": 1, "backup_local": "master"}],
}
"backup_type": "logical",
"file_tag": "DBFILE1M",
"infos": [{"cluster_id": 1, "backup_local": "master"}],
},
"remark": "",
"ticket_type": "MYSQL_HA_FULL_BACKUP",
Expand Down
9 changes: 3 additions & 6 deletions dbm-ui/backend/tests/ticket/test_ticket_revoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from backend.tests.mock_data.components.itsm import ItsmApiMock
from backend.tests.mock_data.iam_app.permission import PermissionMock
from backend.tests.mock_data.ticket.ticket_flow import MYSQL_FULL_BACKUP_TICKET_DATA, SN
from backend.ticket.builders.mysql.mysql_ha_full_backup import MySQLHaFullBackupDetailSerializer
from backend.ticket.builders.mysql.mysql_full_backup import MySQLFullBackupDetailSerializer
from backend.ticket.constants import TicketStatus, TodoStatus, TodoType
from backend.ticket.flow_manager.inner import InnerFlow
from backend.ticket.handler import TicketHandler
Expand All @@ -46,19 +46,16 @@ class TestTicketRevoke:
"""

@patch.object(TicketViewSet, "permission_classes")
@patch.object(MySQLHaFullBackupDetailSerializer, "validate")
@patch.object(InnerFlow, "status", new_callable=PropertyMock)
@patch.object(TicketViewSet, "get_permissions", lambda x: [])
@patch.object(MySQLFullBackupDetailSerializer, "validate", lambda self, attrs: attrs)
@patch("backend.ticket.flow_manager.itsm.ItsmApi", ItsmApiMock())
@patch("backend.db_services.cmdb.biz.CCApi", CCApiMock())
@patch("backend.db_services.cmdb.biz.Permission", PermissionMock)
def test_ticket_revoke(
self, mocked_status, mocked_validate, mocked_permission_classes, query_fixture, db, init_app
):
def test_ticket_revoke(self, mocked_status, mocked_permission_classes, db, init_app):
# 以全库备份为例,测试流程:start --> itsm --> inner --> end
mocked_status.return_value = TicketStatus.SUCCEEDED
mocked_permission_classes.return_value = [AllowAny]
mocked_validate.return_value = MYSQL_FULL_BACKUP_TICKET_DATA

client.login(username="admin")
# 创建单据
Expand Down
20 changes: 14 additions & 6 deletions dbm-ui/backend/ticket/builders/sqlserver/sqlserver_ha_apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,22 +85,30 @@ def format_cluster_domains(self) -> List[Dict[str, str]]:

@classmethod
def insert_ip_into_apply_infos(cls, ticket_data, infos: List[Dict]):
backend_nodes = ticket_data["nodes"]["backend_group"]
backend_nodes = ticket_data["nodes"][MachineType.SQLSERVER_HA.value]
for index, apply_info in enumerate(infos):
# # 每组集群需要两个后端 IP 和两个 Proxy IP
# start, end = index * 2, (index + 1) * 2
apply_info["mssql_master_host"] = backend_nodes[index]["master"]
apply_info["mssql_slave_host"] = backend_nodes[index]["slave"]
# 每组集群需要两个后端 IP 和两个 Proxy IP
start, end = index * 2, (index + 1) * 2
apply_info["mssql_master_host"] = backend_nodes[start:end][0]
apply_info["mssql_slave_host"] = backend_nodes[start:end][1]


class SQLServerHaApplyResourceParamBuilder(SQLServerSingleApplyResourceParamBuilder):
def format(self):
super().format()

@classmethod
def insert_ip_into_apply_infos(cls, ticket_data, infos: List[Dict]):
backend_nodes = ticket_data["nodes"]["backend_group"]
for index, apply_info in enumerate(infos):
# 每组集群需要两个后端 IP 和两个 Proxy IP
apply_info["mssql_master_host"] = backend_nodes[index]["master"]
apply_info["mssql_slave_host"] = backend_nodes[index]["slave"]

def post_callback(self):
next_flow = self.ticket.next_flow()
infos = next_flow.details["ticket_data"]["infos"]
SQLServerHAApplyFlowParamBuilder.insert_ip_into_apply_infos(self.ticket.details, infos)
self.insert_ip_into_apply_infos(self.ticket.details, infos)
next_flow.details["ticket_data"]["resource_spec"]["sqlserver_ha"] = next_flow.details["ticket_data"][
"resource_spec"
]["master"]
Expand Down
6 changes: 2 additions & 4 deletions dbm-ui/backend/ticket/builders/tendbcluster/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,8 @@ class TendbBaseOperateDetailSerializer(MySQLBaseOperateDetailSerializer):

# 实例不可用时,还能正常提单类型的白名单
# spider 接入层异常, 只允许修复接入层异常的单据 1. 踢出故障 spider 2. 上架 (扩容) 新的 spider
# slave或者 spider 不可用不影响分区单据
SPIDER_UNAVAILABLE_WHITELIST = [
TicketType.TENDBCLUSTER_PARTITION.value,
]
# slave或者 spider 不可用不影响分区单据。TODO:暂时放开spider异常限制
SPIDER_UNAVAILABLE_WHITELIST = TicketType.get_values()
# 存储层 master 异常 (dbha 因为某些问题未正常介入),只有切换单据可用
REMOTE_MASTER_UNAVAILABLE_WHITELIST = [
TicketType.TENDBCLUSTER_MASTER_SLAVE_SWITCH,
Expand Down
Binary file modified dbm-ui/backend/ticket/exclusive_ticket.xlsx
Binary file not shown.
33 changes: 21 additions & 12 deletions dbm-ui/backend/ticket/flow_manager/itsm.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,34 @@ def __init__(self, flow_obj: Flow):
@property
def ticket_approval_result(self):
# 优先读取缓存,避免同一个对象内多次请求 ITSM
# TODO: ITSM接口请求比较缓慢
if getattr(self, "_ticket_approval_result", None):
return self._ticket_approval_result

# 调用ITSM接口查询审批状态
data = ItsmApi.ticket_approval_result({"sn": [self.flow_obj.flow_obj_id]}, use_admin=True)
try:
data = ItsmApi.ticket_approval_result({"sn": [self.flow_obj.flow_obj_id]}, use_admin=True)
itsm_ticket_result = data[0]
except IndexError:
except (IndexError, ApiResultError):
itsm_ticket_result = None

setattr(self, "_ticket_approval_result", itsm_ticket_result)
return itsm_ticket_result

@property
def ticket_logs(self):
# 同ticket_approval_result,优先读取缓存
if getattr(self, "_ticket_logs", None):
return self._ticket_logs

try:
itsm_logs = ItsmApi.get_ticket_logs({"sn": [self.flow_obj.flow_obj_id]}, use_admin=True)
ticket_logs = itsm_logs["logs"]
except (KeyError, ApiResultError):
ticket_logs = []

setattr(self, "_ticket_logs", ticket_logs)
return ticket_logs

@property
def _start_time(self) -> str:
return datetime2str(self.flow_obj.create_at)
Expand All @@ -58,23 +72,18 @@ def _end_time(self) -> Union[datetime, Any]:

@property
def _summary(self) -> dict:
try:
logs = ItsmApi.get_ticket_logs({"sn": [self.flow_obj.flow_obj_id]})
except ApiResultError:
return _("未知单据")

# 获取单据审批状态
current_status = self.ticket_approval_result["current_status"]
approve_result = self.ticket_approval_result["approve_result"]
summary = {"status": current_status, "approve_result": approve_result}

# 目前审批流程是固定的,取流程中第三个节点的日志作为概览即可
try:
summary.update(operator=logs["logs"][2]["operator"], message=logs["logs"][2]["message"])
summary.update(operator=self.ticket_logs[2]["operator"], message=self.ticket_logs[2]["message"])
except (IndexError, KeyError):
# 异常时根据状态取默认的概览
msg = TicketStatus.get_choice_label(self.status)
summary.update(operator=logs["logs"][-1]["operator"], status=self.status, message=msg)
summary.update(operator=self.ticket_logs[-1]["operator"], status=self.status, message=msg)
return summary

@property
Expand All @@ -88,8 +97,8 @@ def _status(self) -> str:
return self.flow_obj.update_status(TicketFlowStatus.RUNNING)

todo = self.flow_obj.todo_of_flow.first()
updater = self.ticket_approval_result["updated_by"]

# 非进行中的单据,肯定已经来到了第三个节点,否则也无法处理todo
updater = self.ticket_logs[2]["operator"]
# 撤单
if current_status == ItsmTicketStatus.REVOKED:
todo.set_status(username=updater, status=TodoStatus.DONE_FAILED)
Expand Down

0 comments on commit b790e8d

Please sign in to comment.