Skip to content

Commit 3a81bb1

Browse files
committed
feat(backend): dba工具箱-实例级别主库故障切换 #9538
# Reviewed, transaction id: 41112
1 parent 9325a5e commit 3a81bb1

File tree

16 files changed

+459
-25
lines changed

16 files changed

+459
-25
lines changed

dbm-ui/backend/db_meta/api/cluster/nosqlcomm/create_cluster.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
from backend.db_meta.api import common
2222
from backend.db_meta.enums import AccessLayer, ClusterEntryType, ClusterPhase, ClusterStatus, ClusterType, MachineType
2323
from backend.db_meta.models import Cluster, ClusterEntry, ProxyInstance, StorageInstance
24-
from backend.db_services.dbbase.constants import IP_PORT_DIVIDER
2524
from backend.flow.utils.redis.redis_module_operate import RedisCCTopoOperator
2625

2726
from ....exceptions import (
@@ -59,6 +58,7 @@ def create_twemproxy_cluster(
5958
2. proxy 不能有已绑定的后端
6059
3. 必须只有 1 个 master
6160
"""
61+
from backend.db_services.dbbase.constants import IP_PORT_DIVIDER
6262

6363
ip_port_storages = {"{}{}{}".format(s["ip"], IP_PORT_DIVIDER, s["port"]): s for s in storages}
6464

dbm-ui/backend/db_services/bigdata/influxdb/query.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from backend.db_meta.models.group import Group, GroupInstance
1919
from backend.db_meta.models.instance import StorageInstance
2020
from backend.db_services.bigdata.resources.query import BigDataBaseListRetrieveResource
21-
from backend.db_services.dbbase.constants import IP_PORT_DIVIDER
2221
from backend.db_services.dbbase.resources import query
2322
from backend.db_services.dbbase.resources.register import register_resource_decorator
2423
from backend.db_services.ipchooser.query.resource import ResourceQueryHelper
@@ -129,6 +128,7 @@ def _to_instance(
129128
cls, instance: dict, restart_map: dict, group_id_map: dict, group_name_map: dict, host_id__host_map: dict
130129
) -> dict:
131130
"""实例序列化"""
131+
from backend.db_services.dbbase.constants import IP_PORT_DIVIDER
132132

133133
instance_id = instance["id"]
134134
restart_at = restart_map.get(instance_id, "")

dbm-ui/backend/db_services/cmdb/biz.py

+89-1
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@
1212
import logging
1313
from typing import Dict, List
1414

15+
from django.db.models import Count
16+
1517
from backend import env
1618
from backend.components import CCApi
1719
from backend.components.dbconfig.constants import DEPLOY_FILE_NAME, ConfType, LevelName
1820
from backend.configuration.constants import SystemSettingsEnum
1921
from backend.configuration.models import SystemSettings
20-
from backend.db_meta.models import AppCache, DBModule
22+
from backend.db_meta.models import AppCache, Cluster, DBModule, Machine, ProxyInstance, StorageInstance
23+
from backend.db_services.cmdb.constants import ModuleCountType
2124
from backend.db_services.cmdb.exceptions import BkAppAttrAlreadyExistException
2225
from backend.db_services.dbconfig.dataclass import DBBaseConfig, DBConfigLevelData
2326
from backend.db_services.dbconfig.handlers import DBConfigHandler
@@ -241,3 +244,88 @@ def get_or_create_resource_module():
241244
topo = {"set_id": manage_set_id, "resource_module_id": module_id}
242245
SystemSettings.insert_setting_value(key=SystemSettingsEnum.MANAGE_TOPO.value, value=topo, value_type="dict")
243246
return module_id
247+
248+
249+
def filter_by_biz_name(data: list, biz_name: str) -> list:
250+
return [biz for biz in data if biz["bk_biz_name"] == biz_name]
251+
252+
253+
def filter_by_module_name(data: list, module_name: str) -> list:
254+
result = []
255+
for biz in data:
256+
filtered_modules = [module for module in biz["modules"] if module["module_name"] == module_name]
257+
if filtered_modules:
258+
new_biz = biz.copy()
259+
new_biz["modules"] = filtered_modules
260+
result.append(new_biz)
261+
return result
262+
263+
264+
def process_count_obj(queryset, nested_data):
265+
count_obj = queryset.values("db_module_id", "bk_biz_id").annotate(count=Count("db_module_id")).order_by("-count")
266+
for obj in count_obj:
267+
bk_biz_id = obj["bk_biz_id"]
268+
db_module_id = obj["db_module_id"]
269+
count = obj["count"]
270+
nested_data[bk_biz_id]["count"] += count
271+
nested_data[bk_biz_id]["modules"][db_module_id] = count
272+
273+
274+
def list_biz_module_trees(
275+
cluster_types: str, bk_biz_name: str, module_name: str, count_type: str, role: str
276+
) -> List[Dict]:
277+
"""
278+
获取业务与模块维度集群数量
279+
:param cluster_types: 以逗号分隔的集群类型字符串
280+
:param bk_biz_name: 业务名称
281+
:param module_name: 模块名称
282+
:param count_type: 计数类型(如 "cluster" 或 "instance")
283+
:param role: 是否过滤为实例角色
284+
:return: 包含业务和模块维度的计数列表
285+
"""
286+
cluster_type_list = cluster_types.split(",")
287+
288+
if count_type == ModuleCountType.CLUSTER.value:
289+
queryset = Cluster.objects.filter(cluster_type__in=cluster_type_list)
290+
elif count_type == ModuleCountType.INSTANCE.value:
291+
filters = {"cluster_type__in": cluster_type_list}
292+
if role:
293+
filters["instance_role"] = role
294+
queryset = StorageInstance.objects.filter(**filters)
295+
elif count_type == ModuleCountType.MACHINE.value:
296+
queryset = Machine.objects.filter(cluster_type__in=cluster_type_list)
297+
else:
298+
raise ValueError(f"Unsupported count_type: {count_type}")
299+
300+
nested_data = collections.defaultdict(lambda: {"count": 0, "modules": collections.defaultdict(int)})
301+
process_count_obj(queryset, nested_data)
302+
303+
if count_type == ModuleCountType.INSTANCE.value:
304+
filters = {"cluster_type__in": cluster_type_list}
305+
if role:
306+
filters["access_layer"] = role
307+
proxy_queryset = ProxyInstance.objects.filter(**filters)
308+
process_count_obj(proxy_queryset, nested_data)
309+
310+
db_module_map = DBModule.db_module_map()
311+
id_to_name = AppCache.id_to_name()
312+
313+
final_data = [
314+
{
315+
"bk_biz_name": id_to_name.get(bk_biz_id, ""),
316+
"bk_biz_id": bk_biz_id,
317+
"count": data["count"],
318+
"modules": [
319+
{"module_name": db_module_map.get(module_id, ""), "module_id": module_id, "count": count}
320+
for module_id, count in data["modules"].items()
321+
],
322+
}
323+
for bk_biz_id, data in nested_data.items()
324+
]
325+
326+
if bk_biz_name:
327+
final_data = filter_by_biz_name(final_data, bk_biz_name)
328+
if module_name:
329+
final_data = filter_by_module_name(final_data, module_name)
330+
331+
return final_data

dbm-ui/backend/db_services/cmdb/constants.py

+11
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@
88
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
99
specific language governing permissions and limitations under the License.
1010
"""
11+
from django.utils.translation import gettext as _
12+
13+
from blue_krill.data_types.enum import EnumField, StructuredEnum
1114

1215
MAX_DB_MODULE_LIMIT = 63
1316
MAX_DB_APP_ABBR_LIMIT = 63
17+
18+
19+
class ModuleCountType(str, StructuredEnum):
20+
"""module统计类型"""
21+
22+
CLUSTER = EnumField("cluster", _("集群"))
23+
INSTANCE = EnumField("instance", _("实例"))
24+
MACHINE = EnumField("machine", _("主机"))

dbm-ui/backend/db_services/cmdb/serializers.py

+22-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
from django.utils.translation import gettext_lazy as _
1212
from rest_framework import serializers
1313

14-
from backend.db_meta.enums import ClusterType
15-
from backend.db_services.cmdb.constants import MAX_DB_APP_ABBR_LIMIT, MAX_DB_MODULE_LIMIT
14+
from backend.db_meta.enums import ClusterType, InstanceRole
15+
from backend.db_services.cmdb.constants import MAX_DB_APP_ABBR_LIMIT, MAX_DB_MODULE_LIMIT, ModuleCountType
1616
from backend.iam_app.dataclass import ResourceEnum
1717
from backend.iam_app.dataclass.actions import ActionEnum
1818

@@ -80,3 +80,23 @@ class ListNodesSerializer(TopoSerializer):
8080
page = serializers.IntegerField(help_text=_("页数"))
8181
module_id = serializers.IntegerField(help_text=_("模块ID"), required=False)
8282
set_id = serializers.IntegerField(help_text=_("集群ID"), required=False)
83+
84+
85+
class ListBIZModulesSLZ(serializers.Serializer):
86+
cluster_types = serializers.CharField(help_text=_("集群类型(逗号分隔)"))
87+
bk_biz_name = serializers.CharField(help_text=_("业务名称"), required=False)
88+
module_name = serializers.CharField(help_text=_("模块名称"), required=False)
89+
count_type = serializers.ChoiceField(choices=ModuleCountType.get_choices(), default=ModuleCountType.CLUSTER.value)
90+
role = serializers.ChoiceField(help_text=_("实例角色"), choices=InstanceRole.get_choices(), required=False)
91+
92+
93+
class BIZModuleSLZ(serializers.Serializer):
94+
class ModuleInstanceCountSLZ(serializers.Serializer):
95+
module_name = serializers.CharField(help_text=_("模块名"))
96+
module_id = serializers.IntegerField(help_text=_("模块ID"))
97+
count = serializers.IntegerField(help_text=_("实例数量"))
98+
99+
bk_biz_name = serializers.CharField(help_text=_("业务名"))
100+
bk_biz_id = serializers.IntegerField(help_text=_("业务ID"))
101+
count = serializers.IntegerField(help_text=_("实例数量"))
102+
modules = serializers.ListField(help_text=_("模块信息"), child=ModuleInstanceCountSLZ())

dbm-ui/backend/db_services/cmdb/views.py

+20
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,23 @@ def set_db_app_abbr(self, request, bk_biz_id):
9999
@action(methods=["GET"], detail=True)
100100
def list_cc_obj_user(self, request, bk_biz_id):
101101
return Response(biz.list_cc_obj_user(bk_biz_id))
102+
103+
@common_swagger_auto_schema(
104+
operation_summary=_("业务模块树信息"),
105+
query_serializer=serializers.ListBIZModulesSLZ(),
106+
responses={status.HTTP_200_OK: serializers.BIZModuleSLZ(label=_("业务模块树信息"), many=True)},
107+
tags=[SWAGGER_TAG],
108+
)
109+
@action(methods=["GET"], detail=False, serializer_class=serializers.ListBIZModulesSLZ)
110+
def list_biz_module_trees(self, request):
111+
validated_data = self.params_validate(self.get_serializer_class())
112+
cluster_types = validated_data.get("cluster_types")
113+
bk_biz_name = validated_data.get("bk_biz_name")
114+
module_name = validated_data.get("module_name")
115+
count_type = validated_data.get("count_type")
116+
role = validated_data.get("role")
117+
serializer = serializers.BIZModuleSLZ(
118+
biz.list_biz_module_trees(cluster_types, bk_biz_name, module_name, count_type, role),
119+
many=True,
120+
)
121+
return Response(serializer.data)

dbm-ui/backend/db_services/dbbase/cluster/handlers.py

+22
Original file line numberDiff line numberDiff line change
@@ -370,3 +370,25 @@ def get_cluster_service_handler(bk_biz_id: int, db_type: str = "dbbase"):
370370
handler = ClusterServiceHandler(bk_biz_id)
371371

372372
return handler
373+
374+
375+
def retrieve_resources(self, request, serializer_class, resource_method_name):
376+
"""
377+
通用方法来处理不同资源类型的请求。
378+
"""
379+
from backend.db_services.dbbase.constants import DB_RESOURCE_MAP
380+
381+
query_params = self.params_validate(serializer_class)
382+
bk_biz_id = query_params.pop("bk_biz_id", None)
383+
db_type = query_params.pop("db_type", None)
384+
RetrieveResource = DB_RESOURCE_MAP[db_type]
385+
386+
# 为 MySQL 和 SQLServer 设置集群类型
387+
if db_type in [DBType.MySQL.value, DBType.Sqlserver.value]:
388+
RetrieveResource.cluster_types = ClusterType.db_type_cluster_types_map().get(db_type)
389+
390+
# 动态调用资源方法获取数据
391+
resource_method = getattr(RetrieveResource, resource_method_name)
392+
data = self.paginator.paginate_list(request, bk_biz_id, resource_method, query_params)
393+
394+
return self.get_paginated_response(data)

dbm-ui/backend/db_services/dbbase/constants.py

+35
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,25 @@
1010
"""
1111
from django.utils.translation import ugettext_lazy as _
1212

13+
from backend.configuration.constants import DBType
1314
from backend.db_dirty.constants import PoolType
15+
from backend.db_services.bigdata.doris.query import DorisListRetrieveResource
16+
from backend.db_services.bigdata.es.query import ESListRetrieveResource
17+
from backend.db_services.bigdata.hdfs.query import HDFSListRetrieveResource
18+
from backend.db_services.bigdata.influxdb.query import InfluxDBListRetrieveResource
19+
from backend.db_services.bigdata.kafka.query import KafkaListRetrieveResource
20+
from backend.db_services.bigdata.pulsar.query import PulsarListRetrieveResource
21+
from backend.db_services.bigdata.riak.query import RiakListRetrieveResource
22+
from backend.db_services.bigdata.vm.query import VMListRetrieveResource
23+
from backend.db_services.mongodb.resources.query import MongoDBListRetrieveResource
24+
from backend.db_services.mysql.resources.tendbcluster.query import (
25+
ListRetrieveResource as TendDBClusterRetrieveResource,
26+
)
27+
from backend.db_services.mysql.resources.tendbha.query import ListRetrieveResource as TendDBHAHaRetrieveResource
28+
from backend.db_services.redis.resources.redis_cluster.query import RedisListRetrieveResource
29+
from backend.db_services.sqlserver.resources.sqlserver_ha.query import (
30+
ListRetrieveResource as SqlserverHARetrieveResource,
31+
)
1432
from blue_krill.data_types.enum import EnumField, StructuredEnum
1533

1634
ES_DEFAULT_PORT = 9200
@@ -48,3 +66,20 @@ class ResourceType(str, StructuredEnum):
4866

4967
SPOTTY_HOST = EnumField("spotty_host", _("污点主机"))
5068
RESOURCE_RECORD = EnumField("resource_record", _("资源操作记录"))
69+
70+
71+
DB_RESOURCE_MAP = {
72+
DBType.TenDBCluster.value: TendDBClusterRetrieveResource,
73+
DBType.MySQL.value: TendDBHAHaRetrieveResource,
74+
DBType.Redis.value: RedisListRetrieveResource,
75+
DBType.MongoDB.value: MongoDBListRetrieveResource,
76+
DBType.Kafka.value: KafkaListRetrieveResource,
77+
DBType.Hdfs.value: HDFSListRetrieveResource,
78+
DBType.Es.value: ESListRetrieveResource,
79+
DBType.Pulsar.value: PulsarListRetrieveResource,
80+
DBType.InfluxDB.value: InfluxDBListRetrieveResource,
81+
DBType.Riak.value: RiakListRetrieveResource,
82+
DBType.Sqlserver.value: SqlserverHARetrieveResource,
83+
DBType.Doris.value: DorisListRetrieveResource,
84+
DBType.Vm.value: VMListRetrieveResource,
85+
}

0 commit comments

Comments
 (0)