Quellcode durchsuchen

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

Martin Hauser vor 1 Woche
Ursprung
Commit
021b7c5f5b

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

@@ -62,25 +62,25 @@ class DeviceSerializer(PrimaryModelSerializer):
         nested=True,
         read_only=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(
         nested=True,
         required=False,
         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(
         nested=True,
         required=False,
         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(
         nested=True,
         required=False,
         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()
     cluster = ClusterSerializer(nested=True, required=False, allow_null=True)
@@ -148,9 +148,24 @@ class VirtualDeviceContextSerializer(PrimaryModelSerializer):
     device = DeviceSerializer(nested=True)
     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)
-    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)
 
     # Related object counts

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

@@ -12,7 +12,7 @@ from dcim.constants import *
 from dcim.models import *
 from extras.models import ConfigTemplate, Tag
 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 tenancy.models import Tenant
 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_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):
         default_template = ConfigTemplate.objects.create(
             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):
     model = MACAddress

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

@@ -61,19 +61,19 @@ class VirtualMachineSerializer(PrimaryModelSerializer):
         nested=True,
         read_only=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(
         nested=True,
         required=False,
         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(
         nested=True,
         required=False,
         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)
 

+ 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.models import ConfigTemplate, CustomField
 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.models import Token
 from utilities.testing import (
@@ -557,6 +557,38 @@ class VirtualMachineTestCase(APIViewTestCases.APIViewTestCase):
                 [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):
         default_template = ConfigTemplate.objects.create(
             name='Default Template',