From 36ff92f82816f3e71e162b72b463ba6e019daa30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?v=5Fdcdding=28=E4=B8=81=E8=B6=85=E8=BE=BE=29?= <1151627903@qq.com> Date: Thu, 4 Jan 2024 15:17:25 +0800 Subject: [PATCH] =?UTF-8?q?=E8=8A=82=E7=82=B9=E7=AE=A1=E7=90=86-=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E4=BC=9A:=20=20=E4=B8=BB=E6=9C=BA=E3=80=8C=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=E3=80=8D=E6=8C=89=E9=92=AE=E6=94=AF=E6=8C=81=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E3=80=8C=E7=AE=A1=E6=8E=A7=E5=8C=BA=E5=9F=9F=20ID:IP?= =?UTF-8?q?=E3=80=8D=20(closed=20#1963)=20#=20Reviewed,=20transaction=20id?= =?UTF-8?q?:=203609?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/core/ipchooser/handlers/host_handler.py | 12 +++++ apps/core/ipchooser/tests/test_handlers.py | 48 +++++++++++++++++++ apps/node_man/handlers/host.py | 5 ++ apps/node_man/handlers/plugin.py | 5 ++ apps/node_man/serializers/base.py | 28 ++++++++--- .../node_man/tests/test_handlers/test_host.py | 48 +++++++++++++++++++ .../tests/test_handlers/test_plugin.py | 48 +++++++++++++++++++ apps/node_man/tools/host.py | 35 +++++++++++++- 8 files changed, 222 insertions(+), 7 deletions(-) diff --git a/apps/core/ipchooser/handlers/host_handler.py b/apps/core/ipchooser/handlers/host_handler.py index 78e75c1e1..26117da0b 100644 --- a/apps/core/ipchooser/handlers/host_handler.py +++ b/apps/core/ipchooser/handlers/host_handler.py @@ -66,6 +66,7 @@ def check( bk_host_id_set: typing.Set[int] = set() bk_host_name_set: typing.Set[str] = set() cloud_inner_ip_set: typing.Set[str] = set() + cloud_inner_ipv6_set: typing.Set[str] = set() for ip_or_cloud_ip in ip_list: # 按分隔符切割,获取切割后长度 @@ -74,7 +75,17 @@ def check( if block_num == 1: inner_ip_set.add(ip_or_cloud_ip) else: + if "[" and "]" in ip_or_cloud_ip: + ip_or_cloud_ip = ip_or_cloud_ip.replace("[", "").replace("]", "") cloud_inner_ip_set.add(ip_or_cloud_ip) + for ipv6_or_cloud_ip in ipv6_list: + block_num: int = len(ipv6_or_cloud_ip.split(constants.CommonEnum.SEP.value, 1)) + if block_num == 1: + inner_ip_set.add(ipv6_or_cloud_ip) + else: + if "[" and "]" in ipv6_or_cloud_ip: + ipv6_or_cloud_ip = ipv6_or_cloud_ip.replace("[", "").replace("]", "") + cloud_inner_ipv6_set.add(ipv6_or_cloud_ip) for key in key_list: # 尝试将关键字解析为主机 ID try: @@ -90,6 +101,7 @@ def check( {"key": "bk_host_id", "val": bk_host_id_set}, {"key": "bk_host_name", "val": bk_host_name_set}, {"key": "cloud_inner_ip", "val": cloud_inner_ip_set}, + {"key": "cloud_inner_ipv6", "val": cloud_inner_ipv6_set}, ] return cls.details_base(scope_list, or_conditions, limit_host_ids=limit_host_ids) diff --git a/apps/core/ipchooser/tests/test_handlers.py b/apps/core/ipchooser/tests/test_handlers.py index 29ed269e0..b432972aa 100644 --- a/apps/core/ipchooser/tests/test_handlers.py +++ b/apps/core/ipchooser/tests/test_handlers.py @@ -8,3 +8,51 @@ 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 unittest.mock import patch + +from django.test import TestCase + +from apps.core.ipchooser.handlers.host_handler import HostHandler +from apps.node_man import models +from apps.node_man.tests.utils import MockClient, cmdb_or_cache_biz, create_host + + +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) + def test_check(self): + create_host(1, bk_host_id=1000, ip="127.0.0.1", bk_cloud_id=0) + res = HostHandler.check( + scope_list=[{"scope_type": "biz", "scope_id": f"{i}", "bk_biz_id": i} for i in range(27, 40)], + limit_host_ids=None, + ip_list=["0:[127.0.0.1]"], + ipv6_list=[], + key_list=[], + ) + self.assertEqual(res[0]["ip"], "127.0.0.1") + res = HostHandler.check( + scope_list=[{"scope_type": "biz", "scope_id": f"{i}", "bk_biz_id": i} for i in range(27, 40)], + limit_host_ids=None, + ip_list=["0:127.0.0.1"], + ipv6_list=[], + key_list=[], + ) + self.assertEqual(res[0]["ip"], "127.0.0.1") + create_host(1, bk_host_id=1001, ip="127.0.0.2", bk_cloud_id=0) + models.Host.objects.filter(bk_host_id=1001).update(inner_ipv6="0000:0000:0000:0000:0000:ffff:7f00:0002") + res = HostHandler.check( + scope_list=[{"scope_type": "biz", "scope_id": f"{i}", "bk_biz_id": i} for i in range(27, 40)], + limit_host_ids=None, + ip_list=[], + ipv6_list=["0000:0000:0000:0000:0000:ffff:7f00:0002"], + key_list=[], + ) + self.assertEqual(res[0]["ipv6"], "0000:0000:0000:0000:0000:ffff:7f00:0002") + res = HostHandler.check( + scope_list=[{"scope_type": "biz", "scope_id": f"{i}", "bk_biz_id": i} for i in range(27, 40)], + limit_host_ids=None, + ip_list=[], + ipv6_list=["0:[0000:0000:0000:0000:0000:ffff:7f00:0002]"], + key_list=[], + ) + self.assertEqual(res[0]["ipv6"], "0000:0000:0000:0000:0000:ffff:7f00:0002") diff --git a/apps/node_man/handlers/host.py b/apps/node_man/handlers/host.py index 2bece3e3c..7913b21ba 100644 --- a/apps/node_man/handlers/host.py +++ b/apps/node_man/handlers/host.py @@ -39,6 +39,7 @@ JobTask, ProcessStatus, ) +from apps.node_man.tools.host import HostTools from apps.utils import APIModel from apps.utils.basic import filter_values @@ -143,6 +144,10 @@ def list(self, params: dict, username: str): # 计算总数 hosts_status_count = hosts_status_sql.count() + if params.get("cloud_id_ip", None): + result = HostTools.export_all_cloud_area_colon_ip(params["cloud_id_ip"], hosts_status_sql) + return {"total": len(result), "list": result} + if params["only_ip"] is False: host_fields = core_ipchooser_constants.CommonEnum.DEFAULT_HOST_FIELDS.value + [ "bk_addressing", diff --git a/apps/node_man/handlers/plugin.py b/apps/node_man/handlers/plugin.py index a35558fc0..92745dd94 100644 --- a/apps/node_man/handlers/plugin.py +++ b/apps/node_man/handlers/plugin.py @@ -37,6 +37,7 @@ Subscription, SubscriptionTask, ) +from apps.node_man.tools.host import HostTools from apps.utils import APIModel from common.api import NodeApi @@ -215,6 +216,10 @@ def list(params: Dict[str, Any]): # 非法查询返回空列表 return {"total": 0, "list": []} + if params.get("cloud_id_ip", None): + result = HostTools.export_all_cloud_area_colon_ip(params["cloud_id_ip"], hosts_status_sql) + return {"total": len(result), "list": result} + if params.get("simple"): host_simples = list(hosts_status_sql[begin:end].values("bk_host_id", "bk_biz_id")) return {"total": len(host_simples), "list": host_simples} diff --git a/apps/node_man/serializers/base.py b/apps/node_man/serializers/base.py index 6faff445d..433868587 100644 --- a/apps/node_man/serializers/base.py +++ b/apps/node_man/serializers/base.py @@ -17,11 +17,7 @@ # 放在后台会导致循坏导入 class SubScopeInstSelectorSerializer(serializers.Serializer): - instance_selector = serializers.ListField( - child=serializers.DictField(), - required=False, - label="实例筛选器" - ) + instance_selector = serializers.ListField(child=serializers.DictField(), required=False, label="实例筛选器") # 安装插件配置 @@ -100,10 +96,30 @@ class PaginationSerializer(serializers.Serializer): class HostFieldSelectorSerializer(serializers.Serializer): only_ip = serializers.BooleanField(label=_("只返回IP"), required=False, default=False) + cloud_id_ip = serializers.DictField(label=_("只返回管控区域:IP"), required=False, default={}) return_field = serializers.ChoiceField( - label=_("仅返回的字段"), required=False, default="inner_ip", choices=["inner_ip", "inner_ipv6"] + label=_("仅返回的字段"), + required=False, + default="inner_ip", + choices=[ + "inner_ip", + "inner_ipv6", + "cloud_ipv4", + "cloud_ipv4_with_brackets", + "cloud_ipv6_with_brackets", + ], ) + def validate_cloud_id_ip(self, value): + if not isinstance(value, dict): + raise serializers.ValidationError(_("传入的值必须是字典类型")) + + for key, val in value.items(): + if not isinstance(key, str) or not isinstance(val, bool): + raise serializers.ValidationError(_("字典中的键必须是字符串类型,值必须是布尔类型")) + + return value + class HostSearchSerializer(PaginationSerializer): bk_biz_id = serializers.ListField(label=_("业务ID"), required=False, child=serializers.IntegerField()) diff --git a/apps/node_man/tests/test_handlers/test_host.py b/apps/node_man/tests/test_handlers/test_host.py index 229dd4e80..c3acd148f 100644 --- a/apps/node_man/tests/test_handlers/test_host.py +++ b/apps/node_man/tests/test_handlers/test_host.py @@ -618,3 +618,51 @@ def test_wrong_case_about_bt_node_detection(self): self.assertRegex(host_ip["inner_ip"], IP_REG) self.assertEqual(len(hosts["list"]), 0) self.assertLessEqual(len(hosts["list"]), page_size) + + @patch("apps.node_man.handlers.cmdb.CmdbHandler.cmdb_or_cache_biz", cmdb_or_cache_biz) + @patch("apps.node_man.handlers.cmdb.client_v2", MockClient) + def test_export_cloud_area_colon_ip(self): + number = 10 + create_host(number) + params = { + "pagesize": -1, + "only_ip": False, + "return_field": "cloud_ipv4", + "conditions": [], + "cloud_id_ip": {"ipv4": True}, + } + res = HostHandler().list(params, "admin") + self.assertLessEqual(len(res["list"]), 10) + + params = { + "pagesize": -1, + "only_ip": False, + "return_field": "cloud_ipv4_with_brackets", + "conditions": [], + "cloud_id_ip": {"ipv4_with_brackets": True}, + } + res = HostHandler().list(params, "admin") + self.assertLessEqual(len(res["list"]), 10) + + create_host(1, bk_host_id=43420, ip="127.0.0.1") + Host.objects.filter(bk_host_id=43420).update(inner_ipv6="0000:0000:0000:0000:0000:ffff:7f00:0001") + params = { + "pagesize": -1, + "only_ip": False, + "return_field": "cloud_ipv6_with_brackets", + "conditions": [], + "cloud_id_ip": {"ipv6": True}, + } + res = HostHandler().list(params, "admin") + self.assertLessEqual(len(res["list"]), 1) + + # 验证参数为False的情况 + params = { + "pagesize": -1, + "only_ip": False, + "return_field": "cloud_ipv4", + "conditions": [], + "cloud_id_ip": {"ipv4": False}, + } + res = HostHandler().list(params, "admin") + self.assertEqual(len(res["list"]), 0) diff --git a/apps/node_man/tests/test_handlers/test_plugin.py b/apps/node_man/tests/test_handlers/test_plugin.py index a14834bb3..cb76e9bfc 100644 --- a/apps/node_man/tests/test_handlers/test_plugin.py +++ b/apps/node_man/tests/test_handlers/test_plugin.py @@ -382,3 +382,51 @@ def test_setup_path_using_invalid_ap(self): for host in hosts["list"]: self.assertIn(host["setup_path"], ["/usr/local/gse", "c:\\gse"]) + + @patch("apps.node_man.handlers.cmdb.CmdbHandler.cmdb_or_cache_biz", cmdb_or_cache_biz) + @patch("apps.node_man.handlers.cmdb.client_v2", MockClient) + def test_export_cloud_area_colon_ip(self): + number = 10 + create_host(number) + params = { + "pagesize": -1, + "only_ip": False, + "return_field": "cloud_ipv4", + "conditions": [], + "cloud_id_ip": {"ipv4": True}, + } + res = PluginHandler.list(params) + self.assertLessEqual(len(res["list"]), 10) + + params = { + "pagesize": -1, + "only_ip": False, + "return_field": "cloud_ipv4_with_brackets", + "conditions": [], + "cloud_id_ip": {"ipv4_with_brackets": True}, + } + res = PluginHandler.list(params) + self.assertLessEqual(len(res["list"]), 10) + + create_host(1, bk_host_id=43420, ip="127.0.0.1") + Host.objects.filter(bk_host_id=43420).update(inner_ipv6="0000:0000:0000:0000:0000:ffff:7f00:0001") + params = { + "pagesize": -1, + "only_ip": False, + "return_field": "cloud_ipv6_with_brackets", + "conditions": [], + "cloud_id_ip": {"ipv6": True}, + } + res = PluginHandler.list(params) + self.assertLessEqual(len(res["list"]), 1) + + # 验证参数为False的情况 + params = { + "pagesize": -1, + "only_ip": False, + "return_field": "cloud_ipv4", + "conditions": [], + "cloud_id_ip": {"ipv4": False}, + } + res = PluginHandler.list(params) + self.assertEqual(len(res["list"]), 0) diff --git a/apps/node_man/tools/host.py b/apps/node_man/tools/host.py index c3a32913e..b63d28f40 100644 --- a/apps/node_man/tools/host.py +++ b/apps/node_man/tools/host.py @@ -9,9 +9,10 @@ specific language governing permissions and limitations under the License. """ import base64 -from typing import Type +from typing import Dict, List, Type from bkcrypto.asymmetric.ciphers import BaseAsymmetricCipher +from django.db.models import QuerySet from django.utils.translation import ugettext_lazy as _ from apps.core.encrypt import constants as core_encrypt_constants @@ -62,3 +63,35 @@ def decrypt_with_friendly_exc_handle( return cls.decrypt_with_friendly_exc_handle( cipher=cipher, encrypt_message=decrypt_message, raise_exec=raise_exec ) + + @staticmethod + def export_all_cloud_area_colon_ip(cloud_id_ip_type: Dict, hosts_status_sql: QuerySet) -> List: + """ + 获取管控区域+IP的组合 + :param cloud_id_ip_type:云区域+IP参数类型 + :param hosts_status_sql: + :return: + """ + cloud_id_and_inner_ip_qs: QuerySet = hosts_status_sql.values("bk_cloud_id", "inner_ip") + cloud_id_and_inner_ipv6_qs: QuerySet = hosts_status_sql.values("bk_cloud_id", "inner_ipv6") + if cloud_id_ip_type.get("ipv4", None): + result: List = [ + f'{cloud_id_and_inner_ip_dict["bk_cloud_id"]}:{cloud_id_and_inner_ip_dict["inner_ip"]}' + for cloud_id_and_inner_ip_dict in cloud_id_and_inner_ip_qs + if cloud_id_and_inner_ip_dict["bk_cloud_id"] != "" and cloud_id_and_inner_ip_dict["inner_ip"] + ] + elif cloud_id_ip_type.get("ipv6", None): + result: List = [ + f'{cloud_id_and_inner_ipv6_dict["bk_cloud_id"]}:[{cloud_id_and_inner_ipv6_dict["inner_ipv6"]}]' + for cloud_id_and_inner_ipv6_dict in cloud_id_and_inner_ipv6_qs + if cloud_id_and_inner_ipv6_dict["bk_cloud_id"] != "" and cloud_id_and_inner_ipv6_dict["inner_ipv6"] + ] + elif cloud_id_ip_type.get("ipv4_with_brackets", None): + result: List = [ + f'{cloud_id_and_inner_ip_dict["bk_cloud_id"]}:[{cloud_id_and_inner_ip_dict["inner_ip"]}]' + for cloud_id_and_inner_ip_dict in cloud_id_and_inner_ip_qs + if cloud_id_and_inner_ip_dict["bk_cloud_id"] != "" and cloud_id_and_inner_ip_dict["inner_ip"] + ] + else: + result = [] + return result