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:
@ -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)',
|
||||||
|
@ -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
netbox/dcim/migrations/0042_virtualization.py
Normal file
32
netbox/dcim/migrations/0042_virtualization.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
@ -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({
|
||||||
|
@ -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',
|
|
||||||
)
|
|
||||||
]
|
|
@ -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")
|
||||||
|
@ -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 %}
|
@ -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>
|
||||||
|
@ -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',
|
||||||
]
|
]
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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')
|
||||||
|
@ -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')]),
|
|
||||||
),
|
|
||||||
]
|
]
|
@ -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
|
|
||||||
|
@ -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')
|
||||||
|
@ -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'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user