Skip to content

Commit

Permalink
feat(backend): 资源池统计视图 #6753
Browse files Browse the repository at this point in the history
  • Loading branch information
iSecloud committed Sep 25, 2024
1 parent dbf9a54 commit 3cf72e2
Show file tree
Hide file tree
Showing 12 changed files with 204 additions and 34 deletions.
6 changes: 6 additions & 0 deletions dbm-ui/backend/components/dbresource/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ def __init__(self):
url="/resource/spec/sum",
description=_("预申请获取资源数量"),
)
self.resource_group_count = self.generate_data_api(
method="POST", url="/statistic/groupby/resource_type", description=_("按照组件统计资源数量")
)
self.resource_summary = self.generate_data_api(
method="POST", url="/statistic/summary", description=_("按照条件聚合资源统计")
)


DBResourceApi = _DBResourceApi()
7 changes: 7 additions & 0 deletions dbm-ui/backend/db_meta/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ class BKCityAdmin(admin.ModelAdmin):
search_fields = ("bk_idc_city_name",)


@admin.register(models.city_map.BKSubzone)
class BKSubzoneAdmin(admin.ModelAdmin):
list_display = ("bk_city", "bk_sub_zone", "bk_sub_zone_id")
list_filter = ("bk_city",)
search_fields = ("bk_sub_zone", "bk_sub_zone_id")


@admin.register(models.cluster.Cluster)
class ClusterAdmin(admin.ModelAdmin):
list_display = ("name", "bk_biz_id", "cluster_type", "db_module_id", "immute_domain")
Expand Down
33 changes: 33 additions & 0 deletions dbm-ui/backend/db_meta/migrations/0042_auto_20240903_1138.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 3.2.25 on 2024-09-03 03:38

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("db_meta", "0041_auto_20240819_1031"),
]

operations = [
migrations.CreateModel(
name="BKSubzone",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("creator", models.CharField(max_length=64, verbose_name="创建人")),
("create_at", models.DateTimeField(auto_now_add=True, verbose_name="创建时间")),
("updater", models.CharField(max_length=64, verbose_name="修改人")),
("update_at", models.DateTimeField(auto_now=True, verbose_name="更新时间")),
(
"bk_sub_zone",
models.CharField(blank=True, default="", help_text="子 Zone", max_length=128, null=True),
),
("bk_sub_zone_id", models.IntegerField(default=0, help_text="子 Zone ID")),
("bk_city", models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to="db_meta.bkcity")),
],
options={
"abstract": False,
},
),
]
16 changes: 16 additions & 0 deletions dbm-ui/backend/db_meta/models/city_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,19 @@ def __str__(self):

class Meta:
verbose_name = verbose_name_plural = _("蓝鲸城市表(BKCity)")


class BKSubzone(AuditedModel):
"""
机器实际的园区
"""

bk_sub_zone = models.CharField(max_length=128, default="", blank=True, null=True, help_text=_("子 Zone"))
bk_sub_zone_id = models.IntegerField(default=0, help_text=_("子 Zone ID"))
bk_city = models.ForeignKey(BKCity, on_delete=models.PROTECT)

def __str__(self):
return self.bk_sub_zone

class Meta:
verbose_name = verbose_name_plural = _("蓝鲸园区表(BKSubzone)")
5 changes: 5 additions & 0 deletions dbm-ui/backend/db_services/dbresource/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,8 @@
class ResourceOperation(str, StructuredEnum):
import_hosts = EnumField("imported", _("导入主机"))
consume_hosts = EnumField("consumed", _("消费主机"))


class ResourceGroupByEnum(str, StructuredEnum):
DEVICE_CLASS = EnumField("device_class", _("按照机型聚合"))
SPEC = EnumField("spec", _("按规格聚合"))
31 changes: 27 additions & 4 deletions dbm-ui/backend/db_services/dbresource/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@
"bk_mem": 15,
"bk_cpu": 8,
"bk_disk": 0,
"resource_types": ["influxdb"],
"for_bizs": [{"bk_biz_id": 2005000100, "bk_biz_name": "xxxx"}],
"resource_type": "influxdb",
"for_biz": {"bk_biz_id": 2005000100, "bk_biz_name": "xxxx"},
},
]

Expand All @@ -81,9 +81,32 @@

RESOURCE_UPDATE_PARAMS = {
"bk_host_ids": [192],
"for_bizs": [3],
"resource_types": ["tendbcluster"],
"for_biz": 3,
"resource_type": "tendbcluster",
"set_empty_biz": False,
"set_empty_resource_type": False,
"storage_device": {"/data3": {"size": 200, "disk_type": "HDD"}},
}

RESOURCE_SUMMARY_DATA = [
{
"dedicated_biz": 3,
"for_biz_name": "业务1",
"city": "上海",
"spec_id": 1,
"spec_name": "2核4G100G磁盘",
"spec_machine_type": "single",
"count": 1,
"sub_zone_detail": {"上海-宝信": 1},
},
{
"dedicated_biz": 1001,
"for_biz_name": "业务2",
"city": "上海",
"device_class": "S5.4XLARGE32",
"disk_summary": "",
"cpu_mem_summary": "16核32000G",
"count": 1,
"sub_zone_detail": {"上海-宝信": 1},
},
]
56 changes: 37 additions & 19 deletions dbm-ui/backend/db_services/dbresource/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
from rest_framework import serializers

from backend import env
from backend.configuration.constants import DBType
from backend.constants import INT_MAX
from backend.db_meta.enums import ClusterType, InstanceRole, MachineType
from backend.db_meta.models import Spec
from backend.db_services.dbresource.constants import ResourceOperation
from backend.db_services.dbresource import mock
from backend.db_services.dbresource.constants import ResourceGroupByEnum, ResourceOperation
from backend.db_services.dbresource.mock import (
RECOMMEND_SPEC_DATA,
RESOURCE_LIST_DATA,
Expand All @@ -36,10 +36,8 @@ class HostInfoSerializer(serializers.Serializer):
host_id = serializers.IntegerField()
bk_cloud_id = serializers.IntegerField()

for_bizs = serializers.ListSerializer(help_text=_("专属业务的ID列表"), child=serializers.IntegerField())
resource_types = serializers.ListField(
help_text=_("专属DB"), child=serializers.ChoiceField(choices=DBType.get_choices())
)
for_biz = serializers.IntegerField(help_text=_("专属业务"))
resource_type = serializers.CharField(help_text=_("专属DB"), allow_blank=True, allow_null=True)
bk_biz_id = serializers.IntegerField(help_text=_("机器当前所属的业务id "), default=env.DBA_APP_BK_BIZ_ID)
hosts = serializers.ListSerializer(help_text=_("主机"), child=HostInfoSerializer())
labels = serializers.DictField(help_text=_("标签信息"), required=False)
Expand All @@ -57,7 +55,7 @@ class HostDetailSerializer(serializers.Serializer):
count = serializers.IntegerField(help_text=_("数量"))

bk_cloud_id = serializers.IntegerField(help_text=_("云区域ID"))
resource_type = serializers.CharField(help_text=_("专属DB"), required=False)
resource_type = serializers.CharField(help_text=_("专属DB"), required=False, allow_null=True, allow_blank=True)
for_biz_id = serializers.IntegerField(help_text=_("业务专属ID"), required=False)
details = serializers.ListSerializer(help_text=_("资源申请参数"), child=HostDetailSerializer())

Expand All @@ -72,13 +70,14 @@ class ResourceLimitSerializer(serializers.Serializer):
min = serializers.IntegerField(help_text=_("资源最小值"), required=False)
max = serializers.IntegerField(help_text=_("资源最大值"), required=False)

for_bizs = serializers.CharField(help_text=_("专属业务"), required=False)
resource_types = serializers.CharField(help_text=_("专属DB"), required=False)
for_biz = serializers.IntegerField(help_text=_("专属业务"), required=False)
resource_type = serializers.CharField(help_text=_("专属DB"), required=False, allow_null=True, allow_blank=True)
device_class = serializers.CharField(help_text=_("机型"), required=False)
hosts = serializers.CharField(help_text=_("主机IP列表"), required=False)
bk_cloud_ids = serializers.CharField(help_text=_("云区域ID列表"), required=False)
city = serializers.CharField(help_text=_("城市"), required=False)
subzones = serializers.CharField(help_text=_("园区"), required=False)
subzone_ids = serializers.ListField(help_text=_("园区ID"), child=serializers.CharField(), required=False)

os_type = serializers.CharField(help_text=_("操作系统类型"), required=False)
cpu = serializers.CharField(help_text=_("cpu资源限制"), required=False)
Expand All @@ -102,8 +101,8 @@ def format_fields(attrs, fields):

if attrs.get(field):
attrs[field] = attrs[field].split(divider)
# for_bizs,bk_cloud_ids 要转换为int
if field in ["for_bizs", "bk_cloud_ids"]:
# bk_cloud_ids 要转换为int
if field in ["bk_cloud_ids"]:
attrs[field] = list(map(int, attrs[field]))
# cpu, mem, disk 需要转换为结构体
elif field in ["mem"]:
Expand Down Expand Up @@ -136,8 +135,6 @@ def validate(self, attrs):
self.format_fields(
attrs,
fields=[
"for_bizs",
"resource_types",
"device_class",
"hosts",
"city",
Expand Down Expand Up @@ -176,12 +173,8 @@ class ResourceDeleteSerializer(serializers.Serializer):
class ResourceUpdateSerializer(serializers.Serializer):
bk_host_ids = serializers.ListField(help_text=_("主机ID列表"), child=serializers.IntegerField())
labels = serializers.DictField(help_text=_("Labels"), required=False)
for_bizs = serializers.ListField(help_text=_("专用业务ID"), child=serializers.IntegerField(), required=False)
resource_types = serializers.ListField(
help_text=_("专属DB"),
child=serializers.ChoiceField(choices=DBType.get_choices()),
required=False,
)
for_biz = serializers.IntegerField(help_text=_("专用业务ID"), required=False)
resource_type = serializers.CharField(help_text=_("专属DB"), allow_blank=True, allow_null=True)
set_empty_biz = serializers.BooleanField(help_text=_("是否无专用业务"), required=False, default=False)
set_empty_resource_type = serializers.BooleanField(help_text=_("是否无专用资源类型"), required=False, default=False)
storage_device = serializers.JSONField(help_text=_("磁盘挂载点信息"), required=False)
Expand Down Expand Up @@ -237,6 +230,31 @@ def validate(self, attrs):
return attrs


class ResourceSummarySerializer(serializers.Serializer):
class SpecParamSerializer(serializers.Serializer):
db_type = serializers.CharField(help_text=_("db类型"))
machine_type = serializers.ChoiceField(
help_text=_("机器类型"), choices=MachineType.get_choices(), required=False, default=""
)
cluster_type = serializers.ChoiceField(
help_text=_("集群类型"), choices=ClusterType.get_choices(), required=False, default=""
)
spec_id_list = serializers.ListField(
help_text=_("规格ID列表"), child=serializers.IntegerField(), required=False, default=[]
)

for_biz = serializers.IntegerField(help_text=_("专用业务ID"), required=False, default=0)
city = serializers.CharField(help_text=_("城市名"), required=False, allow_blank=True)
group_by = serializers.ChoiceField(help_text=_("聚合类型"), choices=ResourceGroupByEnum.get_choices())
subzone_ids = serializers.ListField(help_text=_("园区"), child=serializers.CharField(), required=False, default=[])
spec_param = SpecParamSerializer(help_text=_("聚合过滤字段"))


class ResourceSummaryResponseSerializer(serializers.Serializer):
class Meta:
swagger_schema_fields = {"example": mock.RESOURCE_SUMMARY_DATA}


class SpecSerializer(serializers.ModelSerializer):
spec_db_type = serializers.SerializerMethodField(help_text=_("规格组件类型"))

Expand Down
43 changes: 33 additions & 10 deletions dbm-ui/backend/db_services/dbresource/views/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
specific language governing permissions and limitations under the License.
"""

import itertools
import time
from typing import Dict, List

Expand Down Expand Up @@ -47,6 +46,8 @@
ResourceImportSerializer,
ResourceListResponseSerializer,
ResourceListSerializer,
ResourceSummaryResponseSerializer,
ResourceSummarySerializer,
ResourceUpdateSerializer,
SpecCountResourceResponseSerializer,
SpecCountResourceSerializer,
Expand Down Expand Up @@ -103,20 +104,19 @@ class DBResourceViewSet(viewsets.SystemViewSet):
)
@action(detail=False, methods=["POST"], url_path="list", serializer_class=ResourceListSerializer)
def resource_list(self, request):
def _format_resource_fields(data, _cloud_info, _for_biz_infos):
def _format_resource_fields(data, _cloud_info, _biz_infos):
data.update(
{
"bk_cloud_name": _cloud_info[str(data["bk_cloud_id"])]["bk_cloud_name"],
"bk_host_innerip": data["ip"],
# 内存 MB --> GB
"bk_mem": data.pop("dram_cap"),
"bk_cpu": data.pop("cpu_num"),
"bk_disk": data.pop("total_storage_cap"),
"resource_types": data.pop("resource_types"),
"for_bizs": [
{"bk_biz_id": int(bk_biz_id), "bk_biz_name": _for_biz_infos[int(bk_biz_id)]}
for bk_biz_id in data.pop("for_bizs")
],
"resource_type": data.pop("rs_type"),
"for_biz": {
"bk_biz_id": data["dedicated_biz"],
"bk_biz_name": _biz_infos.get(data["dedicated_biz"]),
},
"agent_status": int((data.pop("gse_agent_status_code") == GSE_AGENT_RUNNING_CODE)),
}
)
Expand All @@ -128,8 +128,7 @@ def _format_resource_fields(data, _cloud_info, _for_biz_infos):

# 获取云区域信息和业务信息
cloud_info = ResourceQueryHelper.search_cc_cloud(get_cache=True)
for_biz_ids = list(set(itertools.chain(*[data["for_bizs"] for data in resource_data["details"]])))
for_biz_ids = list(map(int, for_biz_ids))
for_biz_ids = [data["dedicated_biz"] for data in resource_data["details"]]
for_biz_infos = AppCache.batch_get_app_attr(bk_biz_ids=for_biz_ids, attr_name="bk_biz_name")
# 格式化资源池字段信息
for data in resource_data.get("details") or []:
Expand Down Expand Up @@ -353,6 +352,30 @@ def resource_update(self, request):
update_params = self.params_validate(self.get_serializer_class())
return Response(DBResourceApi.resource_batch_update(params=update_params))

@common_swagger_auto_schema(
operation_summary=_("按照组件统计资源数量"),
tags=[SWAGGER_TAG],
)
@action(detail=False, methods=["POST"])
def resource_group_count(self, request):
return Response(DBResourceApi.resource_group_count())

@common_swagger_auto_schema(
operation_summary=_("按照条件聚合资源统计"),
request_body=ResourceSummarySerializer(),
responses={status.HTTP_200_OK: ResourceSummaryResponseSerializer()},
tags=[SWAGGER_TAG],
)
@action(detail=False, methods=["POST"], serializer_class=ResourceSummarySerializer)
def resource_summary(self, request):
group_params = self.params_validate(self.get_serializer_class())
summary_data = DBResourceApi.resource_summary(params=group_params)
# 补充业务名
for_biz_ids = [data["dedicated_biz"] for data in summary_data]
for_biz_infos = AppCache.batch_get_app_attr(bk_biz_ids=for_biz_ids, attr_name="bk_biz_name")
summary_data = [{"for_biz_name": for_biz_infos.get(data["dedicated_biz"]), **data} for data in summary_data]
return Response(summary_data)

@common_swagger_auto_schema(
operation_summary=_("获取资源导入相关链接"),
tags=[SWAGGER_TAG],
Expand Down
12 changes: 12 additions & 0 deletions dbm-ui/backend/db_services/infras/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from django.utils.translation import ugettext_lazy as _

from backend.db_meta.models import BKCity, LogicalCity
from backend.db_meta.models.city_map import BKSubzone
from backend.db_services.dbbase.constants import IpSource
from backend.db_services.infras.constants import InventoryTag

Expand Down Expand Up @@ -71,6 +72,17 @@ def list_logic_cities() -> List:
return logic_cities


def list_subzones(city_code: str = "") -> List:
subzones = BKSubzone.objects.select_related("bk_city").all()
if city_code:
subzones = subzones.filter(bk_city__bk_idc_city_name=city_code)

subzone_infos = []
for zone in subzones:
subzone_infos.append({**model_to_dict(zone), "bk_city_code": zone.bk_city.bk_idc_city_name})
return subzone_infos


def list_host_specs() -> List[HostSpecModel]:
# TODO 暂时全量返回,后续机型设计时再做考虑
return [spec for spec in host_specs]
Expand Down
13 changes: 13 additions & 0 deletions dbm-ui/backend/db_services/infras/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ class LogicCitySLZ(serializers.Serializer):
logical_city_name = serializers.CharField()


class ListSubzoneSLZ(serializers.Serializer):
city_code = serializers.CharField(help_text=_("城市ID"), required=False, default="")


class SubzoneSLZ(serializers.Serializer):
"""城市园区信息"""

bk_city = serializers.IntegerField()
bk_city_code = serializers.CharField()
bk_sub_zone_id = serializers.IntegerField()
bk_sub_zone = serializers.CharField()


class HostSpecSLZ(serializers.Serializer):
"""服务器规格"""

Expand Down
Loading

0 comments on commit 3cf72e2

Please sign in to comment.