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

Ditched VMInterface in favor of reusing dcim.Interface

This commit is contained in:
Jeremy Stretch
2017-08-29 14:24:58 -04:00
parent 6ebd8e78c8
commit e9f75adddb
17 changed files with 158 additions and 237 deletions

View File

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

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -1152,13 +1152,26 @@ class PowerOutlet(models.Model):
@python_2_unicode_compatible @python_2_unicode_compatible
class Interface(models.Model): class Interface(models.Model):
""" """
A physical data interface within a Device. An Interface can connect to exactly one other Interface via the creation A network interface within a Device or VirtualMachine. A physical Interface can connect to exactly one other
of an InterfaceConnection. 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( lag = models.ForeignKey(
'self', to='self',
models.SET_NULL, on_delete=models.SET_NULL,
related_name='member_interfaces', related_name='member_interfaces',
null=True, null=True,
blank=True, blank=True,
@ -1175,11 +1188,6 @@ class Interface(models.Model):
help_text="This interface is used only for out-of-band management" help_text="This interface is used only for out-of-band management"
) )
description = models.CharField(max_length=100, blank=True) 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() objects = InterfaceQuerySet.as_manager()
@ -1192,6 +1200,18 @@ class Interface(models.Model):
def clean(self): 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 # Virtual interfaces cannot be connected
if self.form_factor in NONCONNECTABLE_IFACE_TYPES and self.is_connected: if self.form_factor in NONCONNECTABLE_IFACE_TYPES and self.is_connected:
raise ValidationError({ raise ValidationError({

View File

@ -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',
)
]

View File

@ -409,15 +409,8 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel):
role = models.PositiveSmallIntegerField( role = models.PositiveSmallIntegerField(
'Role', choices=IPADDRESS_ROLE_CHOICES, blank=True, null=True, help_text='The functional role of this IP' 'Role', choices=IPADDRESS_ROLE_CHOICES, blank=True, null=True, help_text='The functional role of this IP'
) )
interface_type = models.ForeignKey( interface = models.ForeignKey(Interface, related_name='ip_addresses', on_delete=models.CASCADE, blank=True,
to=ContentType, null=True)
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')
nat_inside = models.OneToOneField('self', related_name='nat_outside', on_delete=models.SET_NULL, blank=True, nat_inside = models.OneToOneField('self', related_name='nat_outside', on_delete=models.SET_NULL, blank=True,
null=True, verbose_name='NAT (Inside)', null=True, verbose_name='NAT (Inside)',
help_text="The IP for which this address is the \"outside\" IP") help_text="The IP for which this address is the \"outside\" IP")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,7 @@
# -*- coding: utf-8 -*- # -*- 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 from __future__ import unicode_literals
import dcim.fields
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import extras.models import extras.models
@ -13,9 +12,9 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('dcim', '0041_napalm_integration'),
('ipam', '0019_ipaddress_interface_to_gfk'),
('tenancy', '0003_unicode_literals'), ('tenancy', '0003_unicode_literals'),
('ipam', '0018_remove_service_uniqueness_constraint'),
('dcim', '0041_napalm_integration'),
] ]
operations = [ operations = [
@ -77,22 +76,6 @@ class Migration(migrations.Migration):
}, },
bases=(models.Model, extras.models.CustomFieldModel), 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( migrations.AddField(
model_name='cluster', model_name='cluster',
name='group', name='group',
@ -103,8 +86,4 @@ class Migration(migrations.Migration):
name='type', name='type',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='clusters', to='virtualization.ClusterType'), 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')]),
),
] ]

View File

@ -188,49 +188,3 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel):
def get_absolute_url(self): def get_absolute_url(self):
return reverse('virtualization:virtualmachine', args=[self.pk]) 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

View File

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

View File

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

View File

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