From f57b56944bf2bfd6f1a95125c1c6ba62b9bbfce1 Mon Sep 17 00:00:00 2001 From: durant <826035498@qq.com> Date: Fri, 16 Aug 2024 11:11:37 +0800 Subject: [PATCH] =?UTF-8?q?feat(backend):=20=E6=A0=87=E7=AD=BE=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=20#6235=20#=20Reviewed,=20transaction=20id:=2017937?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dbm-ui/backend/db_meta/enums/comm.py | 15 ++++++- dbm-ui/backend/db_meta/models/cluster.py | 2 + dbm-ui/backend/db_meta/models/tag.py | 21 ++++++---- .../db_services/dbbase/resources/query.py | 13 +++++- .../dbbase/resources/serializers.py | 1 + .../backend/db_services/group/serializers.py | 2 +- dbm-ui/backend/db_services/group/views.py | 12 +++--- dbm-ui/backend/db_services/tag/__init__.py | 10 +++++ dbm-ui/backend/db_services/tag/handlers.py | 42 +++++++++++++++++++ dbm-ui/backend/db_services/tag/serializers.py | 20 +++++++++ dbm-ui/backend/db_services/tag/urls.py | 19 +++++++++ dbm-ui/backend/db_services/tag/views.py | 27 ++++++++++++ .../tendbcluster/tendb_fixpoint_rollback.py | 4 +- dbm-ui/backend/urls.py | 1 + dbm-ui/scripts/ci/code_quality.sh | 2 +- dbm-ui/scripts/ci/install.sh | 10 ++--- 16 files changed, 175 insertions(+), 26 deletions(-) create mode 100644 dbm-ui/backend/db_services/tag/__init__.py create mode 100644 dbm-ui/backend/db_services/tag/handlers.py create mode 100644 dbm-ui/backend/db_services/tag/serializers.py create mode 100644 dbm-ui/backend/db_services/tag/urls.py create mode 100644 dbm-ui/backend/db_services/tag/views.py diff --git a/dbm-ui/backend/db_meta/enums/comm.py b/dbm-ui/backend/db_meta/enums/comm.py index 89a82b24f4..e696744984 100644 --- a/dbm-ui/backend/db_meta/enums/comm.py +++ b/dbm-ui/backend/db_meta/enums/comm.py @@ -26,8 +26,9 @@ class DBCCModule(str, StructuredEnum): class TagType(str, StructuredEnum): - CUSTOM = EnumField("custom", _("custom")) - SYSTEM = EnumField("system", _("system")) + CUSTOM = EnumField("custom", _("自定义标签")) + SYSTEM = EnumField("system", _("系统标签")) + BUILTIN = EnumField("builtin", _("内置标签")) class SystemTagEnum(str, StructuredEnum): @@ -36,6 +37,16 @@ class SystemTagEnum(str, StructuredEnum): TEMPORARY = EnumField("temporary", _("temporary")) +class TagResourceEnum(int, StructuredEnum): + """资源类型""" + + CLUSTER = EnumField(0, _("集群")) + STORAGE_INSTANCE = EnumField(1, _("存储实例")) + PROXY_INSTANCE = EnumField(2, _("代理实例")) + MACHINE = EnumField(3, _("实例机器")) + RESOURCE_MACHINE = EnumField(4, _("资源池机器")) + + class RedisVerUpdateNodeType(str, StructuredEnum): """redis版本升级节点类型""" diff --git a/dbm-ui/backend/db_meta/models/cluster.py b/dbm-ui/backend/db_meta/models/cluster.py index 522fc892b1..fe5c5547e9 100644 --- a/dbm-ui/backend/db_meta/models/cluster.py +++ b/dbm-ui/backend/db_meta/models/cluster.py @@ -44,6 +44,7 @@ ClusterSqlserverStatusFlags, ) from backend.db_meta.exceptions import ClusterExclusiveOperateException, DBMetaException +from backend.db_meta.models.tag import Tag from backend.db_services.version.constants import LATEST, PredixyVersion, TwemproxyVersion from backend.flow.consts import DEFAULT_RIAK_PORT from backend.ticket.constants import TicketType @@ -68,6 +69,7 @@ class Cluster(AuditedModel): max_length=128, help_text=_("容灾要求"), choices=AffinityEnum.get_choices(), default=AffinityEnum.NONE.value ) time_zone = models.CharField(max_length=16, default=DEFAULT_TIME_ZONE, help_text=_("集群所在的时区")) + tags = models.ManyToManyField(Tag, blank=True, help_text=_("标签(外键)")) class Meta: unique_together = [("bk_biz_id", "immute_domain", "cluster_type", "db_module_id"), ("immute_domain",)] diff --git a/dbm-ui/backend/db_meta/models/tag.py b/dbm-ui/backend/db_meta/models/tag.py index ecdd353bd5..f45dd16317 100644 --- a/dbm-ui/backend/db_meta/models/tag.py +++ b/dbm-ui/backend/db_meta/models/tag.py @@ -12,20 +12,27 @@ from django.utils.translation import ugettext_lazy as _ from backend.bk_web.models import AuditedModel +from backend.configuration.constants import PLAT_BIZ_ID from backend.db_meta.enums.comm import TagType -from backend.db_meta.models import Cluster class Tag(AuditedModel): - bk_biz_id = models.IntegerField(default=0) - name = models.CharField(max_length=64, default="", help_text=_("tag名称")) - type = models.CharField(max_length=64, help_text=_("tag类型"), choices=TagType.get_choices()) - cluster = models.ManyToManyField(Cluster, blank=True, help_text=_("关联集群")) + bk_biz_id = models.IntegerField(help_text=_("业务 ID"), default=0) + key = models.CharField(help_text=_("标签键"), default="", max_length=64) + value = models.CharField(help_text=_("标签值"), default="", max_length=64) + type = models.CharField(help_text=_("tag类型"), max_length=64, choices=TagType.get_choices()) class Meta: - unique_together = ["bk_biz_id", "name"] + unique_together = ["bk_biz_id", "key", "value"] @property def tag_desc(self): """仅返回tag的信息""" - return {"bk_biz_id": self.bk_biz_id, "name": self.name, "type": self.type} + return {"bk_biz_id": self.bk_biz_id, "key": self.key, "type": self.type} + + @classmethod + def get_or_create_system_tag(cls, key: str, value: str): + tag, created = cls.objects.get_or_create( + bk_biz_id=PLAT_BIZ_ID, key=key, value=value, type=TagType.SYSTEM.value + ) + return tag diff --git a/dbm-ui/backend/db_services/dbbase/resources/query.py b/dbm-ui/backend/db_services/dbbase/resources/query.py index 6a97061951..4359eb94b4 100644 --- a/dbm-ui/backend/db_services/dbbase/resources/query.py +++ b/dbm-ui/backend/db_services/dbbase/resources/query.py @@ -213,6 +213,8 @@ def export_instance(cls, bk_biz_id: int, bk_host_ids: list) -> HttpResponse: @classmethod def get_temporary_cluster_info(cls, cluster, ticket_type): """如果当前集群是临时集群,则补充临时集群相关信息。""" + # TODO 临时关闭 tag + return {} tags = [tag.name for tag in cluster.tag_set.all()] if SystemTagEnum.TEMPORARY.value not in tags: return {} @@ -397,8 +399,14 @@ def _list_clusters( for param in filter_params_map: if query_params.get(param): query_filters &= filter_params_map[param] + + # 对标签进行过滤,标签“且”查询,需以追加 filter 的方式实现 + cluster_queryset = Cluster.objects.filter(query_filters) + for tag_id in query_params.get("tag_ids", "").split(","): + cluster_queryset = cluster_queryset.filter(tags__id=tag_id) + # 一join多的一方会有重复的数据,去重 - cluster_queryset = Cluster.objects.filter(query_filters).distinct() + cluster_queryset = cluster_queryset.distinct() def filter_inst_queryset(_cluster_queryset, _proxy_queryset, _storage_queryset, _filters): # 注意这里用新的变量获取过滤后的queryset,不要用原queryset过滤,会影响后续集群关联实例的获取 @@ -472,7 +480,7 @@ def _filter_cluster_hook( Prefetch("proxyinstance_set", queryset=proxy_queryset.select_related("machine"), to_attr="proxies"), Prefetch("storageinstance_set", queryset=storage_queryset.select_related("machine"), to_attr="storages"), Prefetch("clusterentry_set", to_attr="entries"), - "tag_set", + "tags", ) cluster_ids = list(cluster_queryset.values_list("id", flat=True)) @@ -564,6 +572,7 @@ def _to_cluster_representation( "updater": cluster.updater, "create_at": datetime2str(cluster.create_at), "update_at": datetime2str(cluster.update_at), + "tags": [{tag.key: tag.value} for tag in cluster.tags.all()], } @classmethod diff --git a/dbm-ui/backend/db_services/dbbase/resources/serializers.py b/dbm-ui/backend/db_services/dbbase/resources/serializers.py index a3aaec8878..8a39d2fe05 100644 --- a/dbm-ui/backend/db_services/dbbase/resources/serializers.py +++ b/dbm-ui/backend/db_services/dbbase/resources/serializers.py @@ -33,6 +33,7 @@ class ListResourceSLZ(serializers.Serializer): db_module_id = serializers.CharField(required=False, help_text=_("所属DB模块")) bk_cloud_id = serializers.CharField(required=False, help_text=_("管控区域")) cluster_type = serializers.CharField(required=False, help_text=_("集群类型")) + tag_ids = serializers.CharField(required=False, help_text=_("标签")) class ListMySQLResourceSLZ(ListResourceSLZ): diff --git a/dbm-ui/backend/db_services/group/serializers.py b/dbm-ui/backend/db_services/group/serializers.py index ba73aaa5e1..20fd42e7cf 100644 --- a/dbm-ui/backend/db_services/group/serializers.py +++ b/dbm-ui/backend/db_services/group/serializers.py @@ -16,7 +16,7 @@ from backend.db_meta.models import Group, GroupInstance -class GroupSerializer(AuditedSerializer, serializers.ModelSerializer): +class TagSerializer(AuditedSerializer, serializers.ModelSerializer): instance_count = serializers.SerializerMethodField(help_text=_("实例数量")) class Meta: diff --git a/dbm-ui/backend/db_services/group/views.py b/dbm-ui/backend/db_services/group/views.py index c6241e8d55..b8d1c8a74a 100644 --- a/dbm-ui/backend/db_services/group/views.py +++ b/dbm-ui/backend/db_services/group/views.py @@ -23,7 +23,7 @@ ) from backend.db_meta.models import Group from backend.db_services.group.handlers import GroupHandler -from backend.db_services.group.serializers import GroupMoveInstancesSerializer, GroupSerializer +from backend.db_services.group.serializers import GroupMoveInstancesSerializer, TagSerializer from backend.iam_app.handlers.drf_perm.base import RejectPermission, get_request_key_id SWAGGER_TAG = _("分组") @@ -35,7 +35,7 @@ class GroupViewSet(viewsets.AuditedModelViewSet): """ queryset = Group.objects.all() - serializer_class = GroupSerializer + serializer_class = TagSerializer pagination_class = AuditedLimitOffsetPagination # TODO: 暂时屏蔽对influxdb的鉴权 @@ -74,7 +74,7 @@ def get_queryset(self): @common_swagger_auto_schema( operation_summary=_("分组详情"), - responses={status.HTTP_200_OK: GroupSerializer(label=_("分组详情"))}, + responses={status.HTTP_200_OK: TagSerializer(label=_("分组详情"))}, tags=[SWAGGER_TAG], ) def retrieve(self, request, *args, **kwargs): @@ -98,7 +98,7 @@ def list(self, request, *args, **kwargs): @common_swagger_auto_schema( operation_summary=_("创建新分组"), auto_schema=ResponseSwaggerAutoSchema, - responses={status.HTTP_200_OK: GroupSerializer(label=_("创建新分组"))}, + responses={status.HTTP_200_OK: TagSerializer(label=_("创建新分组"))}, tags=[SWAGGER_TAG], ) def create(self, request, *args, **kwargs): @@ -107,7 +107,7 @@ def create(self, request, *args, **kwargs): @common_swagger_auto_schema( operation_summary=_("更新分组信息"), auto_schema=ResponseSwaggerAutoSchema, - responses={status.HTTP_200_OK: GroupSerializer(label=_("更新分组信息"))}, + responses={status.HTTP_200_OK: TagSerializer(label=_("更新分组信息"))}, tags=[SWAGGER_TAG], ) def update(self, request, *args, **kwargs): @@ -116,7 +116,7 @@ def update(self, request, *args, **kwargs): @common_swagger_auto_schema( operation_summary=_("删除分组"), auto_schema=ResponseSwaggerAutoSchema, - responses={status.HTTP_200_OK: GroupSerializer(label=_("删除分组"))}, + responses={status.HTTP_200_OK: TagSerializer(label=_("删除分组"))}, tags=[SWAGGER_TAG], ) def destroy(self, request, *args, **kwargs): diff --git a/dbm-ui/backend/db_services/tag/__init__.py b/dbm-ui/backend/db_services/tag/__init__.py new file mode 100644 index 0000000000..aa5085c628 --- /dev/null +++ b/dbm-ui/backend/db_services/tag/__init__.py @@ -0,0 +1,10 @@ +# -*- 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. +""" diff --git a/dbm-ui/backend/db_services/tag/handlers.py b/dbm-ui/backend/db_services/tag/handlers.py new file mode 100644 index 0000000000..d0091c4c1c --- /dev/null +++ b/dbm-ui/backend/db_services/tag/handlers.py @@ -0,0 +1,42 @@ +# -*- 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. +""" +from typing import Dict, List + + +class TagHandler: + """标签的操作类""" + + def batch_set_tags(self, tag_ids: List[int]): + """ + 给资源批量设置标签 + """ + # 1. 判断标签中 key 是否允许多值 + + # 2. 批量设置标签 + pass + + def delete_tags(self, tag_ids: List[int]): + """ + 删除标签 + """ + # 1. 检查标签是否被引用 + + # 2. 批量删除标签 + + def create_tags(self, tags: List[Dict[str, str]]): + """ + 批量创建标签 + """ + + def list_tags(self): + """ + 标签列表 + """ diff --git a/dbm-ui/backend/db_services/tag/serializers.py b/dbm-ui/backend/db_services/tag/serializers.py new file mode 100644 index 0000000000..949769f279 --- /dev/null +++ b/dbm-ui/backend/db_services/tag/serializers.py @@ -0,0 +1,20 @@ +# -*- 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. +""" + +from rest_framework import serializers + +from backend.bk_web.serializers import AuditedSerializer + + +class TagSerializer(AuditedSerializer, serializers.ModelSerializer): + """ + 标签序列化器 + """ diff --git a/dbm-ui/backend/db_services/tag/urls.py b/dbm-ui/backend/db_services/tag/urls.py new file mode 100644 index 0000000000..a0ebbfe221 --- /dev/null +++ b/dbm-ui/backend/db_services/tag/urls.py @@ -0,0 +1,19 @@ +# -*- 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. +""" + +from rest_framework.routers import DefaultRouter + +from . import views + +routers = DefaultRouter(trailing_slash=True) +routers.register("", views.TagViewSet, basename="tag") + +urlpatterns = routers.urls diff --git a/dbm-ui/backend/db_services/tag/views.py b/dbm-ui/backend/db_services/tag/views.py new file mode 100644 index 0000000000..5ea2d9810d --- /dev/null +++ b/dbm-ui/backend/db_services/tag/views.py @@ -0,0 +1,27 @@ +# -*- 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. +""" + +from django.utils.translation import ugettext as _ + +from backend.bk_web import viewsets +from backend.db_meta.models import Tag +from backend.db_services.group.serializers import TagSerializer + +SWAGGER_TAG = _("标签") + + +class TagViewSet(viewsets.AuditedModelViewSet): + """ + 标签视图 + """ + + queryset = Tag.objects.all() + serializer_class = TagSerializer diff --git a/dbm-ui/backend/ticket/builders/tendbcluster/tendb_fixpoint_rollback.py b/dbm-ui/backend/ticket/builders/tendbcluster/tendb_fixpoint_rollback.py index 53b4acdb73..eda8768ed4 100644 --- a/dbm-ui/backend/ticket/builders/tendbcluster/tendb_fixpoint_rollback.py +++ b/dbm-ui/backend/ticket/builders/tendbcluster/tendb_fixpoint_rollback.py @@ -97,9 +97,9 @@ def pre_callback(self): # 对临时集群记录变更 temporary_tag, _ = Tag.objects.get_or_create( - bk_biz_id=self.ticket.bk_biz_id, name=SystemTagEnum.TEMPORARY.value, type=TagType.SYSTEM.value + bk_biz_id=self.ticket.bk_biz_id, key=SystemTagEnum.TEMPORARY.value, value=True, type=TagType.SYSTEM.value ) - target_cluster.tag_set.add(temporary_tag) + target_cluster.tags.add(temporary_tag) ClusterOperateRecord.objects.get_or_create( cluster_id=target_cluster.id, ticket=self.ticket, flow=rollback_flow ) diff --git a/dbm-ui/backend/urls.py b/dbm-ui/backend/urls.py index 4c97af35cd..a859da4ae4 100644 --- a/dbm-ui/backend/urls.py +++ b/dbm-ui/backend/urls.py @@ -61,6 +61,7 @@ path("db_dirty/", include("backend.db_dirty.urls")), path("dbbase/", include("backend.db_services.dbbase.urls")), path("quick_search/", include("backend.db_services.quick_search.urls")), + path("tag/", include("backend.db_services.tag.urls")), path("plugin/", include("backend.db_services.plugin.urls")), ] diff --git a/dbm-ui/scripts/ci/code_quality.sh b/dbm-ui/scripts/ci/code_quality.sh index 5a9c5774c0..4e7e437d48 100755 --- a/dbm-ui/scripts/ci/code_quality.sh +++ b/dbm-ui/scripts/ci/code_quality.sh @@ -57,7 +57,7 @@ echo "未通过数: $TEST_NOT_SUCCESS_COUNT" if [[ $TEST_NOT_SUCCESS_COUNT -ne 0 ]]; then - echo -e "$TEST_LOGS" + echo -e "\033[1;31m $TEST_LOGS \033[0m" exit 1 fi diff --git a/dbm-ui/scripts/ci/install.sh b/dbm-ui/scripts/ci/install.sh index a25c698417..5c8cf76cea 100755 --- a/dbm-ui/scripts/ci/install.sh +++ b/dbm-ui/scripts/ci/install.sh @@ -23,7 +23,7 @@ poetry export --without-hashes -f requirements.txt --output requirements.txt pip install -r requirements.txt >> /tmp/pip_install.log if [[ $? -ne 0 ]]; then - echo "Error: pip install -r requirements.txt error!" + echo -e "\033[1;31m Error: pip install -r requirements.txt error! \033[0m" cat /tmp/pip_install.log FAILED_COUNT=$[$FAILED_COUNT+1] fi @@ -57,7 +57,7 @@ echo "开始执行 python manage.py migrate --database=report_db" python manage.py migrate --database=report_db >> /tmp/migrate_report_db.log if [[ $? -ne 0 ]]; then - echo "Error: python manage.py migrate --database=report_db 执行失败!请检查 migrations 文件" + echo -e "\033[1;31m Error: python manage.py migrate --database=report_db 执行失败!请检查 migrations 文件 \033[0m" cat /tmp/migrate_report_db.log FAILED_COUNT=$[$FAILED_COUNT+1] fi @@ -66,7 +66,7 @@ echo "开始执行 python manage.py migrate" python manage.py migrate >> /tmp/migrate.log if [[ $? -ne 0 ]]; then - echo "Error: python manage.py migrate 执行失败!请检查 migrations 文件" + echo -e "\033[1;31m Error: python manage.py migrate 执行失败!请检查 migrations 文件 \033[0m" cat /tmp/migrate.log FAILED_COUNT=$[$FAILED_COUNT+1] fi @@ -76,13 +76,13 @@ python manage.py createcachetable django_cache python manage.py language_finder -p backend/ -m error if [[ $? -ne 0 ]]; then - echo "Error: python manage.py language_finder -p backend/ -m error 执行失败!请检查中文是否已标记" + echo -e "\033[1;31m Error: python manage.py language_finder -p backend/ -m error 执行失败!请检查中文是否已标记 \033[0m" FAILED_COUNT=$[$FAILED_COUNT+1] fi if [[ $FAILED_COUNT -ne 0 ]]; then - echo "Error: 前置命令未通过! 前置命令执行失败数量: $FAILED_COUNT" + echo -e "\033[1;31m Error: 前置命令未通过! 前置命令执行失败数量: $FAILED_COUNT \033[0m" exit 1 else echo "前置命令已通过"