Просмотр исходного кода

Fixes #2305: Make VLAN fields optional when creating a VM interface via the API

Jeremy Stretch 7 лет назад
Родитель
Сommit
24520717e4
2 измененных файлов с 184 добавлено и 9 удалено
  1. 16 9
      netbox/virtualization/api/serializers.py
  2. 168 0
      netbox/virtualization/tests/test_api.py

+ 16 - 9
netbox/virtualization/api/serializers.py

@@ -4,12 +4,12 @@ from rest_framework import serializers
 from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
 from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
 
 
 from dcim.api.serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer
 from dcim.api.serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer
-from dcim.constants import IFACE_MODE_CHOICES
+from dcim.constants import IFACE_FF_CHOICES, IFACE_FF_VIRTUAL, IFACE_MODE_CHOICES
 from dcim.models import Interface
 from dcim.models import Interface
 from extras.api.customfields import CustomFieldModelSerializer
 from extras.api.customfields import CustomFieldModelSerializer
 from ipam.models import IPAddress, VLAN
 from ipam.models import IPAddress, VLAN
 from tenancy.api.serializers import NestedTenantSerializer
 from tenancy.api.serializers import NestedTenantSerializer
-from utilities.api import ChoiceField, ValidatedModelSerializer, WritableNestedSerializer
+from utilities.api import ChoiceField, SerializedPKRelatedField, ValidatedModelSerializer, WritableNestedSerializer
 from virtualization.constants import VM_STATUS_CHOICES
 from virtualization.constants import VM_STATUS_CHOICES
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 
 
@@ -135,7 +135,7 @@ class NestedVirtualMachineSerializer(WritableNestedSerializer):
 #
 #
 
 
 # Cannot import ipam.api.serializers.NestedVLANSerializer due to circular dependency
 # Cannot import ipam.api.serializers.NestedVLANSerializer due to circular dependency
-class InterfaceVLANSerializer(serializers.ModelSerializer):
+class InterfaceVLANSerializer(WritableNestedSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail')
     url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail')
 
 
     class Meta:
     class Meta:
@@ -143,17 +143,24 @@ class InterfaceVLANSerializer(serializers.ModelSerializer):
         fields = ['id', 'url', 'vid', 'name', 'display_name']
         fields = ['id', 'url', 'vid', 'name', 'display_name']
 
 
 
 
-class InterfaceSerializer(serializers.ModelSerializer):
+class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer):
     virtual_machine = NestedVirtualMachineSerializer()
     virtual_machine = NestedVirtualMachineSerializer()
-    mode = ChoiceField(choices=IFACE_MODE_CHOICES)
-    untagged_vlan = InterfaceVLANSerializer()
-    tagged_vlans = InterfaceVLANSerializer(many=True)
+    form_factor = ChoiceField(choices=IFACE_FF_CHOICES, default=IFACE_FF_VIRTUAL, required=False)
+    mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False)
+    untagged_vlan = InterfaceVLANSerializer(required=False, allow_null=True)
+    tagged_vlans = SerializedPKRelatedField(
+        queryset=VLAN.objects.all(),
+        serializer=InterfaceVLANSerializer,
+        required=False,
+        many=True
+    )
+    tags = TagListSerializerField(required=False)
 
 
     class Meta:
     class Meta:
         model = Interface
         model = Interface
         fields = [
         fields = [
-            'id', 'name', 'virtual_machine', 'enabled', 'mac_address', 'mtu', 'mode', 'untagged_vlan', 'tagged_vlans',
-            'description',
+            'id', 'virtual_machine', 'name', 'form_factor', 'enabled', 'mtu', 'mac_address', 'description', 'mode',
+            'untagged_vlan', 'tagged_vlans', 'tags',
         ]
         ]
 
 
 
 

+ 168 - 0
netbox/virtualization/tests/test_api.py

@@ -3,6 +3,9 @@ from __future__ import unicode_literals
 from django.urls import reverse
 from django.urls import reverse
 from rest_framework import status
 from rest_framework import status
 
 
+from dcim.constants import IFACE_FF_VIRTUAL, IFACE_MODE_TAGGED
+from dcim.models import Interface
+from ipam.models import VLAN
 from utilities.testing import APITestCase
 from utilities.testing import APITestCase
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 
 
@@ -390,3 +393,168 @@ class VirtualMachineTest(APITestCase):
 
 
         self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
         self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
         self.assertEqual(VirtualMachine.objects.count(), 2)
         self.assertEqual(VirtualMachine.objects.count(), 2)
+
+
+class InterfaceTest(APITestCase):
+
+    def setUp(self):
+
+        super(InterfaceTest, self).setUp()
+
+        clustertype = ClusterType.objects.create(name='Test Cluster Type 1', slug='test-cluster-type-1')
+        cluster = Cluster.objects.create(name='Test Cluster 1', type=clustertype)
+        self.virtualmachine = VirtualMachine.objects.create(cluster=cluster, name='Test VM 1')
+        self.interface1 = Interface.objects.create(
+            virtual_machine=self.virtualmachine,
+            name='Test Interface 1',
+            form_factor=IFACE_FF_VIRTUAL
+        )
+        self.interface2 = Interface.objects.create(
+            virtual_machine=self.virtualmachine,
+            name='Test Interface 2',
+            form_factor=IFACE_FF_VIRTUAL
+        )
+        self.interface3 = Interface.objects.create(
+            virtual_machine=self.virtualmachine,
+            name='Test Interface 3',
+            form_factor=IFACE_FF_VIRTUAL
+        )
+
+        self.vlan1 = VLAN.objects.create(name="Test VLAN 1", vid=1)
+        self.vlan2 = VLAN.objects.create(name="Test VLAN 2", vid=2)
+        self.vlan3 = VLAN.objects.create(name="Test VLAN 3", vid=3)
+
+    def test_get_interface(self):
+
+        url = reverse('virtualization-api:interface-detail', kwargs={'pk': self.interface1.pk})
+        response = self.client.get(url, **self.header)
+
+        self.assertEqual(response.data['name'], self.interface1.name)
+
+    def test_list_interfaces(self):
+
+        url = reverse('virtualization-api:interface-list')
+        response = self.client.get(url, **self.header)
+
+        self.assertEqual(response.data['count'], 3)
+
+    def test_create_interface(self):
+
+        data = {
+            'virtual_machine': self.virtualmachine.pk,
+            'name': 'Test Interface 4',
+        }
+
+        url = reverse('virtualization-api:interface-list')
+        response = self.client.post(url, data, format='json', **self.header)
+
+        self.assertHttpStatus(response, status.HTTP_201_CREATED)
+        self.assertEqual(Interface.objects.count(), 4)
+        interface4 = Interface.objects.get(pk=response.data['id'])
+        self.assertEqual(interface4.virtual_machine_id, data['virtual_machine'])
+        self.assertEqual(interface4.name, data['name'])
+
+    def test_create_interface_with_802_1q(self):
+
+        data = {
+            'virtual_machine': self.virtualmachine.pk,
+            'name': 'Test Interface 4',
+            'mode': IFACE_MODE_TAGGED,
+            'untagged_vlan': self.vlan3.id,
+            'tagged_vlans': [self.vlan1.id, self.vlan2.id],
+        }
+
+        url = reverse('virtualization-api:interface-list')
+        response = self.client.post(url, data, format='json', **self.header)
+
+        self.assertHttpStatus(response, status.HTTP_201_CREATED)
+        self.assertEqual(Interface.objects.count(), 4)
+        self.assertEqual(response.data['virtual_machine']['id'], data['virtual_machine'])
+        self.assertEqual(response.data['name'], data['name'])
+        self.assertEqual(response.data['untagged_vlan']['id'], data['untagged_vlan'])
+        self.assertEqual([v['id'] for v in response.data['tagged_vlans']], data['tagged_vlans'])
+
+    def test_create_interface_bulk(self):
+
+        data = [
+            {
+                'virtual_machine': self.virtualmachine.pk,
+                'name': 'Test Interface 4',
+            },
+            {
+                'virtual_machine': self.virtualmachine.pk,
+                'name': 'Test Interface 5',
+            },
+            {
+                'virtual_machine': self.virtualmachine.pk,
+                'name': 'Test Interface 6',
+            },
+        ]
+
+        url = reverse('virtualization-api:interface-list')
+        response = self.client.post(url, data, format='json', **self.header)
+
+        self.assertHttpStatus(response, status.HTTP_201_CREATED)
+        self.assertEqual(Interface.objects.count(), 6)
+        self.assertEqual(response.data[0]['name'], data[0]['name'])
+        self.assertEqual(response.data[1]['name'], data[1]['name'])
+        self.assertEqual(response.data[2]['name'], data[2]['name'])
+
+    def test_create_interface_802_1q_bulk(self):
+
+        data = [
+            {
+                'virtual_machine': self.virtualmachine.pk,
+                'name': 'Test Interface 4',
+                'mode': IFACE_MODE_TAGGED,
+                'untagged_vlan': self.vlan2.id,
+                'tagged_vlans': [self.vlan1.id],
+            },
+            {
+                'virtual_machine': self.virtualmachine.pk,
+                'name': 'Test Interface 5',
+                'mode': IFACE_MODE_TAGGED,
+                'untagged_vlan': self.vlan2.id,
+                'tagged_vlans': [self.vlan1.id],
+            },
+            {
+                'virtual_machine': self.virtualmachine.pk,
+                'name': 'Test Interface 6',
+                'mode': IFACE_MODE_TAGGED,
+                'untagged_vlan': self.vlan2.id,
+                'tagged_vlans': [self.vlan1.id],
+            },
+        ]
+
+        url = reverse('virtualization-api:interface-list')
+        response = self.client.post(url, data, format='json', **self.header)
+
+        self.assertHttpStatus(response, status.HTTP_201_CREATED)
+        self.assertEqual(Interface.objects.count(), 6)
+        for i in range(0, 3):
+            self.assertEqual(response.data[i]['name'], data[i]['name'])
+            self.assertEqual([v['id'] for v in response.data[i]['tagged_vlans']], data[i]['tagged_vlans'])
+            self.assertEqual(response.data[i]['untagged_vlan']['id'], data[i]['untagged_vlan'])
+
+    def test_update_interface(self):
+
+        data = {
+            'virtual_machine': self.virtualmachine.pk,
+            'name': 'Test Interface X',
+        }
+
+        url = reverse('virtualization-api:interface-detail', kwargs={'pk': self.interface1.pk})
+        response = self.client.put(url, data, format='json', **self.header)
+
+        self.assertHttpStatus(response, status.HTTP_200_OK)
+        self.assertEqual(Interface.objects.count(), 3)
+        interface1 = Interface.objects.get(pk=response.data['id'])
+        self.assertEqual(interface1.name, data['name'])
+
+    def test_delete_interface(self):
+
+        url = reverse('dcim-api:interface-detail', kwargs={'pk': self.interface1.pk})
+        response = self.client.delete(url, **self.header)
+
+        self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
+        self.assertEqual(Interface.objects.count(), 2)