Explorar el Código

Closes #22174: Include DNS names for primary/OOB IPs and NAT data for VDCs (#22553)

Martin Hauser hace 1 semana
padre
commit
021b7c5f5b

+ 22 - 7
netbox/dcim/api/serializers_/devices.py

@@ -62,25 +62,25 @@ class DeviceSerializer(PrimaryModelSerializer):
         nested=True,
         nested=True,
         read_only=True,
         read_only=True,
         allow_null=True,
         allow_null=True,
-        fields=[*IPAddressSerializer.Meta.brief_fields, 'nat_inside', 'nat_outside'],
+        fields=[*IPAddressSerializer.Meta.brief_fields, 'dns_name', 'nat_inside', 'nat_outside'],
     )
     )
     primary_ip4 = IPAddressSerializer(
     primary_ip4 = IPAddressSerializer(
         nested=True,
         nested=True,
         required=False,
         required=False,
         allow_null=True,
         allow_null=True,
-        fields=[*IPAddressSerializer.Meta.brief_fields, 'nat_inside', 'nat_outside'],
+        fields=[*IPAddressSerializer.Meta.brief_fields, 'dns_name', 'nat_inside', 'nat_outside'],
     )
     )
     primary_ip6 = IPAddressSerializer(
     primary_ip6 = IPAddressSerializer(
         nested=True,
         nested=True,
         required=False,
         required=False,
         allow_null=True,
         allow_null=True,
-        fields=[*IPAddressSerializer.Meta.brief_fields, 'nat_inside', 'nat_outside'],
+        fields=[*IPAddressSerializer.Meta.brief_fields, 'dns_name', 'nat_inside', 'nat_outside'],
     )
     )
     oob_ip = IPAddressSerializer(
     oob_ip = IPAddressSerializer(
         nested=True,
         nested=True,
         required=False,
         required=False,
         allow_null=True,
         allow_null=True,
-        fields=[*IPAddressSerializer.Meta.brief_fields, 'nat_inside', 'nat_outside'],
+        fields=[*IPAddressSerializer.Meta.brief_fields, 'dns_name', 'nat_inside', 'nat_outside'],
     )
     )
     parent_device = serializers.SerializerMethodField()
     parent_device = serializers.SerializerMethodField()
     cluster = ClusterSerializer(nested=True, required=False, allow_null=True)
     cluster = ClusterSerializer(nested=True, required=False, allow_null=True)
@@ -148,9 +148,24 @@ class VirtualDeviceContextSerializer(PrimaryModelSerializer):
     device = DeviceSerializer(nested=True)
     device = DeviceSerializer(nested=True)
     identifier = serializers.IntegerField(allow_null=True, max_value=32767, min_value=0, required=False, default=None)
     identifier = serializers.IntegerField(allow_null=True, max_value=32767, min_value=0, required=False, default=None)
     tenant = TenantSerializer(nested=True, required=False, allow_null=True, default=None)
     tenant = TenantSerializer(nested=True, required=False, allow_null=True, default=None)
-    primary_ip = IPAddressSerializer(nested=True, read_only=True, allow_null=True)
-    primary_ip4 = IPAddressSerializer(nested=True, required=False, allow_null=True)
-    primary_ip6 = IPAddressSerializer(nested=True, required=False, allow_null=True)
+    primary_ip = IPAddressSerializer(
+        nested=True,
+        read_only=True,
+        allow_null=True,
+        fields=[*IPAddressSerializer.Meta.brief_fields, 'dns_name', 'nat_inside', 'nat_outside'],
+    )
+    primary_ip4 = IPAddressSerializer(
+        nested=True,
+        required=False,
+        allow_null=True,
+        fields=[*IPAddressSerializer.Meta.brief_fields, 'dns_name', 'nat_inside', 'nat_outside'],
+    )
+    primary_ip6 = IPAddressSerializer(
+        nested=True,
+        required=False,
+        allow_null=True,
+        fields=[*IPAddressSerializer.Meta.brief_fields, 'dns_name', 'nat_inside', 'nat_outside'],
+    )
     status = ChoiceField(choices=VirtualDeviceContextStatusChoices)
     status = ChoiceField(choices=VirtualDeviceContextStatusChoices)
 
 
     # Related object counts
     # Related object counts

+ 122 - 1
netbox/dcim/tests/test_api.py

@@ -12,7 +12,7 @@ from dcim.constants import *
 from dcim.models import *
 from dcim.models import *
 from extras.models import ConfigTemplate, Tag
 from extras.models import ConfigTemplate, Tag
 from ipam.choices import VLANQinQRoleChoices
 from ipam.choices import VLANQinQRoleChoices
-from ipam.models import ASN, RIR, VLAN, VRF
+from ipam.models import ASN, RIR, VLAN, VRF, IPAddress
 from netbox.api.serializers import GenericObjectSerializer
 from netbox.api.serializers import GenericObjectSerializer
 from tenancy.models import Tenant
 from tenancy.models import Tenant
 from users.constants import TOKEN_PREFIX
 from users.constants import TOKEN_PREFIX
@@ -2081,6 +2081,58 @@ class DeviceTestCase(APIViewTestCases.APIViewTestCase):
         self.assertEqual(response.data['oob_ip']['nat_inside']['address'], str(real_ip.address))
         self.assertEqual(response.data['oob_ip']['nat_inside']['address'], str(real_ip.address))
         self.assertEqual(response.data['oob_ip']['nat_outside'], [])
         self.assertEqual(response.data['oob_ip']['nat_outside'], [])
 
 
+    def test_get_object_includes_dns_name_on_primary_ip(self):
+        device = create_test_device('dns-device')
+        interfaces = (
+            Interface.objects.create(device=device, name='eth0', type='other'),
+            Interface.objects.create(device=device, name='eth1', type='other'),
+        )
+
+        ip4 = IPAddress(address='192.0.2.10/32', dns_name='device4.example.com')
+        ip4.assigned_object = interfaces[0]
+        ip4.save()
+        ip6 = IPAddress(address='2001:db8::10/128', dns_name='device6.example.com')
+        ip6.assigned_object = interfaces[1]
+        ip6.save()
+
+        device.primary_ip4 = ip4
+        device.primary_ip6 = ip6
+        device.save()
+
+        self.add_permissions('dcim.view_device', 'ipam.view_ipaddress')
+        response = self.client.get(
+            f'{self._get_detail_url(device)}?exclude=config_context',
+            **self.header,
+        )
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+
+        self.assertEqual(response.data['primary_ip4']['dns_name'], 'device4.example.com')
+        self.assertEqual(response.data['primary_ip6']['dns_name'], 'device6.example.com')
+        self.assertIn(
+            response.data['primary_ip']['dns_name'],
+            ('device4.example.com', 'device6.example.com'),
+        )
+
+    def test_get_object_includes_dns_name_on_oob_ip(self):
+        device = create_test_device('dns-oob-device')
+        interface = Interface.objects.create(device=device, name='oob0', type='other')
+
+        ip = IPAddress(address='192.0.2.20/32', dns_name='oob.example.com')
+        ip.assigned_object = interface
+        ip.save()
+
+        device.oob_ip = ip
+        device.save()
+
+        self.add_permissions('dcim.view_device', 'ipam.view_ipaddress')
+        response = self.client.get(
+            f'{self._get_detail_url(device)}?exclude=config_context',
+            **self.header,
+        )
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+
+        self.assertEqual(response.data['oob_ip']['dns_name'], 'oob.example.com')
+
     def test_render_config_with_config_template_id(self):
     def test_render_config_with_config_template_id(self):
         default_template = ConfigTemplate.objects.create(
         default_template = ConfigTemplate.objects.create(
             name='Default Template',
             name='Default Template',
@@ -3848,6 +3900,75 @@ class VirtualDeviceContextTestCase(APIViewTestCases.APIViewTestCase):
             },
             },
         ]
         ]
 
 
+    def test_get_object_includes_nat_on_primary_ip(self):
+        device = create_test_device('vdc-nat-device')
+        interfaces = (
+            Interface.objects.create(device=device, name='eth0', type='other'),
+            Interface.objects.create(device=device, name='eth1', type='other'),
+        )
+
+        real_ip4, nat_ip4 = create_test_nat_ip_pair(
+            real_address='10.0.2.10/32',
+            nat_address='198.51.100.30/32',
+            inside_interface=interfaces[0],
+        )
+        real_ip6, nat_ip6 = create_test_nat_ip_pair(
+            real_address='2001:db8:2::10/128',
+            nat_address='2001:db8:2::20/128',
+            inside_interface=interfaces[1],
+        )
+
+        vdc = VirtualDeviceContext.objects.create(
+            name='VDC NAT', identifier=98, device=device, status='active'
+        )
+        vdc.primary_ip4 = nat_ip4
+        vdc.primary_ip6 = real_ip6
+        vdc.save()
+
+        self.add_permissions('dcim.view_virtualdevicecontext', 'ipam.view_ipaddress')
+        response = self.client.get(self._get_detail_url(vdc), **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+
+        self.assertEqual(response.data['primary_ip4']['nat_inside']['address'], str(real_ip4.address))
+        self.assertEqual(response.data['primary_ip4']['nat_outside'], [])
+        self.assertIsNone(response.data['primary_ip6']['nat_inside'])
+        self.assertCountEqual(
+            [ip['address'] for ip in response.data['primary_ip6']['nat_outside']],
+            [str(nat_ip6.address)],
+        )
+
+    def test_get_object_includes_dns_name_on_primary_ip(self):
+        device = create_test_device('vdc-dns-device')
+        interfaces = (
+            Interface.objects.create(device=device, name='eth0', type='other'),
+            Interface.objects.create(device=device, name='eth1', type='other'),
+        )
+
+        ip4 = IPAddress(address='192.0.2.30/32', dns_name='vdc4.example.com')
+        ip4.assigned_object = interfaces[0]
+        ip4.save()
+        ip6 = IPAddress(address='2001:db8::30/128', dns_name='vdc6.example.com')
+        ip6.assigned_object = interfaces[1]
+        ip6.save()
+
+        vdc = VirtualDeviceContext.objects.create(
+            name='VDC DNS', identifier=99, device=device, status='active'
+        )
+        vdc.primary_ip4 = ip4
+        vdc.primary_ip6 = ip6
+        vdc.save()
+
+        self.add_permissions('dcim.view_virtualdevicecontext', 'ipam.view_ipaddress')
+        response = self.client.get(self._get_detail_url(vdc), **self.header)
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+
+        self.assertEqual(response.data['primary_ip4']['dns_name'], 'vdc4.example.com')
+        self.assertEqual(response.data['primary_ip6']['dns_name'], 'vdc6.example.com')
+        self.assertIn(
+            response.data['primary_ip']['dns_name'],
+            ('vdc4.example.com', 'vdc6.example.com'),
+        )
+
 
 
 class MACAddressTestCase(APIViewTestCases.APIViewTestCase):
 class MACAddressTestCase(APIViewTestCases.APIViewTestCase):
     model = MACAddress
     model = MACAddress

+ 3 - 3
netbox/virtualization/api/serializers_/virtualmachines.py

@@ -61,19 +61,19 @@ class VirtualMachineSerializer(PrimaryModelSerializer):
         nested=True,
         nested=True,
         read_only=True,
         read_only=True,
         allow_null=True,
         allow_null=True,
-        fields=[*IPAddressSerializer.Meta.brief_fields, 'nat_inside', 'nat_outside'],
+        fields=[*IPAddressSerializer.Meta.brief_fields, 'dns_name', 'nat_inside', 'nat_outside'],
     )
     )
     primary_ip4 = IPAddressSerializer(
     primary_ip4 = IPAddressSerializer(
         nested=True,
         nested=True,
         required=False,
         required=False,
         allow_null=True,
         allow_null=True,
-        fields=[*IPAddressSerializer.Meta.brief_fields, 'nat_inside', 'nat_outside'],
+        fields=[*IPAddressSerializer.Meta.brief_fields, 'dns_name', 'nat_inside', 'nat_outside'],
     )
     )
     primary_ip6 = IPAddressSerializer(
     primary_ip6 = IPAddressSerializer(
         nested=True,
         nested=True,
         required=False,
         required=False,
         allow_null=True,
         allow_null=True,
-        fields=[*IPAddressSerializer.Meta.brief_fields, 'nat_inside', 'nat_outside'],
+        fields=[*IPAddressSerializer.Meta.brief_fields, 'dns_name', 'nat_inside', 'nat_outside'],
     )
     )
     config_template = ConfigTemplateSerializer(nested=True, required=False, allow_null=True, default=None)
     config_template = ConfigTemplateSerializer(nested=True, required=False, allow_null=True, default=None)
 
 

+ 33 - 1
netbox/virtualization/tests/test_api.py

@@ -11,7 +11,7 @@ from dcim.models import Platform, Site
 from extras.choices import CustomFieldTypeChoices
 from extras.choices import CustomFieldTypeChoices
 from extras.models import ConfigTemplate, CustomField
 from extras.models import ConfigTemplate, CustomField
 from ipam.choices import VLANQinQRoleChoices
 from ipam.choices import VLANQinQRoleChoices
-from ipam.models import VLAN, VRF, Prefix
+from ipam.models import VLAN, VRF, IPAddress, Prefix
 from users.constants import TOKEN_PREFIX
 from users.constants import TOKEN_PREFIX
 from users.models import Token
 from users.models import Token
 from utilities.testing import (
 from utilities.testing import (
@@ -557,6 +557,38 @@ class VirtualMachineTestCase(APIViewTestCases.APIViewTestCase):
                 [str(nat_ip.address)],
                 [str(nat_ip.address)],
             )
             )
 
 
+    def test_get_object_includes_dns_name_on_primary_ip(self):
+        virtualmachine = create_test_virtualmachine('dns-vm')
+        interfaces = (
+            VMInterface.objects.create(virtual_machine=virtualmachine, name='eth0'),
+            VMInterface.objects.create(virtual_machine=virtualmachine, name='eth1'),
+        )
+
+        ip4 = IPAddress(address='192.0.2.40/32', dns_name='vm4.example.com')
+        ip4.assigned_object = interfaces[0]
+        ip4.save()
+        ip6 = IPAddress(address='2001:db8::40/128', dns_name='vm6.example.com')
+        ip6.assigned_object = interfaces[1]
+        ip6.save()
+
+        virtualmachine.primary_ip4 = ip4
+        virtualmachine.primary_ip6 = ip6
+        virtualmachine.save()
+
+        self.add_permissions('virtualization.view_virtualmachine', 'ipam.view_ipaddress')
+        response = self.client.get(
+            f'{self._get_detail_url(virtualmachine)}?exclude=config_context',
+            **self.header,
+        )
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+
+        self.assertEqual(response.data['primary_ip4']['dns_name'], 'vm4.example.com')
+        self.assertEqual(response.data['primary_ip6']['dns_name'], 'vm6.example.com')
+        self.assertIn(
+            response.data['primary_ip']['dns_name'],
+            ('vm4.example.com', 'vm6.example.com'),
+        )
+
     def test_render_config_with_config_template_id(self):
     def test_render_config_with_config_template_id(self):
         default_template = ConfigTemplate.objects.create(
         default_template = ConfigTemplate.objects.create(
             name='Default Template',
             name='Default Template',