Browse Source

Ditched VMInterface in favor of reusing dcim.Interface

Jeremy Stretch 7 years ago
parent
commit
e9f75adddb

+ 6 - 4
netbox/dcim/constants.py

@@ -92,13 +92,15 @@ IFACE_FF_JUNIPER_VCP = 5200
 # Other
 IFACE_FF_OTHER = 32767
 
+VIFACE_FF_CHOICES = [
+    [IFACE_FF_VIRTUAL, 'Virtual'],
+    [IFACE_FF_LAG, 'Link Aggregation Group (LAG)'],
+]
+
 IFACE_FF_CHOICES = [
     [
         'Virtual interfaces',
-        [
-            [IFACE_FF_VIRTUAL, 'Virtual'],
-            [IFACE_FF_LAG, 'Link Aggregation Group (LAG)'],
-        ]
+        VIFACE_FF_CHOICES,
     ],
     [
         'Ethernet (fixed)',

+ 0 - 22
netbox/dcim/migrations/0042_device_cluster.py

@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11.4 on 2017-08-18 19:46
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('virtualization', '0001_initial'),
-        ('dcim', '0041_napalm_integration'),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name='device',
-            name='cluster',
-            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='devices', to='virtualization.Cluster'),
-        ),
-    ]

+ 32 - 0
netbox/dcim/migrations/0042_virtualization.py

@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.4 on 2017-08-29 17:49
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('virtualization', '0001_virtualization'),
+        ('dcim', '0041_napalm_integration'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='device',
+            name='cluster',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='devices', to='virtualization.Cluster'),
+        ),
+        migrations.AddField(
+            model_name='interface',
+            name='virtual_machine',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='virtualization.VirtualMachine'),
+        ),
+        migrations.AlterField(
+            model_name='interface',
+            name='device',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='dcim.Device'),
+        ),
+    ]

+ 30 - 10
netbox/dcim/models.py

@@ -1152,13 +1152,26 @@ class PowerOutlet(models.Model):
 @python_2_unicode_compatible
 class Interface(models.Model):
     """
-    A physical data interface within a Device. An Interface can connect to exactly one other Interface via the creation
-    of an InterfaceConnection.
+    A network interface within a Device or VirtualMachine. A physical Interface can connect to exactly one other
+    Interface via the creation of an InterfaceConnection.
     """
-    device = models.ForeignKey('Device', related_name='interfaces', on_delete=models.CASCADE)
+    device = models.ForeignKey(
+        to='Device',
+        on_delete=models.CASCADE,
+        related_name='interfaces',
+        null=True,
+        blank=True
+    )
+    virtual_machine = models.ForeignKey(
+        to='virtualization.VirtualMachine',
+        on_delete=models.CASCADE,
+        related_name='interfaces',
+        null=True,
+        blank=True
+    )
     lag = models.ForeignKey(
-        'self',
-        models.SET_NULL,
+        to='self',
+        on_delete=models.SET_NULL,
         related_name='member_interfaces',
         null=True,
         blank=True,
@@ -1175,11 +1188,6 @@ class Interface(models.Model):
         help_text="This interface is used only for out-of-band management"
     )
     description = models.CharField(max_length=100, blank=True)
-    ip_addresses = GenericRelation(
-        to='ipam.IPAddress',
-        content_type_field='interface_type',
-        object_id_field='interface_id'
-    )
 
     objects = InterfaceQuerySet.as_manager()
 
@@ -1192,6 +1200,18 @@ class Interface(models.Model):
 
     def clean(self):
 
+        # An Interface must belong to a Device *or* to a VirtualMachine
+        if self.device and self.virtual_machine:
+            raise ValidationError("An interface cannot belong to both a device and a virtual machine.")
+        if not self.device and not self.virtual_machine:
+            raise ValidationError("An interface must belong to either a device or a virtual machine.")
+
+        # VM interfaces must be virtual
+        if self.virtual_machine and self.form_factor not in VIRTUAL_IFACE_TYPES:
+            raise ValidationError({
+                'form_factor': "Virtual machines cannot have physical interfaces."
+            })
+
         # Virtual interfaces cannot be connected
         if self.form_factor in NONCONNECTABLE_IFACE_TYPES and self.is_connected:
             raise ValidationError({

+ 0 - 44
netbox/ipam/migrations/0019_ipaddress_interface_to_gfk.py

@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11.4 on 2017-08-18 19:31
-from __future__ import unicode_literals
-
-from django.contrib.contenttypes.models import ContentType
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-def set_interface_type(apps, schema_editor):
-    """
-    Set the interface_type field to 'Interface' for all IP addresses assigned to an Interface.
-    """
-    Interface = apps.get_model('dcim', 'Interface')
-    interface_type = ContentType.objects.get_for_model(Interface)
-    IPAddress = apps.get_model('ipam', 'IPAddress')
-    IPAddress.objects.filter(interface__isnull=False).update(interface_type=interface_type)
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('contenttypes', '0002_remove_content_type_name'),
-        ('ipam', '0018_remove_service_uniqueness_constraint'),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name='ipaddress',
-            name='interface_type',
-            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='contenttypes.ContentType'),
-        ),
-        migrations.RunPython(set_interface_type),
-        migrations.AlterField(
-            model_name='IPAddress',
-            name='interface',
-            field=models.PositiveIntegerField(blank=True, null=True),
-        ),
-        migrations.RenameField(
-            model_name='IPAddress',
-            old_name='interface',
-            new_name='interface_id',
-        )
-    ]

+ 2 - 9
netbox/ipam/models.py

@@ -409,15 +409,8 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel):
     role = models.PositiveSmallIntegerField(
         'Role', choices=IPADDRESS_ROLE_CHOICES, blank=True, null=True, help_text='The functional role of this IP'
     )
-    interface_type = models.ForeignKey(
-        to=ContentType,
-        on_delete=models.PROTECT,
-        limit_choices_to=Q(app_label='dcim', model='interface') | Q(app_label='virtualization', model='vminterface'),
-        blank=True,
-        null=True
-    )
-    interface_id = models.PositiveIntegerField(blank=True, null=True)
-    interface = GenericForeignKey('interface_type', 'interface_id')
+    interface = models.ForeignKey(Interface, related_name='ip_addresses', on_delete=models.CASCADE, blank=True,
+                                  null=True)
     nat_inside = models.OneToOneField('self', related_name='nat_outside', on_delete=models.SET_NULL, blank=True,
                                       null=True, verbose_name='NAT (Inside)',
                                       help_text="The IP for which this address is the \"outside\" IP")

+ 5 - 5
netbox/templates/virtualization/inc/vminterface.html

@@ -1,5 +1,5 @@
 <tr class="interface{% if not iface.enabled %} danger{% endif %}">
-    {% if selectable and perms.virtualization.change_vminterface or perms.virtualization.delete_vminterface %}
+    {% if selectable and perms.dcim.change_interface or perms.dcim.delete_interface %}
         <td class="pk">
             <input name="pk" type="checkbox" value="{{ iface.pk }}" />
         </td>
@@ -13,13 +13,13 @@
     <td>{{ iface.mtu|default:"" }}</td>
     <td>{{ iface.mac_address|default:"" }}</td>
     <td class="text-right">
-        {% if perms.virtualization.change_vminterface %}
-            <a href="{% url 'virtualization:vminterface_edit' pk=iface.pk %}" class="btn btn-info btn-xs" title="Edit interface">
+        {% if perms.dcim.change_interface %}
+            <a href="{% url 'virtualization:interface_edit' pk=iface.pk %}" class="btn btn-info btn-xs" title="Edit interface">
                 <i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
             </a>
         {% endif %}
-        {% if perms.virtualization.delete_vminterface %}
-            <a href="{% url 'virtualization:vminterface_delete' pk=iface.pk %}" class="btn btn-danger btn-xs" title="Delete interface">
+        {% if perms.dcim.delete_interface %}
+            <a href="{% url 'virtualization:interface_delete' pk=iface.pk %}" class="btn btn-danger btn-xs" title="Delete interface">
                 <i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
             </a>
         {% endif %}

+ 13 - 13
netbox/templates/virtualization/virtualmachine.html

@@ -133,7 +133,7 @@
         </div>
     </div>
     <div class="col-md-7">
-        {% if perms.virtualization.change_vminterface or perms.virtualization.delete_vminterface %}
+        {% if perms.dcim.change_interface or perms.dcim.delete_interface %}
             <form method="post">
             {% csrf_token %}
             <input type="hidden" name="virtual_machine" value="{{ vm.pk }}" />
@@ -145,13 +145,13 @@
                     <button class="btn btn-default btn-xs toggle-ips" selected="selected">
                         <span class="glyphicon glyphicon-check" aria-hidden="true"></span> Show IPs
                     </button>
-                    {% if perms.virtualization.change_vminterface and interfaces|length > 1 %}
+                    {% if perms.dcim.change_interface and interfaces|length > 1 %}
                         <button class="btn btn-default btn-xs toggle">
                             <span class="glyphicon glyphicon-unchecked" aria-hidden="true"></span> Select all
                         </button>
                     {% endif %}
-                    {% if perms.virtualization.add_vminterface and interfaces|length > 10 %}
-                        <a href="{% url 'virtualization:vminterface_add' pk=vm.pk %}" class="btn btn-primary btn-xs">
+                    {% if perms.dcim.add_interface and interfaces|length > 10 %}
+                        <a href="{% url 'virtualization:interface_add' pk=vm.pk %}" class="btn btn-primary btn-xs">
                             <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add interfaces
                         </a>
                     {% endif %}
@@ -159,28 +159,28 @@
             </div>
             <table id="interfaces_table" class="table table-hover panel-body component-list">
                 {% for iface in interfaces %}
-                    {% include 'virtualization/inc/vminterface.html' with selectable=True %}
+                    {% include 'virtualization/inc/interface.html' with selectable=True %}
                 {% empty %}
                     <tr>
                         <td colspan="4">No interfaces defined</td>
                     </tr>
                 {% endfor %}
             </table>
-            {% if perms.virtualization.add_vminterface or perms.virtualization.delete_vminterface %}
+            {% if perms.dcim.add_interface or perms.dcim.delete_interface %}
                 <div class="panel-footer">
-                    {% if interfaces and perms.virtualization.change_vminterface %}
-                        <button type="submit" name="_edit" formaction="{% url 'virtualization:vminterface_bulk_edit' pk=vm.pk %}" class="btn btn-warning btn-xs">
+                    {% if interfaces and perms.dcim.change_interface %}
+                        <button type="submit" name="_edit" formaction="{% url 'virtualization:interface_bulk_edit' pk=vm.pk %}" class="btn btn-warning btn-xs">
                             <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit
                         </button>
                     {% endif %}
-                    {% if interfaces and perms.virtualization.delete_vminterface %}
-                        <button type="submit" name="_delete" formaction="{% url 'virtualization:vminterface_bulk_delete' pk=vm.pk %}" class="btn btn-danger btn-xs">
+                    {% if interfaces and perms.dcim.delete_interface %}
+                        <button type="submit" name="_delete" formaction="{% url 'virtualization:interface_bulk_delete' pk=vm.pk %}" class="btn btn-danger btn-xs">
                             <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete
                         </button>
                     {% endif %}
-                    {% if perms.virtualization.add_vminterface %}
+                    {% if perms.dcim.add_interface %}
                         <div class="pull-right">
-                            <a href="{% url 'virtualization:vminterface_add' pk=vm.pk %}" class="btn btn-primary btn-xs">
+                            <a href="{% url 'virtualization:interface_add' pk=vm.pk %}" class="btn btn-primary btn-xs">
                                 <span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add interfaces
                             </a>
                         </div>
@@ -189,7 +189,7 @@
                  </div>
             {% endif %}
         </div>
-        {% if perms.virtualization.delete_vminterface %}
+        {% if perms.dcim.delete_interface %}
             </form>
         {% endif %}
 	</div>

+ 15 - 12
netbox/virtualization/api/serializers.py

@@ -3,10 +3,12 @@ from __future__ import unicode_literals
 from rest_framework import serializers
 
 from dcim.api.serializers import NestedPlatformSerializer
+from dcim.constants import VIFACE_FF_CHOICES
+from dcim.models import Interface
 from extras.api.customfields import CustomFieldModelSerializer
 from tenancy.api.serializers import NestedTenantSerializer
-from utilities.api import ValidatedModelSerializer
-from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
+from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer
+from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 
 
 #
@@ -102,7 +104,7 @@ class NestedVirtualMachineSerializer(serializers.ModelSerializer):
 class WritableVirtualMachineSerializer(CustomFieldModelSerializer):
 
     class Meta:
-        model = Cluster
+        model = VirtualMachine
         fields = [
             'id', 'name', 'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'comments', 'custom_fields',
         ]
@@ -112,28 +114,29 @@ class WritableVirtualMachineSerializer(CustomFieldModelSerializer):
 # VM interfaces
 #
 
-class VMInterfaceSerializer(serializers.ModelSerializer):
+class InterfaceSerializer(serializers.ModelSerializer):
     virtual_machine = NestedVirtualMachineSerializer()
+    form_factor = ChoiceFieldSerializer(choices=VIFACE_FF_CHOICES)
 
     class Meta:
-        model = VMInterface
+        model = Interface
         fields = [
-            'id', 'name', 'virtual_machine', 'enabled', 'mac_address', 'mtu', 'description',
+            'id', 'name', 'virtual_machine', 'form_factor', 'enabled', 'mac_address', 'mtu', 'description',
         ]
 
 
-class NestedVMInterfaceSerializer(serializers.ModelSerializer):
-    url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:vminterface-detail')
+class NestedInterfaceSerializer(serializers.ModelSerializer):
+    url = serializers.HyperlinkedIdentityField(view_name='virtualization-api:interface-detail')
 
     class Meta:
-        model = VMInterface
+        model = Interface
         fields = ['id', 'url', 'name']
 
 
-class WritableVMInterfaceSerializer(ValidatedModelSerializer):
+class WritableInterfaceSerializer(ValidatedModelSerializer):
 
     class Meta:
-        model = VMInterface
+        model = Interface
         fields = [
-            'id', 'name', 'virtual_machine', 'enabled', 'mac_address', 'mtu', 'description',
+            'id', 'name', 'virtual_machine', 'form_factor', 'enabled', 'mac_address', 'mtu', 'description',
         ]

+ 1 - 1
netbox/virtualization/api/urls.py

@@ -23,7 +23,7 @@ router.register(r'clusters', views.ClusterViewSet)
 
 # VirtualMachines
 router.register(r'virtual-machines', views.VirtualMachineViewSet)
-router.register(r'vm-interfaces', views.VMInterfaceViewSet)
+router.register(r'interfaces', views.InterfaceViewSet)
 
 app_name = 'virtualization-api'
 urlpatterns = router.urls

+ 6 - 5
netbox/virtualization/api/views.py

@@ -2,10 +2,11 @@ from __future__ import unicode_literals
 
 from rest_framework.viewsets import ModelViewSet
 
+from dcim.models import Interface
 from extras.api.views import CustomFieldModelViewSet
 from utilities.api import WritableSerializerMixin
 from virtualization import filters
-from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
+from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 from . import serializers
 
 
@@ -41,7 +42,7 @@ class VirtualMachineViewSet(WritableSerializerMixin, CustomFieldModelViewSet):
     filter_class = filters.VirtualMachineFilter
 
 
-class VMInterfaceViewSet(WritableSerializerMixin, ModelViewSet):
-    queryset = VMInterface.objects.select_related('virtual_machine')
-    serializer_class = serializers.VMInterfaceSerializer
-    write_serializer_class = serializers.WritableVMInterfaceSerializer
+class InterfaceViewSet(WritableSerializerMixin, ModelViewSet):
+    queryset = Interface.objects.filter(virtual_machine__isnull=False).select_related('virtual_machine')
+    serializer_class = serializers.InterfaceSerializer
+    write_serializer_class = serializers.WritableInterfaceSerializer

+ 11 - 9
netbox/virtualization/forms.py

@@ -5,8 +5,9 @@ from mptt.forms import TreeNodeChoiceField
 from django import forms
 from django.db.models import Count
 
+from dcim.constants import VIFACE_FF_CHOICES
 from dcim.formfields import MACAddressFormField
-from dcim.models import Device, Rack, Region, Site
+from dcim.models import Device, Interface, Rack, Region, Site
 from extras.forms import CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm
 from tenancy.forms import TenancyForm
 from tenancy.models import Tenant
@@ -15,7 +16,7 @@ from utilities.forms import (
     ChainedModelChoiceField, ChainedModelMultipleChoiceField, ComponentForm, ConfirmationForm, ExpandableNameField,
     FilterChoiceField, SlugField,
 )
-from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
+from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 
 
 #
@@ -230,18 +231,19 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
 # VM interfaces
 #
 
-class VMInterfaceForm(BootstrapMixin, forms.ModelForm):
+class InterfaceForm(BootstrapMixin, forms.ModelForm):
 
     class Meta:
-        model = VMInterface
-        fields = ['virtual_machine', 'name', 'enabled', 'mac_address', 'mtu', 'description']
+        model = Interface
+        fields = ['virtual_machine', 'name', 'form_factor', 'enabled', 'mac_address', 'mtu', 'description']
         widgets = {
             'virtual_machine': forms.HiddenInput(),
         }
 
 
-class VMInterfaceCreateForm(ComponentForm):
+class InterfaceCreateForm(ComponentForm):
     name_pattern = ExpandableNameField(label='Name')
+    form_factor = forms.ChoiceField(choices=VIFACE_FF_CHOICES)
     enabled = forms.BooleanField(required=False)
     mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')
     mac_address = MACAddressFormField(required=False, label='MAC Address')
@@ -253,11 +255,11 @@ class VMInterfaceCreateForm(ComponentForm):
         kwargs['initial'] = kwargs.get('initial', {}).copy()
         kwargs['initial'].update({'enabled': True})
 
-        super(VMInterfaceCreateForm, self).__init__(*args, **kwargs)
+        super(InterfaceCreateForm, self).__init__(*args, **kwargs)
 
 
-class VMInterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
-    pk = forms.ModelMultipleChoiceField(queryset=VMInterface.objects.all(), widget=forms.MultipleHiddenInput)
+class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
+    pk = forms.ModelMultipleChoiceField(queryset=Interface.objects.all(), widget=forms.MultipleHiddenInput)
     virtual_machine = forms.ModelChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.HiddenInput)
     enabled = forms.NullBooleanField(required=False, widget=BulkEditNullBooleanSelect)
     mtu = forms.IntegerField(required=False, min_value=1, max_value=32767, label='MTU')

+ 3 - 24
netbox/virtualization/migrations/0001_initial.py

@@ -1,8 +1,7 @@
 # -*- coding: utf-8 -*-
-# Generated by Django 1.11.4 on 2017-08-18 19:46
+# Generated by Django 1.11.4 on 2017-08-29 17:49
 from __future__ import unicode_literals
 
-import dcim.fields
 from django.db import migrations, models
 import django.db.models.deletion
 import extras.models
@@ -13,9 +12,9 @@ class Migration(migrations.Migration):
     initial = True
 
     dependencies = [
-        ('dcim', '0041_napalm_integration'),
-        ('ipam', '0019_ipaddress_interface_to_gfk'),
         ('tenancy', '0003_unicode_literals'),
+        ('ipam', '0018_remove_service_uniqueness_constraint'),
+        ('dcim', '0041_napalm_integration'),
     ]
 
     operations = [
@@ -77,22 +76,6 @@ class Migration(migrations.Migration):
             },
             bases=(models.Model, extras.models.CustomFieldModel),
         ),
-        migrations.CreateModel(
-            name='VMInterface',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('name', models.CharField(max_length=30)),
-                ('enabled', models.BooleanField(default=True)),
-                ('mac_address', dcim.fields.MACAddressField(blank=True, null=True, verbose_name='MAC Address')),
-                ('mtu', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='MTU')),
-                ('description', models.CharField(blank=True, max_length=100)),
-                ('virtual_machine', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='virtualization.VirtualMachine')),
-            ],
-            options={
-                'verbose_name': 'VM interface',
-                'ordering': ['virtual_machine', 'name'],
-            },
-        ),
         migrations.AddField(
             model_name='cluster',
             name='group',
@@ -103,8 +86,4 @@ class Migration(migrations.Migration):
             name='type',
             field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='clusters', to='virtualization.ClusterType'),
         ),
-        migrations.AlterUniqueTogether(
-            name='vminterface',
-            unique_together=set([('virtual_machine', 'name')]),
-        ),
     ]

+ 0 - 46
netbox/virtualization/models.py

@@ -188,49 +188,3 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel):
 
     def get_absolute_url(self):
         return reverse('virtualization:virtualmachine', args=[self.pk])
-
-
-@python_2_unicode_compatible
-class VMInterface(models.Model):
-    """
-    A virtual interface which belongs to a VirtualMachine. Like the dcim.Interface model, IPAddresses can be assigned to
-    VMInterfaces.
-    """
-    virtual_machine = models.ForeignKey(
-        to=VirtualMachine,
-        on_delete=models.CASCADE,
-        related_name='interfaces'
-    )
-    name = models.CharField(
-        max_length=30
-    )
-    enabled = models.BooleanField(
-        default=True
-    )
-    mac_address = MACAddressField(
-        blank=True,
-        null=True,
-        verbose_name='MAC Address'
-    )
-    mtu = models.PositiveSmallIntegerField(
-        blank=True,
-        null=True,
-        verbose_name='MTU'
-    )
-    description = models.CharField(
-        max_length=100,
-        blank=True
-    )
-    ip_addresses = GenericRelation(
-        to='ipam.IPAddress',
-        content_type_field='interface_type',
-        object_id_field='interface_id'
-    )
-
-    class Meta:
-        ordering = ['virtual_machine', 'name']
-        unique_together = ['virtual_machine', 'name']
-        verbose_name = 'VM interface'
-
-    def __str__(self):
-        return self.name

+ 4 - 3
netbox/virtualization/tables.py

@@ -3,8 +3,9 @@ from __future__ import unicode_literals
 import django_tables2 as tables
 from django_tables2.utils import Accessor
 
+from dcim.models import Interface
 from utilities.tables import BaseTable, ToggleColumn
-from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
+from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 
 
 CLUSTERTYPE_ACTIONS = """
@@ -89,8 +90,8 @@ class VirtualMachineTable(BaseTable):
 # VM components
 #
 
-class VMInterfaceTable(BaseTable):
+class InterfaceTable(BaseTable):
 
     class Meta(BaseTable.Meta):
-        model = VMInterface
+        model = Interface
         fields = ('name', 'enabled', 'description')

+ 6 - 6
netbox/virtualization/urls.py

@@ -41,11 +41,11 @@ urlpatterns = [
     url(r'^virtual-machines/(?P<pk>\d+)/delete/$', views.VirtualMachineDeleteView.as_view(), name='virtualmachine_delete'),
 
     # VM interfaces
-    # url(r'^virtual-machines/interfaces/add/$', views.VMBulkAddVMInterfaceView.as_view(), name='vm_bulk_add_vminterface'),
-    url(r'^virtual-machines/(?P<pk>\d+)/interfaces/add/$', views.VMInterfaceCreateView.as_view(), name='vminterface_add'),
-    url(r'^virtual-machines/(?P<pk>\d+)/interfaces/edit/$', views.VMInterfaceBulkEditView.as_view(), name='vminterface_bulk_edit'),
-    url(r'^virtual-machines/(?P<pk>\d+)/interfaces/delete/$', views.VMInterfaceBulkDeleteView.as_view(), name='vminterface_bulk_delete'),
-    url(r'^vm-interfaces/(?P<pk>\d+)/edit/$', views.VMInterfaceEditView.as_view(), name='vminterface_edit'),
-    url(r'^vm-interfaces/(?P<pk>\d+)/delete/$', views.VMInterfaceDeleteView.as_view(), name='vminterface_delete'),
+    # url(r'^virtual-machines/interfaces/add/$', views.VMBulkAddInterfaceView.as_view(), name='vm_bulk_add_interface'),
+    url(r'^virtual-machines/(?P<pk>\d+)/interfaces/add/$', views.InterfaceCreateView.as_view(), name='interface_add'),
+    url(r'^virtual-machines/(?P<pk>\d+)/interfaces/edit/$', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'),
+    url(r'^virtual-machines/(?P<pk>\d+)/interfaces/delete/$', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'),
+    url(r'^vm-interfaces/(?P<pk>\d+)/edit/$', views.InterfaceEditView.as_view(), name='interface_edit'),
+    url(r'^vm-interfaces/(?P<pk>\d+)/delete/$', views.InterfaceDeleteView.as_view(), name='interface_delete'),
 
 ]

+ 24 - 24
netbox/virtualization/views.py

@@ -7,13 +7,13 @@ from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse
 from django.views.generic import View
 
-from dcim.models import Device
+from dcim.models import Device, Interface
 from dcim.tables import DeviceTable
 from utilities.views import (
     BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, ComponentDeleteView, ComponentEditView,
     ObjectDeleteView, ObjectEditView, ObjectListView,
 )
-from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
+from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 from . import filters
 from . import forms
 from . import tables
@@ -235,7 +235,7 @@ class VirtualMachineView(View):
     def get(self, request, pk):
 
         vm = get_object_or_404(VirtualMachine.objects.select_related('tenant__group'), pk=pk)
-        interfaces = VMInterface.objects.filter(virtual_machine=vm)
+        interfaces = Interface.objects.filter(virtual_machine=vm)
 
         return render(request, 'virtualization/virtualmachine.html', {
             'vm': vm,
@@ -282,39 +282,39 @@ class VirtualMachineBulkEditView(PermissionRequiredMixin, BulkEditView):
 # VM interfaces
 #
 
-class VMInterfaceCreateView(PermissionRequiredMixin, ComponentCreateView):
-    permission_required = 'virtualization.add_vminterface'
+class InterfaceCreateView(PermissionRequiredMixin, ComponentCreateView):
+    permission_required = 'dcim.add_interface'
     parent_model = VirtualMachine
     parent_field = 'virtual_machine'
-    model = VMInterface
-    form = forms.VMInterfaceCreateForm
-    model_form = forms.VMInterfaceForm
+    model = Interface
+    form = forms.InterfaceCreateForm
+    model_form = forms.InterfaceForm
     template_name = 'virtualization/virtualmachine_component_add.html'
 
 
-class VMInterfaceEditView(PermissionRequiredMixin, ComponentEditView):
-    permission_required = 'virtualization.change_vminterface'
-    model = VMInterface
+class InterfaceEditView(PermissionRequiredMixin, ComponentEditView):
+    permission_required = 'dcim.change_interface'
+    model = Interface
     parent_field = 'virtual_machine'
-    form_class = forms.VMInterfaceForm
+    form_class = forms.InterfaceForm
 
 
-class VMInterfaceDeleteView(PermissionRequiredMixin, ComponentDeleteView):
-    permission_required = 'virtualization.delete_vminterface'
-    model = VMInterface
+class InterfaceDeleteView(PermissionRequiredMixin, ComponentDeleteView):
+    permission_required = 'dcim.delete_interface'
+    model = Interface
     parent_field = 'virtual_machine'
 
 
-class VMInterfaceBulkEditView(PermissionRequiredMixin, BulkEditView):
-    permission_required = 'virtualization.change_vminterface'
-    cls = VMInterface
+class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView):
+    permission_required = 'dcim.change_interface'
+    cls = Interface
     parent_cls = VirtualMachine
-    table = tables.VMInterfaceTable
-    form = forms.VMInterfaceBulkEditForm
+    table = tables.InterfaceTable
+    form = forms.InterfaceBulkEditForm
 
 
-class VMInterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
-    permission_required = 'virtualization.delete_vminterface'
-    cls = VMInterface
+class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
+    permission_required = 'dcim.delete_interface'
+    cls = Interface
     parent_cls = VirtualMachine
-    table = tables.VMInterfaceTable
+    table = tables.InterfaceTable