From 9d9a807285679ef84a52510426be8fb237f5f822 Mon Sep 17 00:00:00 2001 From: wyyalt Date: Mon, 9 Oct 2023 10:13:13 +0800 Subject: [PATCH] =?UTF-8?q?feature:=20=20=E6=8F=90=E4=BE=9B=20Agent=20?= =?UTF-8?q?=E5=8C=85=E7=AE=A1=E7=90=86=E5=90=8E=E5=8F=B0=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=20(closed=20#1683)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/node_man/handlers/meta.py | 28 +++ apps/node_man/serializers/meta.py | 4 + apps/node_man/serializers/packager_manage.py | 113 ++++++++++ apps/node_man/urls.py | 2 + apps/node_man/views/meta.py | 6 +- apps/node_man/views/package_manage.py | 207 +++++++++++++++++++ 6 files changed, 359 insertions(+), 1 deletion(-) create mode 100644 apps/node_man/serializers/packager_manage.py create mode 100644 apps/node_man/views/package_manage.py diff --git a/apps/node_man/handlers/meta.py b/apps/node_man/handlers/meta.py index 24900d5196..42fe4875f5 100644 --- a/apps/node_man/handlers/meta.py +++ b/apps/node_man/handlers/meta.py @@ -469,6 +469,32 @@ def fetch_os_type_children(os_types: Tuple = constants.OsType): os_type_children.append({"id": os_type, "name": constants.OS_CHN.get(os_type, os_type)}) return os_type_children + @staticmethod + def fetch_agent_pkg_manager_children(): + mock_version = [ + {"name": "2.1.8", "id": "2.1.8"}, + {"name": "2.1.7", "id": "2.1.7"}, + ] + mock_tags = [ + {"name": "稳定版本", "id": "stable"}, + {"name": "最新版本", "id": "latest"}, + ] + mock_creator = [ + {"name": "user1", "id": "user1"}, + {"name": "user2", "id": "user2"}, + ] + mock_is_ready = [ + {"name": "启用", "id": True}, + {"name": "停用", "id": False}, + ] + + return [ + {"name": _("版本号"), "id": "version", "children": mock_version}, + {"name": _("标签信息"), "id": "tags", "children": mock_tags}, + {"name": _("上传用户"), "id": "creator", "children": mock_creator}, + {"name": _("状态"), "id": "is_ready", "children": mock_is_ready}, + ] + def filter_condition(self, category): """ 获取过滤条件 @@ -495,6 +521,8 @@ def filter_condition(self, category): elif category == "os_type": ret = self.fetch_os_type_children() return ret + elif category == "agent_pkg_manage": + return self.fetch_agent_pkg_manager_children() @staticmethod def install_default_values_formatter(install_default_values: Dict[str, Dict[str, Any]]): diff --git a/apps/node_man/serializers/meta.py b/apps/node_man/serializers/meta.py index 8bbcee1074..802f46b072 100644 --- a/apps/node_man/serializers/meta.py +++ b/apps/node_man/serializers/meta.py @@ -25,3 +25,7 @@ class JobSettingSerializer(serializers.Serializer): install_download_limit_speed = serializers.IntegerField(label=_("安装下载限速"), max_value=JOB_MAX_VALUE, min_value=0) parallel_install_number = serializers.IntegerField(label=_("并行安装数"), max_value=JOB_MAX_VALUE, min_value=0) node_man_log_level = serializers.ChoiceField(label=_("节点管理日志级别"), choices=list(NODE_MAN_LOG_LEVEL)) + + +class FilterConditionSerializer(serializers.Serializer): + category = serializers.ChoiceField(label=_("分类"), choices=["agent_pkg_manage", "agent_pkg_quick_search"]) diff --git a/apps/node_man/serializers/packager_manage.py b/apps/node_man/serializers/packager_manage.py new file mode 100644 index 0000000000..323259e0ea --- /dev/null +++ b/apps/node_man/serializers/packager_manage.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +""" +TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-节点管理(BlueKing-BK-NODEMAN) available. +Copyright (C) 2017-2022 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_lazy as _ +from rest_framework import serializers + +from apps.exceptions import ValidationError + + +class TagsSerializer(serializers.Serializer): + id = serializers.CharField() + label = serializers.CharField() + + +class ConditionsSerializer(serializers.Serializer): + key = serializers.ChoiceField(choices=["version", "os_cpu_arch", "tags", "is_ready"]) + values = serializers.ListField() + + +class PKGSortSerializer(serializers.Serializer): + head = serializers.ChoiceField(choices=["version", "host_count"]) + sort_type = serializers.ChoiceField(choices=["ASC", "DEC"]) + + +class SearchSerializer(serializers.Serializer): + page = serializers.IntegerField() + pagesize = serializers.IntegerField() + module = serializers.ChoiceField(choices=["agent", "proxy"]) + conditions = ConditionsSerializer(many=True) + sort = PKGSortSerializer(many=True) + + +class SearchResponseSerializer(serializers.Serializer): + id = serializers.IntegerField() + pkg_name = serializers.CharField() + version = serializers.CharField() + os_cpu_arch = serializers.CharField() + tags = TagsSerializer(many=True) + creator = serializers.CharField() + pkg_ctime = serializers.DateTimeField() + host_count = serializers.IntegerField() + is_ready = serializers.BooleanField() + + +class OperateSerializer(serializers.Serializer): + id = serializers.IntegerField() + action = serializers.ChoiceField(choices=["enable", "disable", "delete"]) + + +# TODO 与plugin相同可抽取公共Serializer +class UploadSerializer(serializers.Serializer): + class PkgFileField(serializers.FileField): + def to_internal_value(self, data): + data = super().to_internal_value(data) + file_name = data.name + if not (file_name.endswith(".tgz") or file_name.endswith(".tar.gz")): + raise ValidationError(_("仅支持'tgz', 'tar.gz'拓展名的文件")) + return data + + module = serializers.ChoiceField(choices=["gse_agent", "gse_proxy"], required=False, default="gse_agent") + package_file = PkgFileField() + + +class UploadResponseSerializer(serializers.Serializer): + id = serializers.IntegerField() + name = serializers.CharField() + pkg_size = serializers.IntegerField() + + +class ParseSerializer(serializers.Serializer): + file_name = serializers.CharField() + + +class PackageSerializer(serializers.Serializer): + module = serializers.ChoiceField(choices=["agent", "proxy"]) + pkg_name = serializers.CharField() + pkg_abs_path = serializers.CharField() + version = serializers.CharField() + os = serializers.CharField() + cpu_arch = serializers.CharField() + config_templates = serializers.ListField() + + +class ParseResponseSerializer(serializers.Serializer): + description = serializers.CharField() + packages = PackageSerializer(many=True) + + +class RegisterPackageSerializer(serializers.Serializer): + pkg_abs_path = serializers.CharField() + tags = serializers.ListField() + + +class AgentRegisterSerializer(serializers.Serializer): + is_release = serializers.BooleanField() + packages = RegisterPackageSerializer(many=True) + + +class AgentRegisterTaskSerializer(serializers.Serializer): + job_id = serializers.IntegerField() + + +class AgentRegisterTaskResponseSerializer(serializers.Serializer): + is_finish = serializers.BooleanField() + status = serializers.ChoiceField(choices=["SUCCESS", "FAILED", "RUNNING"]) + message = serializers.CharField() diff --git a/apps/node_man/urls.py b/apps/node_man/urls.py index 42144e2220..1872995e0b 100644 --- a/apps/node_man/urls.py +++ b/apps/node_man/urls.py @@ -40,6 +40,7 @@ ) from apps.node_man.views.healthz import HealthzViewSet from apps.node_man.views.host_v2 import HostV2ViewSet +from apps.node_man.views.package_manage import PackageManageViewSet from apps.node_man.views.plugin import GsePluginViewSet from apps.node_man.views.plugin_v2 import PluginV2ViewSet from apps.node_man.views.sync_task import SyncTaskViewSet @@ -67,6 +68,7 @@ router.register(r"v2/plugin", PluginV2ViewSet, basename="plugin_v2") router.register(r"healthz", HealthzViewSet, basename="healthz") router.register(r"sync_task", SyncTaskViewSet, basename="sync_task") +router.register(r"agent/package", PackageManageViewSet, basename="package_manage") biz_dispatcher = DjangoBasicResourceApiDispatcher(iam, settings.BK_IAM_SYSTEM_ID) biz_dispatcher.register("biz", BusinessResourceProvider()) diff --git a/apps/node_man/views/meta.py b/apps/node_man/views/meta.py index af0db561e7..1c444ab4e0 100644 --- a/apps/node_man/views/meta.py +++ b/apps/node_man/views/meta.py @@ -17,7 +17,10 @@ from apps.node_man.exceptions import NotSuperUserError from apps.node_man.handlers.iam import IamHandler from apps.node_man.handlers.meta import MetaHandler -from apps.node_man.serializers.meta import JobSettingSerializer +from apps.node_man.serializers.meta import ( + FilterConditionSerializer, + JobSettingSerializer, +) from apps.utils.local import get_request_username META_VIEW_TAGS = ["meta"] @@ -26,6 +29,7 @@ class MetaViews(APIViewSet): @swagger_auto_schema( operation_summary="获取过滤条件", + query_serializer=FilterConditionSerializer, tags=META_VIEW_TAGS, ) @action(detail=False) diff --git a/apps/node_man/views/package_manage.py b/apps/node_man/views/package_manage.py new file mode 100644 index 0000000000..e8d50ebccc --- /dev/null +++ b/apps/node_man/views/package_manage.py @@ -0,0 +1,207 @@ +# -*- coding: utf-8 -*- +""" +TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-节点管理(BlueKing-BK-NODEMAN) available. +Copyright (C) 2017-2022 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 drf_yasg.utils import swagger_auto_schema +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework.status import HTTP_200_OK + +from apps.generic import ModelViewSet +from apps.node_man import models +from apps.node_man.serializers import packager_manage as pkg_manage + +PACKAGE_MANAGE_VIEW_TAGS = ["PKG_Manager"] + + +class PackageManageViewSet(ModelViewSet): + model = models.Packages + http_method_names = ["get", "post"] + + @swagger_auto_schema( + operation_summary="查询安装包列表", + tags=PACKAGE_MANAGE_VIEW_TAGS, + responses={HTTP_200_OK: pkg_manage.SearchResponseSerializer}, + ) + @action(detail=False, methods=["POST"], serializer_class=pkg_manage.SearchSerializer) + def search(self, request, *args, **kwargs): + # data = self.validated_data + mock_data = { + "total": 2, + "list": [ + { + "id": 1, + "pkg_name": "pkg_name", + "version": "1.1.1", + "os": "Linux", + "cpu_arch": "x86_64", + "tags": [{"tag_name": "stable", "tag_display": "稳定版本"}], + "creator": "string", + "pkg_ctime": "2019-08-24 14:15:22", + "host_count": 100, + "is_ready": True, + }, + { + "id": 2, + "pkg_name": "pkg_name", + "version": "1.1.2", + "os": "Linux", + "os_cpu_arch": "x86_64", + "tags": [{"tag_name": "stable", "tag_display": "稳定版本"}], + "creator": "string", + "pkg_ctime": "2019-08-24 14:15:22", + "host_count": 100, + "is_ready": True, + }, + ], + } + return Response(mock_data) + + @swagger_auto_schema( + operation_summary="获取快速筛选信息", + tags=PACKAGE_MANAGE_VIEW_TAGS, + ) + @action(detail=False, methods=["GET"]) + def quick_search_condition(self, request, *args, **kwargs): + mock_version = [ + {"name": "2.1.8", "id": "2.1.8", "host_count": 10}, + {"name": "2.1.7", "id": "2.1.7", "host_count": 10}, + {"name": "ALL", "id": "all", "host_count": 20}, + ] + + mock_os_cpu_arch = [ + {"name": "Linux_x86_64", "id": "linux_x86_64", "host_count": 10}, + {"name": "Linux_x86", "id": "linux_x86", "host_count": 10}, + {"name": "ALL", "id": "all", "host_count": 20}, + ] + + mock_data = [ + {"name": "操作系统/架构", "id": "os_cpu_arch", "children": mock_os_cpu_arch}, + {"name": "版本号", "id": "version", "children": mock_version}, + ] + + return Response(mock_data) + + # @swagger_auto_schema( + # operation_summary="删除安装包", + # tags=PACKAGE_MANAGE_VIEW_TAGS, + # ) + # def destroy(self, request, *args, **kwargs): + + # return Response(status=HTTP_204_NO_CONTENT) + + @swagger_auto_schema( + operation_summary="操作类动作:启用/停用/删除", + tags=PACKAGE_MANAGE_VIEW_TAGS, + ) + @action(detail=False, methods=["POST"], serializer_class=pkg_manage.OperateSerializer) + def operate(self, request, *args, **kwargs): + # data = self.validated_data + return Response(status=HTTP_200_OK) + + @swagger_auto_schema( + operation_summary="Agent包上传", + tags=PACKAGE_MANAGE_VIEW_TAGS, + responses={HTTP_200_OK: pkg_manage.UploadResponseSerializer}, + ) + @action(detail=False, methods=["POST"], serializer_class=pkg_manage.UploadSerializer) + def upload(self, request): + # data = self.validated_data + mock_data = { + "id": 1, + "name": "gse_agent.tgz", + "pkg_size": 100, + } + return Response(mock_data) + + @swagger_auto_schema( + operation_summary="解析Agent包", + tags=PACKAGE_MANAGE_VIEW_TAGS, + responses={HTTP_200_OK: pkg_manage.ParseResponseSerializer}, + ) + @action(detail=False, methods=["POST"], serializer_class=pkg_manage.ParseSerializer) + def parse(self, request): + mock_data = { + "description": "test", + "packages": [ + { + "pkg_abs_path": "xxx/xxxxx", + "pkg_name": "gseagent_2.1.7_linux_x86_64.tgz", + "module": "agent", + "version": "2.1.7", + "config_templates": [], + "os": "x86_64", + }, + { + "pkg_abs_path": "xxx/xxxxx", + "pkg_name": "gseagent_2.1.7_linux_x86.tgz", + "module": "agent", + "version": "2.1.7", + "config_templates": [], + "os": "x86", + }, + ], + } + return Response(mock_data) + + @swagger_auto_schema( + operation_summary="创建Agent包注册任务", + tags=PACKAGE_MANAGE_VIEW_TAGS, + responses={HTTP_200_OK: pkg_manage.AgentRegisterTaskSerializer}, + ) + @action(detail=False, methods=["POST"], serializer_class=pkg_manage.AgentRegisterSerializer) + def create_register_task(self, request): + mock_data = {"job_id": 1} + return Response(mock_data) + + @swagger_auto_schema( + operation_summary="查询Agent包注册任务", + tags=PACKAGE_MANAGE_VIEW_TAGS, + query_serializer=pkg_manage.AgentRegisterTaskSerializer, + responses={HTTP_200_OK: pkg_manage.AgentRegisterTaskResponseSerializer}, + ) + @action(detail=False, methods=["GET"], serializer_class=pkg_manage.AgentRegisterTaskSerializer) + def query_register_task(self, request): + + mock_data = { + "is_finish": True, + "status": "SUCCESS", + "message": "", + } + return Response(mock_data) + + @swagger_auto_schema( + operation_summary="获取Agent包标签", + tags=PACKAGE_MANAGE_VIEW_TAGS, + responses={HTTP_200_OK: pkg_manage.TagsSerializer(many=True)}, + ) + @action(detail=False, methods=["GET"]) + def tags(self, request): + # 由tag handler 实现 + mock_data = [ + { + "id": "builtin", + "name": "内置标签", + "children": [ + {"id": "stable", "name": "稳定版本", "children": []}, + {"id": "latest", "name": "最新版本", "children": []}, + ], + }, + {"id": "custom", "name": "自定义标签", "children": [{"id": "custom", "name": "自定义版本", "children": []}]}, + ] + return Response(mock_data) + + @swagger_auto_schema( + operation_summary="获取Agent包标签", + tags=PACKAGE_MANAGE_VIEW_TAGS, + responses={HTTP_200_OK: pkg_manage.TagsSerializer(many=True)}, + ) + @action(detail=False, methods=["GET"]) + def get_agent_version(self, request): + pass