Skip to content

Commit

Permalink
feat(backend): 单据状态细化 #6755
Browse files Browse the repository at this point in the history
  • Loading branch information
iSecloud committed Nov 21, 2024
1 parent ed153c7 commit 93b9f78
Show file tree
Hide file tree
Showing 31 changed files with 835 additions and 390 deletions.
4 changes: 2 additions & 2 deletions dbm-ui/backend/db_services/bigdata/resources/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from backend.db_proxy.models import ClusterExtension
from backend.db_services.dbbase.resources import query
from backend.db_services.ipchooser.query.resource import ResourceQueryHelper
from backend.ticket.constants import TicketFlowStatus
from backend.ticket.constants import TICKET_RUNNING_STATUS
from backend.ticket.models import InstanceOperateRecord
from backend.utils.time import datetime2str

Expand Down Expand Up @@ -65,7 +65,7 @@ def _filter_instance_hook(cls, bk_biz_id, query_params, instances, **kwargs):

# 获取实例的操作与实例记录
records = InstanceOperateRecord.objects.filter(
instance_id__in=instance_ids, ticket__status=TicketFlowStatus.RUNNING
instance_id__in=instance_ids, ticket__status__in=TICKET_RUNNING_STATUS
)
instance_operate_records_map: Dict[int, List] = defaultdict(list)
for record in records:
Expand Down
4 changes: 2 additions & 2 deletions dbm-ui/backend/db_services/mysql/dumper/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from backend.db_meta.enums import InstanceInnerRole
from backend.db_meta.models import Cluster
from backend.db_services.mysql.dumper.models import DumperSubscribeConfig
from backend.ticket.constants import FlowType, TicketFlowStatus, TicketStatus, TicketType
from backend.ticket.constants import TICKET_RUNNING_STATUS, FlowType, TicketFlowStatus, TicketStatus, TicketType
from backend.ticket.models import Flow, Ticket


Expand Down Expand Up @@ -66,7 +66,7 @@ def patch_dumper_list_info(cls, dumper_results: List[Dict], bk_biz_id: int = 0,
dumper_ticket_types.remove(TicketType.TBINLOGDUMPER_INSTALL)
dumper_ticket_types.extend([TicketType.MYSQL_MASTER_SLAVE_SWITCH, TicketType.MYSQL_MASTER_FAIL_OVER])
active_tickets = Ticket.objects.filter(
bk_biz_id=bk_biz_id, status=TicketStatus.RUNNING, ticket_type__in=dumper_ticket_types
bk_biz_id=bk_biz_id, status__in=TICKET_RUNNING_STATUS, ticket_type__in=dumper_ticket_types
)
# 获取每个dumper单据状态与id的映射
dumper_inst_id__ticket: Dict[int, str] = {}
Expand Down
25 changes: 6 additions & 19 deletions dbm-ui/backend/flow/plugins/components/collections/common/pause.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
from pipeline.core.flow.io import ObjectItemSchema, StringItemSchema

from backend.flow.plugins.components.collections.common.base_service import BaseService
from backend.ticket.constants import TodoType
from backend.ticket.models import Ticket, Todo
from backend.ticket.todos.pipeline_todo import PipelineTodoContext
from backend.ticket.models import Ticket
from backend.ticket.todos.pipeline_todo import PipelineTodo

logger = logging.getLogger("root")

Expand All @@ -34,26 +33,14 @@ def _execute(self, data, parent_data):
self.log_info("execute PauseService")
kwargs = data.get_one_of_inputs("kwargs")
global_data = data.get_one_of_inputs("global_data")

# 获取单据和flow信息
ticket_id = global_data["uid"]
ticket = Ticket.objects.get(id=ticket_id)

# todo:这里假设ticket中不会出现并行的flow
flow = ticket.current_flow()

Todo.objects.create(
name=_("【{}】流程待确认,是否继续?").format(ticket.get_ticket_type_display()),
flow=flow,
ticket=ticket,
type=TodoType.INNER_APPROVE,
# todo: 待办人暂定为提单人
operators=[ticket.creator],
context=PipelineTodoContext(
flow.id,
ticket_id,
self.runtime_attrs.get("root_pipeline_id"),
self.runtime_attrs.get("id"),
).to_dict(),
)
# 创建一条代办
PipelineTodo.create(ticket, flow, self.runtime_attrs.get("root_pipeline_id"), self.runtime_attrs.get("id"))

self.log_info("pause kwargs: {}".format(kwargs))
return True
Expand Down
13 changes: 13 additions & 0 deletions dbm-ui/backend/tests/mock_data/ticket/ticket_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@
"ticket_type": "MYSQL_AUTHORIZE_RULES",
}

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"}],
}
},
"remark": "",
"ticket_type": "MYSQL_HA_FULL_BACKUP",
}

MYSQL_PERMISSION_ACCOUNT = {
"items": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@
SCALEUP_POOL_TICKET_DATA,
)
from backend.tests.ticket.server_base import TestFlowBase
from backend.ticket.constants import TicketFlowStatus, TicketStatus
from backend.ticket.constants import TicketFlowStatus

logger = logging.getLogger("test")
pytestmark = pytest.mark.django_db
client = APIClient()

INITIAL_FLOW_FINISHED_STATUS = [TicketFlowStatus.SKIPPED, TicketStatus.SUCCEEDED]
CHANGED_MOCK_STATUS = [TicketFlowStatus.SKIPPED, TicketStatus.SUCCEEDED, TicketFlowStatus.RUNNING]
INITIAL_FLOW_FINISHED_STATUS = [TicketFlowStatus.SKIPPED, TicketFlowStatus.SUCCEEDED]
CHANGED_MOCK_STATUS = [TicketFlowStatus.SKIPPED, TicketFlowStatus.SUCCEEDED, TicketFlowStatus.RUNNING]


@pytest.fixture(autouse=True) # autouse=True 会自动应用这个fixture到所有的测试中
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
pytestmark = pytest.mark.django_db
client = APIClient()

INITIAL_FLOW_FINISHED_STATUS = [TicketFlowStatus.SKIPPED, TicketStatus.SUCCEEDED]
INITIAL_FLOW_FINISHED_STATUS = [TicketFlowStatus.SKIPPED, TicketFlowStatus.SUCCEEDED]
CHANGED_MOCK_STATUS = [TicketFlowStatus.SKIPPED, TicketStatus.SUCCEEDED, TicketFlowStatus.RUNNING]


Expand Down
75 changes: 75 additions & 0 deletions dbm-ui/backend/tests/ticket/test_ticket_revoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available.
Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at https://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""
import copy
import logging
from unittest.mock import PropertyMock, patch

import pytest
from django.conf import settings
from rest_framework.permissions import AllowAny
from rest_framework.test import APIClient

from backend.constants import DEFAULT_SYSTEM_USER
from backend.tests.mock_data.components.cc import CCApiMock
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.constants import TicketStatus, TodoStatus, TodoType
from backend.ticket.flow_manager.inner import InnerFlow
from backend.ticket.handler import TicketHandler
from backend.ticket.models import Flow, Ticket
from backend.ticket.views import TicketViewSet

logger = logging.getLogger("test")
pytestmark = pytest.mark.django_db
client = APIClient()


@pytest.fixture(autouse=True) # autouse=True 会自动应用这个fixture到所有的测试中
def set_empty_middleware():
with patch.object(settings, "MIDDLEWARE", []):
yield


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("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
):
# 以全库备份为例,测试流程: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")
# 创建单据
sql_import_data = copy.deepcopy(MYSQL_FULL_BACKUP_TICKET_DATA)
ticket = client.post("/apis/tickets/", data=sql_import_data).data

# 在todo流程终止
current_flow = Flow.objects.filter(flow_obj_id=SN).first()
client.post(f"/apis/tickets/{current_flow.ticket_id}/callback/")
TicketHandler.revoke_ticket(ticket_ids=[ticket["id"]], operator=DEFAULT_SYSTEM_USER)
# 验证单据和todo已经终止
revoke_ticket = Ticket.objects.get(id=ticket["id"])
assert revoke_ticket.status == TicketStatus.TERMINATED
assert revoke_ticket.todo_of_ticket.filter(type=TodoType.APPROVE)[0].status == TodoStatus.DONE_FAILED
3 changes: 1 addition & 2 deletions dbm-ui/backend/ticket/builders/mysql/mysql_ha_full_backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,4 @@ class MySQLHaFullBackupFlowParamBuilder(builders.FlowParamBuilder):
class MySQLHaFullBackupFlowBuilder(BaseMySQLHATicketFlowBuilder):
serializer = MySQLHaFullBackupDetailSerializer
inner_flow_builder = MySQLHaFullBackupFlowParamBuilder
inner_flow_name = _("全库备份执行")
retry_type = FlowRetryType.MANUAL_RETRY
retry_type = FlowRetryType.AUTO_RETRY
74 changes: 58 additions & 16 deletions dbm-ui/backend/ticket/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ class TodoType(str, StructuredEnum):
待办类型
"""

ITSM = EnumField("ITSM", _("主流程-单据审批"))
APPROVE = EnumField("APPROVE", _("主流程-人工确认"))
INNER_FAILED = EnumField("INNER_FAILED", _("主流程-失败后待确认"))
INNER_APPROVE = EnumField("INNER_APPROVE", _("自动化流程-人工确认"))
RESOURCE_REPLENISH = EnumField("RESOURCE_REPLENISH", _("资源补货"))

Expand All @@ -39,19 +41,24 @@ class CountType(str, StructuredEnum):
单据计数类型
"""

MY_TODO = EnumField("MY_TODO", _("我的待办"))
MY_APPROVE = EnumField("MY_APPROVE", _("我的申请"))
APPROVE = EnumField("APPROVE", _("待我审批"))
TODO = EnumField("TODO", _("待我确认执行"))
RUNNING = EnumField("RUNNING", _("待我继续"))
RESOURCE_REPLENISH = EnumField("RESOURCE_REPLENISH", _("待我补货"))
FAILED = EnumField("FAILED", _("失败待处理"))
DONE = EnumField("DONE", _("我的已办"))
SELF_MANAGE = EnumField("SELF_MANAGE", _("我负责的业务"))


class TodoStatus(str, StructuredEnum):
"""
待办状态枚举
TODO -> (RUNNING,可选) -> DONE_SUCCESS
| -> DONE_FAILED
TODO -> (RUNNING,可选) -> DONE_SUCCESS
| -> DONE_FAILED
"""

TODO = EnumField("TODO", _("待处理"))
RUNNING = EnumField("RUNNING", _("处理中"))
DONE_SUCCESS = EnumField("DONE_SUCCESS", _("已处理"))
DONE_FAILED = EnumField("DONE_FAILED", _("已终止"))

Expand All @@ -68,18 +75,44 @@ class ResourceApplyErrCode(int, StructuredEnum):


TODO_DONE_STATUS = [TodoStatus.DONE_SUCCESS, TodoStatus.DONE_FAILED]
TODO_RUNNING_STATUS = [TodoStatus.TODO, TodoStatus.RUNNING]
TODO_RUNNING_STATUS = [TodoStatus.TODO]


class TicketStatus(str, StructuredEnum):
"""单据状态枚举"""

PENDING = EnumField("PENDING", _("等待中"))
APPROVE = EnumField("APPROVE", _("待审批"))
RESOURCE_REPLENISH = EnumField("RESOURCE_REPLENISH", _("待补货"))
TODO = EnumField("TODO", _("待执行"))
TIMER = EnumField("TIMER", _("定时中"))
RUNNING = EnumField("RUNNING", _("执行中"))
SUCCEEDED = EnumField("SUCCEEDED", _("成功"))
SUCCEEDED = EnumField("SUCCEEDED", _("已完成"))
FAILED = EnumField("FAILED", _("失败"))
REVOKED = EnumField("REVOKED", _("撤销"))
TERMINATED = EnumField("TERMINATED", _("终止"))
REVOKED = EnumField("REVOKED", _("已撤销"))
TERMINATED = EnumField("TERMINATED", _("已终止"))
# 仅展示,不参与状态流转,不落地db
INNER_TODO = EnumField("INNER_TODO", _("待继续"))


# 单据[正在进行]的状态合集
TICKET_RUNNING_STATUS = [
TicketStatus.APPROVE,
TicketStatus.TODO,
TicketStatus.RESOURCE_REPLENISH,
TicketStatus.RUNNING,
TicketStatus.TIMER,
]
# 单据[包含TODO]的状态合集
TICKET_TODO_STATUS = [
TicketStatus.APPROVE,
TicketStatus.TODO,
TicketStatus.RESOURCE_REPLENISH,
TicketStatus.FAILED,
TicketStatus.RUNNING,
]
# 单据[失败]的状态合集
TICKET_FAILED_STATUS = [TicketStatus.REVOKED, TicketStatus.TERMINATED, TicketStatus.FAILED]


class TicketFlowStatus(str, StructuredEnum):
Expand All @@ -94,8 +127,8 @@ class TicketFlowStatus(str, StructuredEnum):
SKIPPED = EnumField("SKIPPED", _("跳过"))


FLOW_FINISHED_STATUS = [TicketFlowStatus.SKIPPED, TicketStatus.SUCCEEDED]
FLOW_NOT_EXECUTE_STATUS = [TicketFlowStatus.SKIPPED, TicketStatus.PENDING]
FLOW_FINISHED_STATUS = [TicketFlowStatus.SKIPPED, TicketFlowStatus.SUCCEEDED]
FLOW_NOT_EXECUTE_STATUS = [TicketFlowStatus.SKIPPED, TicketFlowStatus.PENDING]

BAMBOO_STATE__TICKET_STATE_MAP = {
StateType.FINISHED.value: TicketFlowStatus.SUCCEEDED.value,
Expand Down Expand Up @@ -214,7 +247,7 @@ def get_cluster_type_by_ticket(cls, ticket_type):
MYSQL_SLAVE_MIGRATE_UPGRADE = TicketEnumField("MYSQL_SLAVE_MIGRATE_UPGRADE", _("MySQL Slave 迁移升级"), _("版本升级"))
MYSQL_RO_SLAVE_UNINSTALL = TicketEnumField("MYSQL_RO_SLAVE_UNINSTALL", _("MySQL非stanby slave下架"), _("集群维护"))
MYSQL_PROXY_UPGRADE = TicketEnumField("MYSQL_PROXY_UPGRADE", _("MySQL Proxy升级"), _("版本升级"))
MYSQL_HA_TRANSFER_TO_OTHER_BIZ = TicketEnumField("MYSQL_HA_TRANSFER_TO_OTHER_BIZ", _("TendbHA集群迁移至其他业务"), register_iam=False) # noqa
MYSQL_HA_TRANSFER_TO_OTHER_BIZ = TicketEnumField("MYSQL_HA_TRANSFER_TO_OTHER_BIZ", _("TendbHA集群迁移至其他业务"), register_iam=False) # noqa
MYSQL_PUSH_PERIPHERAL_CONFIG = TicketEnumField("MYSQL_PUSH_PERIPHERAL_CONFIG", _("推送周边配置"), register_iam=False)
MYSQL_ACCOUNT_RULE_CHANGE = TicketEnumField("MYSQL_ACCOUNT_RULE_CHANGE", _("MySQL 授权规则变更"), register_iam=False)

Expand Down Expand Up @@ -411,10 +444,8 @@ def get_cluster_type_by_ticket(cls, ticket_type):
RIAK_CLUSTER_MIGRATE = TicketEnumField("RIAK_CLUSTER_MIGRATE", _("Riak 集群迁移"), _("集群管理"))

# MONGODB
MONGODB_REPLICASET_APPLY = TicketEnumField("MONGODB_REPLICASET_APPLY", _("MongoDB 副本集集群部署"),
register_iam=False) # noqa
MONGODB_SHARD_APPLY = TicketEnumField("MONGODB_SHARD_APPLY", _("MongoDB 分片集群部署"), _("集群管理"),
register_iam=False) # noqa
MONGODB_REPLICASET_APPLY = TicketEnumField("MONGODB_REPLICASET_APPLY", _("MongoDB 副本集集群部署"), register_iam=False) # noqa
MONGODB_SHARD_APPLY = TicketEnumField("MONGODB_SHARD_APPLY", _("MongoDB 分片集群部署"), _("集群管理"), register_iam=False) # noqa
MONGODB_EXEC_SCRIPT_APPLY = TicketEnumField("MONGODB_EXEC_SCRIPT_APPLY", _("MongoDB 变更脚本执行"), _("脚本任务"))
MONGODB_REMOVE_NS = TicketEnumField("MONGODB_REMOVE_NS", _("MongoDB 清档"), _("数据处理"))
MONGODB_FULL_BACKUP = TicketEnumField("MONGODB_FULL_BACKUP", _("MongoDB 全库备份"), _("备份"))
Expand All @@ -430,7 +461,7 @@ def get_cluster_type_by_ticket(cls, ticket_type):
MONGODB_DESTROY = TicketEnumField("MONGODB_DESTROY", _("MongoDB 集群删除"), _("集群管理"))
MONGODB_CUTOFF = TicketEnumField("MONGODB_CUTOFF", _("MongoDB 整机替换"), _("集群维护"))
MONGODB_AUTHORIZE_RULES = TicketEnumField("MONGODB_AUTHORIZE_RULES", _("MongoDB 授权"), _("权限管理"))
MONGODB_EXCEL_AUTHORIZE_RULES = TicketEnumField("MONGODB_EXCEL_AUTHORIZE_RULES", _("MongoDB Excel授权"), _("权限管理")) # noqa
MONGODB_EXCEL_AUTHORIZE_RULES = TicketEnumField("MONGODB_EXCEL_AUTHORIZE_RULES", _("MongoDB Excel授权"), _("权限管理")) # noqa
MONGODB_IMPORT = TicketEnumField("MONGODB_IMPORT", _("MongoDB 数据导入"), _("集群维护"))
MONGODB_RESTORE = TicketEnumField("MONGODB_RESTORE", _("MongoDB 定点回档"), _("集群维护"))
MONGODB_TEMPORARY_DESTROY = TicketEnumField("MONGODB_TEMPORARY_DESTROY", _("MongoDB 临时集群销毁"), _("集群维护"))
Expand Down Expand Up @@ -640,6 +671,7 @@ class OperateNodeActionType(str, StructuredEnum):
DISTRIBUTE = EnumField("DISTRIBUTE", _("派单"))
DELIVER = EnumField("DELIVER", _("转单"))
TERMINATE = EnumField("TERMINATE", _("终止节点和单据"))
WITHDRAW = EnumField("WITHDRAW", _("撤销单据"))


class ItsmTicketNodeEnum(str, StructuredEnum):
Expand Down Expand Up @@ -691,3 +723,13 @@ class TicketExpireType(str, StructuredEnum):
FlowType.RESOURCE_APPLY: TicketExpireType.FLOW_TODO,
FlowType.RESOURCE_BATCH_APPLY: TicketExpireType.FLOW_TODO,
}

# 根据流程类型来映射单据状态
RUNNING_FLOW__TICKET_STATUS = {
FlowType.BK_ITSM: TicketStatus.APPROVE,
FlowType.RESOURCE_APPLY: TicketStatus.RESOURCE_REPLENISH,
FlowType.RESOURCE_BATCH_APPLY: TicketStatus.RESOURCE_REPLENISH,
FlowType.PAUSE: TicketStatus.TODO,
FlowType.INNER_FLOW: TicketStatus.RUNNING,
FlowType.TIMER: TicketStatus.TIMER,
}
6 changes: 3 additions & 3 deletions dbm-ui/backend/ticket/contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ def __init__(
self.spec_map = get_spec_display_map()
self.db_config = {}

bizs = list(AppCache.objects.all())
self.biz_name_map = {biz.bk_biz_id: biz.bk_biz_name for biz in bizs}
self.app_abbr_map = {biz.bk_biz_id: biz.db_app_abbr for biz in bizs}
bizs = AppCache.get_appcache(key="appcache_dict")
self.biz_name_map = {int(bk_biz_id): biz["bk_biz_name"] for bk_biz_id, biz in bizs.items()}
self.app_abbr_map = {int(bk_biz_id): biz["db_app_abbr"] for bk_biz_id, biz in bizs.items()}

db_modules = list(DBModule.objects.all())
self.db_module_map = {module.db_module_id: module.db_module_name for module in db_modules}
Expand Down
Loading

0 comments on commit 93b9f78

Please sign in to comment.