diff --git a/apps/backend/components/collections/agent_new/configure_policy.py b/apps/backend/components/collections/agent_new/configure_policy.py index b76fecc86..b8384216b 100644 --- a/apps/backend/components/collections/agent_new/configure_policy.py +++ b/apps/backend/components/collections/agent_new/configure_policy.py @@ -34,7 +34,9 @@ def _execute(self, data, parent_data, common_data): security_group_factory = get_security_group_factory(security_group_type) ip_list = [] for host in host_id_obj_map.values(): - ip_list.extend([host.outer_ip, host.login_ip]) + outer_ip = host.outer_ip.split(",") + ip_list.extend(outer_ip) + ip_list.append(host.login_ip) # 不同的安全组工厂添加策略后得到的输出可能是不同的,输出到outputs中,在schedule中由工厂对应的check_result方法来校验结果 creator: str = common_data.subscription.creator data.outputs.add_ip_output = security_group_factory.add_ips_to_security_group(ip_list, creator=creator) diff --git a/apps/backend/tests/components/collections/agent_new/test_add_or_update_hosts.py b/apps/backend/tests/components/collections/agent_new/test_add_or_update_hosts.py index 4ef65490e..20ea6ac7d 100644 --- a/apps/backend/tests/components/collections/agent_new/test_add_or_update_hosts.py +++ b/apps/backend/tests/components/collections/agent_new/test_add_or_update_hosts.py @@ -348,3 +348,30 @@ class UpdateOldWhenIncludeLoginInParamTestCase(AddOrUpdateHostsTestCase): def assert_in_teardown(self): super().assert_in_teardown() self.assertEqual(models.Host.objects.filter(login_ip="").count(), 0) + + +class MultiOuterIpHostsTestCase(AddOrUpdateHostsTestCase): + @classmethod + def adjust_test_data_in_db(cls): + super().adjust_test_data_in_db() + + # 多外网IP情况 + for sub_inst_obj in cls.obj_factory.sub_inst_record_objs: + sub_inst_obj.instance_info["host"]["bk_host_outerip"] += ",1.2.3.4" + models.SubscriptionInstanceRecord.objects.bulk_update( + cls.obj_factory.sub_inst_record_objs, fields=["instance_info"] + ) + + def assert_in_teardown(self): + # 更新主机情况 + for host_info in self.cmdb_mock_client.batch_update_host.call_args[0][0]["update"]: + bk_host_outerip = host_info["properties"]["bk_host_outerip"] + outer_ip = bk_host_outerip.split(",") + self.assertEqual(len(outer_ip), 2) + # 新增主机情况 + for bk_host_info in self.cmdb_mock_client.add_host_to_business_idle.call_args[0][0]["bk_host_list"]: + bk_host_outerip = bk_host_info["bk_host_outerip"] + outer_ip = bk_host_outerip.split(",") + self.assertEqual(len(outer_ip), 2) + + super().assert_in_teardown() diff --git a/apps/backend/tests/components/collections/agent_new/test_configure_policy.py b/apps/backend/tests/components/collections/agent_new/test_configure_policy.py index 629bd88f7..e31d83a1c 100644 --- a/apps/backend/tests/components/collections/agent_new/test_configure_policy.py +++ b/apps/backend/tests/components/collections/agent_new/test_configure_policy.py @@ -303,3 +303,36 @@ def cases(self): execute_call_assertion=None, ), ] + + +class YunTiConfigureMultiOuterIpPolicyTestCase(YunTiConfigurePolicyComponentBaseTest): + def cases(self): + outer_ip = self.obj_factory.host_objs[0].outer_ip + login_ip = self.obj_factory.host_objs[0].login_ip + return [ + ComponentTestCase( + name="通过云梯配置多外网IP策略", + inputs=self.common_inputs, + parent_data={}, + execute_assertion=ExecuteAssertion( + success=bool(self.common_inputs["subscription_instance_ids"]), + outputs={ + "add_ip_output": {"ip_list": [outer_ip, login_ip]}, + "polling_time": 0, + "succeeded_subscription_instance_ids": self.common_inputs["subscription_instance_ids"], + }, + ), + schedule_assertion=[ + ScheduleAssertion( + success=True, + schedule_finished=True, + outputs={ + "add_ip_output": {"ip_list": [outer_ip, login_ip]}, + "polling_time": 0, + "succeeded_subscription_instance_ids": self.common_inputs["subscription_instance_ids"], + }, + ), + ], + execute_call_assertion=None, + ), + ] diff --git a/apps/node_man/migrations/0082_alter_host_outer_ip.py b/apps/node_man/migrations/0082_alter_host_outer_ip.py new file mode 100644 index 000000000..f7f66f41d --- /dev/null +++ b/apps/node_man/migrations/0082_alter_host_outer_ip.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.4 on 2024-06-05 06:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("node_man", "0081_auto_20240307_1656"), + ] + + operations = [ + migrations.AlterField( + model_name="host", + name="outer_ip", + field=models.CharField( + blank=True, db_index=True, default="", max_length=255, null=True, verbose_name="外网IP" + ), + ), + ] diff --git a/apps/node_man/models.py b/apps/node_man/models.py index 430a1671e..e08f932ce 100644 --- a/apps/node_man/models.py +++ b/apps/node_man/models.py @@ -235,7 +235,7 @@ class Host(models.Model): bk_addressing = models.CharField(_("寻址方式"), max_length=16, default=constants.CmdbAddressingType.STATIC.value) inner_ip = models.CharField(_("内网IP"), max_length=15, db_index=True) - outer_ip = models.CharField(_("外网IP"), max_length=15, db_index=True, blank=True, null=True, default="") + outer_ip = models.CharField(_("外网IP"), max_length=255, db_index=True, blank=True, null=True, default="") login_ip = models.CharField(_("登录IP"), max_length=45, db_index=True, blank=True, null=True, default="") data_ip = models.CharField(_("数据IP"), max_length=45, db_index=True, blank=True, null=True, default="") diff --git a/apps/node_man/serializers/job.py b/apps/node_man/serializers/job.py index 0287e12ba..37db9f40e 100644 --- a/apps/node_man/serializers/job.py +++ b/apps/node_man/serializers/job.py @@ -10,6 +10,7 @@ """ import typing from collections import defaultdict +from ipaddress import IPv4Address from django.conf import settings from django.db.models import Q @@ -171,7 +172,7 @@ class HostSerializer(InstallBaseSerializer): ap_id = serializers.IntegerField(label=_("接入点ID"), required=False) install_channel_id = serializers.IntegerField(label=_("安装通道ID"), required=False, allow_null=True) inner_ip = serializers.IPAddressField(label=_("内网IP"), required=False, allow_blank=True, protocol="ipv4") - outer_ip = serializers.IPAddressField(label=_("外网IP"), required=False, allow_blank=True, protocol="ipv4") + outer_ip = serializers.CharField(label=_("外网IP"), required=False, allow_blank=True) login_ip = serializers.IPAddressField(label=_("登录IP"), required=False, allow_blank=True, protocol="both") data_ip = serializers.IPAddressField(label=_("数据IP"), required=False, allow_blank=True, protocol="both") inner_ipv6 = serializers.IPAddressField(label=_("内网IPv6"), required=False, allow_blank=True, protocol="ipv6") @@ -203,6 +204,12 @@ def validate(self, attrs): raise ValidationError(_("请求参数 inner_ip 和 inner_ipv6 不能同时为空")) if node_type == constants.NodeType.PROXY and not (attrs.get("outer_ip") or attrs.get("outer_ipv6")): raise ValidationError(_("Proxy 操作的请求参数 outer_ip 和 outer_ipv6 不能同时为空")) + if not attrs.get("outer_ipv6"): + outer_ips = attrs.get("outer_ip").split(",") + try: + [IPv4Address(outer_ip) for outer_ip in outer_ips] + except ValueError: + raise ValidationError(_("Proxy 操作的请求参数 outer_ip:请输入一个合法的IPv4地址")) basic.ipv6_formatter(data=attrs, ipv6_field_names=["inner_ipv6", "outer_ipv6", "login_ip", "data_ip"])