diff --git a/dbm-ui/backend/db_meta/api/cluster/tendbha/detail.py b/dbm-ui/backend/db_meta/api/cluster/tendbha/detail.py index e89e6ca8f2..da7ffc035d 100644 --- a/dbm-ui/backend/db_meta/api/cluster/tendbha/detail.py +++ b/dbm-ui/backend/db_meta/api/cluster/tendbha/detail.py @@ -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) diff --git a/dbm-ui/backend/tests/mock_data/components/itsm.py b/dbm-ui/backend/tests/mock_data/components/itsm.py index 2dd233e2d0..8de5f24460 100644 --- a/dbm-ui/backend/tests/mock_data/components/itsm.py +++ b/dbm-ui/backend/tests/mock_data/components/itsm.py @@ -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 @@ -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) diff --git a/dbm-ui/backend/tests/mock_data/ticket/sqlserver_flow.py b/dbm-ui/backend/tests/mock_data/ticket/sqlserver_flow.py index 1147712fd0..72e5386b1d 100644 --- a/dbm-ui/backend/tests/mock_data/ticket/sqlserver_flow.py +++ b/dbm-ui/backend/tests/mock_data/ticket/sqlserver_flow.py @@ -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", diff --git a/dbm-ui/backend/tests/mock_data/ticket/ticket_flow.py b/dbm-ui/backend/tests/mock_data/ticket/ticket_flow.py index 6806df954a..dec29f1c27 100644 --- a/dbm-ui/backend/tests/mock_data/ticket/ticket_flow.py +++ b/dbm-ui/backend/tests/mock_data/ticket/ticket_flow.py @@ -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", diff --git a/dbm-ui/backend/tests/ticket/test_ticket_revoke.py b/dbm-ui/backend/tests/ticket/test_ticket_revoke.py index ee10680937..bd055a9f54 100644 --- a/dbm-ui/backend/tests/ticket/test_ticket_revoke.py +++ b/dbm-ui/backend/tests/ticket/test_ticket_revoke.py @@ -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 @@ -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") # 创建单据 diff --git a/dbm-ui/backend/ticket/builders/sqlserver/sqlserver_ha_apply.py b/dbm-ui/backend/ticket/builders/sqlserver/sqlserver_ha_apply.py index 9db57848ec..8102792d75 100644 --- a/dbm-ui/backend/ticket/builders/sqlserver/sqlserver_ha_apply.py +++ b/dbm-ui/backend/ticket/builders/sqlserver/sqlserver_ha_apply.py @@ -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"] diff --git a/dbm-ui/backend/ticket/builders/tendbcluster/base.py b/dbm-ui/backend/ticket/builders/tendbcluster/base.py index 632f3a42f6..58f0c7add5 100644 --- a/dbm-ui/backend/ticket/builders/tendbcluster/base.py +++ b/dbm-ui/backend/ticket/builders/tendbcluster/base.py @@ -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, diff --git a/dbm-ui/backend/ticket/exclusive_ticket.xlsx b/dbm-ui/backend/ticket/exclusive_ticket.xlsx index ea37615e51..444af12124 100644 Binary files a/dbm-ui/backend/ticket/exclusive_ticket.xlsx and b/dbm-ui/backend/ticket/exclusive_ticket.xlsx differ diff --git a/dbm-ui/backend/ticket/flow_manager/itsm.py b/dbm-ui/backend/ticket/flow_manager/itsm.py index 9dfdb3e337..b26b01bdb1 100644 --- a/dbm-ui/backend/ticket/flow_manager/itsm.py +++ b/dbm-ui/backend/ticket/flow_manager/itsm.py @@ -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) @@ -58,11 +72,6 @@ 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"] @@ -70,11 +79,11 @@ def _summary(self) -> dict: # 目前审批流程是固定的,取流程中第三个节点的日志作为概览即可 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 @@ -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)