diff --git a/apps/core/ipchooser/constants.py b/apps/core/ipchooser/constants.py index 1d2d85b19..0c7d845c0 100644 --- a/apps/core/ipchooser/constants.py +++ b/apps/core/ipchooser/constants.py @@ -30,6 +30,7 @@ class CommonEnum(EnhanceEnum): "bk_host_name", "os_type", "status", + "ap_id", ] @classmethod @@ -84,3 +85,14 @@ class HostBTNodeDetectionConditionValue(EnhanceEnum): @classmethod def _get_member__alias_map(cls) -> Dict[Enum, str]: return {cls.ENABLE: _("启用"), cls.DISABLE: _("停用")} + + +class GseAgentStatusType(EnhanceEnum): + """对外展示的 Agent 状态""" + + RUNNING = 1 + TERMINATED = 0 + + @classmethod + def _get_member__alias_map(cls) -> Dict[Enum, str]: + return {cls.RUNNING: _("正常"), cls.TERMINATED: _("异常")} diff --git a/apps/core/ipchooser/handlers/host_handler.py b/apps/core/ipchooser/handlers/host_handler.py index 26117da0b..437b2e5d3 100644 --- a/apps/core/ipchooser/handlers/host_handler.py +++ b/apps/core/ipchooser/handlers/host_handler.py @@ -9,9 +9,14 @@ specific language governing permissions and limitations under the License. """ import typing +from collections import defaultdict from django_mysql.models import QuerySet +from apps.adapters.api.gse import get_gse_api_helper +from apps.core.gray.tools import GrayTools +from apps.node_man.models import GlobalSettings + from .. import constants, types from ..tools import base from .base import BaseHandler @@ -23,12 +28,14 @@ def details_base( scope_list: types.ScopeList, or_conditions: typing.List[types.Condition], limit_host_ids: typing.Optional[typing.List[int]] = None, + show_agent_realtime_state: bool = False, ) -> typing.List[types.FormatHostInfo]: """ 获取主机详情 :param scope_list: 资源范围数组 :param or_conditions: 逻辑或查询条件 :param limit_host_ids: 限制检索的主机 ID 列表 + :param show_agent_realtime_state: 是否展示Agent实时状态 :return: """ host_queryset: QuerySet = base.HostQuerySqlHelper.multiple_cond_sql( @@ -40,6 +47,40 @@ def details_base( # 获取主机信息 host_fields: typing.List[str] = constants.CommonEnum.DEFAULT_HOST_FIELDS.value untreated_host_infos: typing.List[types.HostInfo] = list(host_queryset.values(*host_fields)) + + if show_agent_realtime_state: + enable = GlobalSettings.get_config( + key=GlobalSettings.KeyEnum.IP_CHOOSER_ENABLE_SHOW_REALTIME_AGENT_STATE.value, default=False + ) + if not enable: + return BaseHandler.format_hosts(untreated_host_infos) + agent_id__host_id_map: typing.Dict[str, int] = {} + host_id__agent_state_code_map: typing.Dict[int, int] = {} + agent_id__agent_state_info_map: typing.Dict[str, typing.Dict] = {} + gray_tools_instance: GrayTools = GrayTools() + gse_version__hosts_info_map: typing.Dict[str, typing.List[typing.Dict]] = defaultdict(list) + for host_info in untreated_host_infos: + gse_version = gray_tools_instance.get_host_ap_gse_version(host_info["bk_biz_id"], host_info["ap_id"]) + agent_id = get_gse_api_helper(gse_version).get_agent_id(host_info) + agent_id__host_id_map[agent_id] = host_info["bk_host_id"] + gse_version__hosts_info_map[gse_version].append( + { + "bk_agent_id": host_info["bk_agent_id"], + "ip": host_info["inner_ip"] or host_info["inner_ipv6"], + "bk_cloud_id": host_info["bk_cloud_id"], + } + ) + for gse_version, hosts_info in gse_version__hosts_info_map.items(): + gse_api_helper = get_gse_api_helper(gse_version) + agent_id__agent_state_info_map.update(gse_api_helper.list_agent_state(hosts_info)) + for agent_id, agent_state_info in agent_id__agent_state_info_map.items(): + host_id__agent_state_code_map.update( + {agent_id__host_id_map[agent_id]: agent_state_info["bk_agent_alive"]} + ) + for host in untreated_host_infos: + bk_host_id = host["bk_host_id"] + host["status"] = constants.GseAgentStatusType(host_id__agent_state_code_map[bk_host_id]).name + return BaseHandler.format_hosts(untreated_host_infos) @classmethod @@ -107,12 +148,13 @@ def check( @classmethod def details( - cls, scope_list: types.ScopeList, host_list: typing.List[types.FormatHostInfo] + cls, scope_list: types.ScopeList, host_list: typing.List[types.FormatHostInfo], show_agent_realtime_state: bool ) -> typing.List[types.FormatHostInfo]: """ 根据主机关键信息获取机器详情信息 :param scope_list: 资源范围数组 :param host_list: 主机关键信息列表 + :param show_agent_realtime_state: 是否展示Agent实时状态 :return: """ bk_host_id_set: typing.Set[int] = set() @@ -129,4 +171,4 @@ def details( {"key": "bk_host_id", "val": bk_host_id_set}, {"key": "cloud_inner_ip", "val": cloud_inner_ip_set}, ] - return cls.details_base(scope_list, or_conditions) + return cls.details_base(scope_list, or_conditions, show_agent_realtime_state=show_agent_realtime_state) diff --git a/apps/core/ipchooser/serializers/host_sers.py b/apps/core/ipchooser/serializers/host_sers.py index 92c4cddff..4f83b6afc 100644 --- a/apps/core/ipchooser/serializers/host_sers.py +++ b/apps/core/ipchooser/serializers/host_sers.py @@ -47,6 +47,7 @@ class Meta: class HostDetailsRequestSer(base.ScopeSelectorBaseSer): host_list = serializers.ListField(child=base.HostInfoWithMetaSer(), default=[]) + agent_realtime_state = serializers.BooleanField(label=_("agent实时状态"), required=False, default=False) class Meta: swagger_schema_fields = {"example": mock_data.API_HOST_DETAILS_REQUEST} diff --git a/apps/core/ipchooser/tests/test_handlers.py b/apps/core/ipchooser/tests/test_handlers.py index b432972aa..6d60b29b0 100644 --- a/apps/core/ipchooser/tests/test_handlers.py +++ b/apps/core/ipchooser/tests/test_handlers.py @@ -13,13 +13,15 @@ from django.test import TestCase from apps.core.ipchooser.handlers.host_handler import HostHandler +from apps.mock_data.api_mkd.gse.utils import GseApiMockClient, get_gse_api_helper from apps.node_man import models from apps.node_man.tests.utils import MockClient, cmdb_or_cache_biz, create_host +from env.constants import GseVersion class TestHostHandler(TestCase): @patch("apps.node_man.handlers.cmdb.CmdbHandler.cmdb_or_cache_biz", cmdb_or_cache_biz) - @patch("apps.node_man.handlers.cmdb.client_v2", MockClient) + @patch("apps.core.ipchooser.query.resource.CCApi", MockClient.cc) def test_check(self): create_host(1, bk_host_id=1000, ip="127.0.0.1", bk_cloud_id=0) res = HostHandler.check( @@ -56,3 +58,21 @@ def test_check(self): key_list=[], ) self.assertEqual(res[0]["ipv6"], "0000:0000:0000:0000:0000:ffff:7f00:0002") + + @patch("apps.core.ipchooser.query.resource.CCApi", MockClient.cc) + @patch( + "apps.core.ipchooser.handlers.host_handler.get_gse_api_helper", + get_gse_api_helper(GseVersion.V2.value, GseApiMockClient()), + ) + def test_details(self): + models.GlobalSettings.set_config( + key=models.GlobalSettings.KeyEnum.IP_CHOOSER_ENABLE_SHOW_REALTIME_AGENT_STATE.value, value=True + ) + create_host(100) + res = HostHandler().details( + scope_list=[{"scope_type": "biz", "scope_id": f"{i}", "bk_biz_id": i} for i in range(27, 40)], + host_list=[{"host_id": 1, "meta": {"scope_type": "biz", "scope_id": "28", "bk_biz_id": 28}}], + show_agent_realtime_state=True, + ) + self.assertEqual(res[0]["alive"], 1) + self.assertEqual(res[0]["bk_agent_alive"], 1) diff --git a/apps/core/ipchooser/views.py b/apps/core/ipchooser/views.py index 4486bdb6f..0e08558e0 100644 --- a/apps/core/ipchooser/views.py +++ b/apps/core/ipchooser/views.py @@ -128,6 +128,8 @@ def check(self, request, *args, **kwargs): def details(self, request, *args, **kwargs): return Response( host_handler.HostHandler.details( - scope_list=self.validated_data["scope_list"], host_list=self.validated_data["host_list"] + scope_list=self.validated_data["scope_list"], + host_list=self.validated_data["host_list"], + show_agent_realtime_state=self.validated_data["agent_realtime_state"], ) ) diff --git a/apps/node_man/models.py b/apps/node_man/models.py index 5c05c78e9..81c53f002 100644 --- a/apps/node_man/models.py +++ b/apps/node_man/models.py @@ -162,6 +162,8 @@ class KeyEnum(Enum): PLUGIN_PROC_START_CHECK_SECS = "PLUGIN_PROC_START_CHECK_SECS" # 查询服务实例时module_id阈值,当小于该阈值时以单module_id并发查询 SERVICE_INSTANCE_MODULE_ID_THRESHOLD = "SERVICE_INSTANCE_MODULE_ID_THRESHOLD" + # IP选择器详情接口是否实时展示agent状态 + IP_CHOOSER_ENABLE_SHOW_REALTIME_AGENT_STATE = "IP_CHOOSER_ENABLE_SHOW_REALTIME_AGENT_STATE" key = models.CharField(_("键"), max_length=255, db_index=True, primary_key=True) v_json = JSONField(_("值"))