From b2ce926e980d3610b0389d7c8042ed2ad7812540 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?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dbm-ui/backend/db_meta/enums/comm.py | 8 +- .../migrations/0043_auto_20241015_2128.py | 84 +++++++++++++++ dbm-ui/backend/db_meta/models/cluster.py | 2 + dbm-ui/backend/db_meta/models/tag.py | 23 ++-- .../db_services/dbbase/resources/query.py | 13 ++- .../dbbase/resources/serializers.py | 1 + .../tag/__init__.py} | 0 dbm-ui/backend/db_services/tag/constants.py | 17 +++ dbm-ui/backend/db_services/tag/handlers.py | 100 ++++++++++++++++++ dbm-ui/backend/db_services/tag/serializers.py | 51 +++++++++ dbm-ui/backend/db_services/tag/urls.py | 19 ++++ dbm-ui/backend/db_services/tag/views.py | 91 ++++++++++++++++ .../tests/db_monitor/views/__init__.py | 10 ++ .../ticket/builders/mongodb/mongo_restore.py | 2 +- .../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 +- 18 files changed, 416 insertions(+), 22 deletions(-) create mode 100644 dbm-ui/backend/db_meta/migrations/0043_auto_20241015_2128.py rename dbm-ui/backend/{tests/db_monitor/views/__init_.py => db_services/tag/__init__.py} (100%) create mode 100644 dbm-ui/backend/db_services/tag/constants.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 create mode 100644 dbm-ui/backend/tests/db_monitor/views/__init__.py diff --git a/dbm-ui/backend/db_meta/enums/comm.py b/dbm-ui/backend/db_meta/enums/comm.py index 89a82b24f4..9a7b9f1660 100644 --- a/dbm-ui/backend/db_meta/enums/comm.py +++ b/dbm-ui/backend/db_meta/enums/comm.py @@ -26,14 +26,16 @@ 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): """系统内置的tag名称""" - TEMPORARY = EnumField("temporary", _("temporary")) + TEMPORARY = EnumField("temporary", _("临时集群")) + RESOURCE_TAG = EnumField("resource", _("资源标签")) class RedisVerUpdateNodeType(str, StructuredEnum): diff --git a/dbm-ui/backend/db_meta/migrations/0043_auto_20241015_2128.py b/dbm-ui/backend/db_meta/migrations/0043_auto_20241015_2128.py new file mode 100644 index 0000000000..703ef66dbe --- /dev/null +++ b/dbm-ui/backend/db_meta/migrations/0043_auto_20241015_2128.py @@ -0,0 +1,84 @@ +# Generated by Django 3.2.25 on 2024-10-15 13:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("db_meta", "0042_auto_20240903_1138"), + ] + + operations = [ + migrations.AlterModelOptions( + name="bksubzone", + options={"verbose_name": "蓝鲸园区表(BKSubzone)", "verbose_name_plural": "蓝鲸园区表(BKSubzone)"}, + ), + migrations.AddField( + model_name="cluster", + name="tags", + field=models.ManyToManyField(blank=True, help_text="标签(外键)", to="db_meta.Tag"), + ), + migrations.AddField( + model_name="tag", + name="key", + field=models.CharField(default="", help_text="标签键", max_length=64), + ), + migrations.AddField( + model_name="tag", + name="value", + field=models.CharField(default="", help_text="标签值", max_length=255), + ), + migrations.AlterField( + model_name="spec", + name="cpu", + field=models.JSONField(help_text='cpu规格描述:{"min":1,"max":10}', null=True), + ), + migrations.AlterField( + model_name="spec", + name="device_class", + field=models.JSONField(help_text='实际机器机型: ["class1","class2"]', null=True), + ), + migrations.AlterField( + model_name="spec", + name="mem", + field=models.JSONField(help_text='mem规格描述:{"min":100,"max":1000}', null=True), + ), + migrations.AlterField( + model_name="spec", + name="qps", + field=models.JSONField(default=dict, help_text='qps规格描述:{"min": 1, "max": 100}'), + ), + migrations.AlterField( + model_name="spec", + name="storage_spec", + field=models.JSONField(help_text='存储磁盘需求配置:[{"mount_point":"/data","size":500,"type":"ssd"}]', null=True), + ), + migrations.AlterField( + model_name="tag", + name="bk_biz_id", + field=models.IntegerField(default=0, help_text="业务 ID"), + ), + migrations.AlterField( + model_name="tag", + name="type", + field=models.CharField( + choices=[("custom", "自定义标签"), ("system", "系统标签"), ("builtin", "内置标签")], + default="custom", + help_text="tag类型", + max_length=64, + ), + ), + migrations.AlterUniqueTogether( + name="tag", + unique_together={("bk_biz_id", "key", "value")}, + ), + migrations.RemoveField( + model_name="tag", + name="cluster", + ), + migrations.RemoveField( + model_name="tag", + name="name", + ), + ] diff --git a/dbm-ui/backend/db_meta/models/cluster.py b/dbm-ui/backend/db_meta/models/cluster.py index 1c214d6c5d..dde22ac8dd 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.exceptions import ApiError from backend.flow.consts import DEFAULT_RIAK_PORT @@ -69,6 +70,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..aaacff121c 100644 --- a/dbm-ui/backend/db_meta/models/tag.py +++ b/dbm-ui/backend/db_meta/models/tag.py @@ -12,20 +12,29 @@ 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=255) + type = models.CharField( + help_text=_("tag类型"), max_length=64, choices=TagType.get_choices(), default=TagType.CUSTOM.value + ) 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 cf41a864f8..2e34865a8d 100644 --- a/dbm-ui/backend/db_services/dbbase/resources/query.py +++ b/dbm-ui/backend/db_services/dbbase/resources/query.py @@ -213,7 +213,7 @@ def export_instance(cls, bk_biz_id: int, bk_host_ids: list) -> HttpResponse: @classmethod def get_temporary_cluster_info(cls, cluster, ticket_type): """如果当前集群是临时集群,则补充临时集群相关信息。""" - tags = [tag.name for tag in cluster.tag_set.all()] + tags = [tag.key for tag in cluster.tag_set.all()] if SystemTagEnum.TEMPORARY.value not in tags: return {} record = ClusterOperateRecord.objects.filter(cluster_id=cluster.id, ticket__ticket_type=ticket_type).first() @@ -401,8 +401,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_instance_func(_query_params, _cluster_queryset, _proxy_queryset, _storage_queryset): @@ -466,7 +472,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", ) # 由于对 queryset 切片工作方式的模糊性,这里的values可能会获得非预期的排序,所以不要在切片后用values # cluster_ids = list(cluster_queryset.values_list("id", flat=True)) @@ -562,6 +568,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 9a7860af51..7effc153c0 100644 --- a/dbm-ui/backend/db_services/dbbase/resources/serializers.py +++ b/dbm-ui/backend/db_services/dbbase/resources/serializers.py @@ -32,6 +32,7 @@ class ListResourceSLZ(serializers.Serializer): bk_cloud_id = serializers.CharField(required=False, help_text=_("管控区域")) cluster_type = serializers.CharField(required=False, help_text=_("集群类型")) ordering = serializers.CharField(required=False, help_text=_("排序字段,非必填")) + tag_ids = serializers.CharField(required=False, help_text=_("标签")) class ListMySQLResourceSLZ(ListResourceSLZ): diff --git a/dbm-ui/backend/tests/db_monitor/views/__init_.py b/dbm-ui/backend/db_services/tag/__init__.py similarity index 100% rename from dbm-ui/backend/tests/db_monitor/views/__init_.py rename to dbm-ui/backend/db_services/tag/__init__.py diff --git a/dbm-ui/backend/db_services/tag/constants.py b/dbm-ui/backend/db_services/tag/constants.py new file mode 100644 index 0000000000..03be4ecf3b --- /dev/null +++ b/dbm-ui/backend/db_services/tag/constants.py @@ -0,0 +1,17 @@ +# -*- 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 gettext_lazy as _ + +from blue_krill.data_types.enum import EnumField, StructuredEnum + + +class TagResourceType(str, StructuredEnum): + DB_RESOURCE = EnumField("db_resource", _("资源池")) 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..8a0a7aa794 --- /dev/null +++ b/dbm-ui/backend/db_services/tag/handlers.py @@ -0,0 +1,100 @@ +# -*- 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 + +from django.db.models import ManyToManyRel +from django.utils.translation import gettext_lazy as _ + +from backend.db_meta.models import Tag +from backend.db_services.tag.constants import TagResourceType +from backend.exceptions import ValidationError + + +class TagHandler: + """标签的操作类""" + + def batch_set_tags(self, tag_ids: List[int]): + """ + 给资源批量设置标签 + """ + # 1. 判断标签中 key 是否允许多值 + + # 2. 批量设置标签 + pass + + @classmethod + def delete_tags(cls, bk_biz_id: int, ids: List[int]): + """ + 删除标签 + """ + # 1. 检查标签是否被引用 + related_resources = cls.query_related_resources(ids) + for related_resource in related_resources: + if related_resource["count"] > 0: + raise ValidationError(_("标签被引用,无法删除")) + + # 2. 批量删除标签 + Tag.objects.filter(bk_biz_id=bk_biz_id, id__in=ids).delete() + + @classmethod + def query_related_resources(cls, ids: List[int], resource_type: str = None): + """ + 查询关联资源 + """ + # 1. 查询外键关联资源 + data = [] + for tag_id in ids: + info = {"id": tag_id, "related_resources": []} + for field in Tag._meta.get_fields(): + if isinstance(field, ManyToManyRel) and (field.name == resource_type or resource_type is None): + related_objs = field.related_model.objects.prefetch_related("tags").filter(tags__id=tag_id) + info["related_resources"].append( + { + "resource_type": field.name, + "count": related_objs.count(), + } + ) + + # 2. 查询第三方服务关联资源(如资源池、后续可能扩展的别的服务) + if resource_type == TagResourceType.DB_RESOURCE.value or resource_type is None: + info["related_resources"].append( + { + "resource_type": TagResourceType.DB_RESOURCE.value, + # TODO 请求资源池接口得到统计数量 + "count": 0, + } + ) + data.append(info) + return data + + @classmethod + def batch_create(cls, bk_biz_id: int, tags: List[Dict[str, str]], creator: str = ""): + """ + 批量创建标签 + """ + duplicate_tags = cls.verify_duplicated(bk_biz_id, tags) + if duplicate_tags: + raise ValidationError(_("检查到重复的标签"), data=duplicate_tags) + + tag_models = [Tag(bk_biz_id=bk_biz_id, key=tag["key"], value=tag["value"], creator=creator) for tag in tags] + Tag.objects.bulk_create(tag_models) + + @classmethod + def verify_duplicated(cls, bk_biz_id: int, tags: List[Dict[str, str]]) -> List[Dict[str, str]]: + """ + 检查标签是否重复 + """ + biz_tags = [f"{tag.key}:{tag.value}" for tag in Tag.objects.filter(bk_biz_id=bk_biz_id)] + duplicate_tags = [] + for tag in tags: + if f'{tag["key"]}:{tag["value"]}' in biz_tags: + duplicate_tags.append(tag) + return duplicate_tags 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..564c001e98 --- /dev/null +++ b/dbm-ui/backend/db_services/tag/serializers.py @@ -0,0 +1,51 @@ +# -*- 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 gettext_lazy as _ +from rest_framework import serializers + +from backend.bk_web.serializers import AuditedSerializer +from backend.db_meta.models import Tag + + +class TagSerializer(AuditedSerializer, serializers.ModelSerializer): + """ + 标签序列化器 + """ + + class Meta: + model = Tag + fields = "__all__" + + +class BatchCreateTagsSerializer(serializers.Serializer): + class CreateTagSerializer(serializers.Serializer): + key = serializers.CharField(help_text=_("标签key")) + value = serializers.CharField(help_text=_("标签value")) + + bk_biz_id = serializers.IntegerField(help_text=_("业务ID")) + tags = serializers.ListField(child=CreateTagSerializer()) + + +class UpdateTagSerializer(serializers.Serializer): + bk_biz_id = serializers.IntegerField(help_text=_("业务ID")) + id = serializers.IntegerField(help_text=_("标签 ID")) + value = serializers.CharField(help_text=_("标签value")) + + +class DeleteTagsSerializer(serializers.Serializer): + bk_biz_id = serializers.IntegerField(help_text=_("业务ID")) + ids = serializers.ListSerializer(child=serializers.IntegerField(help_text=_("标签 ID")), help_text=_("标签 ID 列表")) + + +class QueryRelatedResourceSerializer(serializers.Serializer): + ids = serializers.ListSerializer(child=serializers.IntegerField(help_text=_("标签 ID")), help_text=_("标签 ID 列表")) + resource_type = serializers.CharField(help_text=_("资源类型"), required=False) 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..a79a98a6c9 --- /dev/null +++ b/dbm-ui/backend/db_services/tag/views.py @@ -0,0 +1,91 @@ +# -*- 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.decorators import method_decorator +from django.utils.translation import ugettext as _ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import filters +from rest_framework.decorators import action +from rest_framework.response import Response + +from backend.bk_web.swagger import common_swagger_auto_schema +from backend.bk_web.viewsets import AuditedModelViewSet +from backend.db_meta.models import Tag +from backend.db_services.tag import serializers +from backend.db_services.tag.handlers import TagHandler + +SWAGGER_TAG = _("标签") + + +@method_decorator( + name="partial_update", + decorator=common_swagger_auto_schema( + operation_summary=_("更新标签"), tags=[SWAGGER_TAG], request_body=serializers.UpdateTagSerializer() + ), +) +@method_decorator( + name="list", + decorator=common_swagger_auto_schema(operation_summary=_("查询标签列表"), tags=[SWAGGER_TAG]), +) +class TagViewSet(AuditedModelViewSet): + """ + 标签视图 + """ + + queryset = Tag.objects.all() + serializer_class = serializers.TagSerializer + filter_backends = [filters.SearchFilter, DjangoFilterBackend] + filter_fields = ("bk_biz_id", "key", "value", "type") + + @common_swagger_auto_schema( + operation_summary=_("查询标签关联资源"), request_body=serializers.QueryRelatedResourceSerializer(), tags=[SWAGGER_TAG] + ) + @action(methods=["POST"], detail=False, serializer_class=serializers.QueryRelatedResourceSerializer) + def related_resources(self, request, *args, **kwargs): + """ + 查询标签关联资源 + """ + validated_data = self.params_validate(self.get_serializer_class()) + return Response(TagHandler.query_related_resources(validated_data["ids"], validated_data.get("resource_type"))) + + @common_swagger_auto_schema( + operation_summary=_("批量创建标签"), request_body=serializers.BatchCreateTagsSerializer(), tags=[SWAGGER_TAG] + ) + @action(methods=["POST"], detail=False, serializer_class=serializers.BatchCreateTagsSerializer) + def batch_create(self, request, *args, **kwargs): + """ + 创建标签 + """ + validated_data = self.params_validate(self.get_serializer_class()) + return Response( + TagHandler.batch_create(validated_data["bk_biz_id"], validated_data["tags"], request.user.username) + ) + + @common_swagger_auto_schema( + operation_summary=_("批量删除标签"), request_body=serializers.DeleteTagsSerializer(), tags=[SWAGGER_TAG] + ) + @action(methods=["DELETE"], detail=False, serializer_class=serializers.DeleteTagsSerializer) + def batch_delete(self, request, *args, **kwargs): + """ + 删除标签 + """ + validated_data = self.params_validate(self.get_serializer_class()) + return Response(TagHandler.delete_tags(validated_data["bk_biz_id"], validated_data["ids"])) + + @common_swagger_auto_schema( + operation_summary=_("校验标签是否重复"), request_body=serializers.BatchCreateTagsSerializer(), tags=[SWAGGER_TAG] + ) + @action(methods=["POST"], detail=False, serializer_class=serializers.BatchCreateTagsSerializer) + def verify_duplicated(self, request, *args, **kwargs): + """ + 校验 + """ + validated_data = self.params_validate(self.get_serializer_class()) + return Response(TagHandler.verify_duplicated(validated_data["bk_biz_id"], validated_data["tags"])) diff --git a/dbm-ui/backend/tests/db_monitor/views/__init__.py b/dbm-ui/backend/tests/db_monitor/views/__init__.py new file mode 100644 index 0000000000..aa5085c628 --- /dev/null +++ b/dbm-ui/backend/tests/db_monitor/views/__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/ticket/builders/mongodb/mongo_restore.py b/dbm-ui/backend/ticket/builders/mongodb/mongo_restore.py index 0a885cc724..2edba3e791 100644 --- a/dbm-ui/backend/ticket/builders/mongodb/mongo_restore.py +++ b/dbm-ui/backend/ticket/builders/mongodb/mongo_restore.py @@ -120,7 +120,7 @@ def pre_callback(self): # 为临时集群添加临时标志和记录 temporary_tag, _ = Tag.objects.get_or_create( - bk_biz_id=bk_biz_id, name=SystemTagEnum.TEMPORARY.value, type=TagType.SYSTEM.value + bk_biz_id=bk_biz_id, key=SystemTagEnum.TEMPORARY.value, type=TagType.SYSTEM.value ) source_cluster_name__cluster: Dict[str, Cluster] = {} cluster_records: List[ClusterOperateRecord] = [] 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 19f5c2a23b..df4c1d8ac3 100644 --- a/dbm-ui/backend/ticket/builders/tendbcluster/tendb_fixpoint_rollback.py +++ b/dbm-ui/backend/ticket/builders/tendbcluster/tendb_fixpoint_rollback.py @@ -98,9 +98,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 51f2d65164..6f475895e4 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")), path("legacy/", include("backend.legacy.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 "前置命令已通过"