Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(backend): 标签功能 #6235 #6414

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions dbm-ui/backend/db_meta/enums/comm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
84 changes: 84 additions & 0 deletions dbm-ui/backend/db_meta/migrations/0043_auto_20241015_2128.py
Original file line number Diff line number Diff line change
@@ -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",
),
]
2 changes: 2 additions & 0 deletions dbm-ui/backend/db_meta/models/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",)]
Expand Down
23 changes: 16 additions & 7 deletions dbm-ui/backend/db_meta/models/tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
13 changes: 10 additions & 3 deletions dbm-ui/backend/db_services/dbbase/resources/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -397,8 +397,14 @@ def _list_clusters(
for param in filter_params_map:
if query_params.get(param):
query_filters &= filter_params_map[param]

# 对标签进行过滤,标签“且”查询,需以追加 filter 的方式实现
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

标签过滤规则目前暂定为:or

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过滤,会影响后续集群关联实例的获取
Expand Down Expand Up @@ -472,7 +478,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))

Expand Down Expand Up @@ -564,6 +570,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
17 changes: 17 additions & 0 deletions dbm-ui/backend/db_services/tag/constants.py
Original file line number Diff line number Diff line change
@@ -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", _("资源池"))
100 changes: 100 additions & 0 deletions dbm-ui/backend/db_services/tag/handlers.py
Original file line number Diff line number Diff line change
@@ -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
51 changes: 51 additions & 0 deletions dbm-ui/backend/db_services/tag/serializers.py
Original file line number Diff line number Diff line change
@@ -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)
19 changes: 19 additions & 0 deletions dbm-ui/backend/db_services/tag/urls.py
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading