1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

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

This commit is contained in:
Jeremy Stretch
2018-08-06 10:35:51 -04:00
parent bd5e860be0
commit 24520717e4
2 changed files with 184 additions and 9 deletions

View File

@ -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) form_factor = ChoiceField(choices=IFACE_FF_CHOICES, default=IFACE_FF_VIRTUAL, required=False)
untagged_vlan = InterfaceVLANSerializer() mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False)
tagged_vlans = InterfaceVLANSerializer(many=True) 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', 'id', 'virtual_machine', 'name', 'form_factor', 'enabled', 'mtu', 'mac_address', 'description', 'mode',
'description', 'untagged_vlan', 'tagged_vlans', 'tags',
] ]

View File

@ -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)