Initial work on #93: Primary IPv4/IPv6 support

This commit is contained in:
Jeremy Stretch
2016-07-11 16:24:46 -04:00
parent daaf3df6df
commit 4f2374f04d
16 changed files with 203 additions and 45 deletions

View File

@@ -221,12 +221,14 @@ class DeviceSerializer(serializers.ModelSerializer):
platform = PlatformNestedSerializer()
rack = RackNestedSerializer()
primary_ip = DeviceIPAddressNestedSerializer()
primary_ip4 = DeviceIPAddressNestedSerializer()
primary_ip6 = DeviceIPAddressNestedSerializer()
parent_device = serializers.SerializerMethodField()
class Meta:
model = Device
fields = ['id', 'name', 'display_name', 'device_type', 'device_role', 'platform', 'serial', 'rack', 'position',
'face', 'parent_device', 'status', 'primary_ip', 'comments']
'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6', 'comments']
def get_parent_device(self, obj):
try:

View File

@@ -194,7 +194,7 @@ class DeviceListView(generics.ListAPIView):
List devices (filterable)
"""
queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'platform', 'rack__site')\
.prefetch_related('primary_ip__nat_outside')
.prefetch_related('primary_ip4__nat_outside', 'primary_ip6__nat_outside')
serializer_class = serializers.DeviceSerializer
filter_class = filters.DeviceFilter
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [BINDZoneRenderer, FlatJSONRenderer]

View File

@@ -1919,7 +1919,8 @@
"position": 1,
"face": 0,
"status": true,
"primary_ip": 1,
"primary_ip4": 1,
"primary_ip6": null,
"comments": ""
}
},
@@ -1938,7 +1939,8 @@
"position": 17,
"face": 0,
"status": true,
"primary_ip": 5,
"primary_ip4": 5,
"primary_ip6": null,
"comments": ""
}
},
@@ -1957,7 +1959,8 @@
"position": 33,
"face": 0,
"status": true,
"primary_ip": null,
"primary_ip4": null,
"primary_ip6": null,
"comments": ""
}
},
@@ -1976,7 +1979,8 @@
"position": 34,
"face": 0,
"status": true,
"primary_ip": null,
"primary_ip4": null,
"primary_ip6": null,
"comments": ""
}
},
@@ -1995,7 +1999,8 @@
"position": 34,
"face": 0,
"status": true,
"primary_ip": null,
"primary_ip4": null,
"primary_ip6": null,
"comments": ""
}
},
@@ -2014,7 +2019,8 @@
"position": 33,
"face": 0,
"status": true,
"primary_ip": null,
"primary_ip4": null,
"primary_ip6": null,
"comments": ""
}
},
@@ -2033,7 +2039,8 @@
"position": 1,
"face": 0,
"status": true,
"primary_ip": 3,
"primary_ip4": 3,
"primary_ip6": null,
"comments": ""
}
},
@@ -2052,7 +2059,8 @@
"position": 17,
"face": 0,
"status": true,
"primary_ip": 19,
"primary_ip4": 19,
"primary_ip6": null,
"comments": ""
}
},
@@ -2071,7 +2079,8 @@
"position": 42,
"face": 0,
"status": true,
"primary_ip": null,
"primary_ip4": null,
"primary_ip6": null,
"comments": ""
}
},
@@ -2090,7 +2099,8 @@
"position": null,
"face": null,
"status": true,
"primary_ip": null,
"primary_ip4": null,
"primary_ip6": null,
"comments": ""
}
},
@@ -2109,7 +2119,8 @@
"position": null,
"face": null,
"status": true,
"primary_ip": null,
"primary_ip4": null,
"primary_ip6": null,
"comments": ""
}
},

View File

@@ -349,7 +349,7 @@ class DeviceForm(forms.ModelForm, BootstrapMixin):
class Meta:
model = Device
fields = ['name', 'device_role', 'device_type', 'serial', 'site', 'rack', 'position', 'face', 'status',
'platform', 'primary_ip', 'comments']
'platform', 'primary_ip4', 'primary_ip6', 'comments']
help_texts = {
'device_role': "The function this device serves",
'serial': "Chassis serial number",
@@ -369,20 +369,23 @@ class DeviceForm(forms.ModelForm, BootstrapMixin):
self.initial['site'] = self.instance.rack.site
self.initial['manufacturer'] = self.instance.device_type.manufacturer
# Compile list of IPs assigned to this device
primary_ip_choices = []
interface_ips = IPAddress.objects.filter(interface__device=self.instance)
primary_ip_choices += [(ip.id, '{} ({})'.format(ip.address, ip.interface)) for ip in interface_ips]
nat_ips = IPAddress.objects.filter(nat_inside__interface__device=self.instance)\
.select_related('nat_inside__interface')
primary_ip_choices += [(ip.id, '{} ({} NAT)'.format(ip.address, ip.nat_inside.interface)) for ip in nat_ips]
self.fields['primary_ip'].choices = [(None, '---------')] + primary_ip_choices
# Compile list of choices for primary IPv4 and IPv6 addresses
for family in [4, 6]:
ip_choices = []
interface_ips = IPAddress.objects.filter(family=family, interface__device=self.instance)
ip_choices += [(ip.id, '{} ({})'.format(ip.address, ip.interface)) for ip in interface_ips]
nat_ips = IPAddress.objects.filter(family=family, nat_inside__interface__device=self.instance)\
.select_related('nat_inside__interface')
ip_choices += [(ip.id, '{} ({} NAT)'.format(ip.address, ip.nat_inside.interface)) for ip in nat_ips]
self.fields['primary_ip{}'.format(family)].choices = [(None, '---------')] + ip_choices
else:
# An object that doesn't exist yet can't have any IPs assigned to it
self.fields['primary_ip'].choices = []
self.fields['primary_ip'].widget.attrs['readonly'] = True
self.fields['primary_ip4'].choices = []
self.fields['primary_ip4'].widget.attrs['readonly'] = True
self.fields['primary_ip6'].choices = []
self.fields['primary_ip6'].widget.attrs['readonly'] = True
# Limit rack choices
if self.is_bound:

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-11 18:40
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('ipam', '0001_initial'),
('dcim', '0005_auto_20160706_1722'),
]
operations = [
migrations.AddField(
model_name='device',
name='primary_ip4',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip4_for', to='ipam.IPAddress', verbose_name=b'Primary IPv4'),
),
migrations.AddField(
model_name='device',
name='primary_ip6',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip6_for', to='ipam.IPAddress', verbose_name=b'Primary IPv6'),
),
]

View File

@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-11 18:40
from __future__ import unicode_literals
from django.db import migrations
def copy_primary_ip(apps, schema_editor):
Device = apps.get_model('dcim', 'Device')
for d in Device.objects.select_related('primary_ip'):
if not d.primary_ip:
continue
if d.primary_ip.family == 4:
d.primary_ip4 = d.primary_ip
elif d.primary_ip.family == 6:
d.primary_ip6 = d.primary_ip
d.save()
def restore_primary_ip(apps, schema_editor):
Device = apps.get_model('dcim', 'Device')
for d in Device.objects.select_related('primary_ip4', 'primary_ip6'):
if d.primary_ip:
continue
# Prefer IPv6 over IPv4
if d.primary_ip6:
d.primary_ip = d.primary_ip6
elif d.primary_ip4:
d.primary_ip = d.primary_ip4
d.save()
class Migration(migrations.Migration):
dependencies = [
('dcim', '0006_add_device_primary_ip4_ip6'),
]
operations = [
migrations.RunPython(copy_primary_ip, restore_primary_ip),
]

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-07-11 19:01
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('dcim', '0007_device_copy_primary_ip'),
]
operations = [
migrations.RemoveField(
model_name='device',
name='primary_ip',
),
]

View File

@@ -605,8 +605,10 @@ class Device(CreatedUpdatedModel):
help_text='Number of the lowest U position occupied by the device')
face = models.PositiveSmallIntegerField(blank=True, null=True, choices=RACK_FACE_CHOICES, verbose_name='Rack face')
status = models.BooleanField(choices=STATUS_CHOICES, default=STATUS_ACTIVE, verbose_name='Status')
primary_ip = models.OneToOneField('ipam.IPAddress', related_name='primary_for', on_delete=models.SET_NULL,
blank=True, null=True, verbose_name='Primary IP')
primary_ip4 = models.OneToOneField('ipam.IPAddress', related_name='primary_ip4_for', on_delete=models.SET_NULL,
blank=True, null=True, verbose_name='Primary IPv4')
primary_ip6 = models.OneToOneField('ipam.IPAddress', related_name='primary_ip6_for', on_delete=models.SET_NULL,
blank=True, null=True, verbose_name='Primary IPv6')
comments = models.TextField(blank=True)
class Meta:
@@ -709,6 +711,15 @@ class Device(CreatedUpdatedModel):
return self.name
return '{{{}}}'.format(self.pk)
@property
def primary_ip(self):
if self.primary_ip6:
return self.primary_ip6
elif self.primary_ip4:
return self.primary_ip4
else:
return None
def get_children(self):
"""
Return the set of child Devices installed in DeviceBays within this Device.

View File

@@ -318,6 +318,8 @@ class DeviceTest(APITestCase):
'parent_device',
'status',
'primary_ip',
'primary_ip4',
'primary_ip6',
'comments',
]
@@ -375,6 +377,10 @@ class DeviceTest(APITestCase):
'primary_ip_address',
'primary_ip_family',
'primary_ip_id',
'primary_ip4_address',
'primary_ip4_family',
'primary_ip4_id',
'primary_ip6',
'rack_display_name',
'rack_facility_id',
'rack_id',

View File

@@ -501,7 +501,8 @@ class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
#
class DeviceListView(ObjectListView):
queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'rack__site', 'primary_ip')
queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'rack__site', 'primary_ip4',
'primary_ip6')
filter = filters.DeviceFilter
filter_form = forms.DeviceFilterForm
table = tables.DeviceTable
@@ -1634,7 +1635,10 @@ def ipaddress_assign(request, pk):
ipaddress.interface))
if form.cleaned_data['set_as_primary']:
device.primary_ip = ipaddress
if ipaddress.family == 4:
device.primary_ip4 = ipaddress
elif ipaddress.family == 6:
device.primary_ip6 = ipaddress
device.save()
if '_addanother' in request.POST: