Skip to content

Commit

Permalink
feature: 插件调试支持按服务实例维度进行调试 (closed #1344)
Browse files Browse the repository at this point in the history
  • Loading branch information
CohleRustW authored and ZhuoZhuoCrayon committed Apr 25, 2023
1 parent 4e1d136 commit 7eecc0f
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 33 deletions.
24 changes: 21 additions & 3 deletions apps/backend/plugin/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from rest_framework import serializers

from apps.exceptions import BackendValidationError, ValidationError
from apps.node_man import constants
from apps.node_man import constants, models
from apps.node_man.models import DownloadRecord, GsePluginDesc, Packages


Expand Down Expand Up @@ -250,17 +250,35 @@ def validate(self, attrs):
return attrs
raise ValidationError("`bk_host_id` or `ip + bk_cloud_id (Only valid for static host)` required")

class InstanceInfoSerializer(serializers.Serializer):
bk_biz_id = serializers.IntegerField(required=True)
id = serializers.IntegerField(required=False)
bk_inst_id = serializers.CharField(required=False)
bk_obj_id = serializers.CharField(required=False)

plugin_id = serializers.IntegerField(required=False)
plugin_name = serializers.CharField(max_length=32, required=False)
version = serializers.CharField(max_length=32, required=False)
config_ids = serializers.ListField(default=[], allow_empty=True, child=serializers.IntegerField())
host_info = HostInfoSerializer(required=True)
object_type = serializers.ChoiceField(
choices=models.Subscription.OBJECT_TYPE_CHOICES, label="对象类型", default=models.Subscription.ObjectType.HOST
)
node_type = serializers.ChoiceField(
choices=models.Subscription.NODE_TYPE_CHOICES, label="节点类别", default=models.Subscription.NodeType.INSTANCE
)
host_info = HostInfoSerializer(required=False)
instance_info = InstanceInfoSerializer(required=False)

def validate(self, attrs):
# 两种参数模式少要有一种满足
if "id" not in attrs and not ("plugin_name" in attrs and "version") in attrs:
if "plugin_id" not in attrs and not ("plugin_name" in attrs and "version") in attrs:
raise ValidationError("`plugin_id` or `plugin_name + version` required")

if "host_info" in attrs and "instance_info" in attrs:
raise ValidationError("`host_info` and `instance_info` cannot be use at the same time")

if "host_info" not in attrs and "instance_info" not in attrs:
raise ValidationError("`host_info` and `instance_info` must choose one")
return attrs


Expand Down
83 changes: 53 additions & 30 deletions apps/backend/plugin/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import os
import re
import shutil
from typing import Any, Dict, List, Optional, Union

import six
from blueapps.account.decorators import login_exempt
Expand Down Expand Up @@ -45,12 +46,13 @@
)
from apps.backend.subscription.handler import SubscriptionHandler
from apps.backend.subscription.tasks import run_subscription_task_and_create_instance
from apps.backend.subscription.tools import get_service_instance_by_ids
from apps.core.files import core_files_constants
from apps.core.files.storage import get_storage
from apps.exceptions import AppBaseException, ValidationError
from apps.generic import APIViewSet
from apps.node_man import constants, models
from apps.node_man.exceptions import HostNotExists
from apps.node_man.exceptions import HostNotExists, ServiceInstanceNotFoundError
from pipeline.engine.exceptions import InvalidOperationException
from pipeline.service import task_service
from pipeline.service.pipeline_engine_adapter.adapter_api import STATE_MAP
Expand Down Expand Up @@ -546,36 +548,57 @@ def start_debug(self, request):
@apiGroup backend_plugin
"""
params = self.validated_data
host_info = params["host_info"]
plugin_name = params["plugin_name"]
plugin_version = params["version"]
if host_info.get("bk_host_id"):
query_host_params = {"bk_biz_id": host_info["bk_biz_id"], "bk_host_id": host_info["bk_host_id"]}
object_type: str = params["object_type"]
node_type: str = params["node_type"]

host_info: Optional[Dict[str, Union[int, str]]] = params.get("host_info")
instance_info: Optional[Dict[str, Union[int, str]]] = params.get("instance_info")

if host_info:
if host_info.get("bk_host_id"):
query_host_params: Dict[str, Union[str, int]] = {
"bk_biz_id": host_info["bk_biz_id"],
"bk_host_id": host_info["bk_host_id"],
}
else:
# 仅支持静态寻址的主机使用 云区域 + IP
query_host_params: Dict[str, Union[str, int]] = {
"bk_biz_id": host_info["bk_biz_id"],
"inner_ip": host_info["ip"],
"bk_cloud_id": host_info["bk_cloud_id"],
"bk_addressing": constants.CmdbAddressingType.STATIC.value,
}
bk_biz_id: int = host_info["bk_biz_id"]
node: Dict[str, Union[int, str]] = host_info
else:
# 仅支持静态寻址的主机使用 云区域 + IP
query_host_params = {
"bk_biz_id": host_info["bk_biz_id"],
"inner_ip": host_info["ip"],
"bk_cloud_id": host_info["bk_cloud_id"],
"bk_addressing": constants.CmdbAddressingType.STATIC.value,
}
bk_biz_id: int = instance_info["bk_biz_id"]
service_instance_id: Optional[int] = instance_info["id"]
service_instance_result: List[Dict[str, Any]] = get_service_instance_by_ids(
bk_biz_id=instance_info["bk_biz_id"], ids=[service_instance_id]
)
try:
bk_host_id: int = service_instance_result[0]["bk_host_id"]
except Exception:
raise ServiceInstanceNotFoundError(id=service_instance_id)
query_host_params: Dict[str, int] = {"bk_biz_id": bk_biz_id, "bk_host_id": bk_host_id}
node: Dict[str, int] = {"id": service_instance_id}

try:
host = models.Host.objects.get(**query_host_params)
host: models.Host = models.Host.objects.get(**query_host_params)
except models.Host.DoesNotExist:
raise HostNotExists("host does not exist")

plugin_id = params.get("plugin_id")
plugin_id: Optional[int] = params.get("plugin_id")
if plugin_id:
try:
package = models.Packages.objects.get(id=plugin_id)
package: Optional[int] = models.Packages.objects.get(id=plugin_id)
except models.Packages.DoesNotExist:
raise exceptions.PluginNotExistError()
else:
os_type = host.os_type.lower()
cpu_arch = host.cpu_arch
os_type: str = host.os_type.lower()
cpu_arch: str = host.cpu_arch
try:
package = models.Packages.objects.get(
package: models.Packages = models.Packages.objects.get(
project=params["plugin_name"], version=params["version"], os=os_type, cpu_arch=cpu_arch
)
except models.Packages.DoesNotExist:
Expand All @@ -586,10 +609,10 @@ def start_debug(self, request):
if not package.is_ready:
raise ValidationError("plugin is not ready")

configs = models.PluginConfigInstance.objects.in_bulk(params["config_ids"])
configs: Dict[str, Any] = models.PluginConfigInstance.objects.in_bulk(params["config_ids"])

# 渲染配置文件
step_config_templates = []
step_config_templates: List[Dict[str, str]] = []
step_params_context = {}
for config_id in params["config_ids"]:
config = configs.get(config_id)
Expand All @@ -603,11 +626,11 @@ def start_debug(self, request):
step_params_context.update(json.loads(config.render_data))

with transaction.atomic():
subscription = models.Subscription.objects.create(
bk_biz_id=host_info["bk_biz_id"],
object_type=models.Subscription.ObjectType.HOST,
node_type=models.Subscription.NodeType.INSTANCE,
nodes=[host_info],
subscription: models.Subscription = models.Subscription.objects.create(
bk_biz_id=bk_biz_id,
object_type=object_type,
node_type=node_type,
nodes=[node],
enable=False,
is_main=params.get("is_main", False),
creator=request.user.username,
Expand All @@ -617,17 +640,17 @@ def start_debug(self, request):
# 创建订阅步骤
models.SubscriptionStep.objects.create(
subscription_id=subscription.id,
step_id=plugin_name,
step_id=package.project,
type="PLUGIN",
config={
"config_templates": step_config_templates,
"plugin_version": plugin_version,
"plugin_name": plugin_name,
"plugin_version": package.version,
"plugin_name": package.project,
"job_type": "DEBUG_PLUGIN",
},
params={"context": step_params_context},
)
subscription_task = models.SubscriptionTask.objects.create(
subscription_task: models.SubscriptionTask = models.SubscriptionTask.objects.create(
subscription_id=subscription.id, scope=subscription.scope, actions={}
)

Expand Down
117 changes: 117 additions & 0 deletions apps/backend/tests/api/test_debug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# -*- 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.
"""
import typing

import mock

from apps.backend.tests.components.collections.plugin import utils
from apps.node_man import models
from apps.utils.unittest.testcase import CustomAPITestCase


class DebugPluginTestCase(utils.PluginTestObjFactory, CustomAPITestCase):

INSTANCE_ID = 1

def setUp(self):
self.ids: typing.Dict[str, int] = self.init_db()
self.plugin_id: int = models.Packages.objects.get(project=utils.PKG_PROJECT_NAME).id
self.start_debug_path: str = "/backend/api/plugin/start_debug/"
self.host_obj: models.Host = models.Host.objects.get(bk_host_id=self.ids["bk_host_id"])

service_instance_result: typing.List[typing.Dict[str, typing.Union[str, int]]] = [
{
"bk_biz_id": utils.DEFAULT_BIZ_ID_NAME["bk_biz_id"],
"id": self.INSTANCE_ID,
"name": "127.0.0.1_bash",
"labels": "",
"bk_host_id": self.host_obj.bk_host_id,
"creator": "admin",
"modifier": "admin",
"create_time": "2022-12-15T09:57:56.764Z",
"last_time": "2022-12-15T09:57:56.764Z",
"bk_supplier_account": "0",
}
]

mock.patch(
"apps.backend.plugin.views.get_service_instance_by_ids", return_value=service_instance_result
).start()

def test_host_info_debug(self):
base_host_info_data: typing.Dict[str, typing.Union[str, int]] = {
"bk_username": "admin",
"bk_app_code": "bk_nodeman",
"plugin_id": self.plugin_id,
"config_ids": [],
"object_type": models.Subscription.ObjectType.HOST,
"node_type": models.Subscription.NodeType.INSTANCE,
}
host_id_info: typing.Dict[str, typing.Dict[str, typing.Union[str, int]]] = {
"host_info": {
"bk_host_id": self.ids["bk_host_id"],
"bk_supplier_id": 0,
"bk_biz_id": utils.DEFAULT_BIZ_ID_NAME["bk_biz_id"],
}
}

host_id_debug_result: typing.Dict[str, str] = self.client.post(
path=self.start_debug_path,
data={**base_host_info_data, **host_id_info},
)

without_host_id_info: typing.Dict[str, typing.Dict[str, typing.Union[str, int]]] = {
"host_info": {
"ip": self.host_obj.inner_ip,
"bk_cloud_id": self.host_obj.bk_cloud_id,
"bk_supplier_id": 0,
"bk_biz_id": utils.DEFAULT_BIZ_ID_NAME["bk_biz_id"],
}
}

without_host_id_debug_result: typing.Dict[str, str] = self.client.post(
path=self.start_debug_path,
data={**base_host_info_data, **without_host_id_info},
)

self.assertTrue(host_id_debug_result["result"])
self.assertTrue(without_host_id_debug_result["result"])

def test_instance_info_debug(self):
base_instance_info_data: typing.Dict[str, typing.Union[str, int]] = {
"bk_username": "admin",
"bk_app_code": "bk_nodeman",
"plugin_id": self.plugin_id,
"config_ids": [],
"object_type": models.Subscription.ObjectType.SERVICE,
"node_type": models.Subscription.NodeType.INSTANCE,
}

plugin_id_info: typing.Dict[str, int] = {"plugin_id": self.plugin_id}

instance_info: typing.Dict[str, typing.Dict[str, typing.Union[str, int]]] = {
"instance_info": {"bk_biz_id": utils.DEFAULT_BIZ_ID_NAME["bk_biz_id"], "id": self.INSTANCE_ID}
}

instance_debug_result: typing.Dict[str, str] = self.client.post(
path=self.start_debug_path, data={**base_instance_info_data, **instance_info, **plugin_id_info}
)

plugin_info: typing.Dict[str, str] = {
"plugin_name": utils.PKG_PROJECT_NAME,
"version": utils.PKG_INFO["version"],
}

instance_debug_without_plugin_id_result: typing.Dict[str, str] = self.client.post(
path=self.start_debug_path, data={**base_instance_info_data, **instance_info, **plugin_info}
)
self.assertTrue(instance_debug_result["result"])
self.assertTrue(instance_debug_without_plugin_id_result["result"])
6 changes: 6 additions & 0 deletions apps/node_man/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,9 @@ class PluginResourcePolicyNoDiff(NodeManBaseException):
class RemoteHostNotExistsError(NodeManBaseException):
MESSAGE = _("远程采集主机不存在")
ERROR_CODE = 41


class ServiceInstanceNotFoundError(NodeManBaseException):
MESSAGE = _("服务实例不存在")
MESSAGE_TPL = _("服务实例 -> [{id}] 不存在")
ERROR_CODE = 42

0 comments on commit 7eecc0f

Please sign in to comment.